Files
vault-dash/tests/test_backtest_ui.py

203 lines
6.8 KiB
Python

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
def test_backtest_page_service_keeps_fixture_history_while_using_caller_portfolio_inputs() -> None:
service = BacktestPageService()
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=9680.0,
loan_amount=222000.0,
margin_call_ltv=0.80,
)
assert result.entry_spot == 100.0
assert result.scenario.initial_portfolio.underlying_units == 9680.0
assert result.scenario.initial_portfolio.loan_amount == 222000.0
assert result.scenario.initial_portfolio.margin_call_ltv == 0.80
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",
]
@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",
),
(
{
"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": 145000.0,
"margin_call_ltv": 0.75,
},
"Historical scenario starts undercollateralized",
),
],
)
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)
def test_backtest_page_service_fails_closed_outside_seeded_fixture_window() -> None:
service = BacktestPageService()
with pytest.raises(ValueError, match="deterministic fixture data only supports GLD"):
service.derive_entry_spot("GLD", date(2024, 1, 3), 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,
)
def test_backtest_preview_validation_requires_supported_fixture_window_even_with_supplied_entry_spot() -> None:
service = BacktestPageService()
with pytest.raises(ValueError, match="deterministic fixture data only supports GLD"):
service.validate_preview_inputs(
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,
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,
)