"""Tests for cross-sectional IC evaluation.""" import numpy as np import pandas as pd from eval.metrics import evaluate_cross_sectional def test_cross_sectional_keys_present(): dates = pd.date_range("2024-01-01", periods=10) cols = ["a", "b", "c"] rng = np.random.default_rng(0) signals = pd.DataFrame(rng.standard_normal((10, 3)), index=dates, columns=cols) returns = pd.DataFrame(rng.standard_normal((10, 3)), index=dates, columns=cols) res = evaluate_cross_sectional(signals, returns) for key in ( "ic_mean", "ic_std", "ir", "rank_ic_mean", "rank_ic_std", "rank_ir", "hit_rate", "n_periods", ): assert key in res def test_perfect_signal_has_positive_rank_ic(): # When the signal equals next-period returns, rank IC should be ~1 each day. dates = pd.date_range("2024-01-01", periods=8) cols = ["a", "b", "c"] rng = np.random.default_rng(42) returns = pd.DataFrame(rng.standard_normal((8, 3)), index=dates, columns=cols) signals = returns.copy() # perfect foresight res = evaluate_cross_sectional(signals, returns) assert res["rank_ic_mean"] > 0.99 assert res["hit_rate"] == 1.0 assert res["n_periods"] == 8 def test_inverted_signal_has_negative_rank_ic(): dates = pd.date_range("2024-01-01", periods=6) cols = ["a", "b", "c"] rng = np.random.default_rng(7) returns = pd.DataFrame(rng.standard_normal((6, 3)), index=dates, columns=cols) signals = -returns # perfectly wrong res = evaluate_cross_sectional(signals, returns) assert res["rank_ic_mean"] < -0.99 def test_single_stock_falls_back_to_rolling(): dates = pd.date_range("2024-01-01", periods=40) rng = np.random.default_rng(1) signals = pd.DataFrame({"a": rng.standard_normal(40)}, index=dates) returns = pd.DataFrame({"a": rng.standard_normal(40)}, index=dates) res = evaluate_cross_sectional(signals, returns) # Rolling fallback still yields the standard metric keys. assert "rank_ic_mean" in res assert res["n_periods"] > 0