"""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 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 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 bars: list[dict[str, Any]] = Field(default_factory=list) 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}