Skip to Content

PnL & Risk

How it works

Velocity calculates your account’s risk using a health metric (0-100) derived from total collateral vs margin requirements. Health 100 = no margin used, health 0 = liquidation eligible.

PnL (Profit and Loss) comes in two forms: unrealized (mark-to-market value of open positions) and realized (settled when positions close). Unrealized PnL is calculated by comparing your position’s entry price to the current oracle price. For perps, you also have funding PnL from periodic funding rate payments between longs and shorts.

Free collateral is the amount of collateral not currently backing positions, it’s what you can withdraw or use to open new positions. Margin requirements increase with position size and vary by market. Leverage is calculated as notional position value divided by total collateral. These metrics update in real-time as prices and positions change.

The 100initialmarginuPnLcap.WhentotalcollateralorfreecollateraliscomputedunderInitialmargin(i.e.foropeningnewpositions,notforliquidationchecks),eachpositionsweighted,positiveunrealizedPnLiscappedat100 initial-margin uPnL cap.** When total collateral or free collateral is computed under `'Initial'` margin (i.e. for opening new positions, not for liquidation checks), each position's *weighted, positive* unrealized PnL is capped at **100 (MAX_POSITIVE_UPNL_FOR_INITIAL_MARGIN, QUOTE_PRECISION) before it can count toward buying power. This guards against a single dangerously-configured or manipulated market inflating a user’s initial margin capacity. The cap is per-position, applies only to gains (losses are never capped), and only kicks in when you request the asset-weighted PnL (see getUnrealizedPNL below) — it has no effect on 'Maintenance'-margin health/liquidation math, and it doesn’t cap raw unweighted PnL.

SDK Usage

These helpers are commonly used for risk checks, dashboards, and liquidation logic.

User health

Return an overall account health score from 0-100, where lower values mean higher liquidation risk.

const user = velocityClient.getUser(); const health = user.getHealth(); // returns number 0-100 console.log(health); // e.g. 85 means 85% healthy
Method User.getHealthReference ↗
ParameterTypeRequired
perpMarketIndex
number
Optional isolated perp market to scope health to; omit for the cross-margin account's health.
No
Returns
number

Collateral, margin requirement, leverage

Get total account collateral value in quote units (typically USD precision).

import { QUOTE_PRECISION, convertToNumber } from "@velocity-exchange/sdk"; // getTotalCollateral returns BN in QUOTE_PRECISION (1e6) // marginCategory defaults to 'Initial'; pass 'Maintenance' for liquidation checks const total = velocityClient.getUser().getTotalCollateral(); console.log(convertToNumber(total, QUOTE_PRECISION)); // e.g. 1500.50 (USD)
Method User.getTotalCollateralReference ↗
ParameterTypeRequired
marginCategory
MarginCategory
`'Initial'` or `'Maintenance'`. Defaults to `'Initial'`.
No
strict
boolean
Use TWAP-bounded oracle pricing. Defaults to false.
No
includeOpenOrders
boolean
Include open orders' worst-case impact. Defaults to true.
No
liquidationBuffer
any
Optional buffer (MARGIN_PRECISION, 1e4); selects the buffered collateral variant when non-zero.
No
perpMarketIndex
number
Optional isolated perp market to scope to.
No
Returns
BN

Get the required margin for the account under initial or maintenance rules.

// getMarginRequirement(marginCategory, liquidationBuffer?, strict?, includeOpenOrders?, perpMarketIndex?) // marginCategory: 'Initial' (for new positions) or 'Maintenance' (for liquidation) const req = velocityClient.getUser().getMarginRequirement('Initial'); console.log(convertToNumber(req, QUOTE_PRECISION)); // USD
Method User.getMarginRequirementReference ↗

Signature 1

ParameterTypeRequired
marginCategory
MarginCategory
Yes
liquidationBuffer
any
No
strict
boolean
No
includeOpenOrders
boolean
No
Returns
BN

Signature 2

ParameterTypeRequired
marginCategory
MarginCategory
The category of margin to calculate ('Initial' or 'Maintenance').
Yes
liquidationBuffer
any
Optional buffer amount (MARGIN_PRECISION, 1e4, added to the margin ratio) to consider during liquidation scenarios.
No
strict
boolean
Optional flag to enforce strict (TWAP-bounded) oracle pricing.
No
includeOpenOrders
boolean
Optional flag to include open orders' worst-case margin impact.
No
perpMarketIndex
number
Optional index of the perpetual market. Scopes the result to that market's isolated position.
No
Returns
BN

Get currently available collateral that can be used for new positions or withdrawals.

const free = velocityClient.getUser().getFreeCollateral(); console.log(convertToNumber(free, QUOTE_PRECISION)); // USD available
Method User.getFreeCollateralReference ↗
ParameterTypeRequired
marginCategory
MarginCategory
`'Initial'` or `'Maintenance'`. Defaults to `'Initial'`; `'Initial'` also enables strict (TWAP-bounded) oracle pricing.
No
perpMarketIndex
number
Optional isolated perp market to scope the calculation to; omit for cross margin.
No
Returns
BN

Get current account leverage as a scaled value (convert to human-readable x leverage).

import { TEN_THOUSAND } from "@velocity-exchange/sdk"; // getLeverage() returns BN scaled by 10000 (e.g. 2x = BN(20000)) const lev = velocityClient.getUser().getLeverage(); console.log(lev.toNumber() / TEN_THOUSAND.toNumber()); // e.g. 2.5 (2.5x leverage)
Method User.getLeverageReference ↗
ParameterTypeRequired
includeOpenOrders
boolean
If true, sizes the perp liability using worst-case open-order exposure. Defaults to true.
No
perpMarketIndex
number
Optional single isolated perp market to scope leverage to (uses that position's own isolated deposit + PnL as its asset value); omit for account-wide leverage.
No
Returns
BN

Isolated positions

A perp position opened in isolated margin mode is backed only by its own dedicated quote deposit rather than the account’s shared cross-margin pool — a loss on an isolated position cannot draw down collateral backing your other positions, and vice versa. Every risk helper above accepts an optional trailing perpMarketIndex to scope its result to a single market’s isolated position bucket instead of the cross-margin account:

const user = velocityClient.getUser(); const isolatedMarketIndex = 2; // Health, collateral, margin requirement, and leverage can all be scoped // to one isolated market instead of the cross-margin account. const isolatedHealth = user.getHealth(isolatedMarketIndex); const isolatedFreeCollateral = user.getFreeCollateral('Initial', isolatedMarketIndex); const isolatedMarginReq = user.getMarginRequirement('Initial', undefined, undefined, undefined, isolatedMarketIndex); const isolatedLeverage = user.getLeverage(true, isolatedMarketIndex);
Example Isolated position riskReference ↗
TypeScript docs unavailable for Isolated position risk.

getFreeCollateral and getLeverage return ZERO for a market with no open isolated position; getMarginRequirement does the same. getTotalCollateral(marginCategory, strict, includeOpenOrders, liquidationBuffer, perpMarketIndex) is the one exception — it throws if no isolated margin calculation exists for that market, so guard the call if the position may not exist.

Unrealized PnL

Get unrealized PnL across open positions (optionally including funding effects).

// getUnrealizedPNL(withFunding?, marketIndex?, withWeightMarginCategory?, strict?, liquidationBuffer?) // Returns BN in QUOTE_PRECISION. Positive = profit, negative = loss. // withWeightMarginCategory ('Initial' | 'Maintenance') applies asset weighting; // under 'Initial' it also applies the $100-per-position cap described above. const pnl = velocityClient.getUser().getUnrealizedPNL(true); // withFunding=true, raw (uncapped) PnL console.log(convertToNumber(pnl, QUOTE_PRECISION)); // e.g. -25.50 (USD)
Method User.getUnrealizedPNLReference ↗
ParameterTypeRequired
withFunding
boolean
If true, includes unsettled funding in each position's PnL.
No
marketIndex
number
Optional single perp market to scope to; omit to sum across all active positions.
No
withWeightMarginCategory
MarginCategory
Optional `'Initial'` or `'Maintenance'` — applies the asset-weighting (and, for `'Initial'`, the $100-per-position cap) described above. Omit for raw, unweighted PnL.
No
strict
boolean
Use the worse of live oracle price vs 5-minute TWAP per position (gains use the lower price, losses use the higher price). Defaults to false.
No
liquidationBuffer
any
Optional buffer (MARGIN_PRECISION, 1e4) that further penalizes negative PnL; only applied when `withWeightMarginCategory` is set.
No
Returns
BN

Get unrealized funding PnL only, separated from price-movement PnL.

// Funding PnL only (accumulated funding payments) const fundingPnl = velocityClient.getUser().getUnrealizedFundingPNL(); console.log(convertToNumber(fundingPnl, QUOTE_PRECISION)); // USD
Method User.getUnrealizedFundingPNLReference ↗
ParameterTypeRequired
marketIndex
number
Optional single perp market to scope to; omit to sum across all positions.
No
Returns
BN

Entry price helper

Compute the effective entry price for a perp position from its cumulative trade data.

import { calculateEntryPrice, PRICE_PRECISION, convertToNumber } from "@velocity-exchange/sdk"; const position = velocityClient.getUser().getPerpPosition(0); if (position) { const entryPrice = calculateEntryPrice(position); // BN in PRICE_PRECISION console.log(convertToNumber(entryPrice, PRICE_PRECISION)); // e.g. 150.25 }
Function calculateEntryPriceReference ↗

Average entry price of the position, before fees/funding (`quoteEntryAmount / baseAssetAmount`).

ParameterTypeRequired
userPosition
PerpPosition
Position to evaluate.
Yes

Average entry price (always non-negative), PRICE_PRECISION (1e6). Zero if flat.

Returns
BN

Settle perp PnL

Realize and settle a user’s perp PnL for a specific market into spot balances.

const user = velocityClient.getUser(); await velocityClient.settlePNL(user.userAccountPublicKey, user.getUserAccount(), 0);
Method VelocityClient.settlePNLReference ↗
ParameterTypeRequired
settleeUserAccountPublicKey
PublicKey
Public key of the user account to settle.
Yes
settleeUserAccount
UserAccount
Decoded user account to settle.
Yes
marketIndex
number
Perp market index to settle.
Yes
txParams
TxParams
Optional compute-unit/priority-fee overrides.
No
optionalIxs
TransactionInstruction[]
Extra instructions prepended to the settle transaction.
No
revenueShareEscrowMap
RevenueShareEscrowMap
Optional builder/referral escrow lookup; see `settlePNLIx`.
No
Returns
Promise<string>
Last updated on