|
|
"""Views for the technical Extension.""" |
|
|
|
|
|
|
|
|
|
|
|
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.""" |
|
|
|
|
|
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( |
|
|
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.""" |
|
|
|
|
|
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( |
|
|
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.""" |
|
|
|
|
|
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( |
|
|
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.""" |
|
|
|
|
|
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( |
|
|
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.""" |
|
|
|
|
|
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") |
|
|
|
|
|
df_ta.columns = [col.title().replace("_", " ") for col in df_ta.columns] |
|
|
|
|
|
|
|
|
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( |
|
|
paper_bgcolor=( |
|
|
"rgba(0,0,0,0)" if text_color == "white" else "rgba(255,255,255,0)" |
|
|
), |
|
|
plot_bgcolor=( |
|
|
"rgba(0,0,0,0)" if text_color == "white" else "rgba(255,255,255,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)" 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.""" |
|
|
|
|
|
from openbb_charting.charts import relative_rotation |
|
|
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 |
|
|
|
|
|
ratios_df = basemodel_to_df(kwargs["obbject_item"].rs_ratios, index="date") |
|
|
momentum_df = basemodel_to_df(kwargs["obbject_item"].rs_momentum, index="date") |
|
|
benchmark_symbol = kwargs["obbject_item"].benchmark |
|
|
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 |
|
|
tail_interval = str(kwargs.get("tail_interval")) if "tail_interval" in kwargs else "week" |
|
|
date = kwargs.get("date") if "date" in kwargs else None |
|
|
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 |
|
|
) |
|
|
|
|
|
if show_tails is False: |
|
|
fig = relative_rotation.create_rrg_without_tails( |
|
|
ratios_df, momentum_df, benchmark_symbol, study, date |
|
|
) |
|
|
|
|
|
figure = OpenBBFigure(fig) |
|
|
font_color = "black" if ChartStyle().plt_style == "light" else "white" |
|
|
figure.update_layout( |
|
|
paper_bgcolor=( |
|
|
"rgba(0,0,0,0)" if font_color == "white" else "rgba(255,255,255,255)" |
|
|
), |
|
|
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.""" |
|
|
|
|
|
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)), |
|
|
paper_bgcolor=( |
|
|
"rgba(0,0,0,0)" if font_color == "white" else "rgba(255,255,255,255)" |
|
|
), |
|
|
plot_bgcolor=( |
|
|
"rgba(0,0,0,0)" if font_color == "white" else "rgba(255,255,255,0)" |
|
|
), |
|
|
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 |
|
|
|