From 16665c8861fc4eefc2dca92ae7ea93210db2f02d Mon Sep 17 00:00:00 2001 From: DongGeon Lee Date: Mon, 15 Apr 2024 02:54:44 +0900 Subject: [PATCH] feat: Add optional params on Stochastic Oscillator (#372) --- stock_indicators/indicators/stoch.py | 17 +++++++-- tests/test_stoch.py | 53 +++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/stock_indicators/indicators/stoch.py b/stock_indicators/indicators/stoch.py index 6a5ad7b5..fe619dbc 100644 --- a/stock_indicators/indicators/stoch.py +++ b/stock_indicators/indicators/stoch.py @@ -2,12 +2,14 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList +from stock_indicators.indicators.common.enums import MAType 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_stoch(quotes: Iterable[Quote], lookback_periods: int = 14, signal_periods: int = 3, smooth_periods: int = 3): +def get_stoch(quotes: Iterable[Quote], lookback_periods: int = 14, signal_periods: int = 3, smooth_periods: int = 3, + k_factor: float = 3, d_factor: float = 2, ma_type: MAType = MAType.SMA): """Get Stochastic Oscillator calculated, with KDJ indexes. Stochastic Oscillatoris a momentum indicator that looks back N periods to produce a scale of 0 to 100. @@ -27,6 +29,16 @@ def get_stoch(quotes: Iterable[Quote], lookback_periods: int = 14, signal_period Smoothing period for the %K Oscillator. Use 3 for Slow or 1 for Fast. + `k_factor` : float, defaults 3 + Weight of %K in the %J calculation. + + `d_factor` : float, defaults 2 + Weight of %K in the %J calculation. + + `ma_type` : MAType, defaults MAType.SMA + Type of moving average to use. + See docs for instructions and options. + Returns: `StochResults[StochResult]` StochResults is list of StochResult with providing useful helper methods. @@ -35,7 +47,8 @@ def get_stoch(quotes: Iterable[Quote], lookback_periods: int = 14, signal_period - [Stochastic Oscillator Reference](https://python.stockindicators.dev/indicators/Stoch/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - stoch_results = CsIndicator.GetStoch[Quote](CsList(Quote, quotes), lookback_periods, signal_periods, smooth_periods) + stoch_results = CsIndicator.GetStoch[Quote](CsList(Quote, quotes), lookback_periods, signal_periods, smooth_periods, + k_factor, d_factor, ma_type.cs_value) return StochResults(stoch_results, StochResult) diff --git a/tests/test_stoch.py b/tests/test_stoch.py index 4dae2e55..9ef29172 100644 --- a/tests/test_stoch.py +++ b/tests/test_stoch.py @@ -1,5 +1,6 @@ import pytest from stock_indicators import indicators +from stock_indicators.indicators.common.enums import MAType class TestStoch: def test_standard(self, quotes): @@ -34,6 +35,43 @@ def test_standard(self, quotes): assert 35.5674 == round(float(r.signal), 4) assert 58.2712 == round(float(r.percent_j), 4) + def test_extended(self, quotes): + results = indicators.get_stoch(quotes, 9, 3, 3, 5, 4, MAType.SMMA) + + assert 502 == len(results) + assert 494 == len(list(filter(lambda x: x.k is not None, results))) + assert 494 == len(list(filter(lambda x: x.d is not None, results))) + + r = results[7] + assert r.k is None + assert r.d is None + assert r.j is None + + r = results[8] + assert 81.9178 == round(float(r.k), 4) + assert 81.9178 == round(float(r.d), 4) + assert 81.9178 == round(float(r.j), 4) + + r = results[17] + assert 82.5181 == round(float(r.k), 4) + assert 76.2603 == round(float(r.d), 4) + assert 107.5491 == round(float(r.j), 4) + + r = results[149] + assert 77.1571 == round(float(r.k), 4) + assert 72.8206 == round(float(r.d), 4) + assert 94.5030 == round(float(r.j), 4) + + r = results[249] + assert 74.3652 == round(float(r.k), 4) + assert 75.5660 == round(float(r.d), 4) + assert 69.5621 == round(float(r.j), 4) + + r = results[501] + assert 46.9807 == round(float(r.k), 4) + assert 32.0413 == round(float(r.d), 4) + assert 106.7382 == round(float(r.j), 4) + def test_no_signal(self, quotes): results = indicators.get_stoch(quotes, 5, 1, 3) @@ -56,7 +94,6 @@ def test_fast(self, quotes): assert 91.6233 == round(float(r.oscillator), 4) assert 36.0608 == round(float(r.signal), 4) - def test_fast_small(self, quotes): results = indicators.get_stoch(quotes, 1, 10, 1) @@ -70,6 +107,20 @@ def test_bad_data(self, bad_quotes): r = indicators.get_stoch(bad_quotes, 15) assert 502 == len(r) + def test_no_quotes(self, quotes): + r = indicators.get_stoch([]) + assert 0 == len(r) + + r = indicators.get_stoch(quotes[:1]) + assert 1 == len(r) + + def test_boundary(self, quotes): + results = indicators.get_stoch(quotes, 14, 3, 3) + + assert 0 == len(list(filter(lambda x: x.k is not None and (x.k < 0 or x.k > 100), results))) + assert 0 == len(list(filter(lambda x: x.d is not None and (x.d < 0 or x.d > 100), results))) + assert 0 == len(list(filter(lambda x: x.j is not None and (x.d < 0 or x.d > 100), results))) + def test_removed(self, quotes): results = indicators.get_stoch(quotes, 14, 3, 3).remove_warmup_periods()