"""Pydantic schemas for request/response validation.""" from datetime import datetime, timezone from decimal import Decimal from typing import Optional, List, Dict, Any from pydantic import BaseModel, Field, ConfigDict, field_validator from enum import Enum class OrderSide(str, Enum): """Order side.""" BUY = "buy" SELL = "sell" class OrderType(str, Enum): """Order type.""" MARKET = "market" LIMIT = "limit" STOP_LOSS = "stop_loss" TAKE_PROFIT = "take_profit" TRAILING_STOP = "trailing_stop" OCO = "oco" ICEBERG = "iceberg" class OrderStatus(str, Enum): """Order status.""" PENDING = "pending" OPEN = "open" PARTIALLY_FILLED = "partially_filled" FILLED = "filled" CANCELLED = "cancelled" REJECTED = "rejected" EXPIRED = "expired" # Trading Schemas class OrderCreate(BaseModel): """Create order request.""" exchange_id: int symbol: str side: OrderSide order_type: OrderType quantity: Decimal price: Optional[Decimal] = None strategy_id: Optional[int] = None paper_trading: bool = True # Advanced order parameters take_profit_price: Optional[Decimal] = None stop_loss_price: Optional[Decimal] = None trail_percent: Optional[Decimal] = None # For trailing stop (e.g., 0.02 for 2%) # For bracket orders bracket_take_profit: Optional[Decimal] = None bracket_stop_loss: Optional[Decimal] = None # For OCO orders oco_price: Optional[Decimal] = None # Second order price for OCO # For iceberg orders visible_quantity: Optional[Decimal] = None # Visible quantity for iceberg class OrderResponse(BaseModel): """Order response.""" model_config = ConfigDict(from_attributes=True, populate_by_name=True) id: int exchange_id: int strategy_id: Optional[int] symbol: str order_type: OrderType side: OrderSide status: OrderStatus quantity: Decimal price: Optional[Decimal] filled_quantity: Decimal average_fill_price: Optional[Decimal] fee: Decimal paper_trading: bool created_at: datetime updated_at: datetime filled_at: Optional[datetime] @field_validator('created_at', 'updated_at', 'filled_at', mode='after') @classmethod def ensure_utc(cls, v: Optional[datetime]) -> Optional[datetime]: if v and v.tzinfo is None: return v.replace(tzinfo=timezone.utc) return v class PositionResponse(BaseModel): """Position response.""" model_config = ConfigDict(from_attributes=True, populate_by_name=True) symbol: str quantity: Decimal entry_price: Decimal current_price: Decimal unrealized_pnl: Decimal realized_pnl: Decimal # Portfolio Schemas class PortfolioResponse(BaseModel): """Portfolio response.""" positions: List[Dict[str, Any]] performance: Dict[str, float] timestamp: str class PortfolioHistoryResponse(BaseModel): """Portfolio history response.""" dates: List[str] values: List[float] pnl: List[float] # Strategy Schemas class StrategyCreate(BaseModel): """Create strategy request.""" name: str description: Optional[str] = None strategy_type: str class_name: str parameters: Dict[str, Any] = Field(default_factory=dict) timeframes: List[str] = Field(default_factory=lambda: ["1h"]) paper_trading: bool = True schedule: Optional[Dict[str, Any]] = None class StrategyUpdate(BaseModel): """Update strategy request.""" name: Optional[str] = None description: Optional[str] = None parameters: Optional[Dict[str, Any]] = None timeframes: Optional[List[str]] = None enabled: Optional[bool] = None schedule: Optional[Dict[str, Any]] = None class StrategyResponse(BaseModel): """Strategy response.""" model_config = ConfigDict(from_attributes=True, populate_by_name=True) id: int name: str description: Optional[str] strategy_type: str class_name: str parameters: Dict[str, Any] timeframes: List[str] enabled: bool running: bool = False paper_trading: bool version: str schedule: Optional[Dict[str, Any]] created_at: datetime updated_at: datetime @field_validator('created_at', 'updated_at', mode='after') @classmethod def ensure_utc(cls, v: Optional[datetime]) -> Optional[datetime]: if v and v.tzinfo is None: return v.replace(tzinfo=timezone.utc) return v # Backtesting Schemas class BacktestRequest(BaseModel): """Backtest request.""" strategy_id: int symbol: str exchange: str timeframe: str start_date: datetime end_date: datetime initial_capital: Decimal = Decimal("100.0") slippage: float = 0.001 fee_rate: float = 0.001 class WalkForwardRequest(BaseModel): """Walk-forward analysis request.""" strategy_id: int symbol: str exchange: str timeframe: str start_date: datetime end_date: datetime train_period_days: int = 90 test_period_days: int = 30 step_days: int = 30 initial_capital: Decimal = Decimal("10000.0") parameter_grid: Optional[Dict[str, List[Any]]] = None optimization_metric: str = "sharpe_ratio" class MonteCarloRequest(BaseModel): """Monte Carlo simulation request.""" strategy_id: int symbol: str exchange: str timeframe: str start_date: datetime end_date: datetime initial_capital: Decimal = Decimal("10000.0") num_simulations: int = 1000 parameter_ranges: Optional[Dict[str, List[float]]] = None # {param_name: [min, max]} random_seed: Optional[int] = None class BacktestResponse(BaseModel): """Backtest response.""" backtest_id: Optional[int] = None results: Dict[str, Any] status: str = "completed" # Exchange Schemas class ExchangeResponse(BaseModel): """Exchange response.""" model_config = ConfigDict(from_attributes=True, populate_by_name=True) id: int name: str sandbox: bool read_only: bool enabled: bool created_at: datetime updated_at: datetime @field_validator('created_at', 'updated_at', mode='after') @classmethod def ensure_utc(cls, v: Optional[datetime]) -> Optional[datetime]: if v and v.tzinfo is None: return v.replace(tzinfo=timezone.utc) return v # WebSocket Messages class PriceUpdate(BaseModel): """Price update message.""" exchange: str symbol: str price: Decimal timestamp: datetime class OrderUpdate(BaseModel): """Order update message.""" order_id: int status: OrderStatus filled_quantity: Optional[Decimal] = None timestamp: datetime