diff --git a/app/services/backtesting/ui_service.py b/app/services/backtesting/ui_service.py index 31c7bc1..cb6b6e9 100644 --- a/app/services/backtesting/ui_service.py +++ b/app/services/backtesting/ui_service.py @@ -11,7 +11,11 @@ from app.models.backtest import ( ProviderRef, TemplateRef, ) -from app.services.backtesting.historical_provider import DailyClosePoint, SyntheticHistoricalProvider +from app.services.backtesting.historical_provider import ( + DailyClosePoint, + SyntheticHistoricalProvider, + SyntheticOptionQuote, +) from app.services.backtesting.service import BacktestService from app.services.strategy_templates import StrategyTemplateService @@ -58,22 +62,46 @@ class BacktestPageRunResult: entry_spot: float +class FixtureBoundHistoricalProvider: + def __init__(self, base_provider: SyntheticHistoricalProvider) -> None: + self.base_provider = base_provider + self.source = DeterministicBacktestFixtureSource() + self.provider_id = base_provider.provider_id + self.pricing_mode = base_provider.pricing_mode + self.implied_volatility = base_provider.implied_volatility + self.risk_free_rate = base_provider.risk_free_rate + + def load_history(self, symbol: str, start_date: date, end_date: date) -> list[DailyClosePoint]: + rows = self.source.load_daily_closes(symbol, start_date, end_date) + filtered = [row for row in rows if start_date <= row.date <= end_date] + return sorted(filtered, key=lambda row: row.date) + + def validate_provider_ref(self, provider_ref: ProviderRef) -> None: + self.base_provider.validate_provider_ref(provider_ref) + + def resolve_expiry(self, trading_days: list[DailyClosePoint], as_of_date: date, target_expiry_days: int) -> date: + return self.base_provider.resolve_expiry(trading_days, as_of_date, target_expiry_days) + + def price_option(self, **kwargs: object) -> SyntheticOptionQuote: + return self.base_provider.price_option(**kwargs) + + @staticmethod + def intrinsic_value(*, option_type: str, spot: float, strike: float) -> float: + return SyntheticHistoricalProvider.intrinsic_value(option_type=option_type, spot=spot, strike=strike) + + class BacktestPageService: def __init__( self, backtest_service: BacktestService | None = None, template_service: StrategyTemplateService | None = None, ) -> None: - self.template_service = template_service or StrategyTemplateService() base_service = backtest_service or BacktestService( - template_service=self.template_service, + template_service=template_service, provider=None, ) - fixture_provider = SyntheticHistoricalProvider( - source=DeterministicBacktestFixtureSource(), - implied_volatility=base_service.provider.implied_volatility, - risk_free_rate=base_service.provider.risk_free_rate, - ) + self.template_service = template_service or base_service.template_service + fixture_provider = FixtureBoundHistoricalProvider(base_service.provider) self.backtest_service = BacktestService( provider=fixture_provider, template_service=self.template_service, diff --git a/tests/test_backtest_ui.py b/tests/test_backtest_ui.py index a91c86f..dba7590 100644 --- a/tests/test_backtest_ui.py +++ b/tests/test_backtest_ui.py @@ -230,7 +230,12 @@ def test_backtest_page_service_does_not_mutate_injected_backtest_service() -> No ) injected_service = BacktestService(provider=provider) - BacktestPageService(backtest_service=injected_service) + page_service = 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 + assert page_service.template_service is injected_service.template_service + assert page_service.backtest_service.provider.implied_volatility == 0.2 + assert page_service.backtest_service.provider.risk_free_rate == 0.01 + seeded_history = page_service.backtest_service.provider.load_history("GLD", date(2024, 1, 2), date(2024, 1, 8)) + assert seeded_history[0].close == 100.0