diff options
author | Danglewood <85772166+deeleeramone@users.noreply.github.com> | 2024-04-10 05:36:10 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-10 12:36:10 +0000 |
commit | 951749156dae3029c43364887c332036589aad7e (patch) | |
tree | 516efd0389909fd69709155edf11c363b6080f0b | |
parent | 72e322fd01133db0d087a35fb53268efb11e8253 (diff) |
[Enhancement] Improve Charting Extension Plots and Indicators (#6243)
* improve historical prices charts
* ruff
* correction in docstring.
* forgot crypto in the router
* pylint
* that one shouldn't have changed.
* pylint again
* and black
* add flag for heikin_ashi
* better kwarg
* ruff
* tests
* update warnings catch
* ruff
* pylint
* don't cast warning when parameter is 'chart_params'
* chart params
* start backend only when the chart is about the be created
* get_params
* add generic line chart method for unsupported endpoints and external data
* return None when render
* query params
* don't add volume to candle legend
* better moving averages
* commit some updates
* add the __init__ file..
* exception handling
* generic charts style
* forgot to remove that print statement
* codespell
* generic bar charts
* group bar charts
* don't raise params warning when 'chart_params' in extra_params
* price performance chart
* black
* ruff
* typing things
* etf price performance chart
* don't warn when 'chart_params' exists
* black
* pylint
* pylint
* pylint
* mypy
* black
* etf holdings chart
* mypy
* bar charts for fred
* pylint
* some review things
* codespell
* pylint
* apply metadata update
* unused import
* typing things
* ruff
* weird title thing
* docstring
* backend syntax
* black
* pylint
* duplicated imports outside of top level
* start backend in constructor
* more linters
* now black
* more linter
* update warning ignore line
* Charting class doctstring
* indicator edge case
* pylint
* that was annoying
* forgot macd indicators query params
* kc default setting
---------
Co-authored-by: Igor Radovanovic <74266147+IgorWounds@users.noreply.github.com>
Co-authored-by: Henrique Joaquim <henriquecjoaquim@gmail.com>
25 files changed, 3357 insertions, 1224 deletions
diff --git a/openbb_platform/core/openbb_core/app/command_runner.py b/openbb_platform/core/openbb_core/app/command_runner.py index 233d7be014f..a8c368839f8 100644 --- a/openbb_platform/core/openbb_core/app/command_runner.py +++ b/openbb_platform/core/openbb_core/app/command_runner.py @@ -1,5 +1,7 @@ """Command runner module.""" +# pylint: disable=R0903 + from copy import deepcopy from dataclasses import asdict, is_dataclass from datetime import datetime @@ -193,6 +195,8 @@ class ParametersBuilder: ): valid = asdict(annotation()) # type: ignore for p in extra_params: + if "chart_params" in p: + continue if p not in valid: warn( message=f"Parameter '{p}' not found.", @@ -225,7 +229,7 @@ class ParametersBuilder: } # We allow extra fields to return with model with 'cc: CommandContext' config = ConfigDict(extra="allow", arbitrary_types_allowed=True) - ValidationModel = create_model(func.__name__, __config__=config, **fields) # type: ignore + ValidationModel = create_model(func.__name__, __config__=config, **fields) # type: ignore # pylint: disable=C0103 # Validate and coerce model = ValidationModel(**kwargs) ParametersBuilder._warn_kwargs( @@ -234,6 +238,7 @@ class ParametersBuilder: ) return dict(model) + # pylint: disable=R0913 @classmethod def build( cls, @@ -274,6 +279,7 @@ class ParametersBuilder: return kwargs +# pylint: disable=too-few-public-methods class StaticCommandRunner: """Static Command Runner.""" @@ -282,6 +288,7 @@ class StaticCommandRunner: cls, func: Callable, kwargs: Dict[str, Any], + show_warnings: bool = True, # pylint: disable=unused-argument # type: ignore ) -> OBBject: """Run a command and return the output.""" obbject = await maybe_coroutine(func, **kwargs) @@ -301,8 +308,26 @@ class StaticCommandRunner: raise OpenBBError( "Charting is not installed. Please install `openbb-charting`." ) - obbject.charting.show(render=False, **kwargs) # type: ignore + chart_params = {} + extra_params = kwargs.get("extra_params", {}) + + if hasattr(extra_params, "__dict__") and hasattr(extra_params, "chart_params"): + chart_params = kwargs["extra_params"].__dict__.get("chart_params", {}) + elif isinstance(extra_params, dict) and "chart_params" in extra_params: + chart_params = kwargs["extra_params"].get("chart_params", {}) + + if "chart_params" in kwargs: + chart_params.update(kwargs.pop("chart_params", {})) + + if "kwargs" in kwargs: + chart_params.update(kwargs.pop("kwargs", {}).get("chart_params", {})) + + if chart_params: + kwargs.update(chart_params) + + obbject.charting.show(render=False, **kwargs) + # pylint: disable=R0913, R0914 @classmethod async def _execute_func( cls, @@ -382,6 +407,7 @@ class StaticCommandRunner: ) return obbject + # pylint: disable=W0718 @classmethod async def run( cls, diff --git a/openbb_platform/extensions/technical/openbb_technical/technical_router.py b/openbb_platform/extensions/technical/openbb_technical/technical_router.py index 2be6483df09..f9b5b3ac3be 100644 --- a/openbb_platform/extensions/technical/openbb_technical/technical_router.py +++ b/openbb_platform/extensions/technical/openbb_technical/technical_router.py @@ -489,7 +489,7 @@ def aroon( data: List[Data], index: str = "date", length: int = 25, - scalar: int = 100, + scalar: float = 100, ) -> OBBject[List[Data]]: """Calculate the Aroon Indicator. @@ -513,7 +513,7 @@ def aroon( Index column name to use with `data`, by default "date". length : int, optional Number of periods to be used for the calculation, by default 25. - scalar : int, optional + scalar : float, optional Scalar to be used for the calculation, by default 100. Returns 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 19a0e6c880f..ab23e816822 100644 --- a/openbb_platform/obbject_extensions/charting/integration/test_charting_api.py +++ b/openbb_platform/obbject_extensions/charting/integration/test_charting_api.py @@ -51,7 +51,7 @@ def get_equity_data(): [ ( { - "provider": "fmp", + "provider": "yfinance", "symbol": "AAPL", "chart": True, } @@ -60,7 +60,7 @@ def get_equity_data(): ) @pytest.mark.integration def test_charting_equity_price_historical(params, headers): - """Test chart equity load.""" + """Test chart equity price historical..""" params = {p: v for p, v in params.items() if v} query_str = get_querystring(params, []) @@ -82,6 +82,130 @@ def test_charting_equity_price_historical(params, headers): [ ( { + "provider": "yfinance", + "symbol": "USDGBP", + "chart": True, + } + ), + ], +) +@pytest.mark.integration +def test_charting_currency_price_historical(params, headers): + """Test chart currency price historical.""" + 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/currency/price/historical?{query_str}" + result = requests.get(url, headers=headers, timeout=40) + 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"] + + +@parametrize( + "params", + [ + ( + { + "provider": "yfinance", + "symbol": "QQQ", + "chart": True, + } + ), + ], +) +@pytest.mark.integration +def test_charting_etf_historical(params, headers): + """Test chart etf historical.""" + 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/etf/historical?{query_str}" + result = requests.get(url, headers=headers, timeout=40) + 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"] + + +@parametrize( + "params", + [ + ( + { + "provider": "yfinance", + "symbol": "NDX", + "chart": True, + } + ), + ], +) +@pytest.mark.integration +def test_charting_index_price_historical(params, headers): + """Test chart index price historical.""" + 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/index/price/historical?{query_str}" + result = requests.get(url, headers=headers, timeout=40) + 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"] + + +@parametrize( + "params", + [ + ( + { + "provider": "yfinance", + "symbol": "BTCUSD", + "chart": True, + } + ), + ], +) +@pytest.mark.integration +def test_charting_crypto_price_historical(params, headers): + """Test chart crypto price historical.""" + 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/crypto/price/historical?{query_str}" + result = requests.get(url, headers=headers, timeout=40) + 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"] + + +@parametrize( + "params", + [ + ( + { "data": "", "index": "date", "length": "60", @@ -446,3 +570,107 @@ def test_charting_economy_fred_series(params, headers): assert chart assert not fig assert list(chart.keys()) == ["content", "format"] + + +@parametrize( + "params", + [ + ( + { + "data": None, + "symbol": "XRT,XLB,XLI,XLH,XLC,XLY,XLU,XLK", + "chart": True, + "provider": "finviz", + } + ) + ], +) +@pytest.mark.integration +def test_charting_equity_price_performance(params, headers): + """Test chart equity price performance.""" + params = {p: v for p, v in params.items() if v} + body = ( + json.dumps( + {"extra_params": {"chart_params": {"limit": 4, "orientation": "h"}}} + ), + ) + query_str = get_querystring(params, []) + url = f"http://0.0.0.0:8000/api/v1/equity/price/performance?{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"] + + +@parametrize( + "params", + [ + ( + { + "data": None, + "symbol": "XRT,XLB,XLI,XLH,XLC,XLY,XLU,XLK", + "chart": True, + "provider": "intrinio", + } + ) + ], +) +@pytest.mark.integration +def test_charting_etf_price_performance(params, headers): + """Test chart equity price performance.""" + params = {p: v for p, v in params.items() if v} + body = (json.dumps({"extra_params": {"chart_params": {"orientation": "v"}}}),) + query_str = get_querystring(params, []) + url = f"http://0.0.0.0:8000/api/v1/etf/price_performance?{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"] + + +@parametrize( + "params", + [ + ( + { + "data": None, + "symbol": "XRT", + "chart": True, + "provider": "fmp", + } + ) + ], +) +@pytest.mark.integration +def test_charting_etf_holdings(params, headers): + """Test chart etf holdings.""" + params = {p: v for p, v in params.items() if v} + body = ( + json.dumps( + {"extra_params": {"chart_params": {"orientation": "v", "limit": 10}}} + ), + ) + query_str = get_querystring(params, []) + url = f"http://0.0.0.0:8000/api/v1/etf/holdings?{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 1995eabf011..9349f826e95 100644 --- a/openbb_platform/obbject_extensions/charting/integration/test_charting_python.py +++ b/openbb_platform/obbject_extensions/charting/integration/test_charting_python.py @@ -42,7 +42,7 @@ def get_equity_data(): [ ( { - "provider": "fmp", + "provider": "yfinance", "symbol": "AAPL", "chart": True, } @@ -65,6 +65,98 @@ def test_charting_equity_price_historical(params, obb): [ ( { + "provider": "yfinance", + "symbol": "JPYUSD", + "chart": True, + } + ), + ], +) +@pytest.mark.integration +def test_charting_currency_price_historical(params, obb): + """Test chart currency price historical.""" + result = obb.currency.price.historical(**params) + assert result + assert isinstance(result, OBBject) + assert len(result.results) > 0 + assert result.chart.content + assert isinstance(result.chart.fig, OpenBBFigure) + + +@parametrize( + "params", + [ + ( + { + "provider": "yfinance", + "symbol": "BTCUSD", + "chart": True, + } + ), + ], +) +@pytest.mark.integration +def test_charting_crypto_price_historical(params, obb): + """Test chart crypto price historical.""" + result = obb.crypto.price.historical(**params) + assert result + assert isinstance(result, OBBject) + assert len(result.results) > 0 + assert result.chart.content + assert isinstance(result.chart.fig, OpenBBFigure) + + +@parametrize( + "params", + [ + ( + { + "provider": "yfinance", + "symbol": "NDX", + "chart": True, + } + ), + ], +) +@pytest.mark.integration +def test_charting_index_price_historical(params, obb): + """Test chart index price historical.""" + result = obb.index.price.historical(**params) + assert result + assert isinstance(result, OBBject) + assert len(result.results) > 0 + assert result.chart.content + assert isinstance(result.chart.fig, OpenBBFigure) + + +@parametrize( + "params", + [ + ( + { + "provider": "yfinance", + "symbol": "QQQ", + "chart": True, + } + ), + ], +) +@pytest.mark.integration +def test_charting_etf_historical(params, obb): + """Test chart etf historical.""" + result = obb.etf.historical(**params) + assert result + assert isinstance(result, OBBject) + assert len(result.results) > 0 + assert result.chart.content + assert isinstance(result.chart.fig, OpenBBFigure) + + +@parametrize( + "params", + [ + ( + { "data": "", "index": "date", "length": "60", @@ -381,3 +473,78 @@ def test_charting_economy_fred_series(params, obb): assert len(result.results) > 0 assert result.chart.content assert isinstance(result.chart.fig, OpenBBFigure) + + +@parametrize( + "params", + [ + ( + { + "data": None, + "symbol": "XRT,XLB,XLI,XLH,XLC,XLY,XLU,XLK", + "chart": True, + "provider": "finviz", + "chart_params": {"limit": 4, "orientation": "h"}, + } + ) + ], +) +@pytest.mark.integration +def test_charting_equity_price_performance(params, obb): + """Test chart equity price performance.""" + result = obb.equity.price.performance(**params) + assert result + assert isinstance(result, OBBject) + assert len(result.results) > 0 + assert result.chart.content + assert isinstance(result.chart.fig, OpenBBFigure) + + +@parametrize( + "params", + [ + ( + { + "data": None, + "symbol": "XRT,XLB,XLI,XLH,XLC,XLY,XLU,XLK", + "chart": True, + "provider": "intrinio", + "chart_params": {"orientation": "v"}, + } + ) + ], +) +@pytest.mark.integration +def test_charting_etf_price_performance(params, obb): + """Test chart etf price performance.""" + result = obb.etf.price_performance(**params) + assert result + assert isinstance(result, OBBject) + assert len(result.results) > 0 + assert result.chart.content + assert isinstance(result.chart.fig, OpenBBFigure) + + +@parametrize( + "params", + [ + ( + { + "data": None, + "symbol": "XRT", + "chart": True, + "provider": "fmp", + "chart_params": {"orientation": "v", "limit": 10}, + } + ) + ], +) +@pytest.mark.integration +def test_charting_etf_holdings(params, obb): + """Test chart etf holdings.""" + result = obb.etf.holdings(**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 5e14f028d6f..ac64c497fae 100644 --- a/openbb_platform/obbject_extensions/charting/openbb_charting/__init__.py +++ b/openbb_platform/obbject_extensions/charting/openbb_charting/__init__.py @@ -1,10 +1,14 @@ """OpenBB OBBject extension for charting.""" +# pylint: disable=too-many-arguments,unused-argument + +import warnings from typing import ( Any, Callable, Dict, List, + Literal, Optional, Tuple, Union, @@ -15,14 +19,23 @@ import numpy as np import pandas as pd from openbb_core.app.model.charts.chart import Chart from openbb_core.app.model.extension import Extension +from openbb_core.app.model.obbject import OBBject from openbb_core.app.utils import basemodel_to_df, convert_to_basemodel from openbb_core.provider.abstract.data import Data +from plotly.graph_objs import Figure |