Guides · Recipes

Recipes

Copy-paste patterns for the things agents actually do — wired with the SDKs. The same flows are available as MCP tools. Pick a language once; every tab on the site follows.

Stream live prices into an agent

Streams are plain SSE — an async generator in TypeScript, an iterator in Python. Collect an hour of 1-minute closes, then hand them to your model's decision step.

import { MultiClient } from "@multidex/sdk";
const multi = new MultiClient();

const ac = new AbortController();
const bars: number[] = [];
for await (const ev of multi.streams.candles("BTC", "1m", { signal: ac.signal })) {
  bars.push(ev.data.close);
  if (bars.length >= 60) ac.abort();      // 1h of context, then act
}
// → feed `bars` into your model's decision step

Place a smart-routed perp trade

Ask the router where the order should go, then place it. Always pass an idempotency key so a retried request can never double-submit.

const multi = new MultiClient({ apiKey: process.env.MULTI_API_KEY });

const routing = await multi.routing.getRecommendation("BTC", { side: "BUY" });
console.log("best venue:", routing.recommended, "@", routing.price);

const order = await multi.orders.place(
  { symbol: "BTC", side: "BUY", type: "MARKET", quantity: "0.01" },
  { idempotencyKey: crypto.randomUUID() },   // safe to retry
);

Open with a TP/SL bracket

Pass takeProfitPrice / stopLossPrice to openPosition and multi places the bracket for you after the entry fills: a reduce-only LIMIT for the take-profit and a reduce-only STOP_MARKET for the stop-loss.

const result = await multi.orders.openPosition(
  {
    symbol: "ETH", direction: "LONG", size: "0.5",
    leverage: 3, orderType: "MARKET",
    takeProfitPrice: 5200,   // reduce-only LIMIT, placed after the entry fills
    stopLossPrice: 4100,     // reduce-only STOP_MARKET
  },
  { idempotencyKey: crypto.randomUUID() },
);

// Brackets are best-effort: a failed leg never rolls back the entry.
const bracketErrors = result.bracket?.errors ?? {};
if (Object.keys(bracketErrors).length) {
  console.warn("bracket leg failed:", bracketErrors);
  // entry is open — place the missing leg yourself (reduce-only) or close
}
Bracket legs are reduce-only (risk-reducing), so they consume no extra daily notional. Always check bracket.errors in the response — the entry stands even if a bracket leg was rejected.

Open a long/short pair trade

One position: long ETH, short BTC, balanced by USD notional, executed as two perp legs.

const pair = await multi.pairs.open({
  longSymbol: "ETH",
  shortSymbol: "BTC",
  notionalUsd: 1000,
  leverage: 3,
  venue: "hyperliquid",
});
// ...later
await multi.pairs.close(pair.id!);

TWAP out of a large position

Exiting a large position in one market order moves the market against you. A server-run TWAP slices it into timed market orders — here, 0.5 BTC over ~50 minutes in 10 slices. reduceOnly guarantees the strategy can only shrink the position, never flip it.

const twap = await multi.strategies.create(
  {
    kind: "twap", symbol: "BTC", side: "SELL",
    totalQuantity: "0.5",
    sliceCount: 10,            // 2–50 slices
    intervalMs: 300_000,       // one slice every 5 minutes
    reduceOnly: true,          // exit-only
  },
  { idempotencyKey: crypto.randomUUID() },
);

// Poll progress — or list everything still running
const live = await multi.strategies.get(twap.id);
console.log(`${live.slicesDone}/${live.sliceCount} slices, filled ${live.filledQuantity}`);
const active = await multi.strategies.list({ status: "active" });

// Changed your mind? Stops all future slices.
await multi.strategies.cancel(twap.id);
The full strategy notional is policy-checked and reserved up-front, so a strategy can never sneak past maxOrderUsd or dailyNotionalUsd by slicing. Prefer limit prices? Use kind: "scaled" with priceLow / priceHigh to ladder resting orders across a band.

Cross-chain swap — non-custodial and custodial

Non-custodial (you sign): quote → prepare → sign the returned transaction with your own wallet.

const quote = await multi.swaps.quote({
  fromChain: 42161, toChain: 8453,
  fromToken: "0x0000000000000000000000000000000000000000",   // ETH on Arbitrum
  toToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",     // USDC on Base
  fromAmount: "5000000000000000",
});
const prepared = await multi.swaps.prepare({ quoteId: quote.bestQuote?.quoteId });
// → sign prepared.transactionRequest with viem / @solana/web3.js and broadcast

Headless (delegated wallet, scope swap:execute):

const result = await multi.swaps.execute({ quoteId: quote.bestQuote?.quoteId });
const status = await multi.swaps.getStatus(result.swapId!);

A complete trading-agent loop

Read in parallel, decide, execute with an idempotency key, and back off on rate limits — policy violations surface as typed errors you can log and move past.

import { MultiClient, MultiRateLimitError } from "@multidex/sdk";
const multi = new MultiClient({ apiKey: process.env.MULTI_API_KEY });

async function tick(symbol: string) {
  const [book, routing] = await Promise.all([
    multi.markets.getOrderbook(symbol),
    multi.routing.getRecommendation(symbol, { side: "BUY" }),
  ]);

  const signal = decide(book, routing);     // your strategy
  if (signal === "buy") {
    try {
      await multi.orders.place(
        { symbol, side: "BUY", type: "MARKET", quantity: "0.01" },
        { idempotencyKey: `${symbol}-${Date.now()}` },
      );
    } catch (e) {
      if (e instanceof MultiRateLimitError) await sleep(e.retryAfterMs ?? 1000);
      else throw e;   // policy violations etc. surface as typed errors
    }
  }
}

See the safety model for guardrails on autonomous loops.