feat(EXEC-001): add hedge strategy builder
This commit is contained in:
@@ -125,8 +125,10 @@ def test_strategy_selection_engine_uses_named_templates(monkeypatch: pytest.Monk
|
||||
]
|
||||
|
||||
|
||||
def test_strategy_template_service_catalog_reads_named_templates() -> None:
|
||||
catalog = StrategyTemplateService().catalog_items()
|
||||
def test_strategy_template_service_catalog_reads_named_templates(tmp_path: Path) -> None:
|
||||
catalog = StrategyTemplateService(
|
||||
repository=FileStrategyTemplateRepository(tmp_path / "strategy_templates.json")
|
||||
).catalog_items()
|
||||
|
||||
assert [item["label"] for item in catalog] == [
|
||||
"Protective Put ATM",
|
||||
@@ -142,3 +144,77 @@ def test_strategy_template_service_catalog_reads_named_templates() -> None:
|
||||
"laddered_put_50_50_atm_otm95",
|
||||
"laddered_put_33_33_33_atm_otm95_otm90",
|
||||
]
|
||||
|
||||
|
||||
def test_strategy_template_service_creates_and_persists_custom_protective_template(tmp_path: Path) -> None:
|
||||
repository = FileStrategyTemplateRepository(tmp_path / "strategy_templates.json")
|
||||
service = StrategyTemplateService(repository=repository)
|
||||
|
||||
template = service.create_custom_template(
|
||||
display_name="Crash Guard 97%",
|
||||
template_kind="protective_put",
|
||||
target_expiry_days=180,
|
||||
strike_pcts=(0.97,),
|
||||
)
|
||||
|
||||
assert template.display_name == "Crash Guard 97%"
|
||||
assert template.slug == "crash-guard-97"
|
||||
assert template.target_expiry_days == 180
|
||||
assert template.legs[0].strike_rule.value == 0.97
|
||||
assert template.tags == ("custom", "protective_put")
|
||||
assert service.get_template("crash-guard-97").display_name == "Crash Guard 97%"
|
||||
|
||||
|
||||
def test_strategy_template_service_rejects_duplicate_custom_template_name(tmp_path: Path) -> None:
|
||||
repository = FileStrategyTemplateRepository(tmp_path / "strategy_templates.json")
|
||||
service = StrategyTemplateService(repository=repository)
|
||||
service.create_custom_template(
|
||||
display_name="Crash Guard 97%",
|
||||
template_kind="protective_put",
|
||||
target_expiry_days=180,
|
||||
strike_pcts=(0.97,),
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="Template name already exists"):
|
||||
service.create_custom_template(
|
||||
display_name="Crash Guard 97%",
|
||||
template_kind="protective_put",
|
||||
target_expiry_days=90,
|
||||
strike_pcts=(0.92,),
|
||||
)
|
||||
|
||||
|
||||
def test_strategy_template_service_catalog_includes_custom_ladder_template(tmp_path: Path) -> None:
|
||||
repository = FileStrategyTemplateRepository(tmp_path / "strategy_templates.json")
|
||||
service = StrategyTemplateService(repository=repository)
|
||||
service.create_custom_template(
|
||||
display_name="Crash Ladder 98/92",
|
||||
template_kind="laddered_put",
|
||||
target_expiry_days=270,
|
||||
strike_pcts=(0.98, 0.92),
|
||||
weights=(0.5, 0.5),
|
||||
)
|
||||
|
||||
custom_item = next(item for item in service.catalog_items() if item["label"] == "Crash Ladder 98/92")
|
||||
|
||||
assert custom_item["coverage"] == "Layered"
|
||||
assert custom_item["estimated_cost"] > 0
|
||||
assert custom_item["downside_put_legs"] == [
|
||||
{"allocation_weight": 0.5, "strike_pct": 0.98},
|
||||
{"allocation_weight": 0.5, "strike_pct": 0.92},
|
||||
]
|
||||
|
||||
|
||||
def test_strategy_template_service_catalog_custom_cost_reflects_expiry_days(tmp_path: Path) -> None:
|
||||
repository = FileStrategyTemplateRepository(tmp_path / "strategy_templates.json")
|
||||
service = StrategyTemplateService(repository=repository)
|
||||
service.create_custom_template(
|
||||
display_name="Crash Guard 95 180d",
|
||||
template_kind="protective_put",
|
||||
target_expiry_days=180,
|
||||
strike_pcts=(0.95,),
|
||||
)
|
||||
|
||||
custom_item = next(item for item in service.catalog_items() if item["label"] == "Crash Guard 95 180d")
|
||||
|
||||
assert custom_item["estimated_cost"] == 3.49
|
||||
|
||||
Reference in New Issue
Block a user