A trading bot is not a signal in a loop. It is a distributed state machine connected to a remote exchange that can be delayed, unavailable or internally inconsistent from your point of view. Reliability comes from separating decisions, enforcing invariants and treating unknown state as risk.
The seven-stage pipeline
| Stage | Owns | Must not assume |
|---|---|---|
| Market data | Ticks, books, candles, timestamps | Freshness or sequence without checks |
| Features | Point-in-time transformations | Future close, revised data or missing symbols |
| Strategy | Desired exposure and invalidation | That an order will fill |
| Risk | Caps, sizing, portfolio admission | That requested position equals actual position |
| Execution | Orders, retries, price policy | That timeout means rejection |
| Reconciliation | Exchange truth vs local intent | That local memory survived restart |
| Monitoring | Health, alerts, audit trail | That silence means healthy |
Make time and freshness explicit
Store exchange event time, local receive time and processing time separately. Reject or quarantine data that is stale, out of sequence or missing required benchmarks. A candle strategy must state whether it acts on a closed bar; using the still-forming high or close in a backtest and the next tick in production creates a hidden model mismatch.
Signals request exposure; risk grants permission
Keep strategy plugins independent from transport. A strategy can propose side, size basis, entry window and exit logic. The engine should enforce exchange precision, maximum notional, margin budget, correlated exposure, daily loss/equity guards and protection requirements. This boundary lets you compare strategies without duplicating safety code.
Idempotency and the uncertain-order problem
Give each intent a deterministic client order ID. If submission times out, query by client ID and reconcile fills before retrying. Store transitions durably: intent created, submitted, acknowledged, partially filled, filled, cancel requested, cancelled or terminally rejected. A retry is safe only when repeating it cannot create additional unintended exposure.
Exchange state is authoritative—but can be temporarily invisible
On startup, acquire a single-instance lock, load durable state, query balances, positions, open orders and recent fills, then reconcile before enabling entries. If private data is unavailable, do not infer “no position” or “no protection.” Unknown is a separate state that blocks risk-increasing actions.
Stale best bid/offer cannot trigger an urgent target order. Missing position data cannot authorize a new entry. An unverified stop cannot be reported as protected. Repeated API failure should trip a circuit breaker, preserve diagnostic context and require recovery criteria before trading resumes.
Observability is part of execution
- Logs: structured event, symbol, intent ID, order ID, state before/after and reason code.
- Metrics: data age, loop lag, API latency/errors, rejected orders, fill slippage, open risk and protection coverage.
- Snapshots: non-secret active configuration, positions, strategy state and last successful reconciliation.
- Alerts: actionable condition, first occurrence, escalation and recovery—not one message per loop.
A safe deployment ladder
- Pure functions and deterministic unit tests.
- Historical event replay with recorded market and exchange responses.
- Paper/shadow mode using live data but no order placement.
- Exchange sandbox if it accurately models the required product.
- Minimum live size with strict caps and supervised sessions.
- Gradual scale only after realized fills, fees, faults and recovery are reviewed.
Implementation references
The CCXT manual documents public/private API separation, rate limiting, order IDs and exchange-specific differences. CROT’s open-source repository is one concrete design to inspect; read its safety assumptions and tests rather than treating open source as proof of profitability.