Skip to content
This repository was archived by the owner on Sep 2, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 120 additions & 47 deletions finta/finta.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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):
Expand All @@ -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(
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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 <MA> 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 <MA> argument. For example BBANDS(MA=TA.KAMA(20)).
"""

std = ohlc[column].rolling(window=period).std()

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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."""
Expand All @@ -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()
Expand Down Expand Up @@ -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"])
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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"
Expand All @@ -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"""

Expand All @@ -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:
"""
Expand Down
13 changes: 12 additions & 1 deletion tests/test_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ def test_bbands():

def test_mobo():
"""test TA.mobo"""

mbb = TA.MOBO(ohlc)

assert isinstance(mbb["BB_UPPER"], series.Series)
Expand Down Expand Up @@ -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