The SDK exports utility functions for position math, TP/SL calculations, fee estimation, order classification, and formatting. These are stateless pure functions — they do not call the API.
Position Calculations
import {
calculatePositionSize,
calculateNotionalValue,
calculateUnrealizedPnl,
calculateRoe,
calculateRequiredMargin,
calculateRealizedPnlPercent,
} from '@lifi/perps-sdk';
calculatePositionSize
Calculate position size from margin, leverage, and price.
const size = calculatePositionSize(marginUsd, leverage, price);
// e.g. calculatePositionSize(1000, 10, 95000) → position size for $10k notional
| Parameter | Type | Description |
|---|
marginUsd | number | Margin amount in USD |
leverage | number | Leverage multiplier |
price | number | Entry price |
calculateNotionalValue
const notional = calculateNotionalValue(size, price);
calculateUnrealizedPnl
const pnl = calculateUnrealizedPnl(entryPrice, currentPrice, size);
calculateRoe
Return on equity (PnL / margin).
const roe = calculateRoe(pnl, margin);
calculateRequiredMargin
const margin = calculateRequiredMargin(notionalValue, leverage);
calculateRealizedPnlPercent
const pnlPercent = calculateRealizedPnlPercent(realizedPnl, size, price);
Position Math (Predictive)
Pure-function helpers for predicting how a perp position changes after a fill — used by add-to-position and partial-close preview blocks. These read SDK shapes as plain numbers (callers parse Position.size, entryPrice, etc. from string), and use the convention long = +1, short = -1. Sizes passed in are non-negative magnitudes; direction is carried by isLong.
import {
directionSign,
predictAverageEntryPrice,
predictNewLeverage,
predictUnrealizedPnl,
realizedPnlOnClose,
} from '@lifi/perps-sdk';
directionSign
Direction sign for a position.
directionSign(true); // 1 (long)
directionSign(false); // -1 (short)
| Parameter | Type | Description |
|---|
isLong | boolean | True for long positions, false for short |
Returns: 1 | -1
predictAverageEntryPrice
Predicted average entry price after adding to an existing position. Weighted average of current entry and the new fill price, weighted by leg size in coin units. Both legs must be in the same direction (this helper is for adding to, not flipping, a position).
const newEntry = predictAverageEntryPrice({
currentSize: 1,
currentEntry: 95_000,
addSize: 0.5,
fillPrice: 96_500,
});
// 95_500
| Parameter | Type | Description |
|---|
currentSize | number | Existing position size in coin units (>= 0) |
currentEntry | number | Existing position’s average entry price |
addSize | number | Size being added in coin units (>= 0) |
fillPrice | number | Price the new size is expected to fill at (mid for market, limit price for limit orders) |
Returns: number | undefined — the new weighted-average entry price, or undefined if the inputs cannot produce a valid average (zero combined size, non-finite values).
predictNewLeverage
Predicted effective leverage (totalNotional / totalMargin) after adding margin and notional. The caller computes notional from size and price (e.g. with calculateNotionalValue) and supplies the additional margin the user is about to put up.
const newLeverage = predictNewLeverage({
currentNotional: 10_000,
currentMargin: 1_000,
addNotional: 5_000,
addMargin: 500,
});
// 10
| Parameter | Type | Description |
|---|
currentNotional | number | Current position notional |
currentMargin | number | Current position margin |
addNotional | number | Notional being added |
addMargin | number | Margin being added |
Returns: number | undefined — the new effective leverage, or undefined if total margin is non-positive.
predictUnrealizedPnl
Predicted unrealised PnL at the current mark price: (markPrice - entryPrice) * size * directionSign(isLong).
const uPnl = predictUnrealizedPnl({
entryPrice: 95_000,
markPrice: 96_000,
size: 1,
isLong: true,
});
// 1_000
| Parameter | Type | Description |
|---|
entryPrice | number | Position entry price |
markPrice | number | Current mark price |
size | number | Position size as a non-negative magnitude |
isLong | boolean | Direction of the position |
Returns: number
realizedPnlOnClose
Realised PnL on the portion of a position being closed: (closePrice - entryPrice) * closeSize * directionSign(isLong).
const rPnl = realizedPnlOnClose({
entryPrice: 95_000,
closePrice: 96_000,
closeSize: 0.5,
isLong: true,
});
// 500
| Parameter | Type | Description |
|---|
entryPrice | number | Position entry price |
closePrice | number | Price the close fills at |
closeSize | number | Size being closed as a non-negative magnitude |
isLong | boolean | Direction of the position being closed |
Returns: number
Order Math (Expected rPnL Previews)
Helpers for previewing the realised PnL the user would lock in if a resting order filled against a matching position. Used by Orders-tab rows on the trading UI. These compose directionSign and realizedPnlOnClose from Position Math, so the two modules stay in lockstep.
Orders that open or add to a position have no defined rPnL and return null (typically rendered as an em-dash in the UI).
import {
expectedRealizedPnlForOpenOrder,
expectedRealizedPnlForTriggerOrder,
findMatchingPosition,
resolveCloseSize,
} from '@lifi/perps-sdk';
import type { OpenOrder, Position, TriggerOrder } from '@lifi/perps-sdk';
findMatchingPosition
Pick the matching open position for an order’s asset, if any.
const matching = findMatchingPosition(order.market.id, positions);
| Parameter | Type | Description |
|---|
marketId | string | The order’s market.id |
positions | readonly Position[] | Open positions list (any symbol) |
Returns: Position | undefined
resolveCloseSize
Resolve the close-size against a position, applying the spec’s cap rules:
orderSize === 0 is the Hyperliquid convention for “close entire position” used by trigger orders → close the full position size.
- Otherwise cap at the absolute position size; an order larger than the position can only close what’s open.
Inputs are non-negative magnitudes.
resolveCloseSize(0, 1.5); // 1.5 (zero == close-all)
resolveCloseSize(0.5, 1.5); // 0.5 (partial close)
resolveCloseSize(2, 1.5); // 1.5 (capped at position)
| Parameter | Type | Description |
|---|
orderSize | number | Order size, non-negative magnitude |
positionSize | number | Open position size, non-negative magnitude |
Returns: number
expectedRealizedPnlForOpenOrder
Expected rPnL for a resting limit order against a matching position.
Reducing requires opposite sides (long position + SELL, short position + BUY). Same-side orders add to the position and return null. Returns null if there is no matching position or the inputs are non-finite.
import { expectedRealizedPnlForOpenOrder, findMatchingPosition } from '@lifi/perps-sdk';
import type { OpenOrder, Position } from '@lifi/perps-sdk';
function previewOrderRPnl(order: OpenOrder, positions: readonly Position[]) {
const position = findMatchingPosition(order.market.id, positions);
return expectedRealizedPnlForOpenOrder(order, position);
// number when the order would reduce the position; null otherwise
}
| Parameter | Type | Description |
|---|
order | OpenOrder | The resting limit order |
position | Position | undefined | Matching open position, if any |
Returns: number | null
expectedRealizedPnlForTriggerOrder
Expected rPnL for a TP/SL trigger order against a matching position.
A trigger order is by construction a closing leg — its direction is the opposite of the position’s. With no matching position there is nothing to close, so rPnL is null. The trigger price (always present on the SDK TriggerOrder shape) is used as the rPnL price; the optional limitPrice for STOP_LIMIT / TAKE_PROFIT_LIMIT is the post-trigger limit, not the rPnL price.
import {
expectedRealizedPnlForTriggerOrder,
findMatchingPosition,
} from '@lifi/perps-sdk';
import type { Position, TriggerOrder } from '@lifi/perps-sdk';
function previewTriggerRPnl(order: TriggerOrder, positions: readonly Position[]) {
const position = findMatchingPosition(order.market.id, positions);
return expectedRealizedPnlForTriggerOrder(order, position);
}
| Parameter | Type | Description |
|---|
order | TriggerOrder | The TP/SL trigger order |
position | Position | undefined | Matching open position, if any |
Returns: number | null
TP/SL Calculations
import {
calculateExpectedPnl,
priceFromPercent,
percentFromPrice,
} from '@lifi/perps-sdk';
calculateExpectedPnl
Calculate expected PnL if a trigger price is hit.
const { amount, percent } = calculateExpectedPnl(
triggerPrice,
entryPrice,
leverage,
isLong,
margin
);
| Parameter | Type | Description |
|---|
triggerPrice | number | TP/SL trigger price |
entryPrice | number | Position entry price |
leverage | number | Leverage |
isLong | boolean | Whether position is long |
margin | number | Position margin |
Returns: { amount: number, percent: number }
priceFromPercent
Convert a target PnL percentage to a trigger price.
const price = priceFromPercent(percent, entryPrice, leverage, isLong);
// e.g. priceFromPercent(50, 95000, 10, true) → price for +50% ROE on a 10x long
percentFromPrice
Convert a trigger price to a PnL percentage.
const percent = percentFromPrice(price, entryPrice, leverage, isLong);
Fee & Slippage
import { estimateFees, applySlippage } from '@lifi/perps-sdk';
estimateFees
const fees = estimateFees(sizeUsd, feeRate);
// e.g. estimateFees(10000, 0.0005) → 5.0
applySlippage
Adjust a price by a slippage percentage.
const adjusted = applySlippage(price, slippagePercent, isBuy);
// Buy: price goes up. Sell: price goes down.
Account Summary
There is no standalone calculateAccountSummary util. Derive portfolio metrics with the PerpsClient.getAccountSummary() method:
const summary = perps.getAccountSummary(account, positions);
// { portfolioValue, availableMargin, marginUsed, unrealizedPnl } — all strings
| Parameter | Type | Description |
|---|
account | AccountResponse | Account from getAccount() |
positions | Position[] | Positions from getPositions() |
Returns: AccountSummary — { portfolioValue, availableMargin, marginUsed, unrealizedPnl }, all string fields.
The underlying pure function is summarizeAccount(account, positions), exported from @lifi/perps-sdk.
Order Classification
import {
isTakeProfitOrder,
isStopLossOrder,
isTpSlOrder,
classifyFill,
} from '@lifi/perps-sdk';
isTakeProfitOrder / isStopLossOrder / isTpSlOrder
Check whether an order is a TP, SL, or either.
if (isTpSlOrder(order)) {
console.log('This is a trigger order');
}
classifyFill
classifyFill is deprecated — read Fill.classification (already populated on every fill) instead.
Classify a fill based on side and realized PnL. Returns a FillClassification enum value (Title-Case strings):
const type = classifyFill(side, realizedPnl);
// FillClassification — e.g. 'Opened Long' | 'Opened Short' | 'Closed Long' | 'Closed Short'
Validation
import { validateMargin } from '@lifi/perps-sdk';
validateMargin
Check whether the user has sufficient margin for an order.
const result = validateMargin(margin, leverage, availableBalance, feeRate, minMarginUsd);
// '' (valid) | 'insufficient' | 'below-minimum'
| Parameter | Type | Description |
|---|
margin | number | Margin to allocate |
leverage | number | Leverage multiplier |
availableBalance | number | User’s available balance |
feeRate | number | Expected fee rate |
minMarginUsd | number | Minimum margin in USD |
Parsing & Conversion
import { stringToFloat, fromBaseUnits, fromBaseUnitsNumber } from '@lifi/perps-sdk';
stringToFloat
Parse formatted currency/percentage strings to a number.
stringToFloat('$1,234.56'); // 1234.56
stringToFloat('50%'); // 50
fromBaseUnits / fromBaseUnitsNumber
Convert a base-unit amount (passed as a string) to a decimal string or number.
fromBaseUnits('1000000', 6); // '1'
fromBaseUnitsNumber('1000000', 6); // 1
Signing
import { signTypedData } from '@lifi/perps-sdk';
signTypedData
Sign EIP-712 typed data with a private key. Useful for server-side signing without a wallet. Async — returns Promise<Hex>.
const signature = await signTypedData(privateKey, typedData);
| Parameter | Type | Description |
|---|
privateKey | Hex | Private key (0x-prefixed) |
typedData | PerpsTypedData | EIP-712 typed data from a create endpoint |
Hyperliquid-specific
These utilities are specific to Hyperliquid’s order formatting requirements and are exported from @lifi/perps-sdk-provider-hyperliquid (not the core SDK):
import {
calculateLiquidationPrice,
calculateMaintenanceMarginRate,
formatOrderPrice,
formatOrderSize,
getMaxPriceDecimals,
} from '@lifi/perps-sdk-provider-hyperliquid';
calculateLiquidationPrice and calculateMaintenanceMarginRate return number | undefined (undefined when inputs can’t yield a valid result).
calculateLiquidationPrice
const liqPrice = calculateLiquidationPrice(entryPrice, leverage, isLong, maxLeverage);
calculateMaintenanceMarginRate
const mmr = calculateMaintenanceMarginRate(maxLeverage);
Format a price for Hyperliquid submission (5 significant figures, correct decimal places).
formatOrderPrice(95123.456, 2); // '95123'
Format a size for Hyperliquid submission (no trailing zeros).
formatOrderSize(0.100, 3); // '0.1'
getMaxPriceDecimals
Get the maximum number of price decimal places for an asset.
const decimals = getMaxPriceDecimals(szDecimals);