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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openbb-technical
3
- Version: 1.2.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.4,<2.0.0)
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.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.4"
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"