test: define benchmark-relative performance contract

This commit is contained in:
ao gong
2026-08-21 22:15:05 +08:00
parent 19fe22b01a
commit de2f9494fc
2 changed files with 74 additions and 0 deletions
+48
View File
@@ -10,6 +10,7 @@ from quant_engine.metrics import (
TRADING_DAYS_PER_YEAR,
annualized_return,
annualized_volatility,
benchmark_summary,
calmar_ratio,
max_drawdown,
sharpe_ratio,
@@ -91,3 +92,50 @@ def test_short_and_empty_series_return_zero() -> None:
assert annualized_volatility(pd.Series([0.01])) == 0.0
assert max_drawdown(pd.Series([0.01])) == 0.0
assert win_rate(pd.Series(dtype=float)) == 0.0
def test_benchmark_summary_uses_aligned_active_returns_and_regression() -> None:
dates = pd.date_range("2026-01-05", periods=4, freq="B")
benchmark = pd.Series([-0.01, 0.0, 0.01, 0.02], index=dates)
portfolio = 0.001 + 1.5 * benchmark
active = portfolio - benchmark
result = benchmark_summary(portfolio, benchmark)
assert result["n_observations"] == 4
assert result["tracking_error"] == pytest.approx(
active.std() * np.sqrt(TRADING_DAYS_PER_YEAR)
)
assert result["information_ratio"] == pytest.approx(
active.mean() / active.std() * np.sqrt(TRADING_DAYS_PER_YEAR)
)
assert result["beta"] == pytest.approx(1.5)
assert result["alpha"] == pytest.approx(1.001**TRADING_DAYS_PER_YEAR - 1.0)
def test_benchmark_summary_rejects_silent_calendar_alignment() -> None:
portfolio = pd.Series([0.01, 0.02], index=pd.date_range("2026-01-05", periods=2))
benchmark = pd.Series([0.01, 0.02], index=pd.date_range("2026-01-06", periods=2))
with pytest.raises(ValueError, match="matching indexes"):
benchmark_summary(portfolio, benchmark)
def test_benchmark_summary_rejects_missing_observations() -> None:
dates = pd.date_range("2026-01-05", periods=2)
portfolio = pd.Series([0.01, np.nan], index=dates)
benchmark = pd.Series([0.0, 0.01], index=dates)
with pytest.raises(ValueError, match="finite"):
benchmark_summary(portfolio, benchmark)
def test_benchmark_summary_marks_constant_benchmark_regression_unestimable() -> None:
dates = pd.date_range("2026-01-05", periods=3)
portfolio = pd.Series([0.01, -0.01, 0.02], index=dates)
benchmark = pd.Series([0.0, 0.0, 0.0], index=dates)
result = benchmark_summary(portfolio, benchmark)
assert np.isnan(result["alpha"])
assert np.isnan(result["beta"])
+26
View File
@@ -265,3 +265,29 @@ def test_factor_backtest_starts_at_first_signal_instead_of_price_warmup() -> Non
pd.Series([1.0, 1.1, 1.2], index=dates[2:], name="nav"),
)
assert result.stats()["n_days"] == 3
def test_factor_backtest_exposes_net_benchmark_metrics() -> None:
dates = _calendar()
scores = pd.DataFrame({"A": [1.0]}, index=dates[:1])
prices = pd.DataFrame({"A": [10.0, 10.0, 11.0, 11.0]}, index=dates)
result = run_factor_backtest_research(
scores,
prices,
prices,
top_k=1,
execution_price_field="open",
valuation_price_field="close",
config=ExecutionConfig(
commission_bps=0,
stamp_tax_bps=0,
slippage_bps=0,
min_trade_amount=0,
),
)
benchmark = pd.Series([0.0, 0.01, -0.01, 0.0], index=dates)
relative = result.benchmark_stats(benchmark)
assert relative["n_observations"] == len(result.returns)
assert relative["tracking_error"] > 0