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 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% healthyMethod User.getHealthReference ↗
Method User.getHealthReference ↗| Parameter | Type | Required |
|---|---|---|
perpMarketIndex | numberOptional 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 ↗
Method User.getTotalCollateralReference ↗| Parameter | Type | Required |
|---|---|---|
marginCategory | MarginCategory`'Initial'` or `'Maintenance'`. Defaults to `'Initial'`. | No |
strict | booleanUse TWAP-bounded oracle pricing. Defaults to false. | No |
includeOpenOrders | booleanInclude open orders' worst-case impact. Defaults to true. | No |
liquidationBuffer | anyOptional buffer (MARGIN_PRECISION, 1e4); selects the buffered collateral variant when non-zero. | No |
perpMarketIndex | numberOptional 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)); // USDMethod User.getMarginRequirementReference ↗
Method User.getMarginRequirementReference ↗Signature 1
| Parameter | Type | Required |
|---|---|---|
marginCategory | MarginCategory | Yes |
liquidationBuffer | any | No |
strict | boolean | No |
includeOpenOrders | boolean | No |
| Returns |
|---|
BN |
Signature 2
| Parameter | Type | Required |
|---|---|---|
marginCategory | MarginCategoryThe category of margin to calculate ('Initial' or 'Maintenance'). | Yes |
liquidationBuffer | anyOptional buffer amount (MARGIN_PRECISION, 1e4, added to the margin ratio) to consider during liquidation scenarios. | No |
strict | booleanOptional flag to enforce strict (TWAP-bounded) oracle pricing. | No |
includeOpenOrders | booleanOptional flag to include open orders' worst-case margin impact. | No |
perpMarketIndex | numberOptional 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 availableMethod User.getFreeCollateralReference ↗
Method User.getFreeCollateralReference ↗| Parameter | Type | Required |
|---|---|---|
marginCategory | MarginCategory`'Initial'` or `'Maintenance'`. Defaults to `'Initial'`; `'Initial'` also enables strict (TWAP-bounded) oracle pricing. | No |
perpMarketIndex | numberOptional 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 ↗
Method User.getLeverageReference ↗| Parameter | Type | Required |
|---|---|---|
includeOpenOrders | booleanIf true, sizes the perp liability using worst-case open-order exposure. Defaults to true. | No |
perpMarketIndex | numberOptional 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 ↗
Example Isolated position riskReference ↗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 ↗
Method User.getUnrealizedPNLReference ↗| Parameter | Type | Required |
|---|---|---|
withFunding | booleanIf true, includes unsettled funding in each position's PnL. | No |
marketIndex | numberOptional single perp market to scope to; omit to sum across all active positions. | No |
withWeightMarginCategory | MarginCategoryOptional `'Initial'` or `'Maintenance'` — applies the asset-weighting (and, for `'Initial'`, the $100-per-position cap) described above. Omit for raw, unweighted PnL. | No |
strict | booleanUse 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 | anyOptional 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)); // USDMethod User.getUnrealizedFundingPNLReference ↗
Method User.getUnrealizedFundingPNLReference ↗| Parameter | Type | Required |
|---|---|---|
marketIndex | numberOptional 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 ↗
Function calculateEntryPriceReference ↗Average entry price of the position, before fees/funding (`quoteEntryAmount / baseAssetAmount`).
| Parameter | Type | Required |
|---|---|---|
userPosition | PerpPositionPosition 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 ↗
Method VelocityClient.settlePNLReference ↗| Parameter | Type | Required |
|---|---|---|
settleeUserAccountPublicKey | PublicKeyPublic key of the user account to settle. | Yes |
settleeUserAccount | UserAccountDecoded user account to settle. | Yes |
marketIndex | numberPerp market index to settle. | Yes |
txParams | TxParamsOptional compute-unit/priority-fee overrides. | No |
optionalIxs | TransactionInstruction[]Extra instructions prepended to the settle transaction. | No |
revenueShareEscrowMap | RevenueShareEscrowMapOptional builder/referral escrow lookup; see `settlePNLIx`. | No |
| Returns |
|---|
Promise<string> |