from datetime import date, timedelta from decimal import Decimal from app.service.allocation_backtest_service import AllocationBacktestService, BacktestPrice def series(start: str, daily_change_pct: str, turnover: str | None) -> list[BacktestPrice]: value = Decimal("100") start_date = date.fromisoformat(start) points: list[BacktestPrice] = [] for offset in range(150): value *= Decimal("1") + Decimal(daily_change_pct) / Decimal("100") points.append(BacktestPrice( trade_date=start_date + timedelta(days=offset), close_price=value, turnover_amount=Decimal(turnover) if turnover is not None else None, )) return points def test_walk_forward_backtest_compares_dynamic_and_static_without_lookahead() -> None: result = AllocationBacktestService.run( { "cash_management_etf": series("2026-01-01", "0.01", "50000000"), "bond_etf": series("2026-01-01", "0.03", "8000000"), "equity_etf": series("2026-01-01", "0.08", "10000000"), }, {"cash_management_etf": 15, "bond_etf": 45, "equity_etf": 40}, return_target_lower_pct=Decimal("6"), max_drawdown_pct=Decimal("15"), liquidity_requirement="within_30_days", ) assert result.status == "ready" assert result.observation_count > 0 assert result.dynamic_rebalance_count > 0 assert result.dynamic_total_return_pct is not None assert result.static_total_return_pct is not None def test_backtest_refuses_to_compare_when_a_quality_approved_asset_class_is_missing() -> None: result = AllocationBacktestService.run( {"bond_etf": series("2026-01-01", "0.03", None)}, {"cash_management_etf": 15, "bond_etf": 45, "equity_etf": 40}, return_target_lower_pct=Decimal("6"), max_drawdown_pct=Decimal("15"), liquidity_requirement="within_30_days", ) assert result.status == "data_quality_required" assert result.limitations == ("missing_quality_approved_asset_class",)