refactor(pre-alpha): align preview and runtime fixture validation

This commit is contained in:
Bu5hm4nn
2026-03-26 12:11:45 +01:00
parent 68275c4d18
commit 18fd0681ca
4 changed files with 79 additions and 21 deletions

View File

@@ -127,7 +127,9 @@ class EventComparisonService:
if not selected_template_slugs: if not selected_template_slugs:
raise ValueError("Event comparison requires at least one template slug") raise ValueError("Event comparison requires at least one template slug")
resolved_history = history or self._load_preset_history(preset) resolved_history = self._load_preset_history(preset) if history is None else history
if not resolved_history:
raise ValueError("Event comparison history must not be empty")
scenario_portfolio = materialize_backtest_portfolio_state( scenario_portfolio = materialize_backtest_portfolio_state(
symbol=preset.symbol, symbol=preset.symbol,
underlying_units=initial_portfolio.underlying_units, underlying_units=initial_portfolio.underlying_units,

View File

@@ -68,7 +68,6 @@ class BacktestPageService:
template_service=self.template_service, template_service=self.template_service,
provider=None, provider=None,
) )
if backtest_service is None:
provider = self.backtest_service.provider provider = self.backtest_service.provider
provider.source = DeterministicBacktestFixtureSource() provider.source = DeterministicBacktestFixtureSource()
@@ -122,11 +121,13 @@ class BacktestPageService:
raise ValueError("Template selection is required") raise ValueError("Template selection is required")
self.template_service.get_template(template_slug) self.template_service.get_template(template_slug)
resolved_entry_spot = ( derived_entry_spot = self.derive_entry_spot(normalized_symbol, start_date, end_date)
entry_spot if entry_spot is not None else self.derive_entry_spot(normalized_symbol, start_date, end_date) if entry_spot is not None and entry_spot != derived_entry_spot:
raise ValueError(
f"Supplied entry spot ${entry_spot:,.2f} does not match derived historical entry spot ${derived_entry_spot:,.2f}"
) )
_validate_initial_collateral(underlying_units, resolved_entry_spot, loan_amount) _validate_initial_collateral(underlying_units, derived_entry_spot, loan_amount)
return resolved_entry_spot return derived_entry_spot
def run_read_only_scenario( def run_read_only_scenario(
self, self,

View File

@@ -170,12 +170,13 @@ def test_backtest_page_service_fails_closed_outside_seeded_fixture_window() -> N
) )
def test_backtest_preview_validation_reuses_supplied_entry_spot() -> None: def test_backtest_preview_validation_requires_supported_fixture_window_even_with_supplied_entry_spot() -> None:
service = BacktestPageService() service = BacktestPageService()
entry_spot = service.validate_preview_inputs( with pytest.raises(ValueError, match="deterministic fixture data only supports GLD"):
service.validate_preview_inputs(
symbol="GLD", symbol="GLD",
start_date=date(2024, 1, 2), start_date=date(2024, 1, 3),
end_date=date(2024, 1, 8), end_date=date(2024, 1, 8),
template_slug="protective-put-atm-12m", template_slug="protective-put-atm-12m",
underlying_units=1000.0, underlying_units=1000.0,
@@ -184,4 +185,18 @@ def test_backtest_preview_validation_reuses_supplied_entry_spot() -> None:
entry_spot=100.0, entry_spot=100.0,
) )
assert entry_spot == 100.0
def test_backtest_preview_validation_rejects_mismatched_supplied_entry_spot() -> None:
service = BacktestPageService()
with pytest.raises(ValueError, match="does not match derived historical entry spot"):
service.validate_preview_inputs(
symbol="GLD",
start_date=date(2024, 1, 2),
end_date=date(2024, 1, 8),
template_slug="protective-put-atm-12m",
underlying_units=1000.0,
loan_amount=68000.0,
margin_call_ltv=0.75,
entry_spot=99.0,
)

View File

@@ -315,3 +315,43 @@ def test_event_comparison_uses_preset_defaults_only_when_template_slugs_are_omit
"protective-put-90pct-12m", "protective-put-90pct-12m",
"ladder-50-50-atm-95pct-12m", "ladder-50-50-atm-95pct-12m",
] ]
with pytest.raises(ValueError, match="at least one template slug"):
service.materialize_scenario(
service.event_preset_service.get_preset("gld-jan-2024-selloff"),
initial_portfolio=BacktestPortfolioState(
currency="USD",
underlying_units=1000.0,
entry_spot=100.0,
loan_amount=68_000.0,
margin_call_ltv=0.75,
),
template_slugs=(),
)
def test_event_comparison_materialize_scenario_rejects_explicit_empty_history() -> None:
provider = SyntheticHistoricalProvider(
source=FakeHistorySource(FIXTURE_HISTORY),
implied_volatility=0.35,
risk_free_rate=0.0,
)
service = EventComparisonService(
provider=provider,
template_service=StrategyTemplateService(),
event_preset_service=EventPresetService(repository=FileEventPresetRepository()),
)
with pytest.raises(ValueError, match="history must not be empty"):
service.materialize_scenario(
service.event_preset_service.get_preset("gld-jan-2024-selloff"),
initial_portfolio=BacktestPortfolioState(
currency="USD",
underlying_units=1000.0,
entry_spot=100.0,
loan_amount=68_000.0,
margin_call_ltv=0.75,
),
template_slugs=("protective-put-atm-12m",),
history=[],
)