from datetime import datetime, date from sqlalchemy import Integer, String, Float, Boolean, DateTime, Date, ForeignKey, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base class Device(Base): __tablename__ = "devices" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) apns_token: Mapped[str] = mapped_column(String, unique=True, index=True) device_name: Mapped[str | None] = mapped_column(String, nullable=True) registered_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) last_seen: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) stock_positions: Mapped[list["StockPosition"]] = relationship("StockPosition", back_populates="device", cascade="all, delete-orphan") option_positions: Mapped[list["OptionPosition"]] = relationship("OptionPosition", back_populates="device", cascade="all, delete-orphan") alerts: Mapped[list["Alert"]] = relationship("Alert", back_populates="device", cascade="all, delete-orphan") class StockPosition(Base): __tablename__ = "stock_positions" __table_args__ = (UniqueConstraint("device_id", "ticker", name="uq_device_ticker"),) id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) device_id: Mapped[int] = mapped_column(Integer, ForeignKey("devices.id"), nullable=False) ticker: Mapped[str] = mapped_column(String, nullable=False) shares: Mapped[int] = mapped_column(Integer, nullable=False) cost_basis: Mapped[float | None] = mapped_column(Float, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) device: Mapped["Device"] = relationship("Device", back_populates="stock_positions") class OptionPosition(Base): __tablename__ = "option_positions" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) device_id: Mapped[int] = mapped_column(Integer, ForeignKey("devices.id"), nullable=False) ticker: Mapped[str] = mapped_column(String, nullable=False) strategy: Mapped[str] = mapped_column(String, nullable=False) # covered_call | cash_secured_put strike: Mapped[float] = mapped_column(Float, nullable=False) expiration: Mapped[date] = mapped_column(Date, nullable=False) premium_received: Mapped[float] = mapped_column(Float, nullable=False) contracts: Mapped[int] = mapped_column(Integer, default=1) status: Mapped[str] = mapped_column(String, default="open") # open | closed | rolled close_reason: Mapped[str | None] = mapped_column(String, nullable=True) # expired | bought_back | rolled opened_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) closed_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) last_signal_hash: Mapped[str | None] = mapped_column(String(16), nullable=True) device: Mapped["Device"] = relationship("Device", back_populates="option_positions") alerts: Mapped[list["Alert"]] = relationship("Alert", back_populates="option_position") class Recommendation(Base): __tablename__ = "recommendations" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) device_id: Mapped[int] = mapped_column(Integer, ForeignKey("devices.id"), nullable=False) ticker: Mapped[str] = mapped_column(String, nullable=False) strategy: Mapped[str] = mapped_column(String, nullable=False) # covered_call | cash_secured_put time_horizon: Mapped[str] = mapped_column(String, nullable=False) # 0dte | 1dte | weekly | monthly current_price: Mapped[float] = mapped_column(Float, nullable=False) recommended_strike: Mapped[float] = mapped_column(Float, nullable=False) recommended_expiration: Mapped[date] = mapped_column(Date, nullable=False) estimated_premium: Mapped[float] = mapped_column(Float, nullable=False) delta: Mapped[float] = mapped_column(Float, nullable=False) theta: Mapped[float] = mapped_column(Float, nullable=False) iv_rank: Mapped[float] = mapped_column(Float, nullable=False) signal_strength: Mapped[str] = mapped_column(String, nullable=False) # strong | moderate | weak earnings_warning: Mapped[bool] = mapped_column(Boolean, default=False) earnings_date: Mapped[date | None] = mapped_column(Date, nullable=True) rationale: Mapped[str] = mapped_column(String, nullable=False) signal_hash: Mapped[str] = mapped_column(String(16), nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) class Alert(Base): __tablename__ = "alerts" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) device_id: Mapped[int] = mapped_column(Integer, ForeignKey("devices.id"), nullable=False) option_position_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("option_positions.id"), nullable=True) ticker: Mapped[str] = mapped_column(String, nullable=False) alert_type: Mapped[str] = mapped_column(String, nullable=False) # close_early | roll_out | roll_up_down | earnings_warning | new_rec message: Mapped[str] = mapped_column(String, nullable=False) old_signal_hash: Mapped[str | None] = mapped_column(String(16), nullable=True) new_signal_hash: Mapped[str | None] = mapped_column(String(16), nullable=True) sent_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) acknowledged: Mapped[bool] = mapped_column(Boolean, default=False) device: Mapped["Device"] = relationship("Device", back_populates="alerts") option_position: Mapped["OptionPosition | None"] = relationship("OptionPosition", back_populates="alerts")