diff options
author | Danglewood <85772166+deeleeramone@users.noreply.github.com> | 2024-05-21 10:02:19 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-21 17:02:19 +0000 |
commit | 8baf4d74b23fdeb3e6bfcaa8d648c3a3a9deb6b6 (patch) | |
tree | 10929fda4b1cbc7978cca683977e9e59c88c8ac2 | |
parent | 63b8d6070470745a2d3723707efc359d5d48c63d (diff) |
[Feature] Government Yield Curves + Chart (#6417)
* stashing
* ecb yield curve
* field_description
* federal reserve yield curve
* econdb yield curve
* countries literal for python 3.9
* list date
* fmp yield curve
* fred yield curve
* integration tests
* chart integration tests
* static assets
* black
* ruff
* pylint
* more pylint
* more pylint
* now black
* country not a standard param
* mypy
* merge branch develop
* add more countries
* econdb date filter
* mypy
* future linting
* make to_chart work
* plot_bgcolor should be 'rbga(255,255,255,0)'
* push deprecation
* push deprecation
---------
Co-authored-by: Igor Radovanovic <74266147+IgorWounds@users.noreply.github.com>
Co-authored-by: hjoaquim <henriquecjoaquim@gmail.com>
36 files changed, 139102 insertions, 183 deletions
diff --git a/openbb_platform/core/openbb_core/provider/standard_models/yield_curve.py b/openbb_platform/core/openbb_core/provider/standard_models/yield_curve.py new file mode 100644 index 00000000000..97909e40804 --- /dev/null +++ b/openbb_platform/core/openbb_core/provider/standard_models/yield_curve.py @@ -0,0 +1,37 @@ +"""Yield Curve Standard Model.""" + +from datetime import date as dateType +from typing import Optional + +from pydantic import Field + +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 YieldCurveQueryParams(QueryParams): + """Yield Curve Query.""" + + date: Optional[str] = Field( + default=None, + description=QUERY_DESCRIPTIONS.get("date", "") + + " By default is the current data.", + ) + + +class YieldCurveData(Data): + """Yield Curve Data.""" + + date: Optional[dateType] = Field( + default=None, + description=DATA_DESCRIPTIONS.get("date", ""), + ) + maturity: str = Field(description="Maturity length of the security.") + rate: float = Field( + description="The yield as a normalized percent (0.05 is 5%)", + json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100}, + ) diff --git a/openbb_platform/extensions/fixedincome/integration/test_fixedincome_api.py b/openbb_platform/extensions/fixedincome/integration/test_fixedincome_api.py index da9c88c77c4..45e7c86a685 100644 --- a/openbb_platform/extensions/fixedincome/integration/test_fixedincome_api.py +++ b/openbb_platform/extensions/fixedincome/integration/test_fixedincome_api.py @@ -623,3 +623,46 @@ def test_fixedincome_corporate_bond_prices(params, headers): result = requests.get(url, headers=headers, timeout=40) assert isinstance(result, requests.Response) assert result.status_code == 200 + + +@parametrize( + "params", + [ + ({"date": "2023-05-01,2024-05-01", "provider": "fmp"}), + ( + { + "date": "2023-05-01", + "country": "united_kingdom", + "provider": "econdb", + "use_cache": True, + } + ), + ( + { + "provider": "ecb", + "yield_curve_type": "par_yield", + "date": None, + "rating": "aaa", + "use_cache": True, + } + ), + ( + { + "provider": "fred", + "yield_curve_type": "nominal", + "date": "2023-05-01,2024-05-01", + } + ), + ({"provider": "federal_reserve", "date": "2023-05-01,2024-05-01"}), + ], +) +@pytest.mark.integration +def test_fixedincome_government_yield_curve(params, headers): + """Test the treasury rates 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/government/yield_curve?{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 57ba30d7fad..3efa8727269 100644 --- a/openbb_platform/extensions/fixedincome/integration/test_fixedincome_python.py +++ b/openbb_platform/extensions/fixedincome/integration/test_fixedincome_python.py @@ -578,3 +578,45 @@ def test_fixedincome_corporate_bond_prices(params, obb): assert result assert isinstance(result, OBBject) assert len(result.results) > 0 + + +@parametrize( + "params", + [ + ({"date": "2023-05-01,2024-05-01", "provider": "fmp"}), + ( + { + "date": "2023-05-01", + "country": "united_kingdom", + "provider": "econdb", + "use_cache": True, + } + ), + ( + { + "provider": "ecb", + "yield_curve_type": "par_yield", + "date": None, + "rating": "aaa", + "use_cache": True, + } + ), + ( + { + "provider": "fred", + "yield_curve_type": "nominal", + "date": "2023-05-01,2024-05-01", + } + ), + ({"provider": "federal_reserve", "date": "2023-05-01,2024-05-01"}), + ], +) +@pytest.mark.integration +def test_fixedincome_government_yield_curve(params, obb): + """Test the government yield curve endpoint.""" + params = {p: v for p, v in params.items() if v} + + result = obb.fixedincome.government.yield_curve(**params) + assert result + assert isinstance(result, OBBject) + assert len(result.results) > 0 diff --git a/openbb_platform/extensions/fixedincome/openbb_fixedincome/government/government_router.py b/openbb_platform/extensions/fixedincome/openbb_fixedincome/government/government_router.py index 7d7a325eb7a..641afb26577 100644 --- a/openbb_platform/extensions/fixedincome/openbb_fixedincome/government/government_router.py +++ b/openbb_platform/extensions/fixedincome/openbb_fixedincome/government/government_router.py @@ -1,5 +1,6 @@ """Fixed Income Government 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 @@ -17,7 +18,45 @@ router = Router(prefix="/government") @router.command( + model="YieldCurve", + examples=[ + APIEx(parameters={"provider": "federal_reserve"}), + APIEx(parameters={"date": "2023-05-01,2024-05-01", "provider": "fmp"}), + APIEx( + parameters={ + "date": "2023-05-01", + "country": "united_kingdom", + "provider": "econdb", + } + ), + APIEx(parameters={"provider": "ecb", "yield_curve_type": "par_yield"}), + APIEx( + parameters={ + "provider": "fred", + "yield_curve_type": "real", + "date": "2023-05-01,2024-05-01", + } + ), + ], +) +async def yield_curve( + cc: CommandContext, + provider_choices: ProviderChoices, + standard_params: StandardParams, + extra_params: ExtraParams, +) -> OBBject: # type: ignore + """Get yield curve data by country and date.""" + return await OBBject.from_query(Query(**locals())) + + +@router.command( model="USYieldCurve", + deprecated=True, + deprecation=OpenBBDeprecationWarning( + message="This endpoint will be removed in a future version. Use, `/fixedincome/government/yield_curve`, instead.", + since=(4, 2), + expected_removal=(4, 4), + ), examples=[ APIEx(parameters={"provider": "fred"}), APIEx(parameters={"inflation_adjusted": True, "provider": "fred"}), @@ -35,6 +74,12 @@ async def us_yield_curve( @router.command( model="EUYieldCurve", + deprecated=True, + deprecation=OpenBBDeprecationWarning( + message="This endpoint will be removed in a future version. Use, `/fixedincome/government/yield_curve`, instead.", + since=(4, 2), + expected_removal=(4, 4), + ), examples=[ APIEx(parameters={"provider": "ecb"}), APIEx(parameters={"yield_curve_type": "spot_rate", "provider": "ecb"}), diff --git a/openbb_platform/obbject_extensions/charting/integration/test_charting_api.py b/openbb_platform/obbject_extensions/charting/integration/test_charting_api.py index 5066d5a40b7..fc9f2b0b646 100644 --- a/openbb_platform/obbject_extensions/charting/integration/test_charting_api.py +++ b/openbb_platform/obbject_extensions/charting/integration/test_charting_api.py @@ -719,3 +719,42 @@ def test_charting_etf_holdings(params, headers): assert chart assert not fig assert list(chart.keys()) == ["content", "format"] + + +@parametrize( + "params", + [ + ( + { + "provider": "econdb", + "country": "united_kingdom", + "date": None, + "chart": True, + } + ), + ( + { + "provider": "fred", + "date": "2023-05-10,2024-05-10", + "chart": True, + } + ), + ], +) +@pytest.mark.integration +def test_charting_fixedincome_government_yield_curve(params, headers): + """Test chart fixedincome government yield curve.""" + params = {p: v for p, v in params.items() if v} + body = (json.dumps({"extra_params": {"chart_params": {"title": "test chart"}}}),) + query_str = get_querystring(params, []) + url = f"http://0.0.0.0:8000/api/v1/fixedincome/government/yield_curve?{query_str}" + result = requests.get(url, headers=headers, timeout=10, json=body) + assert isinstance(result, requests.Response) + assert result.status_code == 200 + + chart = result.json()["chart"] + fig = chart.pop("fig", {}) + + assert chart + assert not fig + assert list(chart.keys()) == ["content", "format"] diff --git a/openbb_platform/obbject_extensions/charting/integration/test_charting_python.py b/openbb_platform/obbject_extensions/charting/integration/test_charting_python.py index f8bc9275e59..b07b5792de8 100644 --- a/openbb_platform/obbject_extensions/charting/integration/test_charting_python.py +++ b/openbb_platform/obbject_extensions/charting/integration/test_charting_python.py @@ -590,3 +590,34 @@ def test_charting_etf_holdings(params, obb): assert len(result.results) > 0 assert result.chart.content assert isinstance(result.chart.fig, OpenBBFigure) + + +@parametrize( + "params", + [ + ( + { + "provider": "econdb", + "country": "united_kingdom", + "date": None, + "chart": True, + } + ), + ( + { + "provider": "fred", + "date": "2023-05-10,2024-05-10", + "chart": True, + } + ), + ], +) +@pytest.mark.integration +def test_charting_fixedincome_government_yield_curve(params, obb): + """Test chart fixedincome government yield curve.""" + result = obb.fixedincome.government.yield_curve(**params) + assert result + assert isinstance(result, OBBject) + assert len(result.results) > 0 + assert result.chart.content + assert isinstance(result.chart.fig, OpenBBFigure) diff --git a/openbb_platform/obbject_extensions/charting/openbb_charting/__init__.py b/openbb_platform/obbject_extensions/charting/openbb_charting/__init__.py index 913849cd41e..b2e5efcfd4a 100644 --- a/openbb_platform/obbject_extensions/charting/openbb_charting/__init__.py +++ b/openbb_platform/obbject_extensions/charting/openbb_charting/__init__.py @@ -459,6 +459,9 @@ class Charting: ) kwargs["provider"] = self._obbject.provider # pylint: disable=protected-access kwargs["extra"] = self._obbject.extra # pylint: disable=protected-access + kwargs["extra_params"] = kwargs["extra"]["metadata"].arguments.get( + "extra_params" + ) if "kwargs" in kwargs: _kwargs = kwargs.pop("kwargs") kwargs.update(_kwargs.get("chart_params", {})) diff --git a/openbb_platform/obbject_extensions/charting/openbb_charting/charting_router.py b/openbb_platform/obbject_extensions/charting/openbb_charting/charting_router.py index 43e534ce2f9..94dcb63ca45 100644 --- a/openbb_platform/obbject_extensions/charting/openbb_charting/charting_router.py +++ b/openbb_platform/obbject_extensions/charting/openbb_charting/charting_router.py @@ -8,6 +8,7 @@ from warnings import warn import pandas as pd from openbb_core.app.model.charts.chart import ChartFormat from openbb_core.app.utils import basemodel_to_df +from openbb_core.provider.abstract.data import Data from plotly.graph_objs import Figure from openbb_charting.core.chart_style import ChartStyle @@ -19,6 +20,7 @@ from openbb_charting.utils import relative_rotation from openbb_charting.utils.generic_charts import bar_chart from openbb_charting.utils.helpers import ( calculate_returns, + duration_sorter, heikin_ashi, should_share_axis, z_score_standardization, @@ -403,7 +405,7 @@ def equity_price_historical( # noqa: PLR0912 name=data[col].name, mode="lines", hovertemplate=hovertemplate, - line=dict(width=1, color=LARGE_CYCLER[i % len(LARGE_CYCLER)]), + line=dict(width=2, color=LARGE_CYCLER[i % len(LARGE_CYCLER)]), yaxis=yaxis, ) @@ -1106,7 +1108,7 @@ def economy_fred_series( # noqa: PLR0912 name=df_ta[col].name, mode="lines", hovertemplate=f"{df_ta[col].name}: %{{y}}<extra></extra>", - line=dict(width=1, color=LARGE_CYCLER[i % len(LARGE_CYCLER)]), + line=dict(width=2, color=LARGE_CYCLER[i % len(LARGE_CYCLER)]), yaxis="y1" if kwargs.get("same_axis") is True else yaxes, ) @@ -1239,7 +1241,7 @@ def technical_relative_rotation( ratios_df, momentum_df, benchmark_symbol, study, date # type: ignore ) - figure = OpenBBFigure(fig) + figure = OpenBBFigure(fig) # pylint: disable=E0606 font_color = "black" if ChartStyle().plt_style == "light" else "white" figure.update_layout( paper_bgcolor="rgba(0,0,0,0)", @@ -1278,3 +1280,167 @@ def technical_relative_rotation( content = figure.to_plotly_json() return figure, content + + +def fixedincome_government_yield_curve( # noqa: PLR0912 + **kwargs, +) -> Tuple[OpenBBFigure, Dict[str, Any]]: + """Government Yield Curve Chart.""" + data = kwargs.get("data", None) + df: pd.DataFrame = pd.DataFrame() + if data: + if isinstance(data, pd.DataFrame) and not data.empty: # noqa: SIM108 + df = data + elif isinstance(data, (list, Data)): + df = basemodel_to_df(data, index=None) # type: ignore + else: + pass + else: + df = pd.DataFrame([d.model_dump() for d in kwargs["obbject_item"]]) # type: ignore + + if df.empty: + raise ValueError("Error: No data to plot.") + + if "maturity" not in df.columns: + raise ValueError("Error: Maturity column not found in the data.") + + if "rate" not in df.columns: + raise ValueError("Error: Rate column not found in the data.") + + if "date" not in df.columns: + raise ValueError("Error: Date column not found in the data.") + + provider = kwargs.get("provider") + df["date"] = df["date"].astype(str) + maturities = duration_sorter(df["maturity"].unique().tolist()) + + # Use the supplied colors, if any. + colors = kwargs.get("colors", []) + if not colors: + colors = LARGE_CYCLER + color_count = 0 + + figure = OpenBBFigure().create_subplots(shared_xaxes=True) + figure.update_layout(ChartStyle().plotly_template.get("layout", {})) + + def create_fig(figure, df, dates, color_count, country: Optional[str] = None): + """Create a scatter for each date in the data.""" + for date in dates: + color = colors[color_count % len(colors)] + plot_df = df[df["date"] == date].copy() + plot_df["ra |