diff options
9 files changed, 220 insertions, 49 deletions
diff --git a/openbb_platform/extensions/technical/integration/test_technical_api.py b/openbb_platform/extensions/technical/integration/test_technical_api.py index 9c3f7f11ec9..2fec3060fa0 100644 --- a/openbb_platform/extensions/technical/integration/test_technical_api.py +++ b/openbb_platform/extensions/technical/integration/test_technical_api.py @@ -900,7 +900,7 @@ def test_technical_cg(params, data_type): "index": "date", "lower_q": "0.3", "upper_q": "0.7", - "model": "Parkinson", + "model": "parkinson", "is_crypto": "True", "trading_periods": "", }, diff --git a/openbb_platform/extensions/technical/integration/test_technical_python.py b/openbb_platform/extensions/technical/integration/test_technical_python.py index c060b3ec0d4..ab51354904e 100644 --- a/openbb_platform/extensions/technical/integration/test_technical_python.py +++ b/openbb_platform/extensions/technical/integration/test_technical_python.py @@ -844,7 +844,7 @@ def test_technical_cg(params, data_type, obb): "index": "date", "lower_q": "0.3", "upper_q": "0.7", - "model": "Parkinson", + "model": "parkinson", "is_crypto": "True", "trading_periods": "", }, diff --git a/openbb_platform/extensions/technical/openbb_technical/helpers.py b/openbb_platform/extensions/technical/openbb_technical/helpers.py index c6a22f3fd17..948a32fede3 100644 --- a/openbb_platform/extensions/technical/openbb_technical/helpers.py +++ b/openbb_platform/extensions/technical/openbb_technical/helpers.py @@ -383,12 +383,12 @@ def calculate_cones( upper_q: float, is_crypto: bool, model: Literal[ - "STD", - "Parkinson", - "Garman-Klass", - "Hodges-Tompkins", - "Rogers-Satchell", - "Yang-Zhang", + "std", + "parkinson", + "garman_klass", + "hodges_tompkins", + "rogers_satchell", + "yang_zhang", ], trading_periods: Optional[int] = None, ) -> pd.DataFrame: @@ -415,12 +415,12 @@ def calculate_cones( data = data.sort_index(ascending=True) model_functions = { - "STD": standard_deviation, - "Parkinson": parkinson, - "Garman-Klass": garman_klass, - "Hodges-Tompkins": hodges_tompkins, - "Rogers-Satchell": rogers_satchell, - "Yang-Zhang": yang_zhang, + "std": standard_deviation, + "parkinson": parkinson, + "garman_klass": garman_klass, + "hodges_tompkins": hodges_tompkins, + "rogers_satchell": rogers_satchell, + "yang_zhang": yang_zhang, } for window in windows: diff --git a/openbb_platform/extensions/technical/openbb_technical/technical_router.py b/openbb_platform/extensions/technical/openbb_technical/technical_router.py index 69405a3e3fe..e4faa20ef90 100644 --- a/openbb_platform/extensions/technical/openbb_technical/technical_router.py +++ b/openbb_platform/extensions/technical/openbb_technical/technical_router.py @@ -1530,10 +1530,10 @@ def cg( methods=["POST"], examples=[ PythonEx( - description="Get the cones indicator.", + description="Realized Volatility Cones.", code=[ - "stock_data = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp')", - "cones_data = obb.technical.cones(data=stock_data.results, lower_q=0.25, upper_q=0.75, model='STD')", + "stock_data = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='yfinance')", + "cones_data = obb.technical.cones(data=stock_data.results, lower_q=0.25, upper_q=0.75, model='std')", ], ), APIEx(parameters={"data": APIEx.mock_data("timeseries")}), @@ -1545,13 +1545,13 @@ def cones( lower_q: float = 0.25, upper_q: float = 0.75, model: Literal[ - "STD", - "Parkinson", - "Garman-Klass", - "Hodges-Tompkins", - "Rogers-Satchell", - "Yang-Zhang", - ] = "STD", + "std", + "parkinson", + "garman_klass", + "hodges_tompkins", + "rogers_satchell", + "yang_zhang", + ] = "std", is_crypto: bool = False, trading_periods: Optional[int] = None, ) -> OBBject[List[Data]]: @@ -1581,7 +1581,7 @@ def cones( The lower quantile value for calculations upper_q : float, optional The upper quantile value for calculations - model : Literal["STD", "Parkinson", "Garman-Klass", "Hodges-Tompkins", "Rogers-Satchell", "Yang-Zhang"], optional + model : Literal["std", "parkinson", "garman_klass", "hodges_tompkins", "rogers_satchell", "yang_zhang"], optional The model used to calculate realized volatility Standard deviation measures how widely returns are dispersed from the average return. diff --git a/openbb_platform/extensions/technical/tests/test_technical_helpers.py b/openbb_platform/extensions/technical/tests/test_technical_helpers.py index c653e6d98e0..82ef6919fed 100644 --- a/openbb_platform/extensions/technical/tests/test_technical_helpers.py +++ b/openbb_platform/extensions/technical/tests/test_technical_helpers.py @@ -81,7 +81,7 @@ def test_calculate_cones_with_mock_data(mock_data): lower_q=0.1, upper_q=0.9, is_crypto=False, - model="STD", + model="std", ) assert not result.empty 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 d5df11fdc63..2c5a8cbb47e 100644 --- a/openbb_platform/obbject_extensions/charting/integration/test_charting_api.py +++ b/openbb_platform/obbject_extensions/charting/integration/test_charting_api.py @@ -402,3 +402,35 @@ def test_charting_technical_zlma(params, headers): assert chart assert not fig assert list(chart.keys()) == ["content", "format"] + + +@parametrize( + "params", + [ + ( + { + "data": "", + "model": "yang_zhang", + "chart": True, + } + ) + ], +) +@pytest.mark.integration +def test_charting_technical_cones(params, headers): + """Test chart ta cones.""" + params = {p: v for p, v in params.items() if v} + body = json.dumps(get_equity_data()) + + query_str = get_querystring(params, []) + url = f"http://0.0.0.0:8000/api/v1/technical/cones?{query_str}" + result = requests.post(url, headers=headers, timeout=10, data=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 68d4e6a18b2..f15df117905 100644 --- a/openbb_platform/obbject_extensions/charting/integration/test_charting_python.py +++ b/openbb_platform/obbject_extensions/charting/integration/test_charting_python.py @@ -348,3 +348,30 @@ def test_charting_technical_zlma(params, obb): assert len(result.results) > 0 assert result.chart.content assert isinstance(result.chart.fig, OpenBBFigure) + + +@parametrize( + "params", + [ + ( + { + "data": "", + "model": "yang_zhang", + "chart": True, + } + ) + ], +) +@pytest.mark.integration +def test_charting_technical_cones(params, obb): + """Test chart ta cones.""" + params = {p: v for p, v in params.items() if v} + + params["data"] = get_equity_data() + + result = obb.technical.cones(**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 ebf29aa54f7..8ca3641e437 100644 --- a/openbb_platform/obbject_extensions/charting/openbb_charting/__init__.py +++ b/openbb_platform/obbject_extensions/charting/openbb_charting/__init__.py @@ -6,7 +6,6 @@ from typing import ( Dict, List, Optional, - Tuple, Union, ) @@ -18,7 +17,7 @@ from openbb_core.app.utils import basemodel_to_df, convert_to_basemodel from openbb_core.provider.abstract.data import Data from openbb_charting import charting_router -from openbb_charting.core.to_chart import ChartIndicators, OpenBBFigure, to_chart +from openbb_charting.core.to_chart import ChartIndicators, to_chart from openbb_charting.utils.helpers import get_charting_functions ext = Extension(name="charting") @@ -49,8 +48,8 @@ class Charting: self._obbject: OBBject = obbject self._charting_settings = ChartingSettings( - user_settings=self._obbject._user_settings, - system_settings=self._obbject._system_settings, + user_settings=self._obbject._user_settings, # type: ignore + system_settings=self._obbject._system_settings, # type: ignore ) self._handle_backend() @@ -185,17 +184,28 @@ class Charting: if has_data else self._obbject.to_dataframe() ) - fig, content = to_chart( - data_as_df, - indicators=indicators, - symbol=symbol, - candles=candles, - volume=volume, - prepost=prepost, - volume_ticks_x=volume_ticks_x, - ) - self._obbject.chart = Chart( - fig=fig, content=content, format=charting_router.CHART_FORMAT - ) - if render: - fig.show(**kwargs) + try: + fig, content = to_chart( + data_as_df, + indicators=indicators, + symbol=symbol, + candles=candles, + volume=volume, + prepost=prepost, + volume_ticks_x=volume_ticks_x, + ) + + self._obbject.chart = Chart( + fig=fig, content=content, format=charting_router.CHART_FORMAT + ) + if render: + fig.show(**kwargs) + + except Exception: + try: + if has_data: + self.show(data=data_as_df, symbol=symbol, render=render, **kwargs) + else: + self.show(**kwargs) + except Exception as e: + raise RuntimeError("Could not create chart from the OBBject.") from e 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 a80c1af8ad9..f13ba058623 100644 --- a/openbb_platform/obbject_extensions/charting/openbb_charting/charting_router.py +++ b/openbb_platform/obbject_extensions/charting/openbb_charting/charting_router.py @@ -1,18 +1,20 @@ """Charting router.""" -from typing import TYPE_CHECKING, Any, Dict, Tuple +from typing import Any, Dict, Tuple +import pandas as pd from openbb_core.app.model.charts.chart import ChartFormat from openbb_core.app.utils import basemodel_to_df -from .core.plotly_ta.ta_class import PlotlyTA +from openbb_charting.core.chart_style import ChartStyle +from openbb_charting.core.openbb_figure import OpenBBFigure +from openbb_charting.core.plotly_ta.ta_class import PlotlyTA CHART_FORMAT = ChartFormat.plotly -if TYPE_CHECKING: - from .core.openbb_figure import OpenBBFigure +# if TYPE_CHECKING: - # from .core.openbb_figure_table import OpenBBFigureTable +# from .core.openbb_figure_table import OpenBBFigureTable def equity_price_historical(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]: @@ -58,6 +60,7 @@ def _ta_ma(ma_type: str, **kwargs): False, volume=False, ) + fig.update_layout(ChartStyle().plotly_template.get("layout", {})) content = fig.show(external=True).to_plotly_json() return fig, content @@ -174,3 +177,102 @@ def technical_ema(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]: """Exponential moving average chart.""" ma_type = "ema" return _ta_ma(ma_type, **kwargs) + + +def technical_cones(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]: + """Volatility Cones Chart.""" + + data = kwargs.get("data") + + if isinstance(data, pd.DataFrame) and not data.empty and "window" in data.columns: + df_ta = data.set_index("window") + else: + df_ta = basemodel_to_df(kwargs["obbject_item"], index="window") + + df_ta.columns = [col.title().replace("_", " ") for col in df_ta.columns] + + # Check if the data is formatted as expected. + if not all(col in df_ta.columns for col in ["Realized", "Min", "Median", "Max"]): + raise ValueError("Data supplied does not match the expected format.") + + model = ( + str(kwargs.get("model")) + .replace("std", "Standard Deviation") + .replace("_", "-") + .title() + if kwargs.get("model") + else "Standard Deviation" + ) + + symbol = str(kwargs.get("symbol")) + " - " if kwargs.get("symbol") else "" + + title = ( + str(kwargs.get("title")) + if kwargs.get("title") + else f"{symbol}Realized Volatility Cones - {model} Model" + ) + + colors = [ + "green", + "red", + "burlywood", + "grey", + "orange", + "blue", + ] + color = 0 + + fig = OpenBBFigure() + + fig.update_layout(ChartStyle().plotly_template.get("layout", {})) + + text_color = "black" if ChartStyle().plt_style == "light" else "white" + + for col in df_ta.columns: + fig.add_scatter( + x=df_ta.index, + y=df_ta[col], + name=col, + mode="lines+markers", + hovertemplate=f"{col}: %{{y}}<extra></extra>", + marker=dict( + color=colors[color], + size=11, + ), + ) + color += 1 + + fig.set_title(title) + + fig.update_layout( + paper_bgcolor="rgba(0,0,0,0)", + plot_bgcolor="rgba(0,0,0,0)", + font=dict(color=text_color), + legend=dict( + orientation="h", + yanchor="bottom", + xanchor="right", + y=1.02, + x=1, + bgcolor="rgba(0,0,0,0)", + ), + yaxis=dict( + ticklen=0, + ), + xaxis=dict( + type="category", + tickmode="array", + ticklen=0, + tickvals=df_ta.index, + ticktext=df_ta.index, + title_text="Period", + showgrid=False, + zeroline=False, + ), + margin=dict(l=20, r=20, b=20), + dragmode="pan", + ) + + content = fig.to_plotly_json() + + return fig, content |