"""Trading-related Pydantic schemas for Redis Streams messages and API payloads.""" from datetime import datetime from enum import Enum from typing import Any from uuid import UUID, uuid4 from pydantic import BaseModel, Field class OrderType(str, Enum): MARKET = "market" LIMIT = "limit" STOP = "stop" class OrderSide(str, Enum): BUY = "BUY" SELL = "SELL" class OrderStatus(str, Enum): PENDING = "PENDING" FILLED = "FILLED" CANCELLED = "CANCELLED" REJECTED = "REJECTED" class SignalDirection(str, Enum): LONG = "LONG" SHORT = "SHORT" NEUTRAL = "NEUTRAL" # --------------------------------------------------------------------------- # API request / response schemas # --------------------------------------------------------------------------- class OrderRequest(BaseModel): """Submitted by the trade executor or the API to place an order.""" ticker: str side: OrderSide qty: float = Field(gt=0) order_type: OrderType = OrderType.MARKET limit_price: float | None = None stop_price: float | None = None model_config = {"from_attributes": True} class OrderResult(BaseModel): """Returned after order submission or status query.""" order_id: str ticker: str side: OrderSide qty: float filled_price: float | None = None status: OrderStatus timestamp: datetime model_config = {"from_attributes": True} class PositionInfo(BaseModel): """Current position state — used in API responses and portfolio views.""" ticker: str qty: float avg_entry: float current_price: float unrealized_pnl: float market_value: float model_config = {"from_attributes": True} class AccountInfo(BaseModel): """Account-level summary from the brokerage.""" equity: float cash: float buying_power: float portfolio_value: float model_config = {"from_attributes": True} # --------------------------------------------------------------------------- # Redis Stream message schemas # --------------------------------------------------------------------------- class TradeSignal(BaseModel): """Published to ``signals:generated`` by the signal generator.""" signal_id: UUID = Field(default_factory=uuid4) ticker: str direction: SignalDirection strength: float = Field(ge=0.0, le=1.0) strategy_sources: list[str] sentiment_context: dict[str, Any] | None = None timestamp: datetime model_config = {"from_attributes": True} class TradeExecution(BaseModel): """Published to ``trades:executed`` by the trade executor.""" trade_id: UUID ticker: str side: OrderSide qty: float price: float status: OrderStatus signal_id: UUID | None = None strategy_id: UUID | None = None strategy_sources: list[str] = Field(default_factory=list) timestamp: datetime model_config = {"from_attributes": True} class OHLCVBar(BaseModel): """Single OHLCV bar.""" timestamp: datetime open: float high: float low: float close: float volume: float class FundamentalsSnapshot(BaseModel): """Fundamental financial data for a single ticker — cached daily.""" ticker: str eps_ttm: float | None = None pe_ratio: float | None = None peg_ratio: float | None = None revenue_growth_yoy: float | None = None profit_margin: float | None = None debt_to_equity: float | None = None market_cap: float | None = None fetched_at: datetime model_config = {"from_attributes": True} class MarketSnapshot(BaseModel): """Snapshot of market data for a single ticker — used by strategies.""" ticker: str current_price: float open: float high: float low: float close: float volume: float sma_20: float | None = None sma_50: float | None = None rsi: float | None = None # Technical indicators — computed by MarketDataManager ema_9: float | None = None ema_21: float | None = None sma_200: float | None = None macd: float | None = None macd_signal: float | None = None macd_histogram: float | None = None bollinger_upper: float | None = None bollinger_mid: float | None = None bollinger_lower: float | None = None vwap: float | None = None atr: float | None = None bars: list[dict[str, Any]] = Field(default_factory=list) fundamentals: FundamentalsSnapshot | None = None model_config = {"from_attributes": True} class SentimentContext(BaseModel): """Aggregated sentiment for a ticker — passed to strategies.""" ticker: str avg_score: float = Field(ge=-1.0, le=1.0) article_count: int = Field(ge=0) recent_scores: list[float] = Field(default_factory=list) avg_confidence: float = Field(ge=0.0, le=1.0) model_config = {"from_attributes": True}