akshare-one 0.3.6__tar.gz → 0.3.7__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.
Files changed (51) hide show
  1. {akshare_one-0.3.6 → akshare_one-0.3.7}/PKG-INFO +2 -2
  2. akshare_one-0.3.7/akshare_one/modules/indicators/simple.py +384 -0
  3. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one.egg-info/PKG-INFO +2 -2
  4. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one.egg-info/requires.txt +1 -1
  5. {akshare_one-0.3.6 → akshare_one-0.3.7}/pyproject.toml +2 -2
  6. {akshare_one-0.3.6 → akshare_one-0.3.7}/tests/test_indicators.py +189 -0
  7. akshare_one-0.3.6/akshare_one/modules/indicators/simple.py +0 -230
  8. {akshare_one-0.3.6 → akshare_one-0.3.7}/LICENSE +0 -0
  9. {akshare_one-0.3.6 → akshare_one-0.3.7}/README.md +0 -0
  10. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/__init__.py +0 -0
  11. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/eastmoney/client.py +0 -0
  12. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/eastmoney/utils.py +0 -0
  13. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/indicators.py +0 -0
  14. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/cache.py +0 -0
  15. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/financial/base.py +0 -0
  16. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/financial/eastmoney_direct.py +0 -0
  17. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/financial/factory.py +0 -0
  18. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/financial/sina.py +0 -0
  19. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/historical/base.py +0 -0
  20. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/historical/eastmoney.py +0 -0
  21. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/historical/eastmoney_direct.py +0 -0
  22. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/historical/factory.py +0 -0
  23. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/historical/sina.py +0 -0
  24. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/indicators/__init__.py +0 -0
  25. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/indicators/base.py +0 -0
  26. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/indicators/factory.py +0 -0
  27. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/indicators/talib.py +0 -0
  28. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/info/base.py +0 -0
  29. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/info/eastmoney.py +0 -0
  30. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/info/factory.py +0 -0
  31. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/insider/base.py +0 -0
  32. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/insider/factory.py +0 -0
  33. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/insider/xueqiu.py +0 -0
  34. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/news/base.py +0 -0
  35. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/news/eastmoney.py +0 -0
  36. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/news/factory.py +0 -0
  37. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/realtime/base.py +0 -0
  38. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/realtime/eastmoney.py +0 -0
  39. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/realtime/eastmoney_direct.py +0 -0
  40. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/realtime/factory.py +0 -0
  41. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/realtime/xueqiu.py +0 -0
  42. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one/modules/utils.py +0 -0
  43. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one.egg-info/SOURCES.txt +0 -0
  44. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one.egg-info/dependency_links.txt +0 -0
  45. {akshare_one-0.3.6 → akshare_one-0.3.7}/akshare_one.egg-info/top_level.txt +0 -0
  46. {akshare_one-0.3.6 → akshare_one-0.3.7}/setup.cfg +0 -0
  47. {akshare_one-0.3.6 → akshare_one-0.3.7}/tests/test_financial.py +0 -0
  48. {akshare_one-0.3.6 → akshare_one-0.3.7}/tests/test_info.py +0 -0
  49. {akshare_one-0.3.6 → akshare_one-0.3.7}/tests/test_insider.py +0 -0
  50. {akshare_one-0.3.6 → akshare_one-0.3.7}/tests/test_news.py +0 -0
  51. {akshare_one-0.3.6 → akshare_one-0.3.7}/tests/test_stock.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akshare-one
3
- Version: 0.3.6
3
+ Version: 0.3.7
4
4
  Summary: Standardized interface for Chinese financial market data, built on AKShare with unified data formats and simplified APIs
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/zwldarren/akshare-one
@@ -9,7 +9,7 @@ Keywords: akshare,financial-data,stock-data,quant
9
9
  Requires-Python: >=3.10
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
- Requires-Dist: akshare>=1.17.21
12
+ Requires-Dist: akshare>=1.17.26
13
13
  Requires-Dist: cachetools>=5.5.2
14
14
  Provides-Extra: talib
15
15
  Requires-Dist: ta-lib>=0.6.4; extra == "talib"
@@ -0,0 +1,384 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ from .base import BaseIndicatorCalculator
4
+
5
+
6
+ class SimpleIndicatorCalculator(BaseIndicatorCalculator):
7
+ """Basic pandas-based indicator implementations"""
8
+
9
+ def _get_ma(self, series: pd.Series, window: int, ma_type: int) -> pd.Series:
10
+ if ma_type == 0:
11
+ return series.rolling(window=window, min_periods=window).mean()
12
+ elif ma_type == 1:
13
+ return series.ewm(span=window, adjust=False, min_periods=window).mean()
14
+ else:
15
+ raise ValueError(
16
+ f"Unsupported ma_type: {ma_type} in simple calculator. Only SMA (0) and EMA (1) are supported."
17
+ )
18
+
19
+ def _wilder_smooth(self, series: pd.Series, window: int) -> pd.Series:
20
+ return series.ewm(alpha=1 / window, adjust=False, min_periods=window).mean()
21
+
22
+ def calculate_sma(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
23
+ return (
24
+ df["close"]
25
+ .rolling(window=window, min_periods=window)
26
+ .mean()
27
+ .to_frame("sma")
28
+ )
29
+
30
+ def calculate_ema(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
31
+ return (
32
+ df["close"]
33
+ .ewm(span=window, adjust=False, min_periods=window)
34
+ .mean()
35
+ .to_frame("ema")
36
+ )
37
+
38
+ def calculate_rsi(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
39
+ delta = df["close"].diff()
40
+ gain = delta.clip(lower=0)
41
+ loss = -delta.clip(upper=0)
42
+
43
+ avg_gain = gain.ewm(alpha=1 / window, min_periods=window, adjust=False).mean()
44
+ avg_loss = loss.ewm(alpha=1 / window, min_periods=window, adjust=False).mean()
45
+
46
+ rs = avg_gain / avg_loss
47
+ rsi = 100 - (100 / (1 + rs))
48
+
49
+ return rsi.clip(0, 100).to_frame("rsi")
50
+
51
+ def calculate_macd(
52
+ self, df: pd.DataFrame, fast: int, slow: int, signal: int
53
+ ) -> pd.DataFrame:
54
+ close = df["close"]
55
+ ema_fast = close.ewm(span=fast, adjust=False, min_periods=fast).mean()
56
+ ema_slow = close.ewm(span=slow, adjust=False, min_periods=slow).mean()
57
+
58
+ macd_line = ema_fast - ema_slow
59
+ signal_line = macd_line.ewm(
60
+ span=signal, adjust=False, min_periods=signal
61
+ ).mean()
62
+
63
+ return pd.DataFrame(
64
+ {
65
+ "macd": macd_line,
66
+ "signal": signal_line,
67
+ "histogram": macd_line - signal_line,
68
+ }
69
+ )
70
+
71
+ def calculate_bollinger_bands(
72
+ self, df: pd.DataFrame, window: int, std: int
73
+ ) -> pd.DataFrame:
74
+ close = df["close"]
75
+ sma = close.rolling(window=window, min_periods=window).mean()
76
+ rolling_std = close.rolling(window=window, min_periods=window).std()
77
+ upper_band = sma + (rolling_std * std)
78
+ lower_band = sma - (rolling_std * std)
79
+ return pd.DataFrame(
80
+ {"upper_band": upper_band, "middle_band": sma, "lower_band": lower_band}
81
+ )
82
+
83
+ def calculate_stoch(
84
+ self, df: pd.DataFrame, window: int, smooth_d: int, smooth_k: int
85
+ ) -> pd.DataFrame:
86
+ high = df["high"]
87
+ low = df["low"]
88
+ close = df["close"]
89
+
90
+ lowest_low = low.rolling(window=window).min()
91
+ highest_high = high.rolling(window=window).max()
92
+
93
+ k = 100 * (close - lowest_low) / (highest_high - lowest_low).replace(0, np.nan)
94
+ slow_k = k.rolling(window=smooth_k, min_periods=smooth_k).mean()
95
+ slow_d = slow_k.rolling(window=smooth_d, min_periods=smooth_d).mean()
96
+
97
+ return pd.DataFrame({"slow_k": slow_k, "slow_d": slow_d})
98
+
99
+ def calculate_atr(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
100
+ high = df["high"]
101
+ low = df["low"]
102
+ close = df["close"]
103
+
104
+ tr1 = high - low
105
+ tr2 = abs(high - close.shift())
106
+ tr3 = abs(low - close.shift())
107
+ tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
108
+
109
+ atr = self._wilder_smooth(tr, window)
110
+ return atr.to_frame("atr")
111
+
112
+ def calculate_cci(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
113
+ high = df["high"]
114
+ low = df["low"]
115
+ close = df["close"]
116
+
117
+ tp = (high + low + close) / 3
118
+ tp_sma = tp.rolling(window=window, min_periods=window).mean()
119
+ mean_dev = tp.rolling(window=window, min_periods=window).apply(
120
+ lambda x: (x - x.mean()).abs().mean()
121
+ )
122
+
123
+ cci = (tp - tp_sma) / (0.015 * mean_dev)
124
+ return cci.to_frame("cci")
125
+
126
+ def calculate_adx(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
127
+ dx = self.calculate_dx(df, window)["dx"]
128
+ adx = self._wilder_smooth(dx, window)
129
+ return adx.to_frame("adx")
130
+
131
+ def calculate_willr(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
132
+ high = df["high"]
133
+ low = df["low"]
134
+ close = df["close"]
135
+ highest_high = high.rolling(window=window, min_periods=window).max()
136
+ lowest_low = low.rolling(window=window, min_periods=window).min()
137
+ willr = -100 * (highest_high - close) / (highest_high - lowest_low)
138
+ return willr.to_frame("willr")
139
+
140
+ def calculate_ad(self, df: pd.DataFrame) -> pd.DataFrame:
141
+ high = df["high"]
142
+ low = df["low"]
143
+ close = df["close"]
144
+ volume = df["volume"]
145
+ mfm = ((close - low) - (high - close)) / (high - low).replace(0, np.nan)
146
+ mfm = mfm.fillna(0)
147
+ mfv = mfm * volume
148
+ ad = mfv.cumsum()
149
+ return ad.to_frame("ad")
150
+
151
+ def calculate_adosc(
152
+ self, df: pd.DataFrame, fast_period: int, slow_period: int
153
+ ) -> pd.DataFrame:
154
+ ad = self.calculate_ad(df)["ad"]
155
+ ema_fast = ad.ewm(span=fast_period, adjust=False).mean()
156
+ ema_slow = ad.ewm(span=slow_period, adjust=False).mean()
157
+ adosc = ema_fast - ema_slow
158
+ return adosc.to_frame("adosc")
159
+
160
+ def calculate_obv(self, df: pd.DataFrame) -> pd.DataFrame:
161
+ close = df["close"]
162
+ volume = df["volume"]
163
+ sign = (close > close.shift(1)).astype(int) - (close < close.shift(1)).astype(
164
+ int
165
+ )
166
+ obv = (volume * sign).cumsum()
167
+ return obv.to_frame("obv")
168
+
169
+ def calculate_mom(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
170
+ close = df["close"]
171
+ mom = close.diff(periods=window)
172
+ return mom.to_frame("mom")
173
+
174
+ def calculate_sar(
175
+ self, df: pd.DataFrame, acceleration: float, maximum: float
176
+ ) -> pd.DataFrame:
177
+ high, low = df["high"], df["low"]
178
+ sar = pd.Series(index=df.index, dtype=float)
179
+ uptrend = True
180
+ accel_factor = acceleration
181
+ extreme_point = high[0]
182
+ sar.iloc[0] = low[0]
183
+
184
+ for i in range(1, len(df)):
185
+ prev_sar = sar.iloc[i - 1]
186
+
187
+ if uptrend:
188
+ sar.iloc[i] = prev_sar + accel_factor * (extreme_point - prev_sar)
189
+ sar.iloc[i] = min(sar.iloc[i], low.iloc[i - 1])
190
+ if i > 1:
191
+ sar.iloc[i] = min(sar.iloc[i], low.iloc[i - 2])
192
+
193
+ if low[i] < sar.iloc[i]:
194
+ uptrend = False
195
+ sar.iloc[i] = extreme_point
196
+ extreme_point = low[i]
197
+ accel_factor = acceleration
198
+ else:
199
+ if high[i] > extreme_point:
200
+ extreme_point = high[i]
201
+ accel_factor = min(maximum, accel_factor + acceleration)
202
+ else:
203
+ sar.iloc[i] = prev_sar - accel_factor * (prev_sar - extreme_point)
204
+ sar.iloc[i] = max(sar.iloc[i], high.iloc[i - 1])
205
+ if i > 1:
206
+ sar.iloc[i] = max(sar.iloc[i], high.iloc[i - 2])
207
+
208
+ if high[i] > sar.iloc[i]:
209
+ uptrend = True
210
+ sar.iloc[i] = extreme_point
211
+ extreme_point = high[i]
212
+ accel_factor = acceleration
213
+ else:
214
+ if low[i] < extreme_point:
215
+ extreme_point = low[i]
216
+ accel_factor = min(maximum, accel_factor + acceleration)
217
+
218
+ return sar.to_frame("sar")
219
+
220
+ def calculate_tsf(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
221
+ close = df["close"]
222
+
223
+ def linear_reg_forecast(y):
224
+ x = np.arange(1, len(y) + 1)
225
+ b_num = len(x) * np.sum(x * y) - np.sum(x) * np.sum(y)
226
+ b_den = len(x) * np.sum(x * x) - np.sum(x) ** 2
227
+ b = b_num / b_den if b_den != 0 else 0
228
+ a = np.mean(y) - b * np.mean(x)
229
+ return a + b * len(y)
230
+
231
+ tsf = close.rolling(window=window, min_periods=window).apply(
232
+ linear_reg_forecast, raw=True
233
+ )
234
+ return tsf.to_frame("tsf")
235
+
236
+ def calculate_apo(
237
+ self, df: pd.DataFrame, fast_period: int, slow_period: int, ma_type: int
238
+ ) -> pd.DataFrame:
239
+ close = df["close"]
240
+ fast_ma = self._get_ma(close, fast_period, ma_type)
241
+ slow_ma = self._get_ma(close, slow_period, ma_type)
242
+ apo = fast_ma - slow_ma
243
+ return apo.to_frame("apo")
244
+
245
+ def calculate_aroon(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
246
+ high = df["high"]
247
+ low = df["low"]
248
+ periods_since_high = high.rolling(window=window, min_periods=window).apply(
249
+ lambda x: len(x) - 1 - np.argmax(x), raw=True
250
+ )
251
+ periods_since_low = low.rolling(window=window, min_periods=window).apply(
252
+ lambda x: len(x) - 1 - np.argmin(x), raw=True
253
+ )
254
+ aroon_up = ((window - periods_since_high) / window) * 100
255
+ aroon_down = ((window - periods_since_low) / window) * 100
256
+ return pd.DataFrame({"aroon_up": aroon_up, "aroon_down": aroon_down})
257
+
258
+ def calculate_aroonosc(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
259
+ aroon_df = self.calculate_aroon(df, window)
260
+ aroonosc = aroon_df["aroon_up"] - aroon_df["aroon_down"]
261
+ return aroonosc.to_frame("aroonosc")
262
+
263
+ def calculate_bop(self, df: pd.DataFrame) -> pd.DataFrame:
264
+ bop = (df["close"] - df["open"]) / (df["high"] - df["low"]).replace(0, np.nan)
265
+ bop = bop.fillna(0)
266
+ return bop.to_frame("bop")
267
+
268
+ def calculate_cmo(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
269
+ close_diff = df["close"].diff(1)
270
+ sum_up = close_diff.where(close_diff > 0, 0).rolling(window=window).sum()
271
+ sum_down = -close_diff.where(close_diff < 0, 0).rolling(window=window).sum()
272
+ cmo = 100 * (sum_up - sum_down) / (sum_up + sum_down).replace(0, np.nan)
273
+ cmo = cmo.fillna(0)
274
+ return cmo.to_frame("cmo")
275
+
276
+ def calculate_dx(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
277
+ plus_di = self.calculate_plus_di(df, window)["plus_di"]
278
+ minus_di = self.calculate_minus_di(df, window)["minus_di"]
279
+ dx = 100 * abs(plus_di - minus_di) / (plus_di + minus_di).replace(0, np.nan)
280
+ dx = dx.fillna(0)
281
+ return dx.to_frame("dx")
282
+
283
+ def calculate_mfi(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
284
+ typical_price = (df["high"] + df["low"] + df["close"]) / 3
285
+ money_flow = typical_price * df["volume"]
286
+ price_diff = typical_price.diff()
287
+ positive_mf = money_flow.where(price_diff > 0, 0)
288
+ negative_mf = money_flow.where(price_diff < 0, 0)
289
+ positive_mf_sum = positive_mf.rolling(window=window).sum()
290
+ negative_mf_sum = negative_mf.rolling(window=window).sum()
291
+ money_ratio = positive_mf_sum / negative_mf_sum.replace(0, np.nan)
292
+ money_ratio = money_ratio.fillna(0)
293
+ mfi = 100 - (100 / (1 + money_ratio))
294
+ return mfi.to_frame("mfi")
295
+
296
+ def calculate_minus_di(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
297
+ atr = self.calculate_atr(df, window)["atr"]
298
+ minus_dm = self.calculate_minus_dm(df, window)["minus_dm"]
299
+ minus_di = 100 * (minus_dm / atr)
300
+ return minus_di.to_frame("minus_di")
301
+
302
+ def calculate_minus_dm(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
303
+ high = df["high"]
304
+ low = df["low"]
305
+ up_move = high.diff()
306
+ down_move = -low.diff()
307
+ minus_dm = down_move.where((down_move > up_move) & (down_move > 0), 0)
308
+ smoothed_minus_dm = self._wilder_smooth(minus_dm, window)
309
+ return smoothed_minus_dm.to_frame("minus_dm")
310
+
311
+ def calculate_plus_di(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
312
+ atr = self.calculate_atr(df, window)["atr"]
313
+ plus_dm = self.calculate_plus_dm(df, window)["plus_dm"]
314
+ plus_di = 100 * (plus_dm / atr)
315
+ return plus_di.to_frame("plus_di")
316
+
317
+ def calculate_plus_dm(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
318
+ high = df["high"]
319
+ low = df["low"]
320
+ up_move = high.diff()
321
+ down_move = -low.diff()
322
+ plus_dm = up_move.where((up_move > down_move) & (up_move > 0), 0)
323
+ smoothed_plus_dm = self._wilder_smooth(plus_dm, window)
324
+ return smoothed_plus_dm.to_frame("plus_dm")
325
+
326
+ def calculate_ppo(
327
+ self, df: pd.DataFrame, fast_period: int, slow_period: int, ma_type: int
328
+ ) -> pd.DataFrame:
329
+ close = df["close"]
330
+ fast_ma = self._get_ma(close, fast_period, ma_type)
331
+ slow_ma = self._get_ma(close, slow_period, ma_type)
332
+ ppo = ((fast_ma - slow_ma) / slow_ma) * 100
333
+ return ppo.to_frame("ppo")
334
+
335
+ def calculate_roc(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
336
+ close = df["close"]
337
+ roc = (close.diff(window) / close.shift(window)) * 100
338
+ return roc.to_frame("roc")
339
+
340
+ def calculate_rocp(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
341
+ close = df["close"]
342
+ rocp = close.diff(window) / close.shift(window)
343
+ return rocp.to_frame("rocp")
344
+
345
+ def calculate_rocr(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
346
+ close = df["close"]
347
+ rocr = close / close.shift(window)
348
+ return rocr.to_frame("rocr")
349
+
350
+ def calculate_rocr100(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
351
+ close = df["close"]
352
+ rocr100 = (close / close.shift(window)) * 100
353
+ return rocr100.to_frame("rocr100")
354
+
355
+ def calculate_trix(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
356
+ close = df["close"]
357
+ ema1 = close.ewm(span=window, adjust=False).mean()
358
+ ema2 = ema1.ewm(span=window, adjust=False).mean()
359
+ ema3 = ema2.ewm(span=window, adjust=False).mean()
360
+ trix = 100 * ema3.diff(1) / ema3.shift(1)
361
+ return trix.to_frame("trix")
362
+
363
+ def calculate_ultosc(
364
+ self, df: pd.DataFrame, window1: int, window2: int, window3: int
365
+ ) -> pd.DataFrame:
366
+ low = df["low"]
367
+ high = df["high"]
368
+ close = df["close"]
369
+ close_prev = close.shift(1)
370
+ true_low = pd.concat([low, close_prev], axis=1).min(axis=1)
371
+ true_high = pd.concat([high, close_prev], axis=1).max(axis=1)
372
+ bp = close - true_low
373
+ tr = true_high - true_low
374
+ tr_sum1 = tr.rolling(window=window1).sum()
375
+ tr_sum2 = tr.rolling(window=window2).sum()
376
+ tr_sum3 = tr.rolling(window=window3).sum()
377
+ avg1 = bp.rolling(window=window1).sum() / tr_sum1.replace(0, np.nan)
378
+ avg2 = bp.rolling(window=window2).sum() / tr_sum2.replace(0, np.nan)
379
+ avg3 = bp.rolling(window=window3).sum() / tr_sum3.replace(0, np.nan)
380
+ avg1 = avg1.fillna(0)
381
+ avg2 = avg2.fillna(0)
382
+ avg3 = avg3.fillna(0)
383
+ ultosc = 100 * (4 * avg1 + 2 * avg2 + 1 * avg3) / (4 + 2 + 1)
384
+ return ultosc.to_frame("ultosc")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akshare-one
3
- Version: 0.3.6
3
+ Version: 0.3.7
4
4
  Summary: Standardized interface for Chinese financial market data, built on AKShare with unified data formats and simplified APIs
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/zwldarren/akshare-one
@@ -9,7 +9,7 @@ Keywords: akshare,financial-data,stock-data,quant
9
9
  Requires-Python: >=3.10
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
- Requires-Dist: akshare>=1.17.21
12
+ Requires-Dist: akshare>=1.17.26
13
13
  Requires-Dist: cachetools>=5.5.2
14
14
  Provides-Extra: talib
15
15
  Requires-Dist: ta-lib>=0.6.4; extra == "talib"
@@ -1,4 +1,4 @@
1
- akshare>=1.17.21
1
+ akshare>=1.17.26
2
2
  cachetools>=5.5.2
3
3
 
4
4
  [talib]
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "akshare-one"
3
- version = "0.3.6"
3
+ version = "0.3.7"
4
4
  description = "Standardized interface for Chinese financial market data, built on AKShare with unified data formats and simplified APIs"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
7
7
  dependencies = [
8
- "akshare>=1.17.21",
8
+ "akshare>=1.17.26",
9
9
  "cachetools>=5.5.2",
10
10
  ]
11
11
  license = "MIT"
@@ -94,12 +94,30 @@ class TestIndicators(unittest.TestCase):
94
94
  result = get_stoch(self.df, 14, 3, 3, calculator_type="simple")
95
95
  self.assertEqual(len(result), len(self.df))
96
96
  self.assertEqual(set(result.columns), {"slow_k", "slow_d"})
97
+ # Test handling of zero range (high == low)
98
+ df_zero_range = self.df.copy()
99
+ df_zero_range["high"] = df_zero_range["low"]
100
+ result_zero = get_stoch(df_zero_range, 14, 3, 3, calculator_type="simple")
101
+ self.assertEqual(len(result_zero), len(df_zero_range))
102
+ self.assertTrue(
103
+ not result_zero["slow_k"].isna().all()
104
+ ) # Should handle zero range
97
105
 
98
106
  def test_simple_atr(self):
99
107
  result = get_atr(self.df, 14, calculator_type="simple")
100
108
  self.assertEqual(len(result), len(self.df))
101
109
  self.assertTrue("atr" in result.columns)
102
110
 
111
+ def test_simple_willr(self):
112
+ result = get_willr(self.df, 14, calculator_type="simple")
113
+ self.assertEqual(len(result), len(self.df))
114
+ self.assertTrue("willr" in result.columns)
115
+ # Test handling of zero range (high == low)
116
+ df_zero_range = self.df.copy()
117
+ df_zero_range["high"] = df_zero_range["low"]
118
+ result_zero = get_willr(df_zero_range, 14, calculator_type="simple")
119
+ self.assertEqual(len(result_zero), len(df_zero_range))
120
+
103
121
  def test_simple_cci(self):
104
122
  result = get_cci(self.df, 14, calculator_type="simple")
105
123
  self.assertEqual(len(result), len(self.df))
@@ -110,6 +128,17 @@ class TestIndicators(unittest.TestCase):
110
128
  self.assertEqual(len(result), len(self.df))
111
129
  self.assertTrue("adx" in result.columns)
112
130
 
131
+ def test_simple_ad(self):
132
+ result = get_ad(self.df, calculator_type="simple")
133
+ self.assertEqual(len(result), len(self.df))
134
+ self.assertTrue("ad" in result.columns)
135
+ # Test handling of zero range (high == low)
136
+ df_zero_range = self.df.copy()
137
+ df_zero_range["high"] = df_zero_range["low"]
138
+ result_zero = get_ad(df_zero_range, calculator_type="simple")
139
+ self.assertEqual(len(result_zero), len(df_zero_range))
140
+ self.assertTrue(not result_zero["ad"].isna().all()) # Should handle zero range
141
+
113
142
  # Explicitly test talib implementation
114
143
  @unittest.skipUnless(TALIB_AVAILABLE, "talib not installed")
115
144
  def test_talib_sma(self):
@@ -329,3 +358,163 @@ class TestIndicators(unittest.TestCase):
329
358
  result = get_ultosc(self.df, 7, 14, 28)
330
359
  self.assertEqual(len(result), len(self.df))
331
360
  self.assertTrue("ultosc" in result.columns)
361
+
362
+ # Additional tests for edge cases in simple implementation
363
+ def test_simple_bop_edge_case(self):
364
+ result = get_bop(self.df, calculator_type="simple")
365
+ self.assertEqual(len(result), len(self.df))
366
+ self.assertTrue("bop" in result.columns)
367
+ # Test handling of zero range (high == low)
368
+ df_zero_range = self.df.copy()
369
+ df_zero_range["high"] = df_zero_range["low"]
370
+ result_zero = get_bop(df_zero_range, calculator_type="simple")
371
+ self.assertEqual(len(result_zero), len(df_zero_range))
372
+ self.assertTrue(not result_zero["bop"].isna().all()) # Should handle zero range
373
+
374
+ def test_simple_mfi_edge_case(self):
375
+ result = get_mfi(self.df, 14, calculator_type="simple")
376
+ self.assertEqual(len(result), len(self.df))
377
+ self.assertTrue("mfi" in result.columns)
378
+ # Test handling of zero negative money flow
379
+ df_constant_price = self.df.copy()
380
+ df_constant_price["close"] = 100 # All prices are the same
381
+ result_zero = get_mfi(df_constant_price, 14, calculator_type="simple")
382
+ self.assertEqual(len(result_zero), len(df_constant_price))
383
+ self.assertTrue(
384
+ not result_zero["mfi"].isna().all()
385
+ ) # Should handle zero negative flow
386
+
387
+ def test_simple_dx_edge_case(self):
388
+ result = get_dx(self.df, 14, calculator_type="simple")
389
+ self.assertEqual(len(result), len(self.df))
390
+ self.assertTrue("dx" in result.columns)
391
+ # Test handling of zero DI sum
392
+ df_flat = self.df.copy()
393
+ df_flat["high"] = df_flat["low"] # No directional movement
394
+ result_zero = get_dx(df_flat, 14, calculator_type="simple")
395
+ self.assertEqual(len(result_zero), len(df_flat))
396
+ self.assertTrue(not result_zero["dx"].isna().all()) # Should handle zero DI sum
397
+
398
+ def test_simple_cmo_edge_case(self):
399
+ result = get_cmo(self.df, 14, calculator_type="simple")
400
+ self.assertEqual(len(result), len(self.df))
401
+ self.assertTrue("cmo" in result.columns)
402
+ # Test handling of zero price movement
403
+ df_flat = self.df.copy()
404
+ df_flat["close"] = 100 # No price movement
405
+ result_zero = get_cmo(df_flat, 14, calculator_type="simple")
406
+ self.assertEqual(len(result_zero), len(df_flat))
407
+ self.assertTrue(
408
+ not result_zero["cmo"].isna().all()
409
+ ) # Should handle zero movement
410
+
411
+ def test_simple_ultosc_edge_case(self):
412
+ result = get_ultosc(self.df, 7, 14, 28, calculator_type="simple")
413
+ self.assertEqual(len(result), len(self.df))
414
+ self.assertTrue("ultosc" in result.columns)
415
+ # Test handling of zero true range
416
+ df_flat = self.df.copy()
417
+ df_flat["high"] = df_flat["low"] # Zero true range
418
+ result_zero = get_ultosc(df_flat, 7, 14, 28, calculator_type="simple")
419
+ self.assertEqual(len(result_zero), len(df_flat))
420
+ self.assertTrue(not result_zero["ultosc"].isna().all()) # Should handle zero TR
421
+
422
+ def test_simple_sar_edge_case(self):
423
+ result = get_sar(self.df, 0.02, 0.2, calculator_type="simple")
424
+ self.assertEqual(len(result), len(self.df))
425
+ self.assertTrue("sar" in result.columns)
426
+ # Test handling of flat market
427
+ df_flat = self.df.copy()
428
+ df_flat["high"] = df_flat["low"] = df_flat["close"] # Flat market
429
+ result_zero = get_sar(df_flat, 0.02, 0.2, calculator_type="simple")
430
+ self.assertEqual(len(result_zero), len(df_flat))
431
+ self.assertTrue(
432
+ not result_zero["sar"].isna().all()
433
+ ) # Should handle flat market
434
+
435
+ # Tests for other simple implementation functions not covered above
436
+ def test_simple_adosc(self):
437
+ result = get_adosc(self.df, 3, 10, calculator_type="simple")
438
+ self.assertEqual(len(result), len(self.df))
439
+ self.assertTrue("adosc" in result.columns)
440
+
441
+ def test_simple_obv(self):
442
+ result = get_obv(self.df, calculator_type="simple")
443
+ self.assertEqual(len(result), len(self.df))
444
+ self.assertTrue("obv" in result.columns)
445
+
446
+ def test_simple_mom(self):
447
+ result = get_mom(self.df, 10, calculator_type="simple")
448
+ self.assertEqual(len(result), len(self.df))
449
+ self.assertTrue("mom" in result.columns)
450
+
451
+ def test_simple_tsf(self):
452
+ result = get_tsf(self.df, 14, calculator_type="simple")
453
+ self.assertEqual(len(result), len(self.df))
454
+ self.assertTrue("tsf" in result.columns)
455
+
456
+ def test_simple_apo(self):
457
+ result = get_apo(self.df, 12, 26, 0, calculator_type="simple")
458
+ self.assertEqual(len(result), len(self.df))
459
+ self.assertTrue("apo" in result.columns)
460
+
461
+ def test_simple_aroon(self):
462
+ result = get_aroon(self.df, 14, calculator_type="simple")
463
+ self.assertEqual(len(result), len(self.df))
464
+ self.assertTrue("aroon_up" in result.columns)
465
+ self.assertTrue("aroon_down" in result.columns)
466
+
467
+ def test_simple_aroonosc(self):
468
+ result = get_aroonosc(self.df, 14, calculator_type="simple")
469
+ self.assertEqual(len(result), len(self.df))
470
+ self.assertTrue("aroonosc" in result.columns)
471
+
472
+ def test_simple_ppo(self):
473
+ result = get_ppo(self.df, 12, 26, 0, calculator_type="simple")
474
+ self.assertEqual(len(result), len(self.df))
475
+ self.assertTrue("ppo" in result.columns)
476
+
477
+ def test_simple_roc(self):
478
+ result = get_roc(self.df, 10, calculator_type="simple")
479
+ self.assertEqual(len(result), len(self.df))
480
+ self.assertTrue("roc" in result.columns)
481
+
482
+ def test_simple_rocp(self):
483
+ result = get_rocp(self.df, 10, calculator_type="simple")
484
+ self.assertEqual(len(result), len(self.df))
485
+ self.assertTrue("rocp" in result.columns)
486
+
487
+ def test_simple_rocr(self):
488
+ result = get_rocr(self.df, 10, calculator_type="simple")
489
+ self.assertEqual(len(result), len(self.df))
490
+ self.assertTrue("rocr" in result.columns)
491
+
492
+ def test_simple_rocr100(self):
493
+ result = get_rocr100(self.df, 10, calculator_type="simple")
494
+ self.assertEqual(len(result), len(self.df))
495
+ self.assertTrue("rocr100" in result.columns)
496
+
497
+ def test_simple_trix(self):
498
+ result = get_trix(self.df, 30, calculator_type="simple")
499
+ self.assertEqual(len(result), len(self.df))
500
+ self.assertTrue("trix" in result.columns)
501
+
502
+ def test_simple_minus_di(self):
503
+ result = get_minus_di(self.df, 14, calculator_type="simple")
504
+ self.assertEqual(len(result), len(self.df))
505
+ self.assertTrue("minus_di" in result.columns)
506
+
507
+ def test_simple_minus_dm(self):
508
+ result = get_minus_dm(self.df, 14, calculator_type="simple")
509
+ self.assertEqual(len(result), len(self.df))
510
+ self.assertTrue("minus_dm" in result.columns)
511
+
512
+ def test_simple_plus_di(self):
513
+ result = get_plus_di(self.df, 14, calculator_type="simple")
514
+ self.assertEqual(len(result), len(self.df))
515
+ self.assertTrue("plus_di" in result.columns)
516
+
517
+ def test_simple_plus_dm(self):
518
+ result = get_plus_dm(self.df, 14, calculator_type="simple")
519
+ self.assertEqual(len(result), len(self.df))
520
+ self.assertTrue("plus_dm" in result.columns)
@@ -1,230 +0,0 @@
1
- import pandas as pd
2
- from .base import BaseIndicatorCalculator
3
-
4
-
5
- class SimpleIndicatorCalculator(BaseIndicatorCalculator):
6
- """Basic pandas-based indicator implementations"""
7
-
8
- def calculate_sma(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
9
- return (
10
- df["close"]
11
- .rolling(window=window, min_periods=window)
12
- .mean()
13
- .to_frame("sma")
14
- )
15
-
16
- def calculate_ema(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
17
- return (
18
- df["close"]
19
- .ewm(span=window, adjust=False, min_periods=window)
20
- .mean()
21
- .to_frame("ema")
22
- )
23
-
24
- def calculate_rsi(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
25
- delta = df["close"].diff()
26
- gain = delta.clip(lower=0)
27
- loss = -delta.clip(upper=0)
28
-
29
- avg_gain = gain.ewm(alpha=1 / window, min_periods=window, adjust=False).mean()
30
- avg_loss = loss.ewm(alpha=1 / window, min_periods=window, adjust=False).mean()
31
-
32
- rs = avg_gain / avg_loss
33
- rsi = 100 - (100 / (1 + rs))
34
-
35
- return rsi.clip(0, 100).to_frame("rsi")
36
-
37
- def calculate_macd(
38
- self, df: pd.DataFrame, fast: int, slow: int, signal: int
39
- ) -> pd.DataFrame:
40
- close = df["close"]
41
- ema_fast = close.ewm(span=fast, adjust=False, min_periods=fast).mean()
42
- ema_slow = close.ewm(span=slow, adjust=False, min_periods=slow).mean()
43
-
44
- macd_line = ema_fast - ema_slow
45
- signal_line = macd_line.ewm(
46
- span=signal, adjust=False, min_periods=signal
47
- ).mean()
48
-
49
- return pd.DataFrame(
50
- {
51
- "macd": macd_line,
52
- "signal": signal_line,
53
- "histogram": macd_line - signal_line,
54
- }
55
- )
56
-
57
- def calculate_bollinger_bands(
58
- self, df: pd.DataFrame, window: int, std: int
59
- ) -> pd.DataFrame:
60
- close = df["close"]
61
- sma = close.rolling(window=window, min_periods=window).mean()
62
- rolling_std = close.rolling(window=window, min_periods=window).std()
63
- upper_band = sma + (rolling_std * std)
64
- lower_band = sma - (rolling_std * std)
65
- return pd.DataFrame(
66
- {"upper_band": upper_band, "middle_band": sma, "lower_band": lower_band}
67
- )
68
-
69
- def calculate_stoch(
70
- self, df: pd.DataFrame, window: int, smooth_d: int, smooth_k: int
71
- ) -> pd.DataFrame:
72
- high = df["high"]
73
- low = df["low"]
74
- close = df["close"]
75
-
76
- lowest_low = low.rolling(window=window).min()
77
- highest_high = high.rolling(window=window).max()
78
-
79
- k = 100 * (close - lowest_low) / (highest_high - lowest_low)
80
- slow_k = k.rolling(window=smooth_k).mean()
81
- slow_d = slow_k.rolling(window=smooth_d).mean()
82
-
83
- return pd.DataFrame({"slow_k": slow_k, "slow_d": slow_d})
84
-
85
- def calculate_atr(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
86
- high = df["high"]
87
- low = df["low"]
88
- close = df["close"]
89
-
90
- tr1 = high - low
91
- tr2 = abs(high - close.shift())
92
- tr3 = abs(low - close.shift())
93
- tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
94
-
95
- atr = tr.ewm(alpha=1 / window, adjust=False, min_periods=window).mean()
96
- return atr.to_frame("atr")
97
-
98
- def calculate_cci(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
99
- high = df["high"]
100
- low = df["low"]
101
- close = df["close"]
102
-
103
- tp = (high + low + close) / 3
104
- tp_sma = tp.rolling(window=window, min_periods=window).mean()
105
- mean_dev = tp.rolling(window=window, min_periods=window).apply(
106
- lambda x: (x - x.mean()).abs().mean()
107
- )
108
-
109
- cci = (tp - tp_sma) / (0.015 * mean_dev)
110
- return cci.to_frame("cci")
111
-
112
- def calculate_adx(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
113
- high = df["high"]
114
- low = df["low"]
115
- close = df["close"]
116
-
117
- # Calculate +DM, -DM and TR
118
- move_up = high.diff()
119
- move_down = low.diff().apply(abs)
120
-
121
- plus_dm = pd.Series((move_up > move_down) & (move_up > 0)).astype(int) * move_up
122
- minus_dm = (
123
- pd.Series((move_down > move_up) & (move_down > 0)).astype(int) * move_down
124
- )
125
-
126
- tr1 = high - low
127
- tr2 = abs(high - close.shift())
128
- tr3 = abs(low - close.shift())
129
- tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
130
-
131
- # Smooth +DM, -DM and TR
132
- atr = tr.ewm(alpha=1 / window, adjust=False, min_periods=window).mean()
133
- plus_di = 100 * (
134
- plus_dm.ewm(alpha=1 / window, adjust=False, min_periods=window).mean() / atr
135
- )
136
- minus_di = 100 * (
137
- minus_dm.ewm(alpha=1 / window, adjust=False, min_periods=window).mean()
138
- / atr
139
- )
140
-
141
- # Calculate ADX
142
- dx = 100 * (abs(plus_di - minus_di) / (plus_di + minus_di))
143
- adx = dx.ewm(alpha=1 / window, adjust=False, min_periods=window).mean()
144
-
145
- return adx.to_frame("adx")
146
-
147
- def calculate_willr(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
148
- raise NotImplementedError("WILLR not implemented in simple calculator")
149
-
150
- def calculate_ad(self, df: pd.DataFrame) -> pd.DataFrame:
151
- raise NotImplementedError("AD not implemented in simple calculator")
152
-
153
- def calculate_adosc(
154
- self, df: pd.DataFrame, fast_period: int, slow_period: int
155
- ) -> pd.DataFrame:
156
- raise NotImplementedError("ADOSC not implemented in simple calculator")
157
-
158
- def calculate_obv(self, df: pd.DataFrame) -> pd.DataFrame:
159
- raise NotImplementedError("OBV not implemented in simple calculator")
160
-
161
- def calculate_mom(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
162
- raise NotImplementedError("MOM not implemented in simple calculator")
163
-
164
- def calculate_sar(
165
- self, df: pd.DataFrame, acceleration: float, maximum: float
166
- ) -> pd.DataFrame:
167
- raise NotImplementedError("SAR not implemented in simple calculator")
168
-
169
- def calculate_tsf(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
170
- raise NotImplementedError("TSF not implemented in simple calculator")
171
-
172
- def calculate_apo(
173
- self, df: pd.DataFrame, fast_period: int, slow_period: int, ma_type: int
174
- ) -> pd.DataFrame:
175
- raise NotImplementedError("APO not implemented in simple calculator")
176
-
177
- def calculate_aroon(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
178
- raise NotImplementedError("AROON not implemented in simple calculator")
179
-
180
- def calculate_aroonosc(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
181
- raise NotImplementedError("AROONOSC not implemented in simple calculator")
182
-
183
- def calculate_bop(self, df: pd.DataFrame) -> pd.DataFrame:
184
- raise NotImplementedError("BOP not implemented in simple calculator")
185
-
186
- def calculate_cmo(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
187
- raise NotImplementedError("CMO not implemented in simple calculator")
188
-
189
- def calculate_dx(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
190
- raise NotImplementedError("DX not implemented in simple calculator")
191
-
192
- def calculate_mfi(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
193
- raise NotImplementedError("MFI not implemented in simple calculator")
194
-
195
- def calculate_minus_di(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
196
- raise NotImplementedError("MINUS_DI not implemented in simple calculator")
197
-
198
- def calculate_minus_dm(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
199
- raise NotImplementedError("MINUS_DM not implemented in simple calculator")
200
-
201
- def calculate_plus_di(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
202
- raise NotImplementedError("PLUS_DI not implemented in simple calculator")
203
-
204
- def calculate_plus_dm(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
205
- raise NotImplementedError("PLUS_DM not implemented in simple calculator")
206
-
207
- def calculate_ppo(
208
- self, df: pd.DataFrame, fast_period: int, slow_period: int, ma_type: int
209
- ) -> pd.DataFrame:
210
- raise NotImplementedError("PPO not implemented in simple calculator")
211
-
212
- def calculate_roc(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
213
- raise NotImplementedError("ROC not implemented in simple calculator")
214
-
215
- def calculate_rocp(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
216
- raise NotImplementedError("ROCP not implemented in simple calculator")
217
-
218
- def calculate_rocr(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
219
- raise NotImplementedError("ROCR not implemented in simple calculator")
220
-
221
- def calculate_rocr100(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
222
- raise NotImplementedError("ROCR100 not implemented in simple calculator")
223
-
224
- def calculate_trix(self, df: pd.DataFrame, window: int) -> pd.DataFrame:
225
- raise NotImplementedError("TRIX not implemented in simple calculator")
226
-
227
- def calculate_ultosc(
228
- self, df: pd.DataFrame, window1: int, window2: int, window3: int
229
- ) -> pd.DataFrame:
230
- raise NotImplementedError("ULTOSC not implemented in simple calculator")
File without changes
File without changes
File without changes