Create technical_indicators.py
Browse files- technical_indicators.py +956 -0
technical_indicators.py
ADDED
|
@@ -0,0 +1,956 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# technical_indicators.py
|
| 2 |
+
|
| 3 |
+
import pandas as pd
|
| 4 |
+
import numpy as np
|
| 5 |
+
from math import sqrt
|
| 6 |
+
|
| 7 |
+
def 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 |
+
|
| 11 |
+
def 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 |
+
|
| 22 |
+
def SSMA(ohlc,period = 9, column = "Close",adjust = True):
|
| 23 |
+
"""
|
| 24 |
+
Smoothed simple moving average.
|
| 25 |
+
|
| 26 |
+
:param ohlc: data
|
| 27 |
+
:param period: range
|
| 28 |
+
:param column: open/close/high/low column of the DataFrame
|
| 29 |
+
:return: result Series
|
| 30 |
+
"""
|
| 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 |
+
|
| 39 |
+
def 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 |
+
|
| 43 |
+
def 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] = 0
|
| 48 |
+
down[down > 0] = 0
|
| 49 |
+
_gain = up.ewm(com=period - 1, adjust=adjust).mean()
|
| 50 |
+
_loss = abs(down.ewm(com=period - 1, adjust=adjust).mean())
|
| 51 |
+
RS = _gain / _loss
|
| 52 |
+
return pd.Series(100 - (100 / (1 + RS)), name=f"RSI_{period}")
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def DEMA(ohlc,period = 9,column = "Close",adjust = True):
|
| 56 |
+
"""
|
| 57 |
+
Double Exponential Moving Average - attempts to remove the inherent lag associated to Moving Averages
|
| 58 |
+
by placing more weight on recent values. The name suggests this is achieved by applying a double exponential
|
| 59 |
+
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 period
|
| 62 |
+
samples needed by a regular EMA
|
| 63 |
+
"""
|
| 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 |
+
|
| 72 |
+
def 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 the
|
| 76 |
+
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 samples
|
| 80 |
+
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_ema
|
| 94 |
+
- 3 * EMA(ohlc, period).ewm(span=period, adjust=adjust).mean()
|
| 95 |
+
+ ema_ema_ema
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
return pd.Series(TEMA, name="{0} period TEMA".format(period))
|
| 99 |
+
|
| 100 |
+
def 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-1031203
|
| 106 |
+
"""
|
| 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 |
+
|
| 114 |
+
def 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 |
+
|
| 134 |
+
def VAMA(ohlcv,period = 8, column = "Close",colvol="Volume"):
|
| 135 |
+
"""
|
| 136 |
+
Volume Adjusted Moving Average
|
| 137 |
+
"""
|
| 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 |
+
|
| 147 |
+
def 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 |
+
|
| 153 |
+
def 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) ** 2
|
| 159 |
+
|
| 160 |
+
sma = ohlc[column].rolling(window=period).mean()
|
| 161 |
+
kama = [float("nan")] * len(ohlc)
|
| 162 |
+
|
| 163 |
+
# Build KAMA line
|
| 164 |
+
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 |
+
|
| 172 |
+
def 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 averages
|
| 175 |
+
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 zlema
|
| 190 |
+
|
| 191 |
+
def 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 _inner
|
| 198 |
+
close = ohlc[column]
|
| 199 |
+
return pd.Series(close.rolling(period, min_periods=period).apply(linear(weights), raw=True), name=f"WMA_{period}")
|
| 200 |
+
|
| 201 |
+
def 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 |
+
|
| 210 |
+
def 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 |
+
|
| 220 |
+
def 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 |
+
|
| 225 |
+
def 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 dividing
|
| 229 |
+
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 |
+
|
| 237 |
+
def 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 * 2
|
| 242 |
+
hh = c.rolling(batch).max()
|
| 243 |
+
ll = c.rolling(batch).min()
|
| 244 |
+
n1 = (hh - ll) / batch
|
| 245 |
+
n2 = n1.shift(batch)
|
| 246 |
+
hh2 = c.rolling(window).max()
|
| 247 |
+
ll2 = c.rolling(window).min()
|
| 248 |
+
n3 = (hh2 - ll2) / window
|
| 249 |
+
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).values
|
| 252 |
+
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 |
+
|
| 260 |
+
def 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 |
+
|
| 290 |
+
def 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 * dev
|
| 295 |
+
lower_band = sma - std * dev
|
| 296 |
+
return pd.DataFrame({"BB_UPPER": upper_band, "BB_LOWER": lower_band})
|
| 297 |
+
|
| 298 |
+
def STOCH(ohlc, period = 14,close="Close",high="High",low="Low"):
|
| 299 |
+
"""Stochastic oscillator %K
|
| 300 |
+
The stochastic oscillator is a momentum indicator comparing the closing price of a security
|
| 301 |
+
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 time
|
| 303 |
+
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 STOCH
|
| 315 |
+
|
| 316 |
+
def STOCHD(ohlc, period = 3, stoch_period = 14,close="Close",high="High",low="Low"):
|
| 317 |
+
"""Stochastic oscillator %D
|
| 318 |
+
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 |
+
|
| 326 |
+
def 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 |
+
|
| 334 |
+
def 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] = 0
|
| 340 |
+
down[down > 0] = 0
|
| 341 |
+
_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 |
+
|
| 346 |
+
def 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 / br
|
| 351 |
+
return pd.Series(emv.rolling(window=period).mean(), name="EMV")
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
def 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 |
+
|
| 359 |
+
def 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 |
+
|
| 365 |
+
def 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 |
+
|
| 379 |
+
def 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 |
+
|
| 398 |
+
def 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 |
+
|
| 404 |
+
def 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 |
+
|
| 413 |
+
def 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 |
+
|
| 425 |
+
def 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 |
+
|
| 431 |
+
def 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] - ema
|
| 435 |
+
bear_power = ohlc[low] - ema
|
| 436 |
+
return pd.DataFrame({"Bull": bull_power, "Bear": bear_power}, index=ohlc.index)
|
| 437 |
+
|
| 438 |
+
def 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 |
+
|
| 443 |
+
def CCI(ohlc, period=20, high="High", low="Low", close="Close"):
|
| 444 |
+
"""Commodity Channel Index"""
|
| 445 |
+
tp = (ohlc[high] + ohlc[low] + ohlc[close]) / 3
|
| 446 |
+
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 |
+
|
| 451 |
+
def 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 |
+
|
| 462 |
+
def 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 |
+
|
| 469 |
+
def 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 |
+
|
| 473 |
+
def WTO(ohlc, channel_length=10, average_length=21, adjust=True):
|
| 474 |
+
"""Wave Trend Oscillator"""
|
| 475 |
+
ap = (ohlc["High"] + ohlc["Low"] + ohlc["Close"]) / 3
|
| 476 |
+
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 |
+
|
| 483 |
+
def 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 values
|
| 490 |
+
sig0, xpt0, af0 = True, high1[0], af
|
| 491 |
+
_sar = [low1[0] - (high1 - low1).std()]
|
| 492 |
+
|
| 493 |
+
for i in range(1, len(ohlc)):
|
| 494 |
+
sig1, xpt1, af1 = sig0, xpt0, af0
|
| 495 |
+
|
| 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]) * af1
|
| 508 |
+
af0 = min(amax, af1 + af)
|
| 509 |
+
|
| 510 |
+
if sig0:
|
| 511 |
+
af0 = af0 if xpt0 > xpt1 else af1
|
| 512 |
+
sari = min(sari, lmin)
|
| 513 |
+
else:
|
| 514 |
+
af0 = af0 if xpt0 < xpt1 else af1
|
| 515 |
+
sari = max(sari, lmax)
|
| 516 |
+
else:
|
| 517 |
+
af0 = af
|
| 518 |
+
sari = xpt0
|
| 519 |
+
|
| 520 |
+
_sar.append(sari)
|
| 521 |
+
|
| 522 |
+
return pd.Series(_sar, index=ohlc.index)
|
| 523 |
+
|
| 524 |
+
def 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.asp
|
| 530 |
+
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] * length
|
| 537 |
+
psarbear = [None] * length
|
| 538 |
+
bull = True
|
| 539 |
+
af = iaf
|
| 540 |
+
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 = False
|
| 550 |
+
|
| 551 |
+
if bull:
|
| 552 |
+
if low1[i] < psar[i]:
|
| 553 |
+
bull = False
|
| 554 |
+
reverse = True
|
| 555 |
+
psar[i] = hp
|
| 556 |
+
lp = low1[i]
|
| 557 |
+
af = iaf
|
| 558 |
+
else:
|
| 559 |
+
if high1[i] > psar[i]:
|
| 560 |
+
bull = True
|
| 561 |
+
reverse = True
|
| 562 |
+
psar[i] = lp
|
| 563 |
+
hp = high1[i]
|
| 564 |
+
af = iaf
|
| 565 |
+
|
| 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_df
|
| 596 |
+
|
| 597 |
+
def 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 |
+
|
| 607 |
+
def 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 change
|
| 611 |
+
momentum = pd.Series(ohlc[column].diff()) ## 1 period momentum
|
| 612 |
+
_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 change
|
| 622 |
+
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 |
+
|
| 640 |
+
def FISH(ohlc, period=10, adjust=True, high="High", low="Low"):
|
| 641 |
+
"""Fisher Transform"""
|
| 642 |
+
med = (ohlc[high] + ohlc[low]) / 2
|
| 643 |
+
ndaylow = med.rolling(window=period).min()
|
| 644 |
+
ndayhigh = med.rolling(window=period).max()
|
| 645 |
+
raw = (2 * ((med - ndaylow) / (ndayhigh - ndaylow))) - 1
|
| 646 |
+
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 |
+
|
| 653 |
+
def 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()) / 2
|
| 658 |
+
kijun_sen = (ohlc[high].rolling(window=kijun_period).max() +
|
| 659 |
+
ohlc[low].rolling(window=kijun_period).min()) / 2
|
| 660 |
+
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_span
|
| 670 |
+
})
|
| 671 |
+
|
| 672 |
+
|
| 673 |
+
def 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) / 2
|
| 678 |
+
return pd.DataFrame({"DC_U": upper, "DC_L": lower, "DC_M": middle})
|
| 679 |
+
|
| 680 |
+
|
| 681 |
+
|
| 682 |
+
def 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 Flow
|
| 686 |
+
mf_sign = np.sign(tp.diff()) # Positive or negative money flow
|
| 687 |
+
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_sum
|
| 694 |
+
mfi = 100 - (100 / (1 + mfratio))
|
| 695 |
+
|
| 696 |
+
return pd.Series(mfi, name=f"{period} period MFI")
|
| 697 |
+
|
| 698 |
+
def 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 |
+
|
| 705 |
+
def 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 variable
|
| 708 |
+
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 indicator
|
| 711 |
+
oscillates between 0 and 100.
|
| 712 |
+
https://www.investopedia.com/terms/d/dynamicmomentumindex.asp
|
| 713 |
+
"""
|
| 714 |
+
|
| 715 |
+
def _get_time(close):
|
| 716 |
+
# Value available from 14th period
|
| 717 |
+
sd = close.rolling(5).std()
|
| 718 |
+
asd = sd.rolling(10).mean()
|
| 719 |
+
v = sd / asd
|
| 720 |
+
t = 14 / v.round()
|
| 721 |
+
t[t.isna()] = 0
|
| 722 |
+
t = t.map(lambda x: int(min(max(x, 5), 30)))
|
| 723 |
+
return t
|
| 724 |
+
|
| 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 |
+
|
| 738 |
+
def VPT(ohlcv, colvol="Volume", column="Close", open="Open", high="High", low="Low"):
|
| 739 |
+
"""Volume Price Trend"""
|
| 740 |
+
hilow = (ohlcv[high] - ohlcv[low]) * 100
|
| 741 |
+
openclose = (ohlcv[column] - ohlcv[open]) * 100
|
| 742 |
+
vol = ohlcv[colvol] / hilow
|
| 743 |
+
spreadvol = (openclose * vol).cumsum()
|
| 744 |
+
vpt = spreadvol + spreadvol
|
| 745 |
+
return pd.Series(vpt, name="VPT")
|
| 746 |
+
|
| 747 |
+
def 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 |
+
|
| 759 |
+
def 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 |
+
|
| 768 |
+
def 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 |
+
|
| 778 |
+
def AO(ohlc, high="High", low="Low"):
|
| 779 |
+
"""Awesome Oscillator"""
|
| 780 |
+
median_price = (ohlc[high] + ohlc[low]) / 2
|
| 781 |
+
ao = median_price.rolling(window=5).mean() - median_price.rolling(window=34).mean()
|
| 782 |
+
return pd.Series(ao, name="AO")
|
| 783 |
+
|
| 784 |
+
def 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 / DEMA9
|
| 790 |
+
return pd.Series(mass.rolling(window=25).sum(), name="MI")
|
| 791 |
+
|
| 792 |
+
|
| 793 |
+
def 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 |
+
|
| 798 |
+
def 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 |
+
|
| 812 |
+
def 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 / bpavg
|
| 821 |
+
nsp = sp / spavg
|
| 822 |
+
|
| 823 |
+
varg = ohlc[colvol].ewm(span=period, adjust=adjust).mean()
|
| 824 |
+
nv = ohlc[colvol] / varg
|
| 825 |
+
|
| 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 |
+
|
| 831 |
+
def 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 / bpavg
|
| 838 |
+
nsp = sp / spavg
|
| 839 |
+
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 |
+
|
| 843 |
+
def 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)) / 2
|
| 849 |
+
_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 ift
|
| 853 |
+
|
| 854 |
+
|
| 855 |
+
def 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 |
+
|
| 867 |
+
def 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 |
+
|
| 879 |
+
def 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]) / 3
|
| 882 |
+
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_dev
|
| 890 |
+
kc_lower = kc_middle - kc_mult * mean_dev
|
| 891 |
+
return pd.DataFrame({
|
| 892 |
+
"KC_MIDDLE": kc_middle,
|
| 893 |
+
"KC_UPPER": kc_upper,
|
| 894 |
+
"KC_LOWER": kc_lower
|
| 895 |
+
})
|
| 896 |
+
|
| 897 |
+
def 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_factor
|
| 901 |
+
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 |
+
|
| 905 |
+
def 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 |
+
|
| 921 |
+
def 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 Low
|
| 928 |
+
|
| 929 |
+
TR2 = pd.Series(
|
| 930 |
+
ohlc[high] - ohlc[close].shift()
|
| 931 |
+
).abs() # True Range = High less Previous Close
|
| 932 |
+
|
| 933 |
+
TR3 = pd.Series(
|
| 934 |
+
ohlc[close].shift() - ohlc[low]
|
| 935 |
+
).abs() # True Range = Previous Close less Low
|
| 936 |
+
|
| 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 |
+
|
| 943 |
+
def 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 |
+
|
| 952 |
+
def 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) * k
|
| 955 |
+
short_stop = ohlc[low].rolling(window=short_period).min() + ATR(ohlc, 22) * k
|
| 956 |
+
return pd.DataFrame({"CHANDELIER_Long": long_stop, "CHANDELIER_Short": short_stop})
|