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 stepPlace 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.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);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 broadcastHeadless (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.