summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanglewood <85772166+deeleeramone@users.noreply.github.com>2024-06-30 09:45:43 -0700
committerDanglewood <85772166+deeleeramone@users.noreply.github.com>2024-06-30 09:45:43 -0700
commit5458c6679d90cbc03e371d5f43a9f3ab2b31d1b5 (patch)
treec218bfa95ca7ae392c8a3b6a6e9efa128125779d
parente84b5a5be5009c5a6f0dea03a18b1894d04aa8cc (diff)
futures curve chart with multiple dates allowed
-rw-r--r--openbb_platform/extensions/derivatives/openbb_derivatives/derivatives_views.py215
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/charting.py14
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/charts/futures_curve.py61
-rw-r--r--openbb_platform/obbject_extensions/charting/openbb_charting/query_params.py18
4 files changed, 217 insertions, 91 deletions
diff --git a/openbb_platform/extensions/derivatives/openbb_derivatives/derivatives_views.py b/openbb_platform/extensions/derivatives/openbb_derivatives/derivatives_views.py
index 2965e176a75..522df6a3b1f 100644
--- a/openbb_platform/extensions/derivatives/openbb_derivatives/derivatives_views.py
+++ b/openbb_platform/extensions/derivatives/openbb_derivatives/derivatives_views.py
@@ -1,10 +1,9 @@
"""Views for the Derivatives Extension."""
-from typing import Any, Dict, Tuple
+from typing import TYPE_CHECKING, Any, Dict, Tuple
-from openbb_charting.charts.futures_curve import futures_curve
-from openbb_charting.charts.price_historical import price_historical
-from openbb_charting.core.openbb_figure import OpenBBFigure
+if TYPE_CHECKING:
+ from openbb_charting.core.openbb_figure import OpenBBFigure
class DerivativesViews:
@@ -13,13 +12,213 @@ class DerivativesViews:
@staticmethod
def derivatives_futures_historical( # noqa: PLR0912
**kwargs,
- ) -> Tuple[OpenBBFigure, Dict[str, Any]]:
+ ) -> Tuple["OpenBBFigure", Dict[str, Any]]:
"""Get Derivatives Futures Historical Chart."""
+ # pylint: disable=import-outside-toplevel
+ from openbb_charting.charts.price_historical import price_historical
+
return price_historical(**kwargs)
@staticmethod
def derivatives_futures_curve( # noqa: PLR0912
**kwargs,
- ) -> Tuple[OpenBBFigure, Dict[str, Any]]:
- """Get Derivatives Futures Curve Chart."""
- return futures_curve(**kwargs)
+ ) -> Tuple["OpenBBFigure", Dict[str, Any]]:
+ """Futures curve chart. All parameters are optional, and are kwargs.
+ Parameters can be directly accessed from the function end point by
+ entering as a nested dictionary to the 'chart_params' key.
+
+ From the API, `chart_params` must be passed as a JSON in the request body with `extra_params`.
+
+ If using the chart post-request, the parameters are passed directly
+ as `key=value` pairs in the `charting.to_chart` or `charting.show` methods.
+
+ Parameters
+ ----------
+ data : Optional[Union[List[Data], DataFrame]]
+ Data for the chart. Required fields are: 'expiration' and 'price'.
+ Multiple dates will be plotted on the same chart.
+ If not supplied, the original OBBject.results will be used.
+ If a DataFrame is supplied, flat data is expected, without a set index.
+ title: Optional[str]
+ Title for the chart. If not supplied, a default title will be used.
+ colors: Optional[List[str]]
+ List of colors to use for the chart. If not supplied, the default colorway will be used.
+ Colors should be in hex format, or named Plotly colors. Invalid colors will raise a Plotly error.
+ layout_kwargs: Optional[Dict[str, Any]]
+ Additional layout parameters for the chart, passed directly to `figure.update_layout` before output.
+ See Plotly documentation for available options.
+
+ Returns
+ -------
+ Tuple[OpenBBFigure, Dict[str, Any]]
+ Tuple with the OpenBBFigure object, and the JSON-serialized content.
+ If using the API, only the JSON content will be returned.
+
+ Examples
+ --------
+ ```python
+ from openbb import obb
+ data = obb.derivatives.futures.curve(symbol="vx", provider="cboe", date=["2020-03-31", "2024-06-28"], chart=True)
+ data.show()
+ ```
+
+ Redraw the chart, from the same data, with a custom colorway and title:
+
+ ```python
+ data.charting.to_chart(colors=["green", "red"], title="VIX Futures Curve - 2020 vs. 2024")
+ ```
+ """
+ # pylint: disable=import-outside-toplevel
+ from openbb_charting.core.chart_style import ChartStyle
+ from openbb_charting.core.openbb_figure import OpenBBFigure
+ from openbb_charting.styles.colors import LARGE_CYCLER
+ from openbb_core.app.model.abstract.error import OpenBBError
+ from openbb_core.provider.abstract.data import Data
+ from pandas import DataFrame, to_datetime
+
+ data = kwargs.get("data", None)
+ symbol = kwargs.get("standard_params", {}).get("symbol", "")
+ df: DataFrame = DataFrame()
+ if data:
+ if isinstance(data, DataFrame) and not data.empty: # noqa: SIM108
+ df = data
+ elif isinstance(data, (list, Data)):
+ df = DataFrame([d.model_dump(exclude_none=True, exclude_unset=True) for d in data]) # type: ignore
+ else:
+ pass
+ else:
+ df = DataFrame(
+ [
+ d.model_dump(exclude_none=True, exclude_unset=True) # type: ignore
+ for d in kwargs["obbject_item"]
+ ]
+ if isinstance(kwargs.get("obbject_item"), list)
+ else kwargs["obbject_item"].model_dump(exclude_none=True, exclude_unset=True) # type: ignore
+ )
+
+ if df.empty:
+ raise OpenBBError("Error: No data to plot.")
+
+ if "expiration" not in df.columns:
+ raise OpenBBError("Expiration field not found in the data.")
+
+ if "price" not in df.columns:
+ raise ValueError("Price field not found in the data.")
+
+ provider = kwargs.get("provider", "")
+
+ df["expiration"] = [
+ to_datetime(d).strftime("%b-%y") if d != "Current" else d
+ for d in df["expiration"]
+ ]
+
+ if (
+ provider == "cboe"
+ and "date" in df.columns
+ and len(df["date"].unique()) > 1
+ and "symbol" in df.columns
+ ):
+ df["expiration"] = df.symbol
+
+ # Use a complete list of expirations to categorize the x-axis across all dates.
+ expirations = df["expiration"].unique().tolist()
+
+ # Use the supplied colors, if any.
+ colors = kwargs.get("colors", [])
+ if not colors:
+ colors = LARGE_CYCLER
+ color_count = 0
+
+ figure = OpenBBFigure().create_subplots(shared_xaxes=True)
+ figure.update_layout(ChartStyle().plotly_template.get("layout", {}))
+
+ def create_fig(figure, df, dates, color_count):
+ """Create a scatter for each date in the data."""
+ for date in dates:
+ color = colors[color_count % len(colors)]
+ plot_df = df[df["date"].astype(str) == date].copy()
+ plot_df = plot_df.drop(
+ columns=["date"] if "date" in plot_df.columns else []
+ ).rename(columns={"expiration": "Expiration", "price": "Price"})
+ figure.add_scatter(
+ x=plot_df["Expiration"],
+ y=plot_df["Price"],
+ # fill=fill,
+ mode="lines+markers",
+ name=date,
+ line=dict(width=3, color=color),
+ marker=dict(size=10, color=color),
+ hovertemplate=(
+ "Expiration: %{x}<br>Price: $%{y}<extra></extra>"
+ if len(dates) == 1
+ else "%{fullData.name}<br>Expiration: %{x}<br>Price: $%{y}<extra></extra>"
+ ),
+ )
+ color_count += 1
+ return figure, color_count
+
+ dates = (
+ df.date.astype(str).unique().tolist()
+ if "date" in df.columns
+ else ["Current"]
+ )
+ figure, color_count = create_fig(figure, df, dates, color_count)
+
+ # Set the title for the chart
+ title: str = ""
+ if provider == "cboe":
+ vx_eod_symbols = ["vx", "vix", "vx_eod", "^vix"]
+ title = (
+ "VIX EOD Futures Curve"
+ if symbol.lower() in vx_eod_symbols
+ else "VIX Mid-Morning TWAP Futures Curve"
+ )
+ if len(dates) == 1 and dates[0] != "Current":
+ title = f"{title} for {dates[0]}"
+ elif provider == "yfinance":
+ title = f"{symbol.upper()} Futures Curve"
+
+ # Use the supplied title, if any.
+ title = kwargs.get("title", title)
+
+ # Update the layout of the figure.
+ figure.update_layout(
+ title=dict(text=title, x=0.5, font=dict(size=20)),
+ plot_bgcolor="rgba(255,255,255,0)",
+ xaxis=dict(
+ title="",
+ ticklen=0,
+ showgrid=False,
+ type="category",
+ categoryorder="array",
+ categoryarray=expirations,
+ ),
+ yaxis=dict(
+ title="Price ($)",
+ ticklen=0,
+ showgrid=True,
+ gridcolor="rgba(128,128,128,0.3)",
+ ),
+ legend=dict(
+ orientation="v",
+ yanchor="top",
+ xanchor="right",
+ y=0.95,
+ x=0,
+ xref="paper",
+ font=dict(size=12),
+ bgcolor="rgba(0,0,0,0)",
+ ),
+ margin=dict(
+ b=10,
+ t=10,
+ ),
+ )
+
+ layout_kwargs = kwargs.get("layout_kwargs", {})
+ if layout_kwargs:
+ figure.update_layout(layout_kwargs)
+
+ content = figure.show(external=True).to_plotly_json()
+
+ return figure, content
diff --git a/openbb_platform/obbject_extensions/charting/openbb_charting/charting.py b/openbb_platform/obbject_extensions/charting/openbb_charting/charting.py
index efe4672533d..e757be90869 100644
--- a/openbb_platform/obbject_extensions/charting/openbb_charting/charting.py
+++ b/openbb_platform/obbject_extensions/charting/openbb_charting/charting.py
@@ -121,10 +121,11 @@ class Charting:
)
return self._functions[adjusted_route]
- def get_params(self) -> ChartParams:
+ def get_params(self) -> Union[ChartParams, Any]:
"""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 the class is not defined, the help for the function will be returned.
"""
if self._obbject._route is None: # pylint: disable=protected-access
raise ValueError("OBBject was initialized with no function route.")
@@ -133,9 +134,14 @@ class Charting:
).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}"
- )
+ else:
+ return help(
+ self._get_chart_function( # pylint: disable=protected-access
+ self._obbject.extra[ # pylint: disable=protected-access
+ "metadata"
+ ].route
+ )
+ )
def _prepare_data_as_df(
self, data: Optional[Union[pd.DataFrame, pd.Series]]
diff --git a/openbb_platform/obbject_extensions/charting/openbb_charting/charts/futures_curve.py b/openbb_platform/obbject_extensions/charting/openbb_charting/charts/futures_curve.py
deleted file mode 100644
index 719e805af48..00000000000
--- a/openbb_platform/obbject_extensions/charting/openbb_charting/charts/futures_curve.py
+++ /dev/null
@@ -1,61 +0,0 @@
-"""Yield curve chart."""
-
-from typing import Any, Dict, Tuple, Union
-
-import pandas as pd
-from openbb_core.app.utils import basemodel_to_df
-from plotly.graph_objs import Figure
-
-from openbb_charting.charts.generic_charts import line_chart
-from openbb_charting.core.openbb_figure import OpenBBFigure
-
-
-def futures_curve(
- **kwargs,
-) -> Tuple[Union[OpenBBFigure, Figure], Dict[str, Any]]: # noqa: PLR0912
- """Futures curve 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=kwargs.get("index", "symbol")) # type: ignore
- else:
- data = basemodel_to_df(
- kwargs["obbject_item"], index=kwargs.get("index", "symbol") # type: ignore
- )
- if not isinstance(data, pd.DataFrame):
- raise ValueError("Data must be a pandas DataFrame")
-
- # Check for required columns
- required_columns = {"expiration", "price", "symbol"}
- if not required_columns.issubset(data.columns):
- missing = required_columns - set(data.columns)
- raise ValueError(f"Missing columns in the DataFrame: {missing}")
-
- data["expiration"] = pd.to_datetime(data["expiration"])
-
- layout_kwargs: Dict[str, Any] = kwargs.get("layout_kwargs", {})
- title = kwargs.pop("title", "Futures Curve Chart")
- orientation = kwargs.pop("orientation", "v")
-
- ytitle = kwargs.pop("ytitle", "Price")
- xtitle = kwargs.pop("xtitle", "Expiration Date")
-
- fig = line_chart(
- data=data,
- x="expiration",
- y="price",
- title=title,
- xtitle=xtitle,
- ytitle=ytitle,
- orientation=orientation,
- layout_kwargs=layout_kwargs,
- **kwargs,
- )
-
- fig.update_traces(
- hovertemplate="Symbol: %{text}<br>Expiration: %{x|%Y-%m-%d}<br>Price: %{y:.2f}",
- text=data["symbol"],
- )
- content = {"plotly_json": fig.to_json()}
-
- return fig, content
diff --git a/openbb_platform/obbject_extensions/charting/openbb_charting/query_params.py b/openbb_platform/obbject_extensions/charting/openbb_charting/query_params.py
index 3eb92d55fbd..4d95aabf84b 100644
--- a/openbb_platform/obbject_extensions/charting/openbb_charting/query_params.py
+++ b/openbb_platform/obbject_extensions/charting/openbb_charting/query_params.py
@@ -116,23 +116,6 @@ class EtfHoldingsChartQueryParams(ChartQueryParams):
)
-class FuturesCurveChartQueryParams(ChartQueryParams):
- """ETF Holdings Chart Query Params."""
-
- title: Optional[str] = Field(
- default=None,
- description="Title of the chart.",
- )
- orientation: Literal["v", "h"] = Field(
- default="v",
- description="Orientation of the bars.",
- )
- layout_kwargs: Optional[Dict[str, Any]] = Field(
- default=None,
- description="Additional keyword arguments to pass to the Plotly `update_layout` method.",
- )
-
-
class EquityPriceHistoricalChartQueryParams(ChartQueryParams):
"""Equity Historical Price Chart Query Params."""
@@ -413,7 +396,6 @@ class ChartParams:
"""Chart Query Params."""
crypto_price_historical = EquityPriceHistoricalChartQueryParams
- derivatives_futures_curve = FuturesCurveChartQueryParams
derivatives_futures_historical = EquityPriceHistoricalChartQueryParams
equity_price_historical = EquityPriceHistoricalChartQueryParams
economy_fred_series = EconomyFredSeriesChartQueryParams