from datetime import date, timedelta from decimal import Decimal from app.service.allocation_backtest_service import ( AllocationBacktestEngine, AllocationBacktestService, BacktestObservation, ) def observation(day: int, cash: str, equity: str, liquid: bool = True) -> BacktestObservation: return BacktestObservation( trade_date=date(2026, 1, 1) + timedelta(days=day), returns_pct={ "cash_management_etf": Decimal(cash), "equity_etf": Decimal(equity), }, liquidity_observed=liquid, ) def test_backtest_compares_static_and_dynamic_performance() -> None: observations = [observation(0, "0", "10"), observation(1, "0", "-10")] result = AllocationBacktestEngine.run( observations, {"cash_management_etf": 50, "bond_etf": 0, "equity_etf": 50}, { observations[1].trade_date: { "cash_management_etf": 100, "bond_etf": 0, "equity_etf": 0, } }, ) assert result.observation_count == 2 assert result.static is not None and result.dynamic is not None assert result.static.total_return_pct == Decimal("-0.2500") assert result.dynamic.total_return_pct == Decimal("5.0000") assert result.dynamic_rebalance_count == 1 assert result.liquidity_history_coverage_pct == Decimal("100") assert result.status == "insufficient_history" def test_rolling_metrics_and_adequate_history_are_ready() -> None: observations = [observation(day, "1", "2") for day in range(20)] metrics = AllocationBacktestService._rolling_metrics(observations) assert {item.asset_class for item in metrics} == {"cash_management_etf", "equity_etf"} assert all(item.product_count == 20 for item in metrics) result = AllocationBacktestEngine.run( observations, {"cash_management_etf": 50, "bond_etf": 0, "equity_etf": 50}, {}, ) assert result.status == "ready" assert result.limitations == ("历史观察不足 120 个交易日,动态优化无法覆盖完整窗口。",) def test_backtest_reports_liquidity_coverage_limit() -> None: observations = [observation(day, "0", "0", day < 10) for day in range(20)] result = AllocationBacktestEngine.run( observations, {"cash_management_etf": 100, "bond_etf": 0, "equity_etf": 0}, {}, ) assert result.status == "partial" assert result.liquidity_history_coverage_pct == Decimal("50") assert any("80%" in limitation for limitation in result.limitations)