Rate Limits & Resilience: Design Trading Bots That Don’t Break

Rate Limits & Resilience: Design Trading Bots That Don’t Break

Most bots don’t fail because of strategy — they fail because of plumbing

In real markets, the difference between a stable system and a broken one is rarely “smarter signals.” It’s things like retries, timeouts, caching, and rate-limit awareness. Mila-ex makes the data consistent; your job is to make your client resilient.

Know the endpoints (and the expensive ones)

These are the most common calls in a market-data pipeline:

  • /api/v1/exchange (discover exchanges \+ capabilities)
  • /api/v1/exchange/markets (market catalog per exchange)
  • /api/v1/exchange/ticker and /api/v1/exchange/tickers (price snapshots)
  • /api/v1/exchange/ohlcv (candles; typically heavily rate-limited)
  • /api/v1/exchange/orderbook and /api/v1/exchange/orderbook/complete (depth \+ prints)

Golden rule: cache what doesn’t change quickly

  • Exchanges list: cache for hours
  • Markets list: cache for minutes (or longer) and refresh in the background
  • Tickers: cache for seconds (depending on your use case)
  • OHLCV: cache aggressively; candles are not tick-by-tick

Error handling: treat errors as data

Mila-ex may return a structured error payload (with Code, Message, and detailed Errors). When your bot sees errors, it should decide whether to retry, switch exchanges, or degrade gracefully.

Retry strategy (simple and effective)

  • Retry only on transient errors: timeouts, 5xx, network failures
  • Backoff: exponential \+ jitter
  • Stop conditions: max attempts; circuit breaker when an exchange is unhealthy

TypeScript: resilient fetch wrapper

async function sleep(ms: number) {
  return new Promise(resolve =\> setTimeout(resolve, ms));
}
async function fetchJsonWithRetry(url: string, apiKey: string, attempts = 4) {
  let lastErr: unknown;
  for (let i = 0; i \< attempts; i++) {
    try {
      const res = await fetch(url, {
        headers: { "x-api-key": apiKey },
      });
      if (\!res.ok) {
        const text = await res.text();
        // Non-2xx could be supported/unsupported operations or other errors.
        // Decide your policy: retry on 5xx, otherwise fail fast.
        if (res.status \>= 500) throw new Error(text);
        throw new Error(text);
      }
      return await res.json();
    } catch (e) {
      lastErr = e;
      const backoff = Math.min(2000, 250 * Math.pow(2, i));
      const jitter = Math.floor(Math.random() * 120);
      await sleep(backoff \+ jitter);
    }
  }
  throw lastErr;
}

Design pattern: capability-aware routing

Use GET /api/v1/exchange to learn whether an exchange supports the operation you want (tickers, order books, candles). Then route traffic accordingly. This avoids wasted calls and improves uptime.

Related reading

Reference

Endpoint definitions and models are documented in the Mila-ex API Docs.