mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-05-21 10:11:02 +08:00
* set ChartStyle.plt_style from UserSettings * fix discrepencies between raw figure and pywry for light/dark
551 lines
18 KiB
Python
551 lines
18 KiB
Python
"""Views for the technical Extension."""
|
|
|
|
# pylint: disable=too-many-locals,use-dict-literal
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from openbb_charting.core.to_chart import to_chart
|
|
from openbb_charting.styles.colors import LARGE_CYCLER
|
|
|
|
if TYPE_CHECKING:
|
|
from openbb_charting.core.openbb_figure import OpenBBFigure
|
|
|
|
|
|
class TechnicalViews:
|
|
"""Technical Views."""
|
|
|
|
@staticmethod
|
|
def technical_sma(**kwargs) -> tuple["OpenBBFigure", dict[str, Any]]:
|
|
"""Plot simple moving average chart."""
|
|
if "ma_type" not in kwargs:
|
|
kwargs["ma_type"] = "sma"
|
|
return _ta_ma(**kwargs)
|
|
|
|
@staticmethod
|
|
def technical_ema(**kwargs) -> tuple["OpenBBFigure", dict[str, Any]]:
|
|
"""Exponential moving average chart."""
|
|
if "ma_type" not in kwargs:
|
|
kwargs["ma_type"] = "ema"
|
|
return _ta_ma(**kwargs)
|
|
|
|
@staticmethod
|
|
def technical_hma(**kwargs) -> tuple["OpenBBFigure", dict[str, Any]]:
|
|
"""Hull moving average chart."""
|
|
if "ma_type" not in kwargs:
|
|
kwargs["ma_type"] = "hma"
|
|
return _ta_ma(**kwargs)
|
|
|
|
@staticmethod
|
|
def technical_wma(**kwargs) -> tuple["OpenBBFigure", dict[str, Any]]:
|
|
"""Weighted moving average chart."""
|
|
if "ma_type" not in kwargs:
|
|
kwargs["ma_type"] = "wma"
|
|
return _ta_ma(**kwargs)
|
|
|
|
@staticmethod
|
|
def technical_zlma(**kwargs) -> tuple["OpenBBFigure", dict[str, Any]]:
|
|
"""Zero lag moving average chart."""
|
|
if "ma_type" not in kwargs:
|
|
kwargs["ma_type"] = "zlma"
|
|
return _ta_ma(**kwargs)
|
|
|
|
@staticmethod
|
|
def technical_aroon(**kwargs) -> tuple["OpenBBFigure", dict[str, Any]]:
|
|
"""Technical Aroon Chart."""
|
|
# pylint: disable=import-outside-toplevel
|
|
from openbb_charting.core.plotly_ta.ta_class import PlotlyTA
|
|
from openbb_core.app.utils import basemodel_to_df
|
|
from pandas import DataFrame
|
|
|
|
if "data" in kwargs and isinstance(kwargs["data"], DataFrame):
|
|
data = kwargs["data"]
|
|
else:
|
|
data = basemodel_to_df(
|
|
kwargs["obbject_item"], index=kwargs.get("index", "date")
|
|
)
|
|
|
|
if "date" in data.columns:
|
|
data = data.set_index("date")
|
|
|
|
if "symbol" in data.columns and len(data.symbol.unique()) > 1:
|
|
raise ValueError(
|
|
"Please provide data with only one symbol and columns for OHLC."
|
|
)
|
|
|
|
symbol = kwargs.get("symbol", "")
|
|
|
|
volume = kwargs.get("volume") is True
|
|
title = f"Aroon Indicator & Oscillator {symbol}"
|
|
|
|
length = kwargs.get("length", 25)
|
|
scalar = kwargs.get("scalar", 100)
|
|
symbol = kwargs.get("symbol", "")
|
|
|
|
ta = PlotlyTA()
|
|
fig = ta.plot( # type: ignore
|
|
data,
|
|
dict(aroon=dict(length=length, scalar=scalar)),
|
|
title,
|
|
False,
|
|
volume=volume,
|
|
)
|
|
|
|
content = fig.show(external=True).to_plotly_json()
|
|
|
|
return fig, content
|
|
|
|
@staticmethod
|
|
def technical_macd(**kwargs) -> tuple["OpenBBFigure", dict[str, Any]]:
|
|
"""Plot moving average convergence divergence chart."""
|
|
# pylint: disable=import-outside-toplevel
|
|
from openbb_charting.core.plotly_ta.ta_class import PlotlyTA
|
|
from openbb_core.app.utils import basemodel_to_df
|
|
from pandas import DataFrame
|
|
|
|
if "data" in kwargs and isinstance(kwargs["data"], DataFrame):
|
|
data = kwargs["data"]
|
|
else:
|
|
data = basemodel_to_df(
|
|
kwargs["obbject_item"], index=kwargs.get("index", "date")
|
|
)
|
|
|
|
if "date" in data.columns:
|
|
data = data.set_index("date")
|
|
|
|
if "symbol" in data.columns and len(data.symbol.unique()) > 1:
|
|
raise ValueError(
|
|
"Please provide data with only one symbol and columns for OHLC."
|
|
)
|
|
|
|
fast = kwargs.get("fast", 12)
|
|
slow = kwargs.get("slow", 26)
|
|
signal = kwargs.get("signal", 9)
|
|
symbol = kwargs.get("symbol", "")
|
|
|
|
title = f"{symbol.upper()} MACD"
|
|
volume = kwargs.get("volume") is True
|
|
|
|
ta = PlotlyTA()
|
|
fig = ta.plot( # type: ignore
|
|
data,
|
|
dict(macd=dict(fast=fast, slow=slow, signal=signal)),
|
|
title,
|
|
False,
|
|
volume=volume,
|
|
)
|
|
content = fig.show(external=True).to_plotly_json()
|
|
|
|
return fig, content
|
|
|
|
@staticmethod
|
|
def technical_adx(**kwargs) -> tuple["OpenBBFigure", dict[str, Any]]:
|
|
"""Average directional movement index chart."""
|
|
# pylint: disable=import-outside-toplevel
|
|
from openbb_charting.core.plotly_ta.ta_class import PlotlyTA
|
|
from openbb_core.app.utils import basemodel_to_df
|
|
from pandas import DataFrame
|
|
|
|
if "data" in kwargs and isinstance(kwargs["data"], DataFrame):
|
|
data = kwargs["data"]
|
|
else:
|
|
data = basemodel_to_df(
|
|
kwargs["obbject_item"], index=kwargs.get("index", "date")
|
|
)
|
|
|
|
if "date" in data.columns:
|
|
data = data.set_index("date")
|
|
|
|
if "symbol" in data.columns and len(data.symbol.unique()) > 1:
|
|
raise ValueError(
|
|
"Please provide data with only one symbol and columns for OHLC."
|
|
)
|
|
|
|
length = kwargs.get("length", 14)
|
|
scalar = kwargs.get("scalar", 100.0)
|
|
drift = kwargs.get("drift", 1)
|
|
symbol = kwargs.get("symbol", "")
|
|
|
|
ta = PlotlyTA()
|
|
fig = ta.plot( # type: ignore
|
|
data,
|
|
dict(adx=dict(length=length, scalar=scalar, drift=drift)),
|
|
f"Average Directional Movement Index (ADX) {symbol}",
|
|
False,
|
|
volume=False,
|
|
)
|
|
content = fig.show(external=True).to_plotly_json()
|
|
|
|
return fig, content
|
|
|
|
@staticmethod
|
|
def technical_rsi(**kwargs) -> tuple["OpenBBFigure", dict[str, Any]]:
|
|
"""Relative strength index chart."""
|
|
# pylint: disable=import-outside-toplevel
|
|
from openbb_charting.core.plotly_ta.ta_class import PlotlyTA
|
|
from openbb_core.app.utils import basemodel_to_df
|
|
from pandas import DataFrame
|
|
|
|
if "data" in kwargs and isinstance(kwargs["data"], DataFrame):
|
|
data = kwargs["data"]
|
|
else:
|
|
data = basemodel_to_df(
|
|
kwargs["obbject_item"], index=kwargs.get("index", "date")
|
|
)
|
|
|
|
if "date" in data.columns:
|
|
data = data.set_index("date")
|
|
|
|
if "symbol" in data.columns and len(data.symbol.unique()) > 1:
|
|
raise ValueError(
|
|
"Please provide data with only one symbol and columns for OHLC."
|
|
)
|
|
|
|
window = kwargs.get("window", 14)
|
|
scalar = kwargs.get("scalar", 100.0)
|
|
drift = kwargs.get("drift", 1)
|
|
symbol = kwargs.get("symbol", "")
|
|
|
|
ta = PlotlyTA()
|
|
fig = ta.plot( # type: ignore
|
|
data,
|
|
dict(rsi=dict(length=window, scalar=scalar, drift=drift)),
|
|
f"{symbol.upper()} RSI {window}",
|
|
False,
|
|
volume=False,
|
|
)
|
|
content = fig.show(external=True).to_plotly_json()
|
|
|
|
return fig, content
|
|
|
|
@staticmethod
|
|
def technical_cones(**kwargs) -> tuple["OpenBBFigure", dict[str, Any]]:
|
|
"""Volatility Cones Chart."""
|
|
# pylint: disable=import-outside-toplevel
|
|
from openbb_charting.core.chart_style import ChartStyle
|
|
from openbb_charting.core.openbb_figure import OpenBBFigure
|
|
from openbb_core.app.utils import basemodel_to_df
|
|
from pandas import DataFrame
|
|
|
|
data = kwargs.get("data")
|
|
|
|
if isinstance(data, 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") # type: ignore
|
|
|
|
df_ta.columns = [col.title().replace("_", " ") for col in df_ta.columns]
|
|
|
|
# Check if the data is formatted as expected.
|
|
if not all(
|
|
col in df_ta.columns for col in ["Realized", "Min", "Median", "Max"]
|
|
):
|
|
raise ValueError("Data supplied does not match the expected format.")
|
|
|
|
model = (
|
|
str(kwargs.get("model"))
|
|
.replace("std", "Standard Deviation")
|
|
.replace("_", "-")
|
|
.title()
|
|
if kwargs.get("model")
|
|
else "Standard Deviation"
|
|
)
|
|
|
|
symbol = str(kwargs.get("symbol")) + " - " if kwargs.get("symbol") else ""
|
|
|
|
title = (
|
|
str(kwargs.get("title"))
|
|
if kwargs.get("title")
|
|
else f"{symbol}Realized Volatility Cones - {model} Model"
|
|
)
|
|
|
|
colors = [
|
|
"green",
|
|
"red",
|
|
"burlywood",
|
|
"grey",
|
|
"orange",
|
|
"blue",
|
|
]
|
|
|
|
fig = OpenBBFigure()
|
|
|
|
fig.update_layout(ChartStyle().plotly_template.get("layout", {}))
|
|
|
|
text_color = "black" if ChartStyle().plt_style == "light" else "white"
|
|
|
|
for i, col in enumerate(df_ta.columns):
|
|
fig.add_scatter(
|
|
x=df_ta.index,
|
|
y=df_ta[col],
|
|
name=col,
|
|
mode="lines+markers",
|
|
hovertemplate=f"{col}: %{{y}}<extra></extra>",
|
|
marker=dict(
|
|
color=colors[i],
|
|
size=11,
|
|
),
|
|
)
|
|
|
|
fig.set_title(title)
|
|
|
|
fig.update_layout(
|
|
font=dict(color=text_color),
|
|
legend=dict(
|
|
orientation="h",
|
|
yanchor="bottom",
|
|
xanchor="right",
|
|
y=1.02,
|
|
x=1,
|
|
bgcolor=(
|
|
"rgba(0,0,0,0)" if text_color == "white" else "rgba(255,255,255,0)"
|
|
),
|
|
),
|
|
yaxis=dict(
|
|
ticklen=0,
|
|
showgrid=True,
|
|
showline=True,
|
|
mirror=True,
|
|
zeroline=False,
|
|
gridcolor="rgba(128,128,128,0.3)",
|
|
),
|
|
xaxis=dict(
|
|
type="category",
|
|
tickmode="array",
|
|
ticklen=0,
|
|
tickvals=df_ta.index,
|
|
ticktext=df_ta.index,
|
|
title_text="Period",
|
|
showgrid=False,
|
|
showline=True,
|
|
mirror=True,
|
|
zeroline=False,
|
|
),
|
|
margin=dict(l=20, r=20, b=20),
|
|
dragmode="pan",
|
|
)
|
|
|
|
content = fig.to_plotly_json()
|
|
|
|
return fig, content
|
|
|
|
@staticmethod
|
|
def technical_relative_rotation(
|
|
**kwargs: Any,
|
|
) -> tuple["OpenBBFigure", dict[str, Any]]:
|
|
"""Relative Rotation Chart."""
|
|
# pylint: disable=import-outside-toplevel
|
|
from openbb_charting.charts import relative_rotation # noqa
|
|
from openbb_charting.core.chart_style import ChartStyle # noqa
|
|
from openbb_charting.core.openbb_figure import OpenBBFigure # noqa
|
|
from openbb_core.app.utils import basemodel_to_df # noqa
|
|
|
|
ratios_df = basemodel_to_df(kwargs["obbject_item"].rs_ratios, index="date") # type: ignore
|
|
momentum_df = basemodel_to_df(kwargs["obbject_item"].rs_momentum, index="date") # type: ignore
|
|
benchmark_symbol = kwargs["obbject_item"].benchmark # type: ignore
|
|
study = kwargs.get("study")
|
|
study = str(kwargs["obbject_item"].study) if study is None else str(study)
|
|
show_tails = kwargs.get("show_tails")
|
|
show_tails = True if show_tails is None else show_tails
|
|
tail_periods = int(kwargs.get("tail_periods")) if "tail_periods" in kwargs else 16 # type: ignore
|
|
tail_interval = str(kwargs.get("tail_interval")) if "tail_interval" in kwargs else "week" # type: ignore
|
|
date = kwargs.get("date") if "date" in kwargs else None # type: ignore
|
|
show_tails = False if date is not None else show_tails
|
|
if ratios_df.empty or momentum_df.empty:
|
|
raise RuntimeError("Error: No data to plot.")
|
|
|
|
if show_tails is True:
|
|
fig = relative_rotation.create_rrg_with_tails(
|
|
ratios_df,
|
|
momentum_df,
|
|
study,
|
|
benchmark_symbol,
|
|
tail_periods,
|
|
tail_interval, # type: ignore
|
|
)
|
|
|
|
if show_tails is False:
|
|
fig = relative_rotation.create_rrg_without_tails(
|
|
ratios_df,
|
|
momentum_df,
|
|
benchmark_symbol,
|
|
study,
|
|
date, # type: ignore
|
|
)
|
|
|
|
figure = OpenBBFigure(fig) # pylint: disable=E0606
|
|
font_color = "black" if ChartStyle().plt_style == "light" else "white"
|
|
figure.update_layout(
|
|
plot_bgcolor="rgba(255,255,255,1)",
|
|
font=dict(color=font_color),
|
|
yaxis=dict(
|
|
showgrid=True,
|
|
gridcolor="rgba(128,128,128,0.3)",
|
|
side="left",
|
|
showline=True,
|
|
zeroline=True,
|
|
mirror=True,
|
|
ticklen=0,
|
|
tickfont=dict(size=14),
|
|
title=dict(font=dict(size=16)),
|
|
),
|
|
xaxis=dict(
|
|
showgrid=True,
|
|
gridcolor="rgba(128,128,128,0.3)",
|
|
showline=True,
|
|
zeroline=True,
|
|
mirror=True,
|
|
ticklen=0,
|
|
tickfont=dict(size=14),
|
|
title=dict(font=dict(size=16)),
|
|
hoverformat="",
|
|
),
|
|
hoverlabel=dict(
|
|
font_size=12,
|
|
),
|
|
)
|
|
|
|
if kwargs.get("title") is not None:
|
|
figure.set_title(str(kwargs.get("title")))
|
|
content = figure.to_plotly_json()
|
|
|
|
return figure, content
|
|
|
|
|
|
def _ta_ma(**kwargs):
|
|
"""Plot moving average helper."""
|
|
# pylint: disable=import-outside-toplevel
|
|
from openbb_charting.core.chart_style import ChartStyle
|
|
from openbb_charting.core.openbb_figure import OpenBBFigure
|
|
from openbb_core.app.utils import basemodel_to_df
|
|
from pandas import DataFrame
|
|
|
|
index = (
|
|
kwargs.get("index")
|
|
if "index" in kwargs and kwargs.get("index") is not None
|
|
else "date"
|
|
)
|
|
data = kwargs.get("data")
|
|
ma_type = (
|
|
kwargs["ma_type"]
|
|
if "ma_type" in kwargs and kwargs.get("ma_type") is not None
|
|
else "sma"
|
|
)
|
|
ma_types = ma_type.split(",") if isinstance(ma_type, str) else ma_type
|
|
|
|
if isinstance(data, DataFrame) and not data.empty:
|
|
data = data.set_index(index) if index in data.columns else data
|
|
|
|
if data is None:
|
|
data = basemodel_to_df(kwargs["obbject_item"], index=index)
|
|
|
|
if isinstance(data, list):
|
|
data = basemodel_to_df(data, index=index)
|
|
|
|
window = (
|
|
kwargs.get("length", [])
|
|
if "length" in kwargs and kwargs.get("length") is not None
|
|
else [50]
|
|
)
|
|
offset = kwargs.get("offset", 0)
|
|
target = (
|
|
kwargs.get("target")
|
|
if "target" in kwargs and kwargs.get("target") is not None
|
|
else "close"
|
|
)
|
|
|
|
if target not in data.columns and "close" in data.columns:
|
|
target = "close"
|
|
|
|
if target not in data.columns and "close" not in data.columns:
|
|
raise ValueError(f"Column '{target}', or 'close', not found in the data.")
|
|
|
|
df = data.copy()
|
|
if target in data.columns:
|
|
df = df[[target]]
|
|
df.columns = ["close"]
|
|
title = (
|
|
kwargs.get("title")
|
|
if "title" in kwargs and kwargs.get("title") is not None
|
|
else f"{ma_type.upper()}"
|
|
)
|
|
|
|
fig = OpenBBFigure()
|
|
fig = fig.create_subplots(
|
|
1,
|
|
1,
|
|
shared_xaxes=True,
|
|
vertical_spacing=0.06,
|
|
horizontal_spacing=0.01,
|
|
row_width=[1],
|
|
specs=[[{"secondary_y": True}]],
|
|
)
|
|
fig.update_layout(ChartStyle().plotly_template.get("layout", {}))
|
|
font_color = "black" if ChartStyle().plt_style == "light" else "white"
|
|
ma_df = DataFrame()
|
|
window = [window] if isinstance(window, int) else window
|
|
for w in window:
|
|
for ma_type in ma_types:
|
|
ma_df[f"{ma_type.upper()} {w}"] = getattr(df.ta, ma_type)(
|
|
length=w, offset=offset
|
|
)
|
|
|
|
if kwargs.get("dropnan") is True:
|
|
ma_df = ma_df.dropna()
|
|
data = data.iloc[-len(ma_df) :]
|
|
|
|
if (
|
|
"candles" in kwargs
|
|
and kwargs.get("candles") is True
|
|
and kwargs.get("target") is None
|
|
):
|
|
volume = kwargs.get("volume") is True
|
|
fig, _ = to_chart(data, candles=True, volume=volume)
|
|
|
|
else:
|
|
ma_df[f"{target}".title()] = data[target]
|
|
|
|
for i, col in enumerate(ma_df.columns):
|
|
name = col.replace("_", " ")
|
|
fig.add_scatter(
|
|
x=ma_df.index,
|
|
y=ma_df[col],
|
|
name=name,
|
|
mode="lines",
|
|
hovertemplate=f"{name}: %{{y}}<extra></extra>",
|
|
line=dict(width=1, color=LARGE_CYCLER[i]),
|
|
showlegend=True,
|
|
)
|
|
|
|
fig.update_layout(
|
|
title=dict(text=title, x=0.5, font=dict(size=16)),
|
|
showlegend=True,
|
|
legend=dict(
|
|
orientation="h",
|
|
yanchor="bottom",
|
|
xanchor="right",
|
|
y=1.02,
|
|
x=0.95,
|
|
bgcolor="rgba(0,0,0,0)" if font_color == "white" else "rgba(255,255,255,0)",
|
|
),
|
|
xaxis=dict(
|
|
ticklen=0,
|
|
showgrid=True,
|
|
gridcolor="rgba(128,128,128,0.3)",
|
|
zeroline=True,
|
|
mirror=True,
|
|
),
|
|
yaxis=dict(
|
|
ticklen=0,
|
|
showgrid=True,
|
|
gridcolor="rgba(128,128,128,0.3)",
|
|
zeroline=True,
|
|
mirror=True,
|
|
autorange=True,
|
|
),
|
|
font=dict(color=font_color),
|
|
)
|
|
|
|
content = fig.show(external=True).to_plotly_json()
|
|
|
|
return fig, content
|