calculateMarginRisk
Fold one account’s balanceInfoOfPartyA fields and its unrealized PnL into the figures a margin panel shows. Pure, exact bigint, no IO.
import { calculateMarginRisk, getAccountBalanceInfo } from "@symmio/trading-core";
const balance = await getAccountBalanceInfo(config, { account: virtualAccount });
const metrics = calculateMarginRisk({ ...balance, upnl });
if (metrics.isLiquidatable) warn();An AccountBalanceInfo spreads straight in — the extra fields it carries (lockedPartyBMM, the pendingLocked* legs) are ignored.
The formulas
totalMargin = allocatedBalance
maintenanceMargin = lockedCVA + lockedLF
initialMargin = lockedPartyAMM + maintenanceMargin
equity = totalMargin + upnl
remainingToLiquidation = equity − maintenanceMargin
zeroUpnlBuffer = totalMargin − maintenanceMargin
liquidationBufferPercent = remainingToLiquidation / zeroUpnlBuffer × 100
isLiquidatable = remainingToLiquidation < 0initialMargin matches the contract’s LockedValuesOps.totalForPartyA() (cva + lf + partyAmm). Despite the name it tracks live locked values, so it shrinks as the position is closed.
isLiquidatable is the protocol’s own test
perps-core v0.8.6 liquidates partyA when LibAccount.partyAAvailableBalanceForLiquidation goes negative:
freeBalance = allocatedBalance − (lockedCVA + lockedLF)
require(freeBalance + upnl < 0, "...") // insolventExpand remainingToLiquidation:
remainingToLiquidation = equity − maintenanceMargin
= (allocatedBalance + upnl) − (lockedCVA + lockedLF)
= freeBalance + upnlSo isLiquidatable is bit-for-bit that comparison, including its strictness: an account whose equity exactly equals its maintenance margin is still solvent.
Prefer it over any threshold on liquidationBufferPercent. That percent is a display signal — it can be undefined, and at the margin it truncates (one wei of deficit against a large cushion rounds to 0%). isLiquidatable never does either.
Single account only
Every figure describes one liquidation domain. Do not pass sums across accounts: the totals would be additive, but the buffer is not, and a blend hides an account that is about to be liquidated behind a comfortable-looking average. See the overview for why.
The same rule constrains upnl: equity = allocatedBalance + upnl is only exact when the uPnL covers the whole account. Feeding the uPnL of a subset — one grouped position out of several sharing an account — understates equity and everything derived from it.
Interpreting liquidationBufferPercent
It is the share of the account’s zero-uPnL cushion that is still intact, as an 18-decimal fixed-point percent (50% → 50_000000000000000000n). Read it as: the position starts at 100% when flat, and reaches 0% at the moment of liquidation.
- Above 100% on a profitable book. Unrealized profit adds cushion that was not there at entry.
- Negative once liquidatable. The deficit is real information, so it is not floored at zero.
- Never clamped. Clamp only the width if you draw a bar:
Math.min(Math.max(pct, 0), 100). undefinedwhen the zero-uPnL cushion is not positive (allocatedBalance ≤ maintenanceMargin). The denominator is degenerate, so the ratio genuinely does not exist — this is not the same as0%. An account in that state can still be perfectly solvent on a large unrealized profit; reporting0%would paint it as maximum risk. Fall back toremainingToLiquidationandisLiquidatable.
Parameters
allocatedBalancebigintrequiredAccount’s allocated collateral, from balanceInfoOfPartyA. Wei.
lockedCVAbigintrequiredAccount’s locked CVA, from balanceInfoOfPartyA. Wei.
lockedLFbigintrequiredAccount’s locked liquidation fee, from balanceInfoOfPartyA. Wei.
lockedPartyAMMbigintrequiredAccount’s locked partyA maintenance margin, from balanceInfoOfPartyA. Wei.
upnlbigintrequiredSigned unrealized PnL of this whole account at the current mark price — positive means in profit. Pass 0n
for a flat book.
Returns
The account’s margin figures, its liquidation cushion, and the solvency flag.
Related
MarginRiskMetrics— the returned shape, field by field.getAccountBalanceInfo— the read that produces the four balance fields.aggregateGroupUpnl— folds a grouped position’s children into theupnlinput.- React
useAccountMarginRisk/useQuoteGroupMarginRisk— the hooks that wire the reads to this fold.