import numpy as np import pytest from trademind.indicators import atr, bollinger, donchian_position, ema, macd, roc, rsi, sma, true_range def test_ema_of_constant_series_is_constant(): values = np.full(50, 42.0) assert np.allclose(ema(values, 10), 42.0) def test_ema_reacts_faster_than_sma(): values = np.concatenate([np.full(30, 100.0), np.full(30, 110.0)]) fast = ema(values, 10)[35] slow = sma(values, 10)[35] assert fast > slow # EMA hat den Sprung stärker eingepreist def test_sma_matches_manual_mean(): values = np.arange(1.0, 11.0) result = sma(values, 3) assert np.isnan(result[:2]).all() assert result[2] == pytest.approx(2.0) assert result[-1] == pytest.approx(9.0) def test_rsi_bounds_and_extremes(): rising = np.arange(1.0, 60.0) values = rsi(rising, 14) finite = values[np.isfinite(values)] assert finite.min() >= 0.0 and finite.max() <= 100.0 assert finite[-1] == pytest.approx(100.0) # nur Gewinne falling = rising[::-1].copy() assert rsi(falling, 14)[-1] == pytest.approx(0.0) def test_rsi_of_flat_series_is_neutral(): values = rsi(np.full(60, 25.0), 14) assert values[-1] == pytest.approx(50.0) def test_true_range_covers_gaps(): high = np.array([10.0, 20.0]) low = np.array([9.0, 19.0]) close = np.array([9.5, 19.5]) tr = true_range(high, low, close) assert tr[0] == pytest.approx(1.0) assert tr[1] == pytest.approx(10.5) # Lücke gegenüber dem Vortagesschluss def test_atr_is_positive_and_warm(): n = 100 rng = np.random.default_rng(3) close = 100 + np.cumsum(rng.normal(0, 1, n)) high = close + 1.0 low = close - 1.0 values = atr(high, low, close, 14) assert np.isnan(values[:13]).all() assert (values[13:] > 0).all() def test_macd_histogram_is_difference(): values = 100 + np.cumsum(np.random.default_rng(1).normal(0, 1, 200)) line, signal, hist = macd(values) assert np.allclose(hist, line - signal) def test_bollinger_bands_are_ordered(): values = 100 + np.cumsum(np.random.default_rng(2).normal(0, 1, 200)) lower, mid, upper = bollinger(values, 20, 2.0) valid = ~np.isnan(mid) assert (lower[valid] <= mid[valid]).all() assert (mid[valid] <= upper[valid]).all() def test_roc_is_relative_change(): values = np.array([100.0] * 10 + [110.0]) assert roc(values, 10)[-1] == pytest.approx(0.10) def test_donchian_position_hits_extremes(): close = np.array([float(i) for i in range(1, 41)]) high = close + 0.0 low = close - 0.0 pos = donchian_position(high, low, close, 20) assert pos[-1] == pytest.approx(1.0) # Schluss auf dem Hoch der Range def test_indicators_reject_wrong_dimensions(): with pytest.raises(ValueError): ema(np.zeros((5, 2)), 3)