from __future__ import annotations from datetime import date import pytest from app.services.backtesting.ui_service import BacktestPageService def test_backtest_page_service_uses_fixture_window_for_deterministic_run() -> None: service = BacktestPageService() entry_spot = service.derive_entry_spot("GLD", date(2024, 1, 2), date(2024, 1, 8)) result = service.run_read_only_scenario( 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, ) assert entry_spot == 100.0 assert result.scenario.initial_portfolio.entry_spot == 100.0 assert result.run_result.template_results[0].summary_metrics.margin_threshold_breached_unhedged is True assert result.run_result.template_results[0].summary_metrics.margin_threshold_breached_hedged is False assert [point.date.isoformat() for point in result.run_result.template_results[0].daily_path] == [ "2024-01-02", "2024-01-03", "2024-01-04", "2024-01-05", "2024-01-08", ] def test_backtest_non_default_template_slug_runs_successfully() -> None: service = BacktestPageService() options = service.template_options("GLD") non_default_slug = str(options[1]["slug"]) result = service.run_read_only_scenario( symbol="GLD", start_date=date(2024, 1, 2), end_date=date(2024, 1, 8), template_slug=non_default_slug, underlying_units=1000.0, loan_amount=68000.0, margin_call_ltv=0.75, ) assert result.scenario.template_refs[0].slug == non_default_slug assert result.run_result.template_results[0].template_slug == non_default_slug @pytest.mark.parametrize( ("kwargs", "message"), [ ( { "symbol": "", "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, }, "Symbol is required", ), ( { "symbol": "GLD", "start_date": date(2024, 1, 8), "end_date": date(2024, 1, 2), "template_slug": "protective-put-atm-12m", "underlying_units": 1000.0, "loan_amount": 68000.0, "margin_call_ltv": 0.75, }, "Start date must be on or before end date", ), ( { "symbol": "GLD", "start_date": date(2024, 1, 2), "end_date": date(2024, 1, 8), "template_slug": "protective-put-atm-12m", "underlying_units": 0.0, "loan_amount": 68000.0, "margin_call_ltv": 0.75, }, "Underlying units must be positive", ), ( { "symbol": "TLT", "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, }, "BT-001A backtests are currently limited to GLD on this page", ), ], ) def test_backtest_page_service_validation_errors_are_user_facing(kwargs: dict[str, object], message: str) -> None: service = BacktestPageService() with pytest.raises(ValueError, match=message): service.run_read_only_scenario(**kwargs)