summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHenrique Joaquim <henriquecjoaquim@gmail.com>2024-06-18 14:45:10 +0100
committerGitHub <noreply@github.com>2024-06-18 13:45:10 +0000
commit5c79635da2146d2e45c9d5d38017941c77cdf0aa (patch)
tree02538b34c5bc550a73c6f19738f6198d50b6622a
parent6445b974d787885d344376353fca097c12b0860e (diff)
[Feature] Charting Modularity (#6477)
* add accessors to the charting extension * equity views * get charting functions from the extensions/accessors * use the private variable instead * etf views * index views * crypto and currency views * technical views * economy views * fixedincome views * right description for price historical * unused * adjust test and deprecate the charting router * import views only with charting installed * adding views throught dependencies instead * removing unnecessary infra and adding entry point to charting extension * fixed income toml * removing accessor * typo * typo * adding extra params to obbject * remove things from init * rename utils to charts instead * updated readme --------- Co-authored-by: Danglewood <85772166+deeleeramone@users.noreply.github.com> Co-authored-by: Igor Radovanovic <74266147+IgorWounds@users.noreply.github.com>
-rw-r--r--openbb_platform/core/openbb_core/app/model/obbject.py2
-rw-r--r--openbb_platform/extensions/crypto/openbb_crypto/crypto_views.py17
-rw-r--r--openbb_platform/extensions/crypto/pyproject.toml3
-rw-r--r--openbb_platform/extensions/currency/openbb_currency/currency_views.py17
-rw-r--r--openbb_platform/extensions/currency/pyproject.toml3
-rw-r--r--openbb_platform/extensions/economy/openbb_economy/economy_views.py307
-rw-r--r--openbb_platform/extensions/economy/pyproject.toml3
-rw-r--r--openbb_platform/extensions/equity/openbb_equity/equity_views.py25
-rw-r--r--openbb_platform/extensions/equity/pyproject.toml3
-rw-r--r--openbb_platform/extensions/etf/openbb_etf/etf_views.py84
-rw-r--r--openbb_platform/extensions/etf/pyproject.toml3
-rw-r--r--openbb_platform/extensions/fixedincome/openbb_fixedincome/fixedincome_views.py183
-rw-r--r--openbb_platform/extensions/fixedincome/pyproject.toml3
-rw-r--r--openbb_platform/extensions/index/openbb_index/index_views.py17
-rw-r--r--openbb_platform/extensions/index/pyproject.toml3
-rw-r--r--openbb_platform/extensions/technical/openbb_technical/technical_views.py511
-rw-r--r--openbb_platform/extensions/technical/pyproject.toml3
-rw-r--r--openbb_platform/obbject_extensions/charting/README.md39
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/__init__.py551
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/builder.py53
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/charting.py563
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/charting_router.py1445
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/charts/__init__.py (renamed from openbb_platform/obbject_extensions/charting/openbb_charting/utils/__init__.py)0
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/charts/generic_charts.py (renamed from openbb_platform/obbject_extensions/charting/openbb_charting/utils/generic_charts.py)11
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/charts/helpers.py (renamed from openbb_platform/obbject_extensions/charting/openbb_charting/utils/helpers.py)24
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/charts/price_historical.py314
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/charts/price_performance.py106
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/charts/relative_rotation.py (renamed from openbb_platform/obbject_extensions/charting/openbb_charting/utils/relative_rotation.py)0
-rw-r--r--openbb_platform/obbject_extensions/charting/tests/test_charting.py8
29 files changed, 2226 insertions, 2075 deletions
diff --git a/openbb_platform/core/openbb_core/app/model/obbject.py b/openbb_platform/core/openbb_core/app/model/obbject.py
index 75078ff2919..d9600746092 100644
--- a/openbb_platform/core/openbb_core/app/model/obbject.py
+++ b/openbb_platform/core/openbb_core/app/model/obbject.py
@@ -72,7 +72,7 @@ class OBBject(Tagged, Generic[T]):
_standard_params: Optional[Dict[str, Any]] = PrivateAttr(
default_factory=dict,
)
- _standard_params: Optional[Dict[str, Any]] = PrivateAttr(
+ _extra_params: Optional[Dict[str, Any]] = PrivateAttr(
default_factory=dict,
)
diff --git a/openbb_platform/extensions/crypto/openbb_crypto/crypto_views.py b/openbb_platform/extensions/crypto/openbb_crypto/crypto_views.py
new file mode 100644
index 00000000000..15e2f92b34c
--- /dev/null
+++ b/openbb_platform/extensions/crypto/openbb_crypto/crypto_views.py
@@ -0,0 +1,17 @@
+"""Views for the crypto Extension."""
+
+from typing import Any, Dict, Tuple
+
+from openbb_charting.charts.price_historical import price_historical
+from openbb_charting.core.openbb_figure import OpenBBFigure
+
+
+class CryptoViews:
+ """Crypto Views."""
+
+ @staticmethod
+ def crypto_price_historical( # noqa: PLR0912
+ **kwargs,
+ ) -> Tuple[OpenBBFigure, Dict[str, Any]]:
+ """Crypto Price Historical Chart."""
+ return price_historical(**kwargs)
diff --git a/openbb_platform/extensions/crypto/pyproject.toml b/openbb_platform/extensions/crypto/pyproject.toml
index e3c74377c1f..818fa9e853d 100644
--- a/openbb_platform/extensions/crypto/pyproject.toml
+++ b/openbb_platform/extensions/crypto/pyproject.toml
@@ -17,3 +17,6 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry.plugins."openbb_core_extension"]
crypto = "openbb_crypto.crypto_router:router"
+
+[tool.poetry.plugins."openbb_charting_extension"]
+crypto = "openbb_crypto.crypto_views:CryptoViews"
diff --git a/openbb_platform/extensions/currency/openbb_currency/currency_views.py b/openbb_platform/extensions/currency/openbb_currency/currency_views.py
new file mode 100644
index 00000000000..dbc70f7b43c
--- /dev/null
+++ b/openbb_platform/extensions/currency/openbb_currency/currency_views.py
@@ -0,0 +1,17 @@
+"""Views for the Currency Extension."""
+
+from typing import Any, Dict, Tuple
+
+from openbb_charting.charts.price_historical import price_historical
+from openbb_charting.core.openbb_figure import OpenBBFigure
+
+
+class CurrencyViews:
+ """Currency Views."""
+
+ @staticmethod
+ def currency_price_historical( # noqa: PLR0912
+ **kwargs,
+ ) -> Tuple[OpenBBFigure, Dict[str, Any]]:
+ """Currency Price Historical Chart."""
+ return price_historical(**kwargs)
diff --git a/openbb_platform/extensions/currency/pyproject.toml b/openbb_platform/extensions/currency/pyproject.toml
index 5dcef853d34..e8117d8f3cd 100644
--- a/openbb_platform/extensions/currency/pyproject.toml
+++ b/openbb_platform/extensions/currency/pyproject.toml
@@ -17,3 +17,6 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry.plugins."openbb_core_extension"]
currency = "openbb_currency.currency_router:router"
+
+[tool.poetry.plugins."openbb_charting_extension"]
+currency = "openbb_currency.currency_views:CurrencyViews"
diff --git a/openbb_platform/extensions/economy/openbb_economy/economy_views.py b/openbb_platform/extensions/economy/openbb_economy/economy_views.py
new file mode 100644
index 00000000000..c0bc2396aa5
--- /dev/null
+++ b/openbb_platform/extensions/economy/openbb_economy/economy_views.py
@@ -0,0 +1,307 @@
+"""Views for the Economy Extension."""
+
+from typing import Any, Dict, Optional, Tuple
+from warnings import warn
+
+import pandas as pd
+from openbb_charting.charts.generic_charts import bar_chart
+from openbb_charting.charts.helpers import (
+ z_score_standardization,
+)
+from openbb_charting.core.openbb_figure import OpenBBFigure
+from openbb_charting.styles.colors import LARGE_CYCLER
+from openbb_core.app.utils import basemodel_to_df
+
+
+class EconomyViews:
+ """economy Views."""
+
+ @staticmethod
+ def economy_fred_series( # noqa: PLR0912
+ **kwargs,
+ ) -> Tuple[OpenBBFigure, Dict[str, Any]]:
+ """FRED Series Chart."""
+ ytitle_dict = {
+ "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 = kwargs.get("provider")
+
+ if provider != "fred":
+ raise RuntimeError(
+ f"This charting method does not support {provider}. Supported providers: fred."
+ )
+
+ columns = basemodel_to_df(kwargs["obbject_item"], index=None).columns.to_list() # type: ignore
+
+ allow_unsafe = kwargs.get("allow_unsafe", False)
+ dropnan = kwargs.get("dropna", True)
+ normalize = kwargs.get("normalize", False)
+
+ data_cols = []
+ data = kwargs.get("data")
+
+ if isinstance(data, pd.DataFrame) and not data.empty:
+ data_cols = data.columns.to_list()
+ df_ta = data
+
+ else:
+ df_ta = basemodel_to_df(kwargs["obbject_item"], index="date") # type: ignore
+
+ # Check for unsupported external data injection.
+ if allow_unsafe is False and data_cols:
+ for data_col in data_cols:
+ if data_col not in columns:
+ raise RuntimeError(
+ f"Column '{data_col}' was not found in the original data."
+ + " External data injection is not supported unless `allow_unsafe = True`."
+ )
+
+ # Align the data so each column has the same index and length.
+ if dropnan:
+ df_ta = df_ta.dropna(how="any")
+
+ if df_ta.empty or len(df_ta) < 2:
+ raise ValueError(
+ "No data is left after dropping NaN values. Try setting `dropnan = False`,"
+ + " or use the `frequency` parameter on request."
+ )
+
+ columns = df_ta.columns.to_list()
+
+ metadata = kwargs["extra"].get("results_metadata", {}) # type: ignore
+
+ # Check if the request was transformed by the FRED API.
+ params = kwargs["extra_params"] if kwargs.get("extra_params") else {}
+ has_params = hasattr(params, "transform") and params.transform is not None # type: ignore
+
+ # Get a unique list of all units of measurement in the DataFrame.
+ y_units = list({metadata.get(col).get("units") for col in columns if col in metadata}) # type: ignore
+ if has_params is True and not y_units:
+ y_units = [ytitle_dict.get(params.transform)] # type: ignore
+
+ if normalize or (
+ kwargs.get("bar") is True
+ and len(y_units) > 1
+ and (
+ has_params is False
+ or not any(
+ i in params.transform for i in ["pc1", "pch", "pca", "cch", "cca", "log"] # type: ignore
+ )
+ )
+ ):
+ normalize = True
+ df_ta = df_ta.apply(z_score_standardization)
+
+ if len(y_units) > 2 and has_params is False and allow_unsafe is False:
+ raise RuntimeError(
+ "This method supports up to 2 y-axis units."
+ + " Please use the 'transform' parameter, in the data request,"
+ + " to compare all series on the same scale, or set `normalize = True`."
+ + " Override this error by setting `allow_unsafe = True`."
+ )
+
+ y1_units = y_units[0] if y_units else None
+ y1title = y1_units
+ y2title = y_units[1] if len(y_units) > 1 else None
+ xtitle = str(kwargs.get("xtitle", ""))
+
+ # If the request was transformed, the y-axis will be shared under these conditions.
+ if has_params and any(
+ i in params.transform for i in ["pc1", "pch", "pca", "cch", "cca", "log"] # type: ignore
+ ):
+ y1title = "Log" if params.transform == "Log" else "Percent" # type: ignore
+ y2title = None
+
+ # Set the title for the chart.
+ title: str = ""
+ if isinstance(kwargs, dict) and title in kwargs:
+ title = kwargs["title"] # type: ignore
+ else:
+ if metadata.get(columns[0]): # type: ignore
+ title = metadata.get(columns[0]).get("title") if len(columns) == 1 else "FRED Series" # type: ignore
+ else:
+ title = "FRED Series"
+ transform_title = ytitle_dict.get(params.transform) if has_params is True else "" # type: ignore
+ title = f"{title} - {transform_title}" if transform_title else title
+
+ # Define this to use as a check.
+ y3title: Optional[str] = ""
+
+ if kwargs.get("plot_bar") is True or len(df_ta.index) < 100:
+ margin = dict(l=10, r=5, b=75 if xtitle else 30)
+ try:
+ if normalize:
+ y1title = None
+ title = f"{title} - Normalized" if title else "Normalized"
+ bar_mode = kwargs.get("barmode", "group")
+ fig = bar_chart(
+ df_ta.reset_index(),
+ "date",
+ df_ta.columns.to_list(),
+ title=title,
+ xtitle=xtitle,
+ ytitle=y1title,
+ barmode=bar_mode, # type: ignore
+ layout_kwargs=dict(margin=margin), # type: ignore
+ )
+ if kwargs.get("layout_kwargs"):
+ fig.update_layout(kwargs.get("layout_kwargs"))
+
+ if kwargs.get("title"):
+ fig.set_title(str(kwargs.get("title"))) # type: ignore
+
+ content = fig.to_plotly_json()
+
+ return fig, content # type: ignore
+ except Exception as _:
+ warn("Bar chart failed. Attempting line chart.")
+
+ # Create the figure object with subplots.
+ fig = OpenBBFigure().create_subplots(
+ rows=1, cols=1, shared_xaxes=True, shared_yaxes=False
+ )
+
+ # For each series in the DataFrame, add a scatter plot.
+ for i, col in enumerate(df_ta.columns):
+
+ # Check if the y-axis should be shared for this series.
+ on_y1 = (
+ (
+ metadata.get(col).get("units") == y1_units # type: ignore
+ or y2title is None # type: ignore
+ or kwargs.get("same_axis") is True
+ )
+ if metadata.get(col) # type: ignore
+ else False
+ )
+ if normalize:
+ on_y1 = True
+
+ yaxes = "y2" if not on_y1 else "y1"
+ on_y3 = not metadata.get(col) and normalize is False # type: ignore
+ if on_y3:
+ yaxes = "y3"
+ y3title = df_ta[col].name # type: ignore
+ fig.add_scatter(
+ x=df_ta.index,
+ y=df_ta[col],
+ name=df_ta[col].name,
+ mode="lines",
+ hovertemplate=f"{df_ta[col].name}: %{{y}}<extra></extra>",
+ line=dict(width=2, color=LARGE_CYCLER[i % len(LARGE_CYCLER)]),
+ yaxis="y1" if kwargs.get("same_axis") is True else yaxes,
+ )
+
+ # Set the y-axis titles, if supplied.
+ if kwargs.get("y1title"):
+ y1title = kwargs.get("y1title")
+ if kwargs.get("y2title") and y2title is not None:
+ y2title = kwargs.get("y2title")
+ # Set the x-axis title, if suppiled.
+ if isinstance(kwargs, dict) and "xtitle" in kwargs:
+ xtitle = kwargs["xtitle"]
+ # If the data was normalized, set the title to reflect this.
+ if normalize:
+ y1title = None
+ y2title = None
+ y3title = None
+ title = f"{title} - Normalized" if title else "Normalized"
+
+ # Now update the layout of the complete figure.
+ fig.update_layout(
+ title=dict(text=title, x=0.5, font=dict(size=16)),
+ paper_bgcolor="rgba(0,0,0,0)",
+ plot_bgcolor="rgba(0,0,0,0)",
+ legend=dict(
+ orientation="h",
+ yanchor="bottom",
+ xanchor="right",
+ y=1.02,
+ x=0.95,
+ bgcolor="rgba(0,0,0,0)",
+ font=dict(size=12),
+ ),
+ yaxis=(
+ dict(
+ ticklen=0,
+ side="right",
+ showline=True,
+ mirror=True,
+ title=dict(text=y1title, standoff=30, font=dict(size=16)),
+ tickfont=dict(size=14),
+ anchor="x",
+ gridcolor="rgba(128,128,128,0.3)",
+ )
+ if y1title
+ else None
+ ),
+ yaxis2=(
+ dict(
+ overlaying="y",
+ side="left",
+ ticklen=0,
+ showgrid=False,
+ title=dict(
+ text=y2title if y2title else None,
+ standoff=10,
+ font=dict(size=16),
+ ),
+ tickfont=dict(size=14),
+ anchor="x",
+ )
+ if y2title
+ else None
+ ),
+ yaxis3=(
+ dict(
+ overlaying="y",
+ side="left",
+ ticklen=0,
+ position=0,
+ showgrid=False,
+ showticklabels=True,
+ title=(
+ dict(text=y3title, standoff=10, font=dict(size=16))
+ if y3title
+ else None
+ ),
+ tickfont=dict(size=12, color="rgba(128,128,128,0.9)"),
+ anchor="free",
+ )
+ if y3title
+ else None
+ ),
+ xaxis=dict(
+ ticklen=0,
+ showgrid=True,
+ showline=True,
+ mirror=True,
+ title=(
+ dict(text=xtitle, standoff=30, font=dict(size=16))
+ if xtitle
+ else None
+ ),
+ gridcolor="rgba(128,128,128,0.3)",
+ domain=[0.095, 0.95] if y3title else None,
+ ),
+ margin=(
+ dict(r=25, l=25, b=75 if xtitle else 30) if normalize is False else None
+ ),
+ autosize=True,
+ dragmode="pan",
+ )
+ if kwargs.get("layout_kwargs"):
+ fig.update_layout(kwargs.get("layout_kwargs"))
+ if kwargs.get("title"):
+ fig.set_title(str(kwargs.get("title")))
+ content = fig.to_plotly_json()
+
+ return fig, content
diff --git a/openbb_platform/extensions/economy/pyproject.toml b/openbb_platform/extensions/economy/pyproject.toml
index 4b2deb7faeb..64ca2811998 100644
--- a/openbb_platform/extensions/economy/pyproject.toml
+++ b/openbb_platform/extensions/economy/pyproject.toml
@@ -17,3 +17,6 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry.plugins."openbb_core_extension"]
economy = "openbb_economy.economy_router:router"
+
+[tool.poetry.plugins."openbb_charting_extension"]
+economy = "openbb_economy.economy_views:EconomyViews"
diff --git a/openbb_platform/extensions/equity/openbb_equity/equity_views.py b/openbb_platform/extensions/equity/openbb_equity/equity_views.py
new file mode 100644
index 00000000000..659abb3738c
--- /dev/null
+++ b/openbb_platform/extensions/equity/openbb_equity/equity_views.py
@@ -0,0 +1,25 @@
+"""Views for the Equity Extension."""
+
+from typing import Any, Dict, Tuple
+
+from openbb_charting.charts.price_historical import price_historical
+from openbb_charting.charts.price_performance import price_performance
+from openbb_charting.core.openbb_figure import OpenBBFigure
+
+
+class EquityViews:
+ """Equity Views."""
+
+ @staticmethod
+ def equity_price_historical( # noqa: PLR0912
+ **kwargs,
+ ) -> Tuple[OpenBBFigure, Dict[str, Any]]:
+ """Equity Price Historical Chart."""
+ return price_historical(**kwargs)
+
+ @staticmethod
+ def equity_price_performance( # noqa: PLR0912
+ **kwargs,
+ ) -> Tuple[OpenBBFigure, Dict[str, Any]]:
+ """Equity Price Performance Chart."""
+ return price_performance(**kwargs)
diff --git a/openbb_platform/extensions/equity/pyproject.toml b/openbb_platform/extensions/equity/pyproject.toml
index 6858807d5d6..a7c87a674d0 100644
--- a/openbb_platform/extensions/equity/pyproject.toml
+++ b/openbb_platform/extensions/equity/pyproject.toml
@@ -17,3 +17,6 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry.plugins."openbb_core_extension"]
equity = "openbb_equity.equity_router:router"
+
+[tool.poetry.plugins."openbb_charting_extension"]
+equity = "openbb_equity.equity_views:EquityViews"
diff --git a/openbb_platform/extensions/etf/openbb_etf/etf_views.py b/openbb_platform/extensions/etf/openbb_etf/etf_views.py
new file mode 100644
index 00000000000..71e2c9e3c23
--- /dev/null
+++ b/openbb_platform/extensions/etf/openbb_etf/etf_views.py
@@ -0,0 +1,84 @@
+"""Views for the ETF Extension."""
+
+from typing import Any, Dict, Tuple, Union
+
+import pandas as pd
+from openbb_charting.charts.generic_charts import bar_chart
+from openbb_charting.charts.price_historical import price_historical
+from openbb_charting.charts.price_performance import price_performance
+from openbb_charting.core.openbb_figure import OpenBBFigure
+from openbb_core.app.utils import basemodel_to_df
+from plotly.graph_objs import Figure
+
+
+class EtfViews:
+ """Etf Views."""
+
+ @staticmethod
+ def etf_historical( # noqa: PLR0912
+ **kwargs,
+ ) -> Tuple[OpenBBFigure, Dict[str, Any]]:
+ """Etf Price Historical Chart."""
+ return price_historical(**kwargs)
+
+ @staticmethod
+ def etf_price_performance( # noqa: PLR0912
+ **kwargs,
+ ) -> Tuple[OpenBBFigure, Dict[str, Any]]:
+ """Etf Price Performance Chart."""
+ return price_performance(**kwargs)
+
+ @staticmethod
+ def etf_holdings(**kwargs) -> Tuple[Union[OpenBBFigure, Figure], Dict[str, Any]]:
+ """Equity Compare Groups Chart."""
+ if "data" in kwargs and isinstance(kwargs["data"], pd.DataFrame):
+ data = kwargs["data"]
+ elif "data" in kwargs and isinstance(kwargs["data"], list):
+ data = basemodel_to_df(kwargs["data"], index=None) # type: ignore