Skip to main content

Data Source Strategy

Position monitoring combines two data sources:
SourceUse For
Subgraph (GraphQL)Listing positions, filtering by status/side/tenor, historical data, closed position history
RPC (contract reads)Real-time PnL, current margin health, liquidation proximity, live forward prices
The subgraph indexes onchain events into a queryable API. RPC reads return the latest block state. Use both together for a complete view.

List Open Positions

Query the subgraph for all open positions belonging to an account.
const SUBGRAPH_URL = "https://api.studio.thegraph.com/query/YOUR_SUBGRAPH_ID";

const query = `{
  positions(
    where: { account: "0xYOUR_ADDRESS", status: "OPEN" }
    orderBy: openTimestamp
    orderDirection: desc
    first: 25
  ) {
    id
    side
    tenor
    notional
    entryStrike
    imLocked
    mmThreshold
    openTimestamp
    fixingTimestamp
  }
}`;

const response = await fetch(SUBGRAPH_URL, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ query }),
});

const { data } = await response.json();

for (const pos of data.positions) {
  console.log(`#${pos.id} ${pos.side === "0" ? "LONG" : "SHORT"} `
    + `${Number(pos.notional) / 1e6} USDC `
    + `@ ${Number(pos.entryStrike) / 1e18}`);
}

Get Real-Time PnL

The subgraph stores static position data (entry strike, notional, margin). For live PnL, read directly from the contract — it computes PnL against the current forward price.
import { createPublicClient, http, formatUnits } from "viem";
import { sepolia } from "viem/chains";
import { positionManagerAbi } from "@nile-markets/sdk";

const client = createPublicClient({
  chain: sepolia,
  transport: http("YOUR_RPC_URL"),
});

const POSITION_MANAGER = "0x..."; // See Contract Addresses page
const positionId = 1n;

const position = await client.readContract({
  address: POSITION_MANAGER,
  abi: positionManagerAbi,
  functionName: "getPosition",
  args: [positionId],
});

// getPositionPnL returns the current unrealized PnL
const pnl = await client.readContract({
  address: POSITION_MANAGER,
  abi: positionManagerAbi,
  functionName: "getPositionPnL",
  args: [positionId],
});

console.log(`Position #${positionId}`);
console.log(`  Notional: ${formatUnits(position.notional, 6)} USDC`);
console.log(`  Entry strike: ${formatUnits(position.entryStrike, 18)}`);
console.log(`  Margin locked: ${formatUnits(position.imLocked, 6)} USDC`);
console.log(`  Unrealized PnL: ${formatUnits(pnl, 6)} USDC`);

Check Margin Health

A position’s margin health determines how close it is to liquidation. The key values:
MetricMeaning
EquityimLocked + unrealizedPnL — your effective collateral value
Maintenance margin (MM)mmThreshold — the minimum equity before liquidation
Health ratioequity / mmThreshold — below 1.0 triggers liquidation
import { positionManagerAbi, marginAccountsAbi } from "@nile-markets/sdk";
import { formatUnits } from "viem";

const POSITION_MANAGER = "0x..."; // See Contract Addresses page
const positionId = 1n;

const [position, pnl] = await Promise.all([
  client.readContract({
    address: POSITION_MANAGER,
    abi: positionManagerAbi,
    functionName: "getPosition",
    args: [positionId],
  }),
  client.readContract({
    address: POSITION_MANAGER,
    abi: positionManagerAbi,
    functionName: "getPositionPnL",
    args: [positionId],
  }),
]);

const equity = BigInt(position.imLocked) + BigInt(pnl);
const mmThreshold = BigInt(position.mmThreshold);
const healthRatio = Number(equity) / Number(mmThreshold);

console.log(`Equity: ${formatUnits(equity, 6)} USDC`);
console.log(`MM threshold: ${formatUnits(mmThreshold, 6)} USDC`);
console.log(`Health ratio: ${healthRatio.toFixed(4)}`);

if (healthRatio < 1.2) {
  console.warn("WARNING: Position is near liquidation — consider adding margin");
}
if (healthRatio < 1.0) {
  console.error("CRITICAL: Position is below MM — eligible for liquidation");
}
When equity drops below the maintenance margin threshold, a keeper can liquidate the position. The trader loses their locked margin minus any remaining equity, plus a liquidation penalty fee. See Liquidation for full mechanics.

Search Positions with Filters

Use the subgraph or CLI to search across all positions with filters.
const query = `{
  positions(
    where: {
      side: 0,
      tenor: 1,
      status: "OPEN",
      notional_gte: "5000000000"
    }
    orderBy: notional
    orderDirection: desc
    first: 10
  ) {
    id
    account { id }
    side
    tenor
    notional
    entryStrike
    openTimestamp
  }
}`;

const response = await fetch(SUBGRAPH_URL, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ query }),
});

const { data } = await response.json();
console.log(`Found ${data.positions.length} LONG 1W positions ≥ 5,000 USDC`);
Available filter fields:
FieldTypeExample
sideInt (0=LONG, 1=SHORT)side: 0
tenorInt (0=1D, 1=1W, 2=1M)tenor: 1
statusStringstatus: "OPEN"
accountAddressaccount: "0x..."
notional_gteString (raw 6-decimal)notional_gte: "5000000000"
openTimestamp_gteString (unix seconds)openTimestamp_gte: "1700000000"

Monitor Account-Level Margin

Beyond individual positions, track your overall account margin health:
import { marginAccountsAbi } from "@nile-markets/sdk";
import { formatUnits } from "viem";

const MARGIN_ACCOUNTS = "0x..."; // See Contract Addresses page
const trader = "0xYOUR_ADDRESS";

const [collateral, locked, available] = await Promise.all([
  client.readContract({
    address: MARGIN_ACCOUNTS,
    abi: marginAccountsAbi,
    functionName: "collateralBalance",
    args: [trader],
  }),
  client.readContract({
    address: MARGIN_ACCOUNTS,
    abi: marginAccountsAbi,
    functionName: "imLockedTotal",
    args: [trader],
  }),
  client.readContract({
    address: MARGIN_ACCOUNTS,
    abi: marginAccountsAbi,
    functionName: "availableBalance",
    args: [trader],
  }),
]);

console.log(`Total collateral: ${formatUnits(collateral, 6)} USDC`);
console.log(`Locked across positions: ${formatUnits(locked, 6)} USDC`);
console.log(`Available for new trades: ${formatUnits(available, 6)} USDC`);

Closed Position History

Query the subgraph for settled, liquidated, or early-terminated positions:
{
  positions(
    where: {
      account: "0xYOUR_ADDRESS",
      status: "CLOSED"
    }
    orderBy: closeTimestamp
    orderDirection: desc
    first: 25
  ) {
    id
    side
    notional
    entryStrike
    closePrice
    closeReason
    realizedPnl
    closeTimestamp
  }
}
The closeReason field tells you how the position was closed:
ValueMeaning
MATUREDSettled at the fixing price after reaching maturity
LIQUIDATEDEquity fell below maintenance margin
EARLY_TERMINATIONTrader closed early at the current forward price

PnL Calculation

Formula and mechanics for how profit and loss is computed.

Liquidation

Maintenance margin thresholds, liquidation process, and penalty fees.

Margin Requirements

Initial and maintenance margin factors by tenor.

Subgraph Queries

Full reference for available GraphQL queries and entity schemas.