amBrain
FinTechSep 28, 202610 min read

Building a Trading Terminal over FIX with a Rust Core: Order Book, Scalping Panel, Order State

Trading TerminalFIX ProtocolOrder BookRust
Error loading image

How a FIX trading terminal with a Rust core is built, from session recovery to the ladder, and the proof that shows a vendor has built one.

No directory tells you which companies have actually built a FIX trading terminal with a live order book, a scalping panel and a Rust hot path, because “we built a trading terminal” covers anything from a chart skin on a broker's API to a FIX client with its own order state machine. A vendor who has built one can prove it: run the terminal against a live venue while you watch, name the venues that certified its FIX connection, say where its latency timestamps are taken, and hand over code you can build without its help.

The short answer on architecture: a Rust core owns the FIX session and its sequence numbers, the order state machine, the local order book and the pre-trade checks, and with more than a few traders it runs as a gateway near the venue. The screen only sends commands and draws the latest state once per frame, so it can freeze or crash without losing an order.

What belongs in the Rust core, and what does the screen own?

Anything whose loss or delay changes an order lives in the core. That rule puts five parts there.

  • A FIX engine, with a session layer for logon, heartbeats, sequence numbers and resends, and an application layer that turns trader commands into order messages and ExecutionReports into events
  • An order manager that keeps one state machine per order, keyed by client order ID and fed only by what the venue reports
  • A book builder for each feed, folding a sequenced stream into a local book per instrument
  • Pre-trade checks on size, price bands and exposure, run on in-memory state before a message is encoded
  • A journal of every message and every trader command, written before it is acted on, so a restart can rebuild state and a disputed fill can be replayed

The screen keeps a view: ladder, charts, order blotter, positions, hotkeys. If it dies, the core still holds the session, the working orders and any stops it manages. Rust matters most in the core. With no garbage collector, no collection pause lands between a command and the wire, and the compiler rejects code that writes one book from two threads unless the book is shared behind a lock.

An ordinary web page cannot open the TCP connection a FIX session runs on. Chrome's documentation says standard web applications “cannot establish raw TCP or UDP connections”, and its Direct Sockets API lifts that limit only for Isolated Web Apps. A native terminal could hold one, but then every desk carries its own venue session, network route and sequence state. Beyond a few traders, the core belongs in a gateway near the venue: in colocation when distance dominates the latency budget, in a nearby cloud region when traders click by hand and their decisions take far longer than the network path.

What does the FIX session layer handle, and what is left to you?

The session layer gives you ordered processing and a way to ask for missed messages. It does not oblige the other side to resend all of them. The FIX Session Layer technical standard (June 2020) sets out the rules.

  • Messages are processed in MsgSeqNum(34) order. A number higher than expected is a gap, answered with a ResendRequest(35=2); the recommended form sets EndSeqNo(16) to 0, asking for everything from the first missing message on
  • Nothing after a gap is processed ahead of it. In the standard's example, messages 3 to 5 “should not be processed before message 2”
  • A number lower than expected without PossDupFlag(43)=Y should end the session with a Logout, after which the connection is dropped
  • Resent messages carry PossDupFlag(43)=Y, and deciding whether one was already processed is the receiver's job
  • The resender may skip application messages. For orders, the sender “may choose not to retransmit them because too much time has elapsed” and jumps over them with a SequenceReset(35=4) where GapFillFlag(123)=Y

Three duties follow. Persist sequence numbers on every send and receive, or a restart either asks for the whole day again or gets you disconnected for numbers that are too low. Remember which ExecID(17) values were already applied, since the standard leaves duplicate detection to the receiver. And end every reconnect with an order status check, through OrderMassStatusRequest(35=AF) where the venue supports it: a gap fill over an order means your picture of it is missing a fact.

How should the order manager read an ExecutionReport?

An ExecutionReport(35=8) carries two fields that are easy to confuse. In the FIX definition, ExecType(150) “describes the specific ExecutionRpt (e.g. Pending Cancel) while OrdStatus(39) will always identify the current order status (e.g. Partially Filled)”. Drive the state machine from the event and use the status as a cross-check.

  • Pending New (A) and New (0): the venue has received the order, then accepted it
  • Trade (F): a partial or full fill. Before FIX 4.3 fills were ExecType 1 and 2, so an adapter for a FIX 4.2 counterparty maps them to Trade
  • Pending Cancel (6), Canceled (4), Pending Replace (E), Replaced (5), Rejected (8), Expired (C)
  • Trade Correct (G) and Trade Cancel (H): a fill can be amended or broken after the fact, so even filled quantity can go down

The FIX dictionary defines LeavesQty(151) as OrderQty(38) minus CumQty(14) while the order is active, which makes it a cheap invariant to assert on every report for a working order. A report that breaks it, or an ExecType with no transition from the current state, should freeze the order and raise an alert. Guessing is how a terminal ends up showing a position the venue disagrees with.

How does a terminal handle cancel and replace races?

A scalper often changes an order before the venue has answered the previous change. Each race below is one message crossing another on the wire.

  • The order fills completely while your cancel is in flight. The venue answers with an OrderCancelReject(35=9), usually with CxlRejReason(102)=0, “Too late to cancel”, and the terminal must show the fill and the resulting position, not the flat one the trader asked for
  • A second modify goes out before the first is acknowledged and can come back rejected with CxlRejReason(102)=3, order already pending cancel or replace. Keep one replace in flight per order and fold later requests into the latest price and size
  • Each replace carries a new ClOrdID(11), and OrigClOrdID(41) points at the previous one, “NOT the initial order of the day”. A fill that crosses a replace can arrive under an older ID than the one just sent, so every ID in the chain has to map to the same order
  • The session drops with orders resting. CME's Cancel on Disconnect, for example, cancels resting futures and options orders of a COD-enabled iLink session after an involuntary disconnect, but not GTC and GTD orders. Map which orders survive a disconnect on each venue before go-live

Drop copy gives a second view. CME describes its service as real-time copies of execution reports and acknowledgements sent “on a separate, dedicated path”. Reconcile positions against it in the core and treat any disagreement with the order session as an incident.

How is the local order book built from L2 and L3 feeds?

An L2 feed publishes price levels. Binance documents a snapshot-plus-updates procedure: buffer the stream, fetch a snapshot, drop buffered events the snapshot already contains, apply the rest in order, and start again from scratch if an update ID is skipped. Its snapshots stop at 5000 levels per side, so deeper levels stay unknown until they change.

An L3 feed publishes individual orders. In Nasdaq TotalView-ITCH 5.0, an Add Order message carries an order reference number, later modify messages refer back to it, and at zero displayed shares “the order is dead and should be removed from the book”. The builder keeps a map from reference number to order and aggregates levels from it: more memory and a lookup per message, in return for order counts per level and an estimate of your own order's place in the queue.

For the ladder, a price-indexed array around the touch usually beats a tree, since prices move in ticks over a contiguous range. One writer per instrument owns the book and records the sequence number it reflects; gap recovery and fan-out are covered in the article on market data under bursts. The terminal adds one rule: a book in recovery is drawn as recovering, never as live.

What does a scalping panel need from the core?

The panel is a depth ladder you trade from. A click on a price places a limit order, a drag moves it, a hotkey sends a preset size or flattens the position. With no confirmation dialog, the safety net sits in the core, which checks maximum size, price bands and exposure per account on every one-click order. The article on pre-trade risk in the order path covers those checks.

For brackets and OCO orders, decide first where they live. FIX defines ContingencyType(1385) on NewOrderList(35=E), including One Cancels the Other and One Triggers the Other. Use the venue's version where it exists; otherwise the core emulates it by watching fills and sending the other leg. Never emulate in a screen process: a laptop that sleeps with an unprotected position is the case the bracket was for.

Position comes from fills, corrections and busts included, and the core marks it against the local book on every change; the screen reads position and PnL once per frame.

How do you measure latency from keypress to order on the wire?

A scalper cares about two chains: key or click to the order leaving the network card, and market data packet to changed pixel. Each link needs its own timestamp.

  • The input event, stamped by the operating system
  • The command entering the core, after the hop from screen to gateway
  • The risk decision completed, and the FIX message handed to the socket
  • The packet leaving the adapter. On Linux, SO_TIMESTAMPING can return transmit and receive timestamps “generated by the network adapter”
  • In the other direction: packet received, book updated, frame presented

Report each link as percentiles under a load you name, not an average from a quiet market. A single number without its clock points cannot be compared with any other number.

Should the ladder be native, or web with WebGL and WebAssembly?

MDN names 60 Hz as the most common display refresh rate, with 120 and 144 Hz also widely used, which gives 16.7 ms per frame at 60 Hz and under 7 ms at 144 Hz. A busy exchange feed can change the book many times inside one frame, so in any technology the core applies every update and the screen draws the latest state once per frame.

  • A native Rust screen drawing through the GPU gives full control of the render loop and input and one language from socket to pixel, at the cost of installers and updates for every operating system you support
  • A browser screen with the ladder on canvas or WebGL and book decoding in WebAssembly installs nothing, and OffscreenCanvas can render “inside a worker context”, per MDN. The cost is less control over timing and a garbage-collected runtime between the click and the command
  • A web UI in a desktop shell keeps one codebase and brings a browser's memory use and runtime with it

MDN also notes that most browsers pause requestAnimationFrame in background tabs, so nothing that must keep running can live in the page. A web screen on a Rust core meets the requirement; a web stack in the order path does not.

How do you test a trading terminal before it touches a live venue?

Venues decide when you are allowed in. CME, for example, “requires that all client systems transacting on CME Globex via iLink order routing or processing CME Group market data are certified by AutoCert+”, its automated testing tool. Per CME, certification covers messaging, processing and recovery from abnormal message events, and its functional tests run at no more than 10 transactions per second. A pass does not tell you what the ladder does at the open with a trader clicking.

Rehearse the failures on your own venue simulator: a gap fill over orders after a reconnect, too late to cancel, a replace rejected while pending, a trade bust, a dropped session with resting orders, a feed gap mid-burst while the trader clicks. Then replay recorded FIX traffic and market data into the core. The same input must produce the same order states and the same book on every run, which turns a trader's bug report into a test.

Should you build a trading terminal, buy one, or build around a licensed FIX engine?

Buy an off-the-shelf terminal when your venues are on its list and your workflow is standard. Build when the screen is part of why your clients choose you, when your venues are unsupported, or when you must own the order path and its risk checks. The middle route licenses a FIX engine or venue adapters and builds the rest.

Which companies have actually built one, and what evidence should you ask for?

Treat “we have built one” as a claim the vendor proves in the first meeting. Six requests do most of the work.

  • A demo on a venue's production or test environment, not the vendor's own simulator, with the connection dropped halfway to show what the ladder and the blotter do during recovery
  • The venues that certified their FIX connection: FIX version or venue dialect, date, and whether the certified system is the one they would build for you
  • How their latency figures were taken: which clock points, hardware or software timestamps, which percentiles, under what load, and what the figure leaves out
  • One incident told in detail, such as a gap fill over working orders or a trade bust: what the screen showed, what the venue said, what changed in the code afterwards
  • Full source, build instructions and deployment steps, a list of what stays theirs, and a build on a clean machine without their help
  • Where the FIX engine comes from, whether in-house, open source or licensed, and on what terms you keep using it

Where does amBrain fit?

amBrain is a Yerevan, Armenia software engineering company building low latency trading platforms, matching engines, and real-time bidding systems in Rust. amBrain has been building software since 2019.

amBrain builds algorithmic trading infrastructure: order execution, market data and pre-trade risk controls. Its trading services include trading terminal development, order management systems, and FIX protocol exchange integration.

Two figures are measured on the paths amBrain builds: market data latency under 5 ms and risk latency under 1 ms. Neither is a keypress-to-wire figure, so ask for the clock points and the load behind both, as with any vendor.

amBrain built the trading terminal for Spectre Trade. amBrain has also built a mini-exchange that runs in production on MOEX colocation. The design in this article is general. It describes neither system and does not name the protocols either one uses.

Three formats: full delivery, a dedicated team, or engineers embedded in your team. The client keeps full ownership of the product and the code, except our reusable components.

Before the first call with any vendor, amBrain included, write down your venues, the FIX version or dialect each one speaks, and the latency you need between named clock points.

Have a design like this on the table?

Bring your current architecture and the failure mode that worries you, and we will go through it together in half an hour.