> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gains.trade/llms.txt
> Use this file to discover all available pages before exploring further.

# Building the virtual order book

> How to derive the gTrade virtual order book from public trading variables, live prices, and SDK price-impact helpers.

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

<Info>
  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.
</Info>

## Public inputs

Use the chain-specific trading backend for pair, fee, depth-band, OI, skew, and collateral state:

```bash theme={null}
curl "https://backend-arbitrum.gains.trade/trading-variables/all"
```

For a smaller response, request only the fields needed for VOB construction:

```bash theme={null}
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:

```bash theme={null}
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](/developer/integrators/price-feed).

## 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.

```typescript theme={null}
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](/developer/integrators/guides/mark-+-index-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.
