# src/web/api/v2/schemas/calibration_risk.py from typing import Literal, Optional, Dict, Any from pydantic import BaseModel, Field, model_validator class StopConfigSchema(BaseModel): type: Literal["fixed", "trailing", "atr"] stop_fraction: Optional[float] = Field(None, gt=0) atr_period: Optional[int] = Field(None, gt=1) atr_multiplier: Optional[float] = Field(None, gt=0) @model_validator(mode="after") def validate_by_type(self): if self.type in ("fixed", "trailing"): if self.stop_fraction is None: raise ValueError( "stop_fraction required for fixed/trailing stop" ) if self.type == "atr": if self.atr_period is None: raise ValueError( "atr_period required for ATR stop" ) if self.atr_multiplier is None: raise ValueError( "atr_multiplier required for ATR stop" ) return self # class StopConfigSchema(BaseModel): # type: Literal["fixed", "trailing", "atr"] # stop_fraction: Optional[float] = Field(None, gt=0) # atr_period: Optional[int] = Field(None, gt=1) # atr_multiplier: Optional[float] = Field(None, gt=0) class RiskConfigSchema(BaseModel): risk_fraction: float = Field(..., gt=0, lt=1) max_position_fraction: float = Field(..., gt=0, lt=1) class GlobalRiskRulesSchema(BaseModel): max_drawdown_pct: float = Field(..., gt=0, lt=1) daily_loss_limit_pct: Optional[float] = Field(None, gt=0, lt=1) max_consecutive_losses: Optional[int] = Field(None, gt=0) cooldown_bars: Optional[int] = Field(None, ge=0) class CalibrationRiskInspectRequest(BaseModel): symbol: str timeframe: str stop: StopConfigSchema risk: RiskConfigSchema global_rules: GlobalRiskRulesSchema account_equity: float = Field(..., gt=0) class CalibrationRiskInspectResponse(BaseModel): valid: bool status: Literal["ok", "warning", "fail"] checks: Dict[str, Any] message: str class CalibrationRiskValidateResponse(BaseModel): valid: bool status: Literal["ok", "warning", "fail"] checks: Dict[str, Any] message: str # ⬇️ NUEVO series: Dict[str, Any]