- RiskManager halts + flattens on max daily loss / drawdown, caps position size and trades per day; adds a manual kill switch (POST /api/bot/kill). - Telegram and webhook notifications on trades, errors and halts. - Optional API bearer token on /api, injected server-side by the frontend nginx so the browser never sees it. - Kraken and Coinbase market data (public); Coinbase live broker. Live on an unsupported exchange now errors instead of silently falling back to paper. - Strategy presets backed by the StrategyConfig table (CRUD API + UI). - Idempotent additive-column migration so existing SQLite databases upgrade in place (fixes the new strategy_configs.name column).
123 lines
4.0 KiB
Python
123 lines
4.0 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)
|
|
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
|
|
)
|