From 9cb82d7b3c48432a2d5628590013751551ea94c4 Mon Sep 17 00:00:00 2001 From: Cedric Zhuang Date: Sun, 30 Jul 2023 15:05:58 +0800 Subject: [PATCH] [GH-175] z-score based kdj is invalid. (#176) 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. --- README.md | 2 +- stockstats.py | 27 +++++++++++++++------------ test.py | 12 +++++++++++- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index b356c59..14d1d7d 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/stockstats.py b/stockstats.py index 7ac754c..d4786bc 100644 --- a/stockstats.py +++ b/stockstats.py @@ -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. @@ -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) @@ -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): @@ -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 @@ -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 @@ -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() @@ -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) @@ -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: @@ -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) @@ -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) diff --git a/test.py b/test.py index 2884584..3b660c2 100644 --- a/test.py +++ b/test.py @@ -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'] @@ -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))