"""Position model representing a trading position.""" from sqlalchemy import Column, Integer, String, DateTime, Numeric, ForeignKey, Date, Enum, Index from sqlalchemy.orm import relationship from sqlalchemy.sql import func import enum from app.database import Base class PositionType(str, enum.Enum): """Enumeration of position types.""" STOCK = "stock" CALL = "call" PUT = "put" class PositionStatus(str, enum.Enum): """Enumeration of position statuses.""" OPEN = "open" CLOSED = "closed" class Position(Base): """ Represents a trading position (open or closed). A position aggregates related transactions (entries and exits) for a specific security. For options, tracks strikes, expirations, and option-specific details. Attributes: id: Primary key account_id: Foreign key to account symbol: Base trading symbol (e.g., AAPL) option_symbol: Full option symbol if applicable (e.g., -AAPL260116C150) position_type: Type (stock, call, put) status: Status (open, closed) open_date: Date position was opened close_date: Date position was closed (if closed) total_quantity: Net quantity (can be negative for short positions) avg_entry_price: Average entry price avg_exit_price: Average exit price (if closed) realized_pnl: Realized profit/loss for closed positions unrealized_pnl: Unrealized profit/loss for open positions created_at: Timestamp of record creation updated_at: Timestamp of last update """ __tablename__ = "positions" id = Column(Integer, primary_key=True, index=True) account_id = Column(Integer, ForeignKey("accounts.id", ondelete="CASCADE"), nullable=False, index=True) # Symbol information symbol = Column(String(50), nullable=False, index=True) option_symbol = Column(String(100), index=True) # Full option symbol for options position_type = Column(Enum(PositionType), nullable=False, default=PositionType.STOCK) # Status and dates status = Column(Enum(PositionStatus), nullable=False, default=PositionStatus.OPEN, index=True) open_date = Column(Date, nullable=False) close_date = Column(Date) # Position metrics total_quantity = Column(Numeric(20, 8), nullable=False) # Can be negative for short avg_entry_price = Column(Numeric(20, 8)) avg_exit_price = Column(Numeric(20, 8)) # P&L tracking realized_pnl = Column(Numeric(20, 2)) # For closed positions unrealized_pnl = Column(Numeric(20, 2)) # For open positions # Timestamps created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False) updated_at = Column(DateTime(timezone=True), onupdate=func.now(), server_default=func.now(), nullable=False) # Relationships account = relationship("Account", back_populates="positions") transaction_links = relationship("PositionTransaction", back_populates="position", cascade="all, delete-orphan") # Composite indexes for common queries __table_args__ = ( Index('idx_account_status', 'account_id', 'status'), Index('idx_account_symbol_status', 'account_id', 'symbol', 'status'), ) class PositionTransaction(Base): """ Junction table linking positions to transactions. A position can have multiple transactions (entries, exits, adjustments). A transaction can be part of multiple positions (e.g., closing multiple lots). Attributes: position_id: Foreign key to position transaction_id: Foreign key to transaction """ __tablename__ = "position_transactions" position_id = Column(Integer, ForeignKey("positions.id", ondelete="CASCADE"), primary_key=True) transaction_id = Column(Integer, ForeignKey("transactions.id", ondelete="CASCADE"), primary_key=True) # Relationships position = relationship("Position", back_populates="transaction_links") transaction = relationship("Transaction", back_populates="position_links")