132 lines
3.2 KiB
Python

"""
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