Files
Options-SideKick/backend/app/models/schemas.py
olsch01 b7d4e900cc Initial implementation of Options Sidekick
Full-stack iOS options trading assistant:
- Python FastAPI backend with SQLite, APScheduler (15-min position monitor),
  APNs push notifications, and yfinance market data integration
- Signal engine: IV Rank (rolling HV proxy), SMA-50/200, swing-based
  support/resistance, earnings detection, signal strength scoring and
  noise-resistant SHA hash for change detection
- Recommendation engine: covered call and cash-secured put strike/expiry
  selection across 0DTE, 1DTE, weekly, and monthly horizons
- REST API: /devices, /portfolio, /recommendations, /positions, /signals, /alerts
- iOS SwiftUI app (iOS 17+): dashboard, recommendations, trades, portfolio,
  and alerts tabs with push notification deep-linking
- Unit + integration tests for signal engine and API layer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 14:38:25 -04:00

167 lines
4.8 KiB
Python

from datetime import datetime, date
from pydantic import BaseModel, field_validator
# ─── Device ───────────────────────────────────────────────────────────────────
class DeviceRegister(BaseModel):
apns_token: str
device_name: str | None = None
class DeviceResponse(BaseModel):
id: int
apns_token: str
device_name: str | None
registered_at: datetime
model_config = {"from_attributes": True}
# ─── Stock Portfolio ───────────────────────────────────────────────────────────
class StockPositionCreate(BaseModel):
ticker: str
shares: int
cost_basis: float | None = None
@field_validator("ticker")
@classmethod
def uppercase_ticker(cls, v: str) -> str:
return v.upper().strip()
@field_validator("shares")
@classmethod
def positive_shares(cls, v: int) -> int:
if v <= 0:
raise ValueError("shares must be positive")
return v
class StockPositionResponse(BaseModel):
id: int
ticker: str
shares: int
cost_basis: float | None
created_at: datetime
model_config = {"from_attributes": True}
# ─── Option Position ───────────────────────────────────────────────────────────
class OptionPositionCreate(BaseModel):
ticker: str
strategy: str # covered_call | cash_secured_put
strike: float
expiration: date
premium_received: float
contracts: int = 1
@field_validator("ticker")
@classmethod
def uppercase_ticker(cls, v: str) -> str:
return v.upper().strip()
@field_validator("strategy")
@classmethod
def valid_strategy(cls, v: str) -> str:
if v not in ("covered_call", "cash_secured_put"):
raise ValueError("strategy must be 'covered_call' or 'cash_secured_put'")
return v
@field_validator("contracts")
@classmethod
def positive_contracts(cls, v: int) -> int:
if v <= 0:
raise ValueError("contracts must be positive")
return v
class OptionPositionClose(BaseModel):
status: str # closed | rolled
close_reason: str | None = None
class OptionPositionResponse(BaseModel):
id: int
ticker: str
strategy: str
strike: float
expiration: date
premium_received: float
contracts: int
status: str
close_reason: str | None
opened_at: datetime
closed_at: datetime | None
last_signal_hash: str | None
model_config = {"from_attributes": True}
# ─── Signals ──────────────────────────────────────────────────────────────────
class SignalSnapshot(BaseModel):
ticker: str
current_price: float
iv_rank: float
sma_50: float
sma_200: float
nearest_support: float | None
nearest_resistance: float | None
trend: str # uptrend | downtrend | sideways
earnings_date: date | None
computed_at: datetime
# ─── Recommendations ──────────────────────────────────────────────────────────
class RecommendationResponse(BaseModel):
id: int
ticker: str
strategy: str
time_horizon: str
current_price: float
recommended_strike: float
recommended_expiration: date
estimated_premium: float
delta: float
theta: float
iv_rank: float
signal_strength: str
earnings_warning: bool
earnings_date: date | None
rationale: str
signal_hash: str
created_at: datetime
model_config = {"from_attributes": True}
class RecommendationWithSignals(BaseModel):
recommendation: RecommendationResponse
signals: SignalSnapshot
# ─── Alerts ───────────────────────────────────────────────────────────────────
class AlertResponse(BaseModel):
id: int
ticker: str
option_position_id: int | None
alert_type: str
message: str
sent_at: datetime
acknowledged: bool
model_config = {"from_attributes": True}
# ─── Health ───────────────────────────────────────────────────────────────────
class HealthResponse(BaseModel):
status: str
scheduler_running: bool
last_run: datetime | None