Overview
Monitor your trading account balances, open positions, and profit/loss using the Indexer Client. This guide covers all balance and position-related queries.
Account Balances Query total equity, free collateral, and margin usage
Open Positions Track position size, entry price, and unrealized PnL
Querying Account Balances
Get Parent Subaccount
The parent subaccount contains all child subaccounts and their balances:
async function getAccountBalances (
client : CompositeClient ,
address : string ,
parentSubaccountNumber : number = 0
) {
const account = await client . indexerClient . account . getParentSubaccount (
address ,
parentSubaccountNumber
);
console . log ( "Parent Subaccount:" , parentSubaccountNumber );
console . log ( "Total Equity:" , account . subaccount . equity );
console . log ( "Free Collateral:" , account . subaccount . freeCollateral );
// Iterate through child subaccounts
for ( const subaccount of account . subaccount . childSubaccounts ) {
console . log ( ` \n Subaccount ${ subaccount . subaccountNumber } :` );
console . log ( ` Equity: $ ${ subaccount . equity } ` );
console . log ( ` Free Collateral: $ ${ subaccount . freeCollateral } ` );
console . log ( ` Margin Usage: $ ${ subaccount . marginUsage } ` );
}
return account ;
}
Total account value including unrealized PnL
Available collateral for opening new positions
Currently used margin for open positions
Understanding Balance Metrics
Formula : Equity = Free Collateral + Margin Usage + Unrealized PnLTotal value of your account including all open positions at current market prices. const equity = parseFloat ( subaccount . equity );
console . log ( `Total Account Value: $ ${ equity . toFixed ( 2 ) } ` );
Definition : Available collateral that can be used to open new positions.const freeCollateral = parseFloat ( subaccount . freeCollateral );
const maxPositionSize = freeCollateral * leverage ;
Always maintain sufficient free collateral to avoid liquidation.
Definition : Collateral currently allocated to open positions.const marginUsage = parseFloat ( subaccount . marginUsage );
const equity = parseFloat ( subaccount . equity );
const utilizationRate = ( marginUsage / equity ) * 100 ;
console . log ( `Margin Utilization: ${ utilizationRate . toFixed ( 1 ) } %` );
Querying Open Positions
Get All Positions
async function getOpenPositions (
client : CompositeClient ,
address : string ,
parentSubaccountNumber : number = 0
) {
const account = await client . indexerClient . account . getParentSubaccount (
address ,
parentSubaccountNumber
);
const positions = [];
for ( const subaccount of account . subaccount . childSubaccounts ) {
const openPositions = subaccount . openPerpetualPositions || {};
for ( const [ market , position ] of Object . entries ( openPositions )) {
positions . push ({
subaccountNumber: subaccount . subaccountNumber ,
market: position . market ,
side: position . side ,
size: parseFloat ( position . size || "0" ),
entryPrice: parseFloat ( position . entryPrice || "0" ),
unrealizedPnl: parseFloat ( position . unrealizedPnl || "0" ),
realizedPnl: parseFloat ( position . realizedPnl || "0" ),
netFunding: parseFloat ( position . netFunding || "0" ),
});
}
}
return positions ;
}
// Display positions
const positions = await getOpenPositions ( client , wallet . address , 0 );
positions . forEach (( pos ) => {
console . log ( ` ${ pos . market } :` );
console . log ( ` Subaccount: ${ pos . subaccountNumber } ` );
console . log ( ` Side: ${ pos . side } ` );
console . log ( ` Size: ${ pos . size } ` );
console . log ( ` Entry Price: $ ${ pos . entryPrice } ` );
console . log ( ` Unrealized PnL: $ ${ pos . unrealizedPnl . toFixed ( 2 ) } ` );
console . log ( ` Net Funding: $ ${ pos . netFunding . toFixed ( 2 ) } ` );
});
Position Fields
Market identifier (e.g., “ETH-USD”)
Position side: "LONG" or "SHORT"
Position size in base currency
Maximum position size reached
Average entry price for the position
Exit price (for closed positions)
Realized profit/loss from closed portion
Current unrealized profit/loss at market price
Net funding payments received/paid
Sum of all opening trades
Sum of all closing trades
Calculating PnL
Unrealized PnL
Calculate unrealized PnL for a position:
async function calculateUnrealizedPnL ( client : CompositeClient , position : any ) {
// Get current market price
const markets = await client . indexerClient . markets . getPerpetualMarkets ();
const market = markets . markets [ position . market ];
const currentPrice = parseFloat ( market . oraclePrice );
const entryPrice = parseFloat ( position . entryPrice );
const size = parseFloat ( position . size );
let pnl = 0 ;
if ( position . side === "LONG" ) {
pnl = ( currentPrice - entryPrice ) * size ;
} else {
// SHORT
pnl = ( entryPrice - currentPrice ) * size ;
}
console . log ( `Unrealized PnL: $ ${ pnl . toFixed ( 2 ) } ` );
console . log ( `ROI: ${ (( pnl / ( entryPrice * size )) * 100 ). toFixed ( 2 ) } %` );
return pnl ;
}
Total PnL
Calculate total PnL including realized and unrealized:
function calculateTotalPnL ( position : any ) : number {
const realized = parseFloat ( position . realizedPnl || "0" );
const unrealized = parseFloat ( position . unrealizedPnl || "0" );
const funding = parseFloat ( position . netFunding || "0" );
const totalPnL = realized + unrealized + funding ;
console . log ( `Realized PnL: $ ${ realized . toFixed ( 2 ) } ` );
console . log ( `Unrealized PnL: $ ${ unrealized . toFixed ( 2 ) } ` );
console . log ( `Net Funding: $ ${ funding . toFixed ( 2 ) } ` );
console . log ( `Total PnL: $ ${ totalPnL . toFixed ( 2 ) } ` );
return totalPnL ;
}
Monitoring Account Health
Margin Ratio
Calculate your margin ratio to monitor liquidation risk:
async function checkMarginHealth (
client : CompositeClient ,
address : string ,
subaccountNumber : number = 0
) {
const account = await client . indexerClient . account . getParentSubaccount (
address ,
0
);
const subaccount = account . subaccount . childSubaccounts . find (
( sub ) => sub . subaccountNumber === subaccountNumber
);
if ( ! subaccount ) {
throw new Error ( `Subaccount ${ subaccountNumber } not found` );
}
const equity = parseFloat ( subaccount . equity );
const marginUsage = parseFloat ( subaccount . marginUsage );
const freeCollateral = parseFloat ( subaccount . freeCollateral );
// Calculate margin ratio
const marginRatio = equity > 0 ? ( freeCollateral / equity ) * 100 : 0 ;
// Calculate utilization
const utilization = equity > 0 ? ( marginUsage / equity ) * 100 : 0 ;
console . log ( `Margin Health Check:` );
console . log ( ` Equity: $ ${ equity . toFixed ( 2 ) } ` );
console . log ( ` Free Collateral: $ ${ freeCollateral . toFixed ( 2 ) } ` );
console . log ( ` Margin Usage: $ ${ marginUsage . toFixed ( 2 ) } ` );
console . log ( ` Margin Ratio: ${ marginRatio . toFixed ( 2 ) } %` );
console . log ( ` Utilization: ${ utilization . toFixed ( 2 ) } %` );
// Liquidation warning
if ( marginRatio < 10 ) {
console . warn ( "⚠️ WARNING: High liquidation risk!" );
} else if ( marginRatio < 25 ) {
console . warn ( "⚠️ CAUTION: Moderate liquidation risk" );
} else {
console . log ( "✅ Margin health is good" );
}
return { equity , freeCollateral , marginUsage , marginRatio , utilization };
}
Monitor your margin ratio regularly. If it falls below the maintenance margin
requirement, your position may be liquidated.
Liquidation Price
Estimate liquidation price for a position:
function estimateLiquidationPrice (
entryPrice : number ,
size : number ,
side : "LONG" | "SHORT" ,
equity : number ,
maintenanceMarginFraction : number = 0.03 // 3% maintenance margin
) : number {
const maintenanceMargin = entryPrice * size * maintenanceMarginFraction ;
let liquidationPrice : number ;
if ( side === "LONG" ) {
// For longs: price drops until equity hits maintenance margin
liquidationPrice = entryPrice - ( equity - maintenanceMargin ) / size ;
} else {
// For shorts: price rises until equity hits maintenance margin
liquidationPrice = entryPrice + ( equity - maintenanceMargin ) / size ;
}
console . log ( `Liquidation Price: $ ${ liquidationPrice . toFixed ( 2 ) } ` );
console . log (
`Distance: ${ Math . abs (( liquidationPrice / entryPrice - 1 ) * 100 ). toFixed (
2
) } %`
);
return liquidationPrice ;
}
// Usage
const liqPrice = estimateLiquidationPrice (
3800 , // Entry price
1.0 , // Size (1 ETH)
"LONG" , // Side
4000 , // Current equity
0.03 // 3% maintenance margin
);
Source Account Balances
Query wallet balances outside of subaccounts:
async function getSourceBalances ( client : CompositeClient , address : string ) {
const balances = await client . validatorClient . get . getAccountBalances ( address );
console . log ( "Source Account Balances:" );
balances . forEach (( balance ) => {
console . log ( ` ${ balance . denom } : ${ balance . amount } ` );
// Convert USDC to human-readable format
if ( balance . denom . includes ( "usdc" ) || balance . denom . includes ( "USDC" )) {
const usdcAmount = parseInt ( balance . amount ) / 1_000_000 ; // 6 decimals
console . log ( ` ( ${ usdcAmount . toFixed ( 2 ) } USDC)` );
}
});
return balances ;
}
Source balances are in the main wallet and not available for trading until
deposited to a subaccount.
Portfolio Analytics
Portfolio Summary
Create a comprehensive portfolio summary:
async function getPortfolioSummary ( client : CompositeClient , address : string ) {
const account = await client . indexerClient . account . getParentSubaccount (
address ,
0
);
let totalEquity = 0 ;
let totalFreeCollateral = 0 ;
let totalMarginUsage = 0 ;
let totalPositions = 0 ;
let totalUnrealizedPnL = 0 ;
for ( const subaccount of account . subaccount . childSubaccounts ) {
totalEquity += parseFloat ( subaccount . equity || "0" );
totalFreeCollateral += parseFloat ( subaccount . freeCollateral || "0" );
totalMarginUsage += parseFloat ( subaccount . marginUsage || "0" );
const positions = subaccount . openPerpetualPositions || {};
totalPositions += Object . keys ( positions ). length ;
for ( const position of Object . values ( positions )) {
totalUnrealizedPnL += parseFloat (( position as any ). unrealizedPnl || "0" );
}
}
const portfolio = {
totalEquity ,
totalFreeCollateral ,
totalMarginUsage ,
totalPositions ,
totalUnrealizedPnL ,
utilizationRate: ( totalMarginUsage / totalEquity ) * 100 ,
roi: ( totalUnrealizedPnL / ( totalEquity - totalUnrealizedPnL )) * 100 ,
};
console . log ( "Portfolio Summary:" );
console . log ( ` Total Equity: $ ${ portfolio . totalEquity . toFixed ( 2 ) } ` );
console . log (
` Free Collateral: $ ${ portfolio . totalFreeCollateral . toFixed ( 2 ) } `
);
console . log ( ` Margin Usage: $ ${ portfolio . totalMarginUsage . toFixed ( 2 ) } ` );
console . log ( ` Open Positions: ${ portfolio . totalPositions } ` );
console . log ( ` Unrealized PnL: $ ${ portfolio . totalUnrealizedPnL . toFixed ( 2 ) } ` );
console . log ( ` Utilization: ${ portfolio . utilizationRate . toFixed ( 2 ) } %` );
console . log ( ` ROI: ${ portfolio . roi . toFixed ( 2 ) } %` );
return portfolio ;
}
Position Breakdown by Market
async function getPositionBreakdown ( client : CompositeClient , address : string ) {
const positions = await getOpenPositions ( client , address , 0 );
const markets = await client . indexerClient . markets . getPerpetualMarkets ();
const breakdown = positions . map (( pos ) => {
const market = markets . markets [ pos . market ];
const currentPrice = parseFloat ( market . oraclePrice );
const notionalValue = pos . size * currentPrice ;
return {
... pos ,
currentPrice ,
notionalValue ,
pnlPercent: ( pos . unrealizedPnl / ( pos . entryPrice * pos . size )) * 100 ,
};
});
// Sort by notional value
breakdown . sort (( a , b ) => b . notionalValue - a . notionalValue );
console . log ( " \n Position Breakdown:" );
breakdown . forEach (( pos ) => {
console . log ( ` \n ${ pos . market } :` );
console . log ( ` Size: ${ pos . size } ${ pos . side } ` );
console . log ( ` Entry: $ ${ pos . entryPrice . toFixed ( 2 ) } ` );
console . log ( ` Current: $ ${ pos . currentPrice . toFixed ( 2 ) } ` );
console . log ( ` Notional: $ ${ pos . notionalValue . toFixed ( 2 ) } ` );
console . log (
` PnL: $ ${ pos . unrealizedPnl . toFixed ( 2 ) } ( ${ pos . pnlPercent . toFixed ( 2 ) } %)`
);
});
return breakdown ;
}
Real-time Balance Monitoring
Polling Strategy
class BalanceMonitor {
private client : CompositeClient ;
private address : string ;
private intervalId ?: NodeJS . Timeout ;
constructor ( client : CompositeClient , address : string ) {
this . client = client ;
this . address = address ;
}
async checkBalances () {
const account = await this . client . indexerClient . account . getParentSubaccount (
this . address ,
0
);
const equity = parseFloat ( account . subaccount . equity );
const freeCollateral = parseFloat ( account . subaccount . freeCollateral );
console . log (
`[ ${ new Date (). toISOString () } ] Equity: $ ${ equity . toFixed (
2
) } , Free: $ ${ freeCollateral . toFixed ( 2 ) } `
);
// Alert if free collateral is low
if ( freeCollateral < equity * 0.1 ) {
console . warn ( "⚠️ Low free collateral warning!" );
}
return { equity , freeCollateral };
}
startMonitoring ( intervalSeconds : number = 30 ) {
console . log (
`Starting balance monitoring every ${ intervalSeconds } seconds...`
);
this . intervalId = setInterval ( async () => {
try {
await this . checkBalances ();
} catch ( error ) {
console . error ( "Error checking balances:" , error );
}
}, intervalSeconds * 1000 );
}
stopMonitoring () {
if ( this . intervalId ) {
clearInterval ( this . intervalId );
console . log ( "Balance monitoring stopped" );
}
}
}
// Usage
const monitor = new BalanceMonitor ( client , wallet . address );
monitor . startMonitoring ( 30 ); // Check every 30 seconds
Best Practices
Check balances before placing large orders: const account = await client . indexerClient . account . getParentSubaccount (
wallet . address ,
0
);
const freeCollateral = parseFloat ( account . subaccount . freeCollateral );
if ( freeCollateral < orderCost ) {
throw new Error ( "Insufficient free collateral" );
}
Set up alerts for low margin ratios: if ( marginRatio < 10 ) {
// Send alert
// Close positions or add collateral
}
Store balance snapshots for performance tracking: interface BalanceSnapshot {
timestamp : Date ;
equity : number ;
freeCollateral : number ;
unrealizedPnL : number ;
}
const snapshots : BalanceSnapshot [] = [];
Next Steps