Skip to content

Commit

Permalink
feat: Add McGinley Dynamic (#367)
Browse files Browse the repository at this point in the history
  • Loading branch information
LeeDongGeon1996 authored Apr 6, 2024
1 parent 18cad32 commit 4448f0f
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 0 deletions.
1 change: 1 addition & 0 deletions stock_indicators/indicators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from .donchian import (get_donchian)
from .dema import (get_dema)
from .dpo import (get_dpo)
from .dynamic import (get_dynamic)
from .elder_ray import (get_elder_ray)
from .ema import (get_ema)
from .epma import (get_epma)
Expand Down
56 changes: 56 additions & 0 deletions stock_indicators/indicators/dynamic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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.results import IndicatorResults, ResultBase
from stock_indicators.indicators.common.quote import Quote


def get_dynamic(quotes: Iterable[Quote], lookback_periods: int, k_factor: float = 0.6):
"""Get McGinley Dynamic calculated.
McGinley Dynamic is a more responsive variant of exponential moving average.
Parameters:
`quotes` : Iterable[Quote]
Historical price quotes.
`lookback_periods` : int
Number of periods in the lookback window.
`k_factor` : float, defaults 0.6
Range adjustment factor.
Returns:
`DynamicResults[DynamicResult]`
DynamicResults is list of DynamicResult with providing useful helper methods.
See more:
- [McGinley Dynamic Reference](https://python.stockindicators.dev/indicators/Dynamic/#content)
- [Helper Methods](https://python.stockindicators.dev/utilities/#content)
"""
results = CsIndicator.GetDynamic[Quote](CsList(Quote, quotes), lookback_periods, k_factor)
return DynamicResults(results, DynamicResult)


class DynamicResult(ResultBase):
"""
A wrapper class for a single unit of McGinley Dynamic results.
"""

@property
def dynamic(self) -> Optional[float]:
return self._csdata.Dynamic

@dynamic.setter
def dynamic(self, value):
self._csdata.Dynamic = value


_T = TypeVar("_T", bound=DynamicResult)
class DynamicResults(IndicatorResults[_T]):
"""
A wrapper class for the list of McGinley Dynamic results.
It is exactly same with built-in `list` except for that it provides
some useful helper methods written in CSharp implementation.
"""
41 changes: 41 additions & 0 deletions tests/test_dynamic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import pytest
from stock_indicators import indicators

class TestDynamic:
def test_standard(self, quotes):
results = indicators.get_dynamic(quotes, 14)

assert 502 == len(results)
assert 501 == len(list(filter(lambda x: x.dynamic is not None, results)))

r = results[1]
assert 212.9465 == round(float(r.dynamic), 4)

r = results[25]
assert 215.4801 == round(float(r.dynamic), 4)

r = results[250]
assert 256.0554== round(float(r.dynamic), 4)

r = results[501]
assert 245.7356 == round(float(r.dynamic), 4)

def test_bad_data(self, bad_quotes):
r = indicators.get_dynamic(bad_quotes, 15)

assert 502 == len(r)

def test_no_quotes(self, quotes):
r = indicators.get_dynamic([], 14)
assert 0 == len(r)

r = indicators.get_dynamic(quotes[:1], 14)
assert 1 == len(r)

def test_exceptions(self, quotes):
from System import ArgumentOutOfRangeException
with pytest.raises(ArgumentOutOfRangeException):
indicators.get_dynamic(quotes, 0)

with pytest.raises(ArgumentOutOfRangeException):
indicators.get_dynamic(quotes, 14, 0)

0 comments on commit 4448f0f

Please sign in to comment.