Skip to main content

Overview

Access comprehensive market data through the Indexer Client. This guide covers querying markets, orderbooks, funding rates, and trading statistics.

Markets

List all perpetual markets and their properties

Orderbooks

View live bids and asks for any market

Statistics

Access volume, price changes, and funding rates

Querying Markets

Get All Perpetual Markets

import { CompositeClient } from "@oraichain/lfg-client-js";

async function getAllMarkets(client: CompositeClient) {
  const response = await client.indexerClient.markets.getPerpetualMarkets();

  console.log("Available Markets:");

  for (const [marketId, market] of Object.entries(response.markets)) {
    console.log(`\n${market.ticker} (${marketId}):`);
    console.log(`  Status: ${market.status}`);
    console.log(`  Price: $${market.oraclePrice}`);
    console.log(`  24h Change: ${market.priceChange24H}%`);
    console.log(`  24h Volume: $${market.volume24H}`);
    console.log(`  Open Interest: $${market.openInterest}`);
    console.log(`  Next Funding Rate: ${market.nextFundingRate}%`);
  }

  return response.markets;
}

Market Data Structure

clobPairId
string
Unique identifier for the CLOB (Central Limit Order Book) pair
ticker
string
Market ticker symbol (e.g., “ETH-USD”, “BTC-USD”)
status
string
Market status: "ACTIVE", "PAUSED", "CANCEL_ONLY", or "POST_ONLY"
oraclePrice
string
Current oracle price in quote currency (USD)
priceChange24H
string
24-hour price change percentage
volume24H
string
24-hour trading volume in USD
trades24H
number
Number of trades in the last 24 hours
nextFundingRate
string
Next funding rate as a percentage
initialMarginFraction
string
Initial margin requirement (e.g., “0.05” = 5% = 20x leverage)
maintenanceMarginFraction
string
Maintenance margin requirement (e.g., “0.03” = 3%)
openInterest
string
Total open interest in USD

Get Specific Market

async function getMarket(client: CompositeClient, marketId: string) {
  const markets = await client.indexerClient.markets.getPerpetualMarkets();
  const market = markets.markets[marketId];

  if (!market) {
    throw new Error(`Market ${marketId} not found`);
  }

  return market;
}

// Get ETH-USD market
const ethMarket = await getMarket(client, "ETH-USD");
console.log(`ETH Price: $${ethMarket.oraclePrice}`);

Filter Active Markets

function getActiveMarkets(markets: any) {
  return Object.entries(markets)
    .filter(([_, market]: [string, any]) => market.status === "ACTIVE")
    .map(([marketId, market]) => ({ marketId, ...market }));
}

const markets = await client.indexerClient.markets.getPerpetualMarkets();
const activeMarkets = getActiveMarkets(markets.markets);

console.log(`Found ${activeMarkets.length} active markets`);

Market Statistics

Get Market Rankings

async function getMarketRankings(client: CompositeClient) {
  const response = await client.indexerClient.markets.getPerpetualMarkets();
  const markets = Object.entries(response.markets).map(
    ([id, data]: [string, any]) => ({
      marketId: id,
      ticker: data.ticker,
      volume24H: parseFloat(data.volume24H),
      trades24H: data.trades24H,
      priceChange24H: parseFloat(data.priceChange24H),
      openInterest: parseFloat(data.openInterest),
    })
  );

  // Sort by 24h volume
  const byVolume = [...markets].sort((a, b) => b.volume24H - a.volume24H);

  console.log("Top 10 Markets by Volume:");
  byVolume.slice(0, 10).forEach((market, index) => {
    console.log(
      `${index + 1}. ${market.ticker}: $${market.volume24H.toFixed(0)}`
    );
  });

  // Sort by price change
  const byPriceChange = [...markets].sort(
    (a, b) => b.priceChange24H - a.priceChange24H
  );

  console.log("\nTop Gainers:");
  byPriceChange.slice(0, 5).forEach((market) => {
    console.log(`${market.ticker}: +${market.priceChange24H.toFixed(2)}%`);
  });

  console.log("\nTop Losers:");
  byPriceChange
    .slice(-5)
    .reverse()
    .forEach((market) => {
      console.log(`${market.ticker}: ${market.priceChange24H.toFixed(2)}%`);
    });

  return { byVolume, byPriceChange };
}

Calculate Maximum Leverage

function calculateMaxLeverage(market: any): number {
  const initialMarginFraction = parseFloat(market.initialMarginFraction);
  return 1 / initialMarginFraction;
}

const markets = await client.indexerClient.markets.getPerpetualMarkets();
const ethMarket = markets.markets["ETH-USD"];

const maxLeverage = calculateMaxLeverage(ethMarket);
console.log(`ETH-USD Max Leverage: ${maxLeverage}x`);

Orderbook Data

Get Market Orderbook

async function getOrderbook(client: CompositeClient, marketId: string) {
  const orderbook =
    await client.indexerClient.markets.getPerpetualMarketOrderbook(marketId);

  console.log(`${marketId} Orderbook:`);

  // Display top 10 asks (sell orders)
  console.log("\nAsks (Sell Orders):");
  orderbook.asks.slice(0, 10).forEach((ask: any) => {
    console.log(
      `  $${parseFloat(ask.price).toFixed(2)} - ${parseFloat(ask.size).toFixed(
        4
      )}`
    );
  });

  // Display top 10 bids (buy orders)
  console.log("\nBids (Buy Orders):");
  orderbook.bids.slice(0, 10).forEach((bid: any) => {
    console.log(
      `  $${parseFloat(bid.price).toFixed(2)} - ${parseFloat(bid.size).toFixed(
        4
      )}`
    );
  });

  return orderbook;
}

await getOrderbook(client, "ETH-USD");

Orderbook Structure

interface OrderbookLevel {
  price: string; // Price level
  size: string; // Total size at this price
}

interface Orderbook {
  bids: OrderbookLevel[]; // Buy orders (descending by price)
  asks: OrderbookLevel[]; // Sell orders (ascending by price)
}

Calculate Spread

function calculateSpread(orderbook: any): number {
  if (orderbook.bids.length === 0 || orderbook.asks.length === 0) {
    return 0;
  }

  const bestBid = parseFloat(orderbook.bids[0].price);
  const bestAsk = parseFloat(orderbook.asks[0].price);

  const spread = bestAsk - bestBid;
  const spreadPercent = (spread / bestBid) * 100;

  console.log(`Best Bid: $${bestBid.toFixed(2)}`);
  console.log(`Best Ask: $${bestAsk.toFixed(2)}`);
  console.log(`Spread: $${spread.toFixed(2)} (${spreadPercent.toFixed(3)}%)`);

  return spread;
}

const orderbook =
  await client.indexerClient.markets.getPerpetualMarketOrderbook("ETH-USD");
calculateSpread(orderbook);

Calculate Market Depth

function calculateMarketDepth(orderbook: any, depthPercent: number = 1): any {
  const bestBid = parseFloat(orderbook.bids[0]?.price || 0);
  const bestAsk = parseFloat(orderbook.asks[0]?.price || 0);

  // Calculate depth for bids
  let bidDepth = 0;
  const bidPriceFloor = bestBid * (1 - depthPercent / 100);

  for (const bid of orderbook.bids) {
    const price = parseFloat(bid.price);
    if (price < bidPriceFloor) break;
    bidDepth += parseFloat(bid.size) * price;
  }

  // Calculate depth for asks
  let askDepth = 0;
  const askPriceCeil = bestAsk * (1 + depthPercent / 100);

  for (const ask of orderbook.asks) {
    const price = parseFloat(ask.price);
    if (price > askPriceCeil) break;
    askDepth += parseFloat(ask.size) * price;
  }

  console.log(`Market Depth (${depthPercent}%):`);
  console.log(`  Bid Depth: $${bidDepth.toFixed(2)}`);
  console.log(`  Ask Depth: $${askDepth.toFixed(2)}`);
  console.log(`  Total Depth: $${(bidDepth + askDepth).toFixed(2)}`);

  return { bidDepth, askDepth, totalDepth: bidDepth + askDepth };
}

const depth = calculateMarketDepth(orderbook, 1); // 1% depth

Funding Rates

Understanding Funding Rates

Funding rates are periodic payments between longs and shorts to keep perpetual prices close to spot:
  • Positive funding rate: Longs pay shorts (perp price > spot)
  • Negative funding rate: Shorts pay longs (perp price < spot)
function analyzeFundingRate(market: any) {
  const fundingRate = parseFloat(market.nextFundingRate);
  const fundingRatePercent = fundingRate * 100;

  console.log(
    `${market.ticker} Funding Rate: ${fundingRatePercent.toFixed(4)}%`
  );

  if (fundingRate > 0) {
    console.log("→ Longs pay shorts (bullish sentiment)");
    console.log(`  Annualized: ${(fundingRate * 365 * 3 * 100).toFixed(2)}%`); // 3 times per day
  } else if (fundingRate < 0) {
    console.log("→ Shorts pay longs (bearish sentiment)");
    console.log(`  Annualized: ${(fundingRate * 365 * 3 * 100).toFixed(2)}%`);
  } else {
    console.log("→ No funding payment (balanced)");
  }

  // Calculate funding cost for a position
  const positionSize = 10000; // $10k position
  const fundingCost = positionSize * fundingRate;
  console.log(`Funding cost for $10k position: $${fundingCost.toFixed(2)}`);
}

const markets = await client.indexerClient.markets.getPerpetualMarkets();
analyzeFundingRate(markets.markets["ETH-USD"]);

Find Best Funding Opportunities

async function findFundingOpportunities(client: CompositeClient) {
  const response = await client.indexerClient.markets.getPerpetualMarkets();

  const fundingRates = Object.entries(response.markets).map(
    ([id, market]: [string, any]) => ({
      marketId: id,
      ticker: market.ticker,
      fundingRate: parseFloat(market.nextFundingRate),
      volume24H: parseFloat(market.volume24H),
    })
  );

  // Sort by funding rate
  fundingRates.sort((a, b) => b.fundingRate - a.fundingRate);

  console.log("Highest Positive Funding (Longs pay shorts):");
  fundingRates.slice(0, 5).forEach((market) => {
    const annualized = market.fundingRate * 365 * 3 * 100; // 3x daily
    console.log(
      `  ${market.ticker}: ${(market.fundingRate * 100).toFixed(
        4
      )}% (${annualized.toFixed(2)}% APR)`
    );
  });

  console.log("\nHighest Negative Funding (Shorts pay longs):");
  fundingRates
    .slice(-5)
    .reverse()
    .forEach((market) => {
      const annualized = market.fundingRate * 365 * 3 * 100;
      console.log(
        `  ${market.ticker}: ${(market.fundingRate * 100).toFixed(
          4
        )}% (${annualized.toFixed(2)}% APR)`
      );
    });
}

Trading Signals

Price Change Analysis

async function analyzePriceMovements(client: CompositeClient) {
  const response = await client.indexerClient.markets.getPerpetualMarkets();

  const movements = Object.entries(response.markets)
    .map(([id, market]: [string, any]) => ({
      ticker: market.ticker,
      priceChange: parseFloat(market.priceChange24H),
      volume: parseFloat(market.volume24H),
      trades: market.trades24H,
      openInterest: parseFloat(market.openInterest),
    }))
    .filter((m) => m.volume > 100000) // Min $100k volume
    .sort((a, b) => Math.abs(b.priceChange) - Math.abs(a.priceChange));

  console.log("Largest Price Movements (24h):");
  movements.slice(0, 10).forEach((market, index) => {
    const direction = market.priceChange > 0 ? "📈" : "📉";
    console.log(
      `${index + 1}. ${direction} ${market.ticker}: ${
        market.priceChange > 0 ? "+" : ""
      }${market.priceChange.toFixed(2)}%`
    );
    console.log(
      `   Volume: $${market.volume.toFixed(0)}, Trades: ${market.trades}`
    );
  });
}

Volume Analysis

async function analyzeVolumeSpikes(client: CompositeClient) {
  const response = await client.indexerClient.markets.getPerpetualMarkets();

  const volumeData = Object.values(response.markets).map((market: any) => ({
    ticker: market.ticker,
    volume24H: parseFloat(market.volume24H),
    trades24H: market.trades24H,
    avgTradeSize: parseFloat(market.volume24H) / (market.trades24H || 1),
    openInterest: parseFloat(market.openInterest),
    volumeToOI:
      parseFloat(market.volume24H) / (parseFloat(market.openInterest) || 1),
  }));

  // Sort by volume/OI ratio (indicates activity level)
  volumeData.sort((a, b) => b.volumeToOI - a.volumeToOI);

  console.log("Most Active Markets (Volume/OI Ratio):");
  volumeData.slice(0, 10).forEach((market, index) => {
    console.log(`${index + 1}. ${market.ticker}:`);
    console.log(`   Volume: $${market.volume24H.toFixed(0)}`);
    console.log(`   Open Interest: $${market.openInterest.toFixed(0)}`);
    console.log(`   Ratio: ${market.volumeToOI.toFixed(2)}x`);
  });
}

Market Monitoring

Real-time Market Monitor

class MarketMonitor {
  private client: CompositeClient;
  private markets: string[];
  private intervalId?: NodeJS.Timeout;

  constructor(client: CompositeClient, markets: string[]) {
    this.client = client;
    this.markets = markets;
  }

  async checkMarkets() {
    console.log(`\n[${new Date().toISOString()}] Market Update:`);

    const response =
      await this.client.indexerClient.markets.getPerpetualMarkets();

    for (const marketId of this.markets) {
      const market = response.markets[marketId];
      if (!market) continue;

      console.log(`\n${market.ticker}:`);
      console.log(`  Price: $${market.oraclePrice}`);
      console.log(`  24h Change: ${market.priceChange24H}%`);
      console.log(`  Volume: $${market.volume24H}`);
      console.log(`  Funding: ${parseFloat(market.nextFundingRate) * 100}%`);

      // Get orderbook
      const orderbook =
        await this.client.indexerClient.markets.getPerpetualMarketOrderbook(
          marketId
        );

      const bestBid = parseFloat(orderbook.bids[0]?.price || "0");
      const bestAsk = parseFloat(orderbook.asks[0]?.price || "0");
      const spread = ((bestAsk - bestBid) / bestBid) * 100;

      console.log(`  Best Bid: $${bestBid.toFixed(2)}`);
      console.log(`  Best Ask: $${bestAsk.toFixed(2)}`);
      console.log(`  Spread: ${spread.toFixed(3)}%`);
    }
  }

  startMonitoring(intervalSeconds: number = 60) {
    console.log(
      `Starting market monitoring every ${intervalSeconds} seconds...`
    );

    this.intervalId = setInterval(async () => {
      try {
        await this.checkMarkets();
      } catch (error) {
        console.error("Error checking markets:", error);
      }
    }, intervalSeconds * 1000);

    // Initial check
    this.checkMarkets();
  }

  stopMonitoring() {
    if (this.intervalId) {
      clearInterval(this.intervalId);
      console.log("Market monitoring stopped");
    }
  }
}

// Usage
const monitor = new MarketMonitor(client, ["ETH-USD", "BTC-USD", "SOL-USD"]);
monitor.startMonitoring(60); // Update every minute

Market Data Utilities

Price Ticker

class PriceTicker {
  private client: CompositeClient;
  private prices: Map<string, number> = new Map();

  constructor(client: CompositeClient) {
    this.client = client;
  }

  async updatePrices() {
    const response =
      await this.client.indexerClient.markets.getPerpetualMarkets();

    for (const [marketId, market] of Object.entries(response.markets)) {
      this.prices.set(marketId, parseFloat((market as any).oraclePrice));
    }
  }

  getPrice(marketId: string): number | undefined {
    return this.prices.get(marketId);
  }

  getAllPrices(): Map<string, number> {
    return new Map(this.prices);
  }

  async startAutoUpdate(intervalMs: number = 5000) {
    await this.updatePrices(); // Initial update

    setInterval(async () => {
      try {
        await this.updatePrices();
      } catch (error) {
        console.error("Error updating prices:", error);
      }
    }, intervalMs);
  }
}

// Usage
const ticker = new PriceTicker(client);
await ticker.startAutoUpdate(5000); // Update every 5 seconds

// Get specific price
const ethPrice = ticker.getPrice("ETH-USD");
console.log(`ETH: $${ethPrice}`);

Best Practices

Cache market data to reduce API calls:
class MarketDataCache {
  private cache: Map<string, { data: any; timestamp: number }> = new Map();
  private ttl: number = 60000; // 60 seconds
  
  set(key: string, data: any) {
    this.cache.set(key, { data, timestamp: Date.now() });
  }
  
  get(key: string): any | null {
    const entry = this.cache.get(key);
    if (!entry) return null;
    
    if (Date.now() - entry.timestamp > this.ttl) {
      this.cache.delete(key);
      return null;
    }
    
    return entry.data;
  }
}
Check market status before trading:
function canTrade(market: any): boolean {
  return market.status === "ACTIVE";
}

function canPlaceOrders(market: any): boolean {
  return ["ACTIVE", "POST_ONLY"].includes(market.status);
}
Assess liquidity before placing large orders:
function hasEnoughLiquidity(
  orderbook: any,
  side: "BUY" | "SELL",
  size: number,
  priceSlippage: number = 0.01 // 1%
): boolean {
  const levels = side === "BUY" ? orderbook.asks : orderbook.bids;
  const bestPrice = parseFloat(levels[0]?.price || 0);
  const maxPrice = side === "BUY" 
    ? bestPrice * (1 + priceSlippage)
    : bestPrice * (1 - priceSlippage);
  
  let availableSize = 0;
  for (const level of levels) {
    const price = parseFloat(level.price);
    if ((side === "BUY" && price > maxPrice) || 
        (side === "SELL" && price < maxPrice)) {
      break;
    }
    availableSize += parseFloat(level.size);
    if (availableSize >= size) return true;
  }
  
  return false;
}

Next Steps