Skip to content

Commit

Permalink
[GH-152] Extract Kaufman's Adaptive Moving Average (#153)
Browse files Browse the repository at this point in the history
The Efficiency Ratio (ER) is calculated by
dividing the price change over a period by the
absolute sum of the price movements that occurred
to achieve that change.

The resulting ratio ranges between 0 and 1 with
higher values representing a more efficient or
trending market.

The default column is close.

The default window is 10.

Formular:
* window_change = ABS(close - close[n])
* last_change = ABS(close-close[1])
* volatility = moving sum of last_change in n
* KER = window_change / volatility

Examples:
* `df['ker']` retrieves the 10 periods KER of the close price
* `df['high_5_ker']` retrieves 5 periods KER of the high price
  • Loading branch information
jealous committed Jun 23, 2023
1 parent 2bac991 commit b912fa5
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 19 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Supported statistics/indicators are:
* MFI: Money Flow Index
* VWMA: Volume Weighted Moving Average
* CHOP: Choppiness Index
* KER: Kaufman's efficiency ratio
* KAMA: Kaufman's Adaptive Moving Average
* PPO: Percentage Price Oscillator
* StochRSI: Stochastic RSI
Expand Down Expand Up @@ -669,6 +670,32 @@ Examples:
* `df['mfi']` retrieves the 14 periods MFI
* `df['mfi_6']` retrieves the 6 periods MFI


#### [KER - Kaufman's efficiency ratio](https://strategyquant.com/codebase/kaufmans-efficiency-ratio-ker/)

The Efficiency Ratio (ER) is calculated by
dividing the price change over a period by the
absolute sum of the price movements that occurred
to achieve that change.

The resulting ratio ranges between 0 and 1 with
higher values representing a more efficient or
trending market.

The default column is close.

The default window is 10.

Formular:
* window_change = ABS(close - close[n])
* last_change = ABS(close-close[1])
* volatility = moving sum of last_change in n
* KER = window_change / volatility

Examples:
* `df['ker']` retrieves the 10 periods KER of the close price
* `df['high_5_ker']` retrieves 5 periods KER of the high price

#### [KAMA - Kaufman's Adaptive Moving Average](https://school.stockcharts.com/doku.php?id=technical_indicators:kaufman_s_adaptive_moving_average)

Kaufman's Adaptive Moving Average is designed to account for market noise or
Expand Down
69 changes: 50 additions & 19 deletions stockstats.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ class StockDataFrame(pd.DataFrame):
WAVE_TREND_1 = 10
WAVE_TREND_2 = 21

ER = 10

KAMA_SLOW = 34
KAMA_FAST = 5

Expand Down Expand Up @@ -719,19 +721,9 @@ def _get_dmi(self):
self['adx'] = self.ema(self['dx'], self.ADX_EMA)
self['adxr'] = self.ema(self['adx'], self.ADXR_EMA)

def _get_um_dm(self):
""" Up move and down move
initialize up move and down move
"""
hd = self._col_delta('high')
self['um'] = (hd > 0) * hd
ld = -self._col_delta('low')
self['dm'] = (ld > 0) * ld

def _get_pdm_ndm(self, window):
hd = self._col_delta('high')
ld = -self._col_delta('low')
hd = self._col_diff('high')
ld = -self._col_diff('low')
p = ((hd > 0) & (hd > ld)) * hd
n = ((ld > 0) & (ld > hd)) * ld
if window > 1:
Expand Down Expand Up @@ -1495,6 +1487,48 @@ def _get_cmo(self, window=None):
res.iloc[0] = 0
self[column_name] = res

def ker(self, column, window):
col = self[column]
col_window_s = self._shift(col, -window)
window_diff = (col - col_window_s).abs()
diff = self._col_diff(column).abs()
volatility = self.mov_sum(diff, window)
ret = window_diff / volatility
ret.iloc[0] = 0
return ret

def _get_ker(self, column=None, window=None):
""" get Kaufman's efficiency ratio
The Efficiency Ratio (ER) is calculated by
dividing the price change over a period by the
absolute sum of the price movements that occurred
to achieve that change.
The resulting ratio ranges between 0 and 1 with
higher values representing a more efficient or
trending market.
The default column is close.
The default window is 10.
https://strategyquant.com/codebase/kaufmans-efficiency-ratio-ker/
Formular:
window_change = ABS(close - close[n])
last_change = ABS(close-close[1])
volatility = moving sum of last_change in n
KER = window_change / volatility
"""
if column is None and window is None:
column = 'close'
window = self.ER
column_name = 'ker'
else:
window = self.get_int_positive(window)
column_name = '{}_{}_ker'.format(column, window)

self[column_name] = self.ker(column, window)

def _get_kama(self, column, windows, fasts=None, slows=None):
""" get Kaufman's Adaptive Moving Average.
Implemented after
Expand All @@ -1516,11 +1550,7 @@ def _get_kama(self, column, windows, fasts=None, slows=None):
column_name = '{}_{}_kama_{}_{}'.format(column, window, fast, slow)

col = self[column]
col_window_s = self._shift(col, -window)
col_last = self._shift(col, -1)
change = (col - col_window_s).abs()
volatility = self.mov_sum((col - col_last).abs(), window)
efficiency_ratio = change / volatility
efficiency_ratio = self.ker(column, window)
fast_ema_smoothing = 2.0 / (fast + 1)
slow_ema_smoothing = 2.0 / (slow + 1)
smoothing_2 = fast_ema_smoothing - slow_ema_smoothing
Expand Down Expand Up @@ -1578,14 +1608,14 @@ def _get_rate(self):
"""
self['rate'] = self['close'].pct_change() * 100

def _col_delta(self, col):
def _col_diff(self, col):
ret = self[col].diff()
ret.iloc[0] = 0.0
return ret

def _get_delta(self, key):
key_to_delta = key.replace('_delta', '')
self[key] = self._col_delta(key_to_delta)
self[key] = self._col_diff(key_to_delta)
return self[key]

def _get_cross(self, key):
Expand Down Expand Up @@ -1683,6 +1713,7 @@ def handler(self):
('coppock',): self._get_coppock,
('ichimoku',): self._get_ichimoku,
('cti',): self._get_cti,
('ker',): self._get_ker,
}

def __init_not_exist_column(self, key):
Expand Down
15 changes: 15 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,21 @@ def test_mfi(self):
assert_that(mfi_15.loc[first], near_to(0.5))
assert_that(mfi_15.loc[last], near_to(0.6733))

def test_ker(self):
stock = self.get_stock_90days()
k = stock['ker']
assert_that(k[20110104], equal_to(0))
assert_that(k[20110105], equal_to(1))
assert_that(k[20110210], near_to(0.305))

k = stock['close_10_ker']
assert_that(k[20110104], equal_to(0))
assert_that(k[20110105], equal_to(1))
assert_that(k[20110210], near_to(0.305))

k = stock['high_5_ker']
assert_that(k[20110210], near_to(0.399))

def test_column_kama(self):
stock = self.get_stock_90days()
idx = 20110331
Expand Down

0 comments on commit b912fa5

Please sign in to comment.