# -*- coding: utf-8 -*- """Backtest API schemas.""" from __future__ import annotations from typing import Any, Dict, List, Optional from pydantic import BaseModel, Field class BacktestRunRequest(BaseModel): code: Optional[str] = Field(None, description="仅回测指定股票") force: bool = Field(False, description="强制重新计算") eval_window_days: Optional[int] = Field(None, ge=1, le=120, description="评估窗口(交易日数)") min_age_days: Optional[int] = Field(None, ge=0, le=365, description="分析记录最小天龄(0=不限)") limit: int = Field(200, ge=1, le=2000, description="最多处理的分析记录数") class BacktestRunResponse(BaseModel): processed: int = Field(..., description="候选记录数") saved: int = Field(..., description="写入回测结果数") completed: int = Field(..., description="完成回测数") insufficient: int = Field(..., description="数据不足数") errors: int = Field(..., description="错误数") class BacktestResultItem(BaseModel): analysis_history_id: int code: str analysis_date: Optional[str] = None eval_window_days: int engine_version: str eval_status: str evaluated_at: Optional[str] = None operation_advice: Optional[str] = None position_recommendation: Optional[str] = None start_price: Optional[float] = None end_close: Optional[float] = None max_high: Optional[float] = None min_low: Optional[float] = None stock_return_pct: Optional[float] = None direction_expected: Optional[str] = None direction_correct: Optional[bool] = None outcome: Optional[str] = None stop_loss: Optional[float] = None take_profit: Optional[float] = None hit_stop_loss: Optional[bool] = None hit_take_profit: Optional[bool] = None first_hit: Optional[str] = None first_hit_date: Optional[str] = None first_hit_trading_days: Optional[int] = None simulated_entry_price: Optional[float] = None simulated_exit_price: Optional[float] = None simulated_exit_reason: Optional[str] = None simulated_return_pct: Optional[float] = None class BacktestResultsResponse(BaseModel): total: int page: int limit: int items: List[BacktestResultItem] = Field(default_factory=list) class PerformanceMetrics(BaseModel): scope: str code: Optional[str] = None eval_window_days: int engine_version: str computed_at: Optional[str] = None total_evaluations: int completed_count: int insufficient_count: int long_count: int cash_count: int win_count: int loss_count: int neutral_count: int direction_accuracy_pct: Optional[float] = None win_rate_pct: Optional[float] = None neutral_rate_pct: Optional[float] = None avg_stock_return_pct: Optional[float] = None avg_simulated_return_pct: Optional[float] = None stop_loss_trigger_rate: Optional[float] = None take_profit_trigger_rate: Optional[float] = None ambiguous_rate: Optional[float] = None avg_days_to_first_hit: Optional[float] = None advice_breakdown: Dict[str, Any] = Field(default_factory=dict) diagnostics: Dict[str, Any] = Field(default_factory=dict)