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.
@@ -1,20 +1,21 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openbb-technical
3
- Version: 1.2.3
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.5,<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
@@ -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 contextlib
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
- def absolute_maximum_scale(data: Series) -> Series:
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(np.log)
99
+ log_return = (data[col] / data[col].shift(1)).apply(log)
98
100
 
99
- result = log_return.rolling(window=window, center=False).std() * np.sqrt(
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 = np.log(1 + df.pct_change(long_period) + epsilon)
134
- momentum_short = np.log(1 + df.pct_change(short_period) + epsilon)
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
- from typing import Any, Dict, Tuple
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
- from openbb_core.app.utils import basemodel_to_df
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
- if "data" in kwargs and isinstance(kwargs["data"], pd.DataFrame):
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
- if "data" in kwargs and isinstance(kwargs["data"], pd.DataFrame):
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
- if "data" in kwargs and isinstance(kwargs["data"], pd.DataFrame):
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
- if "data" in kwargs and isinstance(kwargs["data"], pd.DataFrame):
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, pd.DataFrame) and not data.empty:
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 = pd.DataFrame()
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.2.3"
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.8,<3.12" # scipy forces python <4.0 explicitly
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.3.1"
15
+ scikit-learn = "^1.5.0"
15
16
  pandas-ta = "^0.3.14b"
16
- openbb-core = "^1.2.5"
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"]