Skip to main content

Running a bot in production

A bot that works for an hour on demo can still fail at 3 a.m. on live. This page lists what goes wrong in practice and how to handle it.

Checklist​

Authentication & sessions​

  • Use an API key, never your password. Keep the secret in an environment variable or secret manager.
  • Always pass remember_me=true. Without it, a session with no activity for 5 minutes is logged out.
  • Log in once and refresh. Every login uses one of your account's 5 sessions (or 1, if your broker disabled multi-session). Going over kills the oldest session — possibly your own terminal, or another bot.
  • Store the newest refresh token every time. Refresh tokens rotate, so an old one is rejected.
  • On 401, refresh once and retry. If the refresh fails, log in with the API key again.
  • Call logout on clean shutdown so the session slot is freed.
  • One bot, one API key. Replacing a key stops new logins with the old one. Sessions that are already open keep running until they end.

WebSocket​

  • Connect with both the bearer token and ?session_id=.
  • Let your client answer pings — the server pings every 5 seconds and drops you after about 10 seconds without a pong. Most libraries do this automatically. Don't block the read loop with slow work.
  • Send start_market_feed once per connection. Sending it twice delivers every tick twice.
  • Handle binary frames (price ticks, CSV) and text frames (JSON events) separately.
  • Reconnect with backoff (1 s, 2 s, 4 s… capped). The server doesn't send a close frame, so expect close code 1006 and treat every close as "reconnect".
  • On session_client_logout or unauthorized, the session is gone — log in again, don't just reconnect.
  • One socket per session. Opening a second socket with the same session_id disconnects the first.

Orders​

  • Open the WebSocket before trading. Order results arrive only there.
  • Treat the order 201 as "accepted", not "filled". It has no order ID.
  • Put a unique tag in every order's comment and match events on it.
  • Give every in-flight order a timeout (e.g. 10 s). When it expires, check GET /positions/accounts/me and GET /orders/accounts/me before doing anything else. Never resend blindly — there are no idempotency keys, so a retry can open a second position.
  • Round prices to digits and volumes to step. The server checks min/max lots, not the step.
  • Place SL/TP at least stop_level points away (see SL/TP rules).
  • When modifying an order or a position, send every field. Missing fields are cleared — e.g. moving the stop without resending the take profit removes the take profit.
  • Market orders have no slippage protection. If that matters to you, check the tick age and spread before sending, and compare position_create.open_price with what you expected.

State​

  • Reconcile after every reconnect: re-read open positions and pending orders from REST. Events sent while you were disconnected are not replayed.
  • Positions can also close without your bot: SL, TP, stop-out, or the dealer. Watch position_close and the deal's reason.
  • Check the symbol's trading hours and trade_level before trading. Orders when the market is closed are rejected.

Risk​

  • Enforce your own limits from account_Summary (equity, free margin, margin level) — the bot should stop trading, not just log.
  • On prop accounts, read prop_status from GET /api/v1/accounts/me. A breached rule can disable trading on the account.
  • Have a kill switch: revoke the API key, or close everything with POST /api/v1/positions/close-many/accounts/me.

Limits​

WhatLimit
Auth calls (/auth/v1/oauth2/*)60 per minute per IP → 429
Sessions per account5 (1 if your broker disabled multi-session)
Idle logout5 minutes without REST calls or an open socket, unless remember_me=true
WebSocket heartbeatserver ping every 5 s; closed after ~10 s without a pong
WebSocket send buffer1024 messages; the oldest are dropped if you read too slowly
account_Summary rateat most one every 200 ms per account
List page size500 by default, up to 100000
Open orders + positionsset by your broker; exceeding it is rejected with you've reached the maximum number of open orders and positions

There is no fixed rate limit on authenticated REST calls today, but your broker may add one. Don't poll what the WebSocket already pushes. See Rate limits.

Known API limitations​

These are current behaviours of the API. Build around them:

  • No order ID in the place-order response. Match on comment.
  • No client order ID / idempotency key. Reconcile before retrying.
  • Trailing stop and break-even are WebSocket-only. They're accepted on the WebSocket order_create / position_update actions, not on the REST endpoints.
  • to is ignored unless from is also given on list endpoints.
  • GET /api/v1/deals/accounts/me/summary fails with dates. Call it without from/to; for a date range, sum GET /api/v1/deals/accounts/me yourself.
  • Stop-limit order types (5, 6) are not executed. Don't use them.