diff options
Diffstat (limited to 'openbb_platform/obbject_extensions/charting/openbb_charting/__init__.py')
-rw-r--r-- | openbb_platform/obbject_extensions/charting/openbb_charting/__init__.py | 551 |
1 files changed, 2 insertions, 549 deletions
diff --git a/openbb_platform/obbject_extensions/charting/openbb_charting/__init__.py b/openbb_platform/obbject_extensions/charting/openbb_charting/__init__.py index d20908557d4..fb00de240b0 100644 --- a/openbb_platform/obbject_extensions/charting/openbb_charting/__init__.py +++ b/openbb_platform/obbject_extensions/charting/openbb_charting/__init__.py @@ -1,561 +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, -) -from warnings import warn -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.openbb_figure import OpenBBFigure -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 +from openbb_charting.charting import Charting as _Charting warnings.filterwarnings( "ignore", category=UserWarning, module="openbb_core.app.model.extension", lineno=47 ) ext = Extension(name="charting", description="Create custom charts from OBBject data.") - - -@ext.obbject_accessor -class Charting: - """Charting extension. - - Methods - ------- - show - Display chart and save it to the OBBject. - to_chart - Redraw the chart and save it to the OBBject, with an optional entry point for Data. - 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 - 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. - toggle_chart_style - Toggle the chart style, of an existing chart, between light and dark mode. - """ - - def __init__(self, obbject): - """Initialize Charting extension.""" - # pylint: disable=import-outside-toplevel - from openbb_core.app.model.charts.charting_settings import ChartingSettings - - self._obbject: OBBject = obbject - self._charting_settings = ChartingSettings( - user_settings=self._obbject._user_settings, # type: ignore - system_settings=self._obbject._system_settings, # type: ignore - ) - self._backend: Backend = self._handle_backend() - - @classmethod - def indicators(cls): - """Return an instance of the IndicatorsParams class, containing all available indicators and their parameters. - - Without assigning to a variable, it will print the the information to the console. - """ - return IndicatorsParams() - - @classmethod - def functions(cls): - """Return a list of the available functions.""" - return get_charting_functions() - - def _handle_backend(self) -> Backend: - """Create and start the backend.""" - create_backend(self._charting_settings) - backend = get_backend() - backend.start(debug=self._charting_settings.debug_mode) - return backend - - @staticmethod - def _get_chart_function(route: str) -> Callable: - """Given a route, it returns the chart function. The module must contain the given route.""" - if route is None: - raise ValueError("OBBject was initialized with no function route.") - adjusted_route = route.replace("/", "_")[1:] - return getattr(charting_router, adjusted_route) - - def get_params(self) -> ChartParams: - """Return the ChartQueryParams class for the function the OBBject was created from. - - Without assigning to a variable, it will print the docstring to the console. - """ - if self._obbject._route is None: # pylint: disable=protected-access - raise ValueError("OBBject was initialized with no function route.") - charting_function = ( - self._obbject._route # pylint: disable=protected-access - ).replace("/", "_")[1:] - if hasattr(ChartParams, charting_function): - return getattr(ChartParams, charting_function)() - raise ValueError( - f"Error: No chart parameters are defined for the route: {charting_function}" - ) - - def _prepare_data_as_df( - self, data: Optional[Union[pd.DataFrame, pd.Series]] - ) -> Tuple[pd.DataFrame, bool]: - """Convert supplied data to a DataFrame.""" - has_data = ( - isinstance(data, (Data, pd.DataFrame, pd.Series)) and not data.empty # type: ignore - ) or (bool(data)) - index = ( - data.index.name - if has_data and isinstance(data, (pd.DataFrame, pd.Series)) - else None - ) - data_as_df: pd.DataFrame = ( - basemodel_to_df(convert_to_basemodel(data), index=index) - if has_data - else self._obbject.to_dataframe(index=index) - ) - if "date" in data_as_df.columns: - data_as_df = data_as_df.set_index("date") - if "provider" in data_as_df.columns: - data_as_df.drop(columns="provider", inplace=True) - return data_as_df, has_data - - # pylint: disable=too-many-locals - def create_line_chart( - self, - data: Union[ - list, - dict, - pd.DataFrame, - List[pd.DataFrame], - pd.Series, - List[pd.Series], - np.ndarray, - Data, - ], - index: Optional[str] = None, - target: Optional[str] = None, - title: Optional[str] = None, - x: Optional[str] = None, - xtitle: Optional[str] = None, - y: Optional[Union[str, List[str]]] = None, - ytitle: Optional[str] = None, - y2: Optional[Union[str, List[str]]] = None, - y2title: Optional[str] = None, - layout_kwargs: Optional[dict] = None, - scatter_kwargs: Optional[dict] = None, - normalize: bool = False, - returns: bool = False, - same_axis: bool = False, - render: bool = True, - **kwargs, - ) -> Union[OpenBBFigure, Figure, None]: - """Create a line chart from external data and render a chart or return the OpenBBFigure. - - Parameters - ---------- - data : Union[Data, pd.DataFrame, pd.Series] - Data to be plotted (OHLCV data). - index : Optional[str], optional - Index column, by default None - target : Optional[str], optional - Target column to be plotted, by default None - title : Optional[str], optional - Chart title, by default None - x : Optional[str], optional - X-axis column, by default None - xtitle : Optional[str], optional - X-axis title, by default None - y : Optional[Union[str, List[str]]], optional - Y-axis column(s), by default None - If None are supplied, the layout is optimized for the contents of data. - Where many units/scales are present, - it will attempt to divide based on the range of values. - ytitle : Optional[str], optional - Y-axis title, by default None - y2 : Optional[Union[str, List[str]]], optional - Y2-axis column(s), by default None - y2title : Optional[str], optional - Y2-axis title, by default None - layout_kwargs : Optional[dict], optional - Additional Plotly Layout parameters for `fig.update_layout`, by default None - scatter_kwargs : Optional[dict], optional - Additional Plotly parameters applied on creation of each scatter plot, by default None - normalize : bool, optional - Normalize the data with Z-Score Standardization, by default False - returns : bool, optional - Convert the data to cumulative returns, by default False - same_axis: bool, optional - If True, forces all data onto the same Y-axis, by default False - render: bool, optional - If True, the chart will be rendered, by default True - **kwargs: Dict[str, Any] - Extra parameters to be passed to `figure.show()` - """ - fig = line_chart( - data=data, - index=index, - target=target, - title=title, - x=x, - xtitle=xtitle, - y=y, - ytitle=ytitle, - y2=y2, - y2title=y2title, - layout_kwargs=layout_kwargs, - scatter_kwargs=scatter_kwargs, - normalize=normalize, - returns=returns, - same_axis=same_axis, - **kwargs, - ) - fig = self._set_chart_style(fig) - if render: - return fig.show(**kwargs) - - return fig - - def create_bar_chart( - self, - data: Union[ - list, - dict, - pd.DataFrame, - List[pd.DataFrame], - pd.Series, - List[pd.Series], - np.ndarray, - Data, - ], - x: str, - y: Union[str, List[str]], - barmode: Literal["group", "stack", "relative", "overlay"] = "group", - xtype: Literal[ - "category", "multicategory", "date", "log", "linear" - ] = "category", - title: Optional[str] = None, - xtitle: Optional[str] = None, - ytitle: Optional[str] = None, - orientation: Literal["h", "v"] = "v", - colors: Optional[List[str]] = None, - layout_kwargs: Optional[Dict[str, Any]] = None, - bar_kwargs: Optional[Dict[str, Any]] = None, - render: bool = True, - **kwargs, - ) -> Union[OpenBBFigure, Figure, None]: - """Create a bar chart on a single x-axis with one or more values for the y-axis. - - Parameters - ---------- - data : Union[list, dict, pd.DataFrame, List[pd.DataFrame], pd.Series, List[pd.Series], np.ndarray, Data] - Data to plot. - x : str - The x-axis column name. - y : Union[str, List[str]] - The y-axis column name(s). - barmode : Literal["group", "stack", "relative", "overlay"], optional - The bar mode, by default "group". - xtype : Literal["category", "multicategory", "date", "log", "linear"], optional - The x-axis type, by default "category". - title : str, optional - The title of the chart, by default None. - xtitle : str, optional - The x-axis title, by default None. - ytitle : str, optional - The y-axis title, by default None. - colors: List[str], optional - Manually set the colors to cycle through for each column in 'y', by default None. - bar_kwargs : Dict[str, Any], optional - Additional keyword arguments to apply with figure.add_bar(), by default None. - layout_kwargs : Dict[str, Any], optional - Additional keyword arguments to apply with figure.update_layout(), by default None. - Returns - ------- - OpenBBFigure - The OpenBBFigure object. - """ - fig = bar_chart( - data=data, - x=x, - y=y, - barmode=barmode, - xtype=xtype, - title=title, - xtitle=xtitle, - ytitle=ytitle, - orientation=orientation, - colors=colors, - bar_kwargs=bar_kwargs, - layout_kwargs=layout_kwargs, - ) - fig = self._set_chart_style(fig) - if render: - return fig.show(**kwargs) - - return fig - - # pylint: disable=inconsistent-return-statements - def show(self, render: bool = True, **kwargs): - """Display chart and save it to the OBBject.""" - try: - charting_function = self._get_chart_function( - self._obbject._route # pylint: disable=protected-access - ) - kwargs["obbject_item"] = self._obbject # pylint: disable=protected-access - kwargs["charting_settings"] = ( - self._charting_settings - ) # pylint: disable=protected-access - kwargs["standard_params"] = ( - self._obbject._standard_params - ) # pylint: disable=protected-access - kwargs["extra_params"] = ( - self._obbject._extra_params - ) # pylint: disable=protected-access - kwargs["provider"] = ( - self._obbject.provider - ) # pylint: disable=protected-access - kwargs["extra"] = self._obbject.extra # pylint: disable=protected-access - fig, content = charting_function(**kwargs) - fig = self._set_chart_style(fig) - content = fig.show(external=True, **kwargs).to_plotly_json() - self._obbject.chart = Chart( - fig=fig, content=content, format=charting_router.CHART_FORMAT - ) - if render: - fig.show(**kwargs) - except Exception: # pylint: disable=W0718 - try: - fig = self.create_line_chart(data=self._obbject.results, render=False, **kwargs) # type: ignore - fig = self._set_chart_style(fig) - content = fig.show(external=True, **kwargs).to_plotly_json() # type: ignore - self._obbject.chart = Chart( - fig=fig, content=content, format=charting_router.CHART_FORMAT - ) - if render: - return fig.show(**kwargs) # type: ignore - except Exception as e: - raise RuntimeError( - "Failed to automatically create a generic chart with the data provided." - ) from e - - # pylint: disable=too-many-locals,inconsistent-return-statements - def to_chart( - self, - data: Optional[ - Union[ - list, - dict, - pd.DataFrame, - List[pd.DataFrame], - pd.Series, - List[pd.Series], - np.ndarray, - Data, - ] - ] = None, - target: Optional[str] = None, - index: Optional[str] = None, - indicators: Optional[Dict[str, Dict[str, Any]]] = None, - symbol: str = "", - candles: bool = True, - volume: bool = True, - volume_ticks_x: int = 7, - render: bool = True, - **kwargs, - ): - """Create an OpenBBFigure with user customizations (if any) and save it to the OBBject. - - This function is used to populate, or re-populate, the OBBject with a chart using the data within - the OBBject or external data supplied via the `data` parameter. - This function modifies the original OBBject by overwriting the existing chart. - - Parameters - ---------- - data : Union[Data, pd.DataFrame, pd.Series] - Data to be plotted. - indicators : Dict[str, Dict[str, Any]], optional - Indicators to be plotted, by default None - symbol : str, optional - Symbol to be plotted. This is used for labels and titles, by default "" - candles : bool, optional - If True, candles will be plotted, by default True - volume : bool, optional - If True, volume will be plotted, by default True - volume_ticks_x : int, optional - Volume ticks, by default 7 - render : bool, optional - If True, the chart will be rendered, by default True - kwargs: Dict[str, Any] - Extra parameters to be passed to the chart constructor. - - Examples - -------- - Plotting a time series with TA indicators - - >>> from openbb import obb - >>> res = obb.equity.price.historical("AAPL") - >>> indicators = dict( - >>> sma=dict(length=[20,30,50]), - >>> adx=dict(length=14), - >>> rsi=dict(length=14), - >>> macd=dict(fast=12, slow=26, signal=9), - >>> bbands=dict(length=20, std=2), - >>> stoch=dict(length=14), - >>> ema=dict(length=[20,30,50]), - >>> ) - >>> res.charting.to_chart(**{"indicators": indicators}) - - Get all the available indicators - - >>> res = obb.equity.price.historical("AAPL") - >>> indicators = res.charting.indicators() - >>> indicators? - """ - data_as_df, has_data = self._prepare_data_as_df(data) # type: ignore - if target is not None: - data_as_df = data_as_df[[target]] - kwargs["candles"] = candles - kwargs["volume"] = volume - kwargs["volume_ticks_x"] = volume_ticks_x - kwargs["indicators"] = indicators if indicators else {} - kwargs["symbol"] = symbol - kwargs["target"] = target - kwargs["index"] = index - kwargs["obbject_item"] = self._obbject # pylint: disable=protected-access - kwargs["charting_settings"] = ( - self._charting_settings - ) # pylint: disable=protected-access - kwargs["standard_params"] = ( - self._obbject._standard_params - ) # pylint: disable=protected-access - kwargs["extra_params"] = ( - self._obbject._extra_params - ) # pylint: disable=protected-access - kwargs["provider"] = self._obbject.provider # pylint: disable=protected-access - kwargs["extra"] = self._obbject.extra # pylint: disable=protected-access - try: - if has_data: - self.show(data=data_as_df, render=render, **kwargs) - else: - self.show(**kwargs, render=render) - except Exception: # pylint: disable=W0718 - try: - fig = self.create_line_chart(data=data_as_df, render=False, **kwargs) - fig = self._set_chart_style(fig) - content = fig.show(external=True, **kwargs).to_plotly_json() # type: ignore - self._obbject.chart = Chart( - fig=fig, content=content, format=charting_router.CHART_FORMAT - ) - if render: - return fig.show(**kwargs) # type: ignore - except Exception as e: # pylint: disable=W0718 - raise RuntimeError( - "Failed to automatically create a generic chart with the data provided." - ) from e - - def _set_chart_style(self, figure: Figure): - """Set the user preference for light or dark mode.""" - style = self._charting_settings.chart_style - font_color = "black" if style == "light" else "white" - paper_bgcolor = "white" if style == "light" else "black" - figure = figure.update_layout( - dict( # pylint: disable=R1735 - font_color=font_color, paper_bgcolor=paper_bgcolor - ) - ) - return figure - - def toggle_chart_style(self): - """Toggle the chart style between light and dark mode.""" - if not hasattr(self._obbject.chart, "fig"): - raise ValueError( - "Error: No chart has been created. Please create a chart first." - ) - current = self._charting_settings.chart_style - new = "light" if current == "dark" else "dark" - self._charting_settings.chart_style = new - figure = self._obbject.chart.fig # type: ignore[union-attr] - updated_figure = self._set_chart_style(figure) - self._obbject.chart.fig = updated_figure # type: ignore[union-attr] - self._obbject.chart.content = updated_figure.show( # type: ignore[union-attr] - external=True - ).to_plotly_json() - - def table( - self, - data: Optional[Union[pd.DataFrame, pd.Series]] = None, - title: str = "", - ): - """Display an interactive table. - - Parameters - ---------- - data : Optional[Union[pd.DataFrame, pd.Series]], optional - Data to be plotted, by default None. - If no data is provided the OBBject results will be used. - title : str, optional - Title of the table, by default "". - """ - data_as_df, _ = self._prepare_data_as_df(data) - if isinstance(data_as_df.index, pd.RangeIndex): - data_as_df.reset_index(inplace=True, drop=True) - else: - data_as_df.reset_index(inplace=True) - if self._backend.isatty: - try: - self._backend.send_table( - df_table=data_as_df, - title=title - or self._obbject._route, # pylint: disable=protected-access - theme=self._charting_settings.table_style, - ) - except Exception as e: # pylint: disable=W0718 - warn(f"Failed to show figure with backend. {e}") - - else: - from plotly import ( # pylint:disable=import-outside-toplevel - optional_imports, - ) - - ipython_display = optional_imports.get_module("IPython.display") - if ipython_display: - ipython_display.display(ipython_display.HTML(data_as_df.to_html())) - else: - warn("IPython.display is not available.") - - def url( - self, - url: str, - title: str = "", - width: Optional[int] = None, - height: Optional[int] = None, - ): - """Return the URL of the chart.""" - try: - self._backend.send_url(url=url, title=title, width=width, height=height) - except Exception as e: # pylint: disable=W0718 - warn(f"Failed to show figure with backend. {e}") +Charting = ext.obbject_accessor(_Charting) |