summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanglewood <85772166+deeleeramone@users.noreply.github.com>2024-04-10 05:36:10 -0700
committerGitHub <noreply@github.com>2024-04-10 12:36:10 +0000
commit951749156dae3029c43364887c332036589aad7e (patch)
tree516efd0389909fd69709155edf11c363b6080f0b
parent72e322fd01133db0d087a35fb53268efb11e8253 (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>
-rw-r--r--openbb_platform/core/openbb_core/app/command_runner.py30
-rw-r--r--openbb_platform/extensions/technical/openbb_technical/technical_router.py4
-rw-r--r--openbb_platform/obbject_extensions/charting/integration/test_charting_api.py232
-rw-r--r--openbb_platform/obbject_extensions/charting/integration/test_charting_python.py169
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/__init__.py436
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/charting_router.py923
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/core/config/openbb_styles.py63
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/core/openbb_figure.py9
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/core/plotly.html4
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/core/plotly_ta/data_classes.py2
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/core/plotly_ta/plugins/__init__.py1
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/core/plotly_ta/plugins/custom_indicators_plugin.py25
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/core/plotly_ta/plugins/momentum_plugin.py93
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/core/plotly_ta/plugins/overlap_plugin.py1
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/core/plotly_ta/plugins/trend_indicators_plugin.py1
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/core/plotly_ta/plugins/volatility_plugin.py1
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/core/plotly_ta/plugins/volume_plugin.py1
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/core/plotly_ta/ta_class.py122
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/query_params.py805
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/styles/__init__.py1
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/styles/colors.py39
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/styles/default/dark.pltstyle.json55
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/styles/default/light.pltstyle.json883
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/utils/generic_charts.py606
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/utils/helpers.py75
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
from openbb_charting import charting_router
from openbb_charting.core.backend import Backend, create_backend, get_backend
-from openbb_charting.core.to_chart import ChartIndicators, to_chart
+from openbb_charting.core.openbb_figure import OpenBBFigure
+from openbb_charting.core.to_chart import ChartIndicators
+from openbb_charting.query_params import ChartParams, IndicatorsParams
+from openbb_charting.utils.generic_charts import bar_chart, line_chart
from openbb_charting.utils.helpers import get_charting_functions
+warnings.filterwarnings(
+ "ignore", category=UserWarning, module="openbb_core.app.model.extension", lineno=47
+)
+
ext = Extension(name="charting")
@@ -36,18 +49,25 @@ class Charting:
show
Display chart and save it to the OBBject.
to_chart
- Returns the plotly json representation of the chart.
+ Redraw the chart and save it to the OBBject, with an optional entry point for Data.
functions
- Returns a list of Platform commands with charting functions.
+ Return a list of Platform commands with charting functions.
+ get_params
+ Return the charting parameters for the function the OBBject was created from.
indicators
- Returns a list of the available indicators to use with the `to_chart` method.
+ Return the list of the available technical indicators to use with the `to_chart` method and OHLC+V data.
+ table
+ Display an interactive table.
+ create_line_chart
+ Create a line chart from external data.
+ create_bar_chart
+ Create a bar chart, on a single x-axis with one or more values for the y-axis, from external data.
"""
def __init__(self, obbject):
"""Initialize Charting extension."""
# pylint: disable=import-outside-toplevel
from openbb_core.app.model.charts.charting_settings import ChartingSettings
- from openbb_core.app.model.obbject import OBBject
self._obbject: OBBject = obb