- BacktestSettings dataclass with all configuration fields - BacktestSettingsRepository for persistence per workspace - Settings independent of portfolio configuration - Full validation for dates, symbols, LTV, etc. - 16 comprehensive tests Fields: - settings_id, name: identification - data_source: databento|yfinance|synthetic - dataset, schema: Databento configuration - start_date, end_date: date range - underlying_symbol, start_price, underlying_units: position config - loan_amount, margin_call_ltv: LTV analysis - template_slugs: strategies to test - cache_key, data_cost_usd: caching metadata - provider_ref: provider configuration
89 lines
3.2 KiB
Python
89 lines
3.2 KiB
Python
"""Backtest settings model for configuring backtest scenarios independently of portfolio settings."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from datetime import date
|
|
from typing import Literal
|
|
from uuid import UUID, uuid4
|
|
|
|
# Self type annotation
|
|
from app.models.backtest import ProviderRef
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class BacktestSettings:
|
|
"""Configuration for running backtests independent of portfolio settings.
|
|
|
|
These settings determine what data to fetch and how to run backtests,
|
|
separate from the actual portfolio configurations being tested.
|
|
"""
|
|
|
|
settings_id: UUID
|
|
name: str
|
|
data_source: Literal["databento", "yfinance", "synthetic"]
|
|
dataset: str
|
|
schema: str
|
|
start_date: date
|
|
end_date: date
|
|
underlying_symbol: Literal["GLD", "GC", "XAU"]
|
|
start_price: float
|
|
underlying_units: float
|
|
loan_amount: float
|
|
margin_call_ltv: float
|
|
template_slugs: tuple[str, ...]
|
|
cache_key: str
|
|
data_cost_usd: float
|
|
provider_ref: ProviderRef
|
|
|
|
def __post_init__(self) -> None:
|
|
if not self.settings_id:
|
|
raise ValueError("settings_id is required")
|
|
if not self.name:
|
|
raise ValueError("name is required")
|
|
if self.start_date > self.end_date:
|
|
raise ValueError("start_date must be on or before end_date")
|
|
if self.data_source not in ("databento", "yfinance", "synthetic"):
|
|
raise ValueError("data_source must be 'databento', 'yfinance', or 'synthetic'")
|
|
if self.underlying_symbol not in ("GLD", "GC", "XAU"):
|
|
raise ValueError("underlying_symbol must be 'GLD', 'GC', or 'XAU'")
|
|
if self.start_price < 0:
|
|
raise ValueError("start_price must be non-negative")
|
|
if self.underlying_units <= 0:
|
|
raise ValueError("underlying_units must be positive")
|
|
if self.loan_amount < 0:
|
|
raise ValueError("loan_amount must be non-negative")
|
|
if not 0 < self.margin_call_ltv < 1:
|
|
raise ValueError("margin_call_ltv must be between 0 and 1")
|
|
if not self.template_slugs:
|
|
raise ValueError("at least one template slug is required")
|
|
if self.data_cost_usd < 0:
|
|
raise ValueError("data_cost_usd must be non-negative")
|
|
|
|
@classmethod
|
|
def create_default(cls, name: str = "Default Backtest Settings") -> BacktestSettings:
|
|
"""Create default backtest settings configuration."""
|
|
return cls(
|
|
settings_id=uuid4(),
|
|
name=name,
|
|
data_source="databento",
|
|
dataset="XNAS.BASIC",
|
|
schema="ohlcv-1d",
|
|
start_date=date(2020, 1, 1),
|
|
end_date=date(2023, 12, 31),
|
|
underlying_symbol="GLD",
|
|
start_price=0.0,
|
|
underlying_units=1000.0,
|
|
loan_amount=0.0,
|
|
margin_call_ltv=0.75,
|
|
template_slugs=("default-template",),
|
|
cache_key="",
|
|
data_cost_usd=0.0,
|
|
provider_ref=ProviderRef(provider_id="default", pricing_mode="standard"),
|
|
)
|
|
|
|
|
|
# For backward compatibility - alias to existing models
|
|
BacktestScenario = "app.models.backtest.BacktestScenario"
|
|
# TemplateRef and ProviderRef imported from app.models.backtest
|