Files
crypto_trading/backend/app/schemas.py
T
matitos 57dcfe7adb Aggregate the top status cards across strategies
With several strategies the header cards showed only the first one. Expose
day_pnl and drawdown_pct per strategy and add aggregateStatus() (frontend) so the
cards show the whole account: summed cash/equity/P&L/trades/positions, worst
drawdown, and any halt. Single-strategy runs are unchanged. Adds tests.
2026-09-22 23:11:18 +00:00

288 lines
6.4 KiB
Python

from __future__ import annotations
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field
class Candle(BaseModel):
ts: int
open: float
high: float
low: float
close: float
volume: float
class ParamSpec(BaseModel):
name: str
type: Literal["int", "float", "bool", "str"]
default: Any
description: str = ""
min: float | None = None
max: float | None = None
class StrategyOut(BaseModel):
key: str
name: str
description: str
params: list[ParamSpec]
class PositionOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
strategy_key: str
symbol: str
side: str
qty: float
entry_price: float
status: str
stop_loss: float | None = None
take_profit: float | None = None
exit_price: float | None = None
realized_pnl: float | None = None
unrealized_pnl: float | None = None
opened_at: datetime
closed_at: datetime | None = None
class OrderOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
position_id: int | None
strategy_key: str
symbol: str
side: str
qty: float
price: float
fee: float
reason: str
created_at: datetime
class EventOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
level: str
message: str
strategy_key: str | None
created_at: datetime
class EquityPointOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
equity: float
cash: float
price: float
created_at: datetime
class StatusOut(BaseModel):
running: bool
mode: str
exchange: str
symbol: str
timeframe: str
strategy_key: str | None
strategy_name: str | None
params: dict[str, Any] = Field(default_factory=dict)
price: float | None
cash: float
equity: float
starting_balance: float
realized_pnl: float
unrealized_pnl: float
total_pnl: float
open_positions: int
last_tick: datetime | None
error: str | None = None
halted: bool = False
halt_reason: str | None = None
trades_today: int = 0
day_pnl: float = 0.0
drawdown_pct: float = 0.0
class KillRequest(BaseModel):
reason: str = "manual kill switch"
class StrategyConfigIn(BaseModel):
name: str = Field(default="", max_length=80)
strategy_key: str
symbol: str
timeframe: str
params: dict[str, Any] = Field(default_factory=dict)
class StrategyConfigOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
name: str
strategy_key: str
symbol: str
timeframe: str
params: dict[str, Any]
created_at: datetime
class StrategySpec(BaseModel):
"""One strategy in a multi-strategy run."""
strategy_key: str
params: dict[str, Any] = Field(default_factory=dict)
symbol: str | None = None
timeframe: str | None = None
# Fraction of the account allocated to this strategy (relative weights are
# normalised across the run, so [1, 3] gives 25% / 75%).
weight: float = Field(default=1.0, gt=0)
class StartRequest(BaseModel):
strategy_key: str
symbol: str | None = None
timeframe: str | None = None
params: dict[str, Any] = Field(default_factory=dict)
starting_balance: float | None = None
class StartManyRequest(BaseModel):
strategies: list[StrategySpec] = Field(min_length=1)
starting_balance: float | None = None
class StrategyStatusOut(BaseModel):
"""Per-strategy status inside a multi-strategy run."""
strategy_key: str
strategy_name: str | None
params: dict[str, Any] = Field(default_factory=dict)
symbol: str
timeframe: str
allocation: float
cash: float
equity: float
realized_pnl: float
unrealized_pnl: float
total_pnl: float
open_positions: int
halted: bool = False
halt_reason: str | None = None
trades_today: int = 0
day_pnl: float = 0.0
drawdown_pct: float = 0.0
error: str | None = None
class MultiStatusOut(BaseModel):
running: bool
mode: str
exchange: str
price: float | None = None
starting_balance: float
equity: float
total_pnl: float
strategies: list[StrategyStatusOut] = Field(default_factory=list)
last_tick: datetime | None = None
class ManualOrderRequest(BaseModel):
side: Literal["buy", "sell"]
qty: float | None = None
quote_amount: float | None = None
reason: str = "manual"
class ClosePositionRequest(BaseModel):
reason: str = "manual"
class BacktestRequest(BaseModel):
strategy_key: str
params: dict[str, Any] = Field(default_factory=dict)
symbol: str | None = None
timeframe: str | None = None
limit: int = Field(default=500, ge=20, le=1000)
starting_balance: float | None = None
class BacktestTradeOut(BaseModel):
side: str
entry_ts: int
exit_ts: int
entry_price: float
exit_price: float
qty: float
pnl: float
return_pct: float
reason: str
class BacktestPointOut(BaseModel):
ts: int
equity: float
buy_hold: float
price: float
class BacktestMetricsOut(BaseModel):
initial_equity: float
final_equity: float
total_return_pct: float
buy_hold_return_pct: float
max_drawdown_pct: float
num_trades: int
win_rate_pct: float
profit_factor: float | None
avg_trade_pct: float
best_trade_pct: float
worst_trade_pct: float
sharpe: float | None
class BacktestResult(BaseModel):
strategy_key: str
strategy_name: str
symbol: str
timeframe: str
starting_balance: float
metrics: BacktestMetricsOut
equity_curve: list[BacktestPointOut]
trades: list[BacktestTradeOut]
error: str | None = None
class SweepRequest(BaseModel):
strategy_key: str
base_params: dict[str, Any] = Field(default_factory=dict)
# param name -> list of candidate values (cartesian product)
grid: dict[str, list[Any]] = Field(default_factory=dict)
symbol: str | None = None
timeframe: str | None = None
limit: int = Field(default=500, ge=20, le=1000)
starting_balance: float | None = None
top_n: int = Field(default=20, ge=1, le=100)
class SweepRowOut(BaseModel):
params: dict[str, Any]
metrics: BacktestMetricsOut
class SweepResult(BaseModel):
strategy_key: str
strategy_name: str
symbol: str
timeframe: str
combinations: int
rows: list[SweepRowOut]
error: str | None = None