Risk calculator formulas
Implementation: src/lib/risk.ts (calculateRisk). It is pure (no database or Node imports) and runs in the browser and on the server. All values are computed in full double precision; only the display strings are rounded.
Notation
| Symbol | Meaning |
|---|---|
B | balance. Linear: in the settlement/quote currency (e.g. USDT). Inverse: in the base coin (e.g. BTC). |
r | risk % (riskPct / 100) |
E, S, T | entry, stop, target prices |
d | direction: +1 long, −1 short |
L | leverage (1–125) |
fE, fX | entry / exit fee rate = maker or taker fee % ÷ 100, chosen by entryOrderType / exitOrderType |
s | slippage rate = slippagePct / 100, applied adversely to every fill |
C | inverse contract size in USD (Binance COIN-M: 100 for BTC, 10 for others) |
Q | quantity: base units (linear) or whole contracts (inverse) |
Fill prices (slippage)
E' = E × (1 + s·d) entry fills worse (higher for a long) S' = S × (1 − s·d) stop fills worse (lower for a long) T' = T × (1 − s·d) target fills worse
Per-unit PnL and fees (account currency)
| Linear (unit = 1 base unit) | Inverse (unit = 1 contract), in coin | |
|---|---|---|
gross PnL from price a to b | g(a,b) = (b − a) · d | g(a,b) = C · (1/a − 1/b) · d |
fee for one unit filled at p | p · f | (C / p) · f |
Position sizing
maxLoss = B × r (planned maximum loss, account currency) lossPerUnit = −g(E', S') + fee(E', fE) + fee(S', fX) (loss at the stop INCLUDING fees and slippage) rawQuantity = maxLoss / lossPerUnit Q = floor(rawQuantity to qtyStep) linear (no step → unrounded) Q = floor(rawQuantity) inverse (whole contracts)
After rounding, Q is reduced further if floating-point noise would make Q × lossPerUnit > maxLoss. Guarantee: effectiveRiskPct ≤ riskPct, always. (At an exact boundary such as 25.000000 contracts, float noise can cost one step; the calculator always errs on the side of less risk.)
Rounding uses floorToStep, which corrects for representation error (0.3 / 0.1 = 2.999…) by working in integer step counts and fixed decimals derived from the step.
Outputs
positionNotionalUsd = Q × E (linear; quote ≈ USD) | Q × C (inverse)
positionNotional = Q × E (account currency) | Q × C / E (coin)
initialMargin = positionNotional / L
fees at stop = entry: Q·fee(E', fE) exit: Q·fee(S', fX)
fees at target = entry: Q·fee(E', fE) exit: Q·fee(T', fX)
slippageAtStop = Q × (g(E, S) − g(E', S')) (PnL lost vs. fills at exact prices)
slippageAtTarget = Q × (g(E, T) − g(E', T'))
lossAtStop = Q × lossPerUnit (positive number)
profitAtTarget = Q × g(E', T') − fees at target total
riskReward = profitAtTarget / lossAtStop (net, after costs)
grossRiskReward = |g(E, T)| / |g(E, S)| (price distance only)
returnOnMarginPct = profitAtTarget / initialMargin × 100
lossOnMarginPct = −lossAtStop / initialMargin × 100
effectiveRiskPct = lossAtStop / B × 100
maxAffordableQuantity = floor_step( B / (E/L + fee(E', fE)) ) linear
= floor( B / (C/(E·L) + fee(E', fE)) ) inverse
Currency of each output
accountCurrencyis thecurrencyinput. Every money output is in it unless the field name ends inUsd.- Linear: account currency is the quote (USDT/USDC);
*Usdfields equal the account-currency values (assumes the stablecoin ≈ 1 USD). - Inverse: money outputs are in coin. USD conversions are explicit and use the price at which the amount is realised:
lossAtStopUsd = lossAtStop × S', profitAtTargetUsd = profitAtTarget × T'. positionNotionalUsd = Q × C.
Validation
Errors (ok = false):
| Code | Condition | ||
|---|---|---|---|
BALANCE, ENTRY_PRICE, STOP_PRICE, TARGET_PRICE | non-finite (NaN/∞) or ≤ 0 | ||
STOP_ZERO_DISTANCE | S = E or ` | S − E | < tickSize` |
STOP_WRONG_SIDE | long with S > E, short with S < E | ||
TARGET_ZERO_DISTANCE, TARGET_WRONG_SIDE | target equal to entry / on the losing side | ||
LEVERAGE | outside 1–125 | ||
RISK_PCT | outside (0, 100] | ||
COSTS | fee or slippage negative, NaN or ≥ 100 | ||
CONTRACT_SIZE | inverse without a positive contractSizeUsd | ||
SLIPPAGE | slippage larger than the stop distance | ||
INSUFFICIENT_MARGIN | initialMargin + entry fee > B; message reports maxAffordableQuantity (outputs are still returned) | ||
QTY_ZERO | quantity rounds down to 0; message reports the risk % of the minimum step |
Warnings: RISK_PCT_HIGH (> 5 %), LEVERAGE_HIGH (> 50×, unrealistic), TICK_ALIGNMENT, TARGET_UNPROFITABLE (target does not cover costs), RISK_UNDERUSED (rounding leaves < 50 % of the requested risk), STOP_BEYOND_LIQUIDATION.
Illustrative liquidation (estimate, NOT exchange-precise)
Returned as illustrativeLiquidation { label, price, assumptions[] }. Simplified isolated-margin model in which only the initial margin 1/L of notional absorbs losses:
linear long E × (1 − 1/L) (L = 1 → none) linear short E × (1 + 1/L) inverse long E / (1 + 1/L) inverse short E / (1 − 1/L) (L = 1 → none)
Derivation (inverse long): margin in coin = QC/(E·L); loss in coin = QC(1/P − 1/E); equal when 1/P = (1/E)(1 + 1/L).
Assumptions (also returned in the result): isolated margin; ignores maintenance-margin rate and tiers (real liquidation is closer to entry); ignores fees, funding and liquidation fee; uses the entry price rather than the mark price. Rounded to the tick toward the entry when a tick size is given.