feat(CORE-001D): close remaining boundary cleanup slices
This commit is contained in:
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import date, timedelta
|
||||
from math import isfinite
|
||||
from typing import Protocol
|
||||
|
||||
from app.models.backtest import ProviderRef
|
||||
@@ -35,6 +36,24 @@ class SyntheticOptionQuote:
|
||||
quantity: float
|
||||
mark: float
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
for field_name in ("position_id", "leg_id"):
|
||||
value = getattr(self, field_name)
|
||||
if not isinstance(value, str) or not value:
|
||||
raise ValueError(f"{field_name} is required")
|
||||
for field_name in ("spot", "strike", "quantity", "mark"):
|
||||
value = getattr(self, field_name)
|
||||
if not isinstance(value, (int, float)) or isinstance(value, bool) or not isfinite(float(value)):
|
||||
raise TypeError(f"{field_name} must be a finite number")
|
||||
if self.spot <= 0:
|
||||
raise ValueError("spot must be positive")
|
||||
if self.strike <= 0:
|
||||
raise ValueError("strike must be positive")
|
||||
if self.quantity <= 0:
|
||||
raise ValueError("quantity must be positive")
|
||||
if self.mark < 0:
|
||||
raise ValueError("mark must be non-negative")
|
||||
|
||||
|
||||
class HistoricalPriceSource(Protocol):
|
||||
def load_daily_closes(self, symbol: str, start_date: date, end_date: date) -> list[DailyClosePoint]:
|
||||
@@ -42,6 +61,17 @@ class HistoricalPriceSource(Protocol):
|
||||
|
||||
|
||||
class YFinanceHistoricalPriceSource:
|
||||
@staticmethod
|
||||
def _normalize_daily_close_row(*, row_date: object, close: object) -> DailyClosePoint | None:
|
||||
if close is None:
|
||||
return None
|
||||
if not hasattr(row_date, "date"):
|
||||
raise TypeError(f"historical row date must support .date(), got {type(row_date)!r}")
|
||||
normalized_close = float(close)
|
||||
if not isfinite(normalized_close):
|
||||
raise ValueError("historical close must be finite")
|
||||
return DailyClosePoint(date=row_date.date(), close=normalized_close)
|
||||
|
||||
def load_daily_closes(self, symbol: str, start_date: date, end_date: date) -> list[DailyClosePoint]:
|
||||
if yf is None:
|
||||
raise RuntimeError("yfinance is required to load historical backtest prices")
|
||||
@@ -50,10 +80,9 @@ class YFinanceHistoricalPriceSource:
|
||||
history = ticker.history(start=start_date.isoformat(), end=inclusive_end_date.isoformat(), interval="1d")
|
||||
rows: list[DailyClosePoint] = []
|
||||
for index, row in history.iterrows():
|
||||
close = row.get("Close")
|
||||
if close is None:
|
||||
continue
|
||||
rows.append(DailyClosePoint(date=index.date(), close=float(close)))
|
||||
point = self._normalize_daily_close_row(row_date=index, close=row.get("Close"))
|
||||
if point is not None:
|
||||
rows.append(point)
|
||||
return rows
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user