Files
matitos 9dc00eba95
CI/CD / backend (pull_request) Successful in 6m6s
CI/CD / frontend (pull_request) Successful in 5m17s
CI/CD / build (pull_request) Skipped
CI/CD / deploy (pull_request) Skipped
CI/CD / cleanup (pull_request) Skipped
Add trailing stops and parameter sweeps
Trailing stops: strategies can pass trailing_stop_pct to enter_long/enter_short.
advance_trailing_stop in app/engine/portfolio.py ratchets the stop with the price
(up for longs, down for shorts) and never loosens; the engine persists the
ratcheted level and the backtester applies the same logic. Position gains a
trailing_stop_pct column (additive migration). momentum exposes it as a param.

Parameter sweeps: app/backtest/sweep.py runs a cartesian grid through the same
backtester and ranks by return minus max drawdown. POST /api/sweep plus a
dashboard Parameter sweep panel.
2026-09-19 23:04:20 +00:00

124 lines
4.1 KiB
Python

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
)