openbb-technical 1.2.3__tar.gz → 1.3.0__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.3 → openbb_technical-1.3.0}/PKG-INFO +6 -5
- {openbb_technical-1.2.3 → openbb_technical-1.3.0}/openbb_technical/helpers.py +93 -70
- {openbb_technical-1.2.3 → openbb_technical-1.3.0}/openbb_technical/relative_rotation.py +61 -34
- {openbb_technical-1.2.3 → openbb_technical-1.3.0}/openbb_technical/technical_router.py +93 -3
- {openbb_technical-1.2.3 → openbb_technical-1.3.0}/openbb_technical/technical_views.py +61 -28
- {openbb_technical-1.2.3 → openbb_technical-1.3.0}/pyproject.toml +5 -4
- {openbb_technical-1.2.3 → openbb_technical-1.3.0}/README.md +0 -0
- {openbb_technical-1.2.3 → openbb_technical-1.3.0}/openbb_technical/__init__.py +0 -0
- {openbb_technical-1.2.3 → openbb_technical-1.3.0}/openbb_technical/py.typed +0 -0
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: openbb-technical
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Technical Analysis extension for OpenBB
|
|
5
5
|
License: AGPL-3.0-only
|
|
6
6
|
Author: OpenBB Team
|
|
7
7
|
Author-email: hello@openbb.co
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.9,<3.13
|
|
9
9
|
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.9
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
-
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Dist: numpy (>=1,<2)
|
|
16
|
+
Requires-Dist: openbb-core (>=1.3.0,<2.0.0)
|
|
16
17
|
Requires-Dist: pandas-ta (>=0.3.14b,<0.4.0)
|
|
17
|
-
Requires-Dist: scikit-learn (>=1.
|
|
18
|
+
Requires-Dist: scikit-learn (>=1.5.0,<2.0.0)
|
|
18
19
|
Requires-Dist: scipy (>=1.10.1,<2.0.0)
|
|
19
20
|
Requires-Dist: statsmodels (>=0.14.0,<0.15.0)
|
|
20
21
|
Description-Content-Type: text/markdown
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"""Technical Analysis Helpers."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# pylint: disable=too-many-arguments, too-many-locals
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any, List, Literal, Optional, Tuple, Union
|
|
4
6
|
from warnings import warn
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from pandas import DataFrame, Series, Timestamp
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
def validate_data(data: list, length: Union[int, List[int]]) -> None:
|
|
@@ -19,12 +21,12 @@ def validate_data(data: list, length: Union[int, List[int]]) -> None:
|
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
def parkinson(
|
|
22
|
-
data:
|
|
24
|
+
data: "DataFrame",
|
|
23
25
|
window: int = 30,
|
|
24
26
|
trading_periods: Optional[int] = None,
|
|
25
27
|
is_crypto: bool = False,
|
|
26
28
|
clean=True,
|
|
27
|
-
) ->
|
|
29
|
+
) -> "DataFrame":
|
|
28
30
|
"""Parkinson volatility.
|
|
29
31
|
|
|
30
32
|
Uses the high and low price of the day rather than just close to close prices.
|
|
@@ -32,7 +34,7 @@ def parkinson(
|
|
|
32
34
|
|
|
33
35
|
Parameters
|
|
34
36
|
----------
|
|
35
|
-
data :
|
|
37
|
+
data : DataFrame
|
|
36
38
|
Dataframe of OHLC prices.
|
|
37
39
|
window : int [default: 30]
|
|
38
40
|
Length of window to calculate over.
|
|
@@ -45,9 +47,12 @@ def parkinson(
|
|
|
45
47
|
|
|
46
48
|
Returns
|
|
47
49
|
-------
|
|
48
|
-
|
|
50
|
+
DataFrame : results
|
|
49
51
|
Dataframe with results.
|
|
50
52
|
"""
|
|
53
|
+
# pylint: disable=import-outside-toplevel
|
|
54
|
+
from numpy import log
|
|
55
|
+
|
|
51
56
|
if window < 1:
|
|
52
57
|
warn("Error: Window must be at least 1, defaulting to 30.")
|
|
53
58
|
window = 30
|
|
@@ -58,9 +63,7 @@ def parkinson(
|
|
|
58
63
|
if not trading_periods:
|
|
59
64
|
trading_periods = 365 if is_crypto else 252
|
|
60
65
|
|
|
61
|
-
rs = (1.0 / (4.0 *
|
|
62
|
-
(data["high"] / data["low"]).apply(np.log)
|
|
63
|
-
) ** 2.0
|
|
66
|
+
rs = (1.0 / (4.0 * log(2.0))) * ((data["high"] / data["low"]).apply(log)) ** 2.0
|
|
64
67
|
|
|
65
68
|
def f(v):
|
|
66
69
|
return (trading_periods * v.mean()) ** 0.5
|
|
@@ -74,12 +77,12 @@ def parkinson(
|
|
|
74
77
|
|
|
75
78
|
|
|
76
79
|
def standard_deviation(
|
|
77
|
-
data:
|
|
80
|
+
data: "DataFrame",
|
|
78
81
|
window: int = 30,
|
|
79
82
|
trading_periods: Optional[int] = None,
|
|
80
83
|
is_crypto: bool = False,
|
|
81
84
|
clean: bool = True,
|
|
82
|
-
) ->
|
|
85
|
+
) -> "DataFrame":
|
|
83
86
|
"""Calculate the Standard deviation.
|
|
84
87
|
|
|
85
88
|
Measures how widely returns are dispersed from the average return.
|
|
@@ -87,7 +90,7 @@ def standard_deviation(
|
|
|
87
90
|
|
|
88
91
|
Parameters
|
|
89
92
|
----------
|
|
90
|
-
data :
|
|
93
|
+
data : DataFrame
|
|
91
94
|
Dataframe of OHLC prices.
|
|
92
95
|
window : int [default: 30]
|
|
93
96
|
Length of window to calculate over.
|
|
@@ -100,9 +103,12 @@ def standard_deviation(
|
|
|
100
103
|
|
|
101
104
|
Returns
|
|
102
105
|
-------
|
|
103
|
-
|
|
106
|
+
DataFrame : results
|
|
104
107
|
Dataframe with results.
|
|
105
108
|
"""
|
|
109
|
+
# pylint: disable=import-outside-toplevel
|
|
110
|
+
from numpy import log, sqrt
|
|
111
|
+
|
|
106
112
|
if window < 2:
|
|
107
113
|
warn("Error: Window must be at least 2, defaulting to 30.")
|
|
108
114
|
window = 30
|
|
@@ -113,9 +119,9 @@ def standard_deviation(
|
|
|
113
119
|
if not trading_periods:
|
|
114
120
|
trading_periods = 365 if is_crypto else 252
|
|
115
121
|
|
|
116
|
-
log_return = (data["close"] / data["close"].shift(1)).apply(
|
|
122
|
+
log_return = (data["close"] / data["close"].shift(1)).apply(log)
|
|
117
123
|
|
|
118
|
-
result = log_return.rolling(window=window, center=False).std() *
|
|
124
|
+
result = log_return.rolling(window=window, center=False).std() * sqrt(
|
|
119
125
|
trading_periods
|
|
120
126
|
)
|
|
121
127
|
|
|
@@ -126,12 +132,12 @@ def standard_deviation(
|
|
|
126
132
|
|
|
127
133
|
|
|
128
134
|
def garman_klass(
|
|
129
|
-
data:
|
|
135
|
+
data: "DataFrame",
|
|
130
136
|
window: int = 30,
|
|
131
137
|
trading_periods: Optional[int] = None,
|
|
132
138
|
is_crypto: bool = False,
|
|
133
139
|
clean=True,
|
|
134
|
-
) ->
|
|
140
|
+
) -> "DataFrame":
|
|
135
141
|
"""Garman-Klass volatility.
|
|
136
142
|
|
|
137
143
|
Extends Parkinson volatility by taking into account the opening and closing price.
|
|
@@ -140,7 +146,7 @@ def garman_klass(
|
|
|
140
146
|
|
|
141
147
|
Parameters
|
|
142
148
|
----------
|
|
143
|
-
data :
|
|
149
|
+
data : DataFrame
|
|
144
150
|
Dataframe of OHLC prices.
|
|
145
151
|
window : int [default: 30]
|
|
146
152
|
Length of window to calculate over.
|
|
@@ -153,9 +159,12 @@ def garman_klass(
|
|
|
153
159
|
|
|
154
160
|
Returns
|
|
155
161
|
-------
|
|
156
|
-
|
|
162
|
+
DataFrame : results
|
|
157
163
|
Dataframe with results.
|
|
158
164
|
"""
|
|
165
|
+
# pylint: disable=import-outside-toplevel
|
|
166
|
+
from numpy import log
|
|
167
|
+
|
|
159
168
|
if window < 1:
|
|
160
169
|
warn("Error: Window must be at least 1, defaulting to 30.")
|
|
161
170
|
window = 30
|
|
@@ -166,10 +175,10 @@ def garman_klass(
|
|
|
166
175
|
if not trading_periods:
|
|
167
176
|
trading_periods = 365 if is_crypto else 252
|
|
168
177
|
|
|
169
|
-
log_hl = (data["high"] / data["low"]).apply(
|
|
170
|
-
log_co = (data["close"] / data["open"]).apply(
|
|
178
|
+
log_hl = (data["high"] / data["low"]).apply(log)
|
|
179
|
+
log_co = (data["close"] / data["open"]).apply(log)
|
|
171
180
|
|
|
172
|
-
rs = 0.5 * log_hl**2 - (2 *
|
|
181
|
+
rs = 0.5 * log_hl**2 - (2 * log(2) - 1) * log_co**2
|
|
173
182
|
|
|
174
183
|
def f(v):
|
|
175
184
|
return (trading_periods * v.mean()) ** 0.5
|
|
@@ -183,12 +192,12 @@ def garman_klass(
|
|
|
183
192
|
|
|
184
193
|
|
|
185
194
|
def hodges_tompkins(
|
|
186
|
-
data:
|
|
195
|
+
data: "DataFrame",
|
|
187
196
|
window: int = 30,
|
|
188
197
|
trading_periods: Optional[int] = None,
|
|
189
198
|
is_crypto: bool = False,
|
|
190
199
|
clean=True,
|
|
191
|
-
) ->
|
|
200
|
+
) -> "DataFrame":
|
|
192
201
|
"""Hodges-Tompkins volatility.
|
|
193
202
|
|
|
194
203
|
Is a bias correction for estimation using an overlapping data sample.
|
|
@@ -196,7 +205,7 @@ def hodges_tompkins(
|
|
|
196
205
|
|
|
197
206
|
Parameters
|
|
198
207
|
----------
|
|
199
|
-
data :
|
|
208
|
+
data : DataFrame
|
|
200
209
|
Dataframe of OHLC prices.
|
|
201
210
|
window : int [default: 30]
|
|
202
211
|
Length of window to calculate over.
|
|
@@ -209,7 +218,7 @@ def hodges_tompkins(
|
|
|
209
218
|
|
|
210
219
|
Returns
|
|
211
220
|
-------
|
|
212
|
-
|
|
221
|
+
DataFrame : results
|
|
213
222
|
Dataframe with results.
|
|
214
223
|
|
|
215
224
|
Example
|
|
@@ -217,6 +226,9 @@ def hodges_tompkins(
|
|
|
217
226
|
>>> data = obb.equity.price.historical('BTC-USD')
|
|
218
227
|
>>> df = obb.technical.hodges_tompkins(data, is_crypto = True)
|
|
219
228
|
"""
|
|
229
|
+
# pylint: disable=import-outside-toplevel
|
|
230
|
+
from numpy import log, sqrt
|
|
231
|
+
|
|
220
232
|
if window < 2:
|
|
221
233
|
warn("Error: Window must be at least 2, defaulting to 30.")
|
|
222
234
|
window = 30
|
|
@@ -227,11 +239,9 @@ def hodges_tompkins(
|
|
|
227
239
|
if not trading_periods:
|
|
228
240
|
trading_periods = 365 if is_crypto else 252
|
|
229
241
|
|
|
230
|
-
log_return = (data["close"] / data["close"].shift(1)).apply(
|
|
242
|
+
log_return = (data["close"] / data["close"].shift(1)).apply(log)
|
|
231
243
|
|
|
232
|
-
vol = log_return.rolling(window=window, center=False).std() *
|
|
233
|
-
trading_periods
|
|
234
|
-
)
|
|
244
|
+
vol = log_return.rolling(window=window, center=False).std() * sqrt(trading_periods)
|
|
235
245
|
|
|
236
246
|
h = window
|
|
237
247
|
n = (log_return.count() - h) + 1
|
|
@@ -247,12 +257,12 @@ def hodges_tompkins(
|
|
|
247
257
|
|
|
248
258
|
|
|
249
259
|
def rogers_satchell(
|
|
250
|
-
data:
|
|
260
|
+
data: "DataFrame",
|
|
251
261
|
window: int = 30,
|
|
252
262
|
trading_periods: Optional[int] = None,
|
|
253
263
|
is_crypto: bool = False,
|
|
254
264
|
clean=True,
|
|
255
|
-
) ->
|
|
265
|
+
) -> "Series":
|
|
256
266
|
"""Rogers-Satchell Estimator.
|
|
257
267
|
|
|
258
268
|
Is an estimator for measuring the volatility with an average return not equal to zero.
|
|
@@ -261,7 +271,7 @@ def rogers_satchell(
|
|
|
261
271
|
|
|
262
272
|
Parameters
|
|
263
273
|
----------
|
|
264
|
-
data :
|
|
274
|
+
data : DataFrame
|
|
265
275
|
Dataframe of OHLC prices.
|
|
266
276
|
window : int [default: 30]
|
|
267
277
|
Length of window to calculate over.
|
|
@@ -274,9 +284,12 @@ def rogers_satchell(
|
|
|
274
284
|
|
|
275
285
|
Returns
|
|
276
286
|
-------
|
|
277
|
-
|
|
287
|
+
Series : results
|
|
278
288
|
Pandas Series with results.
|
|
279
289
|
"""
|
|
290
|
+
# pylint: disable=import-outside-toplevel
|
|
291
|
+
from numpy import log
|
|
292
|
+
|
|
280
293
|
if window < 1:
|
|
281
294
|
warn("Error: Window must be at least 1, defaulting to 30.")
|
|
282
295
|
window = 30
|
|
@@ -287,9 +300,9 @@ def rogers_satchell(
|
|
|
287
300
|
if not trading_periods:
|
|
288
301
|
trading_periods = 365 if is_crypto else 252
|
|
289
302
|
|
|
290
|
-
log_ho = (data["high"] / data["open"]).apply(
|
|
291
|
-
log_lo = (data["low"] / data["open"]).apply(
|
|
292
|
-
log_co = (data["close"] / data["open"]).apply(
|
|
303
|
+
log_ho = (data["high"] / data["open"]).apply(log)
|
|
304
|
+
log_lo = (data["low"] / data["open"]).apply(log)
|
|
305
|
+
log_co = (data["close"] / data["open"]).apply(log)
|
|
293
306
|
|
|
294
307
|
rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co)
|
|
295
308
|
|
|
@@ -305,12 +318,12 @@ def rogers_satchell(
|
|
|
305
318
|
|
|
306
319
|
|
|
307
320
|
def yang_zhang(
|
|
308
|
-
data:
|
|
321
|
+
data: "DataFrame",
|
|
309
322
|
window: int = 30,
|
|
310
323
|
trading_periods: Optional[int] = None,
|
|
311
324
|
is_crypto: bool = False,
|
|
312
325
|
clean=True,
|
|
313
|
-
) ->
|
|
326
|
+
) -> "DataFrame":
|
|
314
327
|
"""Yang-Zhang Volatility.
|
|
315
328
|
|
|
316
329
|
Is the combination of the overnight (close-to-open volatility).
|
|
@@ -318,7 +331,7 @@ def yang_zhang(
|
|
|
318
331
|
|
|
319
332
|
Parameters
|
|
320
333
|
----------
|
|
321
|
-
data :
|
|
334
|
+
data : DataFrame
|
|
322
335
|
Dataframe of OHLC prices.
|
|
323
336
|
window : int [default: 30]
|
|
324
337
|
Length of window to calculate standard deviation.
|
|
@@ -331,9 +344,12 @@ def yang_zhang(
|
|
|
331
344
|
|
|
332
345
|
Returns
|
|
333
346
|
-------
|
|
334
|
-
|
|
347
|
+
DataFrame : results
|
|
335
348
|
Dataframe with results.
|
|
336
349
|
"""
|
|
350
|
+
# pylint: disable=import-outside-toplevel
|
|
351
|
+
from numpy import log, sqrt
|
|
352
|
+
|
|
337
353
|
if window < 2:
|
|
338
354
|
warn("Error: Window must be at least 2, defaulting to 30.")
|
|
339
355
|
window = 30
|
|
@@ -344,14 +360,14 @@ def yang_zhang(
|
|
|
344
360
|
if not trading_periods:
|
|
345
361
|
trading_periods = 365 if is_crypto else 252
|
|
346
362
|
|
|
347
|
-
log_ho = (data["high"] / data["open"]).apply(
|
|
348
|
-
log_lo = (data["low"] / data["open"]).apply(
|
|
349
|
-
log_co = (data["close"] / data["open"]).apply(
|
|
363
|
+
log_ho = (data["high"] / data["open"]).apply(log)
|
|
364
|
+
log_lo = (data["low"] / data["open"]).apply(log)
|
|
365
|
+
log_co = (data["close"] / data["open"]).apply(log)
|
|
350
366
|
|
|
351
|
-
log_oc = (data["open"] / data["close"].shift(1)).apply(
|
|
367
|
+
log_oc = (data["open"] / data["close"].shift(1)).apply(log)
|
|
352
368
|
log_oc_sq = log_oc**2
|
|
353
369
|
|
|
354
|
-
log_cc = (data["close"] / data["close"].shift(1)).apply(
|
|
370
|
+
log_cc = (data["close"] / data["close"].shift(1)).apply(log)
|
|
355
371
|
log_cc_sq = log_cc**2
|
|
356
372
|
|
|
357
373
|
rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co)
|
|
@@ -365,7 +381,7 @@ def yang_zhang(
|
|
|
365
381
|
window_rs = rs.rolling(window=window, center=False).sum() * (1.0 / (window - 1.0))
|
|
366
382
|
|
|
367
383
|
k = 0.34 / (1.34 + (window + 1) / (window - 1))
|
|
368
|
-
result = (open_vol + k * close_vol + (1 - k) * window_rs).apply(
|
|
384
|
+
result = (open_vol + k * close_vol + (1 - k) * window_rs).apply(sqrt) * sqrt(
|
|
369
385
|
trading_periods
|
|
370
386
|
)
|
|
371
387
|
|
|
@@ -376,7 +392,7 @@ def yang_zhang(
|
|
|
376
392
|
|
|
377
393
|
|
|
378
394
|
def calculate_cones(
|
|
379
|
-
data:
|
|
395
|
+
data: "DataFrame",
|
|
380
396
|
lower_q: float,
|
|
381
397
|
upper_q: float,
|
|
382
398
|
is_crypto: bool,
|
|
@@ -389,9 +405,12 @@ def calculate_cones(
|
|
|
389
405
|
"yang_zhang",
|
|
390
406
|
],
|
|
391
407
|
trading_periods: Optional[int] = None,
|
|
392
|
-
) ->
|
|
408
|
+
) -> "DataFrame":
|
|
393
409
|
"""Calculate Cones."""
|
|
394
|
-
|
|
410
|
+
# pylint: disable=import-outside-toplevel
|
|
411
|
+
from pandas import DataFrame
|
|
412
|
+
|
|
413
|
+
estimator = DataFrame()
|
|
395
414
|
|
|
396
415
|
if lower_q > upper_q:
|
|
397
416
|
lower_q, upper_q = upper_q, lower_q
|
|
@@ -443,7 +462,7 @@ def calculate_cones(
|
|
|
443
462
|
|
|
444
463
|
df_ = [realized, min_, bottom_q, median, top_q, max_]
|
|
445
464
|
df_windows = allowed_windows
|
|
446
|
-
df =
|
|
465
|
+
df = DataFrame(df_, columns=df_windows)
|
|
447
466
|
df = df.rename(
|
|
448
467
|
index={
|
|
449
468
|
0: "realized",
|
|
@@ -459,8 +478,8 @@ def calculate_cones(
|
|
|
459
478
|
|
|
460
479
|
|
|
461
480
|
def clenow_momentum(
|
|
462
|
-
values:
|
|
463
|
-
) -> Tuple[float, float,
|
|
481
|
+
values: "Series", window: int = 90
|
|
482
|
+
) -> Tuple[float, float, "Series"]:
|
|
464
483
|
"""Clenow Volatility Adjusted Momentum.
|
|
465
484
|
|
|
466
485
|
This is defined as the regression coefficient on log prices multiplied by the R^2
|
|
@@ -468,7 +487,7 @@ def clenow_momentum(
|
|
|
468
487
|
|
|
469
488
|
Parameters
|
|
470
489
|
----------
|
|
471
|
-
values:
|
|
490
|
+
values: Series
|
|
472
491
|
Values to perform regression for
|
|
473
492
|
window: int
|
|
474
493
|
Length of look back period
|
|
@@ -479,43 +498,44 @@ def clenow_momentum(
|
|
|
479
498
|
R2 of fit to log data
|
|
480
499
|
float:
|
|
481
500
|
Coefficient of linear regression
|
|
482
|
-
|
|
501
|
+
Series:
|
|
483
502
|
Values for best fit line
|
|
484
503
|
"""
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
504
|
+
# pylint: disable=import-outside-toplevel
|
|
505
|
+
from numpy import arange, exp, log
|
|
506
|
+
from pandas import Series
|
|
507
|
+
from sklearn.linear_model import LinearRegression
|
|
488
508
|
|
|
489
509
|
if len(values) < window:
|
|
490
510
|
raise ValueError(f"Calculation asks for at least last {window} days of data")
|
|
491
511
|
|
|
492
512
|
values = values[-window:]
|
|
493
513
|
|
|
494
|
-
y =
|
|
495
|
-
X =
|
|
514
|
+
y = log(values)
|
|
515
|
+
X = arange(len(y)).reshape(-1, 1) # pylint: disable=invalid-name
|
|
496
516
|
|
|
497
517
|
lr = LinearRegression()
|
|
498
518
|
lr.fit(X, y)
|
|
499
519
|
|
|
500
520
|
r2 = lr.score(X, y)
|
|
501
521
|
coef = lr.coef_[0]
|
|
502
|
-
annualized_coef = (
|
|
522
|
+
annualized_coef = (exp(coef) ** 252) - 1
|
|
503
523
|
|
|
504
|
-
return r2, annualized_coef,
|
|
524
|
+
return r2, annualized_coef, Series(lr.predict(X))
|
|
505
525
|
|
|
506
526
|
|
|
507
527
|
def calculate_fib_levels(
|
|
508
|
-
data:
|
|
528
|
+
data: "DataFrame",
|
|
509
529
|
close_col: str,
|
|
510
530
|
limit: int = 120,
|
|
511
531
|
start_date: Optional[Any] = None,
|
|
512
532
|
end_date: Optional[Any] = None,
|
|
513
|
-
) -> Tuple[
|
|
533
|
+
) -> Tuple["DataFrame", "Timestamp", "Timestamp", float, float, str]:
|
|
514
534
|
"""Calculate Fibonacci levels.
|
|
515
535
|
|
|
516
536
|
Parameters
|
|
517
537
|
----------
|
|
518
|
-
data :
|
|
538
|
+
data : DataFrame
|
|
519
539
|
Dataframe of prices
|
|
520
540
|
close_col : str
|
|
521
541
|
Column name of close prices
|
|
@@ -528,17 +548,20 @@ def calculate_fib_levels(
|
|
|
528
548
|
|
|
529
549
|
Returns
|
|
530
550
|
-------
|
|
531
|
-
df :
|
|
551
|
+
df : DataFrame
|
|
532
552
|
Dataframe of fib levels
|
|
533
|
-
min_date:
|
|
553
|
+
min_date: Timestamp
|
|
534
554
|
Date of min point
|
|
535
|
-
max_date:
|
|
555
|
+
max_date: Timestamp:
|
|
536
556
|
Date of max point
|
|
537
557
|
min_pr: float
|
|
538
558
|
Price at min point
|
|
539
559
|
max_pr: float
|
|
540
560
|
Price at max point
|
|
541
561
|
"""
|
|
562
|
+
# pylint: disable=import-outside-toplevel
|
|
563
|
+
from pandas import DataFrame
|
|
564
|
+
|
|
542
565
|
if close_col not in data.columns:
|
|
543
566
|
raise ValueError(f"Column {close_col} not in data")
|
|
544
567
|
|
|
@@ -588,7 +611,7 @@ def calculate_fib_levels(
|
|
|
588
611
|
for f_lev in fib_levels
|
|
589
612
|
]
|
|
590
613
|
|
|
591
|
-
df =
|
|
614
|
+
df = DataFrame()
|
|
592
615
|
df["Level"] = fib_levels
|
|
593
616
|
df["Level"] = df["Level"].apply(lambda x: str(x * 100) + "%")
|
|
594
617
|
df["Price"] = levels
|
|
@@ -3,41 +3,39 @@
|
|
|
3
3
|
# pylint: disable=too-many-arguments, too-many-instance-attributes, protected-access
|
|
4
4
|
# pylint: disable=too-many-locals, too-few-public-methods, unused-argument
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
from typing import Any, Dict, List, Literal, Optional, Tuple, Union
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple, Union
|
|
8
7
|
|
|
9
|
-
import numpy as np
|
|
10
|
-
from openbb_core.app.model.obbject import OBBject
|
|
11
|
-
from openbb_core.app.utils import basemodel_to_df, convert_to_basemodel, df_to_basemodel
|
|
12
8
|
from openbb_core.provider.abstract.data import Data
|
|
13
9
|
from openbb_core.provider.abstract.fetcher import Fetcher
|
|
14
10
|
from openbb_core.provider.abstract.query_params import QueryParams
|
|
15
|
-
from pandas import DataFrame, Series, to_datetime
|
|
16
11
|
from pydantic import Field, field_validator
|
|
17
12
|
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from pandas import DataFrame, Series
|
|
18
15
|
|
|
19
|
-
|
|
16
|
+
|
|
17
|
+
def absolute_maximum_scale(data: "Series") -> "Series":
|
|
20
18
|
"""Absolute Maximum Scale Normaliztion Method."""
|
|
21
19
|
return data / data.abs().max()
|
|
22
20
|
|
|
23
21
|
|
|
24
|
-
def min_max_scaling(data: Series) -> Series:
|
|
22
|
+
def min_max_scaling(data: "Series") -> "Series":
|
|
25
23
|
"""Min/Max ScalingNormalization Method."""
|
|
26
24
|
return (data - data.min()) / (data.max() - data.min())
|
|
27
25
|
|
|
28
26
|
|
|
29
|
-
def z_score_standardization(data: Series) -> Series:
|
|
27
|
+
def z_score_standardization(data: "Series") -> "Series":
|
|
30
28
|
"""Z-Score Standardization Method."""
|
|
31
29
|
return (data - data.mean()) / data.std()
|
|
32
30
|
|
|
33
31
|
|
|
34
|
-
def normalize(data: DataFrame, method: Literal["z", "m", "a"] = "z") -> DataFrame:
|
|
32
|
+
def normalize(data: "DataFrame", method: Literal["z", "m", "a"] = "z") -> "DataFrame":
|
|
35
33
|
"""
|
|
36
34
|
Normalize a Pandas DataFrame based on method.
|
|
37
35
|
|
|
38
36
|
Parameters
|
|
39
37
|
----------
|
|
40
|
-
data: DataFrame
|
|
38
|
+
data: "DataFrame"
|
|
41
39
|
Pandas DataFrame with any number of columns to be normalized.
|
|
42
40
|
method: Literal["z", "m", "a"]
|
|
43
41
|
Normalization method.
|
|
@@ -65,10 +63,10 @@ def normalize(data: DataFrame, method: Literal["z", "m", "a"] = "z") -> DataFram
|
|
|
65
63
|
|
|
66
64
|
|
|
67
65
|
def standard_deviation(
|
|
68
|
-
data: DataFrame,
|
|
66
|
+
data: "DataFrame",
|
|
69
67
|
window: int = 21,
|
|
70
68
|
trading_periods: int = 252,
|
|
71
|
-
) -> DataFrame:
|
|
69
|
+
) -> "DataFrame":
|
|
72
70
|
"""
|
|
73
71
|
Measures how widely returns are dispersed from the average return.
|
|
74
72
|
|
|
@@ -88,15 +86,19 @@ def standard_deviation(
|
|
|
88
86
|
pd.DataFrame : results
|
|
89
87
|
Dataframe with results.
|
|
90
88
|
"""
|
|
89
|
+
# pylint: disable=import-outside-toplevel
|
|
90
|
+
from numpy import log, sqrt
|
|
91
|
+
from pandas import DataFrame
|
|
92
|
+
|
|
91
93
|
data = data.copy()
|
|
92
94
|
results = DataFrame()
|
|
93
95
|
if window < 2:
|
|
94
96
|
window = 21
|
|
95
97
|
|
|
96
98
|
for col in data.columns.tolist():
|
|
97
|
-
log_return = (data[col] / data[col].shift(1)).apply(
|
|
99
|
+
log_return = (data[col] / data[col].shift(1)).apply(log)
|
|
98
100
|
|
|
99
|
-
result = log_return.rolling(window=window, center=False).std() *
|
|
101
|
+
result = log_return.rolling(window=window, center=False).std() * sqrt(
|
|
100
102
|
trading_periods
|
|
101
103
|
)
|
|
102
104
|
results[col] = result
|
|
@@ -105,8 +107,8 @@ def standard_deviation(
|
|
|
105
107
|
|
|
106
108
|
|
|
107
109
|
def calculate_momentum(
|
|
108
|
-
data: Series, long_period: int = 252, short_period: int = 21
|
|
109
|
-
) -> Series:
|
|
110
|
+
data: "Series", long_period: int = 252, short_period: int = 21
|
|
111
|
+
) -> "Series":
|
|
110
112
|
"""
|
|
111
113
|
Momentum is calculated as the log trailing 12-month return minus trailing one-month return.
|
|
112
114
|
|
|
@@ -116,7 +118,7 @@ def calculate_momentum(
|
|
|
116
118
|
|
|
117
119
|
Parameters
|
|
118
120
|
----------
|
|
119
|
-
data: Series
|
|
121
|
+
data: "Series"
|
|
120
122
|
Time series data to calculate the momentum for.
|
|
121
123
|
long_period: Optional[int]
|
|
122
124
|
Long period to base the calculation on. Default is one standard trading year.
|
|
@@ -128,18 +130,21 @@ def calculate_momentum(
|
|
|
128
130
|
Series
|
|
129
131
|
Pandas Series with the calculated momentum.
|
|
130
132
|
"""
|
|
133
|
+
# pylint: disable=import-outside-toplevel
|
|
134
|
+
from numpy import log
|
|
135
|
+
|
|
131
136
|
df = data.copy()
|
|
132
137
|
epsilon = 1e-10
|
|
133
|
-
momentum_long =
|
|
134
|
-
momentum_short =
|
|
138
|
+
momentum_long = log(1 + df.pct_change(long_period) + epsilon)
|
|
139
|
+
momentum_short = log(1 + df.pct_change(short_period) + epsilon)
|
|
135
140
|
data = momentum_long - momentum_short # type: ignore
|
|
136
141
|
|
|
137
142
|
return data
|
|
138
143
|
|
|
139
144
|
|
|
140
145
|
def get_momentum(
|
|
141
|
-
data: DataFrame, long_period: int = 252, short_period: int = 21
|
|
142
|
-
) -> DataFrame:
|
|
146
|
+
data: "DataFrame", long_period: int = 252, short_period: int = 21
|
|
147
|
+
) -> "DataFrame":
|
|
143
148
|
"""
|
|
144
149
|
Calculate the Relative-Strength Momentum Indicator.
|
|
145
150
|
|
|
@@ -147,7 +152,7 @@ def get_momentum(
|
|
|
147
152
|
|
|
148
153
|
Parameters
|
|
149
154
|
----------
|
|
150
|
-
data: DataFrame
|
|
155
|
+
data: "DataFrame"
|
|
151
156
|
Indexed time series data formatted with each column representing a ticker.
|
|
152
157
|
long_period: Optional[int]
|
|
153
158
|
Long period to base the calculation on. Default is one standard trading year.
|
|
@@ -159,6 +164,9 @@ def get_momentum(
|
|
|
159
164
|
DataFrame
|
|
160
165
|
Pandas DataFrame with the calculated historical momentum factor exposure score.
|
|
161
166
|
"""
|
|
167
|
+
# pylint: disable=import-outside-toplevel
|
|
168
|
+
from pandas import DataFrame
|
|
169
|
+
|
|
162
170
|
df = data.copy()
|
|
163
171
|
rs_momentum = DataFrame()
|
|
164
172
|
for ticker in df.columns.to_list():
|
|
@@ -170,9 +178,9 @@ def get_momentum(
|
|
|
170
178
|
|
|
171
179
|
|
|
172
180
|
def calculate_relative_strength_ratio(
|
|
173
|
-
symbols_data: DataFrame,
|
|
174
|
-
benchmark_data: DataFrame,
|
|
175
|
-
) -> DataFrame:
|
|
181
|
+
symbols_data: "DataFrame",
|
|
182
|
+
benchmark_data: "DataFrame",
|
|
183
|
+
) -> "DataFrame":
|
|
176
184
|
"""Calculate the Relative Strength Ratio for each ticker (column) in a DataFrame against the benchmark.
|
|
177
185
|
|
|
178
186
|
Symbols data and benchmark data should have the same index,
|
|
@@ -180,9 +188,9 @@ def calculate_relative_strength_ratio(
|
|
|
180
188
|
|
|
181
189
|
Parameters
|
|
182
190
|
----------
|
|
183
|
-
symbols_data: DataFrame
|
|
191
|
+
symbols_data: "DataFrame"
|
|
184
192
|
Pandas DataFrame with the symbols data to compare against the benchmark.
|
|
185
|
-
benchmark_data: DataFrame
|
|
193
|
+
benchmark_data: "DataFrame"
|
|
186
194
|
Pandas DataFrame with the benchmark data.
|
|
187
195
|
|
|
188
196
|
Returns
|
|
@@ -200,19 +208,19 @@ def calculate_relative_strength_ratio(
|
|
|
200
208
|
|
|
201
209
|
|
|
202
210
|
def process_data(
|
|
203
|
-
symbols_data: DataFrame,
|
|
204
|
-
benchmark_data: DataFrame,
|
|
211
|
+
symbols_data: "DataFrame",
|
|
212
|
+
benchmark_data: "DataFrame",
|
|
205
213
|
long_period: int = 252,
|
|
206
214
|
short_period: int = 21,
|
|
207
215
|
normalize_method: Literal["z", "m", "a"] = "z",
|
|
208
|
-
) -> Tuple[DataFrame, DataFrame]:
|
|
216
|
+
) -> Tuple["DataFrame", "DataFrame"]:
|
|
209
217
|
"""Process the raw data into normalized indicator values.
|
|
210
218
|
|
|
211
219
|
Parameters
|
|
212
220
|
----------
|
|
213
|
-
symbols_data: DataFrame
|
|
221
|
+
symbols_data: "DataFrame"
|
|
214
222
|
Indexed time series data formatted with each column representing a ticker.
|
|
215
|
-
benchmark_data: DataFrame
|
|
223
|
+
benchmark_data: "DataFrame"
|
|
216
224
|
Indexed time series data of the benchmark symbol.
|
|
217
225
|
long_period: Optional[int]
|
|
218
226
|
Long period to base the calculation on. Default is one standard trading year.
|
|
@@ -238,7 +246,7 @@ class RelativeRotation:
|
|
|
238
246
|
|
|
239
247
|
def __init__(
|
|
240
248
|
self,
|
|
241
|
-
data: Union[List[Data], DataFrame],
|
|
249
|
+
data: Union[List[Data], "DataFrame"],
|
|
242
250
|
benchmark: str,
|
|
243
251
|
study: Optional[Literal["price", "volume", "volatility"]] = "price",
|
|
244
252
|
long_period: Optional[int] = 252,
|
|
@@ -247,6 +255,16 @@ class RelativeRotation:
|
|
|
247
255
|
trading_periods: Optional[int] = 252,
|
|
248
256
|
):
|
|
249
257
|
"""Initialize the class."""
|
|
258
|
+
# pylint: disable=import-outside-toplevel
|
|
259
|
+
import contextlib # noqa
|
|
260
|
+
from openbb_core.app.model.obbject import OBBject # noqa
|
|
261
|
+
from openbb_core.app.utils import ( # noqa
|
|
262
|
+
basemodel_to_df,
|
|
263
|
+
convert_to_basemodel,
|
|
264
|
+
df_to_basemodel,
|
|
265
|
+
)
|
|
266
|
+
from pandas import DataFrame # noqa
|
|
267
|
+
|
|
250
268
|
benchmark = benchmark.upper()
|
|
251
269
|
df = DataFrame()
|
|
252
270
|
|
|
@@ -307,6 +325,10 @@ class RelativeRotation:
|
|
|
307
325
|
|
|
308
326
|
def _process_data(self):
|
|
309
327
|
"""Process the data."""
|
|
328
|
+
# pylint: disable=import-outside-toplevel
|
|
329
|
+
from openbb_core.app.utils import df_to_basemodel
|
|
330
|
+
from pandas import to_datetime
|
|
331
|
+
|
|
310
332
|
if self.study == "volatility":
|
|
311
333
|
self.symbols_data = standard_deviation(
|
|
312
334
|
self.symbols_data, # type: ignore
|
|
@@ -421,6 +443,11 @@ class RelativeRotationQueryParams(QueryParams):
|
|
|
421
443
|
@classmethod
|
|
422
444
|
def convert_data(cls, v):
|
|
423
445
|
"""Validate the data format."""
|
|
446
|
+
# pylint: disable=import-outside-toplevel
|
|
447
|
+
from openbb_core.app.model.obbject import OBBject
|
|
448
|
+
from openbb_core.app.utils import convert_to_basemodel, df_to_basemodel
|
|
449
|
+
from pandas import DataFrame
|
|
450
|
+
|
|
424
451
|
if isinstance(v, OBBject):
|
|
425
452
|
return v.results
|
|
426
453
|
if isinstance(v, Data):
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"""Technical Analysis Router."""
|
|
2
2
|
|
|
3
|
-
# pylint: disable=too-many-lines
|
|
3
|
+
# pylint: disable=too-many-lines,unused-import,too-many-arguments
|
|
4
|
+
|
|
4
5
|
from typing import Any, Dict, List, Literal, Optional
|
|
5
6
|
|
|
6
|
-
import pandas as pd
|
|
7
|
-
import pandas_ta as ta
|
|
8
7
|
from openbb_core.app.model.example import APIEx, PythonEx
|
|
9
8
|
from openbb_core.app.model.obbject import OBBject
|
|
10
9
|
from openbb_core.app.router import Router
|
|
@@ -215,6 +214,10 @@ def atr(
|
|
|
215
214
|
OBBject[List[Data]]
|
|
216
215
|
List of data with the indicator applied.
|
|
217
216
|
"""
|
|
217
|
+
# pylint: disable=import-outside-toplevel
|
|
218
|
+
import pandas as pd
|
|
219
|
+
import pandas_ta as ta # noqa
|
|
220
|
+
|
|
218
221
|
validate_data(data, length)
|
|
219
222
|
df = basemodel_to_df(data, index=index)
|
|
220
223
|
df_target = get_target_columns(df, ["high", "low", "close"])
|
|
@@ -342,6 +345,10 @@ def obv(
|
|
|
342
345
|
OBBject[List[Data]]
|
|
343
346
|
List of data with the indicator applied.
|
|
344
347
|
"""
|
|
348
|
+
# pylint: disable=import-outside-toplevel
|
|
349
|
+
import pandas as pd
|
|
350
|
+
import pandas_ta as ta # noqa
|
|
351
|
+
|
|
345
352
|
df = basemodel_to_df(data, index=index)
|
|
346
353
|
df_target = get_target_columns(df, ["close", "volume"])
|
|
347
354
|
df_obv = pd.DataFrame(df_target.ta.obv(offset=offset))
|
|
@@ -395,6 +402,10 @@ def fisher(
|
|
|
395
402
|
OBBject[List[Data]]
|
|
396
403
|
List of data with the indicator applied.
|
|
397
404
|
"""
|
|
405
|
+
# pylint: disable=import-outside-toplevel
|
|
406
|
+
import pandas as pd
|
|
407
|
+
import pandas_ta as ta # noqa
|
|
408
|
+
|
|
398
409
|
validate_data(data, [length, signal])
|
|
399
410
|
df = basemodel_to_df(data, index=index)
|
|
400
411
|
df_target = get_target_columns(df, ["high", "low"])
|
|
@@ -453,6 +464,10 @@ def adosc(
|
|
|
453
464
|
OBBject[List[Data]]
|
|
454
465
|
The calculated data.
|
|
455
466
|
"""
|
|
467
|
+
# pylint: disable=import-outside-toplevel
|
|
468
|
+
import pandas as pd
|
|
469
|
+
import pandas_ta as ta # noqa
|
|
470
|
+
|
|
456
471
|
validate_data(data, [fast, slow])
|
|
457
472
|
df = basemodel_to_df(data, index=index)
|
|
458
473
|
df_target = get_target_columns(df, ["open", "high", "low", "close", "volume"])
|
|
@@ -523,6 +538,10 @@ def bbands(
|
|
|
523
538
|
OBBject[List[Data]]
|
|
524
539
|
The calculated data.
|
|
525
540
|
"""
|
|
541
|
+
# pylint: disable=import-outside-toplevel
|
|
542
|
+
import pandas as pd
|
|
543
|
+
import pandas_ta as ta # noqa
|
|
544
|
+
|
|
526
545
|
validate_data(data, length)
|
|
527
546
|
df = basemodel_to_df(data, index=index)
|
|
528
547
|
df_target = get_target_column(df, target).to_frame()
|
|
@@ -590,6 +609,10 @@ def zlma(
|
|
|
590
609
|
OBBject[List[Data]]
|
|
591
610
|
The calculated data.
|
|
592
611
|
"""
|
|
612
|
+
# pylint: disable=import-outside-toplevel
|
|
613
|
+
import pandas as pd
|
|
614
|
+
import pandas_ta as ta # noqa
|
|
615
|
+
|
|
593
616
|
validate_data(data, length)
|
|
594
617
|
df = basemodel_to_df(data, index=index)
|
|
595
618
|
df_target = get_target_column(df, target).to_frame()
|
|
@@ -657,6 +680,10 @@ def aroon(
|
|
|
657
680
|
OBBject[List[Data]]
|
|
658
681
|
The calculated data.
|
|
659
682
|
"""
|
|
683
|
+
# pylint: disable=import-outside-toplevel
|
|
684
|
+
import pandas as pd
|
|
685
|
+
import pandas_ta as ta # noqa
|
|
686
|
+
|
|
660
687
|
validate_data(data, length)
|
|
661
688
|
df = basemodel_to_df(data, index=index)
|
|
662
689
|
df_target = get_target_columns(df, ["high", "low", "close"])
|
|
@@ -716,6 +743,10 @@ def sma(
|
|
|
716
743
|
OBBject[List[Data]]
|
|
717
744
|
The calculated data.
|
|
718
745
|
"""
|
|
746
|
+
# pylint: disable=import-outside-toplevel
|
|
747
|
+
import pandas as pd
|
|
748
|
+
import pandas_ta as ta # noqa
|
|
749
|
+
|
|
719
750
|
validate_data(data, length)
|
|
720
751
|
df = basemodel_to_df(data, index=index)
|
|
721
752
|
df_target = get_target_column(df, target).to_frame()
|
|
@@ -783,6 +814,9 @@ def demark(
|
|
|
783
814
|
OBBject[List[Data]]
|
|
784
815
|
The calculated data.
|
|
785
816
|
"""
|
|
817
|
+
# pylint: disable=import-outside-toplevel
|
|
818
|
+
import pandas_ta as ta # noqa
|
|
819
|
+
|
|
786
820
|
df = basemodel_to_df(data, index=index)
|
|
787
821
|
df_target = get_target_column(df, target).to_frame()
|
|
788
822
|
_demark = ta.td_seq(
|
|
@@ -838,6 +872,10 @@ def vwap(
|
|
|
838
872
|
OBBject[List[Data]]
|
|
839
873
|
The calculated data.
|
|
840
874
|
"""
|
|
875
|
+
# pylint: disable=import-outside-toplevel
|
|
876
|
+
import pandas as pd
|
|
877
|
+
import pandas_ta as ta # noqa
|
|
878
|
+
|
|
841
879
|
df = basemodel_to_df(data, index=index)
|
|
842
880
|
if index == "date":
|
|
843
881
|
df.index = pd.to_datetime(df.index)
|
|
@@ -910,6 +948,10 @@ def macd(
|
|
|
910
948
|
OBBject[List[Data]]
|
|
911
949
|
The calculated data.
|
|
912
950
|
"""
|
|
951
|
+
# pylint: disable=import-outside-toplevel
|
|
952
|
+
import pandas as pd
|
|
953
|
+
import pandas_ta as ta # noqa
|
|
954
|
+
|
|
913
955
|
validate_data(data, [fast, slow, signal])
|
|
914
956
|
df = basemodel_to_df(data, index=index)
|
|
915
957
|
df_target = get_target_column(df, target).to_frame()
|
|
@@ -972,6 +1014,10 @@ def hma(
|
|
|
972
1014
|
OBBject[List[Data]]
|
|
973
1015
|
The calculated data.
|
|
974
1016
|
"""
|
|
1017
|
+
# pylint: disable=import-outside-toplevel
|
|
1018
|
+
import pandas as pd
|
|
1019
|
+
import pandas_ta as ta # noqa
|
|
1020
|
+
|
|
975
1021
|
validate_data(data, length)
|
|
976
1022
|
df = basemodel_to_df(data, index=index)
|
|
977
1023
|
df_target = get_target_column(df, target).to_frame()
|
|
@@ -1042,6 +1088,10 @@ def donchian(
|
|
|
1042
1088
|
OBBject[List[Data]]
|
|
1043
1089
|
The calculated data.
|
|
1044
1090
|
"""
|
|
1091
|
+
# pylint: disable=import-outside-toplevel
|
|
1092
|
+
import pandas as pd
|
|
1093
|
+
import pandas_ta as ta # noqa
|
|
1094
|
+
|
|
1045
1095
|
validate_data(data, [lower_length, upper_length])
|
|
1046
1096
|
df = basemodel_to_df(data, index=index)
|
|
1047
1097
|
df_target = get_target_columns(df, ["high", "low"])
|
|
@@ -1168,6 +1218,10 @@ def clenow(
|
|
|
1168
1218
|
OBBject[List[Data]]
|
|
1169
1219
|
The calculated data.
|
|
1170
1220
|
"""
|
|
1221
|
+
# pylint: disable=import-outside-toplevel
|
|
1222
|
+
import pandas as pd
|
|
1223
|
+
import pandas_ta as ta # noqa
|
|
1224
|
+
|
|
1171
1225
|
validate_data(data, period)
|
|
1172
1226
|
df = basemodel_to_df(data, index=index)
|
|
1173
1227
|
df_target = get_target_column(df, target)
|
|
@@ -1232,6 +1286,10 @@ def ad(data: List[Data], index: str = "date", offset: int = 0) -> OBBject[List[D
|
|
|
1232
1286
|
OBBject[List[Data]]
|
|
1233
1287
|
The calculated data.
|
|
1234
1288
|
"""
|
|
1289
|
+
# pylint: disable=import-outside-toplevel
|
|
1290
|
+
import pandas as pd
|
|
1291
|
+
import pandas_ta as ta # noqa
|
|
1292
|
+
|
|
1235
1293
|
df = basemodel_to_df(data, index=index)
|
|
1236
1294
|
df_target = get_target_columns(df, ["high", "low", "close", "volume"])
|
|
1237
1295
|
ad_df = pd.DataFrame(df_target.ta.ad(offset=offset).dropna())
|
|
@@ -1286,6 +1344,10 @@ def adx(
|
|
|
1286
1344
|
OBBject[List[Data]]
|
|
1287
1345
|
The calculated data.
|
|
1288
1346
|
"""
|
|
1347
|
+
# pylint: disable=import-outside-toplevel
|
|
1348
|
+
import pandas as pd
|
|
1349
|
+
import pandas_ta as ta # noqa
|
|
1350
|
+
|
|
1289
1351
|
validate_data(data, length)
|
|
1290
1352
|
df = basemodel_to_df(data, index=index)
|
|
1291
1353
|
df_target = get_target_columns(df, ["close", "high", "low"])
|
|
@@ -1344,6 +1406,10 @@ def wma(
|
|
|
1344
1406
|
OBBject[List[Data]]
|
|
1345
1407
|
The WMA data.
|
|
1346
1408
|
"""
|
|
1409
|
+
# pylint: disable=import-outside-toplevel
|
|
1410
|
+
import pandas as pd
|
|
1411
|
+
import pandas_ta as ta # noqa
|
|
1412
|
+
|
|
1347
1413
|
validate_data(data, length)
|
|
1348
1414
|
df = basemodel_to_df(data, index=index)
|
|
1349
1415
|
df_target = get_target_column(df, target).to_frame()
|
|
@@ -1405,6 +1471,10 @@ def cci(
|
|
|
1405
1471
|
OBBject[List[Data]]
|
|
1406
1472
|
The CCI data.
|
|
1407
1473
|
"""
|
|
1474
|
+
# pylint: disable=import-outside-toplevel
|
|
1475
|
+
import pandas as pd
|
|
1476
|
+
import pandas_ta as ta # noqa
|
|
1477
|
+
|
|
1408
1478
|
validate_data(data, length)
|
|
1409
1479
|
df = basemodel_to_df(data, index=index)
|
|
1410
1480
|
df_target = get_target_columns(df, ["close", "high", "low"])
|
|
@@ -1465,6 +1535,10 @@ def rsi(
|
|
|
1465
1535
|
OBBject[List[Data]]
|
|
1466
1536
|
The RSI data.
|
|
1467
1537
|
"""
|
|
1538
|
+
# pylint: disable=import-outside-toplevel
|
|
1539
|
+
import pandas as pd
|
|
1540
|
+
import pandas_ta as ta # noqa
|
|
1541
|
+
|
|
1468
1542
|
validate_data(data, length)
|
|
1469
1543
|
df = basemodel_to_df(data, index=index)
|
|
1470
1544
|
df_target = get_target_column(df, target).to_frame()
|
|
@@ -1530,6 +1604,10 @@ def stoch(
|
|
|
1530
1604
|
OBBject[List[Data]]
|
|
1531
1605
|
The Stochastic Oscillator data.
|
|
1532
1606
|
"""
|
|
1607
|
+
# pylint: disable=import-outside-toplevel
|
|
1608
|
+
import pandas as pd
|
|
1609
|
+
import pandas_ta as ta # noqa
|
|
1610
|
+
|
|
1533
1611
|
validate_data(data, [fast_k_period, slow_d_period, slow_k_period])
|
|
1534
1612
|
df = basemodel_to_df(data, index=index)
|
|
1535
1613
|
df_target = get_target_columns(df, ["close", "high", "low"])
|
|
@@ -1596,6 +1674,10 @@ def kc(
|
|
|
1596
1674
|
OBBject[List[Data]]
|
|
1597
1675
|
The Keltner Channels data.
|
|
1598
1676
|
"""
|
|
1677
|
+
# pylint: disable=import-outside-toplevel
|
|
1678
|
+
import pandas as pd
|
|
1679
|
+
import pandas_ta as ta # noqa
|
|
1680
|
+
|
|
1599
1681
|
validate_data(data, length)
|
|
1600
1682
|
df = basemodel_to_df(data, index=index)
|
|
1601
1683
|
df_target = get_target_columns(df, ["high", "low", "close"])
|
|
@@ -1651,6 +1733,10 @@ def cg(
|
|
|
1651
1733
|
OBBject[List[Data]]
|
|
1652
1734
|
The COG data.
|
|
1653
1735
|
"""
|
|
1736
|
+
# pylint: disable=import-outside-toplevel
|
|
1737
|
+
import pandas as pd
|
|
1738
|
+
import pandas_ta as ta # noqa
|
|
1739
|
+
|
|
1654
1740
|
validate_data(data, length)
|
|
1655
1741
|
df = basemodel_to_df(data, index=index)
|
|
1656
1742
|
df_target = get_target_columns(df, ["high", "low", "close"])
|
|
@@ -1812,6 +1898,10 @@ def ema(
|
|
|
1812
1898
|
OBBject[List[Data]]
|
|
1813
1899
|
The calculated data.
|
|
1814
1900
|
"""
|
|
1901
|
+
# pylint: disable=import-outside-toplevel
|
|
1902
|
+
import pandas as pd
|
|
1903
|
+
import pandas_ta as ta # noqa
|
|
1904
|
+
|
|
1815
1905
|
validate_data(data, length)
|
|
1816
1906
|
df = basemodel_to_df(data, index=index)
|
|
1817
1907
|
df_target = get_target_column(df, target).to_frame()
|
|
@@ -1,59 +1,63 @@
|
|
|
1
1
|
"""Views for the technical Extension."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# pylint: disable=too-many-locals,use-dict-literal
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Dict, Tuple
|
|
4
6
|
|
|
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
7
|
from openbb_charting.core.to_chart import to_chart
|
|
11
8
|
from openbb_charting.styles.colors import LARGE_CYCLER
|
|
12
|
-
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from openbb_charting.core.openbb_figure import OpenBBFigure
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
class TechnicalViews:
|
|
16
15
|
"""Technical Views."""
|
|
17
16
|
|
|
18
17
|
@staticmethod
|
|
19
|
-
def technical_sma(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
18
|
+
def technical_sma(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
20
19
|
"""Plot simple moving average chart."""
|
|
21
20
|
if "ma_type" not in kwargs:
|
|
22
21
|
kwargs["ma_type"] = "sma"
|
|
23
22
|
return _ta_ma(**kwargs)
|
|
24
23
|
|
|
25
24
|
@staticmethod
|
|
26
|
-
def technical_ema(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
25
|
+
def technical_ema(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
27
26
|
"""Exponential moving average chart."""
|
|
28
27
|
if "ma_type" not in kwargs:
|
|
29
28
|
kwargs["ma_type"] = "ema"
|
|
30
29
|
return _ta_ma(**kwargs)
|
|
31
30
|
|
|
32
31
|
@staticmethod
|
|
33
|
-
def technical_hma(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
32
|
+
def technical_hma(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
34
33
|
"""Hull moving average chart."""
|
|
35
34
|
if "ma_type" not in kwargs:
|
|
36
35
|
kwargs["ma_type"] = "hma"
|
|
37
36
|
return _ta_ma(**kwargs)
|
|
38
37
|
|
|
39
38
|
@staticmethod
|
|
40
|
-
def technical_wma(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
39
|
+
def technical_wma(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
41
40
|
"""Weighted moving average chart."""
|
|
42
41
|
if "ma_type" not in kwargs:
|
|
43
42
|
kwargs["ma_type"] = "wma"
|
|
44
43
|
return _ta_ma(**kwargs)
|
|
45
44
|
|
|
46
45
|
@staticmethod
|
|
47
|
-
def technical_zlma(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
46
|
+
def technical_zlma(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
48
47
|
"""Zero lag moving average chart."""
|
|
49
48
|
if "ma_type" not in kwargs:
|
|
50
49
|
kwargs["ma_type"] = "zlma"
|
|
51
50
|
return _ta_ma(**kwargs)
|
|
52
51
|
|
|
53
52
|
@staticmethod
|
|
54
|
-
def technical_aroon(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
53
|
+
def technical_aroon(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
55
54
|
"""Technical Aroon Chart."""
|
|
56
|
-
|
|
55
|
+
# pylint: disable=import-outside-toplevel
|
|
56
|
+
from openbb_charting.core.plotly_ta.ta_class import PlotlyTA
|
|
57
|
+
from openbb_core.app.utils import basemodel_to_df
|
|
58
|
+
from pandas import DataFrame
|
|
59
|
+
|
|
60
|
+
if "data" in kwargs and isinstance(kwargs["data"], DataFrame):
|
|
57
61
|
data = kwargs["data"]
|
|
58
62
|
else:
|
|
59
63
|
data = basemodel_to_df(
|
|
@@ -91,9 +95,14 @@ class TechnicalViews:
|
|
|
91
95
|
return fig, content
|
|
92
96
|
|
|
93
97
|
@staticmethod
|
|
94
|
-
def technical_macd(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
98
|
+
def technical_macd(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
95
99
|
"""Plot moving average convergence divergence chart."""
|
|
96
|
-
|
|
100
|
+
# pylint: disable=import-outside-toplevel
|
|
101
|
+
from openbb_charting.core.plotly_ta.ta_class import PlotlyTA
|
|
102
|
+
from openbb_core.app.utils import basemodel_to_df
|
|
103
|
+
from pandas import DataFrame
|
|
104
|
+
|
|
105
|
+
if "data" in kwargs and isinstance(kwargs["data"], DataFrame):
|
|
97
106
|
data = kwargs["data"]
|
|
98
107
|
else:
|
|
99
108
|
data = basemodel_to_df(
|
|
@@ -129,9 +138,14 @@ class TechnicalViews:
|
|
|
129
138
|
return fig, content
|
|
130
139
|
|
|
131
140
|
@staticmethod
|
|
132
|
-
def technical_adx(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
141
|
+
def technical_adx(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
133
142
|
"""Average directional movement index chart."""
|
|
134
|
-
|
|
143
|
+
# pylint: disable=import-outside-toplevel
|
|
144
|
+
from openbb_charting.core.plotly_ta.ta_class import PlotlyTA
|
|
145
|
+
from openbb_core.app.utils import basemodel_to_df
|
|
146
|
+
from pandas import DataFrame
|
|
147
|
+
|
|
148
|
+
if "data" in kwargs and isinstance(kwargs["data"], DataFrame):
|
|
135
149
|
data = kwargs["data"]
|
|
136
150
|
else:
|
|
137
151
|
data = basemodel_to_df(
|
|
@@ -164,9 +178,14 @@ class TechnicalViews:
|
|
|
164
178
|
return fig, content
|
|
165
179
|
|
|
166
180
|
@staticmethod
|
|
167
|
-
def technical_rsi(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
181
|
+
def technical_rsi(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
168
182
|
"""Relative strength index chart."""
|
|
169
|
-
|
|
183
|
+
# pylint: disable=import-outside-toplevel
|
|
184
|
+
from openbb_charting.core.plotly_ta.ta_class import PlotlyTA
|
|
185
|
+
from openbb_core.app.utils import basemodel_to_df
|
|
186
|
+
from pandas import DataFrame
|
|
187
|
+
|
|
188
|
+
if "data" in kwargs and isinstance(kwargs["data"], DataFrame):
|
|
170
189
|
data = kwargs["data"]
|
|
171
190
|
else:
|
|
172
191
|
data = basemodel_to_df(
|
|
@@ -199,15 +218,17 @@ class TechnicalViews:
|
|
|
199
218
|
return fig, content
|
|
200
219
|
|
|
201
220
|
@staticmethod
|
|
202
|
-
def technical_cones(**kwargs) -> Tuple[OpenBBFigure, Dict[str, Any]]:
|
|
221
|
+
def technical_cones(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
203
222
|
"""Volatility Cones Chart."""
|
|
223
|
+
# pylint: disable=import-outside-toplevel
|
|
224
|
+
from openbb_charting.core.chart_style import ChartStyle
|
|
225
|
+
from openbb_charting.core.openbb_figure import OpenBBFigure
|
|
226
|
+
from openbb_core.app.utils import basemodel_to_df
|
|
227
|
+
from pandas import DataFrame
|
|
228
|
+
|
|
204
229
|
data = kwargs.get("data")
|
|
205
230
|
|
|
206
|
-
if (
|
|
207
|
-
isinstance(data, pd.DataFrame)
|
|
208
|
-
and not data.empty
|
|
209
|
-
and "window" in data.columns
|
|
210
|
-
):
|
|
231
|
+
if isinstance(data, DataFrame) and not data.empty and "window" in data.columns:
|
|
211
232
|
df_ta = data.set_index("window")
|
|
212
233
|
else:
|
|
213
234
|
df_ta = basemodel_to_df(kwargs["obbject_item"], index="window") # type: ignore
|
|
@@ -312,6 +333,12 @@ class TechnicalViews:
|
|
|
312
333
|
**kwargs: Any,
|
|
313
334
|
) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
314
335
|
"""Relative Rotation Chart."""
|
|
336
|
+
# pylint: disable=import-outside-toplevel
|
|
337
|
+
from openbb_charting.charts import relative_rotation # noqa
|
|
338
|
+
from openbb_charting.core.chart_style import ChartStyle # noqa
|
|
339
|
+
from openbb_charting.core.openbb_figure import OpenBBFigure # noqa
|
|
340
|
+
from openbb_core.app.utils import basemodel_to_df # noqa
|
|
341
|
+
|
|
315
342
|
ratios_df = basemodel_to_df(kwargs["obbject_item"].rs_ratios, index="date") # type: ignore
|
|
316
343
|
momentum_df = basemodel_to_df(kwargs["obbject_item"].rs_momentum, index="date") # type: ignore
|
|
317
344
|
benchmark_symbol = kwargs["obbject_item"].benchmark # type: ignore
|
|
@@ -379,6 +406,12 @@ class TechnicalViews:
|
|
|
379
406
|
|
|
380
407
|
def _ta_ma(**kwargs):
|
|
381
408
|
"""Plot moving average helper."""
|
|
409
|
+
# pylint: disable=import-outside-toplevel
|
|
410
|
+
from openbb_charting.core.chart_style import ChartStyle
|
|
411
|
+
from openbb_charting.core.openbb_figure import OpenBBFigure
|
|
412
|
+
from openbb_core.app.utils import basemodel_to_df
|
|
413
|
+
from pandas import DataFrame
|
|
414
|
+
|
|
382
415
|
index = (
|
|
383
416
|
kwargs.get("index")
|
|
384
417
|
if "index" in kwargs and kwargs.get("index") is not None
|
|
@@ -392,7 +425,7 @@ def _ta_ma(**kwargs):
|
|
|
392
425
|
)
|
|
393
426
|
ma_types = ma_type.split(",") if isinstance(ma_type, str) else ma_type
|
|
394
427
|
|
|
395
|
-
if isinstance(data,
|
|
428
|
+
if isinstance(data, DataFrame) and not data.empty:
|
|
396
429
|
data = data.set_index(index) if index in data.columns else data
|
|
397
430
|
|
|
398
431
|
if data is None:
|
|
@@ -441,7 +474,7 @@ def _ta_ma(**kwargs):
|
|
|
441
474
|
)
|
|
442
475
|
fig.update_layout(ChartStyle().plotly_template.get("layout", {}))
|
|
443
476
|
|
|
444
|
-
ma_df =
|
|
477
|
+
ma_df = DataFrame()
|
|
445
478
|
window = [window] if isinstance(window, int) else window
|
|
446
479
|
for w in window:
|
|
447
480
|
for ma_type in ma_types:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "openbb-technical"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.3.0"
|
|
4
4
|
description = "Technical Analysis extension for OpenBB"
|
|
5
5
|
authors = ["OpenBB Team <hello@openbb.co>"]
|
|
6
6
|
license = "AGPL-3.0-only"
|
|
@@ -8,12 +8,13 @@ readme = "README.md"
|
|
|
8
8
|
packages = [{ include = "openbb_technical" }]
|
|
9
9
|
|
|
10
10
|
[tool.poetry.dependencies]
|
|
11
|
-
python = ">=3.
|
|
11
|
+
python = ">=3.9,<3.13" # scipy forces python <4.0 explicitly
|
|
12
|
+
openbb-core = "^1.3.0"
|
|
12
13
|
scipy = "^1.10.1"
|
|
13
14
|
statsmodels = "^0.14.0"
|
|
14
|
-
scikit-learn = "^1.
|
|
15
|
+
scikit-learn = "^1.5.0"
|
|
15
16
|
pandas-ta = "^0.3.14b"
|
|
16
|
-
|
|
17
|
+
numpy = "^1" # pin until pandas-ta is updated to support numpy 2.0
|
|
17
18
|
|
|
18
19
|
[build-system]
|
|
19
20
|
requires = ["poetry-core"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|