diff --git a/technical/indicators.py b/technical/indicators.py index ec2d7ea6..86a7ffff 100644 --- a/technical/indicators.py +++ b/technical/indicators.py @@ -4,6 +4,7 @@ """ from numpy.core.records import ndarray from pandas import Series, DataFrame +import numpy as np def aroon(dataframe, period=25, field='close', colum_prefix="bb") -> DataFrame: @@ -74,3 +75,537 @@ def cci(dataframe, period) -> ndarray: from pyti.commodity_channel_index import commodity_channel_index return commodity_channel_index(dataframe['close'], dataframe['high'], dataframe['low'], period) + +# from https://www.quantopian.com/posts/technical-analysis-indicators-without-talib-code + + +def moving_average(df, n): + """Calculate the moving average for the given data. + + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + MA = pd.Series(df['Close'].rolling(n, min_periods=n).mean(), name = 'MA_' + str(n)) + df = df.join(MA) + return df + + +def exponential_moving_average(df, n): + """ + + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + EMA = pd.Series(df['Close'].ewm(span = n, min_periods = n).mean(), name = 'EMA_' + str(n)) + df = df.join(EMA) + return df + + +def momentum(df, n): + """ + + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + M = pd.Series(df['Close'].diff(n), name = 'Momentum_' + str(n)) + df = df.join(M) + return df + + +def rate_of_change(df, n): + """ + + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + M = df['Close'].diff(n - 1) + N = df['Close'].shift(n - 1) + ROC = pd.Series(M / N, name = 'ROC_' + str(n)) + df = df.join(ROC) + return df + + +def average_true_range(df, n): + """ + + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + i = 0 + TR_l = [0] + while i < df.index[-1]: + TR = max(df.loc[i + 1, 'High'], df.loc[i, 'Close']) - min(df.loc[i + 1, 'Low'], df.loc[i, 'Close']) + TR_l.append(TR) + i = i + 1 + TR_s = pd.Series(TR_l) + ATR = pd.Series(TR_s.ewm(span = n, min_periods = n).mean(), name = 'ATR_' + str(n)) + df = df.join(ATR) + return df + + +def bollinger_bands(df, n): + """ + + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + MA = pd.Series(df['Close'].rolling(n, min_periods=n).mean()) + MSD = pd.Series(df['Close'].rolling(n, min_periods=n).std()) + b1 = 4 * MSD / MA + B1 = pd.Series(b1, name = 'BollingerB_' + str(n)) + df = df.join(B1) + b2 = (df['Close'] - MA + 2 * MSD) / (4 * MSD) + B2 = pd.Series(b2, name = 'Bollinger%b_' + str(n)) + df = df.join(B2) + return df + + +def ppsr(df): + """Calculate Pivot Points, Supports and Resistances for given data + + :param df: pandas.DataFrame + :return: pandas.DataFrame + """ + PP = pd.Series((df['High'] + df['Low'] + df['Close']) / 3) + R1 = pd.Series(2 * PP - df['Low']) + S1 = pd.Series(2 * PP - df['High']) + R2 = pd.Series(PP + df['High'] - df['Low']) + S2 = pd.Series(PP - df['High'] + df['Low']) + R3 = pd.Series(df['High'] + 2 * (PP - df['Low'])) + S3 = pd.Series(df['Low'] - 2 * (df['High'] - PP)) + psr = {'PP':PP, 'R1':R1, 'S1':S1, 'R2':R2, 'S2':S2, 'R3':R3, 'S3':S3} + PSR = pd.DataFrame(psr) + df = df.join(PSR) + return df + + +def stochastic_oscillator_k(df): + """Calculate stochastic oscillator %K for given data. + + :param df: pandas.DataFrame + :return: pandas.DataFrame + """ + SOk = pd.Series((df['Close'] - df['Low']) / (df['High'] - df['Low']), name = 'SO%k') + df = df.join(SOk) + return df + + +def stochastic_oscillator_d(df, n): + """Calculate stochastic oscillator %D for given data. + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + SOk = pd.Series((df['Close'] - df['Low']) / (df['High'] - df['Low']), name = 'SO%k') + SOd = pd.Series(SOk.ewm(span = n, min_periods = n).mean(), name = 'SO%d_' + str(n)) + df = df.join(SOd) + return df + + +def trix(df, n): + """Calculate TRIX for given data. + + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + EX1 = df['Close'].ewm(span = n, min_periods = n ).mean() + EX2 = EX1.ewm(span = n, min_periods = n).mean() + EX3 = EX2.ewm(span = n, min_periods = n).mean() + i = 0 + ROC_l = [np.nan] + while i + 1 <= df.index[-1]: + ROC = (EX3[i + 1] - EX3[i]) / EX3[i] + ROC_l.append(ROC) + i = i + 1 + Trix = pd.Series(ROC_l, name = 'Trix_' + str(n)) + df = df.join(Trix) + return df + + +def average_directional_movement_index(df, n, n_ADX): + """Calculate the Average Directional Movement Index for given data. + + :param df: pandas.DataFrame + :param n: + :param n_ADX: + :return: pandas.DataFrame + """ + i = 0 + UpI = [] + DoI = [] + while i + 1 <= df.index[-1]: + UpMove = df.loc[i + 1, 'High'] - df.loc[i, 'High'] + DoMove = df.loc[i, 'Low'] - df.loc[i + 1, 'Low'] + if UpMove > DoMove and UpMove > 0: + UpD = UpMove + else: UpD = 0 + UpI.append(UpD) + if DoMove > UpMove and DoMove > 0: + DoD = DoMove + else: DoD = 0 + DoI.append(DoD) + i = i + 1 + i = 0 + TR_l = [0] + while i < df.index[-1]: + TR = max(df.loc[i + 1, 'High'], df.loc[i, 'Close']) - min(df.loc[i + 1, 'Low'], df.loc[i, 'Close']) + TR_l.append(TR) + i = i + 1 + TR_s = pd.Series(TR_l) + ATR = pd.Series(TR_s.ewm(span = n, min_periods = n).mean()) + UpI = pd.Series(UpI) + DoI = pd.Series(DoI) + PosDI = pd.Series(UpI.ewm(span = n, min_periods = n).mean() / ATR) + NegDI = pd.Series(DoI.ewm(span = n, min_periods = n).mean() / ATR) + ADX = pd.Series((abs(PosDI - NegDI) / (PosDI + NegDI)).ewm(span = n_ADX, min_periods = n_ADX).mean(), name = 'ADX_' + str(n) + '_' + str(n_ADX)) + df = df.join(ADX) + return df + + +def macd(df, n_fast, n_slow): + """Calculate MACD, MACD Signal and MACD difference + + :param df: pandas.DataFrame + :param n_fast: + :param n_slow: + :return: pandas.DataFrame + """ + EMAfast = pd.Series(df['Close'].ewm(span = n_fast, min_periods = n_slow).mean()) + EMAslow = pd.Series(df['Close'].ewm(span = n_slow, min_periods = n_slow).mean()) + MACD = pd.Series(EMAfast - EMAslow, name = 'MACD_' + str(n_fast) + '_' + str(n_slow)) + MACDsign = pd.Series(MACD.ewm(span = 9, min_periods = 9).mean(), name = 'MACDsign_' + str(n_fast) + '_' + str(n_slow)) + MACDdiff = pd.Series(MACD - MACDsign, name = 'MACDdiff_' + str(n_fast) + '_' + str(n_slow)) + df = df.join(MACD) + df = df.join(MACDsign) + df = df.join(MACDdiff) + return df + + +def mass_index(df): + """Calculate the Mass Index for given data. + + :param df: pandas.DataFrame + :return: pandas.DataFrame + """ + Range = df['High'] - df['Low'] + EX1 = Range.ewm(span = 9, min_periods = 9).mean() + EX2 = EX1.ewm(span = 9, min_periods = 9).mean() + Mass = EX1 / EX2 + MassI = pd.Series(Mass.rolling(25).sum(), name = 'Mass Index') + df = df.join(MassI) + return df + + +def vortex_indicator(df, n): + """Calculate the Vortex Indicator for given data. + + Vortex Indicator described here: + http://www.vortexindicator.com/VFX_VORTEX.PDF + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + i = 0 + TR = [0] + while i < df.index[-1]: + Range = max(df.loc[i + 1, 'High'], df.loc[i, 'Close']) - min(df.loc[i + 1, 'Low'], df.loc[i, 'Close']) + TR.append(Range) + i = i + 1 + i = 0 + VM = [0] + while i < df.index[-1]: + Range = abs(df.loc[i + 1, 'High'] - df.loc[i, 'Low']) - abs(df.loc[i + 1, 'Low'] - df.loc[i, 'High']) + VM.append(Range) + i = i + 1 + VI = pd.Series(pd.Series(VM).rolling(n).sum() / pd.Series(TR).rolling(n).sum(), name = 'Vortex_' + str(n)) + df = df.join(VI) + return df + + +def kst_oscillator(df, r1, r2, r3, r4, n1, n2, n3, n4): + """Calculate KST Oscillator for given data. + + :param df: pandas.DataFrame + :param r1: + :param r2: + :param r3: + :param r4: + :param n1: + :param n2: + :param n3: + :param n4: + :return: pandas.DataFrame + """ + M = df['Close'].diff(r1 - 1) + N = df['Close'].shift(r1 - 1) + ROC1 = M / N + M = df['Close'].diff(r2 - 1) + N = df['Close'].shift(r2 - 1) + ROC2 = M / N + M = df['Close'].diff(r3 - 1) + N = df['Close'].shift(r3 - 1) + ROC3 = M / N + M = df['Close'].diff(r4 - 1) + N = df['Close'].shift(r4 - 1) + ROC4 = M / N + KST = pd.Series(ROC1.rolling(n1).sum() + ROC2.rolling(n2).sum() * 2 + ROC3.rolling(n3).sum() * 3 + ROC4.rolling(n4).sum() * 4, name = 'KST_' + str(r1) + '_' + str(r2) + '_' + str(r3) + '_' + str(r4) + '_' + str(n1) + '_' + str(n2) + '_' + str(n3) + '_' + str(n4)) + df = df.join(KST) + return df + + +def relative_strength_index(df, n): + """Calculate Relative Strength Index(RSI) for given data. + + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + i = 0 + UpI = [0] + DoI = [0] + while i + 1 <= df.index[-1]: + UpMove = df.loc[i + 1, 'High'] - df.loc[i, 'High'] + DoMove = df.loc[i, 'Low'] - df.loc[i + 1, 'Low'] + if UpMove > DoMove and UpMove > 0: + UpD = UpMove + else: UpD = 0 + UpI.append(UpD) + if DoMove > UpMove and DoMove > 0: + DoD = DoMove + else: DoD = 0 + DoI.append(DoD) + i = i + 1 + UpI = pd.Series(UpI) + DoI = pd.Series(DoI) + PosDI = pd.Series(UpI.ewm(span = n, min_periods = n).mean()) + NegDI = pd.Series(DoI.ewm(span = n, min_periods = n).mean()) + RSI = pd.Series(PosDI / (PosDI + NegDI), name = 'RSI_' + str(n)) + df = df.join(RSI) + return df + + +def true_strength_index(df, r, s): + """Calculate True Strength Index (TSI) for given data. + + :param df: pandas.DataFrame + :param r: + :param s: + :return: pandas.DataFrame + """ + M = pd.Series(df['Close'].diff(1)) + aM = abs(M) + EMA1 = pd.Series(M.ewm(span = r, min_periods = r).mean()) + aEMA1 = pd.Series(aM.ewm(span = r, min_periods = r).mean()) + EMA2 = pd.Series(EMA1.ewm(span = s, min_periods = s).mean()) + aEMA2 = pd.Series(aEMA1.ewm(span = s, min_periods = s).mean()) + TSI = pd.Series(EMA2 / aEMA2, name = 'TSI_' + str(r) + '_' + str(s)) + df = df.join(TSI) + return df + + +def accumulation_distribution(df, n): + """Calculate Accumulation/Distribution for given data. + + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + ad = (2 * df['Close'] - df['High'] - df['Low']) / (df['High'] - df['Low']) * df['Volume'] + M = ad.diff(n - 1) + N = ad.shift(n - 1) + ROC = M / N + AD = pd.Series(ROC, name = 'Acc/Dist_ROC_' + str(n)) + df = df.join(AD) + return df + + +def chaikin_oscillator(df): + """Calculate Chaikin Oscillator for given data. + + :param df: pandas.DataFrame + :return: pandas.DataFrame + """ + ad = (2 * df['Close'] - df['High'] - df['Low']) / (df['High'] - df['Low']) * df['Volume'] + Chaikin = pd.Series(ad.ewm(span = 3, min_periods = 3).mean() - ad.ewm(span = 10, min_periods = 10).mean(), name = 'Chaikin') + df = df.join(Chaikin) + return df + + +def money_flow_index(df, n): + """Calculate Money Flow Index and Ratio for given data. + + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + PP = (df['High'] + df['Low'] + df['Close']) / 3 + i = 0 + PosMF = [0] + while i < df.index[-1]: + if PP[i + 1] > PP[i]: + PosMF.append(PP[i + 1] * df.loc[i + 1, 'Volume']) + else: + PosMF.append(0) + i = i + 1 + PosMF = pd.Series(PosMF) + TotMF = PP * df['Volume'] + MFR = pd.Series(PosMF / TotMF) + MFI = pd.Series(MFR.rolling(n, min_periods=n).mean(), name = 'MFI_' + str(n)) + df = df.join(MFI) + return df + + +def on_balance_volume(df, n): + """Calculate On-Balance Volume for given data. + + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + i = 0 + OBV = [0] + while i < df.index[-1]: + if df.loc[i + 1, 'Close'] - df.loc[i, 'Close'] > 0: + OBV.append(df.loc[i + 1, 'Volume']) + if df.loc[i + 1, 'Close'] - df.loc[i, 'Close'] == 0: + OBV.append(0) + if df.loc[i + 1, 'Close'] - df.loc[i, 'Close'] < 0: + OBV.append(-df.loc[i + 1, 'Volume']) + i = i + 1 + OBV = pd.Series(OBV) + OBV_ma = pd.Series(OBV.rolling(n, min_periods=n).mean(), name = 'OBV_' + str(n)) + df = df.join(OBV_ma) + return df + + +def force_index(df, n): + """Calculate Force Index for given data. + + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + F = pd.Series(df['Close'].diff(n) * df['Volume'].diff(n), name = 'Force_' + str(n)) + df = df.join(F) + return df + + +def ease_of_movement(df, n): + """Calculate Ease of Movement for given data. + + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + EoM = (df['High'].diff(1) + df['Low'].diff(1)) * (df['High'] - df['Low']) / (2 * df['Volume']) + Eom_ma = pd.Series(EoM.rolling(n, min_periods=n).mean(), name = 'EoM_' + str(n)) + df = df.join(Eom_ma) + return df + + +def commodity_channel_index(df, n): + """Calculate Commodity Channel Index for given data. + + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + PP = (df['High'] + df['Low'] + df['Close']) / 3 + CCI = pd.Series((PP - PP.rolling(n, min_periods=n).mean()) / PP.rolling(n, min_periods=n).std(), name = 'CCI_' + str(n)) + df = df.join(CCI) + return df + + +def coppock_curve(df, n): + """Calculate Coppock Curve for given data. + + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + M = df['Close'].diff(int(n * 11 / 10) - 1) + N = df['Close'].shift(int(n * 11 / 10) - 1) + ROC1 = M / N + M = df['Close'].diff(int(n * 14 / 10) - 1) + N = df['Close'].shift(int(n * 14 / 10) - 1) + ROC2 = M / N + Copp = pd.Series((ROC1 + ROC2).ewm(span = n, min_periods = n).mean(), name = 'Copp_' + str(n)) + df = df.join(Copp) + return df + + +def keltner_channel(df, n): + """Calculate Keltner Channel for given data. + + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + KelChM = pd.Series(((df['High'] + df['Low'] + df['Close']) / 3).rolling(n, min_periods=n).mean(), name = 'KelChM_' + str(n)) + KelChU = pd.Series(((4 * df['High'] - 2 * df['Low'] + df['Close']) / 3).rolling(n, min_periods=n).mean(), name = 'KelChU_' + str(n)) + KelChD = pd.Series(((-2 * df['High'] + 4 * df['Low'] + df['Close']) / 3).rolling(n, min_periods=n).mean(), name = 'KelChD_' + str(n)) + df = df.join(KelChM) + df = df.join(KelChU) + df = df.join(KelChD) + return df + + +def ultimate_oscillator(df): + """Calculate Ultimate Oscillator for given data. + + :param df: pandas.DataFrame + :return: pandas.DataFrame + """ + i = 0 + TR_l = [0] + BP_l = [0] + while i < df.index[-1]: + TR = max(df.loc[i + 1, 'High'], df.loc[i, 'Close']) - min(df.loc[i + 1, 'Low'], df.loc[i, 'Close']) + TR_l.append(TR) + BP = df.loc[i + 1, 'Close'] - min(df.loc[i + 1, 'Low'], df.loc[i, 'Close']) + BP_l.append(BP) + i = i + 1 + UltO = pd.Series((4 * pd.Series(BP_l).rolling(7).sum() / pd.Series(TR_l).rolling(7).sum()) + (2 * pd.Series(BP_l).rolling(14).sum() / pd.Series(TR_l).rolling(14).sum()) + (pd.Series(BP_l).rolling(28).sum() / pd.Series(TR_l).rolling(28).sum()), name = 'Ultimate_Osc') + df = df.join(UltO) + return df + + +def donchian_channel(df, n): + """Calculate donchian channel of given pandas data frame. + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + i = 0 + dc_l = [] + while i < n - 1: + dc_l.append(0) + i += 1 + + i = 0 + while i + n - 1 < df.index[-1]: + dc = max(df['High'].ix[i:i + n - 1]) - min(df['Low'].ix[i:i + n - 1]) + dc_l.append(dc) + i += 1 + + donchian_chan = pd.Series(DC_l, name='Donchian_' + str(n)) + donchian_chan = donchian_chan.shift(n - 1) + return df.join(donchian_chan) + + +def standard_deviation(df, n): + """Calculate Standard Deviation for given data. + + :param df: pandas.DataFrame + :param n: + :return: pandas.DataFrame + """ + df = df.join(pd.Series(df['Close'].rolling(n, min_periods=n).std(), name='STD_' + str(n))) + return df