summaryrefslogtreecommitdiffstats
path: root/openbb_platform/obbject_extensions/charting/openbb_charting/charting_router.py
diff options
context:
space:
mode:
Diffstat (limited to 'openbb_platform/obbject_extensions/charting/openbb_charting/charting_router.py')
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/charting_router.py268
1 files changed, 266 insertions, 2 deletions
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 f13ba058623..44b92a2c6c5 100644
--- a/openbb_platform/obbject_extensions/charting/openbb_charting/charting_router.py
+++ b/openbb_platform/obbject_extensions/charting/openbb_charting/charting_router.py
@@ -1,5 +1,6 @@
"""Charting router."""
+import json
from typing import Any, Dict, Tuple
import pandas as pd
@@ -9,6 +10,10 @@ from openbb_core.app.utils import basemodel_to_df
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
+from openbb_charting.query_params import (
+ FredSeriesChartQueryParams,
+ TechnicalConesChartQueryParams,
+)
CHART_FORMAT = ChartFormat.plotly
@@ -179,7 +184,9 @@ def technical_ema(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
return _ta_ma(ma_type, **kwargs)
-def technical_cones(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
+def technical_cones(
+ **kwargs: TechnicalConesChartQueryParams,
+) -> Tuple["OpenBBFigure", Dict[str, Any]]:
"""Volatility Cones Chart."""
data = kwargs.get("data")
@@ -187,7 +194,7 @@ def technical_cones(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
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 = basemodel_to_df(kwargs["obbject_item"], index="window") # type: ignore
df_ta.columns = [col.title().replace("_", " ") for col in df_ta.columns]
@@ -276,3 +283,260 @@ def technical_cones(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
content = fig.to_plotly_json()
return fig, content
+
+
+def economy_fred_series(
+ **kwargs: FredSeriesChartQueryParams,
+) -> 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",
+ }
+
+ colors = [
+ "#1f77b4",
+ "#7f7f7f",
+ "#ff7f0e",
+ "#2ca02c",
+ "#d62728",
+ "#9467bd",
+ "#8c564b",
+ "#e377c2",
+ "#7f7f7f",
+ "#bcbd22",
+ "#17becf",
+ ]
+
+ 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()
+
+ def z_score_standardization(data: pd.Series) -> pd.Series:
+ """Z-Score Standardization Method."""
+ return (data - data.mean()) / data.std()
+
+ if normalize:
+ df_ta = df_ta.apply(z_score_standardization)
+
+ # Extract the metadata from the warnings.
+ warnings = kwargs.get("warnings")
+ metadata = json.loads(warnings[0].message) if warnings else {} # 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 len(y_units) > 2 and has_params is False and allow_unsafe is True:
+ 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]
+
+ y1title = y1_units
+
+ y2title = y_units[1] if len(y_units) > 1 else None
+
+ 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.
+ if kwargs.get("title"):
+ title = kwargs.get("title")
+ else:
+ if metadata.get(columns[0]):
+ 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 = ""
+
+ # Create the figure object with subplots.
+ fig = OpenBBFigure().create_subplots(
+ rows=1, cols=1, shared_xaxes=True, shared_yaxes=False
+ )
+ fig.update_layout(ChartStyle().plotly_template.get("layout", {}))
+ text_color = "black" if ChartStyle().plt_style == "light" else "white"
+
+ # 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
+ or y2title is None # type: ignore
+ )
+ if metadata.get(col)
+ 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
+ if on_y3:
+ yaxes = "y3"
+ y3title = df_ta[col].name
+ 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=1, color=colors[i % len(colors)]),
+ yaxis=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 kwargs.get("xtitle"):
+ xtitle = kwargs.get("xtitle")
+ # If the data was normalized, set the title to reflect this.
+ if normalize:
+ y1title = None
+ y2title = None
+ y3title = None
+ title = f"{title} - 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)",
+ 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,
+ side="right",
+ title=dict(text=y1title, standoff=30, font=dict(size=18)),
+ tickfont=dict(size=14),
+ anchor="x",
+ )
+ 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=18)
+ ),
+ 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.75)"),
+ anchor="free",
+ )
+ if y3title
+ else None
+ ),
+ xaxis=dict(
+ ticklen=0,
+ showgrid=False,
+ title=(
+ dict(text=xtitle, standoff=30, font=dict(size=18)) if xtitle else None
+ ),
+ domain=[0.095, 0.95] if y3title else None,
+ ),
+ margin=dict(r=25, l=25) if normalize is False else None,
+ autosize=True,
+ dragmode="pan",
+ )
+
+ content = fig.to_plotly_json()
+
+ return fig, content