diff options
author | Danglewood <85772166+deeleeramone@users.noreply.github.com> | 2024-02-15 04:30:16 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-15 12:30:16 +0000 |
commit | 07e98ab58b029b76762d2d5acb3ef537d33c864a (patch) | |
tree | b0248697e6b90c3e8a3e2ea60f47a47ca090394b | |
parent | 7deb4e69f646e9212e65eceec9762d9b964516c3 (diff) |
[Feature] Add Regional Data Endpoint from FRED (#6071)
* add regional data endpoint from FRED
* codespell
* ruff
* sort imports
* add provder='fred' to integration test
* thought I already sorted those imports..
* pylint..
* more test params....
* even more test params...
* even more test params....
* static assets
* reconfigure params
* ruff
* alias dict
* recapture cassette
* router doctstring
---------
Co-authored-by: Igor Radovanovic <74266147+IgorWounds@users.noreply.github.com>
10 files changed, 2025 insertions, 9 deletions
diff --git a/openbb_platform/extensions/economy/integration/test_economy_api.py b/openbb_platform/extensions/economy/integration/test_economy_api.py index dcdcb21ef1b..6a5ac1eb362 100644 --- a/openbb_platform/extensions/economy/integration/test_economy_api.py +++ b/openbb_platform/extensions/economy/integration/test_economy_api.py @@ -266,6 +266,7 @@ def test_economy_balance_of_payments(params, headers): "filter_value": "Monthly", "tag_names": "nsa", "exclude_tag_names": None, + "series_id": None, "provider": "fred", } ), @@ -280,6 +281,7 @@ def test_economy_balance_of_payments(params, headers): "filter_value": None, "tag_names": None, "exclude_tag_names": None, + "series_id": None, "provider": "fred", } ), @@ -294,6 +296,22 @@ def test_economy_balance_of_payments(params, headers): "filter_value": None, "tag_names": None, "exclude_tag_names": None, + "series_id": None, + "provider": "fred", + } + ), + ( + { + "query": None, + "is_release": False, + "release_id": None, + "offset": None, + "limit": None, + "filter_variable": None, + "filter_value": None, + "tag_names": None, + "exclude_tag_names": None, + "series_id": "NYICLAIMS", "provider": "fred", } ), @@ -478,3 +496,51 @@ def test_economy_long_term_interest_rate(params, headers): result = requests.get(url, headers=headers, timeout=10) assert isinstance(result, requests.Response) assert result.status_code == 200 + + +@parametrize( + argnames="params", + argvalues=[ + ( + { + "symbol": "156241", + "is_series_group": True, + "start_date": "2000-01-01", + "end_date": None, + "frequency": "w", + "units": "Number", + "region_type": "state", + "season": "NSA", + "aggregation_method": "eop", + "transform": "ch1", + "provider": "fred", + "limit": None, + } + ), + ( + { + "symbol": "CAICLAIMS", + "is_series_group": False, + "start_date": "1990-01-01", + "end_date": "2010-01-01", + "frequency": None, + "units": None, + "region_type": None, + "season": None, + "aggregation_method": None, + "transform": None, + "provider": "fred", + "limit": None, + } + ), + ], +) +@pytest.mark.integration +def test_economy_fred_regional(params, headers): + params = {p: v for p, v in params.items() if v} + + query_str = get_querystring(params, []) + url = f"http://0.0.0.0:8000/api/v1/economy/fred_regional?{query_str}" + result = requests.get(url, headers=headers, timeout=10) + assert isinstance(result, requests.Response) + assert result.status_code == 200 diff --git a/openbb_platform/extensions/economy/integration/test_economy_python.py b/openbb_platform/extensions/economy/integration/test_economy_python.py index 95c73732055..8c4c7ff14c6 100644 --- a/openbb_platform/extensions/economy/integration/test_economy_python.py +++ b/openbb_platform/extensions/economy/integration/test_economy_python.py @@ -225,6 +225,7 @@ def test_economy_balance_of_payments(params, obb): "filter_value": "Monthly", "tag_names": "nsa", "exclude_tag_names": None, + "series_id": None, "provider": "fred", } ), @@ -239,6 +240,7 @@ def test_economy_balance_of_payments(params, obb): "filter_value": None, "tag_names": None, "exclude_tag_names": None, + "series_id": None, "provider": "fred", } ), @@ -253,6 +255,22 @@ def test_economy_balance_of_payments(params, obb): "filter_value": None, "tag_names": None, "exclude_tag_names": None, + "series_id": None, + "provider": "fred", + } + ), + ( + { + "query": None, + "is_release": False, + "release_id": None, + "offset": None, + "limit": None, + "filter_variable": None, + "filter_value": None, + "tag_names": None, + "exclude_tag_names": None, + "series_id": "NYICLAIMS", "provider": "fred", } ), @@ -430,3 +448,50 @@ def test_economy_long_term_interest_rate(params, obb): assert result assert isinstance(result, OBBject) assert len(result.results) > 0 + + +@parametrize( + argnames="params", + argvalues=[ + ( + { + "symbol": "156241", + "is_series_group": True, + "start_date": "2000-01-01", + "end_date": None, + "frequency": "w", + "units": "Number", + "region_type": "state", + "season": "NSA", + "aggregation_method": "eop", + "transform": "ch1", + "provider": "fred", + "limit": None, + } + ), + ( + { + "symbol": "CAICLAIMS", + "is_series_group": False, + "start_date": "1990-01-01", + "end_date": "2010-01-01", + "frequency": None, + "units": None, + "region_type": None, + "season": None, + "aggregation_method": None, + "transform": None, + "provider": "fred", + "limit": None, + } + ), + ], +) +@pytest.mark.integration +def test_economy_fred_regional(params, obb): + params = {p: v for p, v in params.items() if v} + + result = obb.economy.long_term_interest_rate(**params) + assert result + assert isinstance(result, OBBject) + assert len(result.results) > 0 diff --git a/openbb_platform/extensions/economy/openbb_economy/economy_router.py b/openbb_platform/extensions/economy/openbb_economy/economy_router.py index 4ea2efce047..b6f0bde05d5 100644 --- a/openbb_platform/extensions/economy/openbb_economy/economy_router.py +++ b/openbb_platform/extensions/economy/openbb_economy/economy_router.py @@ -231,3 +231,26 @@ async def long_term_interest_rate( Low long-term interest rates encourage investment in new equipment and high interest rates discourage it. Investment is, in turn, a major source of economic growth.""" return await OBBject.from_query(Query(**locals())) + + +@router.command( + model="FredRegional", + exclude_auto_examples=True, + examples=[ + "#### With no date, the most recent report is returned. ####", + 'obb.economy.fred_regional("NYICLAIMS")', + "#### With a date, time series data is returned. ####", + 'obb.economy.fred_regional("NYICLAIMS", start_date="2021-01-01")', + ], +) +async def fred_regional( + cc: CommandContext, + provider_choices: ProviderChoices, + standard_params: StandardParams, + extra_params: ExtraParams, +) -> OBBject: + """ + Query the Geo Fred API for regional economic data by series group. + The series group ID is found by using `fred_search` and the `series_id` parameter. + """ + return await OBBject.from_query(Query(**locals())) diff --git a/openbb_platform/openbb/package/economy.py b/openbb_platform/openbb/package/economy.py index 4fab46a8170..bdcde1a270b 100644 --- a/openbb_platform/openbb/package/economy.py +++ b/openbb_platform/openbb/package/economy.py @@ -16,6 +16,7 @@ class ROUTER_economy(Container): calendar composite_leading_indicator cpi + fred_regional fred_search fred_series /gdp @@ -349,6 +350,153 @@ class ROUTER_economy(Container): ) @validate + def fred_regional( + self, + symbol: Annotated[ + str, OpenBBCustomParameter(description="Symbol to get data for.") + ], + start_date: Annotated[ + Union[datetime.date, None, str], + OpenBBCustomParameter( + description="Start date of the data, in YYYY-MM-DD format." + ), + ] = None, + end_date: Annotated[ + Union[datetime.date, None, str], + OpenBBCustomParameter( + description="End date of the data, in YYYY-MM-DD format." + ), + ] = None, + limit: Annotated[ + Optional[int], + OpenBBCustomParameter(description="The number of data entries to return."), + ] = 100000, + provider: Optional[Literal["fred"]] = None, + **kwargs + ) -> OBBject: + """ + Query the Geo Fred API for regional economic data by series group. + The series group ID is found by using `fred_search` and the `series_id` parameter. + + + Parameters + ---------- + symbol : str + Symbol to get data for. + start_date : Optional[datetime.date] + Start date of the data, in YYYY-MM-DD format. + end_date : Optional[datetime.date] + End date of the data, in YYYY-MM-DD format. + limit : Optional[int] + The number of data entries to return. + provider : Optional[Literal['fred']] + The provider to use for the query, by default None. + If None, the provider specified in defaults is selected or 'fred' if there is + no default. + is_series_group : bool + When True, the symbol provided is for a series_group, else it is for a series ID. (provider: fred) + region_type : Optional[Literal['bea', 'msa', 'frb', 'necta', 'state', 'country', 'county', 'censusregion']] + The type of regional data. Parameter is only valid when `is_series_group` is True. (provider: fred) + season : Optional[Literal['SA', 'NSA', 'SSA']] + The seasonal adjustments to the data. Parameter is only valid when `is_series_group` is True. (provider: fred) + units : Optional[str] + The units of the data. This should match the units returned from searching by series ID. An incorrect field will not necessarily return an error. Parameter is only valid when `is_series_group` is True. (provider: fred) + frequency : Optional[Literal['d', 'w', 'bw', 'm', 'q', 'sa', 'a', 'wef', 'weth', 'wew', 'wetu', 'wem', 'wesu', 'wesa', 'bwew', 'bwem']] + + Frequency aggregation to convert high frequency data to lower frequency. + Parameter is only valid when `is_series_group` is True. + a = Annual + sa= Semiannual + q = Quarterly + m = Monthly + w = Weekly + d = Daily + wef = Weekly, Ending Friday + weth = Weekly, Ending Thursday + wew = Weekly, Ending Wednesday + wetu = Weekly, Ending Tuesday + wem = Weekly, Ending Monday + wesu = Weekly, Ending Sunday + wesa = Weekly, Ending Saturday + bwew = Biweekly, Ending Wednesday + bwem = Biweekly, Ending Monday + (provider: fred) + aggregation_method : Literal['avg', 'sum', 'eop'] + + A key that indicates the aggregation method used for frequency aggregation. + This parameter has no affect if the frequency parameter is not set. + Only valid when `is_series_group` is True. + avg = Average + sum = Sum + eop = End of Period + (provider: fred) + transform : Literal['lin', 'chg', 'ch1', 'pch', 'pc1', 'pca', 'cch', 'cca', 'log'] + + Transformation type. Only valid when `is_series_group` is True. + lin = Levels (No transformation) + chg = Change + ch1 = Change from Year Ago + pch = Percent Change + pc1 = Percent Change from Year Ago + pca = Compounded Annual Rate of Change + cch = Continuously Compounded Rate of Change + cca = Continuously Compounded Annual Rate of Change + log = Natural Log + (provider: fred) + + Returns + ------- + OBBject + results : List[FredRegional] + Serializable results. + provider : Optional[Literal['fred']] + Provider name. + warnings : Optional[List[Warning_]] + List of warnings. + chart : Optional[Chart] + Chart object. + extra: Dict[str, Any] + Extra info. + + FredRegional + ------------ + date : date + The date of the data. + region : Optional[str] + The name of the region. (provider: fred) + code : Optional[Union[str, int]] + The code of the region. (provider: fred) + value : Optional[Union[float, int]] + The obersvation value. The units are defined in the search results by series ID. (provider: fred) + series_id : Optional[str] + The individual series ID for the region. (provider: fred) + + Example + ------- + >>> from openbb import obb + >>> #### With no date, the most recent report is returned. #### + >>> obb.economy.fred_regional("NYICLAIMS") + >>> #### With a date, time series data is returned. #### + >>> obb.economy.fred_regional("NYICLAIMS", start_date="2021-01-01") + """ # noqa: E501 + + return self._run( + "/economy/fred_regional", + **filter_inputs( + provider_choices={ + "provider": provider, + }, + standard_params={ + "symbol": symbol, + "start_date": start_date, + "end_date": end_date, + "limit": limit, + }, + extra_params=kwargs, + ) + ) + + @validate def fred_search( self, query: Annotated[ @@ -387,6 +535,8 @@ class ROUTER_economy(Container): A semicolon delimited list of tag names that series match all of. Example: 'japan;imports' (provider: fred) exclude_tag_names : Optional[str] A semicolon delimited list of tag names that series match none of. Example: 'imports;services'. Requires that variable tag_names also be set to limit the number of matching series. (provider: fred) + series_id : Optional[str] + A FRED Series ID to return series group information for. This returns the required information to query for regional data. Not all series that are in FRED have geographical data. Entering a value for series_id will override all other parameters. Multiple series_ids can be separated by commas. (provider: fred) Returns ------- @@ -440,6 +590,10 @@ class ROUTER_economy(Container): Popularity of the series (provider: fred) group_popularity : Optional[int] Group popularity of the release (provider: fred) + region_type : Optional[str] + The region type of the series. (provider: fred) + series_group : Optional[Union[int, str]] + The series group ID of the series. This value is used to query for regional data. (provider: fred) Example ------- diff --git a/openbb_platform/openbb/package/module_map.json b/openbb_platform/openbb/package/module_map.json index 2cee252c131..39397c721a3 100644 --- a/openbb_platform/openbb/package/module_map.json +++ b/openbb_platform/openbb/package/module_map.json @@ -19,6 +19,7 @@ "economy_calendar": "/economy/calendar", "economy_composite_leading_indicator": "/economy/composite_leading_indicator", "economy_cpi": "/economy/cpi", + "economy_fred_regional": "/economy/fred_regional", "economy_fred_search": "/economy/fred_search", "economy_fred_series": "/economy/fred_series", "economy_gdp": "/economy/gdp", diff --git a/openbb_platform/providers/fred/openbb_fred/__init__.py b/openbb_platform/providers/fred/openbb_fred/__init__.py index c0d250a0d90..b2292c4d946 100644 --- a/openbb_platform/providers/fred/openbb_fred/__init__.py +++ b/openbb_platform/providers/fred/openbb_fred/__init__.py @@ -16,6 +16,7 @@ from openbb_fred.models.hqm import FREDHighQualityMarketCorporateBondFetcher from openbb_fred.models.ice_bofa import FREDICEBofAFetcher from openbb_fred.models.iorb_rates import FREDIORBFetcher from openbb_fred.models.moody import FREDMoodyCorporateBondIndexFetcher +from openbb_fred.models.regional import FredRegionalDataFetcher from openbb_fred.models.search import ( FredSearchFetcher, ) @@ -51,6 +52,7 @@ fred_provider = Provider( "CommercialPaper": FREDCommercialPaperFetcher, "FredSearch": FredSearchFetcher, "FredSeries": FredSeriesFetcher, + "FredRegional": FredRegionalDataFetcher, "SpotRate": FREDSpotRateFetcher, "HighQualityMarketCorporateBond": FREDHighQualityMarketCorporateBondFetcher, "TreasuryConstantMaturity": FREDTreasuryConstantMaturityFetcher, diff --git a/openbb_platform/providers/fred/openbb_fred/models/regional.py b/openbb_platform/providers/fred/openbb_fred/models/regional.py new file mode 100644 index 00000000000..64d8b12cc6f --- /dev/null +++ b/openbb_platform/providers/fred/openbb_fred/models/regional.py @@ -0,0 +1,269 @@ +"""FRED Regional Data Model.""" + +# pylint: disable=unused-argument +import json +import warnings +from datetime import datetime +from typing import Any, Dict, List, Literal, Optional, Union + +from openbb_core.provider.abstract.fetcher import Fetcher +from openbb_core.provider.standard_models.fred_series import ( + SeriesData, + SeriesQueryParams, +) +from openbb_core.provider.utils.errors import EmptyDataError +from openbb_core.provider.utils.helpers import ( + amake_request, + get_querystring, +) +from pydantic import Field, model_validator + +_warn = warnings.warn + + +class FredRegionalQueryParams(SeriesQueryParams): + """FRED Regional Data Query Params.""" + + __alias_dict__ = { + "symbol": "series_group", + "transform": "transformation", + } + symbol: str = Field( + description="For this function, it is the series_group ID or series ID." + + " If the symbol provided is for a series_group, set the `is_series_group` parameter to True." + + " Not all series that are in FRED have geographical data." + ) + is_series_group: bool = Field( + default=False, + description="When True, the symbol provided is for a series_group, else it is for a series ID.", + ) + region_type: Union[ + None, + Literal[ + "bea", + "msa", + "frb", + "necta", + "state", + "country", + "county", + "censusregion", + ], + ] = Field( + default=None, + description="The type of regional data." + + " Parameter is only valid when `is_series_group` is True.", + ) + season: Union[ + None, + Literal[ + "SA", + "NSA", + "SSA", + ], + ] = Field( + default="NSA", + description="The seasonal adjustments to the data." + + " Parameter is only valid when `is_series_group` is True.", + ) + units: Optional[str] = Field( + default=None, + description="The units of the data." + + " This should match the units returned from searching by series ID." + + " An incorrect field will not necessarily return an error." + + " Parameter is only valid when `is_series_group` is True.", + ) + frequency: Union[ + None, + Literal[ + "d", + "w", + "bw", + "m", + "q", + "sa", + "a", + "wef", + "weth", + "wew", + "wetu", + "wem", + "wesu", + "wesa", + "bwew", + "bwem", + ], + ] = Field( + default=None, + description=""" + Frequency aggregation to convert high frequency data to lower frequency. + Parameter is only valid when `is_series_group` is True. + a = Annual + sa= Semiannual + q = Quarterly + m = Monthly + w = Weekly + d = Daily + wef = Weekly, Ending Friday + weth = Weekly, Ending Thursday + wew = Weekly, Ending Wednesday + wetu = Weekly, Ending Tuesday + wem = Weekly, Ending Monday + wesu = Weekly, Ending Sunday + wesa = Weekly, Ending Saturday + bwew = Biweekly, Ending Wednesday + bwem = Biweekly, Ending Monday + """, + ) + aggregation_method: Literal["avg", "sum", "eop"] = Field( + default="avg", + description=""" + A key that indicates the aggregation method used for frequency aggregation. + This parameter has no affect if the frequency parameter is not set. + Only valid when `is_series_group` is True. + avg = Average + sum = Sum + eop = End of Period + """, + ) + transform: Literal[ + "lin", "chg", "ch1", "pch", "pc1", "pca", "cch", "cca", "log" + ] = Field( + default="lin", + description=""" + Transformation type. Only valid when `is_series_group` is True. + lin = Levels (No transformation) + chg = Change + ch1 = Change from Year Ago + pch = Percent Change + pc1 = Percent Change from Year Ago + pca = Compounded Annual Rate of Change + cch = Continuously Compounded Rate of Change + cca = Continuously Compounded Annual Rate of Change + log = Natural Log + """, + ) + + @model_validator(mode="before") + @classmethod + def transform_validate(cls, values): + """Add default start date.""" + if values.get("is_series_group") is True: + required = ["frequency", "region_type", "units"] + for key in required: + if values.get(key) is None: + raise ValueError( + f"{key} is a required field missing for series_group." + ) + + values["start_date"] = ( + "1900-01-01" + if values.get("start_date") is None + else values.get("start_date") + ) + if values.get("is_series_group") is False: + values["start_date"] = ( + None if values.get("start_date") is None else values.get("start_date") + ) + return values + + +class FredRegionalData(SeriesData): + """FRED Regional Data.""" + + __alias_dict__ = { + "date": "observation_date", + } + region: str = Field( + description="The name of the region.", + ) + code: Union[str, int] = Field( + description="The code of the region.", + ) + value: Optional[Union[int, float]] = Field( + default=None, + description="The obersvation value. The units are defined in the search results by series ID.", + ) + series_id: str = Field( + description="The individual series ID for the region.", + ) + + +class FredRegionalDataFetcher( + Fetcher[ + FredRegionalQueryParams, + List[FredRegionalData], + ] +): + """FRED Regional Data Fetcher.""" + + @staticmethod + def transform_query(params: Dict[str, Any]) -> FredRegionalQueryParams: + """Transform query.""" + return FredRegionalQueryParams(**params) + + @staticmethod + async def aextract_data( + |