Case study · Trading systems
Six layers, and the measurement that killed the strategy running on them
A six-layer crypto execution platform I built for myself, and the two weeks of measurement that proved the strategy on top of it could not pay for its own fees. The engineering was fine. The economics were never checked, until they were.
- Client
- My own project, my own capital
- Domain
- Trading systems
- My role
- Own project, whole stack
- Period
- 2025 – 2026
- Result
- Signal worth 0.5–1.5 bp against 11 bp of cost. No live order ever sent.
Context
This one is mine. No client, no desk, my own capital and my own architecture, which is why I can publish the internals and the result.
The platform is a crypto execution stack in six layers. L0 holds the exchange drivers, one per venue, speaking each exchange’s native protocol and nothing else. L1 is the OMS/EMS: pre-trade risk, order routing, balance reservation, failover between several L0 instances, publishing everything downstream. Above that sit the strategies, a separate risk engine, an indicator service, a data recorder, an order simulator, an API gateway and a React front end. C++17 where latency is on the path, Go for services, TypeScript for the UI, gRPC between the hot components and NATS for everything that fans out.
The strategy running on it analysed order book imbalance: the classic microstructure family, OBI and OFI and microprice, top of book, sub-second horizon.
The problem
The platform was in better shape than the question it was built to answer.
In July 2026 I reviewed my own analyser as if a client had handed it to me, and the verdict was uncomfortable: this was not a trading system, it was an engineering-shaped hypothesis with no measurement behind it. Three findings did the damage.
Fees appeared nowhere in the economics. GetFeeRate existed in the code, and
the number it returned never reached the risk-reward filter, the expected PnL
calculation or the take-profit level. The PnL tracker was reporting gross.
Every “profitable” session I had looked at was measuring the wrong thing.
The signal horizon and the exit horizon disagreed by four orders of magnitude. OBI and OFI predict price over hundreds of milliseconds. My exits were set at 1 percent stop loss and 5 percent take profit, which is a horizon of days. The entry came from a microstructure signal that dies in a second, and then the position was held like a swing trade. Whatever the win rate was, it was a random walk between minus one and plus five, not a signal.
And the whole path from exchange to order was hundreds of milliseconds. This particular alpha gets collected by colocated participants in single-digit milliseconds. Arriving late to that queue does not give you a smaller share of the edge. It gives you the negative half of the distribution, because your fills systematically happen after the signal has already turned.
Constraints
The cost floor was fixed and public. VIP0 taker fees on Bybit are 5.5 basis points a side, so a round trip costs 11 bp before spread and slippage. That number is not negotiable by writing better code, and near-zero maker tiers are reachable through VIP and market-maker programmes rather than through effort.
Retail latency was measurable, and I measured it. A book update took 82 to 85 ms at p50 just to arrive at the VPS, with a p99 above 108 ms. The gap between that and colocation is the whole strategy.
No live testing. At an edge this size a week of live trading that appears to work proves nothing, and it costs real money to learn nothing. The decision had to come from recorded data.
My own judgement was the biggest risk in the project. I wanted this to work. I had already spent months on the platform. That is exactly the condition under which people find edges in noise, so the process had to be built to stop me: pre-registered candidates, declared forward boundaries, an economic gate written down before any data was collected.
What I did
Wrote the kill criterion before writing the analysis. Compare the expected move given a signal and the actual delay against the full round trip of spread plus both fees plus slippage. Below cost, the hypothesis is dead and debugging execution is a waste. Above cost, fix things in a specific order. Writing that down in advance is the only reason the eventual answer was believable.
Recorded raw frames rather than derived features. A recorder on a VPS captured WebSocket frames, order book and trades as compressed segments. The main capture ran 51 hours 5 minutes across BTC, ETH and SOL: 613 segments, 1.9 GB compressed, frame IDs continuous to 18,974,374.
Ran structural QC before letting myself look at any result. Foreign session
rows, frame gaps, deltas before snapshots, non-consecutive update IDs, sequence
reversals, crossed or empty books, duplicate trade IDs. All zero, with a
SHA-256 manifest per file. Contaminated runs, .partial files, smoke sessions
and old collectors were excluded by rule rather than by judgement on the day.
Replayed causally, with the pessimistic assumption everywhere it mattered. Feature histories and pending targets were destroyed at every stale interval and every reconnect, so nothing leaked across a gap. Execution priced at executable L1 bid and ask instead of mid. Latency and fees included. Confidence intervals from hourly block bootstrap, 10,000 resamples, fixed seed.
Froze the candidates and announced the boundary timestamp in advance. Parameters were fixed before any post-boundary market row was read. The forward check then ran on data that had never touched parameter selection.
What went wrong
I nearly convinced myself. The ETH probe came back with a mean gross of +0.680 bp and a bootstrap lower bound above zero, which is exactly what a find looks like. It had 48 signals against my own pre-declared gate of 100. Tens of thousands of configurations had been searched. A single positive confidence interval out of that many attempts is the expected output of selection bias, and the only reason I did not treat it as a discovery is that the gate of 100 was written down two weeks earlier, when I had no stake in the answer.
The BTC probe failed in a more familiar way. Positive mean gross, lower bound below zero, and 36 of its 42 signals concentrated in the first quarter of the window. A result that lives in one time slice is a description of that time slice.
The volatile universe was the last real hope, and it closed the question. Eight liquid-but-wild instruments, another 24 hours, separate continuation and exhaustion rules. Among configurations with at least 100 signals, positive net results: zero.
One self-inflicted operational failure. The first analytical QC was killed
by the OOM killer because I had put a 280 MB feature database and a fresh trade
index in the VPS /tmp, which is a 465 MiB tmpfs with no swap. The recorders
survived and raised no warning, so the data was intact and only my analysis
died. Heavy intermediates moved to disk-backed storage and the run completed.
And the structural mistake, which is the point of the whole case. I built six layers of platform before establishing that anything profitable would flow through them. The infrastructure was the enjoyable part and the economics were the deferred part, and the order should have been reversed. A week of recording and offline analysis, done first, would have reached the same conclusion.
Result
The gross edge measured between 0.5 and 1.5 bp. The taker round trip costs 11 bp. That is a gap of 16 to 21 times, which no amount of execution tuning closes: for a 1.5x margin the entire round trip would need to cost under 0.454 bp, and that fee tier does not exist for a retail account.
So the deliverable was a decision. Live orders were never enabled, not once. The recorders were stopped cleanly on 18 July 2026, the raw data was kept, and the research was closed with the negative result written up in full rather than quietly abandoned.
What survived is the part worth having: the recorder, the QC pipeline, the causal replay harness, and the habit of declaring the gate before collecting the data. Those moved to a funding-carry project, where the income is a published number rather than a prediction, and where the same discipline can be applied to a class of strategy that is not structurally lost before it starts.
The platform still runs. It is being pointed at strategies that can explain who is paying, and why.
Stack: C++17 · Go · TypeScript · gRPC · NATS · PostgreSQL · Redis · Docker