Skip to main content
Build an automated trading bot on the WhiteBIT API in Python, using requests for signed REST calls and websockets for streaming market data and fills. The guide assumes Python 3.9+ and a WhiteBIT account with a Trading-permission API key (see Prerequisites below).

Prerequisites

  • A WhiteBIT account (register)
  • An API key with Trading permission (create one)
  • Funds in the Trade balance — transfer from Main balance if needed (see Balances & Transfers). Grid strategies require both the quote currency (for buy orders) and the base currency (for sell orders).
  • Familiarity with HMAC-SHA512 signing — see Authentication for the contract and First API Call for a worked signing example with troubleshooting
  • Python 3.9+ with requests and websockets packages installed (the production guidance in the worked example relies on asyncio.to_thread, added in Python 3.9)
WhiteBIT has no public testnet or sandbox. All orders execute against the live orderbook. For risk-free testing, use Demo Tokens (DBTC/DUSDT) — activate from the WhiteBIT Codes page. Test with the DBTC_DUSDT market pair before switching to real assets.

Architecture overview

A trading bot operates as a continuous loop:
  1. Market data — receive prices and orderbook updates via WebSocket (or REST polling for lower-frequency strategies)
  2. Signal generation — apply strategy logic to determine when and what to trade
  3. Order execution — place orders via REST API (single or bulk)
  4. Fill monitoring — track order fills and state changes via WebSocket
  5. Position management — update internal state, replace filled orders, manage risk
Signal generation (step 2) is strategy-specific. The grid bot example at the end of the guide demonstrates one concrete strategy.

Market data ingestion

REST polling

For strategies that do not require sub-second data, poll market data via REST:
For Go and PHP examples, see SDKs. Endpoints:

WebSocket streaming

For real-time data, subscribe to WebSocket market streams:
See the WebSocket Quickstart for connection setup and authentication.
Incremental depth_update deltas chain via past_update_id. If a delta’s past_update_id does not match the last-seen update_id for this subscription, a message was missed — resnapshot by unsubscribing and re-subscribing. Keepalive snapshots (params[0] is true, past_update_id absent) are full resets, not gap signals — the snapshot update_id may exceed the last delta’s. See WS Quickstart — State recovery after reconnect for the full pattern.

Order placement

Every private endpoint in this guide is signed with HMAC-SHA512. The send_request helper below handles the signing and a thread-safe nonce; the rest of the guide reuses it.
send_request uses a millisecond nonce. If multiple threads call the helper in the same millisecond, the server rejects the duplicate via its nonceWindow (±5 seconds). The helper below uses a monotonic counter protected by a lock — safe under concurrent callers (for example, the kill-switch heartbeat thread and the strategy thread placing orders simultaneously).
Private WebSocket channels require an authorize handshake. The helper below fetches a token via REST (rate limit: 10 requests / 60 s) and sends the authorize message. Call it once per WebSocket connection, before any private subscribe:
See Authentication for the full signing walkthrough.

Single order

Place a limit order using the order creation endpoint: Endpoint: POST /api/v4/order/new
For bot integrations, include clientOrderId in the request to map fills back to internal strategy state without depending on the server-assigned orderId. The identifier is preserved across order/modify calls (modify issues a new orderId but retains the caller-supplied clientOrderId), which makes it the right key for order reconciliation. See Client Order ID for usage patterns.

Bulk orders

Place up to 20 limit orders in a single request — one round trip instead of up to 20 for multi-order strategies. Endpoint: POST /api/v4/order/bulk
The bulk endpoint accepts up to 20 limit orders per request. All orders in a batch must target the same market pair. Partial failures are possible — always check each order result individually, or set stopOnFail: true to stop processing at the first failed order.

Kill-switch setup

The kill-switch cancels every active order on the configured market after a timeout that the bot must refresh. If the bot crashes or loses connectivity to the API, the timer expires and the API cancels every stale order automatically. Endpoints:

Activate kill-switch

The kill-switch scope defaults to all order types (spot, margin, futures). To scope it to just spot orders — for example, when the same account runs futures positions that must not be canceled by a spot kill-switch — pass "types": ["spot"] in the request.

Heartbeat refresh loop

The bot must refresh the kill-switch before the timer expires. If the bot fails to refresh, all orders cancel automatically. Retry a failed refresh after a short delay instead of waiting the full interval — with a 45-second interval and a 60-second timeout, one skipped refresh expires the timer before the next scheduled attempt.

Deactivate kill-switch

WebSocket fill monitoring

Subscribe to account streams to receive real-time notifications for order state changes and trade executions:

Error handling

Unattended bots must handle three failure modes: HTTP 429 rate limits, transient HTTP 5xx responses, and HTTP 4xx validation errors that should not be retried.

Rate limit backoff

Error classification

Never auto-retry 401 authentication errors. A failed auth indicates an incorrect signature, expired key, or IP whitelist mismatch — retrying floods the API without resolving the root cause.
See Rate Limits & Error Codes for the complete error reference.

Reconnection and recovery

WebSocket connections drop due to network issues, server maintenance, or idle timeouts. A production bot must handle disconnections without losing state.

Ping/pong keepalive

Send periodic ping messages to detect stale connections before a timeout occurs. The server closes idle connections after 60 seconds of inactivity; send a ping every 50 seconds and treat a pong that takes longer than 10 seconds as a dead connection (see WebSocket rate limits). A longer pong wait delays disconnect detection — long enough for the kill-switch timer to expire and cancel the working orders before the bot even notices the drop:
The keepalive function above is shown in isolation. In production, integrate ping/pong handling into the main message loop — running a separate recv() consumer conflicts with the primary message loop and causes missed messages.

Reconnection with exponential backoff

Auto-resubscription after reconnect

After reconnecting, the bot must re-authenticate (for private channels) and re-subscribe to all channels.

State reconciliation

WebSocket streams do not replay messages that arrived during a disconnection. After reconnecting and re-authenticating, reconcile local state against the authoritative server state:
Call reconcile_state(MARKET, grid_orders) after authenticate_ws(ws) and before re-subscribing. For a per-fill audit trail, also query POST /api/v4/trade-account/executed-history to see fills that happened during the disconnect.

Worked example: Grid bot

A grid bot places buy and sell orders at fixed price intervals around a center price. When a buy fills, a sell is placed one grid level above. When a sell fills, a buy is placed one grid level below. The strategy profits from price oscillation within the grid range. The bot assembles the building blocks from the previous sections — the signed send_request helper, authenticate_ws, the kill-switch heartbeat, and the reconnection loop — around one strategy-specific piece: the fill-replacement trigger. The lifecycle, mapped to the sections above:
  1. Center the grid — fetch the market’s last_price from GET /api/v4/public/ticker and snap to the grid spacing.
  2. Arm the kill-switch first — activate the timer and start the heartbeat refresh loop before any order exists, so a crash mid-startup still cleans up.
  3. Clean slate — cancel leftovers from a previous run with POST /api/v4/order/cancel/all scoped to the market. The call removes every open spot order on the market, including manually placed ones.
  4. Place the grid — buy levels below center, sell levels above, batched through the bulk endpoint in groups of 20. Track orderId → {side, price, level} in a local dict (grid_orders below).
  5. Monitor and replace — subscribe to the account streams; each fully executed grid order triggers an opposite order one level away.
  6. Recover — on disconnect, reconnect with backoff, re-authorize, reconcile against POST /api/v4/orders (a grid order missing from the server filled or was canceled during the gap), then re-subscribe.
The replacement trigger is the only strategy-specific code — and the step bots most often get wrong:
Inside the async message loop, wrap synchronous REST calls in asyncio.to_thread() — blocking calls (including the backoff sleeps in send_request_with_retry) delay WebSocket message processing. When reconciling after a reconnect, confirm a vanished order’s outcome via POST /api/v4/trade-account/executed-history before treating the disappearance as a fill.
Before running against real funds: validate all order parameters against the market’s minAmount and minTotal from GET /api/v4/public/markets, and add proper logging. The startup order/cancel/all call removes every open spot order on the configured market — including orders placed manually — so run the bot on a dedicated account or sub-account.

What’s Next

Account Monitoring

Monitor balances, deposits, and order activity across all account types.

Client Order ID

Attach custom identifiers to orders for tracking across systems.

Rate Limits & Errors

Per-endpoint rate limits, error codes, and retry strategies.