""" Backtest related Pydantic schemas. """ from datetime import date, datetime from decimal import Decimal from typing import Optional, List, Dict, Any from enum import Enum from pydantic import BaseModel, Field from app.schemas.portfolio import FloatDecimal class RebalancePeriod(str, Enum): MONTHLY = "monthly" QUARTERLY = "quarterly" SEMI_ANNUAL = "semi_annual" ANNUAL = "annual" class BacktestStatus(str, Enum): PENDING = "pending" RUNNING = "running" COMPLETED = "completed" FAILED = "failed" class BacktestCreate(BaseModel): """Request to create a new backtest.""" strategy_type: str = Field(..., description="multi_factor, quality, or value_momentum") strategy_params: Dict[str, Any] = Field(default_factory=dict) start_date: date end_date: date rebalance_period: RebalancePeriod = RebalancePeriod.QUARTERLY initial_capital: Decimal = Field(default=Decimal("100000000"), gt=0) commission_rate: Decimal = Field(default=Decimal("0.00015"), ge=0, le=1) slippage_rate: Decimal = Field(default=Decimal("0.001"), ge=0, le=1) benchmark: str = Field(default="KOSPI") top_n: int = Field(default=30, ge=1, le=100) class BacktestMetrics(BaseModel): """Backtest result metrics.""" total_return: FloatDecimal cagr: FloatDecimal mdd: FloatDecimal sharpe_ratio: FloatDecimal volatility: FloatDecimal benchmark_return: FloatDecimal excess_return: FloatDecimal class BacktestResponse(BaseModel): """Backtest response with status and optional results.""" id: int user_id: int strategy_type: str strategy_params: Dict[str, Any] start_date: date end_date: date rebalance_period: str initial_capital: FloatDecimal commission_rate: FloatDecimal slippage_rate: FloatDecimal benchmark: str status: str created_at: datetime completed_at: Optional[datetime] = None error_message: Optional[str] = None result: Optional[BacktestMetrics] = None class Config: from_attributes = True class BacktestListItem(BaseModel): """Backtest item for list view.""" id: int strategy_type: str start_date: date end_date: date rebalance_period: str status: str created_at: datetime total_return: Optional[FloatDecimal] = None cagr: Optional[FloatDecimal] = None mdd: Optional[FloatDecimal] = None class Config: from_attributes = True class EquityCurvePoint(BaseModel): """Single point in equity curve.""" date: date portfolio_value: FloatDecimal benchmark_value: FloatDecimal drawdown: FloatDecimal class Config: from_attributes = True class HoldingItem(BaseModel): """Single holding at a rebalance date.""" ticker: str name: str weight: FloatDecimal shares: int price: FloatDecimal class RebalanceHoldings(BaseModel): """Holdings at a specific rebalance date.""" rebalance_date: date holdings: List[HoldingItem] class TransactionItem(BaseModel): """Single transaction.""" id: int date: date ticker: str name: str | None = None action: str shares: int price: FloatDecimal commission: FloatDecimal class Config: from_attributes = True