Back to the blog

ProCat Solutions

Crypto trading and automation bots: what we built after Web3

Exchange REST and WebSocket APIs, order lifecycle, rate limits, risk controls and monitoring: engineering lessons from building crypto trading bots.

ProCat Solutions cryptoweb3websocketnodejsautomationrisk-management
Crypto trading and automation bots: what we built after Web3

This article is not investment advice. We build software: the infrastructure for trading and automation bots, not trading strategies for clients’ money. The strategy always belongs to the client; our job is to make sure the system does exactly, predictably and safely what the strategy prescribes. Below we summarise the engineering questions that came up when, after the smart contract and token presale projects, we turned towards exchange automation.

Exchange APIs: REST and WebSocket together

Nearly every centralised exchange offers two channels. Over the REST API we place and cancel orders, query balances and history. Over the WebSocket channel we receive prices, order book updates and status changes of our own orders. The two channels are not in sync: it happens that WebSocket already shows a fill while REST still reports “open”, or the other way round.

Our practical solution: WebSocket is the primary source of state, but a periodic REST reconciliation runs behind it, and in case of a discrepancy we treat the exchange’s REST response as the truth. The WebSocket connection is watched by a heartbeat, and on reconnect we request a full snapshot rather than just the continuation, because how missed messages can be backfilled differs per exchange and is rarely reliable.

Order lifecycle, idempotency and rate limits

An order’s state machine looks simple (created, open, partially filled, filled, cancelled, rejected); the difficulty is in the transitions. What happens if the REST call times out? We do not know whether the order was accepted. So we assign every order a client-side identifier, which most exchanges accept, so that a repeat does not create a duplicate but returns the already existing order.

We write all state changes into an append-only event log in PostgreSQL, and current state is derived from it. If anything is disputed, what happened and when can be reconstructed from the event sequence.

The other constraint every bot runs into is the rate limit. Exchanges apply weighted rate limits: fetching an order book costs more than a ping. Exceeding the limit brings a short ban, which hurts precisely when you need to cancel an order quickly. Therefore:

  • a client-side token bucket counts consumption, read back from the exchange’s own headers;
  • we reserve headroom for critical operations (cancellation, kill switch) that queries are not allowed to consume;
  • on error we use exponential backoff with jitter, and after a 429 response we respect the wait the exchange asks for, not our own.

Risk management at the software level

Risk management for us is not part of the strategy but an independent layer running underneath it, which the strategy cannot bypass:

  • position limits per asset and in aggregate; above the limit the system will not place an opening order;
  • a maximum daily loss, on reaching which the bot only permits closing orders;
  • a kill switch: a single command that cancels every open order and puts the bot into passive mode, reachable over a separate channel as well (not only the bot’s own web interface);
  • a paper trading mode where everything runs the same way, except orders go to a simulated executor.

Paper trading is not an optional convenience feature but the basis of testing: every code change runs there first for days, on the live data stream.

Monitoring and secrets

A bot that silently stops is worse than one that fails loudly. So the bot’s state is reported by heartbeat metrics (age of the last price update, number of open orders, connection state), and an alert fires if any of them falls outside the expected range, or if the data stream stalls even without an error message. Alerts go out over several channels, because the bot itself cannot know which channel is alive at the time.

API keys never end up in the repository or in the Docker image. They arrive from a secret store at startup, as environment variables, the keys have minimal permissions (trading yes, withdrawal no), and they are IP-restricted. Revocability matters more than encryption: a compromised key has to be replaceable within minutes.

Determinism and the traps of backtesting

Backtesting is seductive because it shows pretty curves, but the result is only as good as the assumptions. The typical errors we can address on the software side:

  • look-ahead: the strategy uses data that was not yet available at that moment, so the backtesting engine only serves data that had “already arrived” according to its timestamp;
  • slippage and fees: the simulated executor does not fill at the mid price but based on the order book, with fees;
  • missing data: we do not interpolate missing candles, we mark them, and the gap is visible to the strategy as well;
  • non-deterministic runs: the same input always produces the same result, because randomness is seeded and time is injected rather than read from the system clock.

This layer guarantees that the backtest is reproducible and honest. It does not guarantee that the strategy is good. It is important to keep the two apart, and in development we take responsibility for the first.

Széchenyi Terv Plusz kedvezményezetti infoblokk
QR Code