nxaura/Technical-Analysis-Dashboard
0
1# technical_indicators.py2 3import pandas as pd4import numpy as np5from math import sqrt6 7def 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 11def 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 22def SSMA(ohlc,period = 9, column = "Close",adjust = True):23 """24 Smoothed simple moving average.25 26 :param ohlc: data27 :param period: range28 :param column: open/close/high/low column of the DataFrame29 :return: result Series30 """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 39def 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 43def 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] = 048 down[down > 0] = 049 _gain = up.ewm(com=period - 1, adjust=adjust).mean()50 _loss = abs(down.ewm(com=period - 1, adjust=adjust).mean())51 RS = _gain / _loss52 return pd.Series(100 - (100 / (1 + RS)), name=f"RSI_{period}")53 54 55def DEMA(ohlc,period = 9,column = "Close",adjust = True):56 """57 Double Exponential Moving Average - attempts to remove the inherent lag associated to Moving Averages58 by placing more weight on recent values. The name suggests this is achieved by applying a double exponential59 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 period62 samples needed by a regular EMA63 """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 72def 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 the76 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 samples80 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_ema94 - 3 * EMA(ohlc, period).ewm(span=period, adjust=adjust).mean()95 + ema_ema_ema96 )97 98 return pd.Series(TEMA, name="{0} period TEMA".format(period))99 100def 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-1031203106 """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 114def 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 134def VAMA(ohlcv,period = 8, column = "Close",colvol="Volume"):135 """136 Volume Adjusted Moving Average137 """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 147def 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 153def 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) ** 2159 160 sma = ohlc[column].rolling(window=period).mean()161 kama = [float("nan")] * len(ohlc)162 163 # Build KAMA line164 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 172def 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 averages175 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 zlema190 191def 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 _inner198 close = ohlc[column]199 return pd.Series(close.rolling(period, min_periods=period).apply(linear(weights), raw=True), name=f"WMA_{period}")200 201def 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 210def 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 220def 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 225def 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 dividing229 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 237def 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 * 2242 hh = c.rolling(batch).max()243 ll = c.rolling(batch).min()244 n1 = (hh - ll) / batch245 n2 = n1.shift(batch)246 hh2 = c.rolling(window).max()247 ll2 = c.rolling(window).min()248 n3 = (hh2 - ll2) / window249 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).values252 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 260def 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 290def 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 * dev295 lower_band = sma - std * dev296 return pd.DataFrame({"BB_UPPER": upper_band, "BB_LOWER": lower_band})297 298def STOCH(ohlc, period = 14,close="Close",high="High",low="Low"):299 """Stochastic oscillator %K300 The stochastic oscillator is a momentum indicator comparing the closing price of a security301 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 time303 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 STOCH315 316def STOCHD(ohlc, period = 3, stoch_period = 14,close="Close",high="High",low="Low"):317 """Stochastic oscillator %D318 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 326def 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 334def 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] = 0340 down[down > 0] = 0341 _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 346def 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 / br351 return pd.Series(emv.rolling(window=period).mean(), name="EMV")352 353 354def 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 359def 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 365def 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 379def 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 398def 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 404def 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 413def 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 425def 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 431def 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] - ema435 bear_power = ohlc[low] - ema436 return pd.DataFrame({"Bull": bull_power, "Bear": bear_power}, index=ohlc.index)437 438def 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 443def CCI(ohlc, period=20, high="High", low="Low", close="Close"):444 """Commodity Channel Index"""445 tp = (ohlc[high] + ohlc[low] + ohlc[close]) / 3446 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 451def 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 462def 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 469def 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 473def WTO(ohlc, channel_length=10, average_length=21, adjust=True):474 """Wave Trend Oscillator"""475 ap = (ohlc["High"] + ohlc["Low"] + ohlc["Close"]) / 3476 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 483def 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 values490 sig0, xpt0, af0 = True, high1[0], af491 _sar = [low1[0] - (high1 - low1).std()]492 493 for i in range(1, len(ohlc)):494 sig1, xpt1, af1 = sig0, xpt0, af0495 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]) * af1508 af0 = min(amax, af1 + af)509 510 if sig0:511 af0 = af0 if xpt0 > xpt1 else af1512 sari = min(sari, lmin)513 else:514 af0 = af0 if xpt0 < xpt1 else af1515 sari = max(sari, lmax)516 else:517 af0 = af518 sari = xpt0519 520 _sar.append(sari)521 522 return pd.Series(_sar, index=ohlc.index)523 524def 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.asp530 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] * length537 psarbear = [None] * length538 bull = True539 af = iaf540 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 = False550 551 if bull:552 if low1[i] < psar[i]:553 bull = False554 reverse = True555 psar[i] = hp556 lp = low1[i]557 af = iaf558 else:559 if high1[i] > psar[i]:560 bull = True561 reverse = True562 psar[i] = lp563 hp = high1[i]564 af = iaf565 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_df596 597def 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 607def 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 change611 momentum = pd.Series(ohlc[column].diff()) ## 1 period momentum612 _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 change622 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 640def FISH(ohlc, period=10, adjust=True, high="High", low="Low"):641 """Fisher Transform"""642 med = (ohlc[high] + ohlc[low]) / 2643 ndaylow = med.rolling(window=period).min()644 ndayhigh = med.rolling(window=period).max()645 raw = (2 * ((med - ndaylow) / (ndayhigh - ndaylow))) - 1646 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 653def 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()) / 2658 kijun_sen = (ohlc[high].rolling(window=kijun_period).max() +659 ohlc[low].rolling(window=kijun_period).min()) / 2660 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_span670 })671 672 673def 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) / 2678 return pd.DataFrame({"DC_U": upper, "DC_L": lower, "DC_M": middle})679 680 681 682def 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 Flow686 mf_sign = np.sign(tp.diff()) # Positive or negative money flow687 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_sum694 mfi = 100 - (100 / (1 + mfratio))695 696 return pd.Series(mfi, name=f"{period} period MFI")697 698def 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 705def 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 variable708 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 indicator711 oscillates between 0 and 100.712 https://www.investopedia.com/terms/d/dynamicmomentumindex.asp713 """714 715 def _get_time(close):716 # Value available from 14th period717 sd = close.rolling(5).std()718 asd = sd.rolling(10).mean()719 v = sd / asd720 t = 14 / v.round()721 t[t.isna()] = 0722 t = t.map(lambda x: int(min(max(x, 5), 30)))723 return t724 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 738def VPT(ohlcv, colvol="Volume", column="Close", open="Open", high="High", low="Low"):739 """Volume Price Trend"""740 hilow = (ohlcv[high] - ohlcv[low]) * 100741 openclose = (ohlcv[column] - ohlcv[open]) * 100742 vol = ohlcv[colvol] / hilow743 spreadvol = (openclose * vol).cumsum()744 vpt = spreadvol + spreadvol745 return pd.Series(vpt, name="VPT")746 747def 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 759def 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 768def 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 778def AO(ohlc, high="High", low="Low"):779 """Awesome Oscillator"""780 median_price = (ohlc[high] + ohlc[low]) / 2781 ao = median_price.rolling(window=5).mean() - median_price.rolling(window=34).mean()782 return pd.Series(ao, name="AO")783 784def 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 / DEMA9790 return pd.Series(mass.rolling(window=25).sum(), name="MI")791 792 793def 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 798def 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 812def 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 / bpavg821 nsp = sp / spavg822 823 varg = ohlc[colvol].ewm(span=period, adjust=adjust).mean()824 nv = ohlc[colvol] / varg825 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 831def 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 / bpavg838 nsp = sp / spavg839 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 843def 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)) / 2849 _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 ift853 854 855def 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 867def 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 879def 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]) / 3882 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_dev890 kc_lower = kc_middle - kc_mult * mean_dev891 return pd.DataFrame({892 "KC_MIDDLE": kc_middle,893 "KC_UPPER": kc_upper,894 "KC_LOWER": kc_lower895 })896 897def 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_factor901 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 905def 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 921def 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 Low928 929 TR2 = pd.Series(930 ohlc[high] - ohlc[close].shift()931 ).abs() # True Range = High less Previous Close932 933 TR3 = pd.Series(934 ohlc[close].shift() - ohlc[low]935 ).abs() # True Range = Previous Close less Low936 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 943def 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 952def 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) * k955 short_stop = ohlc[low].rolling(window=short_period).min() + ATR(ohlc, 22) * k956 return pd.DataFrame({"CHANDELIER_Long": long_stop, "CHANDELIER_Short": short_stop})957 