-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add Chande Momentum Oscillator (CMO) (#370)
- Loading branch information
1 parent
ce944e6
commit ad9030c
Showing
3 changed files
with
102 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
from typing import Iterable, Optional, TypeVar | ||
|
||
from stock_indicators._cslib import CsIndicator | ||
from stock_indicators._cstypes import List as CsList | ||
from stock_indicators.indicators.common.helpers import RemoveWarmupMixin | ||
from stock_indicators.indicators.common.results import IndicatorResults, ResultBase | ||
from stock_indicators.indicators.common.quote import Quote | ||
|
||
|
||
def get_cmo(quotes: Iterable[Quote], lookback_periods: int): | ||
"""Get CMO calculated. | ||
The Chande Momentum Oscillator (CMO) is a momentum indicator | ||
depicting the weighted percentof higher prices in financial markets. | ||
Parameters: | ||
`quotes` : Iterable[Quote] | ||
Historical price quotes. | ||
`lookback_periods` : int | ||
Number of periods in the lookback window. | ||
Returns: | ||
`CMOResults[CMOResult]` | ||
CMOResults is list of CMOResult with providing useful helper methods. | ||
See more: | ||
- [CMO Reference](https://python.stockindicators.dev/indicators/Cmo/#content) | ||
- [Helper Methods](https://python.stockindicators.dev/utilities/#content) | ||
""" | ||
results = CsIndicator.GetCmo[Quote](CsList(Quote, quotes), lookback_periods) | ||
return CMOResults(results, CMOResult) | ||
|
||
|
||
class CMOResult(ResultBase): | ||
""" | ||
A wrapper class for a single unit of Chande Momentum Oscillator (CMO) results. | ||
""" | ||
|
||
@property | ||
def cmo(self) -> Optional[float]: | ||
return self._csdata.Cmo | ||
|
||
@cmo.setter | ||
def cmo(self, value): | ||
self._csdata.Cmo = value | ||
|
||
|
||
_T = TypeVar("_T", bound=CMOResult) | ||
class CMOResults(RemoveWarmupMixin, IndicatorResults[_T]): | ||
""" | ||
A wrapper class for the list of Chande Momentum Oscillator (CMO) results. | ||
It is exactly same with built-in `list` except for that it provides | ||
some useful helper methods written in CSharp implementation. | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import pytest | ||
from stock_indicators import indicators | ||
|
||
class TestCMO: | ||
def test_standard(self, quotes): | ||
results = indicators.get_cmo(quotes, 14) | ||
|
||
assert 502 == len(results) | ||
assert 488 == len(list(filter(lambda x: x.cmo is not None, results))) | ||
|
||
r = results[13] | ||
assert r.cmo is None | ||
|
||
r = results[14] | ||
assert 24.1081 == round(float(r.cmo), 4) | ||
|
||
r = results[249] | ||
assert 48.9614 == round(float(r.cmo), 4) | ||
|
||
r = results[501] | ||
assert -26.7502 == round(float(r.cmo), 4) | ||
|
||
def test_bad_data(self, bad_quotes): | ||
r = indicators.get_cmo(bad_quotes, 15) | ||
|
||
assert 502 == len(r) | ||
|
||
def test_no_quotes(self, quotes): | ||
r = indicators.get_cmo([], 5) | ||
assert 0 == len(r) | ||
|
||
r = indicators.get_cmo(quotes[:1], 5) | ||
assert 1 == len(r) | ||
|
||
def test_removed(self, quotes): | ||
results = indicators.get_cmo(quotes, 14).remove_warmup_periods() | ||
|
||
assert 488 == len(results) | ||
|
||
last = results.pop() | ||
assert -26.7502 == round(float(last.cmo), 4) | ||
|
||
def test_exceptions(self, quotes): | ||
from System import ArgumentOutOfRangeException | ||
with pytest.raises(ArgumentOutOfRangeException): | ||
indicators.get_cmo(quotes, 0) |