82,000 Lines of Code Later
There is a version of this update that begins with a number. 82,000 lines. That is what the repository contains as of this writing — not counting tests, not counting configuration, not counting the documentation site. Just the platform itself. The trading engine, the orchestration layer, the adapter suite, the backtesting simulator, the billing system, the analytics pipeline, the dashboard.
I am not leading with the number to impress anyone. Line count is a poor proxy for quality, and anyone who has maintained a serious codebase knows that the lines you delete are usually more valuable than the ones you add. I am leading with it because it captures something true about what the last few weeks have actually been: relentless, cumulative, and — more than I expected — irreversible in the best possible way. Each exchange added, each edge case handled, each abstraction that finally resolved itself cleanly into the architecture — none of it can be undone. The platform is more real than it was. Considerably more.
The launch post described a platform connecting to one exchange. The first update brought the count to four — BitMEX, Bybit, Binance, Kraken — and documented the infrastructure expansion, the billing migration, and the growth of the strategy component library from 28 to 48. This post covers what has happened since. The short answer is: a lot. The longer answer is below.
The Exchange Count
When I wrote the last update, four exchanges were live. As of this week, that number is nine.
OKX and KuCoin joined within days of each other, followed shortly by Deribit, Gemini, Phemex, and WooX. Each of these required a full adapter: WebSocket and REST, authentication, instrument discovery, candle fetching, order management, position tracking, account balance, funding rate, open interest, latency probes, regional endpoint routing, backtesting data feeds. No approximations. No shortcuts. Each one had to be correct before it shipped.
The honest thing to say is that I underestimated how different exchanges are from each other. Not at the level of concept — every exchange has orders, positions, fills, funding rates — but at the level of implementation. The divergences accumulate into something genuinely demanding. Authentication schemes, timestamp formats, error encoding conventions, WebSocket reconnection behaviour, candle ordering, symbol naming, contract maths — each exchange has made its own choices on each of these, and those choices are frequently incompatible with every other exchange in the list. What looks like a simple integration task from the outside is, in practice, a sustained exercise in discovering exactly what another system assumes the world looks like, and then building a translation layer that holds under live conditions.
At nine exchanges, that surface area is substantial. I have documented it in full at the end of this post. It is worth reading — not because it is relevant to most users most of the time, but because it gives an honest picture of what correct multi-exchange support actually requires.
Infrastructure
The compute layer has continued to evolve. All instances have been downsized from four cores to two, which turned out to be the right call — the price reduction is meaningful, and the workload profile of a trading bot is latency-bound rather than compute-bound. A four-core instance buying nothing that a two-core instance could not deliver is a cost that compounds across every user, every region, every deployment. Revised pricing: Hetzner from £0.09 per hour, Vultr from £0.27 per hour, AWS from £0.34 per hour.
AWS Hong Kong is now live as a deployment region. This was added specifically for OKX, whose matching engine is in Hong Kong — the latency numbers make the reasoning clear. London regions are available on both AWS and Vultr. The full compute tier is now Hetzner, Vultr, and AWS across EU, US, APAC, and London, with regional pricing that reflects actual cost and actual latency characteristics.
The ARM-to-x86 migration that was mentioned briefly in the last update deserves a note. The degradation observed on ARM instances was not a configuration issue — it was a consequence of AZ physical placement on the M6G family producing worse network paths to exchange endpoints than the equivalent M6I instances. Moving to x86 resolved it. This is the kind of thing that only surfaces under real conditions, and it is a good example of why the infrastructure decisions here are driven by measurement rather than specification.
Equity Protection
A set of new risk parameters were added alongside the latest exchange integrations: daily profit target, equity drawdown limit, drawdown check hysteresis, and queue depth warning. These sit at the account level rather than the bot level, and they address a class of risk that per-bot limits cannot fully capture — the aggregate behaviour of a fleet over time. A bot that individually stays within its per-trade loss limits can still contribute to a portfolio drawdown that warrants intervention. These parameters close that gap.
The hysteresis parameter in particular is something I spent time on. Drawdown limits without hysteresis produce thrashing — a bot that hits its limit, recovers slightly, re-enters, and immediately hits its limit again. The hysteresis creates a recovery buffer before the limit reactivates, which makes the protection meaningful rather than mechanical.
What Else Changed
Bot versioning, which shipped during the last update cycle, has settled in well as a pattern. Every running bot reports its version on the dashboard. Updates are opt-in and run in-place on the same instance, which means IP whitelists remain intact — a detail that matters more than it might seem, because re-whitelisting across multiple exchanges every time you update a bot is friction that compounds.
The backtest tooling has continued to improve. Long and short breakdowns, expectancy, consecutive streak analysis, hold time distributions, a date range picker, sortable trade tables with CSV export — the full list is in the changelog. More importantly, the simulator now handles all nine exchanges, including correct inverse contract arithmetic for the instruments that require it, and synthesised candle intervals for exchanges that do not natively expose every timeframe the platform supports.
Documentation has been substantially expanded. The compute providers guide, the strategy cookbook, the full exchanges reference, the updated backtesting docs — these reflect a platform that has grown complex enough that orientation matters.
On the Nature of This Work
There is a temptation, when writing progress updates, to make the pace of development sound linear. To describe a sequence of features shipped, a list of exchanges added, a set of improvements deployed — and to leave the impression that it was simply a matter of applying effort over time.
It is not quite like that.
What the last few weeks have actually felt like is more like sustained pressure against a surface that keeps revealing new texture. Every exchange added exposed new assumptions in the adapter architecture. Every new assumption required a decision: absorb it into the existing abstraction, or let the abstraction evolve. Several times, the abstraction evolved. The architecture that supports nine exchanges is not the architecture that was designed for one — it has been revised, and the revisions were not always comfortable.
82,000 lines is not a clean number. It is the residue of a lot of thinking done under constraint, a lot of decisions made without full information, and a lot of subsequent corrections that brought things closer to right. The platform it represents is better than the platform that launched. There is a meaningful distance between those two things. There is also a meaningful distance left to cover.
OKX adapter audit is next. Then Kraken adapter refinements. Then a stable release package — a versioned, tested, deployable artifact that users can point to and know exactly what they are running. After that, the roadmap has its own logic, and the platform will follow it.
Behavioural differences and known limitations across supported exchanges
Candle / Kline Data
No WebSocket candle feeds: Kraken and Gemini provide no real-time candle stream — candle data is polled via REST every 60 seconds and reconstructed from 1-minute data.
Missing native intervals:
- BitMEX only natively supports 1m, 5m, 1h, and 1D candles — 3m and 4h are synthesised
- KuCoin does not support 3m candles via REST (only WebSocket), so 3m is excluded from native intervals entirely
- Phemex is missing 3m, 2h, 6h, and 12h — all synthesised
- Deribit is missing 4h — synthesised differently depending on whether data is coming from REST or live WebSocket
- Gemini is missing 4h — only 1m, 5m, 15m, 30m, 1h, 6h, and 1D are available
Candle ordering: Bybit, OKX, Binance, and Gemini return historical candles newest-first — the adapter reverses them to chronological order.
Unusual candle field order: Bybit and KuCoin WebSocket candle events place the close price before high and low, contrary to standard OHLC ordering. REST responses from these exchanges are standard.
Inverse contract candles: Bybit inverse contracts use quote volume rather than contract count for the volume field.
Backtesting: Incomplete trailing candle groups are intentionally discarded — partial candles would produce misleading indicator readings.
Order Types
Trailing stops — software-managed on most exchanges: Bybit, Kraken, KuCoin, Phemex, WooX, Gemini, and Deribit have no native trailing stop — the platform tracks the stop price in software and updates it as price moves.
Native trailing stops (with caveats):
- Binance has native trailing stops but uses a percentage callback rather than an absolute offset — the adapter converts
- Deribit has native trailing with an absolute trigger offset
- BitMEX has native trailing via a peg mechanism
- OKX has native trailing via a move-order-stop instruction
No market orders — Gemini: Gemini does not support market orders. They are simulated using immediate-or-cancel limit orders placed at 0.5% slippage from the current price.
Order amendment:
- Binance, KuCoin, and Gemini cannot amend orders in-place — they use cancel-and-replace
- Binance and Gemini place the new order before cancelling the old; KuCoin cancels first
- Kraken trailing stops cannot be amended and must also be cancelled and replaced
Order Status
Conditional order detection — Bybit: Bybit labels all orders as either Market or Limit regardless of whether they are conditional — the adapter identifies conditional orders by the presence of a trigger price.
Cancelled conditionals — Bybit: Triggered-then-expired conditional orders emit a “Deactivated” status, which the adapter maps to Cancelled.
Partially filled detection — Deribit: Deribit has no PartiallyFilled state — orders remain “open” until fully closed. The adapter infers partial fill status by inspecting the filled quantity directly.
Nested error responses — Kraken: Kraken REST responses can report failure inside a nested field even when the top-level status says success — both levels must be checked.
Authentication
Three-part credentials: OKX and KuCoin require an API key, secret, and passphrase — these are stored as two fields with the passphrase appended to the secret.
Timestamp format inconsistency — OKX: OKX uses ISO 8601 timestamps when signing REST requests but Unix seconds when authenticating WebSocket connections — two different formats within the same adapter.
Header-only authentication — Gemini: Gemini sends all POST request data inside signed headers rather than in the request body. The body is left empty.
OAuth2 — Deribit: Deribit uses OAuth2 client credentials rather than HMAC signing. Tokens are refreshed automatically 30 seconds before expiry.
Challenge-response — Kraken: Kraken’s private WebSocket authentication involves a multi-step challenge-response flow rather than a simple signed header. The signing path also requires stripping a prefix before computing the signature.
Clock Drift & Time Sync
Timestamp-sensitive exchanges: Binance, Bybit, OKX, and KuCoin use timestamps in their authentication — if the local clock drifts too far from the server, requests are rejected. The platform detects this automatically and resyncs using a median of five server time samples, refreshing every 30 minutes.
Drift-tolerant exchanges: BitMEX (5-second window) and Phemex (60-second window) accept some clock skew without requiring explicit sync.
Nonce-based — no time dependency: Kraken and Gemini use monotonically increasing nonces rather than timestamps, so clock drift is irrelevant.
Token-based — no time dependency: Deribit uses OAuth tokens and does not sign individual requests with timestamps.
No server time available: BitMEX, Phemex, and WooX expose no server time endpoint — local system time is used as fallback.
Rate Limiting
Weight-based — Binance: Binance charges different “weights” per endpoint rather than counting raw requests. The platform tracks consumed weight via response headers. Exceeding limits triggers a temporary IP ban (2–48 hours) that worsens if retried — ban responses are treated as non-retryable.
All other exchanges use straightforward request-count-based rate limits with varying thresholds.
Error Handling
HTTP 200 with embedded errors: Bybit, KuCoin, OKX, Phemex, WooX, and Binance return HTTP 200 even when the request has logically failed — the adapter always inspects a status code embedded in the response body.
Success code formats vary: KuCoin’s success code is the string "200000", OKX’s is "0", others use numeric integers.
BitMEX uses standard HTTP status codes as the primary signal.
Kraken Charts API omits the result field entirely — HTTP 200 alone indicates success.
Currency & Symbol Formats
Each exchange uses its own symbol conventions:
| Exchange | Format example | Notes |
|---|---|---|
| BitMEX | XBTUSD | XBT = BTC; uses non-standard unit scaling (satoshis, micro-USDT, lamports) |
| Kraken | PF_XBTUSD | XBT = BTC; PF_ = linear, PI_ = inverse |
| Gemini | btcgusdperp | Lowercase, concatenated |
| KuCoin | XBTUSDTM | Suffix M indicates perpetual |
| OKX | BTC-USDT-SWAP | Dash-separated with SWAP suffix |
| WooX | PERP_BTC_USDT | PERP_ prefix, underscore-separated |
Inverse Contract Maths
Inverse contracts (settled in the base asset, e.g. BTC) require different calculations from linear contracts:
- Position sizing does not involve dividing by price — margin and size are both in the settlement coin
- PnL percentage is calculated relative to the current price, not the entry price — using the linear formula here would overstate early gains and understate later losses
- Converting to contracts requires a reference price at the time of order placement; if none is available for a market order, the current price is fetched first
- BitMEX returns zero quantity when no average fill price is available — this is expected behaviour, not an error
- Phemex inverse uses scaled integers throughout — all price and value fields carry a fixed multiplier rather than decimal strings
Account & Balance Quirks
- BitMEX account totals mix BTC and USDT balances into a single meaningless sum — per-coin balances must always be used instead
- Bybit requires a Unified account — Classic and Contract account types return incorrect data
- Deribit maintains separate account summaries per currency (BTC, ETH, USDC, etc.)
- Kraken supports multi-collateral margin accounts with a legacy single-collateral fallback
- KuCoin WebSocket wallet updates do not include unrealised PnL — only available balance and held balance
- KuCoin trade history does not return the client order ID — this field will always be empty for KuCoin fills
- Gemini does not track realised PnL — always reported as zero
- Phemex uses sign convention for margin mode: positive leverage = isolated, zero or negative = cross
WebSocket Behaviour
Private topic re-subscription: Private channel subscriptions are not automatically restored on reconnect — the bot must explicitly re-subscribe to private topics after each successful reconnect and re-authentication.
Event ordering:
- Bybit order and fill events arrive in unpredictable order — state cleanup is deferred until the fill event confirms finality
- OKX fills are subscribed before orders to ensure fills arrive first
- Deribit combines orders, trades, and positions into a single message — trades are dispatched before orders
Client order ID gaps:
- Kraken WebSocket does not include client order IDs — an internal mapping from exchange order ID to client order ID is maintained separately
- OKX fills occasionally omit the client order ID — the same mapping is used as fallback
Silent disconnection — Binance: Binance WebSocket connections can drop silently when the session token (listen key) goes stale — no error is emitted. The adapter detects this on the next connect and renews the token. Linear and inverse streams require separate tokens.
Buffer overflow — Phemex: Phemex orderbook delta updates buffer up to 500 messages while waiting for a snapshot — if the buffer overflows before the snapshot arrives, the subscription is restarted.
Abbreviated field names — WooX: WooX WebSocket messages use single-letter field names throughout — the adapter translates these to readable names before any further processing.
Fill deduplication — Kraken: To prevent double-counting fees after a reconnect, Kraken fills are deduplicated against a bounded history of recently seen fill IDs.
Synthetic cancellation detection — Kraken: Kraken does not emit explicit cancel events — the adapter detects cancellations by comparing successive order snapshots and emitting a synthetic cancel event when an order disappears.
Margin & Leverage
- KuCoin cross-margin leverage can be set directly; isolated margin leverage can only be changed by depositing or withdrawing margin
- OKX bundles margin mode and leverage into a single setting — changing mode requires preserving the current leverage value
- WooX leverage is configured per symbol, not globally
- Binance returns an error if you attempt to set the margin type that is already active — this is silently ignored for idempotency
- OKX enforces net position mode at the account level — the adapter validates this during startup and throws if the account is misconfigured
Funding Rate & Open Interest
- Binance does not include funding rate or open interest in its standard ticker response — two additional REST calls are made in parallel to supplement the ticker. Both are treated as non-critical and handled gracefully if they fail
- All other exchanges include funding rate natively in their ticker
Strategy components gracefully degrade if funding rate or open interest data is unavailable — no error is raised.
Endpoint & Routing Quirks
- Binance maintains entirely separate REST clients for linear and inverse products — every REST call is routed based on the symbol type
- Phemex uses different URL paths for linear vs inverse contracts across all endpoints — market data, orders, and positions are each split
- KuCoin stop orders are placed via the standard orders endpoint but must be queried
Client Order ID Constraints
- WooX requires client order IDs to be numeric — the platform maps internal UUID-based identifiers to monotonic numeric IDs with a 24-hour expiry
- Kraken WebSocket omits client order IDs entirely — requires the same internal mapping approach
- KuCoin trade history never returns the client order ID


