- Complete MVP for tracking Fidelity brokerage account performance - Transaction import from CSV with deduplication - Automatic FIFO position tracking with options support - Real-time P&L calculations with market data caching - Dashboard with timeframe filtering (30/90/180 days, 1 year, YTD, all time) - Docker-based deployment with PostgreSQL backend - React/TypeScript frontend with TailwindCSS - FastAPI backend with SQLAlchemy ORM Features: - Multi-account support - Import via CSV upload or filesystem - Open and closed position tracking - Balance history charting - Performance analytics and metrics - Top trades analysis - Responsive UI design Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
105 lines
3.9 KiB
Python
105 lines
3.9 KiB
Python
"""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")
|