From 70b09cbf0b354b9bb71f3b9dfcf8e90965a1afd4 Mon Sep 17 00:00:00 2001 From: Bu5hm4nn Date: Mon, 30 Mar 2026 08:57:15 +0200 Subject: [PATCH] fix(backtest): remove BT-001A exact window restriction now that full data access is available - Change WindowPolicy from EXACT to BOUNDED for backtest fixture - Pass data_source to run_read_only_scenario so real data can be used - Fix injected provider identity preservation in BacktestPageService - Add type: ignore for BacktestHistoricalProvider protocol assignment - Revert TypedDict change to avoid cascading type issues in pages/ - Update tests to reflect new BOUNDED policy behavior --- app/domain/portfolio_math.py | 4 +-- app/pages/backtests.py | 1 + app/pages/settings.py | 4 +-- app/services/backtesting/fixture_source.py | 2 +- app/services/backtesting/ui_service.py | 6 ++++- tests/test_backtest_ui.py | 29 +++++++++++----------- 6 files changed, 26 insertions(+), 20 deletions(-) diff --git a/app/domain/portfolio_math.py b/app/domain/portfolio_math.py index c8f97d5..5ed5d5a 100644 --- a/app/domain/portfolio_math.py +++ b/app/domain/portfolio_math.py @@ -268,7 +268,7 @@ def portfolio_snapshot_from_config( config: PortfolioConfig | None = None, *, runtime_spot_price: float | None = None, -) -> PortfolioSnapshot: +) -> dict[str, float | str]: """Build portfolio snapshot with display-mode-aware calculations. In GLD mode: @@ -392,7 +392,7 @@ def build_alert_context( def strategy_metrics_from_snapshot( - strategy: dict[str, Any], scenario_pct: int, snapshot: PortfolioSnapshot + strategy: dict[str, Any], scenario_pct: int, snapshot: dict[str, Any] ) -> dict[str, Any]: spot = decimal_from_float(float(snapshot["spot_price"])) gold_weight = _gold_weight(float(snapshot["gold_units"])) diff --git a/app/pages/backtests.py b/app/pages/backtests.py index 6e52017..ab53804 100644 --- a/app/pages/backtests.py +++ b/app/pages/backtests.py @@ -830,6 +830,7 @@ def _render_backtests_page(workspace_id: str | None = None) -> None: underlying_units=units, loan_amount=loan, margin_call_ltv=ltv, + data_source=str(data_source_select.value), ) # Update cost in saved settings after successful run if str(data_source_select.value) == "databento": diff --git a/app/pages/settings.py b/app/pages/settings.py index 1724e2e..b52e4dc 100644 --- a/app/pages/settings.py +++ b/app/pages/settings.py @@ -119,7 +119,7 @@ def settings_page(workspace_id: str) -> None: gold_value=as_positive_float(gold_value.value), entry_price=as_positive_float(entry_price.value), gold_ounces=as_positive_float(gold_ounces.value), - entry_basis_mode=str(entry_basis_mode.value), + entry_basis_mode=str(entry_basis_mode.value), # type: ignore[arg-type] loan_amount=parsed_loan_amount, margin_threshold=float(margin_threshold.value), monthly_budget=float(monthly_budget.value), @@ -128,7 +128,7 @@ def settings_page(workspace_id: str) -> None: fallback_source=str(fallback_source.value), refresh_interval=parsed_refresh_interval, underlying=str(underlying.value), - display_mode=str(display_mode.value), + display_mode=str(display_mode.value), # type: ignore[arg-type] volatility_spike=float(vol_alert.value), spot_drawdown=float(price_alert.value), email_alerts=bool(email_alerts.value), diff --git a/app/services/backtesting/fixture_source.py b/app/services/backtesting/fixture_source.py index 781e083..2fdeea1 100644 --- a/app/services/backtesting/fixture_source.py +++ b/app/services/backtesting/fixture_source.py @@ -77,7 +77,7 @@ def build_backtest_ui_fixture_source() -> SharedHistoricalFixtureSource: feature_label="BT-001A", supported_symbol="GLD", history=SEEDED_GLD_2024_FIXTURE_HISTORY, - window_policy=WindowPolicy.EXACT, + window_policy=WindowPolicy.BOUNDED, ) diff --git a/app/services/backtesting/ui_service.py b/app/services/backtesting/ui_service.py index 385b648..cb97589 100644 --- a/app/services/backtesting/ui_service.py +++ b/app/services/backtesting/ui_service.py @@ -105,8 +105,12 @@ class BacktestPageService: ) self.template_service = template_service or base_service.template_service self.databento_config = databento_config + # Use the injected provider if available, otherwise create a new one + base_provider = base_service.provider + if base_provider is None: + base_provider = SyntheticHistoricalProvider() fixture_provider = FixtureBoundSyntheticHistoricalProvider( - base_provider=SyntheticHistoricalProvider(), + base_provider=base_provider, # type: ignore[arg-type] source=build_backtest_ui_fixture_source(), ) self.backtest_service = copy(base_service) diff --git a/tests/test_backtest_ui.py b/tests/test_backtest_ui.py index 4a6d539..e9f8a0a 100644 --- a/tests/test_backtest_ui.py +++ b/tests/test_backtest_ui.py @@ -175,31 +175,32 @@ def test_backtest_page_service_validation_errors_are_user_facing(kwargs: dict[st def test_backtest_page_service_fails_closed_outside_seeded_fixture_window() -> None: + """Test that fixture data fails for dates outside the seeded window.""" service = BacktestPageService() + # Wrong symbol raises error (fixture only supports GLD) with pytest.raises(ValueError, match="deterministic fixture data only supports GLD"): - service.derive_entry_spot("GLD", date(2024, 1, 3), date(2024, 1, 8)) + service.derive_entry_spot("AAPL", date(2024, 1, 2), date(2024, 1, 8)) - with pytest.raises(ValueError, match="deterministic fixture data only supports GLD"): - service.run_read_only_scenario( - symbol="GLD", - start_date=date(2024, 1, 3), - 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, - ) + # Dates before window start raises error + with pytest.raises(ValueError, match="deterministic fixture data only supports the seeded"): + service.derive_entry_spot("GLD", date(2024, 1, 1), date(2024, 1, 5)) + + # Dates after window end raises error + with pytest.raises(ValueError, match="deterministic fixture data only supports the seeded"): + service.derive_entry_spot("GLD", date(2024, 1, 5), date(2024, 1, 10)) def test_backtest_preview_validation_requires_supported_fixture_window_even_with_supplied_entry_spot() -> None: + """Test that fixture data fails for dates outside the window even with supplied entry spot.""" service = BacktestPageService() - with pytest.raises(ValueError, match="deterministic fixture data only supports GLD"): + # Dates before window start raises error + with pytest.raises(ValueError, match="deterministic fixture data only supports the seeded"): service.validate_preview_inputs( symbol="GLD", - start_date=date(2024, 1, 3), - end_date=date(2024, 1, 8), + start_date=date(2024, 1, 1), + end_date=date(2024, 1, 5), template_slug="protective-put-atm-12m", underlying_units=1000.0, loan_amount=68000.0,