Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GH-143] Retrieve Chande Momentum Oscillator #144

Merged
merged 1 commit into from
Jun 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 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.5.4
VERSION: 0.5.5

## Introduction

Expand Down Expand Up @@ -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
Expand Down
41 changes: 39 additions & 2 deletions stockstats.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ class StockDataFrame(pd.DataFrame):

WR = 14

CMO = 14

WAVE_TREND_1 = 10
WAVE_TREND_2 = 21

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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):
Expand Down
13 changes: 13 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down