feat: pydantic schemas for all service message types
- 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
2026-02-22 15:19:00 +00:00
|
|
|
"""Trading-related Pydantic schemas for Redis Streams messages and API payloads."""
|
|
|
|
|
|
2026-05-24 00:59:56 +00:00
|
|
|
from datetime import UTC, datetime
|
|
|
|
|
from decimal import Decimal
|
feat: pydantic schemas for all service message types
- 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
2026-02-22 15:19:00 +00:00
|
|
|
from enum import Enum
|
|
|
|
|
from typing import Any
|
2026-02-22 19:52:45 +00:00
|
|
|
from uuid import UUID, uuid4
|
feat: pydantic schemas for all service message types
- 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
2026-02-22 15:19:00 +00:00
|
|
|
|
|
|
|
|
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"
|
2026-05-24 00:59:56 +00:00
|
|
|
EXIT = "EXIT"
|
feat: pydantic schemas for all service message types
- 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
2026-02-22 15:19:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# 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."""
|
|
|
|
|
|
2026-02-22 19:52:45 +00:00
|
|
|
signal_id: UUID = Field(default_factory=uuid4)
|
feat: pydantic schemas for all service message types
- 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
2026-02-22 15:19:00 +00:00
|
|
|
ticker: str
|
|
|
|
|
direction: SignalDirection
|
|
|
|
|
strength: float = Field(ge=0.0, le=1.0)
|
|
|
|
|
strategy_sources: list[str]
|
|
|
|
|
sentiment_context: dict[str, Any] | None = None
|
2026-05-24 00:59:56 +00:00
|
|
|
timestamp: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
|
|
|
|
|
|
|
|
# --- Kevin v2 extensions (optional) ---
|
|
|
|
|
strategy_id: UUID | None = None
|
|
|
|
|
target_dollars: Decimal | None = None
|
|
|
|
|
stop_loss_pct: Decimal | None = None
|
|
|
|
|
take_profit_pct: Decimal | None = None
|
feat: pydantic schemas for all service message types
- 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
2026-02-22 15:19:00 +00:00
|
|
|
|
|
|
|
|
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
|
2026-02-25 22:02:25 +00:00
|
|
|
strategy_sources: list[str] = Field(default_factory=list)
|
feat: pydantic schemas for all service message types
- 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
2026-02-22 15:19:00 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2026-02-23 21:39:37 +00:00
|
|
|
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}
|
|
|
|
|
|
|
|
|
|
|
feat: pydantic schemas for all service message types
- 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
2026-02-22 15:19:00 +00:00
|
|
|
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
|
2026-02-23 21:39:37 +00:00
|
|
|
# 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
|
feat: pydantic schemas for all service message types
- 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
2026-02-22 15:19:00 +00:00
|
|
|
bars: list[dict[str, Any]] = Field(default_factory=list)
|
2026-02-23 21:39:37 +00:00
|
|
|
fundamentals: FundamentalsSnapshot | None = None
|
feat: pydantic schemas for all service message types
- 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
2026-02-22 15:19:00 +00:00
|
|
|
|
|
|
|
|
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}
|