Skip to content
Join the waitlistWaitlist

Sizing from the stop

How Guard turns a stop price into a position size, with the exact formula from zunder-risk and hand-worked examples.

Guard does not ask “how big is this order?”. It asks “how much does this order lose if the stop is hit?”. The size follows from that.

This is true today: it is RiskEngine::size_entry in crates/zunder-risk/src/engine.rs, the same code Zunder’s own runner uses on Hyperliquid testnet.

For one new entry, with equity E:

risk per unit = |entry − stop| + round-trip cost per unit
trade budget = E × risk_per_trade (default 2%)
open budget = E × max_open_risk − open risk already (default 6%)
risk budget = min(trade budget, open budget)
notional room = E × max_leverage − open position value (default 5x)
quantity = min( risk budget / risk per unit , notional room / entry )
rounded down to the venue's lot size

Then:

  • If the stop is not on the losing side of the entry (below it for a buy, above it for a sell), the entry is refused: stop_on_wrong_side.
  • If the risk budget is zero or less, refused: open_risk_exhausted.
  • If the notional room is zero or less, refused: leverage_exhausted.
  • If the rounded quantity is zero, or worth less than the venue’s minimum order value, refused: below_minimum.
  • The quantity is never rounded up.

Which equity. The engine uses the smallest of three numbers: the equity in the request, the last equity it observed itself, and the equity cap if one is set (Equity cap). A wrong or stale number can only make the size smaller.

Which open risk. The engine keeps its own record of open positions. It uses the larger of its record and the venue’s, symbol by symbol. A position without a stop has no bounded risk, so nothing is sized next to it: unprotected_position.

Open risk of one position is quantity × distance from the current price to its stop. Its value is quantity × current price (crates/zunder-risk/src/book.rs).

Drag the stop and watch the size. The real engine sizes it, in your browser, with your rules from this site or the defaults.

Try it · a BTC long at 60,000 defaults · computed with the formula above

Your rules: 2% at the stop, 5× leverage, 11 bps round trip.

stop price
$58,800
allowed loss at the stop
$40.00
quantity Guard sends
0.03159 BTC
position value
$1,895
effective leverage
0.95×
the rule that sets the size
loss at the stop
loss if stopped$39.99
position vs leverage room$10,000

Includes the costs of getting in and out (11 bps of the price) and the venue's lot (0.00001 BTC, rounded down). A tight stop asks for a large position, until the leverage cap takes over.

Equity 2,000 USDC. Defaults: 2% per trade, 6% open risk, 5x. No open positions.

A long on BTC: entry 60,000, stop 58,800. Round-trip costs 12 basis points of the price, so 60,000 × 0.0012 = 72 per BTC. Lot size 0.00001 BTC (an assumption for the example).

risk per unit = 1,200 + 72 = 1,272
trade budget = 2,000 × 0.02 = 40
open budget = 2,000 × 0.06 − 0 = 120
risk budget = min(40, 120) = 40
notional room = 2,000 × 5 − 0 = 10,000
by risk = 40 / 1,272 = 0.031446…
by leverage = 10,000 / 60,000 = 0.1666…
quantity = 0.031446… → 0.03144 BTC

Check: at the stop, 0.03144 × 1,272 = 39.99 USDC, just under 2% of 2,000. The position is worth 0.03144 × 60,000 = 1,886.40 USDC, under 1x.

The 12 basis points are the measured round-trip cost for the most liquid perps at fee tier 0 (docs/decisions.md, 5 Oct 2026, “Maker execution measured; taker cost assumption”). Your costs may differ.

Same account, but two positions already risk 100 USDC at their stops (5%).

open budget = 120 − 100 = 20
risk budget = min(40, 20) = 20
quantity = 20 / 1,272 = 0.015723… → 0.01572 BTC

The new trade gets half the size. All stops together may still lose at most 6% (19.996 + 100 ≤ 120).

Equity 2,000, no positions. Entry 100, stop 99.8, costs 0.12 per unit.

risk per unit = 0.2 + 0.12 = 0.32
by risk = 40 / 0.32 = 125 units (worth 12,500)
by leverage = 10,000 / 100 = 100 units (worth 10,000)
quantity = 100 units

A very tight stop asks for a large position. The leverage cap wins. This trade risks only 100 × 0.32 = 32 USDC (1.6%).

A buy at 100 with a stop at 101. The distance is −1. Refused: stop_on_wrong_side. Nothing is sized.

Example 5: the numbers from the latency benchmark

Section titled “Example 5: the numbers from the latency benchmark”

The benchmark in crates/zunder-exec/src/hyperliquid/signing.rs sizes this request 20,000 times:

equity 2,000 · buy · entry 3,101.5 · stop 3,040 · cost 2.8 per unit
open risk 35 · open value 1,800 · lot 0.0001 · minimum value 10
risk per unit = 61.5 + 2.8 = 64.3
trade budget = 40
open budget = 120 − 35 = 85
risk budget = 40
notional room = 10,000 − 1,800 = 8,200
by risk = 40 / 64.3 = 0.622083…
by leverage = 8,200 / 3,101.5 = 2.643881…
quantity = 0.6220

At the stop: 0.6220 × 64.3 = 39.99 USDC.

  • Gaps. A stop can fill beyond its trigger. Zunder’s session allows a protective stop to fill up to 10% beyond its trigger (stop_slippage, the same as Hyperliquid’s own market stops). A gap through the stop costs more than the budget.
  • Costs are an estimate. The round-trip cost is a number you give. If it is too low, a stop-out costs a little more than 2%.

This page as plain Markdown, for people and LLMs: /docs/concepts/sizing.md