openbb-technical 1.2.2__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.
@@ -1,20 +1,21 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openbb-technical
3
- Version: 1.2.2
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,<3.12
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
- Requires-Dist: openbb-core (>=1.2.4,<2.0.0)
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.3.1,<2.0.0)
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
- from typing import Any, List, Literal, Optional, Tuple, Union
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
- import numpy as np
7
- import pandas as pd
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: pd.DataFrame,
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
- ) -> pd.DataFrame:
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 : pd.DataFrame
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
- pd.DataFrame : results
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 * np.log(2.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: pd.DataFrame,
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
- ) -> pd.DataFrame:
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 : pd.DataFrame
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
- pd.DataFrame : results
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(np.log)
122
+ log_return = (data["close"] / data["close"].shift(1)).apply(log)
117
123
 
118
- result = log_return.rolling(window=window, center=False).std() * np.sqrt(
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: pd.DataFrame,
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
- ) -> pd.DataFrame:
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 : pd.DataFrame
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
- pd.DataFrame : results
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(np.log)
170
- log_co = (data["close"] / data["open"]).apply(np.log)
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 * np.log(2) - 1) * log_co**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: pd.DataFrame,
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
- ) -> pd.DataFrame:
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 : pd.DataFrame
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
- pd.DataFrame : results
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(np.log)
242
+ log_return = (data["close"] / data["close"].shift(1)).apply(log)
231
243
 
232
- vol = log_return.rolling(window=window, center=False).std() * np.sqrt(
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: pd.DataFrame,
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
- ) -> pd.Series:
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 : pd.DataFrame
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
- pd.Series : results
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(np.log)
291
- log_lo = (data["low"] / data["open"]).apply(np.log)
292
- log_co = (data["close"] / data["open"]).apply(np.log)
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: pd.DataFrame,
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
- ) -> pd.DataFrame:
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 : pd.DataFrame
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
- pd.DataFrame : results
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(np.log)
348
- log_lo = (data["low"] / data["open"]).apply(np.log)
349
- log_co = (data["close"] / data["open"]).apply(np.log)
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(np.log)
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(np.log)
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(np.sqrt) * np.sqrt(
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: pd.DataFrame,
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
- ) -> pd.DataFrame:
408
+ ) -> "DataFrame":
393
409
  """Calculate Cones."""
394
- estimator = pd.DataFrame()
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 = pd.DataFrame(df_, columns=df_windows)
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: pd.Series, window: int = 90
463
- ) -> Tuple[float, float, pd.Series]:
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: pd.Series
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
- pd.Series:
501
+ Series:
483
502
  Values for best fit line
484
503
  """
485
- from sklearn.linear_model import ( # pylint: disable=import-outside-toplevel # type: ignore
486
- LinearRegression,
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 = np.log(values)
495
- X = np.arange(len(y)).reshape(-1, 1)
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 = (np.exp(coef) ** 252) - 1
522
+ annualized_coef = (exp(coef) ** 252) - 1
503
523
 
504
- return r2, annualized_coef, pd.Series(lr.predict(X))
524
+ return r2, annualized_coef, Series(lr.predict(X))
505
525
 
506
526
 
507
527
  def calculate_fib_levels(
508
- data: pd.DataFrame,
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[pd.DataFrame, pd.Timestamp, pd.Timestamp, float, float, str]:
533
+ ) -> Tuple["DataFrame", "Timestamp", "Timestamp", float, float, str]:
514
534
  """Calculate Fibonacci levels.
515
535
 
516
536
  Parameters
517
537
  ----------
518
- data : pd.DataFrame
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 : pd.DataFrame
551
+ df : DataFrame
532
552
  Dataframe of fib levels
533
- min_date: pd.Timestamp
553
+ min_date: Timestamp
534
554
  Date of min point
535
- max_date: pd.Timestamp:
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 = pd.DataFrame()
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