From 86583268dde59548e2acc6f3dee408457dd6d86d Mon Sep 17 00:00:00 2001 From: Gregory Morse Date: Thu, 9 Apr 2026 11:42:03 +0200 Subject: [PATCH] fix: validate accuracy is a positive even integer upfront (closes #25) calc_support_resistance and get_extrema now raise a clear ValueError with a descriptive message when accuracy is not a positive even integer, instead of letting findiff raise a cryptic traceback deep in the call stack. The root cause of the original reports (default accuracy=1) was already fixed by changing the default to accuracy=2. --- CHANGELOG.md | 8 ++++++++ setup.py | 2 +- tests/test_trendln.py | 21 +++++++++++++++++++++ trendln/__init__.py | 4 ++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9df58e9..33d92a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ Change Log =========== +0.1.13 +------- +- Added upfront validation of the ``accuracy`` parameter in + ``calc_support_resistance`` and ``get_extrema``: raises a clear + ``ValueError('accuracy must be a positive even integer')`` instead of a + cryptic findiff traceback when an odd or non-integer value is supplied; + the default remains ``accuracy=2`` (closes #25) + 0.1.12 ------- - `calc_support_resistance` and `get_extrema` now accept string names for their diff --git a/setup.py b/setup.py index 4a24b65..4a01807 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ setup( name='trendln', - version="0.1.12", + version="0.1.13", description='Support and Resistance Trend lines Calculator for Financial Analysis', long_description=long_description, long_description_content_type='text/markdown', diff --git a/tests/test_trendln.py b/tests/test_trendln.py index 43ccafb..43757e3 100644 --- a/tests/test_trendln.py +++ b/tests/test_trendln.py @@ -279,6 +279,27 @@ def test_tuple_both_none_raises(self): with pytest.raises(ValueError): calc_support_resistance((None, None)) + def test_accuracy_must_be_int(self): + with pytest.raises(ValueError, match='accuracy'): + calc_support_resistance(DATA_SIMPLE, accuracy=2.0) + + def test_accuracy_must_be_positive(self): + with pytest.raises(ValueError, match='accuracy'): + calc_support_resistance(DATA_SIMPLE, accuracy=0) + + def test_accuracy_must_be_even(self): + with pytest.raises(ValueError, match='accuracy'): + calc_support_resistance(DATA_SIMPLE, accuracy=1) + + def test_accuracy_odd_raises_on_get_extrema(self): + with pytest.raises(ValueError, match='accuracy'): + get_extrema(DATA_SIMPLE, accuracy=3) + + def test_accuracy_even_values_accepted(self): + # 2, 4, 6, 8 should all be accepted without error + for acc in (2, 4, 6, 8): + calc_support_resistance(DATA_SIMPLE, accuracy=acc) + # --------------------------------------------------------------------------- # String aliases for method / extmethod constants (issue #14) diff --git a/trendln/__init__.py b/trendln/__init__.py index 13350f7..2cc9c12 100644 --- a/trendln/__init__.py +++ b/trendln/__init__.py @@ -516,6 +516,8 @@ def _resolve(explicit, default_key): def get_extrema(h, extmethod=METHOD_NUMDIFF, accuracy=2): extmethod = _resolve_name(extmethod, _EXTMETHOD_NAMES, 'extmethod') + if not type(accuracy) is int or accuracy <= 0 or accuracy % 2 != 0: + raise ValueError('accuracy must be a positive even integer (e.g. 2, 4, 6, 8)') #h must be single dimensional array-like object e.g. List, np.ndarray, pd.Series if type(h) is tuple and len(h) == 2 and (h[0] is None or check_num_alike(h[0])) and (h[1] is None or check_num_alike(h[1])) and (not h[0] is None or not h[1] is None): hmin, hmax = h[0], h[1] @@ -591,6 +593,8 @@ def calc_support_resistance(h, extmethod = METHOD_NUMDIFF, method=METHOD_NSQURED raise ValueError('house_prob_iter must be of type int') if not type(sortError) is bool: raise ValueError('sortError must be True of False') + if not type(accuracy) is int or accuracy <= 0 or accuracy % 2 != 0: + raise ValueError('accuracy must be a positive even integer (e.g. 2, 4, 6, 8)') #h = hist.Close.tolist() if type(h) is tuple and len(h) == 2 and (h[0] is None or check_num_alike(h[0])) and (h[1] is None or check_num_alike(h[1])) and (not h[0] is None or not h[1] is None): if not h[0] is None and not h[1] is None and len(h[0]) != len(h[1]): #not strict requirement, but contextually ideal