CoolFace
Apppublic

nxaura/Technical-Analysis-Dashboard

sourceHugging Faceapache-2.0updated 1y agoView on Hugging Face
0likes
technical_indicators.py957 linesDownload Raw Back to root
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