Skip to main content
This guide shows how to combine them to derive current value, unrealized PnL, and percent change for both spot and perp positions.

Philosophy

Your app likely its own high-performance system for live prices. (If not, we can recommend pricing APIs for you.) For that reason, the Clicker API only returns historical data: cost basis, realized PnL, and position amounts. You bring your own live price data to these calculations. These fields can be found in metadata and metadata.positionStats of a HydratedPosition.

Detecting Spot vs Perps

Check metadata.perpPositionType. If it’s "long" or "short", the position is a perp. If it’s null or absent, it’s spot. For Hyperliquid positions, you can also check for the presence of positionAmountWithLeverage.

Spot Positions

Current Value

currentValue = positionAmount * currentPrice
FieldSource
positionAmountmetadata.positionAmount
currentPriceYour pricing feed

Unrealized PnL

costBasis = holdingsCostBasisUSD + holdingReceivedCostBasisUSD
unrealizedPnL = currentValue - costBasis
FieldSource
holdingsCostBasisUSDmetadata.positionStats.holdingsCostBasisUSD
holdingReceivedCostBasisUSDmetadata.positionStats.holdingReceivedCostBasisUSD
holdingsCostBasisUSD is the cost basis of tokens the trader bought. holdingReceivedCostBasisUSD is the cost basis of tokens the trader received via airdrops or transfers, valued at market price at the time of receipt. Together they form the full cost basis of current holdings.

Total PnL

To show total PnL (realized + unrealized):
totalPnL = realizedGainsUSD + unrealizedPnL
realizedGainsUSD captures profit or loss from tokens already sold.

Percent Change

totalInvested = boughtUSD + receivedCostBasisUSD
percentChange = (totalPnL / totalInvested) * 100
FieldSource
boughtUSDmetadata.positionStats.boughtUSD
receivedCostBasisUSDmetadata.positionStats.receivedCostBasisUSD
Only compute percent change when totalInvested >= 1. Below that threshold, the percentage is meaningless.

Example

A trader bought 10,000 tokens at $0.05 for $500. The token is now $0.08.
currentValue   = 10000 * 0.08            = $800
costBasis      = 500 + 0                  = $500
unrealizedPnL  = 800 - 500               = $300
totalPnL       = 0 + 300                  = $300  (no realized gains yet)
percentChange  = (300 / 500) * 100        = 60%

Perp Positions

Perps use leveraged values for PnL and margin (collateral) for position value.

Unrealized PnL

Compute the current leveraged notional value:
leveragedNotionalValue = abs(positionAmountWithLeverage) * currentPrice
Then compute PnL based on direction:
// Long
unrealizedPnL = leveragedNotionalValue - holdingsCostBasisUSDWithLeverage

// Short
unrealizedPnL = holdingsCostBasisUSDWithLeverage - leveragedNotionalValue
FieldSource
positionAmountWithLeveragemetadata.positionAmountWithLeverage
holdingsCostBasisUSDWithLeveragemetadata.positionStats.holdingsCostBasisUSDWithLeverage

Current Value (Equity)

For perps, the position value is the trader’s equity: margin plus unrealized PnL.
margin = holdingsCostBasisUSD
currentValue = abs(margin + unrealizedPnL)
holdingsCostBasisUSD on a perp position represents the margin (collateral) posted, not the full notional exposure.

Total PnL

totalPnL = realizedGainsUSD + unrealizedPnL

Percent Change

totalInvested = boughtUSD + receivedCostBasisUSD
percentChange = (totalPnL / totalInvested) * 100
Same formula as spot. boughtUSD on a perp is the total margin posted across all entries.

Example

A trader opens a 5x long on ETH at $3,000 with $1,000 margin. ETH is now $3,300.
positionAmountWithLeverage             = 1.6667  (5000 / 3000)
holdingsCostBasisUSDWithLeverage       = $5,000
holdingsCostBasisUSD (margin)          = $1,000

leveragedNotionalValue  = 1.6667 * 3300      = $5,500
unrealizedPnL      = 5500 - 5000        = $500
currentValue       = abs(1000 + 500)     = $1,500
totalPnL           = 0 + 500            = $500
percentChange      = (500 / 1000) * 100  = 50%
The 10% move in ETH produced a 50% return because of 5x leverage.

Closed Positions

A position is closed when the trader has exited entirely. For closed positions, there is no unrealized PnL to compute. Display realizedGainsUSD as the total PnL and compute percent change against totalInvested as above.
totalPnL      = realizedGainsUSD
percentChange = (realizedGainsUSD / totalInvested) * 100
You can detect a closed position by checking metadata.positionStats.isOpen === false, or by checking that positionAmount is near zero (below 0.0001).

TL;DR Calculation

function calculatePosition(
  metadata: HydratedPosition["metadata"],
  currentPrice: number,
) {
  const { positionStats: stats } = metadata;
  const isPerp =
    metadata.perpPositionType === "long" ||
    metadata.perpPositionType === "short";

  // Unrealized PnL
  const notionalValue =
    Math.abs(
      isPerp ? metadata.positionAmountWithLeverage : metadata.positionAmount,
    ) * currentPrice;
  const costBasis = isPerp
    ? stats.holdingsCostBasisUSDWithLeverage
    : stats.holdingsCostBasisUSD + stats.holdingReceivedCostBasisUSD;
  const unrealizedPnL =
    metadata.perpPositionType === "short"
      ? costBasis - notionalValue
      : notionalValue - costBasis;

  // Current value, total PnL, percent change
  const currentValue = isPerp
    ? Math.abs(stats.holdingsCostBasisUSD + unrealizedPnL)
    : notionalValue;
  const totalPnL = stats.realizedGainsUSD + unrealizedPnL;
  const totalInvested = stats.boughtUSD + stats.receivedCostBasisUSD;
  const percentChange =
    totalInvested >= 1 ? (totalPnL / totalInvested) * 100 : undefined;

  return { currentValue, unrealizedPnL, totalPnL, percentChange };
}