from decimal import Decimal from app.service.dynamic_allocation_optimizer import ( AssetClassMarketMetric, DynamicAllocationOptimizer, ) def metric( asset_class: str, trailing_return: str, drawdown: str, turnover: str ) -> AssetClassMarketMetric: return AssetClassMarketMetric( asset_class=asset_class, trailing_120d_return_pct=Decimal(trailing_return), max_drawdown_pct=Decimal(drawdown), average_daily_turnover_amount=Decimal(turnover), product_count=2, ) def test_optimizer_tilts_strategic_weights_using_return_drawdown_and_liquidity() -> None: result = DynamicAllocationOptimizer.optimize( {"cash_management_etf": 15, "bond_etf": 45, "equity_etf": 40}, [ metric("cash_management_etf", "1", "0", "50000000"), metric("bond_etf", "5", "-3", "8000000"), metric("equity_etf", "15", "-20", "10000000"), ], return_target_lower_pct=Decimal("6"), max_drawdown_pct=Decimal("15"), liquidity_requirement="within_30_days", ) assert result.dynamic is True assert result.metric_coverage_pct == Decimal("100") assert result.weights["bond_etf"] > 45 assert result.weights["cash_management_etf"] < 15 assert sum(result.weights.values()) == 100 assert {item["asset_class"] for item in result.factors} == { "cash_management_etf", "bond_etf", "equity_etf" } def test_optimizer_applies_drawdown_cap_even_when_equity_metrics_are_strong() -> None: result = DynamicAllocationOptimizer.optimize( {"cash_management_etf": 5, "bond_etf": 15, "equity_etf": 80}, [ metric("cash_management_etf", "1", "0", "50000000"), metric("bond_etf", "2", "-1", "5000000"), metric("equity_etf", "30", "-5", "100000000"), ], return_target_lower_pct=Decimal("8"), max_drawdown_pct=Decimal("10"), liquidity_requirement="within_30_days", ) assert result.weights["equity_etf"] <= 20 assert sum(result.weights.values()) == 100