Skip to main content

Position Tracking

Track and manage positions across multiple DeFi protocols with the ZeroQuant SDK.

Overview

The position tracking system provides:

  • Real-time position monitoring across protocols
  • PnL calculation (unrealized and realized)
  • Margin and liquidation tracking
  • Portfolio aggregation
  • Position closing operations

Quick Start

import { createPositionTracker } from '@zeroquant/sdk';

const tracker = createPositionTracker({
provider,
priceFeed: {
getPrice: async (token) => fetchPriceFromOracle(token),
},
});

// Get all open positions
const positions = tracker.getOpenPositions(vaultAddress);

// Get portfolio summary
const summary = await tracker.getPortfolioSummary(vaultAddress);

console.log('Total Value:', summary.totalValueUSD);
console.log('Unrealized PnL:', summary.totalUnrealizedPnL);
console.log('Position Count:', summary.positionCount);

Position Types

Spot Positions

const spotPosition = {
id: 'pos_spot_weth_1',
vaultAddress,
protocol: 'uniswap_v3',
type: 'spot',
asset: WETH_ADDRESS,
size: ethers.parseEther('5'),
entryPrice: ethers.parseUnits('2350', 6),
side: 'long',
status: 'open',
};

Lending Positions

const lendingPosition = {
id: 'pos_aave_weth_1',
vaultAddress,
protocol: 'aave_v3',
type: 'lending',
asset: WETH_ADDRESS,
size: ethers.parseEther('10'),
entryPrice: ethers.parseUnits('2400', 6),
side: 'long',
status: 'open',
metadata: {
apy: 3.5,
healthFactor: 1.8,
},
};

Perpetual Positions

const perpPosition = {
id: 'pos_gmx_btc_1',
vaultAddress,
protocol: 'gmx',
type: 'perpetual',
asset: WBTC_ADDRESS,
size: BigInt(10000000), // 0.1 BTC
entryPrice: ethers.parseUnits('41500', 6),
side: 'long',
leverage: 5,
status: 'open',
margin: {
initial: ethers.parseUnits('840', 6),
maintenance: ethers.parseUnits('420', 6),
used: ethers.parseUnits('840', 6),
available: ethers.parseUnits('160', 6),
},
};

LP Positions

const lpPosition = {
id: 'pos_uni_v3_lp_1',
vaultAddress,
protocol: 'uniswap_v3',
type: 'liquidity',
asset: LP_TOKEN_ADDRESS,
size: BigInt(1), // NFT ID
status: 'open',
lpInfo: {
token0: WETH_ADDRESS,
token1: USDC_ADDRESS,
amount0: ethers.parseEther('1'),
amount1: ethers.parseUnits('2500', 6),
feeTier: 3000,
tickLower: -887220,
tickUpper: 887220,
liquidity: BigInt('1234567890'),
feesEarned0: ethers.parseEther('0.01'),
feesEarned1: ethers.parseUnits('25', 6),
},
};

Querying Positions

Get All Open Positions

const positions = tracker.getOpenPositions(vaultAddress);

for (const pos of positions) {
console.log(`${pos.protocol} - ${pos.asset}`);
console.log(` Size: ${pos.size}`);
console.log(` Entry: ${pos.entryPrice}`);
console.log(` PnL: ${pos.pnl?.unrealized}`);
}

Get Specific Position

const position = await client.getPositionInformation('pos_gmx_btc_1');

if (position) {
console.log('Position:', position.id);
console.log('Current Value:', position.currentValueUSD);
console.log('PnL %:', position.pnl?.percentage);
}

Filter by Protocol

const aavePositions = positions.filter(p => p.protocol === 'aave_v3');
const gmxPositions = positions.filter(p => p.protocol === 'gmx');

PnL Calculation

const position = tracker.getPosition('pos_gmx_btc_1');

if (position.pnl) {
console.log('PnL Breakdown:');
console.log(' Unrealized:', position.pnl.unrealized);
console.log(' Realized:', position.pnl.realized);
console.log(' Fees Paid:', position.pnl.fees);
console.log(' Funding:', position.pnl.funding);
console.log(' Net PnL:', position.pnl.net);
console.log(' Percentage:', position.pnl.percentage.toFixed(2) + '%');
}

Portfolio Summary

const summary = await tracker.getPortfolioSummary(vaultAddress);

console.log('Portfolio Summary:');
console.log(' Total Value:', summary.totalValueUSD);
console.log(' Unrealized PnL:', summary.totalUnrealizedPnL);
console.log(' Realized PnL:', summary.totalRealizedPnL);
console.log(' Total Fees:', summary.totalFees);
console.log(' Position Count:', summary.positionCount);

console.log('\n By Protocol:');
for (const [protocol, value] of Object.entries(summary.byProtocol)) {
console.log(` ${protocol}: $${value}`);
}

console.log('\n By Asset:');
for (const [asset, value] of Object.entries(summary.byAsset)) {
console.log(` ${asset}: $${value}`);
}

Closing Positions

Full Close

const result = await tracker.closePosition({
positionId: 'pos_gmx_btc_1',
});

if (result.success) {
console.log('Position closed');
console.log(' Closed Amount:', result.closedAmount);
console.log(' Close Price:', result.closePrice);
console.log(' Realized PnL:', result.realizedPnL);
console.log(' TX Hash:', result.txHash);
}

Partial Close

const result = await tracker.closePosition({
positionId: 'pos_aave_weth_1',
amount: ethers.parseEther('2'), // Close 2 of 10 ETH
});

Margin & Liquidation

Check Margin Health

const position = tracker.getPosition('pos_gmx_btc_1');

if (position.margin) {
const healthRatio = position.margin.available / position.margin.maintenance;

console.log('Margin Status:');
console.log(' Initial:', position.margin.initial);
console.log(' Maintenance:', position.margin.maintenance);
console.log(' Used:', position.margin.used);
console.log(' Available:', position.margin.available);
console.log(' Health Ratio:', healthRatio.toFixed(2));

if (healthRatio < 1.2) {
console.warn('WARNING: Position approaching liquidation!');
}
}

Liquidation Price

note

Liquidation prices only apply to leveraged positions (perpetuals, margin lending). Spot positions do not have liquidation prices.

// Only check liquidation for leveraged position types
if (position.type === 'perpetual' || position.type === 'lending') {
if (position.liquidationPrice) {
const currentPrice = await priceFeed.getPrice(position.asset);

// Guard against zero/undefined current price
if (!currentPrice || Number(currentPrice) === 0) {
console.log('Current price unavailable - cannot calculate distance');
return;
}

const distanceToLiq = Math.abs(
(Number(currentPrice) - Number(position.liquidationPrice)) / Number(currentPrice)
);

console.log('Liquidation Price:', position.liquidationPrice);
console.log('Current Price:', currentPrice);
console.log('Distance to Liquidation:', (distanceToLiq * 100).toFixed(2) + '%');

if (distanceToLiq < 0.1) {
console.warn('WARNING: Position within 10% of liquidation!');
}
}
} else {
console.log('Spot position - no liquidation price applicable');
}

Real-Time Updates

Subscribe to Position Updates

const unsubscribe = tracker.onPositionUpdate(vaultAddress, (position) => {
console.log('Position Update:', position.id);
console.log(' New PnL:', position.pnl?.unrealized);
});

// Connect WebSocket
tracker.connect();

// Later: cleanup
unsubscribe();
tracker.disconnect();

Subscribe to Liquidation Warnings

const unsubscribe = tracker.onLiquidationWarning(vaultAddress, (position) => {
console.warn('LIQUIDATION WARNING:', position.id);
console.warn(' Health Ratio:', position.margin?.available / position.margin?.maintenance);
});

Supported Protocols

ProtocolPosition TypesFeatures
Uniswap V2/V3Spot, LPSwap tracking, LP positions
Aave V3LendingCollateral, borrowing, health factor
Compound V3LendingSupply, borrow tracking
GMXPerpetualLeverage, funding, liquidation
CurveLPStable pools, gauge positions
BalancerLPWeighted pools, boosted pools

Integration with Exchange Client

const client = createExchangeClient({ /* config */ });

// Position information through exchange client
const positions = await client.getPositionInformation();

// Portfolio summary
const portfolio = await client.getPortfolioSummary();

// Close position
const result = await client.closePosition('pos_id', amount);

Next Steps