Files
crypto_trading/backend/app/schemas.py
T
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

236 lines
5.0 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 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 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