"""Trading domain models: Strategy, Signal, Trade, Position, StrategyWeightHistory.""" import enum import uuid from sqlalchemy import Boolean, Float, ForeignKey, String, Text from sqlalchemy.dialects.postgresql import JSON, UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from shared.models.base import Base, TimestampMixin # --------------------------------------------------------------------------- # Enums # --------------------------------------------------------------------------- class TradeSide(str, enum.Enum): BUY = "BUY" SELL = "SELL" class TradeStatus(str, enum.Enum): PENDING = "PENDING" FILLED = "FILLED" CANCELLED = "CANCELLED" REJECTED = "REJECTED" class SignalDirection(str, enum.Enum): LONG = "LONG" SHORT = "SHORT" NEUTRAL = "NEUTRAL" # --------------------------------------------------------------------------- # Models # --------------------------------------------------------------------------- class Strategy(TimestampMixin, Base): __tablename__ = "strategies" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) name: Mapped[str] = mapped_column(String(255), unique=True, nullable=False) description: Mapped[str | None] = mapped_column(Text, nullable=True) current_weight: Mapped[float] = mapped_column(Float, nullable=False, default=0.333) active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) # Relationships trades: Mapped[list["Trade"]] = relationship(back_populates="strategy") signals: Mapped[list["Signal"]] = relationship(back_populates="strategy", foreign_keys="Signal.strategy_id", viewonly=True) weight_history: Mapped[list["StrategyWeightHistory"]] = relationship(back_populates="strategy") class Signal(TimestampMixin, Base): __tablename__ = "signals" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) ticker: Mapped[str] = mapped_column(String(20), nullable=False, index=True) direction: Mapped[SignalDirection] = mapped_column(nullable=False) strength: Mapped[float] = mapped_column(Float, nullable=False) strategy_sources: Mapped[dict | None] = mapped_column(JSON, nullable=True) sentiment_score: Mapped[float | None] = mapped_column(Float, nullable=True) acted_on: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) strategy_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("strategies.id"), nullable=True ) # Relationships strategy: Mapped[Strategy | None] = relationship(back_populates="signals", foreign_keys=[strategy_id]) trades: Mapped[list["Trade"]] = relationship(back_populates="signal") class Trade(TimestampMixin, Base): __tablename__ = "trades" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) ticker: Mapped[str] = mapped_column(String(20), nullable=False, index=True) side: Mapped[TradeSide] = mapped_column(nullable=False) qty: Mapped[float] = mapped_column(Float, nullable=False) price: Mapped[float] = mapped_column(Float, nullable=False) timestamp: Mapped[str | None] = mapped_column(String, nullable=True) strategy_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("strategies.id"), nullable=True ) signal_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("signals.id"), nullable=True ) status: Mapped[TradeStatus] = mapped_column(nullable=False, default=TradeStatus.PENDING) pnl: Mapped[float | None] = mapped_column(Float, nullable=True) # Relationships strategy: Mapped[Strategy | None] = relationship(back_populates="trades") signal: Mapped[Signal | None] = relationship(back_populates="trades") outcome: Mapped["TradeOutcome | None"] = relationship( "TradeOutcome", back_populates="trade", uselist=False ) class Position(TimestampMixin, Base): __tablename__ = "positions" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) ticker: Mapped[str] = mapped_column(String(20), unique=True, nullable=False) qty: Mapped[float] = mapped_column(Float, nullable=False) avg_entry: Mapped[float] = mapped_column(Float, nullable=False) unrealized_pnl: Mapped[float | None] = mapped_column(Float, nullable=True) stop_loss: Mapped[float | None] = mapped_column(Float, nullable=True) take_profit: Mapped[float | None] = mapped_column(Float, nullable=True) class StrategyWeightHistory(TimestampMixin, Base): __tablename__ = "strategy_weight_history" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) strategy_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("strategies.id"), nullable=False ) old_weight: Mapped[float] = mapped_column(Float, nullable=False) new_weight: Mapped[float] = mapped_column(Float, nullable=False) reason: Mapped[str | None] = mapped_column(String(500), nullable=True) # Relationships strategy: Mapped[Strategy] = relationship(back_populates="weight_history") # Avoid circular import — TradeOutcome is defined in learning.py from shared.models.learning import TradeOutcome # noqa: E402, F401