from decimal import Decimal from app.service.asset_allocation_service import AssetAllocationService from app.service.dynamic_allocation_optimizer import ( AssetClassMarketMetric, DynamicAllocationOptimizer, ) def metric( asset_class: str, return_pct: str, drawdown: str, turnover: str ) -> AssetClassMarketMetric: return AssetClassMarketMetric( asset_class=asset_class, trailing_120d_return_pct=Decimal(return_pct), max_drawdown_pct=Decimal(drawdown), average_daily_turnover_amount=Decimal(turnover), product_count=2, ) def test_strategic_weights_apply_horizon_liquidity_and_drawdown_constraints() -> None: weights = AssetAllocationService._strategic_weights("C1", 6, "daily", Decimal("10")) assert weights == {"cash_management_etf": 65, "bond_etf": 35, "equity_etf": 0} weights = AssetAllocationService._strategic_weights("C5", 72, "over_30_days", Decimal("30")) assert weights == {"cash_management_etf": 5, "bond_etf": 10, "equity_etf": 85} def test_optimizer_uses_return_drawdown_and_liquidity_evidence() -> None: metrics = [ metric("cash_management_etf", "2", "1", "20000000"), metric("bond_etf", "6", "8", "5000000"), metric("equity_etf", "12", "25", "1000000"), ] result = DynamicAllocationOptimizer.optimize( {"cash_management_etf": 15, "bond_etf": 45, "equity_etf": 40}, metrics, return_target_lower_pct=Decimal("6"), max_drawdown_pct=Decimal("15"), liquidity_requirement="within_7_days", ) assert result.dynamic is True assert sum(result.weights.values()) == 100 assert result.weights["equity_etf"] <= 40 assert result.metric_coverage_pct == Decimal("100") evidence = {item["asset_class"]: item for item in result.factors} assert evidence["equity_etf"]["composite_score"] is not None def test_optimizer_falls_back_to_static_weights_when_coverage_is_insufficient() -> None: strategic = {"cash_management_etf": 30, "bond_etf": 50, "equity_etf": 20} result = DynamicAllocationOptimizer.optimize( strategic, [metric("bond_etf", "6", "8", "5000000")], return_target_lower_pct=Decimal("6"), max_drawdown_pct=Decimal("15"), liquidity_requirement="within_7_days", ) assert result.dynamic is False assert result.weights == strategic assert result.metric_coverage_pct == Decimal("33.33333333333333333333333333") assert result.factors[0]["composite_score"] is None