- shared/schemas/trading.py: OrderRequest, OrderResult, PositionInfo, AccountInfo, TradeSignal, TradeExecution, MarketSnapshot, SentimentContext - shared/schemas/news.py: RawArticle, ScoredArticle - shared/schemas/learning.py: TradeOutcomeSchema, WeightAdjustment - shared/schemas/auth.py: RegisterRequest, LoginRequest, TokenResponse - 49 schema tests covering validation constraints, serialization round-trips, required fields, and range checks
163 lines
3.7 KiB
Python
163 lines
3.7 KiB
Python
"""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
|
|
|
|
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."""
|
|
|
|
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}
|