Skip to content

Commit

Permalink
[GH-175] z-score based kdj is invalid. (#176)
Browse files Browse the repository at this point in the history
The initial value of z-score is set to 0, which breaks the kdj
calucation.  We should use the first valid value to fill the gap.
  • Loading branch information
jealous authored Jul 30, 2023
1 parent 37ae9c1 commit 9cb82d7
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 14 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![codecov](https://codecov.io/gh/jealous/stockstats/branch/master/graph/badge.svg?token=IFMD1pVJ7T)](https://codecov.io/gh/jealous/stockstats)
[![pypi](https://img.shields.io/pypi/v/stockstats.svg)](https://pypi.python.org/pypi/stockstats)

VERSION: 0.6.1
VERSION: 0.6.2

## Introduction

Expand Down
27 changes: 15 additions & 12 deletions stockstats.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ def _get_r(self, meta: _Meta):
self[meta.name] = self.roc(self[meta.column], shift)

@staticmethod
def _shift(series: pd.Series, window: int):
def s_shift(series: pd.Series, window: int):
""" Shift the series
When window is negative, shift the past period to current.
Expand All @@ -386,11 +386,11 @@ def _get_s(self, meta: _Meta):
negative values meaning data in the past,
positive values meaning data in the future.
"""
self[meta.name] = self._shift(self[meta.column], meta.int)
self[meta.name] = self.s_shift(self[meta.column], meta.int)

def _get_log_ret(self, _: _Meta):
close = self['close']
self['log-ret'] = np.log(close / self._shift(close, -1))
self['log-ret'] = np.log(close / self.s_shift(close, -1))

def _get_c(self, meta: _Meta) -> pd.Series:
""" get the count of column in range (shifts)
Expand Down Expand Up @@ -424,7 +424,7 @@ def _shifted_columns(self,
col = self.get(column)
res = pd.DataFrame()
for i in shifts:
res[int(i)] = self._shift(col, i).values
res[int(i)] = self.s_shift(col, i).values
return res

def _get_max(self, meta: _Meta):
Expand Down Expand Up @@ -538,7 +538,7 @@ def _get_trix(self, meta: _Meta):
single = self.ema(self[meta.column], window)
double = self.ema(single, window)
triple = self.ema(double, window)
prev_triple = self._shift(triple, -1)
prev_triple = self.s_shift(triple, -1)
triple_change = self._delta(triple, -1)
self[meta.name] = triple_change * 100 / prev_triple

Expand Down Expand Up @@ -585,7 +585,7 @@ def _get_cci(self, meta: _Meta):
self[meta.name] = (tp - tp_sma) / (.015 * mad)

def _tr(self):
prev_close = self._shift(self['close'], -1)
prev_close = self.s_shift(self['close'], -1)
high = self['high']
low = self['low']
c1 = high - low
Expand Down Expand Up @@ -724,7 +724,10 @@ def _get_z(self, meta: _Meta):
col = self[meta.column]
mean = self.sma(col, window)
std = self.mov_std(col, window)
self[meta.name] = ((col - mean) / std).fillna(0.0)
value = (col - mean) / std
if len(value) > 1:
value.iloc[0] = value.iloc[1]
self[meta.name] = value

def _atr(self, window):
tr = self._tr()
Expand Down Expand Up @@ -854,8 +857,8 @@ def _get_cr(self, meta: _Meta):
"""
window = meta.int
middle = self._tp()
last_middle = self._shift(middle, -1)
ym = self._shift(middle, -1)
last_middle = self.s_shift(middle, -1)
ym = self.s_shift(middle, -1)
high = self['high']
low = self['low']
p1_m = pd.concat((last_middle, high), axis=1).min(axis=1)
Expand All @@ -871,7 +874,7 @@ def _get_cr(self, meta: _Meta):

def _shifted_cr_sma(self, cr, window):
cr_sma = self.sma(cr, window)
return self._shift(cr_sma, -int(window / 2.5 + 1))
return self.s_shift(cr_sma, -int(window / 2.5 + 1))

def _tp(self):
if 'amount' in self:
Expand Down Expand Up @@ -1336,7 +1339,7 @@ def _get_mfi(self, meta: _Meta):
window = meta.int
middle = self._tp()
money_flow = (middle * self["volume"]).fillna(0.0)
shifted = self._shift(middle, -1)
shifted = self.s_shift(middle, -1)
delta = (middle - shifted).fillna(0)
pos_flow = money_flow.mask(delta < 0, 0)
neg_flow = money_flow.mask(delta >= 0, 0)
Expand Down Expand Up @@ -1407,7 +1410,7 @@ def _get_cmo(self, meta: _Meta):

def ker(self, column, window):
col = self[column]
col_window_s = self._shift(col, -window)
col_window_s = self.s_shift(col, -window)
window_diff = (col - col_window_s).abs()
diff = self._col_diff(column).abs()
volatility = self.mov_sum(diff, window)
Expand Down
12 changes: 11 additions & 1 deletion test.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,16 @@ def test_column_kdjj(self):
assert_that(kdjk_3.loc[20110104], near_to(74.5614))
assert_that(kdjk_3.loc[20110120], near_to(7.37))

def test_z_kdj(self):
stock = self.get_stock_90days()
for col in ['open', 'high', 'low', 'close', 'volume']:
stock[col] = stock[f'{col}_20_z']
_ = stock[['kdjk', 'kdjd', 'kdjj']]
row = stock.loc[20110104]
assert_that(row['kdjk'], near_to(66.666))
assert_that(row['kdjd'], near_to(55.555))
assert_that(row['kdjj'], near_to(88.888))

def test_column_cross(self):
stock = self.get_stock_30days()
cross = stock['kdjk_3_x_kdjd_3']
Expand Down Expand Up @@ -865,7 +875,7 @@ def test_aroon(self):
def test_close_z(self):
stock = self._supor[:100]
_ = stock['close_14_z']
assert_that(stock.loc[20040817, 'close_14_z'], equal_to(0))
assert_that(stock.loc[20040817, 'close_14_z'], near_to(-0.7071))
assert_that(stock.loc[20040915, 'close_14_z'], near_to(2.005))
assert_that(stock.loc[20041014, 'close_14_z'], near_to(-2.014))

Expand Down

0 comments on commit 9cb82d7

Please sign in to comment.