Overview

Since the Provider API gives you access to the same markets on different exchanges through a single interface, you can detect price discrepancies and execute hedged trades. This recipe shows a basic cross-provider arbitrage strategy.
Arbitrage carries execution risk. Prices can move between detection and execution. Slippage, fees, and network latency must be factored in. Always start on testnet.

How It Works

  1. Stream prices from both Hyperliquid and Aster for the same market
  2. When the price difference exceeds a threshold (accounting for fees), execute:
    • Buy on the cheaper exchange
    • Sell on the more expensive exchange
  3. You now hold a hedged position with locked-in profit

Step 1: Stream Prices from Both Providers

const API_KEY = "ps_live_abc123def456";
const SYMBOL = "BTC";
const MIN_SPREAD_BPS = 10; // Minimum 10bps spread to trade

const prices: Record<string, number> = {};

const ws = new WebSocket(
  `wss://api.perps.studio/ws/v1/perps?apiKey=${API_KEY}`
);

ws.onopen = () => {
  // Subscribe to ticker on both providers
  for (const provider of ["hyperliquid", "aster"]) {
    ws.send(JSON.stringify({
      event: "subscribe",
      data: {
        provider,
        channel: "ticker",
        params: { symbol: SYMBOL },
      },
    }));
  }
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.channel !== "ticker") return;

  prices[msg.provider] = parseFloat(msg.data.markPrice);

  // Check for arb opportunity when we have both prices
  if (prices.hyperliquid && prices.aster) {
    checkArbOpportunity();
  }
};

Step 2: Detect Opportunities

function checkArbOpportunity() {
  const hlPrice = prices.hyperliquid;
  const asterPrice = prices.aster;

  const spreadBps = Math.abs(hlPrice - asterPrice) / Math.min(hlPrice, asterPrice) * 10000;

  if (spreadBps < MIN_SPREAD_BPS) return;

  const buyProvider = hlPrice < asterPrice ? "hyperliquid" : "aster";
  const sellProvider = hlPrice < asterPrice ? "aster" : "hyperliquid";

  console.log(
    `ARB DETECTED: ${SYMBOL} | ` +
    `Buy on ${buyProvider} @ ${prices[buyProvider]} | ` +
    `Sell on ${sellProvider} @ ${prices[sellProvider]} | ` +
    `Spread: ${spreadBps.toFixed(1)}bps`
  );

  executeArb(buyProvider, sellProvider);
}

Step 3: Execute Hedged Trade

const BASE = "https://api.perps.studio/v1";
const ARB_SIZE = "0.01"; // BTC

async function executeArb(buyProvider: string, sellProvider: string) {
  // Execute both legs simultaneously
  const [buyResult, sellResult] = await Promise.allSettled([
    fetch(`${BASE}/perps/${buyProvider}/orders`, {
      method: "POST",
      headers: {
        "X-API-Key": API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        symbol: SYMBOL,
        side: "buy",
        type: "market",
        size: ARB_SIZE,
        wallet: WALLET,
        signature: await signOrder(buyProvider, "buy"),
        nonce: Date.now(),
      }),
    }).then((r) => r.json()),

    fetch(`${BASE}/perps/${sellProvider}/orders`, {
      method: "POST",
      headers: {
        "X-API-Key": API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        symbol: SYMBOL,
        side: "sell",
        type: "market",
        size: ARB_SIZE,
        wallet: WALLET,
        signature: await signOrder(sellProvider, "sell"),
        nonce: Date.now(),
      }),
    }).then((r) => r.json()),
  ]);

  console.log("Buy result:", buyResult);
  console.log("Sell result:", sellResult);

  // Check if both legs executed
  if (buyResult.status === "rejected" || sellResult.status === "rejected") {
    console.error("One leg failed! Manual intervention needed.");
    // In production: auto-unwind the successful leg
  }
}

Step 4: Risk Management

// Track open arb positions
let openArbPositions = 0;
const MAX_ARB_POSITIONS = 3;

function checkArbOpportunity() {
  if (openArbPositions >= MAX_ARB_POSITIONS) {
    console.log("Max arb positions reached, skipping");
    return;
  }

  // ... detection logic ...

  // Account for fees
  const hlFee = 0.00035; // taker fee
  const asterFee = 0.0005;
  const totalFeeBps = (hlFee + asterFee) * 10000;

  if (spreadBps < totalFeeBps + MIN_SPREAD_BPS) {
    return; // Not profitable after fees
  }

  executeArb(buyProvider, sellProvider);
  openArbPositions++;
}

Closing Arb Positions

Once the price converges, close both positions:
async function closeArbPosition(provider: string, side: string) {
  const closeSide = side === "buy" ? "sell" : "buy";

  await fetch(`${BASE}/perps/${provider}/orders`, {
    method: "POST",
    headers: {
      "X-API-Key": API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      symbol: SYMBOL,
      side: closeSide,
      type: "market",
      size: ARB_SIZE,
      reduceOnly: true,
      wallet: WALLET,
      signature: await signOrder(provider, closeSide),
      nonce: Date.now(),
    }),
  });
}

Key Considerations

Market orders can slip. Use the orderbook depth endpoint to estimate slippage before executing. For large sizes, consider limit orders with tight timeouts.
If you hold positions across providers for extended periods, funding rate differences will affect your PnL. Monitor funding rates and factor them into your profitability calculations.
If one leg of the arb fails (e.g., rejected order), you have an unhedged position. Implement automatic unwinding of the successful leg if the other fails.
You need margin on both exchanges. Consider using the spot transfer endpoint to move capital between spot and perp wallets as needed.