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_feedonce 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
1006and treat every close as "reconnect". - On
session_client_logoutorunauthorized, the session is gone — log in again, don't just reconnect. - One socket per session. Opening a second socket with the same
session_iddisconnects the first.
Orders
- Open the WebSocket before trading. Order results arrive only there.
- Treat the order
201as "accepted", not "filled". It has no order ID. - Put a unique tag in every order's
commentand match events on it. - Give every in-flight order a timeout (e.g. 10 s). When it expires,
check
GET /positions/accounts/meandGET /orders/accounts/mebefore doing anything else. Never resend blindly — there are no idempotency keys, so a retry can open a second position. - Round prices to
digitsand volumes tostep. The server checks min/max lots, not the step. - Place SL/TP at least
stop_levelpoints 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_pricewith 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_closeand the deal'sreason. - Check the symbol's trading hours and
trade_levelbefore 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_statusfromGET /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
| What | Limit |
|---|---|
Auth calls (/auth/v1/oauth2/*) | 60 per minute per IP → 429 |
| Sessions per account | 5 (1 if your broker disabled multi-session) |
| Idle logout | 5 minutes without REST calls or an open socket, unless remember_me=true |
| WebSocket heartbeat | server ping every 5 s; closed after ~10 s without a pong |
| WebSocket send buffer | 1024 messages; the oldest are dropped if you read too slowly |
account_Summary rate | at most one every 200 ms per account |
| List page size | 500 by default, up to 100000 |
| Open orders + positions | set 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_updateactions, not on the REST endpoints. tois ignored unlessfromis also given on list endpoints.GET /api/v1/deals/accounts/me/summaryfails with dates. Call it withoutfrom/to; for a date range, sumGET /api/v1/deals/accounts/meyourself.- Stop-limit order types (
5,6) are not executed. Don't use them.