lucifer7210 commited on
Commit
e1f1632
·
verified ·
1 Parent(s): 33c8578

Create technical_indicators.py

Browse files
Files changed (1) hide show
  1. src/technical_indicators.py +956 -0
src/technical_indicators.py ADDED
@@ -0,0 +1,956 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # technical_indicators.py
2
+
3
+ import pandas as pd
4
+ import numpy as np
5
+ from math import sqrt
6
+
7
+ def SMA(ohlc, period=14, column="Close"):
8
+ """Simple Moving Average"""
9
+ return pd.Series(ohlc[column].rolling(window=period).mean(), name=f"SMA_{period}")
10
+
11
+ def SMM(ohlc, period= 9, column= "Close"):
12
+ """
13
+ Simple moving median, an alternative to moving average. SMA, when used to estimate the underlying trend in a time series,
14
+ is susceptible to rare events such as rapid shocks or other anomalies. A more robust estimate of the trend is the simple moving median over n time periods.
15
+ """
16
+
17
+ return pd.Series(
18
+ ohlc[column].rolling(window=period).median(),
19
+ name="{0} period SMM".format(period),
20
+ )
21
+
22
+ def SSMA(ohlc,period = 9, column = "Close",adjust = True):
23
+ """
24
+ Smoothed simple moving average.
25
+
26
+ :param ohlc: data
27
+ :param period: range
28
+ :param column: open/close/high/low column of the DataFrame
29
+ :return: result Series
30
+ """
31
+
32
+ return pd.Series(
33
+ ohlc[column]
34
+ .ewm(ignore_na=False, alpha=1.0 / period, min_periods=0, adjust=adjust)
35
+ .mean(),
36
+ name="{0} period SSMA".format(period),
37
+ )
38
+
39
+ def EMA(ohlc, period=14, column="Close", adjust=True):
40
+ """Exponential Moving Average"""
41
+ return pd.Series(ohlc[column].ewm(span=period, adjust=adjust).mean(), name=f"EMA_{period}")
42
+
43
+ def RSI(ohlc, period=14, column="Close", adjust=True):
44
+ """Relative Strength Index"""
45
+ delta = ohlc[column].diff()
46
+ up, down = delta.copy(), delta.copy()
47
+ up[up < 0] = 0
48
+ down[down > 0] = 0
49
+ _gain = up.ewm(com=period - 1, adjust=adjust).mean()
50
+ _loss = abs(down.ewm(com=period - 1, adjust=adjust).mean())
51
+ RS = _gain / _loss
52
+ return pd.Series(100 - (100 / (1 + RS)), name=f"RSI_{period}")
53
+
54
+
55
+ def DEMA(ohlc,period = 9,column = "Close",adjust = True):
56
+ """
57
+ Double Exponential Moving Average - attempts to remove the inherent lag associated to Moving Averages
58
+ by placing more weight on recent values. The name suggests this is achieved by applying a double exponential
59
+ smoothing which is not the case. The name double comes from the fact that the value of an EMA (Exponential Moving Average) is doubled.
60
+ To keep it in line with the actual data and to remove the lag the value 'EMA of EMA' is subtracted from the previously doubled EMA.
61
+ Because EMA(EMA) is used in the calculation, DEMA needs 2 * period -1 samples to start producing values in contrast to the period
62
+ samples needed by a regular EMA
63
+ """
64
+
65
+ DEMA = (
66
+ 2 * EMA(ohlc, period)
67
+ - EMA(ohlc, period).ewm(span=period, adjust=adjust).mean()
68
+ )
69
+
70
+ return pd.Series(DEMA, name="{0} period DEMA".format(period))
71
+
72
+ def TEMA(ohlc, period = 9, adjust = True):
73
+ """
74
+ Triple exponential moving average - attempts to remove the inherent lag associated to Moving Averages by placing more weight on recent values.
75
+ The name suggests this is achieved by applying a triple exponential smoothing which is not the case. The name triple comes from the fact that the
76
+ value of an EMA (Exponential Moving Average) is triple.
77
+ To keep it in line with the actual data and to remove the lag the value 'EMA of EMA' is subtracted 3 times from the previously tripled EMA.
78
+ Finally 'EMA of EMA of EMA' is added.
79
+ Because EMA(EMA(EMA)) is used in the calculation, TEMA needs 3 * period - 2 samples to start producing values in contrast to the period samples
80
+ needed by a regular EMA.
81
+ """
82
+
83
+ triple_ema = 3 * EMA(ohlc, period)
84
+ ema_ema_ema = (
85
+ EMA(ohlc, period)
86
+ .ewm(ignore_na=False, span=period, adjust=adjust)
87
+ .mean()
88
+ .ewm(ignore_na=False, span=period, adjust=adjust)
89
+ .mean()
90
+ )
91
+
92
+ TEMA = (
93
+ triple_ema
94
+ - 3 * EMA(ohlc, period).ewm(span=period, adjust=adjust).mean()
95
+ + ema_ema_ema
96
+ )
97
+
98
+ return pd.Series(TEMA, name="{0} period TEMA".format(period))
99
+
100
+ def TRIMA(ohlc, period = 18,column="Close"):
101
+ """
102
+ The Triangular Moving Average (TRIMA) [also known as TMA] represents an average of prices,
103
+ but places weight on the middle prices of the time period.
104
+ The calculations double-smooth the data using a window width that is one-half the length of the series.
105
+ source: https://www.thebalance.com/triangular-moving-average-tma-description-and-uses-1031203
106
+ """
107
+
108
+ weights = np.concatenate([np.arange(1, period // 2 + 1), np.arange(period // 2, 0, -1)])
109
+ weights = weights / weights.sum()
110
+ def triangular(x):
111
+ return np.dot(x, weights)
112
+ return pd.Series(ohlc[column].rolling(period).apply(triangular, raw=True), name=f"TRIMA_{period}")
113
+
114
+ def TRIX(ohlc,period = 20,column = "Close",adjust = True):
115
+ """
116
+ The TRIX indicator calculates the rate of change of a triple exponential moving average.
117
+ The values oscillate around zero. Buy/sell signals are generated when the TRIX crosses above/below zero.
118
+ A (typically) 9 period exponential moving average of the TRIX can be used as a signal line.
119
+ A buy/sell signals are generated when the TRIX crosses above/below the signal line and is also above/below zero.
120
+
121
+ The TRIX was developed by Jack K. Hutson, publisher of Technical Analysis of Stocks & Commodities magazine,
122
+ and was introduced in Volume 1, Number 5 of that magazine.
123
+ """
124
+
125
+ data = ohlc[column]
126
+
127
+ def _ema(data, period, adjust):
128
+ return pd.Series(data.ewm(span=period, adjust=adjust).mean())
129
+
130
+ m = _ema(_ema(_ema(data, period, adjust), period, adjust), period, adjust)
131
+
132
+ return pd.Series(100 * (m.diff() / m), name="{0} period TRIX".format(period))
133
+
134
+ def VAMA(ohlcv,period = 8, column = "Close",colvol="Volume"):
135
+ """
136
+ Volume Adjusted Moving Average
137
+ """
138
+
139
+ vp = ohlcv[colvol] * ohlcv[column]
140
+ volsum = ohlcv[colvol].rolling(window=period).mean()
141
+ volRatio = pd.Series(vp / volsum, name="VAMA")
142
+ cumSum = (volRatio * ohlcv[column]).rolling(window=period).sum()
143
+ cumDiv = volRatio.rolling(window=period).sum()
144
+
145
+ return pd.Series(cumSum / cumDiv, name="{0} period VAMA".format(period))
146
+
147
+ def ER(ohlc, period=10, column="Close"):
148
+ """Efficiency Ratio"""
149
+ change = ohlc[column].diff(period).abs()
150
+ total_change = ohlc[column].diff().abs().rolling(window=period).sum()
151
+ return pd.Series(change / total_change, name="ER")
152
+
153
+ def KAMA(ohlc, er=10, ema_fast=2, ema_slow=30, period=20, column="Close", adjust=True):
154
+ """Kaufman Adaptive Moving Average"""
155
+ efficiency_ratio = ER(ohlc, er, column=column)
156
+ fast_alpha = 2 / (ema_fast + 1)
157
+ slow_alpha = 2 / (ema_slow + 1)
158
+ smoothing_constant = (efficiency_ratio * (fast_alpha - slow_alpha) + slow_alpha) ** 2
159
+
160
+ sma = ohlc[column].rolling(window=period).mean()
161
+ kama = [float("nan")] * len(ohlc)
162
+
163
+ # Build KAMA line
164
+ for i in range(period, len(ohlc)):
165
+ if np.isnan(kama[i - 1]):
166
+ kama[i] = sma.iloc[i]
167
+ else:
168
+ kama[i] = kama[i - 1] + smoothing_constant.iloc[i] * (ohlc[column].iloc[i] - kama[i - 1])
169
+
170
+ return pd.Series(kama, index=ohlc.index, name=f"{period} period KAMA")
171
+
172
+ def ZLEMA(ohlc,period = 26,adjust = True,column = "Close"):
173
+ """ZLEMA is an abbreviation of Zero Lag Exponential Moving Average. It was developed by John Ehlers and Rick Way.
174
+ ZLEMA is a kind of Exponential moving average but its main idea is to eliminate the lag arising from the very nature of the moving averages
175
+ and other trend following indicators. As it follows price closer, it also provides better price averaging and responds better to price swings."""
176
+
177
+ lag = int((period - 1) / 2)
178
+
179
+ ema = pd.Series(
180
+ (ohlc[column] + (ohlc[column].diff(lag))),
181
+ name="{0} period ZLEMA.".format(period),
182
+ )
183
+
184
+ zlema = pd.Series(
185
+ ema.ewm(span=period, adjust=adjust).mean(),
186
+ name="{0} period ZLEMA".format(period),
187
+ )
188
+
189
+ return zlema
190
+
191
+ def WMA(ohlc, period=14, column="Close"):
192
+ """Weighted Moving Average"""
193
+ weights = np.arange(1, period + 1)
194
+ def linear(w):
195
+ def _inner(x):
196
+ return np.dot(x, w) / w.sum()
197
+ return _inner
198
+ close = ohlc[column]
199
+ return pd.Series(close.rolling(period, min_periods=period).apply(linear(weights), raw=True), name=f"WMA_{period}")
200
+
201
+ def HMA(ohlc, period=20, column="Close"):
202
+ """Hull Moving Average"""
203
+ half_length = int(period / 2)
204
+ sqrt_length = int(sqrt(period))
205
+ wma_half = WMA(ohlc, half_length, column)
206
+ wma_full = WMA(ohlc, period, column)
207
+ hma = WMA(pd.DataFrame({column: 2 * wma_half - wma_full}), sqrt_length, column)
208
+ return hma.rename(f"HMA_{period}")
209
+
210
+ def EVWMA(ohlcv, period=20, high="High", low="Low", close="Close", colvol="Volume", adjust=True):
211
+ """Ehlers Volatility Weighted Moving Average"""
212
+ tr = pd.concat([
213
+ ohlcv[high] - ohlcv[low],
214
+ abs(ohlcv[high] - ohlcv[close].shift()),
215
+ abs(ohlcv[low] - ohlcv[close].shift())
216
+ ], axis=1).max(axis=1)
217
+ vol_weight = ohlcv[colvol] / tr.rolling(window=period).mean()
218
+ return pd.Series((vol_weight * ohlcv[close]).ewm(span=period, adjust=adjust).mean(), name="EVWMA")
219
+
220
+ def TP(ohlc,high="High",low="Low",column="Close"):
221
+ """Typical Price refers to the arithmetic average of the high, low, and closing prices for a given period."""
222
+
223
+ return pd.Series((ohlc[high] + ohlc[low] + ohlc[column]) / 3, name="TP")
224
+
225
+ def VWAP(ohlcv,colvol="Volume"):
226
+ """
227
+ The volume weighted average price (VWAP) is a trading benchmark used especially in pension plans.
228
+ VWAP is calculated by adding up the dollars traded for every transaction (price multiplied by number of shares traded) and then dividing
229
+ by the total shares traded for the day.
230
+ """
231
+
232
+ return pd.Series(
233
+ ((ohlcv[colvol] * TP(ohlcv,open="Open",close="Close",high="High",low="Low")).cumsum()) / ohlcv[colvol].cumsum(),
234
+ name="VWAP.",
235
+ )
236
+
237
+ def FRAMA(ohlc, period=20, batch=10, column="Close", adjust=True):
238
+ """Fractal Adaptive Moving Average"""
239
+ assert period % 2 == 0, "FRAMA period must be even"
240
+ c = ohlc[column].copy()
241
+ window = batch * 2
242
+ hh = c.rolling(batch).max()
243
+ ll = c.rolling(batch).min()
244
+ n1 = (hh - ll) / batch
245
+ n2 = n1.shift(batch)
246
+ hh2 = c.rolling(window).max()
247
+ ll2 = c.rolling(window).min()
248
+ n3 = (hh2 - ll2) / window
249
+ D = (np.log(n1 + n2) - np.log(n3)) / np.log(2)
250
+ alp = np.exp(-4.6 * (D - 1))
251
+ alp = np.clip(alp, 0.01, 1).values
252
+ filt = np.zeros(len(c))
253
+ for i in range(len(c)):
254
+ if i < window:
255
+ filt[i] = c.iloc[i]
256
+ else:
257
+ filt[i] = c.iloc[i] * alp[i] + (1 - alp[i]) * filt[i - 1]
258
+ return pd.Series(filt, index=ohlc.index, name=f"FRAMA_{period}")
259
+
260
+ def MACD(ohlc, period_fast = 12, period_slow = 26,signal = 9,column = "Close",adjust = True):
261
+ """
262
+ MACD, MACD Signal and MACD difference.
263
+ The MACD Line oscillates above and below the zero line, which is also known as the centerline.
264
+ These crossovers signal that the 12-day EMA has crossed the 26-day EMA. The direction, of course, depends on the direction of the moving average cross.
265
+ Positive MACD indicates that the 12-day EMA is above the 26-day EMA. Positive values increase as the shorter EMA diverges further from the longer EMA.
266
+ This means upside momentum is increasing. Negative MACD values indicates that the 12-day EMA is below the 26-day EMA.
267
+ Negative values increase as the shorter EMA diverges further below the longer EMA. This means downside momentum is increasing.
268
+
269
+ Signal line crossovers are the most common MACD signals. The signal line is a 9-day EMA of the MACD Line.
270
+ As a moving average of the indicator, it...curs when the MACD turns up and crosses above the signal line.
271
+ A bearish crossover occurs when the MACD turns down and crosses below the signal line.
272
+ """
273
+
274
+ EMA_fast = pd.Series(
275
+ ohlc[column].ewm(ignore_na=False, span=period_fast, adjust=adjust).mean(),
276
+ name="EMA_fast",
277
+ )
278
+ EMA_slow = pd.Series(
279
+ ohlc[column].ewm(ignore_na=False, span=period_slow, adjust=adjust).mean(),
280
+ name="EMA_slow",
281
+ )
282
+ MACD = pd.Series(EMA_fast - EMA_slow, name="MACD")
283
+ MACD_signal = pd.Series(
284
+ MACD.ewm(ignore_na=False, span=signal, adjust=adjust).mean(), name="SIGNAL"
285
+ )
286
+
287
+ return pd.concat([MACD, MACD_signal], axis=1)
288
+
289
+
290
+ def BOLLINGER(ohlc, period=20, dev=2, column="Close"):
291
+ """Bollinger Bands"""
292
+ sma = ohlc[column].rolling(window=period).mean()
293
+ std = ohlc[column].rolling(window=period).std()
294
+ upper_band = sma + std * dev
295
+ lower_band = sma - std * dev
296
+ return pd.DataFrame({"BB_UPPER": upper_band, "BB_LOWER": lower_band})
297
+
298
+ def STOCH(ohlc, period = 14,close="Close",high="High",low="Low"):
299
+ """Stochastic oscillator %K
300
+ The stochastic oscillator is a momentum indicator comparing the closing price of a security
301
+ to the range of its prices over a certain period of time.
302
+ The sensitivity of the oscillator to market movements is reducible by adjusting that time
303
+ period or by taking a moving average of the result.
304
+ """
305
+
306
+ highest_high = ohlc[high].rolling(center=False, window=period).max()
307
+ lowest_low = ohlc[low].rolling(center=False, window=period).min()
308
+
309
+ STOCH = pd.Series(
310
+ (ohlc[close] - lowest_low) / (highest_high - lowest_low) * 100,
311
+ name="{0} period STOCH %K".format(period),
312
+ )
313
+
314
+ return STOCH
315
+
316
+ def STOCHD(ohlc, period = 3, stoch_period = 14,close="Close",high="High",low="Low"):
317
+ """Stochastic oscillator %D
318
+ STOCH%D is a 3 period simple moving average of %K.
319
+ """
320
+
321
+ return pd.Series(
322
+ STOCH(ohlc, period = stoch_period,close=close,high=high,low=low).rolling(center=False, window=period).mean(),
323
+ name="{0} period STOCH %D.".format(period),
324
+ )
325
+
326
+ def STOCHRSI(ohlc, rsi_period=14, stoch_period=14, column="Close", adjust=True):
327
+ """Stochastic RSI"""
328
+ rsi = RSI(ohlc, rsi_period, column, adjust)
329
+ min_val = rsi.rolling(window=stoch_period).min()
330
+ max_val = rsi.rolling(window=stoch_period).max()
331
+ stochrsi = 100 * (rsi - min_val) / (max_val - min_val)
332
+ return pd.Series(stochrsi, name=f"STOCHRSI_{rsi_period}_{stoch_period}")
333
+
334
+ def CMO(ohlc, period=9, factor=100, column="Close", adjust=True):
335
+ """Chande Momentum Oscillator"""
336
+ delta = ohlc[column].diff()
337
+ up = delta.copy()
338
+ down = delta.copy()
339
+ up[up < 0] = 0
340
+ down[down > 0] = 0
341
+ _gain = up.ewm(com=period, adjust=adjust).mean()
342
+ _loss = abs(down.ewm(com=period, adjust=adjust).mean())
343
+ return pd.Series(factor * ((_gain - _loss) / (_gain + _loss)), name="CMO")
344
+
345
+
346
+ def EMV(ohlcv, period=14, high="High", low="Low", colvol="Volume"):
347
+ """Ease of Movement"""
348
+ dm = ((ohlcv[high] + ohlcv[low]) / 2) - ((ohlcv[high].shift() + ohlcv[low].shift()) / 2)
349
+ br = (ohlcv[colvol] / 100000000) / ((ohlcv[high] - ohlcv[low]))
350
+ emv = dm / br
351
+ return pd.Series(emv.rolling(window=period).mean(), name="EMV")
352
+
353
+
354
+ def CHAIKIN(ohlcv, colvol="Volume", column="Close", high="High", low="Low", adjust=True):
355
+ """Chaikin Oscillator"""
356
+ adl = ADL(ohlcv, colvol, column, high, low)
357
+ return pd.Series(adl.ewm(span=3, adjust=adjust).mean() - adl.ewm(span=10, adjust=adjust).mean(), name="CHAIKIN")
358
+
359
+ def ADL(ohlcv, colvol="Volume", column="Close", high="High", low="Low"):
360
+ """Accumulation/Distribution Line"""
361
+ clv = ((ohlcv[column] - ohlcv[low]) - (ohlcv[high] - ohlcv[column])) / (ohlcv[high] - ohlcv[low])
362
+ clv = clv.fillna(0)
363
+ return pd.Series((clv * ohlcv[colvol]).cumsum(), name="ADL")
364
+
365
+ def OBV(ohlcv, column="Close", colvol="Volume"):
366
+ """On-Balance Volume"""
367
+ obv = [0]
368
+ for i in range(1, len(ohlcv)):
369
+ if ohlcv[column].iloc[i] > ohlcv[column].iloc[i - 1]:
370
+ obv.append(obv[-1] + ohlcv[colvol].iloc[i])
371
+ elif ohlcv[column].iloc[i] < ohlcv[column].iloc[i - 1]:
372
+ obv.append(obv[-1] - ohlcv[colvol].iloc[i])
373
+ else:
374
+ obv.append(obv[-1])
375
+ return pd.Series(obv, index=ohlcv.index, name="OBV")
376
+
377
+
378
+
379
+ def ADX(ohlc, period=14, high="High", low="Low", close="Close", adjust=True):
380
+ """Average Directional Index"""
381
+ tr1 = ohlc[high] - ohlc[low]
382
+ tr2 = abs(ohlc[high] - ohlc[close].shift())
383
+ tr3 = abs(ohlc[low] - ohlc[close].shift())
384
+ tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
385
+ atr = tr.ewm(span=period, min_periods=period).mean()
386
+
387
+ up_diff = ohlc[high].diff()
388
+ down_diff = ohlc[low].diff()
389
+ plus_dm = pd.Series(np.where((up_diff > down_diff) & (up_diff > 0), up_diff, 0), name="plus_dm")
390
+ minus_dm = pd.Series(np.where((down_diff > up_diff) & (down_diff > 0), down_diff, 0), name="minus_dm")
391
+
392
+ plus_di = 100 * (plus_dm.ewm(span=period, min_periods=period).mean() / atr)
393
+ minus_di = 100 * (minus_dm.ewm(span=period, min_periods=period).mean() / atr)
394
+ dx = 100 * abs(plus_di - minus_di) / (plus_di + minus_di)
395
+ adx = dx.ewm(span=period, min_periods=period).mean()
396
+ return pd.Series(adx, name=f"ADX_{period}")
397
+
398
+ def EFI(ohlc, period=13, column="Close", colvol="Volume", adjust=True):
399
+ """Elder's Force Index"""
400
+ fi1 = pd.Series(ohlc[colvol] * ohlc[column].diff())
401
+ return pd.Series(fi1.ewm(ignore_na=False, min_periods=9, span=10, adjust=adjust).mean(), name="EFI")
402
+
403
+
404
+ def WOBV(ohlcv, column="Close", colvol="Volume"):
405
+ """Weighted On-Balance Volume"""
406
+ obv = [0]
407
+ for i in range(1, len(ohlcv)):
408
+ delta = ohlcv[column].iloc[i] - ohlcv[column].iloc[i - 1]
409
+ obv.append(obv[-1] + delta * ohlcv[colvol].iloc[i])
410
+ return pd.Series(obv, index=ohlcv.index, name="WOBV")
411
+
412
+
413
+ def DMI(ohlc, period=14, high="High", low="Low", column="Close"):
414
+ """Directional Movement Index"""
415
+ up_diff = ohlc[high].diff()
416
+ down_diff = ohlc[low].diff()
417
+ plus_dm = pd.Series(np.where((up_diff > down_diff) & (up_diff > 0), up_diff, 0), name="plus_dm")
418
+ minus_dm = pd.Series(np.where((down_diff > up_diff) & (down_diff > 0), down_diff, 0), name="minus_dm")
419
+ tr = pd.concat([ohlc[high] - ohlc[low], abs(ohlc[high] - ohlc[column].shift()), abs(ohlc[low] - ohlc[column].shift())], axis=1).max(axis=1)
420
+ atr = tr.ewm(span=period, min_periods=period).mean()
421
+ plus_di = 100 * (plus_dm.ewm(span=period, min_periods=period).mean() / atr)
422
+ minus_di = 100 * (minus_dm.ewm(span=period, min_periods=period).mean() / atr)
423
+ return pd.DataFrame({"+DI": plus_di, "-DI": minus_di})
424
+
425
+ def CFI(ohlcv, column="Close", colvol="Volume", adjust=True):
426
+ """Cumulative Force Index"""
427
+ fi1 = pd.Series(ohlcv[colvol] * ohlcv[column].diff())
428
+ cfi = pd.Series(fi1.ewm(ignore_na=False, min_periods=9, span=10, adjust=adjust).mean(), name="CFI")
429
+ return cfi.cumsum()
430
+
431
+ def EBBP(ohlc, period=13, high="High", low="Low", column="Close", adjust=True):
432
+ """Elder Bull Power / Bear Power"""
433
+ ema = ohlc[column].ewm(span=period, adjust=adjust).mean()
434
+ bull_power = ohlc[high] - ema
435
+ bear_power = ohlc[low] - ema
436
+ return pd.DataFrame({"Bull": bull_power, "Bear": bear_power}, index=ohlc.index)
437
+
438
+ def ROC(ohlc, period=10, column="Close"):
439
+ """Rate of Change"""
440
+ return pd.Series(ohlc[column].pct_change(period) * 100, name=f"ROC_{period}")
441
+
442
+
443
+ def CCI(ohlc, period=20, high="High", low="Low", close="Close"):
444
+ """Commodity Channel Index"""
445
+ tp = (ohlc[high] + ohlc[low] + ohlc[close]) / 3
446
+ sma = tp.rolling(window=period).mean()
447
+ mean_deviation = tp.rolling(window=period).apply(lambda x: np.fabs(x - x.mean()).mean())
448
+ cci = (tp - sma) / (0.015 * mean_deviation)
449
+ return pd.Series(cci, name=f"CCI_{period}")
450
+
451
+ def COPP(ohlc, adjust = True):
452
+ """The Coppock Curve is a momentum indicator, it signals buying opportunities when the indicator moved from negative territory to positive territory."""
453
+
454
+ roc1 = ROC(ohlc, 14)
455
+ roc2 = ROC(ohlc, 11)
456
+
457
+ return pd.Series(
458
+ (roc1 + roc2).ewm(span=10, min_periods=9, adjust=adjust).mean(),
459
+ name="Coppock Curve",
460
+ )
461
+
462
+ def VBM(ohlc, period=14, std_dev=2, column="Close"):
463
+ """Volatility-Based Momentum"""
464
+ volatility = ohlc[column].pct_change().rolling(window=period).std() * np.sqrt(period)
465
+ momentum = ohlc[column].pct_change(period)
466
+ return pd.Series(momentum / volatility, name="VBM")
467
+
468
+
469
+ def QSTICK(ohlc, period=10, open="Open", close="Close"):
470
+ """Q Stick Indicator"""
471
+ return pd.Series(ohlc[close].pct_change(period) - ohlc[open].pct_change(period), name="QSTICK")
472
+
473
+ def WTO(ohlc, channel_length=10, average_length=21, adjust=True):
474
+ """Wave Trend Oscillator"""
475
+ ap = (ohlc["High"] + ohlc["Low"] + ohlc["Close"]) / 3
476
+ esa = ap.ewm(span=average_length, adjust=adjust).mean()
477
+ d = pd.Series((ap - esa).abs().ewm(span=channel_length, adjust=adjust).mean(), name="d")
478
+ ci = (ap - esa) / (0.015 * d)
479
+ wt1 = pd.Series(ci.ewm(span=average_length, adjust=adjust).mean(), name="WT1.")
480
+ wt2 = pd.Series(wt1.rolling(window=4).mean(), name="WT2.")
481
+ return pd.concat([wt1, wt2], axis=1)
482
+
483
+ def SAR(ohlc, af = 0.02, amax = 0.2,high="High",low="Low"):
484
+ """SAR stands for "stop and reverse," which is the actual indicator used in the system.
485
+ SAR trails price as the trend extends over time. The indicator is below prices when prices are rising and above prices when prices are falling.
486
+ In this regard, the indicator stops and reverses when the price trend reverses and breaks above or below the indicator."""
487
+ high1, low1 = ohlc[high], ohlc[low]
488
+
489
+ # Starting values
490
+ sig0, xpt0, af0 = True, high1[0], af
491
+ _sar = [low1[0] - (high1 - low1).std()]
492
+
493
+ for i in range(1, len(ohlc)):
494
+ sig1, xpt1, af1 = sig0, xpt0, af0
495
+
496
+ lmin = min(low1[i - 1], low1[i])
497
+ lmax = max(high1[i - 1], high1[i])
498
+
499
+ if sig1:
500
+ sig0 = low1[i] > _sar[-1]
501
+ xpt0 = max(lmax, xpt1)
502
+ else:
503
+ sig0 = high1[i] >= _sar[-1]
504
+ xpt0 = min(lmin, xpt1)
505
+
506
+ if sig0 == sig1:
507
+ sari = _sar[-1] + (xpt1 - _sar[-1]) * af1
508
+ af0 = min(amax, af1 + af)
509
+
510
+ if sig0:
511
+ af0 = af0 if xpt0 > xpt1 else af1
512
+ sari = min(sari, lmin)
513
+ else:
514
+ af0 = af0 if xpt0 < xpt1 else af1
515
+ sari = max(sari, lmax)
516
+ else:
517
+ af0 = af
518
+ sari = xpt0
519
+
520
+ _sar.append(sari)
521
+
522
+ return pd.Series(_sar, index=ohlc.index)
523
+
524
+ def PSAR(ohlc, iaf = 0.02, maxaf = 0.2,high="High",low="Low",close="Close"):
525
+ """
526
+ The parabolic SAR indicator, developed by J. Wells Wilder, is used by traders to determine trend direction and potential reversals in price.
527
+ The indicator uses a trailing stop and reverse method called "SAR," or stop and reverse, to identify suitable exit and entry points.
528
+ Traders also refer to the indicator as the parabolic stop and reverse, parabolic SAR, or PSAR.
529
+ https://www.investopedia.com/terms/p/parabolicindicator.asp
530
+ https://virtualizedfrog.wordpress.com/2014/12/09/parabolic-sar-implementation-in-python/
531
+ """
532
+
533
+ length = len(ohlc)
534
+ high1, low1, close1 = ohlc[high], ohlc[low], ohlc[close]
535
+ psar = close1[0 : len(close1)]
536
+ psarbull = [None] * length
537
+ psarbear = [None] * length
538
+ bull = True
539
+ af = iaf
540
+ hp = high1[0]
541
+ lp = low1[0]
542
+
543
+ for i in range(2, length):
544
+ if bull:
545
+ psar[i] = psar[i - 1] + af * (hp - psar[i - 1])
546
+ else:
547
+ psar[i] = psar[i - 1] + af * (lp - psar[i - 1])
548
+
549
+ reverse = False
550
+
551
+ if bull:
552
+ if low1[i] < psar[i]:
553
+ bull = False
554
+ reverse = True
555
+ psar[i] = hp
556
+ lp = low1[i]
557
+ af = iaf
558
+ else:
559
+ if high1[i] > psar[i]:
560
+ bull = True
561
+ reverse = True
562
+ psar[i] = lp
563
+ hp = high1[i]
564
+ af = iaf
565
+
566
+ if not reverse:
567
+ if bull:
568
+ if high1[i] > hp:
569
+ hp = high1[i]
570
+ af = min(af + iaf, maxaf)
571
+ if low1[i - 1] < psar[i]:
572
+ psar[i] = low1[i - 1]
573
+ if low1[i - 2] < psar[i]:
574
+ psar[i] = low1[i - 2]
575
+ else:
576
+ if low1[i] < lp:
577
+ lp = low1[i]
578
+ af = min(af + iaf, maxaf)
579
+ if high1[i - 1] > psar[i]:
580
+ psar[i] = high1[i - 1]
581
+ if high1[i - 2] > psar[i]:
582
+ psar[i] = high1[i - 2]
583
+
584
+ if bull:
585
+ psarbull[i] = psar[i]
586
+ else:
587
+ psarbear[i] = psar[i]
588
+
589
+ psar = pd.Series(psar, name="psar", index=ohlc.index)
590
+ psarbear = pd.Series(psarbear, name="psarbear", index=ohlc.index)
591
+ psarbull = pd.Series(psarbull, name="psarbull", index=ohlc.index)
592
+
593
+ psar_df = pd.concat([psar, psarbull, psarbear], axis=1)
594
+
595
+ return psar_df
596
+
597
+ def KST(ohlc, r1=10, r2=15, r3=20, r4=30, column="Close"):
598
+ """Know Sure Thing"""
599
+ r1 = ROC(ohlc, r1, column).rolling(window=10).mean()
600
+ r2 = ROC(ohlc, r2, column).rolling(window=10).mean()
601
+ r3 = ROC(ohlc, r3, column).rolling(window=10).mean()
602
+ r4 = ROC(ohlc, r4, column).rolling(window=15).mean()
603
+ k = pd.Series((r1 * 1) + (r2 * 2) + (r3 * 3) + (r4 * 4), name="KST")
604
+ signal = pd.Series(k.rolling(window=10).mean(), name="signal")
605
+ return pd.concat([k, signal], axis=1)
606
+
607
+ def TSI(ohlc,long = 25,short = 13,signal = 13,column = "Close",adjust = True):
608
+ """True Strength Index (TSI) is a momentum oscillator based on a double smoothing of price changes."""
609
+
610
+ ## Double smoother price change
611
+ momentum = pd.Series(ohlc[column].diff()) ## 1 period momentum
612
+ _EMA25 = pd.Series(
613
+ momentum.ewm(span=long, min_periods=long - 1, adjust=adjust).mean(),
614
+ name="_price change EMA25",
615
+ )
616
+ _DEMA13 = pd.Series(
617
+ _EMA25.ewm(span=short, min_periods=short - 1, adjust=adjust).mean(),
618
+ name="_price change double smoothed DEMA13",
619
+ )
620
+
621
+ ## Double smoothed absolute price change
622
+ absmomentum = pd.Series(ohlc[column].diff().abs())
623
+ _aEMA25 = pd.Series(
624
+ absmomentum.ewm(span=long, min_periods=long - 1, adjust=adjust).mean(),
625
+ name="_abs_price_change EMA25",
626
+ )
627
+ _aDEMA13 = pd.Series(
628
+ _aEMA25.ewm(span=short, min_periods=short - 1, adjust=adjust).mean(),
629
+ name="_abs_price_change double smoothed DEMA13",
630
+ )
631
+
632
+ TSI = pd.Series((_DEMA13 / _aDEMA13) * 100, name="TSI")
633
+ signal = pd.Series(
634
+ TSI.ewm(span=signal, min_periods=signal - 1, adjust=adjust).mean(),
635
+ name="signal",
636
+ )
637
+
638
+ return pd.concat([TSI, signal], axis=1)
639
+
640
+ def FISH(ohlc, period=10, adjust=True, high="High", low="Low"):
641
+ """Fisher Transform"""
642
+ med = (ohlc[high] + ohlc[low]) / 2
643
+ ndaylow = med.rolling(window=period).min()
644
+ ndayhigh = med.rolling(window=period).max()
645
+ raw = (2 * ((med - ndaylow) / (ndayhigh - ndaylow))) - 1
646
+ smooth = raw.ewm(span=5, adjust=adjust).mean()
647
+ _smooth = smooth.fillna(0)
648
+ return pd.Series(
649
+ np.log((1 + _smooth) / (1 - _smooth)).ewm(span=3, adjust=adjust).mean(),
650
+ name=f"FISH_{period}"
651
+ )
652
+
653
+ def ICHIMOKU(ohlc, kijun_period=26, tenkan_period=9, senkou_period=52, chikou_period=26,
654
+ high="High", low="Low", close="Close", open="Open"):
655
+ """Ichimoku Cloud"""
656
+ tenkan_sen = (ohlc[high].rolling(window=tenkan_period).max() +
657
+ ohlc[low].rolling(window=tenkan_period).min()) / 2
658
+ kijun_sen = (ohlc[high].rolling(window=kijun_period).max() +
659
+ ohlc[low].rolling(window=kijun_period).min()) / 2
660
+ senkou_span_a = pd.Series(((tenkan_sen + kijun_sen) / 2).shift(kijun_period), name="SENKOU_A")
661
+ senkou_span_b = pd.Series(((ohlc[high].rolling(window=senkou_period).max() +
662
+ ohlc[low].rolling(window=senkou_period).min()) / 2).shift(kijun_period), name="SENKOU_B")
663
+ chikou_span = pd.Series(ohlc[close].shift(-chikou_period), name="CHIKOU")
664
+ return pd.DataFrame({
665
+ "TENKAN": tenkan_sen,
666
+ "KIJUN": kijun_sen,
667
+ "SENKOU_A": senkou_span_a,
668
+ "SENKOU_B": senkou_span_b,
669
+ "CHIKOU": chikou_span
670
+ })
671
+
672
+
673
+ def DC(ohlc, period=20, high="High", low="Low", close="Close", adjust=True):
674
+ """Donchian Channels"""
675
+ upper = ohlc[high].rolling(window=period).max()
676
+ lower = ohlc[low].rolling(window=period).min()
677
+ middle = (upper + lower) / 2
678
+ return pd.DataFrame({"DC_U": upper, "DC_L": lower, "DC_M": middle})
679
+
680
+
681
+
682
+ def MFI(ohlc, period=14, high="High", low="Low", close="Close", colvol="Volume"):
683
+ """Money Flow Index"""
684
+ tp = TP(ohlc, high=high, low=low, column=close)
685
+ rmf = tp * ohlc[colvol] # Raw Money Flow
686
+ mf_sign = np.sign(tp.diff()) # Positive or negative money flow
687
+ pos_mf = np.where(mf_sign == 1, rmf, 0)
688
+ neg_mf = np.where(mf_sign == -1, rmf, 0)
689
+
690
+ pos_mf_sum = pd.Series(pos_mf).rolling(window=period).sum()
691
+ neg_mf_sum = pd.Series(neg_mf).rolling(window=period).sum()
692
+
693
+ mfratio = pos_mf_sum / neg_mf_sum
694
+ mfi = 100 - (100 / (1 + mfratio))
695
+
696
+ return pd.Series(mfi, name=f"{period} period MFI")
697
+
698
+ def MOM(ohlc, period = 10, column = "Close"):
699
+ """Market momentum is measured by continually taking price differences for a fixed time interval.
700
+ To construct a 10-day momentum line, simply subtract the closing price 10 days ago from the last closing price.
701
+ This positive or negative value is then plotted around a zero line."""
702
+
703
+ return pd.Series(ohlc[column].diff(period), name="MOM".format(period))
704
+
705
+ def DYMI(ohlc, column = "Close", adjust = True):
706
+ """
707
+ The Dynamic Momentum Index is a variable term RSI. The RSI term varies from 3 to 30. The variable
708
+ time period makes the RSI more responsive to short-term moves. The more volatile the price is,
709
+ the shorter the time period is. It is interpreted in the same way as the RSI, but provides signals earlier.
710
+ Readings below 30 are considered oversold, and levels over 70 are considered overbought. The indicator
711
+ oscillates between 0 and 100.
712
+ https://www.investopedia.com/terms/d/dynamicmomentumindex.asp
713
+ """
714
+
715
+ def _get_time(close):
716
+ # Value available from 14th period
717
+ sd = close.rolling(5).std()
718
+ asd = sd.rolling(10).mean()
719
+ v = sd / asd
720
+ t = 14 / v.round()
721
+ t[t.isna()] = 0
722
+ t = t.map(lambda x: int(min(max(x, 5), 30)))
723
+ return t
724
+
725
+ def _dmi(index):
726
+ time = t.iloc[index]
727
+ if (index - time) < 0:
728
+ subset = ohlc.iloc[0:index]
729
+ else:
730
+ subset = ohlc.iloc[(index - time) : index]
731
+ return RSI(subset, period=time, column = column,adjust=adjust).values[-1]
732
+
733
+ dates = pd.Series(ohlc.index)
734
+ periods = pd.Series(data=range(14, len(dates)), index=ohlc.index[14:].values)
735
+ t = _get_time(ohlc[column])
736
+ return periods.map(lambda x: _dmi(x))
737
+
738
+ def VPT(ohlcv, colvol="Volume", column="Close", open="Open", high="High", low="Low"):
739
+ """Volume Price Trend"""
740
+ hilow = (ohlcv[high] - ohlcv[low]) * 100
741
+ openclose = (ohlcv[column] - ohlcv[open]) * 100
742
+ vol = ohlcv[colvol] / hilow
743
+ spreadvol = (openclose * vol).cumsum()
744
+ vpt = spreadvol + spreadvol
745
+ return pd.Series(vpt, name="VPT")
746
+
747
+ def FVE(ohlcv, period=22, factor=0.3, colvol="Volume", column="Close", open="Open", high="High", low="Low"):
748
+ """Fractal Volume Efficiency"""
749
+ mf = (ohlcv[column] - ((ohlcv[high] + ohlcv[low]) / 2))
750
+ smav = ohlcv[column].rolling(window=period).mean()
751
+ vol_shift = pd.Series(np.where(mf > factor * ohlcv[column] / 100,
752
+ ohlcv[colvol],
753
+ np.where(mf < -factor * ohlcv[column] / 100,
754
+ -ohlcv[colvol], 0)),
755
+ index=ohlcv.index)
756
+ _sum = vol_shift.rolling(window=period).sum()
757
+ return pd.Series((_sum / smav) / period * 100, name="FVE")
758
+
759
+ def PPO(ohlcv, fast=12, slow=26, signal=9, column="Close", colvol="Volume", adjust=True):
760
+ """Price Percentage Oscillator"""
761
+ _fast = ohlcv[column].ewm(span=fast, adjust=adjust).mean()
762
+ _slow = ohlcv[column].ewm(span=slow, adjust=adjust).mean()
763
+ ppo = pd.Series(((_fast - _slow) / _slow) * 100, name="PPO")
764
+ signal_line = ppo.ewm(span=signal, adjust=adjust).mean()
765
+ histogram = pd.Series(ppo - signal_line, name="PPO_histo")
766
+ return pd.DataFrame({"PPO": ppo, "PPO_signal": signal_line, "PPO_histo": histogram})
767
+
768
+ def VW_MACD(ohlcv, period_fast=12, period_slow=26, signal=9, column="Close", colvol="Volume", adjust=True):
769
+ """Volume Weighted MACD"""
770
+ vp = (ohlcv[column] * ohlcv[colvol]).ewm(span=period_fast, adjust=adjust).mean()
771
+ vslow = (ohlcv[column] * ohlcv[colvol]).ewm(span=period_slow, adjust=adjust).mean()
772
+ vfast = (ohlcv[column] * ohlcv[colvol]).ewm(span=period_fast, adjust=adjust).mean()
773
+ macd = pd.Series(vp - vslow, name="VW_MACD")
774
+ signal_line = macd.ewm(span=signal, adjust=adjust).mean()
775
+ return pd.DataFrame({"VW_MACD": macd, "Signal": signal_line})
776
+
777
+
778
+ def AO(ohlc, high="High", low="Low"):
779
+ """Awesome Oscillator"""
780
+ median_price = (ohlc[high] + ohlc[low]) / 2
781
+ ao = median_price.rolling(window=5).mean() - median_price.rolling(window=34).mean()
782
+ return pd.Series(ao, name="AO")
783
+
784
+ def MI(ohlc, period=9, adjust=True, high="High", low="Low"):
785
+ """Mass Index"""
786
+ _range = ohlc[high] - ohlc[low]
787
+ EMA9 = _range.ewm(span=period, ignore_na=False, adjust=adjust).mean()
788
+ DEMA9 = EMA9.ewm(span=period, ignore_na=False, adjust=adjust).mean()
789
+ mass = EMA9 / DEMA9
790
+ return pd.Series(mass.rolling(window=25).sum(), name="MI")
791
+
792
+
793
+ def PZO(ohlcv, period=14, column="Close", colvol="Volume", adjust=True):
794
+ """Price Zone Oscillator"""
795
+ pzo = ohlcv[column].pct_change(period)
796
+ return pd.Series(pzo.ewm(span=period, adjust=adjust).mean(), name="PZO")
797
+
798
+ def UO(ohlc, period=14, high="High", low="Low", close="Close", column="Close"):
799
+ """Ultimate Oscillator"""
800
+ bp = ohlc[column] - ohlc[[low, column]].min(axis=1)
801
+ tr = pd.concat([
802
+ ohlc[high] - ohlc[low],
803
+ abs(ohlc[high] - ohlc[close].shift()),
804
+ abs(ohlc[low] - ohlc[close].shift())
805
+ ], axis=1).max(axis=1)
806
+ avg7 = bp.rolling(window=7).sum() / tr.rolling(window=7).sum()
807
+ avg14 = bp.rolling(window=14).sum() / tr.rolling(window=14).sum()
808
+ avg28 = bp.rolling(window=28).sum() / tr.rolling(window=28).sum()
809
+ uo = (avg7 * 4 + avg14 * 2 + avg28) / (4 + 2 + 1)
810
+ return pd.Series(uo * 100, name="UO")
811
+
812
+ def BASP(ohlc, period = 40, adjust = True,colvol="Volume",high="High",low="Low",close="Close"):
813
+ """BASP indicator serves to identify buying and selling pressure."""
814
+
815
+ sp = ohlc[high] - ohlc[close]
816
+ bp = ohlc[close] - ohlc[low]
817
+ spavg = sp.ewm(span=period, adjust=adjust).mean()
818
+ bpavg = bp.ewm(span=period, adjust=adjust).mean()
819
+
820
+ nbp = bp / bpavg
821
+ nsp = sp / spavg
822
+
823
+ varg = ohlc[colvol].ewm(span=period, adjust=adjust).mean()
824
+ nv = ohlc[colvol] / varg
825
+
826
+ nbfraw = pd.Series(nbp * nv, name="Buy.")
827
+ nsfraw = pd.Series(nsp * nv, name="Sell.")
828
+
829
+ return pd.concat([nbfraw, nsfraw], axis=1)
830
+
831
+ def BASPN(ohlcv, period=40, adjust=True, colvol="Volume", high="High", low="Low", close="Close"):
832
+ """Normalized Buyer/Seller Pressure"""
833
+ sp = ohlcv[high] - ohlcv[close]
834
+ bp = ohlcv[close] - ohlcv[low]
835
+ spavg = sp.ewm(span=period, adjust=adjust).mean()
836
+ bpavg = bp.ewm(span=period, adjust=adjust).mean()
837
+ nbp = bp / bpavg
838
+ nsp = sp / spavg
839
+ nbf = pd.Series((nbp * (ohlcv[colvol] / spavg)).ewm(span=20, adjust=adjust).mean(), name="Buy.")
840
+ nsf = pd.Series((nsp * (ohlcv[colvol] / spavg)).ewm(span=20, adjust=adjust).mean(), name="Sell.")
841
+ return pd.DataFrame({"BASPN_Buy": nbf, "BASPN_Sell": nsf})
842
+
843
+ def IFT_RSI(ohlc, rsi_period=5, wma_period=9, column="Close", adjust=True):
844
+ """Inverse Fisher Transform RSI"""
845
+ rsi = RSI(ohlc, rsi_period, column, adjust)
846
+ v1 = pd.Series(0.1 * (rsi - 50), name="v1")
847
+ weights = np.arange(1, wma_period + 1)
848
+ d = (wma_period * (wma_period + 1)) / 2
849
+ _wma = v1.rolling(wma_period, min_periods=wma_period)
850
+ v2 = _wma.apply(lambda x: np.dot(x, weights) / d, raw=True)
851
+ ift = pd.Series(((v2 ** 2 - 1) / (v2 ** 2 + 1)), name="IFT_RSI")
852
+ return ift
853
+
854
+
855
+ def PIVOT(ohlc, open="Open", close="Close", high="High", low="Low"):
856
+ """Classic Pivot Points"""
857
+ df = ohlc.shift()
858
+ pp = pd.Series((df[high] + df[low] + df[close]) / 3, name="pivot")
859
+ r1 = pd.Series(2 * pp - df[low], name="r1")
860
+ r2 = pd.Series(pp + (df[high] - df[low]), name="r2")
861
+ r3 = pd.Series(df[high] + 2 * (pp - df[low]), name="r3")
862
+ s1 = pd.Series(2 * pp - df[high], name="s1")
863
+ s2 = pd.Series(pp - (df[high] - df[low]), name="s2")
864
+ s3 = pd.Series(pp - 2 * (df[high] - df[low]), name="s3")
865
+ return pd.concat([pp, s1, s2, s3, r1, r2, r3], axis=1)
866
+
867
+ def PIVOT_FIB(ohlc, open="Open", close="Close", high="High", low="Low"):
868
+ """Fibonacci Pivot Points"""
869
+ df = ohlc.shift()
870
+ pp = pd.Series((df[high] + df[low] + df[close]) / 3, name="pivot")
871
+ s1 = pd.Series(pp - 0.382 * (df[high] - df[low]), name="s1")
872
+ s2 = pd.Series(pp - 0.618 * (df[high] - df[low]), name="s2")
873
+ s3 = pd.Series(pp - 1.0 * (df[high] - df[low]), name="s3")
874
+ r1 = pd.Series(pp + 0.382 * (df[high] - df[low]), name="r1")
875
+ r2 = pd.Series(pp + 0.618 * (df[high] - df[low]), name="r2")
876
+ r3 = pd.Series(pp + 1.0 * (df[high] - df[low]), name="r3")
877
+ return pd.concat([pp, s1, s2, s3, r1, r2, r3], axis=1)
878
+
879
+ def KC(ohlc, period=20, atr_period=10, kc_mult=2, high="High", low="Low", column="Close", adjust=True):
880
+ """Keltner Channels"""
881
+ tp = (ohlc[high] + ohlc[low] + ohlc[column]) / 3
882
+ kc_middle = tp.ewm(span=period, adjust=adjust).mean()
883
+ tr = pd.concat([
884
+ ohlc[high] - ohlc[low],
885
+ abs(ohlc[high] - ohlc[column].shift()),
886
+ abs(ohlc[low] - ohlc[column].shift())
887
+ ], axis=1).max(axis=1)
888
+ mean_dev = tr.ewm(span=atr_period, adjust=adjust).mean()
889
+ kc_upper = kc_middle + kc_mult * mean_dev
890
+ kc_lower = kc_middle - kc_mult * mean_dev
891
+ return pd.DataFrame({
892
+ "KC_MIDDLE": kc_middle,
893
+ "KC_UPPER": kc_upper,
894
+ "KC_LOWER": kc_lower
895
+ })
896
+
897
+ def APZ(ohlc, period=21, dev_factor=2, column="Close", high="High", low="Low", adjust=True):
898
+ """Adaptive Price Zone"""
899
+ ma = ohlc[column].ewm(span=period, adjust=adjust).mean()
900
+ std = ohlc[column].pct_change().rolling(window=period).std() * dev_factor
901
+ upper_band = ma + std * ohlc[column]
902
+ lower_band = ma - std * ohlc[column]
903
+ return pd.DataFrame({"APZ_UPPER": upper_band, "APZ_LOWER": lower_band})
904
+
905
+ def VZO(ohlc,period = 14,column = "Close",colvol="Volume",adjust = True):
906
+ """VZO uses price, previous price and moving averages to compute its oscillating value.
907
+ It is a leading indicator that calculates buy and sell signals based on oversold / overbought conditions.
908
+ Oscillations between the 5% and 40% levels mark a bullish trend zone, while oscillations between -40% and 5% mark a bearish trend zone.
909
+ Meanwhile, readings above 40% signal an overbought condition, while readings above 60% signal an extremely overbought condition.
910
+ Alternatively, readings below -40% indicate an oversold condition, which becomes extremely oversold below -60%."""
911
+
912
+ sign = lambda a: (a > 0) - (a < 0)
913
+ r = ohlc[column].diff().apply(sign) * ohlc[colvol]
914
+ dvma = r.ewm(span=period, adjust=adjust).mean()
915
+ vma = ohlc[colvol].ewm(span=period, adjust=adjust).mean()
916
+
917
+ return pd.Series(100 * (dvma / vma), name="VZO")
918
+
919
+
920
+
921
+ def TR(ohlc,high="High",low="Low",close="Close"):
922
+ """True Range is the maximum of three price ranges.
923
+ Most recent period's high minus the most recent period's low.
924
+ Absolute value of the most recent period's high minus the previous close.
925
+ Absolute value of the most recent period's low minus the previous close."""
926
+
927
+ TR1 = pd.Series(ohlc[high] - ohlc[low]).abs() # True Range = High less Low
928
+
929
+ TR2 = pd.Series(
930
+ ohlc[high] - ohlc[close].shift()
931
+ ).abs() # True Range = High less Previous Close
932
+
933
+ TR3 = pd.Series(
934
+ ohlc[close].shift() - ohlc[low]
935
+ ).abs() # True Range = Previous Close less Low
936
+
937
+ _TR = pd.concat([TR1, TR2, TR3], axis=1)
938
+
939
+ _TR["TR"] = _TR.max(axis=1)
940
+
941
+ return pd.Series(_TR["TR"], name="TR")
942
+
943
+ def ATR(ohlc, period = 14,high="High",low="Low",close="Close"):
944
+ """Average True Range is moving average of True Range."""
945
+
946
+ mytr=TR(ohlc,high=high,low=low,close=close)
947
+ return pd.Series(
948
+ mytr.rolling(center=False, window=period).mean(),
949
+ name="{0} period ATR".format(period),
950
+ )
951
+
952
+ def CHANDELIER(ohlc, short_period=22, long_period=22, k=3, high="High", low="Low"):
953
+ """Chandelier Exit"""
954
+ long_stop = ohlc[high].rolling(window=long_period).max() - ATR(ohlc, 22) * k
955
+ short_stop = ohlc[low].rolling(window=short_period).min() + ATR(ohlc, 22) * k
956
+ return pd.DataFrame({"CHANDELIER_Long": long_stop, "CHANDELIER_Short": short_stop})