Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- Learning engine: expand default weights from 3 to all 9 strategies - Learning engine: resolve placeholder strategy_id with DB lookup - Learning engine: pass strategy_sources from trade execution - Trade executor: respect trading:paused Redis flag in RiskManager - Portfolio sync: compute actual daily P&L from day-start snapshot - Portfolio API: cumulative P&L from first snapshot, read pause flag - Portfolio metrics: compute max drawdown and avg hold duration - Add strategy_sources field to TradeExecution schema - Add dev_mode config (TRADING_DEV_MODE) to bypass auth for local dev - Dashboard: VITE_DEV_MODE bypasses ProtectedRoute and 401 redirects - Vite proxy target configurable via VITE_API_TARGET - Add top-level README.md and remaining-work-plan.md - Update CLAUDE.md with correct counts and remove stale TODOs - 404 tests passing Made-with: Cursor
194 lines
4.8 KiB
Python
194 lines
4.8 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, 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}
|