From d7a9e666b98a7456d8bcc01c1ae7d026a2a566b3 Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Fri, 7 Jun 2024 03:57:11 -0700 Subject: [Feature] ICE BofAML Bond Indices (FRED) (#6486) * fred bond indices * maturity dict * review items --- .../provider/standard_models/bond_indices.py | 52 ++ .../integration/test_fixedincome_api.py | 28 + .../integration/test_fixedincome_python.py | 27 + .../corporate/corporate_router.py | 7 + .../openbb_fixedincome/fixedincome_router.py | 37 ++ openbb_platform/openbb/assets/reference.json | 223 +++++++- openbb_platform/openbb/package/fixedincome.py | 143 +++++ .../openbb/package/fixedincome_corporate.py | 15 +- .../providers/fred/openbb_fred/__init__.py | 2 + .../fred/openbb_fred/models/bond_indices.py | 588 +++++++++++++++++++++ .../test_fred_bond_indices_fetcher.yaml | 168 ++++++ .../providers/fred/tests/test_fred_fetchers.py | 16 + 12 files changed, 1303 insertions(+), 3 deletions(-) create mode 100644 openbb_platform/core/openbb_core/provider/standard_models/bond_indices.py create mode 100644 openbb_platform/providers/fred/openbb_fred/models/bond_indices.py create mode 100644 openbb_platform/providers/fred/tests/record/http/test_fred_fetchers/test_fred_bond_indices_fetcher.yaml diff --git a/openbb_platform/core/openbb_core/provider/standard_models/bond_indices.py b/openbb_platform/core/openbb_core/provider/standard_models/bond_indices.py new file mode 100644 index 00000000000..f778c3bb6c1 --- /dev/null +++ b/openbb_platform/core/openbb_core/provider/standard_models/bond_indices.py @@ -0,0 +1,52 @@ +"""Bond Indices Standard Model.""" + +from datetime import ( + date as dateType, +) +from typing import Literal, Optional + +from pydantic import Field, field_validator + +from openbb_core.provider.abstract.data import Data +from openbb_core.provider.abstract.query_params import QueryParams +from openbb_core.provider.utils.descriptions import ( + DATA_DESCRIPTIONS, + QUERY_DESCRIPTIONS, +) + + +class BondIndicesQueryParams(QueryParams): + """Bond Indices Query.""" + + start_date: Optional[dateType] = Field( + default=None, + description=QUERY_DESCRIPTIONS.get("start_date", ""), + ) + end_date: Optional[dateType] = Field( + default=None, + description=QUERY_DESCRIPTIONS.get("end_date", ""), + ) + index_type: Literal["yield", "yield_to_worst", "total_return", "oas"] = Field( + default="yield", + description="The type of series. OAS is the option-adjusted spread. Default is yield.", + json_schema_extra={ + "choices": ["yield", "yield_to_worst", "total_return", "oas"] + }, + ) + + @field_validator("index_type", mode="before", check_fields=False) + @classmethod + def to_lower(cls, v: Optional[str]) -> Optional[str]: + """Convert field to lowercase.""" + return v.lower() if v else v + + +class BondIndicesData(Data): + """Bond Indices Data.""" + + date: dateType = Field(description=DATA_DESCRIPTIONS.get("date", "")) + symbol: Optional[str] = Field( + default=None, + description=DATA_DESCRIPTIONS.get("symbol", ""), + ) + value: float = Field(description="Index values.") diff --git a/openbb_platform/extensions/fixedincome/integration/test_fixedincome_api.py b/openbb_platform/extensions/fixedincome/integration/test_fixedincome_api.py index 45e7c86a685..1e9844767f3 100644 --- a/openbb_platform/extensions/fixedincome/integration/test_fixedincome_api.py +++ b/openbb_platform/extensions/fixedincome/integration/test_fixedincome_api.py @@ -666,3 +666,31 @@ def test_fixedincome_government_yield_curve(params, headers): result = requests.get(url, headers=headers, timeout=10) assert isinstance(result, requests.Response) assert result.status_code == 200 + + +@parametrize( + "params", + [ + { + "provider": "fred", + "category": "high_yield", + "index": "us,europe,emerging", + "index_type": "total_return", + "start_date": "2023-05-31", + "end_date": "2024-06-01", + "transform": None, + "frequency": None, + "aggregation_method": "avg", + }, + ], +) +@pytest.mark.integration +def test_fixedincome_bond_indices(params, headers): + """Test the bond indices endpoint.""" + 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/fixedincome/bond_indices?{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/fixedincome/integration/test_fixedincome_python.py b/openbb_platform/extensions/fixedincome/integration/test_fixedincome_python.py index 3efa8727269..433b96a3d8f 100644 --- a/openbb_platform/extensions/fixedincome/integration/test_fixedincome_python.py +++ b/openbb_platform/extensions/fixedincome/integration/test_fixedincome_python.py @@ -620,3 +620,30 @@ def test_fixedincome_government_yield_curve(params, obb): assert result assert isinstance(result, OBBject) assert len(result.results) > 0 + + +@parametrize( + "params", + [ + { + "provider": "fred", + "category": "high_yield", + "index": "us,europe,emerging", + "index_type": "total_return", + "start_date": "2023-05-31", + "end_date": "2024-06-01", + "transform": None, + "frequency": None, + "aggregation_method": "avg", + }, + ], +) +@pytest.mark.integration +def test_fixedincome_bond_indices(params, obb): + """Test the bond indices endpoint.""" + params = {p: v for p, v in params.items() if v} + + result = obb.fixedincome.bond_indices(**params) + assert result + assert isinstance(result, OBBject) + assert len(result.results) > 0 diff --git a/openbb_platform/extensions/fixedincome/openbb_fixedincome/corporate/corporate_router.py b/openbb_platform/extensions/fixedincome/openbb_fixedincome/corporate/corporate_router.py index 18631946f2e..996a5820bea 100644 --- a/openbb_platform/extensions/fixedincome/openbb_fixedincome/corporate/corporate_router.py +++ b/openbb_platform/extensions/fixedincome/openbb_fixedincome/corporate/corporate_router.py @@ -1,5 +1,6 @@ """Fixed Income Corporate Router.""" +from openbb_core.app.deprecation import OpenBBDeprecationWarning from openbb_core.app.model.command_context import CommandContext from openbb_core.app.model.example import APIEx from openbb_core.app.model.obbject import OBBject @@ -22,6 +23,12 @@ router = Router(prefix="/corporate") APIEx(parameters={"provider": "fred"}), APIEx(parameters={"index_type": "yield_to_worst", "provider": "fred"}), ], + deprecated=True, + deprecation=OpenBBDeprecationWarning( + message="This endpoint is deprecated; use `/fixedincome/bond_indices` instead.", + since=(4, 2), + expected_removal=(4, 5), + ), ) async def ice_bofa( cc: CommandContext, diff --git a/openbb_platform/extensions/fixedincome/openbb_fixedincome/fixedincome_router.py b/openbb_platform/extensions/fixedincome/openbb_fixedincome/fixedincome_router.py index 0e232cb1cdb..fb346dd3cc3 100644 --- a/openbb_platform/extensions/fixedincome/openbb_fixedincome/fixedincome_router.py +++ b/openbb_platform/extensions/fixedincome/openbb_fixedincome/fixedincome_router.py @@ -44,3 +44,40 @@ async def sofr( borrowing cash overnight collateralizing by Treasury securities. """ return await OBBject.from_query(Query(**locals())) + + +@router.command( + model="BondIndices", + examples=[ + APIEx( + description="The default state for FRED are series for constructing the US Corporate Bond Yield Curve.", + parameters={"provider": "fred"}, + ), + APIEx( + description="Multiple indices, from within the same 'category', can be requested.", + parameters={ + "category": "high_yield", + "index": "us,europe,emerging", + "index_type": "total_return", + "provider": "fred", + }, + ), + APIEx( + description="From FRED, there are three main categories, 'high_yield', 'us', and 'emerging_markets'." + + " Emerging markets is a broad category.", + parameters={ + "category": "emerging_markets", + "index": "corporate,private_sector,public_sector", + "provider": "fred", + }, + ), + ], +) +async def bond_indices( + cc: CommandContext, + provider_choices: ProviderChoices, + standard_params: StandardParams, + extra_params: ExtraParams, +) -> OBBject: # type: ignore + """Bond Indices.""" + return await OBBject.from_query(Query(**locals())) diff --git a/openbb_platform/openbb/assets/reference.json b/openbb_platform/openbb/assets/reference.json index bb5ee238f37..c1c5e76fd72 100644 --- a/openbb_platform/openbb/assets/reference.json +++ b/openbb_platform/openbb/assets/reference.json @@ -31361,8 +31361,8 @@ }, "/fixedincome/corporate/ice_bofa": { "deprecated": { - "flag": null, - "message": null + "flag": true, + "message": "This endpoint is deprecated; use `/fixedincome/bond_indices` instead. Deprecated in OpenBB Platform V4.2 to be removed in V4.5." }, "description": "ICE BofA US Corporate Bond Indices.\n\nThe ICE BofA US Corporate Index tracks the performance of US dollar denominated investment grade corporate debt\npublicly issued in the US domestic market. Qualifying securities must have an investment grade rating (based on an\naverage of Moody\u2019s, S&P and Fitch), at least 18 months to final maturity at the time of issuance, at least one year\nremaining term to final maturity as of the rebalance date, a fixed coupon schedule and a minimum amount\noutstanding of $250 million. The ICE BofA US Corporate Index is a component of the US Corporate Master Index.", "examples": "\nExamples\n--------\n\n```python\nfrom openbb import obb\nobb.fixedincome.corporate.ice_bofa(provider='fred')\nobb.fixedincome.corporate.ice_bofa(index_type='yield_to_worst', provider='fred')\n```\n\n", @@ -32016,6 +32016,225 @@ }, "model": "SOFR" }, + "/fixedincome/bond_indices": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Bond Indices.", + "examples": "\nExamples\n--------\n\n```python\nfrom openbb import obb\n# The default state for FRED are series for constructing the US Corporate Bond Yield Curve.\nobb.fixedincome.bond_indices(provider='fred')\n# Multiple indices, from within the same 'category', can be requested.\nobb.fixedincome.bond_indices(category=high_yield, index=us,europe,emerging, index_type='total_return', provider='fred')\n# From FRED, there are three main categories, 'high_yield', 'us', and 'emerging_markets'. Emerging markets is a broad category.\nobb.fixedincome.bond_indices(category=emerging_markets, index=corporate,private_sector,public_sector, provider='fred')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "start_date", + "type": "Union[date, str]", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "end_date", + "type": "Union[date, str]", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "index_type", + "type": "Literal['yield', 'yield_to_worst', 'total_return', 'oas']", + "description": "The type of series. OAS is the option-adjusted spread. Default is yield.", + "default": "yield", + "optional": true, + "choices": [ + "yield", + "yield_to_worst", + "total_return", + "oas" + ] + }, + { + "name": "provider", + "type": "Literal['fred']", + "description": "The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: f, r, e, d.", + "default": null, + "optional": true + } + ], + "fred": [ + { + "name": "category", + "type": "Literal['high_yield', 'us', 'emerging_markets']", + "description": "The type of index category. Used in conjunction with 'index', default is 'us'.", + "default": "us", + "optional": true, + "choices": null + }, + { + "name": "index", + "type": "Union[str, List[str]]", + "description": "The specific index to query. Used in conjunction with 'category' and 'index_type', default is 'yield_curve'. Multiple items allowed for provider(s): fred.", + "default": "yield_curve", + "optional": true, + "choices": [ + "a", + "aa", + "aaa", + "asia", + "b", + "bb", + "bbb", + "ccc", + "corporate", + "crossover", + "emea", + "high_grade", + "high_yield", + "latam", + "liquid_aaa", + "liquid_asia", + "liquid_bbb", + "liquid_corporate", + "liquid_emea", + "liquid_latam", + "non_financial", + "private_sector", + "public_sector", + "yield_curve" + ] + }, + { + "name": "frequency", + "type": "Literal['a', 'q', 'm', 'w', 'd', 'wef', 'weth', 'wew', 'wetu', 'wem', 'wesu', 'wesa', 'bwew', 'bwem']", + "description": "Frequency aggregation to convert daily data to lower frequency. None = No change a = Annual 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", + "default": null, + "optional": true, + "choices": [ + "a", + "q", + "m", + "w", + "d", + "wef", + "weth", + "wew", + "wetu", + "wem", + "wesu", + "wesa", + "bwew", + "bwem" + ] + }, + { + "name": "aggregation_method", + "type": "Literal['avg', 'sum', 'eop']", + "description": "A key that indicates the aggregation method used for frequency aggregation. This parameter has no affect if the frequency parameter is not set, default is 'avg'. avg = Average sum = Sum eop = End of Period", + "default": "avg", + "optional": true, + "choices": [ + "avg", + "sum", + "eop" + ] + }, + { + "name": "transform", + "type": "Literal['chg', 'ch1', 'pch', 'pc1', 'pca', 'cch', 'cca', 'log']", + "description": "Transformation type None = 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", + "default": null, + "optional": true, + "choices": [ + "chg", + "ch1", + "pch", + "pc1", + "pca", + "cch", + "cca", + "log" + ] + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "List[BondIndices]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Optional[Literal['fred']]", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[List[Warning_]]", + "description": "List of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "Dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date", + "description": "The date of the data.", + "default": "", + "optional": false, + "choices": null + }, + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "value", + "type": "float", + "description": "Index values.", + "default": "", + "optional": false, + "choices": null + } + ], + "fred": [ + { + "name": "maturity", + "type": "str", + "description": "The maturity range of the bond index. Only applicable when 'index' is 'yield_curve'.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "title", + "type": "str", + "description": "The title of the index.", + "default": "", + "optional": false, + "choices": null + } + ] + }, + "model": "BondIndices" + }, "/index/price/historical": { "deprecated": { "flag": null, diff --git a/openbb_platform/openbb/package/fixedincome.py b/openbb_platform/openbb/package/fixedincome.py index 4f23ed3b986..456845abcf0 100644 --- a/openbb_platform/openbb/package/fixedincome.py +++ b/openbb_platform/openbb/package/fixedincome.py @@ -13,6 +13,7 @@ from typing_extensions import Annotated class ROUTER_fixedincome(Container): """/fixedincome + bond_indices /corporate /government /rate @@ -23,6 +24,148 @@ class ROUTER_fixedincome(Container): def __repr__(self) -> str: return self.__doc__ or "" + @exception_handler + @validate + def bond_indices( + self, + start_date: Annotated[ + Union[datetime.date, None, str], + OpenBBField(description="Start date of the data, in YYYY-MM-DD format."), + ] = None, + end_date: Annotated[ + Union[datetime.date, None, str], + OpenBBField(description="End date of the data, in YYYY-MM-DD format."), + ] = None, + index_type: Annotated[ + Literal["yield", "yield_to_worst", "total_return", "oas"], + OpenBBField( + description="The type of series. OAS is the option-adjusted spread. Default is yield.", + choices=["yield", "yield_to_worst", "total_return", "oas"], + ), + ] = "yield", + provider: Annotated[ + Optional[Literal["fred"]], + OpenBBField( + description="The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: fred." + ), + ] = None, + **kwargs + ) -> OBBject: + """Bond Indices. + + Parameters + ---------- + start_date : Union[datetime.date, None, str] + Start date of the data, in YYYY-MM-DD format. + end_date : Union[datetime.date, None, str] + End date of the data, in YYYY-MM-DD format. + index_type : Literal['yield', 'yield_to_worst', 'total_return', 'oas'] + The type of series. OAS is the option-adjusted spread. Default is yield. + provider : Optional[Literal['fred']] + The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: fred. + category : Literal['high_yield', 'us', 'emerging_markets'] + The type of index category. Used in conjunction with 'index', default is 'us'. (provider: fred) + index : str + The specific index to query. Used in conjunction with 'category' and 'index_type', default is 'yield_curve'. Multiple comma separated items allowed. (provider: fred) + frequency : Optional[Literal['a', 'q', 'm', 'w', 'd', 'wef', 'weth', 'wew', 'wetu', 'wem', 'wesu', 'wesa', 'bwew', 'bwem']] + + Frequency aggregation to convert daily data to lower frequency. + None = No change + a = Annual + 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, default is 'avg'. + avg = Average + sum = Sum + eop = End of Period + (provider: fred) + transform : Optional[Literal['chg', 'ch1', 'pch', 'pc1', 'pca', 'cch', 'cca', 'log']] + + Transformation type + None = 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[BondIndices] + 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. + + BondIndices + ----------- + date : date + The date of the data. + symbol : Optional[str] + Symbol representing the entity requested in the data. + value : float + Index values. + maturity : Optional[str] + The maturity range of the bond index. Only applicable when 'index' is 'yield_curve'. (provider: fred) + title : Optional[str] + The title of the index. (provider: fred) + + Examples + -------- + >>> from openbb import obb + >>> # The default state for FRED are series for constructing the US Corporate Bond Yield Curve. + >>> obb.fixedincome.bond_indices(provider='fred') + >>> # Multiple indices, from within the same 'category', can be requested. + >>> obb.fixedincome.bond_indices(category='high_yield', index='us,europe,emerging', index_type='total_return', provider='fred') + >>> # From FRED, there are three main categories, 'high_yield', 'us', and 'emerging_markets'. Emerging markets is a broad category. + >>> obb.fixedincome.bond_indices(category='emerging_markets', index='corporate,private_sector,public_sector', provider='fred') + """ # noqa: E501 + + return self._run( + "/fixedincome/bond_indices", + **filter_inputs( + provider_choices={ + "provider": self._get_provider( + provider, + "fixedincome.bond_indices", + ("fred",), + ) + }, + standard_params={ + "start_date": start_date, + "end_date": end_date, + "index_type": index_type, + }, + extra_params=kwargs, + info={"index": {"fred": {"multiple_items_allowed": True}}}, + ) + ) + @property def corporate(self): # pylint: disable=import-outside-toplevel diff --git a/openbb_platform/openbb/package/fixedincome_corporate.py b/openbb_platform/openbb/package/fixedincome_corporate.py index 9d4d80b6e9c..1fed4cbdf10 100644 --- a/openbb_platform/openbb/package/fixedincome_corporate.py +++ b/openbb_platform/openbb/package/fixedincome_corporate.py @@ -2,13 +2,15 @@ import datetime from typing import List, Literal, Optional, Union +from warnings import simplefilter, warn +from openbb_core.app.deprecation import OpenBBDeprecationWarning from openbb_core.app.model.field import OpenBBField from openbb_core.app.model.obbject import OBBject from openbb_core.app.static.container import Container from openbb_core.app.static.utils.decorators import exception_handler, validate from openbb_core.app.static.utils.filters import filter_inputs -from typing_extensions import Annotated +from typing_extensions import Annotated, deprecated class ROUTER_fixedincome_corporate(Container): @@ -216,6 +218,10 @@ class ROUTER_fixedincome_corporate(Container): @exception_handler @validate + @deprecated( + "This endpoint is deprecated; use `/fixedincome/bond_indices` instead. Deprecated in OpenBB Platform V4.2 to be removed in V4.5.", + category=OpenBBDeprecationWarning, + ) def ice_bofa( self, start_date: Annotated[ @@ -294,6 +300,13 @@ class ROUTER_fixedincome_corporate(Container): >>> obb.fixedincome.corporate.ice_bofa(index_type='yield_to_worst', provider='fred') """ # noqa: E501 + simplefilter("always", DeprecationWarning) + warn( + "This endpoint is deprecated; use `/fixedincome/bond_indices` instead. Deprecated in OpenBB Platform V4.2 to be removed in V4.5.", + category=DeprecationWarning, + stacklevel=2, + ) + return self._run( "/fixedincome/corporate/ice_bofa", **filter_inputs( diff --git a/openbb_platform/providers/fred/openbb_fred/__init__.py b/openbb_platform/providers/fred/openbb_fred/__init__.py index d4a86ac40f8..d68a8d1558e 100644 --- a/openbb_platform/providers/fred/openbb_fred/__init__.py +++ b/openbb_platform/providers/fred/openbb_fred/__init__.py @@ -3,6 +3,7 @@ from openbb_core.provider.abstract.provider import Provider from openbb_fred.models.ameribor_rates import FREDAMERIBORFetcher from openbb_fred.models.balance_of_payments import FredBalanceOfPaymentsFetcher +from openbb_fred.models.bond_indices import FredBondIndicesFetcher from openbb_fred.models.consumer_price_index import FREDConsumerPriceIndexFetcher from openbb_fred.models.cp import FREDCommercialPaperFetcher from openbb_fred.models.dwpcr_rates import FREDDiscountWindowPrimaryCreditRateFetcher @@ -42,6 +43,7 @@ Research division of the Federal Reserve Bank of St. Louis that has more than credentials=["api_key"], fetcher_dict={ "BalanceOfPayments": FredBalanceOfPaymentsFetcher, + "BondIndices": FredBondIndicesFetcher, "ConsumerPriceIndex": FREDConsumerPriceIndexFetcher, "USYieldCurve": FREDUSYieldCurveFetcher, "SOFR": FREDSOFRFetcher, diff --git a/openbb_platform/providers/fred/openbb_fred/models/bond_indices.py b/openbb_platform/providers/fred/openbb_fred/models/bond_indices.py new file mode 100644 index 00000000000..345004e7ff3 --- /dev/null +++ b/openbb_platform/providers/fred/openbb_fred/models/bond_indices.py @@ -0,0 +1,588 @@ +"""FRED Bond Indices Model.""" + +# pylint: disable=unused-argument,too-many-statements,too-many-branches + +from typing import Any, Dict, List, Literal, Optional, Union +from warnings import warn + +from openbb_core.app.model.abstract.error import OpenBBError +from openbb_core.provider.abstract.annotated_result import AnnotatedResult +from openbb_core.provider.abstract.fetcher import Fetcher +from openbb_core.provider.standard_models.bond_indices import ( + BondIndicesData, + BondIndicesQueryParams, +) +from openbb_core.provider.utils.errors import EmptyDataError +from openbb_fred.models.series import FredSeriesFetcher +from pandas import Categorical, DataFrame +from pydantic import Field, PrivateAttr + +BAML_CATEGORIES = { + "high_yield": { + "us": { + "total_return": "BAMLHYH0A0HYM2TRIV", + "yield": "BAMLH0A0HYM2EY", + "oas": "BAMLH0A0HYM2", + "yield_to_worst": "BAMLH0A0HYM2SYTW", + }, + "europe": { + "total_return": "BAMLHE00EHYITRIV", + "yield": "BAMLHE00EHYIEY", + "oas": "BAMLHE00EHYIOAS", + "yield_to_worst": "BAMLHE00EHYISYTW", + }, + "emerging": { + "total_return": "BAMLEMHBHYCRPITRIV", + "yield": "BAMLEMHBHYCRPIEY", + "oas": "BAMLEMHBHYCRPIOAS", + "yield_to_worst": "BAMLEMHBHYCRPISYTW", + }, + }, + "us": { + "corporate": { + "total_return": "BAMLCC0A0CMTRIV", + "yield": "BAMLC0A0CMEY", + "oas": "BAMLC0A0CM", + "yield_to_worst": "BAMLC0A0CMSYTW", + }, + "high_yield": { + "total_return": "BAMLHYH0A0HYM2TRIV", + "yield": "BAMLH0A0HYM2EY", + "oas": "BAMLH0A0HYM2", + "yield_to_worst": "BAMLH0A0HYM2SYTW", + }, + "yield_curve": { + "year1_year3": { + "total_return": "BAMLCC1A013YTRIV", + "yield": "BAMLC1A0C13YEY", + "oas": "BAMLC1A0C13Y", + "yield_to_worst": "BAMLC1A0C13YSYTW", + }, + "year3_year5": { + "total_return": "BAMLCC2A035YTRIV", + "yield": "BAMLC2A0C35YEY", + "oas": "BAMLC2A0C35Y", + "yield_to_worst": "BAMLC2A0C35YSYTW", + }, + "year5_year7": { + "total_return": "BAMLCC3A057YTRIV", + "yield": "BAMLC3A0C57YEY", + "oas": "BAMLC3A0C57Y", + "yield_to_worst": "BAMLC3A0C57YSYTW", + }, + "year7_year10": { + "total_return": "BAMLCC4A0710YTRIV", + "yield": "BAMLC4A0C710YEY", + "oas": "BAMLC4A0C710Y", + "yield_to_worst": "BAMLC4A0C710YSYTW", + }, + "year10_year15": { + "total_return": "BAMLCC7A01015YTRIV", + "yield": "BAMLC7A0C1015YEY", + "oas": "BAMLC7A0C1015Y", + "yield_to_worst": "BAMLC7A0C1015YSYTW", + }, + "year15+": { + "total_return": "BAMLCC8A015PYTRIV", + "yield": "BAMLC8A0C15PYEY", + "oas": "BAMLC8A0C15PY", + "yield_to_worst": "BAMLC8A0C15PYSYTW", + }, + }, + "aaa": { + "total_return": "BAMLCC0A1AAATRIV", + "yield": "BAMLC0A1CAAAEY", + "oas": "BAMLC0A1CAAA", + "yield_to_worst": "BAMLC0A1CAAASYTW", + }, + "aa": { + "total_return": "BAMLCC0A2AATRIV", + "yield": "BAMLC0A2CAAEY", + "oas": "BAMLC0A2CAA", + "yield_to_worst": "BAMLC0A2CAASYTW", + }, + "a": { + "total_return": "BAMLCC0A3ATRIV", + "yield": "BAMLC0A3CAEY", + "oas": "BAMLC0A3CA", + "yield_to_worst": "BAMLC0A3CASYTW", + }, + "bbb": { + "total_return": "BAMLCC0A4BBBTRIV", + "yield": "BAMLC0A4CBBBEY", + "oas": "BAMLC0A4CBBB", + "yield_to_worst": "BAMLC0A4CBBBSYTW", + }, + "bb": { + "total_return": "BAMLHYH0A1BBTRIV", + "yield": "BAMLH0A1HYBBEY", + "oas": "BAMLH0A1HYBB", + "yield_to_worst": "BAMLH0A1HYBBSYTW", + }, + "b": { + "total_return": "BAMLHYH0A2BTRIV", + "yield": "BAMLH0A2HYBEY", + "oas": "BAMLH0A2HYB", + "yield_to_worst": "BAMLH0A2HYBSYTW", + }, + "ccc": { + "total_return": "BAMLHYH0A3CMTRIV", + "yield": "BAMLH0A3HYCEY", + "oas": "BAMLH0A3HYCC", + "yield_to_worst": "BAMLH0A3HYCCSYTW", + }, + }, + "emerging_markets": { + "corporate": { + "total_return": "BAMLEMCBPITRIV", + "yield": "BAMLEMCBPIEY", + "yield_to_worst": "BAMLEMCBPISYTW", + "oas": "BAMLEMCBPIOAS", + }, + "liquid_corporate": { + "total_return": "BAMLEMCLLCRPIUSTRIV", + "yield": "BAMLEMCLLCRPIUSEY", + "yield_to_worst": "BAMLEMCLLCRPIUSSYTW", + "oas": "BAMLEMCLLCRPIUSOAS", + }, + "crossover": { + "total_return": "BAMLEM5BCOCRPITRIV", + "yield": "BAMLEM5BCOCRPIEY", + "oas": "BAMLEM5BCOCRPIOAS", + "yield_to_worst": "BAMLEM5BCOCRPISYTW", + }, + "public_sector": { + "total_return": "BAMLEMPUPUBSLCRPIUSTRIV", + "yield": "BAMLEMPUPUBSLCRPIUSEY", + "oas": "BAMLEMPUPUBSLCRPIUSOAS", + "yield_to_worst": "BAMLEMPUPUBSLCRPIUSSYTW", + }, + "private_sector": { + "total_return": "BAMLEMFSFCRPITRIV", + "yield": "BAMLEMFSFCRPIEY", + "oas": "BAMLEMFSFCRPIOAS", + "yield_to_worst": "BAMLEMFSFCRPISYTW", + }, + "non_financial": { + "total_return": "BAMLEMNFNFLCRPIUSTRIV", + "yield": "BAMLEMNFNFLCRPIUSEY", + "oas": "BAMLEMNFNFLCRPIUSOAS", + "yield_to_worst": "BAMLEMNFNFLCRPIUSSYTW", + }, + "high_grade": { + "total_return": "BAMLEMIBHGCRPITRIV", + "yield": "BAMLEMIBHGCRPIEY", + "oas": "BAMLEMIBHGCRPIOAS", + "yield_to_worst": "BAMLEMIBHGCRPISYTW", + }, + "high_yield": { + "total_return": "BAMLEMHBHYCRPITRIV", + "yield": "BAMLEMHBHYCRPIEY", + "oas": "BAMLEMHBHYCRPIOAS", + "yield_to_worst": "BAMLEMHBHYCRPISYTW", + }, + "liquid_emea": { + "total_return": "BAMLEMELLCRPIEMEAUSTRIV", + "yield": "BAMLEMELLCRPIEMEAUSEY", + "oas": "BAMLEMELLCRPIEMEAUSOAS", + "yield_to_worst": "BAMLEMELLCRPIEMEAUSSYTW", + }, + "emea": { + "total_return": "BAMLEMRECRPIEMEATRIV", + "yield": "BAMLEMRECRPIEMEAEY", + "oas": "BAMLEMRECRPIEMEAOAS", + "yield_to_worst": "BAMLEMRECRPIEMEASYTW", + }, + "liquid_asia": { + "total_return": "BAMLEMALLCRPIASIAUSTRIV", + "yield": "BAMLEMALLCRPIASIAUSEY", + "oas": "BAMLEMALLCRPIASIAUSOAS", + "yield_to_worst": "BAMLEMALLCRPIASIAUSSYTW", + }, + "asia": { + "total_return": "BAMLEMRACRPIASIATRIV", + "yield": "BAMLEMRACRPIASIAEY", + "oas": "BAMLEMRACRPIASIAOAS", + "yield_to_worst": "BAMLEMRACRPIASIASYTW", + }, + "liquid_latam": { + "total_return": "BAMLEMLLLCRPILAUSTRIV", + "yield": "BAMLEMLLLCRPILAUSEY", + "oas": "BAMLEMLLLCRPILAUSOAS", + "yield_to_worst": "BAMLEMLLLCRPILAUSSYTW", + }, + "latam": { + "total_return": "BAMLEMRLCRPILATRIV", + "yield": "BAMLEMRLCRPILAEY", + "oas": "BAMLEMRLCRPILAOAS", + "yield_to_worst": "BAMLEMRLCRPILASYTW", + }, + "liquid_aaa": { + "total_return": "BAMLEM1RAAA2ALCRPIUSTRIV", + "yield": "BAMLEM1RAAA2ALCRPIUSEY", + "oas": "BAMLEM1RAAA2ALCRPIUSOAS", + "yield_to_worst": "BAMLEM1RAAA2ALCRPIUSSYTW", + }, + "liquid_bbb": { + "total_return": "BAMLEM2RBBBLCRPIUSTRIV", + "yield": "BAMLEM2RBBBLCRPIUSEY", + "oas": "BAMLEM2RBBBLCRPIUSOAS", + "yield_to_worst": "BAMLEM2RBBBLCRPIUSSYTW", + }, + "aaa": { + "total_return": "BAMLEM1BRRAAA2ACRPITRIV", + "yield": "BAMLEM1BRRAAA2ACRPIEY", + "oas": "BAMLEM1BRRAAA2ACRPIOAS", + "yield_to_worst": "BAMLEM1BRRAAA2ACRPISYTW", + }, + "bbb": { + "total_return": "BAMLEM2BRRBBBCRPITRIV", + "yield": "BAMLEM2BRRBBBCRPIEY", + "oas": "BAMLEM2BRRBBBCRPIOAS", + "yield_to_worst": "BAMLEM2BRRBBBCRPISYTW", + }, + "bb": { + "total_return": "BAMLEM3BRRBBCRPITRIV", + "yield": "BAMLEM3BRRBBCRPIEY", + "oas": "BAMLEM3BRRBBCRPIOAS", + "yield_to_worst": "BAMLEM3BRRBBCRPISYTW", + }, + "b": { + "total_return": "BAMLEM4BRRBLCRPITRIV", + "yield": "BAMLEM4BRRBLCRPIEY", + "oas": "BAMLEM4BRRBLCRPIOAS", + "yield_to_worst": "BAMLEM4BRRBLCRPISYTW", + }, + }, +} + +BamlCategories = Literal["high_yield", "us", "emerging_markets"] +INDEX_CHOICES = [ + "corporate", + "liquid_corporate", + "yield_curve", + "crossover", + "public_sector", + "private_sector", + "non_financial", + "high_grade", + "high_yield", + "liquid_emea", + "emea", + "liquid_asia", + "asia", + "liquid_latam", + "latam", + "liquid_aaa", + "liquid_bbb", + "aaa", + "aa", + "a", + "bbb", + "bb", + "b", + "ccc", +] + + +class FredBondIndicesQueryParams(BondIndicesQueryParams): + """FRED Bond Indices Query.""" + + __json_schema_extra__ = {"index": {"multiple_items_allowed": True}} + + category: BamlCategories = Field( + default="us", + description="The type of index category. Used in conjunction with 'index', default is 'us'.", + ) + index: str = Field( + default="yield_curve", + description="The specific index to query." + + " Used in conjunction with 'category' and 'index_type', default is 'yield_curve'.", + choices=sorted(INDEX_CHOICES), + ) + frequency: Union[ + None, + Literal[ + "a", + "q", + "m", + "w", + "d", + "wef", + "weth", + "wew", + "wetu", + "wem", + "wesu", + "wesa", + "bwew", + "bwem", + ], + ] = Field( + default=None, + description=""" + Frequency aggregation to convert daily data to lower frequency. + None = No change + a = Annual + 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 + """, + json_schema_extra={ + "choices": [ + "a", + "q", + "m", + "w", + "d", + "wef", + "weth", + "wew", + "wetu", + "wem", + "wesu", + "wesa", + "bwew", + "bwem", + ] + }, + ) + 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, default is 'avg'. + avg = Average + sum = Sum + eop = End of Period + """, + json_schema_extra={"choices": ["avg", "sum", "eop"]}, + ) + transform: Union[ + None, Literal["chg", "ch1", "pch", "pc1", "pca", "cch", "cca", "log"] + ] = Field( + default=None, + description=""" + Transformation type + None = 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 + """, + json_schema_extra={ + "choices": ["chg", "ch1", "pch", "pc1", "pca", "cch", "cca", "log"] + }, + ) + _symbols: Optional[str] = PrivateAttr(default=None) + + +class FredBondIndicesData(BondIndicesData): + """FRED Bond Indices Data.""" + + maturity: Optional[str] = Field( + default=None, + description="The maturity range of the bond index." + + " Only applicable when 'index' is 'yield_curve'.", + ) + title: str = Field( + description="The title of the index.", + ) + + +class FredBondIndicesFetcher( + Fetcher[ + FredBondIndicesQueryParams, + List[FredBondIndicesData], + ] +): + """FRED Bond Indices Fetcher.""" + + @staticmethod + def transform_query(params: Dict[str, Any]) -> FredBondIndicesQueryParams: + """Transform query.""" + values = params.copy() + new_index = [] + messages = [] + values.setdefault("index", "yield_curve") + values.setdefault("category", "us") + values.setdefault("index_type", "yield") + is_yield_curve = False + if "yield_curve" in values["index"]: + values["category"] = "us" + values["index"] = "yield_curve" + new_index.append("yield_curve") + is_yield_curve = True + if ( + isinstance(values["index"], list) + and len(values["index"] > 1) + or isinstance(values["index"], str) + and "," in values["index"] + ): + message = "Multiple indices not allowed for: 'yield_curve'." + messages.append(message) + if is_yield_curve is False: + indices = ( + values["index"] + if isinstance(values["index"], list) + else values["index"].split(",") + ) + for index in indices: + if values["category"] == "us": + if index not in BAML_CATEGORIES.get("us"): + message = ( + "Invalid index for category: 'us'." + + f" Must be one of {','.join(BAML_CATEGORIES.get('us'))}." + ) + messages.append(message) + else: + new_index.append(index) + if values["category"] == "high_yield": + if index not in ("us", "europe", "emerging"): + message = ( + "Invalid index for category: 'high_yield'." + + f" Must be one of {','.join(BAML_CATEGORIES.get('high_yield', ''))}." + ) + messages.append(message) + else: + new_index.append(index) + if values["category"] == "emerging_markets": + if index not in BAML_CATEGORIES.get("emerging_markets"): + message = ( + "Invalid index for category: 'emerging_markets'." + + f" Must be one of {','.join(BAML_CATEGORIES.get('emerging_markets', ''))}." + ) + messages.append(message) + else: + new_index.append(index) + if not new_index: + raise OpenBBError( + "No valid combinations of parameters were found." + + f"\n{','.join(messages) if messages else ''}" + ) + if messages: + warn(",".join(messages)) + + symbols: List = [] + if "yield_curve" in values["index"]: + maturities_dict = BAML_CATEGORIES[values["category"]][values["index"]] + maturities = list(maturities_dict) + symbols = [ + maturities_dict[item][values["index_type"]] for item in maturities + ] + else: + items = ( + values["index"] + if isinstance(values["index"], list) + else values["index"].split(",") + ) + symbols = [ + BAML_CATEGORIES[values["category"]] + .get(item, {}) + .get(values["index_type"]) + for item in items + ] + symbols = [symbol for symbol in symbols if symbol] + if not symbols: + raise OpenBBError( + "Error mapping the provided choices to series ID." + + f"\n{','.join(messages) if messages else ''}" + ) + values["index"] = ",".join(new_index) + new_params = FredBondIndicesQueryParams(**values) + new_params._symbols = ",".join(symbols) # pylint: disable=protected-access + + return new_params + + @staticmethod + async def aextract_data( + query: FredBondIndicesQueryParams, + credentials: Optional[Dict[str, str]], + **kwargs: Any, + ) -> Dict: + """Extract data.""" + api_key = credentials.get("fred_api_key") if credentials else "" + series_ids = query._symbols # pylint: disable=protected-access + credentials = {"fred_api_key": api_key} + item_query = dict( # pylint: disable=R1735 + symbol=series_ids, + start_date=query.start_date, + end_date=query.end_date, + frequency=query.frequency, + aggregation_method=query.aggregation_method, + ) + results: Dict = {} + temp = await FredSeriesFetcher.fetch_data(item_query, credentials) + result = [d.model_dump() for d in temp.result] + results["metadata"] = temp.metadata + results["data"] = result + + return results + + @staticmethod + def transform_data( + query: FredBondIndicesQueryParams, + data: Dict, + **kwargs: Any, + ) -> List[FredBondIndicesData]: + """Transform data.""" + if not data: + raise EmptyDataError("The request was returned empty.") + df = DataFrame.from_records(data["data"]) + if df.empty: + raise EmptyDataError( + "No data found for the given query. Try adjusting the parameters." + ) + # Flatten the data as a pivot table. + df = ( + df.melt(id_vars="date", var_name="symbol", value_name="value") + .query("value.notnull()") + .set_index(["date", "symbol"]) + .sort_index() + .reset_index() + ) + # Normalize the percent values. + if query.index_type != "total_return": + df["value"] = df["value"] / 100 + + titles_dict = { + symbol: data["metadata"][symbol].get("title") + for symbol in query._symbols.split(",") # pylint: disable=protected-access + } + df["title"] = df.symbol.map(titles_dict) + + if query.index == "yield_curve": + maturities_dict = BAML_CATEGORIES[query.category][query.index] + maturities = list(maturities_dict) + maturity_dict = { + maturities_dict[item][query.index_type]: item for item in maturities + } + df["maturity"] = df.symbol.map(maturity_dict) + df["maturity"] = Categorical( + df["maturity"], + categories=maturities, + ordered=True, + ) + df = df.sort_values(by=["date", "maturity"]).reset_index(drop=True) + + records = df.to_dict(orient="records") + metadata = data.get("metadata", {}) + + return AnnotatedResult( + result=[FredBondIndicesData.model_validate(r) for r in records], + metadata=metadata, + ) diff --git a/openbb_platform/providers/fred/tests/record/http/test_fred_fetchers/test_fred_bond_indices_fetcher.yaml b/openbb_platform/providers/fred/tests/record/http/test_fred_fetchers/test_fred_bond_indices_fetcher.yaml new file mode 100644 index 00000000000..068896b69a9 --- /dev/null +++ b/openbb_platform/providers/fred/tests/record/http/test_fred_fetchers/test_fred_bond_indices_fetcher.yaml @@ -0,0 +1,168 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + method: GET + uri: https://api.stlouisfed.org/fred/series/observations?aggregation_method=avg&api_key=MOCK_API_KEY&file_type=json&limit=100000&observation_end=2024-06-04&observation_start=2024-06-01&series_id=BAMLC0A0CMEY + response: + body: + string: !!binary | + H4sIAAAAAAAEA6pWKkpNzCnJzE2NLy5JLCpRslIyMjAy0TUw0zUwVdJByKbmpaDL5ScVpxaVJZZk + 5udhajZU0lFCVoCm30RJR6k0L7OkWMlKKSczD6S4tKSgtCS+pLIgVcnKUEcpLTMnFcpTyirOBysp + Skktik+qVLJCMTolsSRVSUepOL+oJD4fpETJSimxOFlJRyk5vzSvRMnKSEcpPy2tOLVEycpARykn + MzezRMnK0AAEUBxZrGQVTUmAgB2CFIDGSjpKZYk5palKVkqmeiYWSrU61DQeFIZIxgNmrFQbWwsA + QjFl2dABAAA= + headers: + Cache-Control: + - max-age=0, no-cache + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Length: + - '236' + Content-Type: + - application/json; charset=UTF-8 + Date: + - Thu, 06 Jun 2024 03:18:32 GMT + Expires: + - Thu, 06 Jun 2024 03:18:32 GMT + Last-Modified: + - Wed, 05 Jun 2024 12:43:05 GMT + Pragma: + - no-cache + Server: + - Apache + Set-Cookie: + - ak_bmsc=81940D9F2B9DA1F344016ADD98A66DDB~000000000000000000000000000000~YAAQRPAPFzQeWqOPAQAAjSOO6xjjISR3qU9p9ehCD7atzjNROSt0ALRbveNn0wXMB9gQK56uh0Xc1awlcFf3GpvzR24NvzsUh4Tip6bCOYUE3V7ijpdew8ar7pkb9+iovo+hcEff5STTUGwjvtpLhEjVfAhhZKN41Hc0FXWVyltY6sLcwskwRyqmSmgUToABM1HjfCo3Y17QWqLhSTJCHbsfAk9Hmkbt6xReN2Wy4vDBrBfYhbZJ5Ce0PAct8CtKXzwAWkAByJLv091DhYjuqNY5mck+sXgpj2fF+5uQ+QeQDhj3+tgq8+BdCLPe8j8LwGcIZzA26+1QquiWbzCLwBAyJC7+xhVymELNuty/K/nPCe5qM7KQOeB5N+KgVgKg; + Domain=.stlouisfed.org; Path=/; Expires=Thu, 06 Jun 2024 05:18:31 GMT; Max-Age=7199 + Strict-Transport-Security: + - max-age=86400 + Vary: + - Accept-Encoding + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Cookie: + - ak_bmsc=81940D9F2B9DA1F344016ADD98A66DDB~000000000000000000000000000000~YAAQRPAPFzQeWqOPAQAAjSOO6xjjISR3qU9p9ehCD7atzjNROSt0ALRbveNn0wXMB9gQK56uh0Xc1awlcFf3GpvzR24NvzsUh4Tip6bCOYUE3V7ijpdew8ar7pkb9+iovo+hcEff5STTUGwjvtpLhEjVfAhhZKN41Hc0FXWVyltY6sLcwskwRyqmSmgUToABM1HjfCo3Y17QWqLhSTJCHbsfAk9Hmkbt6xReN2Wy4vDBrBfYhbZJ5Ce0PAct8CtKXzwAWkAByJLv091DhYjuqNY5mck+sXgpj2fF+5uQ+QeQDhj3+tgq8+BdCLPe8j8LwGcIZzA26+1QquiWbzCLwBAyJC7+xhVymELNuty/K/nPCe5qM7KQOeB5N+KgVgKg + method: GET + uri: https://api.stlouisfed.org/fred/series?api_key=MOCK_API_KEY&file_type=json&series_id=BAMLC0A0CMEY + response: + body: + string: !!binary | + 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 + NrYWAA7lQACDGQAA + headers: + Cache-Control: + - max-age=0, no-cache + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Length: + - '3888' + Content-Type: + - application/json; charset=UTF-8 + Date: + - Thu, 06 Jun 2024 03:18:32 GMT + Expires: + - Thu, 06 Jun 2024 03:18:32 GMT + Last-Modified: + - Wed, 05 Jun 2024 12:43:05 GMT + Pragma: + - no-cache + Server: + - Apache + Set-Cookie: + - ak_bmsc=81940D9F2B9DA1F344016ADD98A66DDB~000000000000000000000000000000~YAAQRPAPF0QeWqOPAQAANySO6xggPtiJI26HwaQCFYSkTpJXySafy3DEM4vZNvm3exMUfozy6uVg66m5sv4oM9LlyFj+8yyVWg5wQr6vZx9LQHQPDOpxrchiU50SMAYsewvYmmftgNGBwdY/YeL8KU81mQ64Tg4YfKdXv2cG426TAtiQws2IrCCudBcvQ2/qYWVuuxXTlXqyZfr1f8OLre5KIjyHxzuWrHNSP6Jh8KFYLqCModUWeMAUo303zhvuIpUj6EfTKteFE9wfr7Kp4HpaclX7hI3tIkFNQOLFp3D/Szmg39r5sxc4G+x8w7CpPv1yVtGzDyz3LzMytMgrymB5V5xV2Et4u33N+v1tweP+66vcxlI0R0yFWM2lrtm5nKLZqh7NOR56NWk=; + Domain=.stlouisfed.org; Path=/; Expires=Thu, 06 Jun 2024 05:18:31 GMT; Max-Age=7199 + Strict-Transport-Security: + - max-age=86400 + Vary: + - Accept-Encoding + status: + code: 200 + message: OK +version: 1 diff --git a/openbb_platform/providers/fred/tests/test_fred_fetchers.py b/openbb_platform/providers/fred/tests/test_fred_fetchers.py index e6aba0b0d28..736098c7247 100644 --- a/openbb_platform/providers/fred/tests/test_fred_fetchers.py +++ b/openbb_platform/providers/fred/tests/test_fred_fetchers.py @@ -6,6 +6,7 @@ import pytest from openbb_core.app.service.user_service import UserService from openbb_fred.models.ameribor_rates import FREDAMERIBORFetcher from openbb_fred.models.balance_of_payments import FredBalanceOfPaymentsFetcher +from openbb_fred.models.bond_indices import FredBondIndicesFetcher from openbb_fred.models.consumer_price_index import FREDConsumerPriceIndexFetcher from openbb_fred.models.cp import FREDCommercialPaperFetcher from openbb_fred.models.dwpcr_rates import FREDDiscountWindowPrimaryCreditRateFetcher @@ -341,3 +342,18 @@ def test_fred_retail_prices_fetcher(credentials=test_credentials): fetcher = FredRetailPricesFetcher() result = fetcher.test(params, credentials) assert result is None + + +@pytest.mark.record_http +def test_fred_bond_indices_fetcher(credentials=test_credentials): + """Test FredBondIndicesFetcher.""" + params = { + "category": "us", + "index": "corporate", + "start_date": datetime.date(2024, 6, 1), + "end_date": datetime.date(2024, 6, 4), + } + + fetcher = FredBondIndicesFetcher() + result = fetcher.test(params, credentials) + assert result is None -- cgit v1.2.3 From aa7bccc4eccc67764ecd3a62f1e7c5f8323cbac0 Mon Sep 17 00:00:00 2001 From: Henrique Joaquim Date: Fri, 7 Jun 2024 11:57:23 +0100 Subject: [Feature] CLI logging (#6487) * remove old logging references * cli as logging subapp option * changing the subapp to the cli on init --- cli/openbb_cli/cli.py | 10 ++++++- cli/openbb_cli/controllers/cli