from __future__ import annotations from datetime import UTC, datetime from enum import StrEnum from typing import Any from sqlalchemy import ( JSON, DateTime, Float, ForeignKey, Integer, String, ) from sqlalchemy import ( Enum as SAEnum, ) from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship def utcnow() -> datetime: return datetime.now(UTC) class Base(DeclarativeBase): pass class PositionSide(StrEnum): LONG = "long" SHORT = "short" class PositionStatus(StrEnum): OPEN = "open" CLOSED = "closed" class OrderSide(StrEnum): BUY = "buy" SELL = "sell" class StrategyConfig(Base): __tablename__ = "strategy_configs" id: Mapped[int] = mapped_column(Integer, primary_key=True) name: Mapped[str] = mapped_column(String(80), default="") strategy_key: Mapped[str] = mapped_column(String(64), index=True) symbol: Mapped[str] = mapped_column(String(32)) timeframe: Mapped[str] = mapped_column(String(16)) params: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) class Position(Base): __tablename__ = "positions" id: Mapped[int] = mapped_column(Integer, primary_key=True) strategy_key: Mapped[str] = mapped_column(String(64), index=True) symbol: Mapped[str] = mapped_column(String(32), index=True) side: Mapped[PositionSide] = mapped_column(SAEnum(PositionSide)) qty: Mapped[float] = mapped_column(Float) entry_price: Mapped[float] = mapped_column(Float) status: Mapped[PositionStatus] = mapped_column( SAEnum(PositionStatus), default=PositionStatus.OPEN, index=True ) stop_loss: Mapped[float | None] = mapped_column(Float, nullable=True) take_profit: Mapped[float | None] = mapped_column(Float, nullable=True) trailing_stop_pct: Mapped[float] = mapped_column(Float, default=0.0) entry_fee: Mapped[float] = mapped_column(Float, default=0.0) exit_price: Mapped[float | None] = mapped_column(Float, nullable=True) exit_fee: Mapped[float] = mapped_column(Float, default=0.0) realized_pnl: Mapped[float | None] = mapped_column(Float, nullable=True) opened_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) closed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) orders: Mapped[list[Order]] = relationship(back_populates="position") class Order(Base): __tablename__ = "orders" id: Mapped[int] = mapped_column(Integer, primary_key=True) position_id: Mapped[int | None] = mapped_column( ForeignKey("positions.id"), nullable=True, index=True ) strategy_key: Mapped[str] = mapped_column(String(64), index=True) symbol: Mapped[str] = mapped_column(String(32)) side: Mapped[OrderSide] = mapped_column(SAEnum(OrderSide)) qty: Mapped[float] = mapped_column(Float) price: Mapped[float] = mapped_column(Float) fee: Mapped[float] = mapped_column(Float, default=0.0) reason: Mapped[str] = mapped_column(String(255), default="") created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=utcnow, index=True ) position: Mapped[Position | None] = relationship(back_populates="orders") class EquitySnapshot(Base): __tablename__ = "equity_snapshots" id: Mapped[int] = mapped_column(Integer, primary_key=True) equity: Mapped[float] = mapped_column(Float) cash: Mapped[float] = mapped_column(Float) price: Mapped[float] = mapped_column(Float) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=utcnow, index=True ) class Event(Base): __tablename__ = "events" id: Mapped[int] = mapped_column(Integer, primary_key=True) level: Mapped[str] = mapped_column(String(16), default="info") message: Mapped[str] = mapped_column(String(512)) strategy_key: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=utcnow, index=True )