Add trailing stops and parameter sweeps
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

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.
This commit is contained in:
matitos committed 2026-09-19 23:04:20 +00:00
1 parent d80e7b155e
commit 9dc00eba95
22 files changed
+575 -7

No files matched your search

+11 -1
View File
@@ -93,12 +93,16 @@ class MyStrategy(Strategy):
| Price / candles | `ctx.price`, `ctx.candles`, `ctx.closes`, `ctx.high(n)`, `ctx.low(n)` |
| Portfolio | `ctx.is_flat`, `ctx.side`, `ctx.position`, `ctx.equity`, `ctx.cash` |
| Indicators | `ctx.sma(p)`, `ctx.ema(p)`, `ctx.rsi(p)`, `ctx.atr(p)`, `ctx.std(p)` |
| Orders | `ctx.enter_long(size, stop_loss, take_profit, reason)`, `ctx.enter_short(...)`, `ctx.close(reason)` |
| Orders | `ctx.enter_long(size, stop_loss, take_profit, trailing_stop_pct, reason)`, `ctx.enter_short(...)`, `ctx.close(reason)` |
Notes:
- `size` is a fraction of equity (1.0 = all-in). It is optional; defaults to 1.0.
- `stop_loss` / `take_profit` are absolute prices; the engine enforces them.
- `trailing_stop_pct` (e.g. `0.02` for 2%) trails the stop with the price and
never loosens: for longs it ratchets up with the high, for shorts down with the
low. The engine and backtester both call `advance_trailing_stop`, which is the
only place that math lives.
- Only one position is open at a time. Intents that conflict are ignored (logged
via the engine status, not raised).
- Raise nothing for "not enough data" — just `return` early, as above.
@@ -184,6 +188,12 @@ Sharpe), the equity curve and the trade list. The dashboard's **Backtest** panel
renders all of it. Add a strategy-focused test in `tests/test_backtest.py` if the
behaviour is non-obvious.
`POST /api/sweep` runs a cartesian grid of params through the same backtester
(`app/backtest/sweep.py`) and returns the top N rows, ranked by
`return − max_drawdown` (see `_score`). The dashboard's **Parameter sweep** panel
drives it. Like backtests, sweeps share `app/engine/portfolio.py` with the live
engine, so a winning parameter set behaves the same live.
## Commands
Backend (run from `backend/`):