''' Momentum FSP numerical regressions. ''' import numpy as np import pytest from piker.fsp._momo import ema @pytest.mark.parametrize( 'args,kwargs,expected', [ ((), {}, [1., 1.5, 2.25]), ((None,), {}, [1., 1.5, 2.25]), ((None, None), {}, [1., 1.5, 2.25]), ((0.25,), {}, [1., 1.25, 1.6875]), ((), {'alpha': 0.25}, [1., 1.25, 1.6875]), ((), {'ylast': 4.}, [4., 3., 3.]), ((None, 4.), {}, [4., 3., 3.]), ((0.5, 4.), {}, [4., 3., 3.]), ], ) def test_ema_optional_arguments( args: tuple[float|None, ...], kwargs: dict[str, float], expected: list[float], ) -> None: ''' Accept omitted EMA defaults through the real Numba dispatcher. `ema()` exposed optional smoothing and seed arguments, but its eager signature accepted only explicit values or `None`. Numba's omitted-argument types therefore raised `TypeError` before the numerical kernel ran. Exercise positional and keyword omissions as well as explicit arguments, checking the resulting recurrence against hand-computed values. Import the production dispatcher so this catches signature regressions that a call to `ema.py_func` would miss. ''' signal: np.ndarray = np.array([1., 2., 3.]) result: np.ndarray = ema(signal, *args, **kwargs) np.testing.assert_allclose(result, expected) assert result.dtype == np.float64 def test_ema_single_sample_continuation() -> None: ''' Preserve the previous EMA when advancing one realtime sample. The old one-sample path multiplied an absent seed by a float, failing instead of initializing from the sample. Removing the eager Numba signature must retain the existing initialization fix and the previous-value update used by realtime RSI. Use a distinct seed and smoothing factor so copying either the seed or sample fails; verify an omitted seed uses the sole sample. ''' signal: np.ndarray = np.array([3.]) np.testing.assert_allclose(ema(signal), [3.]) np.testing.assert_allclose(ema(signal, 0.25, 7.), [6.])