Risk controls
The hard risk limits — drawdown, daily loss, per-trade, position caps — and the kill-switch that enforce them before a strategy can lose real capital.
Risk controls are the fixed limits that decide whether a trade is allowed to open and when all trading must stop. They are checked in code at order-submit time and in the portfolio-risk loop, not left to a strategy's discretion. This page is the operator reference for what each limit is, its default, the range you can set it to, and — most importantly — how the limits interact so a single bad run cannot become a catastrophic one.
The live risk dashboard shows these controls as gauges; this page documents the controls themselves. Most of them only bite in live trading. In beta the app hard-locks to paper — publicly the "candidate" stage — so the limits still evaluate, but no real order is ever placed.
Forven is a research tool. Nothing here is a prediction, a recommendation, or financial advice. Risk controls cap downside; they do not create an edge, and no setting guarantees against loss. Every number below is an illustrative default, not a suggested value — tune it to your own risk budget.
The two enforcement points
Every risk control runs at one of two moments:
- At order submit —
can_open()is the gate each new trade must pass. It checks the kill-switch state, the daily-loss halt, per-trade risk, the position cap, and the per-strategy cooldown gate. The check fails closed: if a limit is breached or cannot be evaluated, the order is rejected with a reason explaining which limit it hit (for example, that the requested risk exceeds the per-trade max). The optional risk/reward gate is applied earlier, when the signal is evaluated, rather than insidecan_open(). - In the portfolio-risk loop — the equity update that runs every daemon tick (
update_equity()) compares portfolio drawdown againstmax_drawdown_pctand same-day realized PnL againstmax_daily_loss_pct, tripping the kill-switch or the daily-loss halt when a threshold is crossed.
The first stops a bad trade before it opens. The second stops all trading after the portfolio as a whole has fallen too far.
The hard limits
These are the configurable caps. Set them in Settings → Trading; the next portfolio-risk check reads the new values through _get_risk_limits(). They are persisted in the KV store under forven:settings and also live in config.json.
| Control | Config key | Default | Range | Enforced |
|---|---|---|---|---|
| Max drawdown | max_drawdown_pct | 10 | 1–30 | Portfolio loop → kill-switch |
| Max daily loss | max_daily_loss_pct | 5 | 0.5–10 | Portfolio loop → daily-loss halt |
| Max risk per trade | max_risk_per_trade_pct | 2 | 0.5–10 | can_open() at submit |
| Max concurrent positions (live) | max_concurrent_positions | unlimited | — | can_open() against shared wallet |
| Max concurrent positions (paper) | paper_max_concurrent_positions | unlimited | — | can_open() per paper session |
| Min risk/reward | min_risk_reward_ratio | 0 (off) | — | Signal evaluation (scanner) |
| Cooldown after loss | cooldown_after_loss_hours | 0 (off) | 0–24 | can_open() per strategy |
Defaults shown are illustrative. unlimited means the key is unset (null) and no cap applies — set an explicit number to enforce one.
Max drawdown — the kill-switch threshold
max_drawdown_pct is the deepest portfolio drawdown allowed before the kill-switch fires and force-closes everything. Drawdown is measured against the high-water mark (HWM) — the peak equity since the last reset — as:
drawdown = 1 - current_equity / high_water_markWhen that figure reaches max_drawdown_pct (default 10%, illustrative), the kill-switch trips. See The kill-switch below for what happens next.
Max daily loss — the daily halt
max_daily_loss_pct caps same-day realized loss as a percentage of initial_capital (default starting capital 10000, illustrative). When the day's realized PnL falls past the cap, daily_loss_halt is set and new trades are blocked for the rest of the current UTC day. Existing positions are not force-closed — this is a brake on opening more, not an emergency exit.
The halt is scoped to the current UTC day only. At UTC midnight the day's start-of-day equity snapshot resets and trading is allowed again, even if yesterday hit the limit. It also clears on a manual trading-halt reset (POST /api/system/trading/reset) or when the kill-switch is reset.
A legacy USD form, max_daily_loss, is still honoured but is overridden by max_daily_loss_pct when both are set.
Max risk per trade
max_risk_per_trade_pct (default 2%, illustrative) caps the risk any single order may carry, as a percentage of the account. can_open() enforces it at submit time; an order whose risk exceeds the cap is rejected with a reason noting the requested risk exceeds the per-trade max. On the risk dashboard, current_per_trade_risk reports the risk of your largest open position — when that gauge sits near the limit, you have little headroom for the next signal.
A legacy max_position_size_pct exists and is overridden by max_risk_per_trade_pct when both are set.
Position caps — and why live and paper differ
There are two separate caps because live and paper account for positions differently:
max_concurrent_positionsapplies to the shared live wallet. Live execution pools every active strategy's positions into one HyperLiquid wallet, so this cap is global — it counts all strategies together, not per-strategy.paper_max_concurrent_positionsapplies per paper session. Paper sessions are isolated sandboxes with their own capital; the cap counts only that session's positions.
The risk dashboard reflects this split with two non-additive counts: open_positions (live) and open_positions_paper (paper).
Optional entry gates
Two gates are off by default and tighten which trades may open rather than how many:
min_risk_reward_ratio— rejects an entry whose risk/reward is below the threshold (for example1.5requires at least 1:1.5). Default0disables it. A tight value reduces trade frequency, so set it deliberately.cooldown_after_loss_hours— blocks a strategy from reopening for N hours after it posts a loss. This is per-strategy: a different strategy can still trade while one is cooling down. Default0disables it.
Two more keys feed assumed costs into the risk math: risk_fee_bps and risk_slippage_bps (both default 0). They let position sizing account for fees and slippage when computing per-trade risk.
The kill-switch
The kill-switch is the drawdown circuit breaker — the control that converts "the portfolio is down too far" into "stop everything now." It is governed by kill_switch_enabled (default true); when false, drawdown is still computed but the switch will not trigger.
When portfolio drawdown reaches max_drawdown_pct, the switch fires automatically:
- The portfolio-risk loop (
update_equity(), run each daemon tick) detects the breach and setskill_switch_active = true. - An emergency-close loop iterates over every open live position and submits a market-close for each.
- Each close attempt uses an escalating slippage cap so it still fills in a fast market — 300 bps on the first attempt, 600 bps, then 1000 bps on retries.
- If a position fails to close after three attempts, it is marked pending-close-reconcile (with the failure recorded under
kill_switch_close_errorin the trade's signal data) and left open for human review. - Once the loop finishes,
is_trading_allowed()returns false and no new positions open. - Trading stays halted until an operator manually resets the switch.
A 429 rate-limit during the emergency close does not trip the trade circuit breaker; signed submits retry with bounded backoff (0.5s up to 4s) so the close path keeps working through rate-limit bursts. Only true outages (5xx, connection errors) open a breaker.
Resetting the kill-switch
Reset is an explicit operator action — there is no auto-reset. From the risk page button, the CLI, or the API:
# Reset the kill-switch (operator-only; the request must confirm the action)
Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:8003/api/kill-switch/reset" `
-ContentType "application/json" -Body '{"confirm": true}'Resetting is destructive to drawdown tracking. The reset clears
kill_switch_activeand re-baselines the HWM to current equity. The next kill-switch threshold is then measured from the reset point, not the original peak. Reset after a 20% drawdown and a further 10% fall from there is roughly a 28% fall from the original peak before the switch fires again. Reset only once you have addressed the cause — never just to silence the alarm.
If the HWM is unset or zero, drawdown cannot be computed and the kill-switch will not fire. The HWM is synced from the account value at startup; if it is missing, that sync needs investigating before you rely on the switch.
The decay kill-switch (per-strategy)
Separate from the portfolio kill-switch, the risk-manager agent watches each live strategy's recent performance. If a strategy's 72-hour live Sharpe degrades past decay_kill_switch_pct (default 30%, illustrative) versus its walk-forward baseline, that single strategy is autonomously demoted back to the Test stage and its trading halts — the rest of the portfolio is untouched. This is how a strategy that decays out-of-sample retires itself instead of bleeding capital.
How the limits interact
The controls are layered, narrowest first, so a problem is caught at the smallest scope that can contain it:
- Per trade —
can_open()rejects an oversized or low-quality order before it ever opens. - Per strategy — cooldown-after-loss and the decay kill-switch pause or demote one misbehaving strategy without touching the others.
- Per day — the daily-loss halt stops new trades once the day's realized loss budget is spent, then clears at UTC midnight.
- Whole portfolio — the kill-switch force-closes everything when total drawdown breaches the cap, and stays latched until you reset it.
A trade must clear every applicable layer to open. Any one layer can halt activity on its own. The daily-loss halt is reversible by the clock; the kill-switch is latched and requires a deliberate, documented reset.
Configure the limits
Steps
- Open Settings and go to the Trading (Risk) section. The current values are shown.
- Set
max_drawdown_pct(default 10, range 1–30) — the kill-switch threshold. - Set
max_daily_loss_pct(default 5, range 0.5–10), or the legacymax_daily_lossin USD. - Set
max_risk_per_trade_pct(default 2, range 0.5–10). - Set
max_concurrent_positionsfor the live wallet andpaper_max_concurrent_positionsfor each paper session. - Optionally enable
min_risk_reward_ratioandcooldown_after_loss_hours. - Save. Settings persist to the KV store (
forven:settings); the next portfolio-risk check applies them.
What you'll see
After saving, the risk dashboard reflects the new caps under Risk limits (these are the limits, not current usage). The kill-switch threshold and daily-loss limit are active immediately on the next risk check — no restart needed.
Caveats
- In beta, execution is paper-locked. Live-only controls (real-wallet drawdown, the kill-switch close loop, the position cap) populate but never place a real order until live trading is unlocked. See execution modes.
- Paper PnL is not a proxy for live PnL. Paper trades fill at local candle prices, carry no exchange order ID, and are never reconciled against the exchange — paper drawdown is informative, not predictive.
- The kill-switch reset re-baselines the HWM and is destructive to drawdown tracking; understand the consequence before resetting.
- A failed emergency close (recorded as
kill_switch_close_errorand marked pending-close-reconcile) leaves a position open and needs manual intervention — it is not silently retried forever. - Stale pending-open trades have their risk slot freed after 180s (illustrative) so the position cap cannot deadlock on a never-filled order; if such a trade is genuinely open, manual review may be needed.
Related
- Risk & safety dashboard — read these controls as live gauges and know when to escalate.
- Circuit breakers — the exchange-health state machine that protects the order path.
- Going live safely — the full checklist before any real capital.
- Execution modes — paper versus live, and the beta paper-lock.
Execution modes: paper vs live
How Forven's paper mode (local simulation) and live mode (real HyperLiquid orders) differ, when to use each, and how to switch safely.
Circuit breakers
How Forven's exchange-health circuit breakers (hl_price, hl_trade, hl_account) trip, recover, and tell rate-limits apart from real outages.