Build1 publisher3 min readPublished
The Elixir arbitrage roadmap that puts the Rust parser third, not first
A five-phase build order for a BEAM triangular arbitrage engine treats broadcast fan-out as the first bottleneck and JSON parsing as the third. The sequencing is the transferable part.
The Engineer · Build desk
Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction
What happened
- A dev.to roadmap describes a step-by-step engineering plan for an ultra-low latency cryptocurrency HFT triangular arbitrage engine using Elixir, Rust and the Notification-Oriented Paradigm (PON).
- The development process is structured into five successive optimization phases in this order: Phase 1 PON Engine and Adjacency Registry; Phase 2 Decoupled Telemetry and TUI; Phase 3 Rustler JSON Parser NIF; Phase 4 0ns Config Compiler Hot-Swap; Phase 5 Private API HMAC Authentication.
- The primary objective is to build a sparse market adjacency matrix that routes updates point-to-point, avoiding costly global broadcasts or sweeping iterations.
- At boot the engine queries Binance's public /api/v3/exchangeInfo REST endpoint and runs a depth-first search to identify closed 3-leg arbitrage cycles starting and ending at designated hub assets USDT, BTC, ETH and BNB.
- A public ETS table named :tickers is initialized with read_concurrency: true and stores the latest bid/ask order book data.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
A roadmap published on dev.to lays out a five-phase build order for a cryptocurrency triangular arbitrage engine in Elixir and Rust: a notification-oriented engine with an adjacency registry, then decoupled telemetry and a terminal dashboard, then a Rustler JSON parser NIF, then a config compiler hot-swap, then HMAC authentication for private API calls [1][2]. What makes it worth reading is the ordering: the first thing it attacks is message fan-out, and the Rust parser waits until phase three [2][3].
Phase one is structural. At boot the engine queries Binance's public /api/v3/exchangeInfo endpoint and runs a depth-first search for closed three-leg cycles that start and end at designated hub assets: USDT, BTC, ETH and BNB [4]. The stated objective is a sparse market adjacency matrix that routes updates point-to-point instead of using global broadcasts or sweeping iterations [3]. Latest bid/ask book data lives in a public ETS table named :tickers created with read_concurrency: true [5]. Two filters sit in front of the work. If an incoming update's bid, ask and quantities match what is already in ETS, execution aborts at the entry point, which the author calls a blocked flicker [6]. If the state did change, the row is written with a monotonic nanosecond timestamp and Registry.dispatch sends {:ticker_changed, symbol} only to the processes registered on that symbol [7]. Rule processes each register to the three symbols in their own triangle using Elixir's :duplicate Registry [8]. The consequence is that a tick's dispatch cost tracks the number of rules that contain that symbol, not the number of rules in the system [9]. Inside the rule, handle_info drains any further queued notifications before evaluating, so a burst of pending messages collapses into a single evaluation [10][11].
Phase two removes observability from the hot path. The roadmap treats IO.puts as a millisecond-level blockage and requires the rule engines to stay silent during evaluation [12]. Results go out through :telemetry to a stateless handler, which issues a non-blocking GenServer.cast to a separate metrics process [13]. That dashboard process keeps counters in memory and redraws on a Process.send_after loop every 200ms, or 5Hz [14], which caps terminal writes at five per second no matter how fast the ticker stream arrives [15].
Only then does the Rust NIF appear [2]. That sequence is defensible on its own terms: a faster parser sitting upstream of a broadcast fan-out delivers the same storm earlier, and per-message parsing wins get multiplied by whatever the fan-out factor is. Fixing the multiplier before the multiplicand is the cheaper order of operations, and it is also the order that keeps the risky part, native code inside the VM, until after the message topology is stable.
Two things to watch. The supplied roadmap text contains no latency measurements for any phase, while the headline asserts sub-microsecond performance, so the claim is unbenchmarked as published [16][17]. And phase four is labelled a "0ns config compiler hot-swap" [18] - zero nanoseconds is a description of compile-time inlining, not a measured figure, and it is the phase most likely to hide a cost. Note also that authentication for private API calls is phase five [2], which means everything before it is a read-only simulator.