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

StageOwnsMust not assume
Market dataTicks, books, candles, timestampsFreshness or sequence without checks
FeaturesPoint-in-time transformationsFuture close, revised data or missing symbols
StrategyDesired exposure and invalidationThat an order will fill
RiskCaps, sizing, portfolio admissionThat requested position equals actual position
ExecutionOrders, retries, price policyThat timeout means rejection
ReconciliationExchange truth vs local intentThat local memory survived restart
MonitoringHealth, alerts, audit trailThat 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.

Fail-closed examples

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

  1. Pure functions and deterministic unit tests.
  2. Historical event replay with recorded market and exchange responses.
  3. Paper/shadow mode using live data but no order placement.
  4. Exchange sandbox if it accurately models the required product.
  5. Minimum live size with strict caps and supervised sessions.
  6. 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.