diff --git a/finta/finta.py b/finta/finta.py index 019e8e8..554cf4d 100644 --- a/finta/finta.py +++ b/finta/finta.py @@ -245,7 +245,7 @@ def VIDYA( smoothing_period: int = 12, column: str = "close", ) -> Series: - """ Vidya (variable index dynamic average) indicator is a modification of the traditional Exponential Moving Average (EMA) indicator. + """Vidya (variable index dynamic average) indicator is a modification of the traditional Exponential Moving Average (EMA) indicator. The main difference between EMA and Vidya is in the way the smoothing factor F is calculated. In EMA the smoothing factor is a constant value F=2/(period+1); in Vidya the smoothing factor is variable and depends on bar-to-bar price movements.""" @@ -255,7 +255,7 @@ def VIDYA( @classmethod def ER(cls, ohlc: DataFrame, period: int = 10, column: str = "close") -> Series: """The Kaufman Efficiency indicator is an oscillator indicator that oscillates between +100 and -100, where zero is the center point. - +100 is upward forex trending market and -100 is downwards trending markets.""" + +100 is upward forex trending market and -100 is downwards trending markets.""" change = ohlc[column].diff(period).abs() volatility = ohlc[column].diff().abs().rolling(window=period).sum() @@ -403,7 +403,9 @@ def EVWMA(cls, ohlcv: DataFrame, period: int = 20) -> Series: evwma.append(evwma[-1] * x[1] + y[1]) return pd.Series( - evwma[1:], index=ohlcv.index, name="{0} period EVWMA.".format(period), + evwma[1:], + index=ohlcv.index, + name="{0} period EVWMA.".format(period), ) @classmethod @@ -460,7 +462,7 @@ def MAMA(cls, ohlc: DataFrame, period: int = 16) -> Series: raise NotImplementedError @classmethod - def FRAMA(cls, ohlc: DataFrame, period: int = 16, batch: int=10) -> Series: + def FRAMA(cls, ohlc: DataFrame, period: int = 16, batch: int = 10) -> Series: """Fractal Adaptive Moving Average Source: http://www.stockspotter.com/Files/frama.pdf Adopted from: https://www.quantopian.com/posts/frama-fractal-adaptive-moving-average-in-python @@ -487,7 +489,7 @@ def FRAMA(cls, ohlc: DataFrame, period: int = 16, batch: int=10) -> Series: # calculate fractal dimension D = (np.log(n1 + n2) - np.log(n3)) / np.log(2) alp = np.exp(-4.6 * (D - 1)) - alp = np.clip(alp, .01, 1).values + alp = np.clip(alp, 0.01, 1).values filt = c.values for i, x in enumerate(alp): @@ -496,7 +498,9 @@ def FRAMA(cls, ohlc: DataFrame, period: int = 16, batch: int=10) -> Series: continue filt[i] = cl * x + (1 - x) * filt[i - 1] - return pd.Series(filt, index=ohlc.index, name="{0} period FRAMA.".format(period)) + return pd.Series( + filt, index=ohlc.index, name="{0} period FRAMA.".format(period) + ) @classmethod def MACD( @@ -581,7 +585,7 @@ def VW_MACD( column: str = "close", adjust: bool = True, ) -> DataFrame: - """"Volume-Weighted MACD" is an indicator that shows how a volume-weighted moving average can be used to calculate moving average convergence/divergence (MACD). + """ "Volume-Weighted MACD" is an indicator that shows how a volume-weighted moving average can be used to calculate moving average convergence/divergence (MACD). This technique was first used by Buff Dormeier, CMT, and has been written about since at least 2002.""" vp = ohlcv["volume"] * ohlcv[column] @@ -941,13 +945,13 @@ def BBANDS( std_multiplier: float = 2, ) -> DataFrame: """ - Developed by John Bollinger, Bollinger Bands® are volatility bands placed above and below a moving average. - Volatility is based on the standard deviation, which changes as volatility increases and decreases. - The bands automatically widen when volatility increases and narrow when volatility decreases. + Developed by John Bollinger, Bollinger Bands® are volatility bands placed above and below a moving average. + Volatility is based on the standard deviation, which changes as volatility increases and decreases. + The bands automatically widen when volatility increases and narrow when volatility decreases. - This method allows input of some other form of moving average like EMA or KAMA around which BBAND will be formed. - Pass desired moving average as argument. For example BBANDS(MA=TA.KAMA(20)). - """ + This method allows input of some other form of moving average like EMA or KAMA around which BBAND will be formed. + Pass desired moving average as argument. For example BBANDS(MA=TA.KAMA(20)). + """ std = ohlc[column].rolling(window=period).std() @@ -1197,10 +1201,10 @@ def PIVOT_FIB(cls, ohlc: DataFrame) -> DataFrame: @classmethod def STOCH(cls, ohlc: DataFrame, period: int = 14) -> Series: """Stochastic oscillator %K - The stochastic oscillator is a momentum indicator comparing the closing price of a security - to the range of its prices over a certain period of time. - The sensitivity of the oscillator to market movements is reducible by adjusting that time - period or by taking a moving average of the result. + The stochastic oscillator is a momentum indicator comparing the closing price of a security + to the range of its prices over a certain period of time. + The sensitivity of the oscillator to market movements is reducible by adjusting that time + period or by taking a moving average of the result. """ highest_high = ohlc["high"].rolling(center=False, window=period).max() @@ -1243,10 +1247,10 @@ def STOCHRSI( @classmethod def WILLIAMS(cls, ohlc: DataFrame, period: int = 14) -> Series: """Williams %R, or just %R, is a technical analysis oscillator showing the current closing price in relation to the high and low - of the past N days (for a given N). It was developed by a publisher and promoter of trading materials, Larry Williams. - Its purpose is to tell whether a stock or commodity market is trading near the high or the low, or somewhere in between, - of its recent trading range. - The oscillator is on a negative scale, from −100 (lowest) up to 0 (highest). + of the past N days (for a given N). It was developed by a publisher and promoter of trading materials, Larry Williams. + Its purpose is to tell whether a stock or commodity market is trading near the high or the low, or somewhere in between, + of its recent trading range. + The oscillator is on a negative scale, from −100 (lowest) up to 0 (highest). """ highest_high = ohlc["high"].rolling(center=False, window=period).max() @@ -1285,7 +1289,7 @@ def AO(cls, ohlc: DataFrame, slow_period: int = 34, fast_period: int = 5) -> Ser """'EMA', Awesome Oscillator is an indicator used to measure market momentum. AO calculates the difference of a 34 Period and 5 Period Simple Moving Averages. The Simple Moving Averages that are used are not calculated using closing price but rather each bar's midpoints. - AO is generally used to affirm trends or to anticipate possible reversals. """ + AO is generally used to affirm trends or to anticipate possible reversals.""" slow = pd.Series( ((ohlc["high"] + ohlc["low"]) / 2).rolling(window=slow_period).mean(), @@ -1322,12 +1326,12 @@ def BOP(cls, ohlc: DataFrame) -> Series: @classmethod def VORTEX(cls, ohlc: DataFrame, period: int = 14) -> DataFrame: """The Vortex indicator plots two oscillating lines, one to identify positive trend movement and the other - to identify negative price movement. - Indicator construction revolves around the highs and lows of the last two days or periods. - The distance from the current high to the prior low designates positive trend movement while the - distance between the current low and the prior high designates negative trend movement. - Strongly positive or negative trend movements will show a longer length between the two numbers while - weaker positive or negative trend movement will show a shorter length.""" + to identify negative price movement. + Indicator construction revolves around the highs and lows of the last two days or periods. + The distance from the current high to the prior low designates positive trend movement while the + distance between the current low and the prior high designates negative trend movement. + Strongly positive or negative trend movements will show a longer length between the two numbers while + weaker positive or negative trend movement will show a shorter length.""" VMP = pd.Series((ohlc["high"] - ohlc["low"].shift()).abs()) VMM = pd.Series((ohlc["low"] - ohlc["high"].shift()).abs()) @@ -1401,6 +1405,65 @@ def TSI( return pd.concat([TSI, signal], axis=1) + @classmethod + def SUPERTREND(cls, ohlc: DataFrame, period: int = 7, factor: int = 3) -> Series: + """ + Supertrend is a trend following indicator. In an uptrend, it shows the price + of the support which must be held to maintain the uptrend and vice-versa for downtrend. + This implementation is converted from: + https://www.tradingview.com/script/PyYDw8ji-Supertrend-v3-w-Alerts-version-3/ + """ + + hl2 = (ohlc["high"] + ohlc["low"]) / 2 + + # atr_rma has RMA smoothing - not the standard SMA smoothing + def atr_rma(ohlc, period): + TR = cls.TR(ohlc) + return pd.Series( + TR.ewm(alpha=1.0 / period, adjust=True).mean(), + name="{0} period ATR".format(period), + ) + + atr = atr_rma(ohlc, period) + + st_data = pd.DataFrame() + st_data["up"] = hl2 - (factor * atr) + st_data["down"] = hl2 + (factor * atr) + st_data["trend_up"] = 0 + st_data["trend_down"] = 0 + st_data["trend_dir"] = 0 + st_data["price"] = 0 + + for i in range(period + 1, len(ohlc.values)): + st_data.loc[i, "trend_up"] = ( + max(st_data.loc[i, "up"], st_data.loc[i - 1, "trend_up"]) + if ohlc.loc[i - 1, "close"] > st_data.loc[i - 1, "trend_up"] + else st_data.loc[i, "up"] + ) + + st_data.loc[i, "trend_down"] = ( + min(st_data.loc[i, "down"], st_data.loc[i - 1, "trend_down"]) + if ohlc.loc[i - 1, "close"] < st_data.loc[i - 1, "trend_down"] + else st_data.loc[i, "down"] + ) + + # trend_dir = -1 for downtrend, 1 for uptrend + st_data.loc[i, "trend_dir"] = ( + 1 + if ohlc.loc[i, "close"] > st_data.loc[i - 1, "trend_down"] + else -1 + if ohlc.loc[i, "close"] < st_data.loc[i - 1, "trend_up"] + else st_data.loc[i - 1, "trend_dir"] + ) + + st_data.loc[i, "price"] = ( + st_data.loc[i, "trend_up"] + if st_data.loc[i, "trend_dir"] == 1 + else st_data.loc[i, "trend_down"] + ) + + return st_data.filter(items=["trend_dir", "price"], axis=1) + @classmethod def TP(cls, ohlc: DataFrame) -> Series: """Typical Price refers to the arithmetic average of the high, low, and closing prices for a given period.""" @@ -1427,9 +1490,9 @@ def ADL(cls, ohlcv: DataFrame) -> Series: @inputvalidator(input_="ohlcv") def CHAIKIN(cls, ohlcv: DataFrame, adjust: bool = True) -> Series: """Chaikin Oscillator, named after its creator, Marc Chaikin, the Chaikin oscillator is an oscillator that measures the accumulation/distribution - line of the moving average convergence divergence (MACD). The Chaikin oscillator is calculated by subtracting a 10-day exponential moving average (EMA) - of the accumulation/distribution line from a three-day EMA of the accumulation/distribution line, and highlights the momentum implied by the - accumulation/distribution line.""" + line of the moving average convergence divergence (MACD). The Chaikin oscillator is calculated by subtracting a 10-day exponential moving average (EMA) + of the accumulation/distribution line from a three-day EMA of the accumulation/distribution line, and highlights the momentum implied by the + accumulation/distribution line.""" return pd.Series( cls.ADL(ohlcv).ewm(span=3, min_periods=2, adjust=adjust).mean() @@ -1571,7 +1634,7 @@ def EFI( adjust: bool = True, ) -> Series: """Elder's Force Index is an indicator that uses price and volume to assess the power - behind a move or identify possible turning points.""" + behind a move or identify possible turning points.""" # https://tradingsim.com/blog/elders-force-index/ fi = pd.Series(ohlcv[column].diff() * ohlcv["volume"]) @@ -2052,7 +2115,8 @@ def VFI( cutoff = pd.Series(factor * vinter * ohlc["close"], name="cutoff") price_change = pd.Series(typical.diff(), name="pc") # price change mav = pd.Series( - ohlc["volume"].rolling(center=False, window=period).mean(), name="mav", + ohlc["volume"].rolling(center=False, window=period).mean(), + name="mav", ) _va = pd.concat([ohlc["volume"], mav.shift()], axis=1) @@ -2099,9 +2163,7 @@ def _multiplier(row): return vfi @classmethod - def MSD( - cls, ohlc: DataFrame, period: int = 21, column: str = "close" - ) -> Series: + def MSD(cls, ohlc: DataFrame, period: int = 21, column: str = "close") -> Series: """ Standard deviation is a statistical term that measures the amount of variability or dispersion around an average. Standard deviation is also a measure of volatility. Generally speaking, dispersion is the difference between the actual value and the average value. @@ -2125,7 +2187,7 @@ def STC( k_period: int = 10, d_period: int = 3, column: str = "close", - adjust: bool = True + adjust: bool = True, ) -> Series: """ The Schaff Trend Cycle (Oscillator) can be viewed as Double Smoothed @@ -2162,10 +2224,16 @@ def STC( MACD = pd.Series((EMA_fast - EMA_slow), name="MACD") - STOK = pd.Series(( - (MACD - MACD.rolling(window=k_period).min()) - / (MACD.rolling(window=k_period).max() - MACD.rolling(window=k_period).min()) - ) * 100) + STOK = pd.Series( + ( + (MACD - MACD.rolling(window=k_period).min()) + / ( + MACD.rolling(window=k_period).max() + - MACD.rolling(window=k_period).min() + ) + ) + * 100 + ) STOD = STOK.rolling(window=d_period).mean() STOD_DoubleSmooth = STOD.rolling(window=d_period).mean() # "double smoothed" @@ -2180,7 +2248,7 @@ def EVSTC( period_slow: int = 30, k_period: int = 10, d_period: int = 3, - adjust: bool = True + adjust: bool = True, ) -> Series: """Modification of Schaff Trend Cycle using EVWMA MACD for calculation""" @@ -2189,17 +2257,22 @@ def EVSTC( macd = ema_fast - ema_slow - STOK = pd.Series(( - (macd - macd.rolling(window=k_period).min()) - / (macd.rolling(window=k_period).max() - macd.rolling(window=k_period).min()) - ) * 100) + STOK = pd.Series( + ( + (macd - macd.rolling(window=k_period).min()) + / ( + macd.rolling(window=k_period).max() + - macd.rolling(window=k_period).min() + ) + ) + * 100 + ) STOD = STOK.rolling(window=d_period).mean() STOD_DoubleSmooth = STOD.rolling(window=d_period).mean() return pd.Series(STOD_DoubleSmooth, name="{0} period EVSTC".format(k_period)) - @classmethod def WILLIAMS_FRACTAL(cls, ohlc: DataFrame, period: int = 2) -> DataFrame: """ diff --git a/tests/test_unit.py b/tests/test_unit.py index 6da40cd..0878afb 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -330,7 +330,7 @@ def test_bbands(): def test_mobo(): """test TA.mobo""" - + mbb = TA.MOBO(ohlc) assert isinstance(mbb["BB_UPPER"], series.Series) @@ -867,3 +867,14 @@ def test_williams_fractal(): assert isinstance(fractals["BearishFractal"], series.Series) assert fractals.BearishFractal.values[-3] == 0 assert fractals.BullishFractal.values[-3] == 0 + +def test_supertrend(): + """test TA.SUPERTREND""" + + st = TA.SUPERTREND(ohlc) + + assert isinstance(st.trend_dir, series.Series) + assert isinstance(st.price, series.Series) + + assert st.trend_dir.values[-3] == -1 + assert st.price.values[-3] == 7498.7438561297895