Skip to main content
gTrade does not expose a public REST endpoint that returns the virtual order book (VOB) as a ready-made ladder. The VOB shown in the trading interface is derived client-side from public data:
  • trading variables from the chain-specific backend
  • the current price for the pair
  • SDK price-impact helpers
The VOB is synthetic. gTrade does not match orders through a resting order book; it uses shared vault liquidity and deterministic price-impact rules. The VOB is a visualization of available execution liquidity at different price-impact levels.

Public inputs

Use the chain-specific trading backend for pair, fee, depth-band, OI, skew, and collateral state:
curl "https://backend-arbitrum.gains.trade/trading-variables/all"
For a smaller response, request only the fields needed for VOB construction:
curl "https://backend-arbitrum.gains.trade/trading-variables/pairInfos,depthBandsMapping,pairs,fees,collaterals,oiWindows,oiWindowsSettings"
The depth-band fields are:
  • pairInfos.pairDepthBands - per-pair depth-band slots
  • depthBandsMapping - global mapping for the 30 depth-band offsets
Use the pricing backend for the current pair price:
curl "https://backend-pricing.eu.gains.trade/charts"
For a continuously updated VOB, fetch the initial /charts snapshot and then keep prices fresh with the price stream documented in Live prices and OHLC snapshots.

High-level flow

  1. Fetch trading variables from backend-<network>.gains.trade.
  2. Convert the raw backend response with transformGlobalTradingVariables.
  3. Fetch current prices from /charts or maintain them from the pricing WebSocket.
  4. Build the skew-adjusted market price with buildMarketPriceContext and getCurrentMarketPrice.
  5. Probe opening price impact for a set of target impacts and position sizes.
  6. Convert the probes into ask and bid levels.

Example

This example mirrors the approach used by the gTrade frontend: it creates a position-size ladder for one pair and one collateral.
import {
  buildMarketPriceContext,
  buildTradeOpeningPriceImpactContext,
  ContractsVersion,
  getCurrentMarketPrice,
  getFixedSpreadP,
  getTradeOpeningPriceImpact,
  transformGlobalTradingVariables,
} from "@gainsnetwork/sdk";

type VobLevel = {
  price: number;
  sizeUsd: number;
  cumulativeUsd: number;
  impactPct: number;
};

type VirtualOrderBook = {
  marketPrice: number;
  asks: VobLevel[];
  bids: VobLevel[];
};

const TARGET_IMPACTS_PCT = [
  0.0005, 0.001, 0.0025, 0.005, 0.01, 0.02, 0.03, 0.05, 0.075, 0.1,
  0.15, 0.2, 0.3, 0.4, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0,
];

const TARGET_SIZES_USD = [
  1_000, 5_000, 10_000, 25_000, 50_000, 75_000, 100_000, 150_000,
  250_000, 500_000, 750_000, 1_000_000, 2_500_000, 5_000_000, 10_000_000,
];

const MIN_LEVEL_SIZE_USD = 2_000;
const MAX_LEVELS = 15;
const MAX_IMPACT_PCT = 2;
const IMPACT_SIMILARITY_THRESHOLD = 0.0001;

async function buildVirtualOrderBook(
  chainBackendUrl: string,
  pairIndex: number,
  collateralIndex: number
): Promise<VirtualOrderBook | null> {
  const [rawTradingVariables, charts] = await Promise.all([
    fetch(`${chainBackendUrl}/trading-variables/all`).then((res) => res.json()),
    fetch("https://backend-pricing.eu.gains.trade/charts").then((res) => res.json()),
  ]);

  const { globalTradingVariables, blockNumber } =
    transformGlobalTradingVariables(rawTradingVariables);

  const oraclePrice = charts.closes[pairIndex];
  const pair = globalTradingVariables.pairs?.[pairIndex];
  const collateral = globalTradingVariables.collaterals?.[collateralIndex - 1];
  const fee = pair ? globalTradingVariables.fees?.[pair.feeIndex] : undefined;

  if (!oraclePrice || !pair || !fee || !collateral) {
    return null;
  }

  const marketPriceContext = buildMarketPriceContext(collateral, pairIndex);
  const currentMarketPrice = getCurrentMarketPrice(
    pairIndex,
    oraclePrice,
    marketPriceContext
  );
  const marketPrice =
    marketPriceContext.skewDepth > 0 ? currentMarketPrice.marketPrice : oraclePrice;

  const pairDepthBands = globalTradingVariables.pairDepthBands?.[pairIndex];
  const hasDepth =
    (pairDepthBands?.above?.totalDepthUsd ?? 0) > 0 ||
    (pairDepthBands?.below?.totalDepthUsd ?? 0) > 0;
  const hasSkew = (collateral.pairSkewDepths?.[pairIndex] ?? 0) > 0;

  if (!hasDepth && !hasSkew) {
    const halfSpreadPct = getFixedSpreadP(pair.spreadP, true, true);

    return {
      marketPrice,
      asks: [
        {
          price: marketPrice * (1 + halfSpreadPct / 100),
          sizeUsd: 0,
          cumulativeUsd: 0,
          impactPct: halfSpreadPct,
        },
      ],
      bids: [
        {
          price: marketPrice * (1 - halfSpreadPct / 100),
          sizeUsd: 0,
          cumulativeUsd: 0,
          impactPct: -halfSpreadPct,
        },
      ],
    };
  }

  const impactContext = buildTradeOpeningPriceImpactContext(
    globalTradingVariables,
    collateralIndex,
    pairIndex,
    {
      currentBlock: blockNumber ?? 0,
      contractsVersion: ContractsVersion.V10,
      protectionCloseFactorWhitelist: false,
    }
  );

  if (!impactContext) {
    return null;
  }

  const impactForSizeUsd = (sizeUsd: number, long: boolean): number | null => {
    try {
      const result = getTradeOpeningPriceImpact(
        {
          collateralIndex,
          pairIndex,
          long,
          collateralAmount: sizeUsd / impactContext.collateralPriceUsd,
          leverage: 1,
          openPrice: oraclePrice,
          pairSpreadP: pair.spreadP,
          fee,
          contractsVersion: ContractsVersion.V10,
          isCounterTrade: false,
        },
        impactContext
      );

      return result.totalPriceImpactPFromMarketPrice;
    } catch {
      return null;
    }
  };

  const sizeForImpactUsd = (targetImpactPct: number, long: boolean): number => {
    let minSizeUsd = 100;
    let maxSizeUsd = 50_000_000;
    const tolerance = Math.max(targetImpactPct * 0.1, 0.0001);

    for (let i = 0; i < 20 && maxSizeUsd - minSizeUsd > 100; i++) {
      const midSizeUsd = (minSizeUsd + maxSizeUsd) / 2;
      const impactPct = impactForSizeUsd(midSizeUsd, long);

      if (impactPct === null) {
        maxSizeUsd = midSizeUsd;
        continue;
      }

      const absImpactPct = Math.abs(impactPct);
      if (Math.abs(absImpactPct - targetImpactPct) < tolerance) {
        return midSizeUsd;
      }

      if (absImpactPct < targetImpactPct) {
        minSizeUsd = midSizeUsd;
      } else {
        maxSizeUsd = midSizeUsd;
      }
    }

    return (minSizeUsd + maxSizeUsd) / 2;
  };

  const buildSide = (long: boolean): VobLevel[] => {
    const bands: Array<{ cumulativeUsd: number; impactPct: number }> = [];

    const addBand = (cumulativeUsd: number) => {
      if (bands.length >= MAX_LEVELS || cumulativeUsd < MIN_LEVEL_SIZE_USD) {
        return;
      }

      const impactPct = impactForSizeUsd(cumulativeUsd, long);
      if (impactPct === null || Math.abs(impactPct) > MAX_IMPACT_PCT) {
        return;
      }

      if (
        bands.some(
          (band) => Math.abs(band.impactPct - impactPct) < IMPACT_SIMILARITY_THRESHOLD
        )
      ) {
        return;
      }

      bands.push({ cumulativeUsd, impactPct });
    };

    for (const targetImpactPct of TARGET_IMPACTS_PCT) {
      addBand(sizeForImpactUsd(targetImpactPct, long));
    }

    for (const targetSizeUsd of TARGET_SIZES_USD) {
      addBand(targetSizeUsd);
    }

    return bands
      .sort((a, b) => a.cumulativeUsd - b.cumulativeUsd)
      .slice(0, MAX_LEVELS)
      .map((band, index, sortedBands) => ({
        price: marketPrice * (1 + band.impactPct / 100),
        sizeUsd:
          band.cumulativeUsd - (index > 0 ? sortedBands[index - 1].cumulativeUsd : 0),
        cumulativeUsd: band.cumulativeUsd,
        impactPct: band.impactPct,
      }));
  };

  return {
    marketPrice,
    asks: buildSide(true),
    bids: buildSide(false),
  };
}

const btcUsdcVob = await buildVirtualOrderBook(
  "https://backend-arbitrum.gains.trade",
  0, // BTC/USD
  3  // USDC collateral on Arbitrum; verify the index from collaterals for your chain
);

console.log(btcUsdcVob);

Price selection

The frontend VOB uses the current pair price from the pricing feed and then applies skew-market-price logic through the SDK. For live integrations:
  • use /charts for the initial snapshot
  • subscribe to the price WebSocket for updates
  • rebuild or throttle the VOB when the selected pair price changes
See Mark + Index prices introduction if your integration separates mark and index prices.

Fixed-spread pairs

Some pairs do not have depth bands or skew depth. For those markets, the VOB has no size-dependent ladder. Show the fixed long and short execution prices from getFixedSpreadP instead.

Operational notes

  • No authentication is required for public read endpoints, but rate limits apply.
  • Prefer GET /trading-variables/all when you need currentBlock. Use keyed trading variables when you already have a current block from another source.
  • collateralIndex is 1-based in SDK helpers; array access is collaterals[collateralIndex - 1].
  • The displayed ask side corresponds to opening longs. The displayed bid side corresponds to opening shorts.
  • Pair availability and collateral availability vary by chain. Always derive pair and collateral metadata from the same chain backend you are using for the VOB.