fix(pre-alpha): preserve injected backtest services
This commit is contained in:
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
from math import isclose
|
||||||
|
|
||||||
from app.domain.backtesting_math import materialize_backtest_portfolio_state
|
from app.domain.backtesting_math import materialize_backtest_portfolio_state
|
||||||
from app.models.backtest import (
|
from app.models.backtest import (
|
||||||
@@ -10,7 +11,7 @@ from app.models.backtest import (
|
|||||||
ProviderRef,
|
ProviderRef,
|
||||||
TemplateRef,
|
TemplateRef,
|
||||||
)
|
)
|
||||||
from app.services.backtesting.historical_provider import DailyClosePoint
|
from app.services.backtesting.historical_provider import DailyClosePoint, SyntheticHistoricalProvider
|
||||||
from app.services.backtesting.service import BacktestService
|
from app.services.backtesting.service import BacktestService
|
||||||
from app.services.strategy_templates import StrategyTemplateService
|
from app.services.strategy_templates import StrategyTemplateService
|
||||||
|
|
||||||
@@ -64,12 +65,19 @@ class BacktestPageService:
|
|||||||
template_service: StrategyTemplateService | None = None,
|
template_service: StrategyTemplateService | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.template_service = template_service or StrategyTemplateService()
|
self.template_service = template_service or StrategyTemplateService()
|
||||||
self.backtest_service = backtest_service or BacktestService(
|
base_service = backtest_service or BacktestService(
|
||||||
template_service=self.template_service,
|
template_service=self.template_service,
|
||||||
provider=None,
|
provider=None,
|
||||||
)
|
)
|
||||||
provider = self.backtest_service.provider
|
fixture_provider = SyntheticHistoricalProvider(
|
||||||
provider.source = DeterministicBacktestFixtureSource()
|
source=DeterministicBacktestFixtureSource(),
|
||||||
|
implied_volatility=base_service.provider.implied_volatility,
|
||||||
|
risk_free_rate=base_service.provider.risk_free_rate,
|
||||||
|
)
|
||||||
|
self.backtest_service = BacktestService(
|
||||||
|
provider=fixture_provider,
|
||||||
|
template_service=self.template_service,
|
||||||
|
)
|
||||||
|
|
||||||
def template_options(self, symbol: str = "GLD") -> list[dict[str, str | int]]:
|
def template_options(self, symbol: str = "GLD") -> list[dict[str, str | int]]:
|
||||||
return [
|
return [
|
||||||
@@ -122,7 +130,12 @@ class BacktestPageService:
|
|||||||
|
|
||||||
self.template_service.get_template(template_slug)
|
self.template_service.get_template(template_slug)
|
||||||
derived_entry_spot = self.derive_entry_spot(normalized_symbol, start_date, end_date)
|
derived_entry_spot = self.derive_entry_spot(normalized_symbol, start_date, end_date)
|
||||||
if entry_spot is not None and entry_spot != derived_entry_spot:
|
if entry_spot is not None and not isclose(
|
||||||
|
entry_spot,
|
||||||
|
derived_entry_spot,
|
||||||
|
rel_tol=BacktestService.ENTRY_SPOT_REL_TOLERANCE,
|
||||||
|
abs_tol=BacktestService.ENTRY_SPOT_ABS_TOLERANCE,
|
||||||
|
):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Supplied entry spot ${entry_spot:,.2f} does not match derived historical entry spot ${derived_entry_spot:,.2f}"
|
f"Supplied entry spot ${entry_spot:,.2f} does not match derived historical entry spot ${derived_entry_spot:,.2f}"
|
||||||
)
|
)
|
||||||
|
|||||||
10
tests/helpers_backtest_sources.py
Normal file
10
tests/helpers_backtest_sources.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from app.services.backtesting.historical_provider import DailyClosePoint
|
||||||
|
|
||||||
|
|
||||||
|
class StaticBacktestSource:
|
||||||
|
def load_daily_closes(self, symbol: str, start_date: date, end_date: date) -> list[DailyClosePoint]:
|
||||||
|
return [DailyClosePoint(date=date(2024, 1, 3), close=123.0)]
|
||||||
@@ -4,7 +4,10 @@ from datetime import date
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from app.services.backtesting.historical_provider import SyntheticHistoricalProvider
|
||||||
|
from app.services.backtesting.service import BacktestService
|
||||||
from app.services.backtesting.ui_service import BacktestPageService
|
from app.services.backtesting.ui_service import BacktestPageService
|
||||||
|
from tests.helpers_backtest_sources import StaticBacktestSource
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_page_service_uses_fixture_window_for_deterministic_run() -> None:
|
def test_backtest_page_service_uses_fixture_window_for_deterministic_run() -> None:
|
||||||
@@ -186,6 +189,23 @@ def test_backtest_preview_validation_requires_supported_fixture_window_even_with
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_backtest_preview_validation_accepts_close_supplied_entry_spot() -> None:
|
||||||
|
service = BacktestPageService()
|
||||||
|
|
||||||
|
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=100.005,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert entry_spot == 100.0
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_preview_validation_rejects_mismatched_supplied_entry_spot() -> None:
|
def test_backtest_preview_validation_rejects_mismatched_supplied_entry_spot() -> None:
|
||||||
service = BacktestPageService()
|
service = BacktestPageService()
|
||||||
|
|
||||||
@@ -200,3 +220,17 @@ def test_backtest_preview_validation_rejects_mismatched_supplied_entry_spot() ->
|
|||||||
margin_call_ltv=0.75,
|
margin_call_ltv=0.75,
|
||||||
entry_spot=99.0,
|
entry_spot=99.0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_backtest_page_service_does_not_mutate_injected_backtest_service() -> None:
|
||||||
|
provider = SyntheticHistoricalProvider(
|
||||||
|
source=StaticBacktestSource(),
|
||||||
|
implied_volatility=0.2,
|
||||||
|
risk_free_rate=0.01,
|
||||||
|
)
|
||||||
|
injected_service = BacktestService(provider=provider)
|
||||||
|
|
||||||
|
BacktestPageService(backtest_service=injected_service)
|
||||||
|
|
||||||
|
history = injected_service.provider.load_history("GLD", date(2024, 1, 3), date(2024, 1, 3))
|
||||||
|
assert history[0].close == 123.0
|
||||||
|
|||||||
Reference in New Issue
Block a user