import numpy as np from trademind.features import ( FEATURE_NAMES, N_FEATURES, build_feature_matrix, compute_features, required_bars, ) from .conftest import make_candles def test_matrix_has_expected_shape(candles, rules): matrix = build_feature_matrix(candles, rules) assert matrix is not None assert matrix.values.shape == (len(candles), N_FEATURES) assert matrix.first_valid == required_bars(rules) - 1 def test_all_feature_values_are_finite_and_bounded(candles, rules): matrix = build_feature_matrix(candles, rules) valid = matrix.values[matrix.first_valid :] assert np.isfinite(valid).all() assert np.abs(valid).max() <= 8.0 def test_too_short_history_returns_none(rules): short = make_candles(n=50) assert build_feature_matrix(short, rules) is None assert compute_features(short, rules) is None def test_snapshot_exposes_raw_indicators(candles, rules): snapshot = compute_features(candles, rules) assert snapshot is not None assert snapshot.price == float(candles.close[-1]) assert snapshot.atr > 0 assert 0.0 <= snapshot.rsi <= 100.0 assert len(snapshot.values) == len(FEATURE_NAMES) assert set(snapshot.as_dict()) == set(FEATURE_NAMES) def test_snapshot_before_warmup_is_none(candles, rules): matrix = build_feature_matrix(candles, rules) assert matrix.snapshot(matrix.first_valid - 1) is None assert matrix.snapshot(matrix.first_valid) is not None def test_uptrend_produces_positive_trend_distance(rules): up = make_candles(n=500, trend=0.001, noise=0.0005, seed=11) snapshot = compute_features(up, rules) features = snapshot.as_dict() assert features["trend_dist"] > 0 assert features["ema_spread"] > 0 def test_downtrend_produces_negative_trend_distance(rules): down = make_candles(n=500, trend=-0.001, noise=0.0005, seed=12) features = compute_features(down, rules).as_dict() assert features["trend_dist"] < 0 assert features["ema_spread"] < 0 def test_features_are_scale_invariant(rules): """Ein zehnfach höherer Kurs darf die normierten Merkmale kaum verändern.""" cheap = make_candles(n=400, start_price=100.0, seed=5) expensive = make_candles(n=400, start_price=1_000.0, seed=5) a = compute_features(cheap, rules).values b = compute_features(expensive, rules).values assert np.allclose(a, b, atol=1e-8) def test_time_features_are_on_the_unit_circle(candles, rules): snapshot = compute_features(candles, rules).as_dict() radius = snapshot["time_sin"] ** 2 + snapshot["time_cos"] ** 2 assert radius == 1.0 or abs(radius - 1.0) < 1e-9 def test_matrix_rows_match_pointwise_snapshots(candles, rules): matrix = build_feature_matrix(candles, rules) index = matrix.first_valid + 25 assert np.allclose(matrix.snapshot(index).values, matrix.values[index])