From f67395796eb33e8b453a768ff188b8c40f91c1af Mon Sep 17 00:00:00 2001 From: Cedric Zhuang Date: Fri, 16 Jun 2023 22:18:22 +0800 Subject: [PATCH] [GH-143] Retrieve Chande Momentum Oscillator (#144) The Chande Momentum Oscillator (CMO) is a technical momentum indicator developed by Tushar Chande. https://www.investopedia.com/terms/c/chandemomentumoscillator.asp ``` CMO = 100 * ((sH - sL) / (sH + sL)) ``` where: * sH=the sum of higher closes over N periods * sL=the sum of lower closes of N periods --- README.md | 26 +++++++++++++++++++++++++- stockstats.py | 41 +++++++++++++++++++++++++++++++++++++++-- test.py | 13 +++++++++++++ 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 946e357..350aa0d 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.5.4 +VERSION: 0.5.5 ## Introduction @@ -725,6 +725,30 @@ BOP = (close - open) / (high - low) Example: * `df['bop']` returns the Balance of Power +#### [Chande Momentum Oscillator] (https://www.investopedia.com/terms/c/chandemomentumoscillator.asp) + +The Chande Momentum Oscillator (CMO) is a technical momentum +indicator developed by Tushar Chande. + +The formula calculates the difference between the sum of recent +gains and the sum of recent losses and then divides the result +by the sum of all price movements over the same period. + +The default window is 14. + +Formular: +``` +CMO = 100 * ((sH - sL) / (sH + sL)) +``` + +where: +* sH=the sum of higher closes over N periods +* sL=the sum of lower closes of N periods + +Examples: +* `df['cmo']` returns the CMO with a window of 14 +* `df['cmo_5']` returns the CMO with a window of 5 + ## Issues We use [Github Issues](https://github.com/jealous/stockstats/issues) to track diff --git a/stockstats.py b/stockstats.py index b597f69..e0b9d23 100644 --- a/stockstats.py +++ b/stockstats.py @@ -99,6 +99,8 @@ class StockDataFrame(pd.DataFrame): WR = 14 + CMO = 14 + WAVE_TREND_1 = 10 WAVE_TREND_2 = 21 @@ -728,9 +730,9 @@ def _get_um_dm(self): initialize up move and down move """ - hd = self['high_delta'] + hd = self._col_delta('high') self['um'] = (hd + hd.abs()) / 2 - ld = -self['low_delta'] + ld = -self._col_delta('low') self['dm'] = (ld + ld.abs()) / 2 def _get_pdm(self, windows): @@ -1215,6 +1217,37 @@ def _get_bop(self): divisor = self['high'] - self['low'] self['bop'] = dividend / divisor + def _get_cmo(self, window=None): + """ get Chande Momentum Oscillator + + The Chande Momentum Oscillator (CMO) is a technical momentum + indicator developed by Tushar Chande. + https://www.investopedia.com/terms/c/chandemomentumoscillator.asp + + CMO = 100 * ((sH - sL) / (sH + sL)) + + where: + * sH=the sum of higher closes over N periods + * sL=the sum of lower closes of N periods + """ + if window is None: + window = self.CMO + column_name = 'cmo' + else: + window = self.get_int_positive(window) + column_name = 'cmo_{}'.format(window) + + close_diff = self['close'].diff() + up = close_diff.clip(lower=0) + down = close_diff.clip(upper=0).abs() + sum_up = self._mov_sum(up, window) + sum_down = self._mov_sum(down, window) + dividend = sum_up - sum_down + divisor = sum_up + sum_down + res = 100 * dividend / divisor + res.iloc[0] = 0 + self[column_name] = res + def _get_kama(self, column, windows, fasts=None, slows=None): """ get Kaufman's Adaptive Moving Average. Implemented after @@ -1298,6 +1331,9 @@ def _get_rate(self): """ self['rate'] = self['close'].pct_change() * 100 + def _col_delta(self, col): + return self[col].diff() + def _get_delta(self, key): key_to_delta = key.replace('_delta', '') self[key] = self[key_to_delta].diff() @@ -1395,6 +1431,7 @@ def handler(self): ('aroon',): self._get_aroon, ('ao',): self._get_ao, ('bop',): self._get_bop, + ('cmo',): self._get_cmo, } def __init_not_exist_column(self, key): diff --git a/test.py b/test.py index bffea14..75edb80 100644 --- a/test.py +++ b/test.py @@ -687,6 +687,19 @@ def test_bop(self): assert_that(bop[20110104], near_to(0.5)) assert_that(bop[20110106], near_to(-0.207)) + def test_cmo(self): + stock = self.get_stock_30day() + cmo = stock['cmo'] + assert_that(cmo[20110104], equal_to(0)) + assert_that(cmo[20110126], near_to(7.023)) + assert_that(cmo[20110127], near_to(-16.129)) + + cmo_14 = stock['cmo_14'] + assert_that(cmo_14[20110126], near_to(7.023)) + + cmo_5 = stock['cmo_5'] + assert_that(cmo_5[20110126], near_to(7.895)) + def test_drop_column_inplace(self): stock = self._supor[:20] stock.columns.name = 'Luke'