59 lines
2.0 KiB
Python
59 lines
2.0 KiB
Python
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
|