bullishpy 0.4.0__py3-none-any.whl → 0.6.0__py3-none-any.whl

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.

Potentially problematic release.


This version of bullishpy might be problematic. Click here for more details.

@@ -0,0 +1,344 @@
1
+ import logging
2
+ from datetime import date
3
+ from typing import Optional, Callable
4
+
5
+ import pandas as pd
6
+ import pandas_ta as ta # type: ignore
7
+
8
+ from pydantic import BaseModel
9
+
10
+ logger = logging.getLogger(__name__)
11
+ try:
12
+ import talib
13
+ except Exception:
14
+ logger.warning("Talib is not installed, skipping analysis")
15
+
16
+
17
+ def cross(
18
+ series_a: pd.Series, series_b: pd.Series, above: bool = True
19
+ ) -> Optional[date]:
20
+ crossing = ta.cross(series_a=series_a, series_b=series_b, above=above)
21
+ if not crossing[crossing == 1].index.empty:
22
+ return pd.Timestamp(crossing[crossing == 1].index[-1]).date()
23
+ return None
24
+
25
+
26
+ def cross_value(series: pd.Series, number: int, above: bool = True) -> Optional[date]:
27
+ return cross(series, pd.Series(number, index=series.index), above=above)
28
+
29
+
30
+ def compute_adx(data: pd.DataFrame) -> pd.DataFrame:
31
+ results = pd.DataFrame(index=data.index)
32
+ results["ADX_14"] = talib.ADX(data.high, data.low, close=data.close) # type: ignore
33
+ results["MINUS_DI"] = talib.MINUS_DI(data.high, data.low, data.close) # type: ignore
34
+ results["PLUS_DI"] = talib.PLUS_DI(data.high, data.low, data.close) # type: ignore
35
+ return results
36
+
37
+
38
+ def compute_pandas_ta_adx(data: pd.DataFrame) -> pd.DataFrame:
39
+ results = pd.DataFrame(index=data.index)
40
+ adx = ta.adx(data.high, data.low, data.close, length=14)
41
+ results["ADX_14"] = adx["ADX_14"]
42
+ results["MINUS_DI"] = adx["DMN_14"]
43
+ results["PLUS_DI"] = adx["DMP_14"]
44
+ return results
45
+
46
+
47
+ def compute_macd(data: pd.DataFrame) -> pd.DataFrame:
48
+ results = pd.DataFrame(index=data.index)
49
+ (
50
+ results["MACD_12_26_9"],
51
+ results["MACD_12_26_9_SIGNAL"],
52
+ results["MACD_12_26_9_HIST"],
53
+ ) = talib.MACD(
54
+ data.close # type: ignore
55
+ )
56
+ return results
57
+
58
+
59
+ def compute_pandas_ta_macd(data: pd.DataFrame) -> pd.DataFrame:
60
+
61
+ macd = ta.macd(data.close, fast=12, slow=26, signal=9)
62
+ results = pd.DataFrame(index=macd.index)
63
+ results["MACD_12_26_9"] = macd["MACD_12_26_9"]
64
+ results["MACD_12_26_9_SIGNAL"] = macd["MACDs_12_26_9"]
65
+ results["MACD_12_26_9_HIST"] = macd["MACDh_12_26_9"]
66
+ return results
67
+
68
+
69
+ def compute_rsi(data: pd.DataFrame) -> pd.DataFrame:
70
+ results = pd.DataFrame(index=data.index)
71
+ results["RSI"] = talib.RSI(data.close) # type: ignore
72
+ return results
73
+
74
+
75
+ def compute_pandas_ta_rsi(data: pd.DataFrame) -> pd.DataFrame:
76
+ results = pd.DataFrame(index=data.index)
77
+ results["RSI"] = ta.rsi(data.close, length=14)
78
+ return results
79
+
80
+
81
+ def compute_stoch(data: pd.DataFrame) -> pd.DataFrame:
82
+ results = pd.DataFrame(index=data.index)
83
+ results["SLOW_K"], results["SLOW_D"] = talib.STOCH(data.high, data.low, data.close) # type: ignore
84
+ return results
85
+
86
+
87
+ def compute_pandas_ta_stoch(data: pd.DataFrame) -> pd.DataFrame:
88
+ results = pd.DataFrame(index=data.index)
89
+ stoch = ta.stoch(data.high, data.low, data.close, k=5, d=3, smooth_k=3)
90
+ results["SLOW_K"] = stoch["STOCHk_5_3_3"]
91
+ results["SLOW_D"] = stoch["STOCHd_5_3_3"]
92
+ return results
93
+
94
+
95
+ def compute_mfi(data: pd.DataFrame) -> pd.DataFrame:
96
+ results = pd.DataFrame(index=data.index)
97
+ results["MFI"] = talib.MFI(data.high, data.low, data.close, data.volume) # type: ignore
98
+ return results
99
+
100
+
101
+ def compute_pandas_ta_mfi(data: pd.DataFrame) -> pd.DataFrame:
102
+ results = pd.DataFrame(index=data.index)
103
+ results["MFI"] = ta.mfi(data.high, data.low, data.close, data.volume, length=14)
104
+ return results
105
+
106
+
107
+ def compute_roc(data: pd.DataFrame) -> pd.DataFrame:
108
+ results = pd.DataFrame(index=data.index)
109
+ results["ROC_7"] = talib.ROC(data.close, timeperiod=7) # type: ignore
110
+ results["ROC_1"] = talib.ROC(data.close, timeperiod=1) # type: ignore
111
+ results["ROC_30"] = talib.ROC(data.close, timeperiod=30) # type: ignore
112
+ return results
113
+
114
+
115
+ def compute_pandas_ta_roc(data: pd.DataFrame) -> pd.DataFrame:
116
+ results = pd.DataFrame(index=data.index)
117
+ results["ROC_7"] = ta.roc(data.close, length=7)
118
+ results["ROC_1"] = ta.roc(data.close, length=1)
119
+ results["ROC_30"] = ta.roc(data.close, length=30)
120
+ return results
121
+
122
+
123
+ def compute_sma(data: pd.DataFrame) -> pd.DataFrame:
124
+ results = pd.DataFrame(index=data.index)
125
+ results["SMA_50"] = talib.SMA(data.close, timeperiod=50) # type: ignore
126
+ results["SMA_200"] = talib.SMA(data.close, timeperiod=200) # type: ignore
127
+ return results
128
+
129
+
130
+ def compute_pandas_ta_sma(data: pd.DataFrame) -> pd.DataFrame:
131
+ results = pd.DataFrame(index=data.index)
132
+ results["SMA_50"] = ta.sma(data.close, length=50)
133
+ results["SMA_200"] = ta.sma(data.close, length=200)
134
+ return results
135
+
136
+
137
+ def compute_adosc(data: pd.DataFrame) -> pd.DataFrame:
138
+ data_ = data.copy()
139
+ results = pd.DataFrame(index=data.index)
140
+ results["ADOSC"] = talib.ADOSC(data.high, data.low, data.close, data.volume) # type: ignore
141
+ data_["ADOSC"] = results["ADOSC"]
142
+ data_["HIGHEST_20"] = data_.close.rolling(window=20).max()
143
+ results["ADOSC_SIGNAL"] = (data_.close > data_["HIGHEST_20"].shift(1)) & (
144
+ data_["ADOSC"] > 0
145
+ )
146
+ return results
147
+
148
+
149
+ def compute_pandas_ta_adosc(data: pd.DataFrame) -> pd.DataFrame:
150
+ data_ = data.copy()
151
+ results = pd.DataFrame(index=data.index)
152
+ results["ADOSC"] = ta.adosc(data.high, data.low, data.close, data.volume)
153
+ data_["ADOSC"] = results["ADOSC"]
154
+ data_["HIGHEST_20"] = data_.close.rolling(window=20).max()
155
+ results["ADOSC_SIGNAL"] = (data_.close > data_["HIGHEST_20"].shift(1)) & (
156
+ data_["ADOSC"] > 0
157
+ )
158
+ return results
159
+
160
+
161
+ def compute_ad(data: pd.DataFrame) -> pd.DataFrame:
162
+ results = pd.DataFrame(index=data.index)
163
+ results["AD"] = talib.AD(data.high, data.low, data.close, data.volume) # type: ignore
164
+ return results
165
+
166
+
167
+ def compute_pandas_ta_ad(data: pd.DataFrame) -> pd.DataFrame:
168
+ results = pd.DataFrame(index=data.index)
169
+ results["AD"] = ta.ad(data.high, data.low, data.close, data.volume)
170
+ return results
171
+
172
+
173
+ def compute_obv(data: pd.DataFrame) -> pd.DataFrame:
174
+ results = pd.DataFrame(index=data.index)
175
+ results["OBV"] = talib.OBV(data.close, data.volume) # type: ignore
176
+ return results
177
+
178
+
179
+ def compute_pandas_ta_obv(data: pd.DataFrame) -> pd.DataFrame:
180
+ results = pd.DataFrame(index=data.index)
181
+ results["OBV"] = ta.obv(data.close, data.volume)
182
+ return results
183
+
184
+
185
+ def compute_atr(data: pd.DataFrame) -> pd.DataFrame:
186
+ results = pd.DataFrame(index=data.index)
187
+ results["ATR"] = talib.ATR(data.high, data.low, data.close) # type: ignore
188
+ return results
189
+
190
+
191
+ def compute_pandas_ta_atr(data: pd.DataFrame) -> pd.DataFrame:
192
+ results = pd.DataFrame(index=data.index)
193
+ results["ATR"] = ta.atr(data.high, data.low, data.close, length=14)
194
+ return results
195
+
196
+
197
+ def compute_natr(data: pd.DataFrame) -> pd.DataFrame:
198
+ results = pd.DataFrame(index=data.index)
199
+ results["NATR"] = talib.NATR(data.high, data.low, data.close) # type: ignore
200
+ return results
201
+
202
+
203
+ def compute_pandas_ta_natr(data: pd.DataFrame) -> pd.DataFrame:
204
+ results = pd.DataFrame(index=data.index)
205
+ results["NATR"] = ta.natr(data.high, data.low, data.close, length=14)
206
+ return results
207
+
208
+
209
+ def compute_trange(data: pd.DataFrame) -> pd.DataFrame:
210
+ results = pd.DataFrame(index=data.index)
211
+ results["TRANGE"] = talib.TRANGE(data.high, data.low, data.close) # type: ignore
212
+ return results
213
+
214
+
215
+ def compute_pandas_ta_trange(data: pd.DataFrame) -> pd.DataFrame:
216
+ results = pd.DataFrame(index=data.index)
217
+ results["TRANGE"] = ta.true_range(data.high, data.low, data.close)
218
+ return results
219
+
220
+
221
+ def compute_patterns(data: pd.DataFrame) -> pd.DataFrame:
222
+ results = pd.DataFrame(index=data.index)
223
+ results["CDLMORNINGSTAR"] = talib.CDLMORNINGSTAR(
224
+ data.open, data.high, data.low, data.close # type: ignore
225
+ )
226
+ results["CDL3LINESTRIKE"] = talib.CDL3LINESTRIKE(
227
+ data.open, data.high, data.low, data.close # type: ignore
228
+ )
229
+ results["CDL3WHITESOLDIERS"] = talib.CDL3WHITESOLDIERS(
230
+ data.open, data.high, data.low, data.close # type: ignore
231
+ )
232
+ results["CDLABANDONEDBABY"] = talib.CDLABANDONEDBABY(
233
+ data.open, data.high, data.low, data.close # type: ignore
234
+ )
235
+ results["CDLTASUKIGAP"] = talib.CDLTASUKIGAP(
236
+ data.open, data.high, data.low, data.close # type: ignore
237
+ )
238
+ results["CDLPIERCING"] = talib.CDLPIERCING(
239
+ data.open, data.high, data.low, data.close # type: ignore
240
+ )
241
+ results["CDLENGULFING"] = talib.CDLENGULFING(
242
+ data.open, data.high, data.low, data.close # type: ignore
243
+ )
244
+ return results
245
+
246
+
247
+ class IndicatorFunction(BaseModel):
248
+ expected_columns: list[str]
249
+ functions: list[Callable[[pd.DataFrame], pd.DataFrame]]
250
+
251
+ def call(self, data: pd.DataFrame) -> pd.DataFrame:
252
+ data_ = None
253
+ for function in self.functions:
254
+ try:
255
+ data_ = function(data)
256
+ break
257
+ except Exception as e:
258
+ logger.warning(f"Fail to compute function {function.__name__}: {e}")
259
+ if data_ is None:
260
+ raise ValueError("No data returned from indicator functions.")
261
+ if not set(self.expected_columns).issubset(set(data_.columns)):
262
+ raise ValueError(
263
+ f"Expected columns {self.expected_columns} not found in data columns {data_.columns.tolist()}"
264
+ )
265
+ return data_
266
+
267
+
268
+ ADX = IndicatorFunction(
269
+ expected_columns=["ADX_14", "MINUS_DI", "PLUS_DI"],
270
+ functions=[compute_adx, compute_pandas_ta_adx],
271
+ )
272
+ MACD = IndicatorFunction(
273
+ expected_columns=["MACD_12_26_9", "MACD_12_26_9_SIGNAL", "MACD_12_26_9_HIST"],
274
+ functions=[compute_macd, compute_pandas_ta_macd],
275
+ )
276
+ RSI = IndicatorFunction(
277
+ expected_columns=["RSI"], functions=[compute_rsi, compute_pandas_ta_rsi]
278
+ )
279
+ STOCH = IndicatorFunction(
280
+ expected_columns=["SLOW_K", "SLOW_D"],
281
+ functions=[compute_stoch, compute_pandas_ta_stoch],
282
+ )
283
+ MFI = IndicatorFunction(
284
+ expected_columns=["MFI"], functions=[compute_mfi, compute_pandas_ta_mfi]
285
+ )
286
+ ROC = IndicatorFunction(
287
+ expected_columns=["ROC_7", "ROC_1", "ROC_30"],
288
+ functions=[compute_roc, compute_pandas_ta_roc],
289
+ )
290
+ CANDLESTOCK_PATTERNS = IndicatorFunction(
291
+ expected_columns=[
292
+ "CDLMORNINGSTAR",
293
+ "CDL3LINESTRIKE",
294
+ "CDL3WHITESOLDIERS",
295
+ "CDLABANDONEDBABY",
296
+ "CDLTASUKIGAP",
297
+ "CDLPIERCING",
298
+ "CDLENGULFING",
299
+ ],
300
+ functions=[compute_patterns],
301
+ )
302
+
303
+ SMA = IndicatorFunction(
304
+ expected_columns=["SMA_50", "SMA_200"],
305
+ functions=[compute_sma, compute_pandas_ta_sma],
306
+ )
307
+
308
+ ADOSC = IndicatorFunction(
309
+ expected_columns=["ADOSC", "ADOSC_SIGNAL"],
310
+ functions=[compute_adosc, compute_pandas_ta_adosc],
311
+ )
312
+
313
+ AD = IndicatorFunction(
314
+ expected_columns=["AD"],
315
+ functions=[compute_ad, compute_pandas_ta_ad],
316
+ )
317
+ OBV = IndicatorFunction(
318
+ expected_columns=["OBV"],
319
+ functions=[compute_obv, compute_pandas_ta_obv],
320
+ )
321
+ ATR = IndicatorFunction(
322
+ expected_columns=["ATR"],
323
+ functions=[compute_atr, compute_pandas_ta_atr],
324
+ )
325
+ NATR = IndicatorFunction(
326
+ expected_columns=["NATR"],
327
+ functions=[compute_natr, compute_pandas_ta_natr],
328
+ )
329
+ TRANGE = IndicatorFunction(
330
+ expected_columns=["TRANGE"],
331
+ functions=[compute_trange, compute_pandas_ta_trange],
332
+ )
333
+
334
+
335
+ def add_indicators(data: pd.DataFrame) -> pd.DataFrame:
336
+ indicators = [ADX, MACD, RSI, STOCH, SMA, ADOSC, AD, OBV, ATR, NATR, TRANGE]
337
+ expected_columns = [c for i in indicators for c in i.expected_columns]
338
+ for indicator in indicators:
339
+ data = pd.concat([data, indicator.call(data)], axis=1)
340
+ if not set(expected_columns).issubset(set(data.columns)):
341
+ raise ValueError(
342
+ f"Expected columns {expected_columns} not found in data columns {data.columns.tolist()}"
343
+ )
344
+ return data