Inspiration

When a trading system loses money, people usually blame the strategy. But many losses come from the plumbing. An order goes through but the confirmation never comes back. A price feed freezes and the system keeps trading on an old price. The same fill gets recorded twice. The system thinks it owns nothing while the exchange says otherwise.

None of these are strategy problems. They are correctness problems, and a normal backtest never shows them.

We wanted a system where these failures happen on purpose, get handled by a clear rule, and leave records anyone can check afterwards. We started from one question. What should a trading system do when it doesn't know what happened? Our answer is that it never guesses.

What it does

QuoteGate is a fully simulated trading system, from market data to orders to accounting, with a browser console that lets anyone watch it work, break it on purpose, and check their own trading records against the same rules.

1. It refuses to trade on old prices. Simulated exchanges ("venues") stream prices to QuoteGate. For each venue it remembers when the last price arrived. If that was \(t − r\) seconds ago, the venue counts as stale once

\(t − r \ge \tau\)

where \(\tau\) is 50 to 100 ms. Prices that are stale, out of order, or don't make sense (an asking price not above the bid) are ignored. In the demo we freeze a venue, QuoteGate stops using it within about 50 ms, and it picks the venue back up the moment fresh prices return.

2. It sends each order to the best fresh price. Buys go to the venue with the lowest asking price and sells to the highest bid, as long as that venue is fresh and has enough size. Every venue it skips is reported with a reason.

3. It never guesses whether an order filled. If an order's confirmation goes missing, the order is marked UNKNOWN, trading stops, and QuoteGate asks the venue what happened. It never resends the order, because that could buy twice. If the venue restarted and forgot the order, QuoteGate stays stopped rather than guessing.

4. It accounts for every fill exactly. All prices and quantities are whole numbers. Each fill updates cash and position

\(\text{cash} \leftarrow \text{cash} − \text{price} \times \text{quantity} − \text{fee}\)

The same fill recorded twice counts once, and the position can never exceed the limit \(\lvert q \rvert \le 5\). After every run, every venue fill is checked against our records.

5. It lets you see all of this, and test it yourself. The QuoteGate console runs in the browser on your own machine. The Live view shows each venue's health with a ring that drains as its last price ages toward the stale limit, a 30 second timeline of every feed, where an order would be routed right now and why other venues were skipped, the strategy's signal and target, the position against its limit, and a stream of events. A plain English status line says what the system is doing ("Paused because venue A's last price is 80 ms old") and a Why button explains the reason in one sentence. The Orders view draws every order's journey from sent to resolved, unknown, or lost, and compares the venue's records with ours fill by fill.

6. It walks newcomers through the failures. A guided demo takes about a minute. It asks you to freeze a venue's prices, lose an order confirmation, and restart the venue in the middle of an order. Each step only ticks when the running system actually shows the expected outcome, and it shows how long that took. In our runs the frozen venue was marked stale in 50 ms, and a lost confirmation was resolved by asking the venue in about 300 ms without being resent. When the run ends, a scorecard sums it up (orders, whether the books match the venue, faults injected and handled, final position and profit after fees) with a report you can download.

7. It checks your own trades. Check fills is built for anyone running a trading bot. Drop in your broker's fill export and your bot's own trade log as two CSV files. QuoteGate detects the columns automatically, matches the files order by order, and reports in plain language which trades were booked twice, which the broker filled but the bot never recorded, which the bot recorded but the broker never made, and which have a wrong price, quantity, or fee. It shows whether both sides agree on the final position, computes realized profit before and after fees, checks an optional position limit, and explains the usual cause and fix for each problem. Files are read in memory and never saved. Two sample data sets are built in. The realistic one has 150 orders with partial fills and fee rebates and eight planted problems, and the checker finds every one.

The strategy. To exercise the system, a simple strategy watches how much size sits on each side of the order book

\(I = \frac{B − A}{B + A}\)

where \(B\) is the size waiting to buy and \(A\) the size waiting to sell. If \(I > 0.2\) it aims to hold +5, if \(I < −0.2\) it aims to hold −5, and otherwise 0. In our simulated market we deliberately made \(I\) slightly predictive. The price moves up with probability \(\frac{1 + \beta I}{2}\), with \(\beta = 0.5\), and \(\beta = 0\) for a comparison with no signal.

Results. All 224 automated tests pass and run on every push. On one machine, 1,217 orders were sent and 1,217 filled, with zero mismatches against the venue's records. Across two machines with confirmations deliberately lost, 21 orders went UNKNOWN and all 21 were recovered, none resent, and the records matched exactly. With a venue frozen under load, none of 60,000 decisions used a price older than 100 ms. An order's round trip took about 53 µs on one machine and about 649 µs across two.

The honest finding is that the signal works but doesn't make money. Across 30 test runs, the strategy predicted direction 63% of the time versus 50% with no signal, and beat the version without a signal on all 30. But it still lost money in every scenario. A trade costs about 3 ticks round trip in spread and fees, while the signal earns about \(0.13\) ticks per step held, and positions only last one or two steps. We report that instead of tuning until it looks good.

We also replayed 10 minutes of real Coinbase Bitcoin order book data through the same code, with pretend orders only. It lost $0.02 before fees and $94.67 with a 10 basis point fee.

How we built it

QuoteGate is written in Python 3.12 with no outside libraries, so you clone it and run it with nothing to install. The core logic takes the current time as an input, which lets our tests check exactly when a price turns stale. Each venue keeps one TCP connection open and sends JSON messages, one per line. Message sizes, connections, open orders, and stored records all have limits, and anything dropped is counted rather than lost silently. The same strategy, routing, and accounting code runs in a fast offline backtest and in the live networked version.

The console is plain HTML, CSS, and JavaScript served by a small Python web server bound to your own machine. Every number on screen comes from the running code or from our published result files, and every fault button calls a fault hook the simulated venues already had. The fill checker is a separate pure Python module that reuses the same ledger for its profit calculation. The design uses one consistent set of components, a dark purple theme with gray cards, our own logo, and layouts that work from a wide desktop down to a phone, with motion that switches off for people who prefer reduced motion.

Real market data came from Coinbase's public price feed, read only, with no account and no orders. Optional analytics run in Tiger Cloud (TimescaleDB). GitHub Actions runs the full test suite on every push, and the research paper is written in LaTeX. Claude Code, Codex, and a local Hermes agent helped write and review code during development. Every change was tested before we kept it, and no AI runs inside the app.

Challenges we ran into

The hardest question wasn't speed. It was what to do when an order might have filled. Resending risks buying twice, and assuming it failed risks a position nobody is tracking. We chose to stop, ask, and stay stopped if the answer is lost.

We had to be honest about freshness. We can only measure when we received a price, not when the exchange sent it, and we say so everywhere. We also kept every claim as narrow as its evidence, so each number says exactly what was measured and how.

Our automated tests caught two real concurrency bugs, a client that could starve the price feed and a shutdown that could hang. Both are fixed and tested. Reviewing the paper turned up a wrong count and some inaccurate wording, which we fixed without the results changing.

Building the console raised its own challenge. Mockups from a design tool were full of convincing details the code doesn't produce, such as exchange names, extra venues, and statistics we never computed. We removed every one, so the interface never shows invented data. Testing the fill checker on realistic sample data also exposed column names that real bot logs use but we hadn't recognized, which we then added.

Accomplishments that we're proud of

Our records match the venue's with zero mismatches, even when confirmations are lost or connections drop. Every failure we talk about is a test, including lost confirmations, disconnects, venue restarts, duplicate reports, and reused order IDs. All 21 orders that went UNKNOWN across two machines were recovered and none was sent twice. Anyone can rerun our experiment and get the same numbers, and we published a negative result instead of hiding it.

We are also proud that the project is usable by someone who has never seen it. A newcomer can run the guided demo and watch each guarantee prove itself in about a minute, and a bot developer can check their own fills against their broker without writing any code.

What we learned

"I don't know" is a valid answer for a trading system. Things only go wrong when it pretends to know.

A latency number means nothing until you say what was measured. 53 µs on one machine and 649 µs across two are both true, and they are not comparable.

Model fees and spread first. Here they decided the outcome before any tuning could. A signal that predicts direction isn't automatically a signal that makes money.

A dashboard is only trustworthy if every number on it comes from somewhere real. Showing people the system proving itself, step by step, is far more convincing than describing it.

What's next for Quote Gate

Remember orders across restarts so a venue crash can't erase what happened.

Paper trading against a real exchange sandbox using the same rule of never guessing.

Ready made presets for broker exports from Alpaca and Coinbase in the fill checker.

Risk controls such as a kill switch, a maximum loss limit, and a tested way to close positions.

More realistic fills with partial fills, queue position, and market impact.

Scaling up by splitting price handling from strategy work so the system can grow.

Everything is simulated. No real orders, no real money, no exchange account. Nothing here claims real market profits, production readiness, or exchange grade speed.

Built With

Share this project:

Updates

Submission history