fix(CORE-001D): close boundary review gaps
This commit is contained in:
@@ -117,13 +117,18 @@ class EventComparisonPageService:
|
||||
) -> BacktestScenario:
|
||||
if not template_slugs:
|
||||
raise ValueError("Select at least one strategy template.")
|
||||
normalized_inputs = normalize_historical_scenario_inputs(
|
||||
underlying_units=underlying_units,
|
||||
loan_amount=loan_amount,
|
||||
margin_call_ltv=margin_call_ltv,
|
||||
)
|
||||
try:
|
||||
scenario = self.comparison_service.preview_scenario_from_inputs(
|
||||
preset_slug=preset_slug,
|
||||
template_slugs=template_slugs,
|
||||
underlying_units=underlying_units,
|
||||
loan_amount=loan_amount,
|
||||
margin_call_ltv=margin_call_ltv,
|
||||
underlying_units=normalized_inputs.underlying_units,
|
||||
loan_amount=normalized_inputs.loan_amount,
|
||||
margin_call_ltv=normalized_inputs.margin_call_ltv,
|
||||
)
|
||||
except ValueError as exc:
|
||||
if str(exc) == "loan_amount must be less than initial collateral value":
|
||||
@@ -134,9 +139,17 @@ class EventComparisonPageService:
|
||||
preset.window_end,
|
||||
)
|
||||
if preview:
|
||||
_validate_initial_collateral(underlying_units, preview[0].close, loan_amount)
|
||||
_validate_initial_collateral(
|
||||
normalized_inputs.underlying_units,
|
||||
preview[0].close,
|
||||
normalized_inputs.loan_amount,
|
||||
)
|
||||
raise
|
||||
_validate_initial_collateral(underlying_units, scenario.initial_portfolio.entry_spot, loan_amount)
|
||||
_validate_initial_collateral(
|
||||
normalized_inputs.underlying_units,
|
||||
scenario.initial_portfolio.entry_spot,
|
||||
normalized_inputs.loan_amount,
|
||||
)
|
||||
return scenario
|
||||
|
||||
def run_read_only_comparison(
|
||||
|
||||
@@ -52,38 +52,44 @@ class PriceFeed:
|
||||
self._cache = get_cache()
|
||||
|
||||
@staticmethod
|
||||
def _normalize_cached_price_payload(payload: object, *, expected_symbol: str) -> PriceData:
|
||||
def _required_payload_value(payload: Mapping[str, object], key: str, *, context: str) -> object:
|
||||
if key not in payload:
|
||||
raise TypeError(f"{context} is missing required field: {key}")
|
||||
return payload[key]
|
||||
|
||||
@classmethod
|
||||
def _normalize_cached_price_payload(cls, payload: object, *, expected_symbol: str) -> PriceData:
|
||||
if not isinstance(payload, Mapping):
|
||||
raise TypeError("cached price payload must be an object")
|
||||
payload_symbol = str(payload.get("symbol", expected_symbol)).strip().upper()
|
||||
normalized_symbol = expected_symbol.strip().upper()
|
||||
if payload_symbol != normalized_symbol:
|
||||
raise ValueError(f"cached symbol mismatch: {payload_symbol} != {normalized_symbol}")
|
||||
timestamp = payload.get("timestamp")
|
||||
timestamp = cls._required_payload_value(payload, "timestamp", context="cached price payload")
|
||||
if not isinstance(timestamp, str) or not timestamp.strip():
|
||||
raise TypeError("cached timestamp must be a non-empty ISO string")
|
||||
return PriceData(
|
||||
symbol=payload_symbol,
|
||||
price=float(payload["price"]),
|
||||
price=float(cls._required_payload_value(payload, "price", context="cached price payload")),
|
||||
currency=str(payload.get("currency", "USD")),
|
||||
timestamp=datetime.fromisoformat(timestamp),
|
||||
source=str(payload.get("source", "yfinance")),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _normalize_provider_price_payload(payload: object, *, expected_symbol: str) -> PriceData:
|
||||
@classmethod
|
||||
def _normalize_provider_price_payload(cls, payload: object, *, expected_symbol: str) -> PriceData:
|
||||
if not isinstance(payload, Mapping):
|
||||
raise TypeError("provider price payload must be an object")
|
||||
payload_symbol = str(payload.get("symbol", expected_symbol)).strip().upper()
|
||||
normalized_symbol = expected_symbol.strip().upper()
|
||||
if payload_symbol != normalized_symbol:
|
||||
raise ValueError(f"provider symbol mismatch: {payload_symbol} != {normalized_symbol}")
|
||||
timestamp = payload.get("timestamp")
|
||||
timestamp = cls._required_payload_value(payload, "timestamp", context="provider price payload")
|
||||
if not isinstance(timestamp, datetime):
|
||||
raise TypeError("provider timestamp must be a datetime")
|
||||
return PriceData(
|
||||
symbol=payload_symbol,
|
||||
price=float(payload["price"]),
|
||||
price=float(cls._required_payload_value(payload, "price", context="provider price payload")),
|
||||
currency=str(payload.get("currency", "USD")),
|
||||
timestamp=timestamp,
|
||||
source=str(payload.get("source", "yfinance")),
|
||||
|
||||
Reference in New Issue
Block a user