openbb-technical 1.2.2__tar.gz → 1.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openbb_technical-1.2.2 → openbb_technical-1.2.3}/PKG-INFO +2 -2
- openbb_technical-1.2.3/openbb_technical/technical_views.py +511 -0
- {openbb_technical-1.2.2 → openbb_technical-1.2.3}/pyproject.toml +5 -2
- {openbb_technical-1.2.2 → openbb_technical-1.2.3}/README.md +0 -0
- {openbb_technical-1.2.2 → openbb_technical-1.2.3}/openbb_technical/__init__.py +0 -0
- {openbb_technical-1.2.2 → openbb_technical-1.2.3}/openbb_technical/helpers.py +0 -0
- {openbb_technical-1.2.2 → openbb_technical-1.2.3}/openbb_technical/py.typed +0 -0
- {openbb_technical-1.2.2 → openbb_technical-1.2.3}/openbb_technical/relative_rotation.py +0 -0
- {openbb_technical-1.2.2 → openbb_technical-1.2.3}/openbb_technical/technical_router.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: openbb-technical
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.3
|
|
4
4
|
Summary: Technical Analysis extension for OpenBB
|
|
5
5
|
License: AGPL-3.0-only
|
|
6
6
|
Author: OpenBB Team
|
|
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.9
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
-
Requires-Dist: openbb-core (>=1.2.
|
|
15
|
+
Requires-Dist: openbb-core (>=1.2.5,<2.0.0)
|
|
16
16
|
Requires-Dist: pandas-ta (>=0.3.14b,<0.4.0)
|
|
17
17
|
Requires-Dist: scikit-learn (>=1.3.1,<2.0.0)
|
|
18
18
|
Requires-Dist: scipy (>=1.10.1,<2.0.0)
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
"""Views for the technical Extension."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Tuple
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from openbb_charting.charts import relative_rotation
|
|
7
|
+
from openbb_charting.core.chart_style import ChartStyle
|
|
8
|
+
from openbb_charting.core.openbb_figure import OpenBBFigure
|
|
9
|
+
from openbb_charting.core.plotly_ta.ta_class import PlotlyTA
|
|
10
|
+
from openbb_charting.core.to_chart import to_chart
|
|
11
|
+
from openbb_charting.styles.colors import LARGE_CYCLER
|
|
12
|
+
from openbb_core.app.utils import basemodel_to_df
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TechnicalViews:
|
|
16
|
+
"""Technical Views."""
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def technical_sma(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
20
|
+
"""Plot simple moving average chart."""
|
|
21
|
+
if "ma_type" not in kwargs:
|
|
22
|
+
kwargs["ma_type"] = "sma"
|
|
23
|
+
return _ta_ma(**kwargs)
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def technical_ema(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
27
|
+
"""Exponential moving average chart."""
|
|
28
|
+
if "ma_type" not in kwargs:
|
|
29
|
+
kwargs["ma_type"] = "ema"
|
|
30
|
+
return _ta_ma(**kwargs)
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def technical_hma(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
34
|
+
"""Hull moving average chart."""
|
|
35
|
+
if "ma_type" not in kwargs:
|
|
36
|
+
kwargs["ma_type"] = "hma"
|
|
37
|
+
return _ta_ma(**kwargs)
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def technical_wma(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
41
|
+
"""Weighted moving average chart."""
|
|
42
|
+
if "ma_type" not in kwargs:
|
|
43
|
+
kwargs["ma_type"] = "wma"
|
|
44
|
+
return _ta_ma(**kwargs)
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def technical_zlma(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
48
|
+
"""Zero lag moving average chart."""
|
|
49
|
+
if "ma_type" not in kwargs:
|
|
50
|
+
kwargs["ma_type"] = "zlma"
|
|
51
|
+
return _ta_ma(**kwargs)
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def technical_aroon(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
55
|
+
"""Technical Aroon Chart."""
|
|
56
|
+
if "data" in kwargs and isinstance(kwargs["data"], pd.DataFrame):
|
|
57
|
+
data = kwargs["data"]
|
|
58
|
+
else:
|
|
59
|
+
data = basemodel_to_df(
|
|
60
|
+
kwargs["obbject_item"], index=kwargs.get("index", "date")
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if "date" in data.columns:
|
|
64
|
+
data = data.set_index("date")
|
|
65
|
+
|
|
66
|
+
if "symbol" in data.columns and len(data.symbol.unique()) > 1:
|
|
67
|
+
raise ValueError(
|
|
68
|
+
"Please provide data with only one symbol and columns for OHLC."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
symbol = kwargs.get("symbol", "")
|
|
72
|
+
|
|
73
|
+
volume = kwargs.get("volume") is True
|
|
74
|
+
title = f"Aroon Indicator & Oscillator {symbol}"
|
|
75
|
+
|
|
76
|
+
length = kwargs.get("length", 25)
|
|
77
|
+
scalar = kwargs.get("scalar", 100)
|
|
78
|
+
symbol = kwargs.get("symbol", "")
|
|
79
|
+
|
|
80
|
+
ta = PlotlyTA()
|
|
81
|
+
fig = ta.plot( # type: ignore
|
|
82
|
+
data,
|
|
83
|
+
dict(aroon=dict(length=length, scalar=scalar)),
|
|
84
|
+
title,
|
|
85
|
+
False,
|
|
86
|
+
volume=volume,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
content = fig.show(external=True).to_plotly_json()
|
|
90
|
+
|
|
91
|
+
return fig, content
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def technical_macd(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
95
|
+
"""Plot moving average convergence divergence chart."""
|
|
96
|
+
if "data" in kwargs and isinstance(kwargs["data"], pd.DataFrame):
|
|
97
|
+
data = kwargs["data"]
|
|
98
|
+
else:
|
|
99
|
+
data = basemodel_to_df(
|
|
100
|
+
kwargs["obbject_item"], index=kwargs.get("index", "date")
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if "date" in data.columns:
|
|
104
|
+
data = data.set_index("date")
|
|
105
|
+
|
|
106
|
+
if "symbol" in data.columns and len(data.symbol.unique()) > 1:
|
|
107
|
+
raise ValueError(
|
|
108
|
+
"Please provide data with only one symbol and columns for OHLC."
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
fast = kwargs.get("fast", 12)
|
|
112
|
+
slow = kwargs.get("slow", 26)
|
|
113
|
+
signal = kwargs.get("signal", 9)
|
|
114
|
+
symbol = kwargs.get("symbol", "")
|
|
115
|
+
|
|
116
|
+
title = f"{symbol.upper()} MACD"
|
|
117
|
+
volume = kwargs.get("volume") is True
|
|
118
|
+
|
|
119
|
+
ta = PlotlyTA()
|
|
120
|
+
fig = ta.plot( # type: ignore
|
|
121
|
+
data,
|
|
122
|
+
dict(macd=dict(fast=fast, slow=slow, signal=signal)),
|
|
123
|
+
title,
|
|
124
|
+
False,
|
|
125
|
+
volume=volume,
|
|
126
|
+
)
|
|
127
|
+
content = fig.show(external=True).to_plotly_json()
|
|
128
|
+
|
|
129
|
+
return fig, content
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def technical_adx(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
133
|
+
"""Average directional movement index chart."""
|
|
134
|
+
if "data" in kwargs and isinstance(kwargs["data"], pd.DataFrame):
|
|
135
|
+
data = kwargs["data"]
|
|
136
|
+
else:
|
|
137
|
+
data = basemodel_to_df(
|
|
138
|
+
kwargs["obbject_item"], index=kwargs.get("index", "date")
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if "date" in data.columns:
|
|
142
|
+
data = data.set_index("date")
|
|
143
|
+
|
|
144
|
+
if "symbol" in data.columns and len(data.symbol.unique()) > 1:
|
|
145
|
+
raise ValueError(
|
|
146
|
+
"Please provide data with only one symbol and columns for OHLC."
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
length = kwargs.get("length", 14)
|
|
150
|
+
scalar = kwargs.get("scalar", 100.0)
|
|
151
|
+
drift = kwargs.get("drift", 1)
|
|
152
|
+
symbol = kwargs.get("symbol", "")
|
|
153
|
+
|
|
154
|
+
ta = PlotlyTA()
|
|
155
|
+
fig = ta.plot( # type: ignore
|
|
156
|
+
data,
|
|
157
|
+
dict(adx=dict(length=length, scalar=scalar, drift=drift)),
|
|
158
|
+
f"Average Directional Movement Index (ADX) {symbol}",
|
|
159
|
+
False,
|
|
160
|
+
volume=False,
|
|
161
|
+
)
|
|
162
|
+
content = fig.show(external=True).to_plotly_json()
|
|
163
|
+
|
|
164
|
+
return fig, content
|
|
165
|
+
|
|
166
|
+
@staticmethod
|
|
167
|
+
def technical_rsi(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
168
|
+
"""Relative strength index chart."""
|
|
169
|
+
if "data" in kwargs and isinstance(kwargs["data"], pd.DataFrame):
|
|
170
|
+
data = kwargs["data"]
|
|
171
|
+
else:
|
|
172
|
+
data = basemodel_to_df(
|
|
173
|
+
kwargs["obbject_item"], index=kwargs.get("index", "date")
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if "date" in data.columns:
|
|
177
|
+
data = data.set_index("date")
|
|
178
|
+
|
|
179
|
+
if "symbol" in data.columns and len(data.symbol.unique()) > 1:
|
|
180
|
+
raise ValueError(
|
|
181
|
+
"Please provide data with only one symbol and columns for OHLC."
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
window = kwargs.get("window", 14)
|
|
185
|
+
scalar = kwargs.get("scalar", 100.0)
|
|
186
|
+
drift = kwargs.get("drift", 1)
|
|
187
|
+
symbol = kwargs.get("symbol", "")
|
|
188
|
+
|
|
189
|
+
ta = PlotlyTA()
|
|
190
|
+
fig = ta.plot( # type: ignore
|
|
191
|
+
data,
|
|
192
|
+
dict(rsi=dict(length=window, scalar=scalar, drift=drift)),
|
|
193
|
+
f"{symbol.upper()} RSI {window}",
|
|
194
|
+
False,
|
|
195
|
+
volume=False,
|
|
196
|
+
)
|
|
197
|
+
content = fig.show(external=True).to_plotly_json()
|
|
198
|
+
|
|
199
|
+
return fig, content
|
|
200
|
+
|
|
201
|
+
@staticmethod
|
|
202
|
+
def technical_cones(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
203
|
+
"""Volatility Cones Chart."""
|
|
204
|
+
data = kwargs.get("data")
|
|
205
|
+
|
|
206
|
+
if (
|
|
207
|
+
isinstance(data, pd.DataFrame)
|
|
208
|
+
and not data.empty
|
|
209
|
+
and "window" in data.columns
|
|
210
|
+
):
|
|
211
|
+
df_ta = data.set_index("window")
|
|
212
|
+
else:
|
|
213
|
+
df_ta = basemodel_to_df(kwargs["obbject_item"], index="window") # type: ignore
|
|
214
|
+
|
|
215
|
+
df_ta.columns = [col.title().replace("_", " ") for col in df_ta.columns]
|
|
216
|
+
|
|
217
|
+
# Check if the data is formatted as expected.
|
|
218
|
+
if not all(
|
|
219
|
+
col in df_ta.columns for col in ["Realized", "Min", "Median", "Max"]
|
|
220
|
+
):
|
|
221
|
+
raise ValueError("Data supplied does not match the expected format.")
|
|
222
|
+
|
|
223
|
+
model = (
|
|
224
|
+
str(kwargs.get("model"))
|
|
225
|
+
.replace("std", "Standard Deviation")
|
|
226
|
+
.replace("_", "-")
|
|
227
|
+
.title()
|
|
228
|
+
if kwargs.get("model")
|
|
229
|
+
else "Standard Deviation"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
symbol = str(kwargs.get("symbol")) + " - " if kwargs.get("symbol") else ""
|
|
233
|
+
|
|
234
|
+
title = (
|
|
235
|
+
str(kwargs.get("title"))
|
|
236
|
+
if kwargs.get("title")
|
|
237
|
+
else f"{symbol}Realized Volatility Cones - {model} Model"
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
colors = [
|
|
241
|
+
"green",
|
|
242
|
+
"red",
|
|
243
|
+
"burlywood",
|
|
244
|
+
"grey",
|
|
245
|
+
"orange",
|
|
246
|
+
"blue",
|
|
247
|
+
]
|
|
248
|
+
|
|
249
|
+
fig = OpenBBFigure()
|
|
250
|
+
|
|
251
|
+
fig.update_layout(ChartStyle().plotly_template.get("layout", {}))
|
|
252
|
+
|
|
253
|
+
text_color = "black" if ChartStyle().plt_style == "light" else "white"
|
|
254
|
+
|
|
255
|
+
for i, col in enumerate(df_ta.columns):
|
|
256
|
+
fig.add_scatter(
|
|
257
|
+
x=df_ta.index,
|
|
258
|
+
y=df_ta[col],
|
|
259
|
+
name=col,
|
|
260
|
+
mode="lines+markers",
|
|
261
|
+
hovertemplate=f"{col}: %{{y}}<extra></extra>",
|
|
262
|
+
marker=dict(
|
|
263
|
+
color=colors[i],
|
|
264
|
+
size=11,
|
|
265
|
+
),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
fig.set_title(title)
|
|
269
|
+
|
|
270
|
+
fig.update_layout(
|
|
271
|
+
paper_bgcolor="rgba(0,0,0,0)",
|
|
272
|
+
plot_bgcolor="rgba(0,0,0,0)",
|
|
273
|
+
font=dict(color=text_color),
|
|
274
|
+
legend=dict(
|
|
275
|
+
orientation="h",
|
|
276
|
+
yanchor="bottom",
|
|
277
|
+
xanchor="right",
|
|
278
|
+
y=1.02,
|
|
279
|
+
x=1,
|
|
280
|
+
bgcolor="rgba(0,0,0,0)",
|
|
281
|
+
),
|
|
282
|
+
yaxis=dict(
|
|
283
|
+
ticklen=0,
|
|
284
|
+
showgrid=True,
|
|
285
|
+
showline=True,
|
|
286
|
+
mirror=True,
|
|
287
|
+
zeroline=False,
|
|
288
|
+
gridcolor="rgba(128,128,128,0.3)",
|
|
289
|
+
),
|
|
290
|
+
xaxis=dict(
|
|
291
|
+
type="category",
|
|
292
|
+
tickmode="array",
|
|
293
|
+
ticklen=0,
|
|
294
|
+
tickvals=df_ta.index,
|
|
295
|
+
ticktext=df_ta.index,
|
|
296
|
+
title_text="Period",
|
|
297
|
+
showgrid=False,
|
|
298
|
+
showline=True,
|
|
299
|
+
mirror=True,
|
|
300
|
+
zeroline=False,
|
|
301
|
+
),
|
|
302
|
+
margin=dict(l=20, r=20, b=20),
|
|
303
|
+
dragmode="pan",
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
content = fig.to_plotly_json()
|
|
307
|
+
|
|
308
|
+
return fig, content
|
|
309
|
+
|
|
310
|
+
@staticmethod
|
|
311
|
+
def technical_relative_rotation(
|
|
312
|
+
**kwargs: Any,
|
|
313
|
+
) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
314
|
+
"""Relative Rotation Chart."""
|
|
315
|
+
ratios_df = basemodel_to_df(kwargs["obbject_item"].rs_ratios, index="date") # type: ignore
|
|
316
|
+
momentum_df = basemodel_to_df(kwargs["obbject_item"].rs_momentum, index="date") # type: ignore
|
|
317
|
+
benchmark_symbol = kwargs["obbject_item"].benchmark # type: ignore
|
|
318
|
+
study = kwargs.get("study", None)
|
|
319
|
+
study = str(kwargs["obbject_item"].study) if study is None else str(study)
|
|
320
|
+
show_tails = kwargs.get("show_tails")
|
|
321
|
+
show_tails = True if show_tails is None else show_tails
|
|
322
|
+
tail_periods = int(kwargs.get("tail_periods")) if "tail_periods" in kwargs else 16 # type: ignore
|
|
323
|
+
tail_interval = str(kwargs.get("tail_interval")) if "tail_interval" in kwargs else "week" # type: ignore
|
|
324
|
+
date = kwargs.get("date") if "date" in kwargs else None # type: ignore
|
|
325
|
+
show_tails = False if date is not None else show_tails
|
|
326
|
+
if ratios_df.empty or momentum_df.empty:
|
|
327
|
+
raise RuntimeError("Error: No data to plot.")
|
|
328
|
+
|
|
329
|
+
if show_tails is True:
|
|
330
|
+
fig = relative_rotation.create_rrg_with_tails(
|
|
331
|
+
ratios_df, momentum_df, study, benchmark_symbol, tail_periods, tail_interval # type: ignore
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
if show_tails is False:
|
|
335
|
+
fig = relative_rotation.create_rrg_without_tails(
|
|
336
|
+
ratios_df, momentum_df, benchmark_symbol, study, date # type: ignore
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
figure = OpenBBFigure(fig) # pylint: disable=E0606
|
|
340
|
+
font_color = "black" if ChartStyle().plt_style == "light" else "white"
|
|
341
|
+
figure.update_layout(
|
|
342
|
+
paper_bgcolor="rgba(0,0,0,0)",
|
|
343
|
+
plot_bgcolor="rgba(255,255,255,1)",
|
|
344
|
+
font=dict(color=font_color),
|
|
345
|
+
yaxis=dict(
|
|
346
|
+
showgrid=True,
|
|
347
|
+
gridcolor="rgba(128,128,128,0.3)",
|
|
348
|
+
side="left",
|
|
349
|
+
showline=True,
|
|
350
|
+
zeroline=True,
|
|
351
|
+
mirror=True,
|
|
352
|
+
ticklen=0,
|
|
353
|
+
tickfont=dict(size=14),
|
|
354
|
+
titlefont=dict(size=16),
|
|
355
|
+
),
|
|
356
|
+
xaxis=dict(
|
|
357
|
+
showgrid=True,
|
|
358
|
+
gridcolor="rgba(128,128,128,0.3)",
|
|
359
|
+
showline=True,
|
|
360
|
+
zeroline=True,
|
|
361
|
+
mirror=True,
|
|
362
|
+
ticklen=0,
|
|
363
|
+
tickfont=dict(size=14),
|
|
364
|
+
titlefont=dict(size=16),
|
|
365
|
+
hoverformat="",
|
|
366
|
+
),
|
|
367
|
+
hoverlabel=dict(
|
|
368
|
+
font_size=12,
|
|
369
|
+
),
|
|
370
|
+
hovermode="x",
|
|
371
|
+
hoverdistance=50,
|
|
372
|
+
)
|
|
373
|
+
if kwargs.get("title") is not None:
|
|
374
|
+
figure.set_title(str(kwargs.get("title")))
|
|
375
|
+
content = figure.to_plotly_json()
|
|
376
|
+
|
|
377
|
+
return figure, content
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _ta_ma(**kwargs):
|
|
381
|
+
"""Plot moving average helper."""
|
|
382
|
+
index = (
|
|
383
|
+
kwargs.get("index")
|
|
384
|
+
if "index" in kwargs and kwargs.get("index") is not None
|
|
385
|
+
else "date"
|
|
386
|
+
)
|
|
387
|
+
data = kwargs.get("data")
|
|
388
|
+
ma_type = (
|
|
389
|
+
kwargs["ma_type"]
|
|
390
|
+
if "ma_type" in kwargs and kwargs.get("ma_type") is not None
|
|
391
|
+
else "sma"
|
|
392
|
+
)
|
|
393
|
+
ma_types = ma_type.split(",") if isinstance(ma_type, str) else ma_type
|
|
394
|
+
|
|
395
|
+
if isinstance(data, pd.DataFrame) and not data.empty:
|
|
396
|
+
data = data.set_index(index) if index in data.columns else data
|
|
397
|
+
|
|
398
|
+
if data is None:
|
|
399
|
+
data = basemodel_to_df(kwargs["obbject_item"], index=index)
|
|
400
|
+
|
|
401
|
+
if isinstance(data, list):
|
|
402
|
+
data = basemodel_to_df(data, index=index)
|
|
403
|
+
|
|
404
|
+
window = (
|
|
405
|
+
kwargs.get("length", [])
|
|
406
|
+
if "length" in kwargs and kwargs.get("length") is not None
|
|
407
|
+
else [50]
|
|
408
|
+
)
|
|
409
|
+
offset = kwargs.get("offset", 0)
|
|
410
|
+
target = (
|
|
411
|
+
kwargs.get("target")
|
|
412
|
+
if "target" in kwargs and kwargs.get("target") is not None
|
|
413
|
+
else "close"
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
if target not in data.columns and "close" in data.columns:
|
|
417
|
+
target = "close"
|
|
418
|
+
|
|
419
|
+
if target not in data.columns and "close" not in data.columns:
|
|
420
|
+
raise ValueError(f"Column '{target}', or 'close', not found in the data.")
|
|
421
|
+
|
|
422
|
+
df = data.copy()
|
|
423
|
+
if target in data.columns:
|
|
424
|
+
df = df[[target]]
|
|
425
|
+
df.columns = ["close"]
|
|
426
|
+
title = (
|
|
427
|
+
kwargs.get("title")
|
|
428
|
+
if "title" in kwargs and kwargs.get("title") is not None
|
|
429
|
+
else f"{ma_type.upper()}"
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
fig = OpenBBFigure()
|
|
433
|
+
fig = fig.create_subplots(
|
|
434
|
+
1,
|
|
435
|
+
1,
|
|
436
|
+
shared_xaxes=True,
|
|
437
|
+
vertical_spacing=0.06,
|
|
438
|
+
horizontal_spacing=0.01,
|
|
439
|
+
row_width=[1],
|
|
440
|
+
specs=[[{"secondary_y": True}]],
|
|
441
|
+
)
|
|
442
|
+
fig.update_layout(ChartStyle().plotly_template.get("layout", {}))
|
|
443
|
+
|
|
444
|
+
ma_df = pd.DataFrame()
|
|
445
|
+
window = [window] if isinstance(window, int) else window
|
|
446
|
+
for w in window:
|
|
447
|
+
for ma_type in ma_types:
|
|
448
|
+
ma_df[f"{ma_type.upper()} {w}"] = getattr(df.ta, ma_type)(
|
|
449
|
+
length=w, offset=offset
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
if kwargs.get("dropnan") is True:
|
|
453
|
+
ma_df = ma_df.dropna()
|
|
454
|
+
data = data.iloc[-len(ma_df) :]
|
|
455
|
+
|
|
456
|
+
if (
|
|
457
|
+
"candles" in kwargs
|
|
458
|
+
and kwargs.get("candles") is True
|
|
459
|
+
and kwargs.get("target") is None
|
|
460
|
+
):
|
|
461
|
+
volume = kwargs.get("volume") is True
|
|
462
|
+
fig, _ = to_chart(data, candles=True, volume=volume)
|
|
463
|
+
|
|
464
|
+
else:
|
|
465
|
+
ma_df[f"{target}".title()] = data[target]
|
|
466
|
+
|
|
467
|
+
for i, col in enumerate(ma_df.columns):
|
|
468
|
+
name = col.replace("_", " ")
|
|
469
|
+
fig.add_scatter(
|
|
470
|
+
x=ma_df.index,
|
|
471
|
+
y=ma_df[col],
|
|
472
|
+
name=name,
|
|
473
|
+
mode="lines",
|
|
474
|
+
hovertemplate=f"{name}: %{{y}}<extra></extra>",
|
|
475
|
+
line=dict(width=1, color=LARGE_CYCLER[i]),
|
|
476
|
+
showlegend=True,
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
fig.update_layout(
|
|
480
|
+
title=dict(text=title, x=0.5, font=dict(size=16)),
|
|
481
|
+
paper_bgcolor="rgba(0,0,0,0)",
|
|
482
|
+
plot_bgcolor="rgba(0,0,0,0)",
|
|
483
|
+
showlegend=True,
|
|
484
|
+
legend=dict(
|
|
485
|
+
orientation="h",
|
|
486
|
+
yanchor="bottom",
|
|
487
|
+
xanchor="right",
|
|
488
|
+
y=1.02,
|
|
489
|
+
x=0.95,
|
|
490
|
+
bgcolor="rgba(0,0,0,0)",
|
|
491
|
+
),
|
|
492
|
+
xaxis=dict(
|
|
493
|
+
ticklen=0,
|
|
494
|
+
showgrid=True,
|
|
495
|
+
gridcolor="rgba(128,128,128,0.3)",
|
|
496
|
+
zeroline=True,
|
|
497
|
+
mirror=True,
|
|
498
|
+
),
|
|
499
|
+
yaxis=dict(
|
|
500
|
+
ticklen=0,
|
|
501
|
+
showgrid=True,
|
|
502
|
+
gridcolor="rgba(128,128,128,0.3)",
|
|
503
|
+
zeroline=True,
|
|
504
|
+
mirror=True,
|
|
505
|
+
autorange=True,
|
|
506
|
+
),
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
content = fig.show(external=True).to_plotly_json()
|
|
510
|
+
|
|
511
|
+
return fig, content
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "openbb-technical"
|
|
3
|
-
version = "1.2.
|
|
3
|
+
version = "1.2.3"
|
|
4
4
|
description = "Technical Analysis extension for OpenBB"
|
|
5
5
|
authors = ["OpenBB Team <hello@openbb.co>"]
|
|
6
6
|
license = "AGPL-3.0-only"
|
|
@@ -13,7 +13,7 @@ scipy = "^1.10.1"
|
|
|
13
13
|
statsmodels = "^0.14.0"
|
|
14
14
|
scikit-learn = "^1.3.1"
|
|
15
15
|
pandas-ta = "^0.3.14b"
|
|
16
|
-
openbb-core = "^1.2.
|
|
16
|
+
openbb-core = "^1.2.5"
|
|
17
17
|
|
|
18
18
|
[build-system]
|
|
19
19
|
requires = ["poetry-core"]
|
|
@@ -21,3 +21,6 @@ build-backend = "poetry.core.masonry.api"
|
|
|
21
21
|
|
|
22
22
|
[tool.poetry.plugins."openbb_core_extension"]
|
|
23
23
|
technical = "openbb_technical.technical_router:router"
|
|
24
|
+
|
|
25
|
+
[tool.poetry.plugins."openbb_charting_extension"]
|
|
26
|
+
technical = "openbb_technical.technical_views:TechnicalViews"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|