BLOG · #Engineering

Trading app architecture: six constraints and what they force

A trading client’s architecture is mostly settled before you choose a stack — by data rate, irreversibility, catalog size, platform count, team count, and an untrusted device. Here is what each one forces, and how the design collapses when your version of the constraint is smaller.

Also in 中文. A six-part series.

You cannot pick a trading client’s architecture by taste. Most of it is decided before you write a line — by how fast the data changes, what a wrong order costs, how big the catalog is, how many platforms and teams you have, and the fact that the app runs on a device you don’t control.

So the useful question is not “which stack.” It is: what does each of those constraints force, and what happens to the design when your version of that constraint is smaller?

That second half is the part worth caring about. Large financial apps converge on a recognizable shape, and the shape is often copied by teams who don’t share the constraints that produced it. What follows is the reasoning behind each element — informed by taking a large trading app apart and checking that the theory matched what a team at real scale actually built — and then the conditions under which each element is simply unnecessary.

The six constraints

1. Freshness. Market data is a high-rate, unbounded stream whose value decays in milliseconds. It cannot be polled, the consumer is permanently slower than the producer, and a silently dropped update is not missing data but wrong data. This forces a pushed, sequenced, back-pressured transport, and a decision about which streams may be conflated and which must be resynced. → Real-time data pipelines: push, backpressure, and conflation

2. Irreversibility. An order cannot be undone by the user, and exactly-once delivery does not exist. This forces an idempotency key minted and persisted before the first send, an explicit unknown state on timeout, reconciliation instead of blind retry, and a server that is the only source of truth. → Order reliability: idempotency, unknown states, and reconciliation

3. A large catalog. Hundreds of thousands of instruments, searchable instantly, including offline. This forces separating slow-changing reference data from fast-changing values, shipping a snapshot on the device, and syncing deltas against a monotonic version — while the ledger stays authoritative on the server. → Local-first data: snapshots, deltas, and the two kinds of state

4. Two platforms, one truth. iOS and Android must agree exactly on financial rules, and should not agree on interaction idioms. This forces sharing the layer where divergence is a defect and keeping native the layer where divergence is correct — and paying attention to the cost of the boundary between them. → The cross-platform boundary: share what must not diverge

5. Many teams, many years. The system must keep shipping while it changes shape. This forces module boundaries drawn around decisions likely to change, a core that receives its dependencies rather than reaching for them, and migration by growing the new inside the old instead of rewriting. → Modularity and migration: cutting boundaries that survive

6. An untrusted device. Every control you ship can be removed. This forces treating the client as an input and never an authority, keeping no secrets in the binary, auditing effective rather than declared policy, and treating every deep link and bridge call as hostile input. → The client security model: an input, never an authority

Flutter implementation notes

The six above are framework-neutral. This second group takes the same constraints down to a concrete Flutter build — with the patterns, the costs, and a reference design you could start from on Monday.

The decision rules

Every part above ends with “adopt it when a measurement demands it,” which is meaningless until someone names the measurement. One article supplies the numbers — frame budgets, message rates, catalog sizes, team sizes — the ladder of cheaper fixes to exhaust first, and the counter-signals that should stop you even past a threshold.

It also draws the distinction the rest of the series glosses over: correctness has no threshold. Idempotency keys, server-side authorization, sequence numbers, and decimal money are day-one work at any scale, because the cost of retrofitting them is unbounded. Only performance decisions wait for a number.

Which of these are actually yours?

Each requirement came from a constraint, and constraints have magnitude. Shrink one and the requirement it produced collapses into something much smaller:

If your reality is…The requirement…becomes…
Data changes at human speedpushed, sequenced, conflated pipelinea WebSocket into UI state
Actions are reversibleidempotency key, unknown state, reconciliationa request with a retry
Small or server-side catalogon-device snapshot + delta synca search endpoint
One platform, or thin domain logicshared cross-platform corebuild it once, natively
One small teammodule registry, ports and adaptersone codebase, no enforced boundaries
Low-value or public datalayered hardeningstandard auth and TLS, done properly

Read the right column. For a small team building a quotes-and-watchlist MVP, this reasoning produces: one cross-platform codebase, REST plus a WebSocket, a search endpoint, no shared-core layer, no native performance work, no custom transport. That is not a simplified version of the large architecture. For those constraints it is the correct architecture, and the large one would be a mistake.

The large app is not more advanced. It is the same six constraints at full magnitude, plus years of accumulated migration state that no one would choose deliberately.

How to use this

Write down your own six magnitudes honestly. Work out what each one forces. Build only that.

Then adopt each heavy element on the day a measured constraint demands it — the queue actually grows, the profile actually points at the decode loop, the merge conflicts actually recur — and not one sprint earlier. Most of the cost of a large architecture is paid by teams who adopted it in anticipation of a scale they never reached.

What compounds is the map from constraints to structure. The structure itself is disposable, and someone else’s structure, copied, is a liability with good PR.

References


The series is grounded in a static architectural analysis of a large trading app: what layers exist and how they communicate. No running app, no source recovery, no private protocols — and the detailed teardown notes stay out of the series deliberately, because the transferable part is the reasoning, not the artifact.