Monday we did the responsible thing. The agent had a momentum strategy on a mid-cap USDT-M perpetual, clipping 25,000 USDT a trade, and the first backtest without a volume filter filled every signal as if the book were BTC. So we added the textbook gate: twenty one-minute bars of dollar volume had to exceed twenty times the clip before an order could go out. Twenty minutes, half a million dollars of tape. The coin printed about $40 million on a busy day. The filter kept most of the signals. The equity curve got less jagged. We sent it to paper.
Eleven hours later a 25,000 USDT buy ate 8,400 of bid and stopped. The twenty-bar volume on that minute was $1.14 million. The filter had said yes.
Monday: the filter that made us feel honest
The agent's note on the pull request was "capacity check added, 20× notional." I approved it. I have approved that sentence before. It is the kind of rule you write after someone shows you a backtest that traded a $4,000-OI coin in $50,000 clips, and everyone in the room makes a face, and you add a volume multiple so it cannot happen again.
On Monday the rule did what it always does in a backtest. It dropped the dead hours. It kept the hours where the exchange's own volume number looked loud. Turnover fell by about a third. Average trade size stayed 25,000 because the fill model is a coward: it takes the whole clip at last-plus-a-few-bps whenever the gate is open. Nobody asked the book whether 25,000 was actually sitting there.
I still flinch when a coin's 24h volume is a round number next to thin open interest. That is leftover from 2021, when Discord "market making" bots were two subaccounts trading with each other and pasting the volume screenshot in #brag. The tape looks like a party. The book is cardboard. I told myself a 20× filter on one-minute bars was how you grow out of that. It is not. It is how you average the cardboard into a smoother number.
Tuesday: paper, then a leftover
Paper started kind. First five clips filled in two to four hops down the ask. Ugly, but done. Then 14:07 UTC, a long signal, 25,000 USDT, top of book 1,100, next level 2,400, then air. We got 8,400 filled as taker and 16,600 resting. Forty seconds later the remainder canceled on our own time-in-force. The strategy thought it was long a full clip. The account was long a third of one.
But the filter had said yes. I pulled the bar. Dollar volume in the twenty minutes through 14:07 was $1.14 million, comfortably over $500k. Roughly $380k of that $1.14 million was pairs of prints: same price, same size, opposite aggressor flags, two to eight milliseconds apart, and the bid/ask size at that price did not budge. Those trades paid fees to each other. They did not offer us a 25,000 USDT hole.
Wednesday: matching the tape to the book
Wednesday was the slow day, the one I would budget twice as long for next time. We took five weekdays of trades and L2 snapshots on the same perp, 100ms book photos, and classified every print against the book a snapshot before and a snapshot after.
- If size at the trade price dropped by about the trade size, it touched the book. Count it.
- If a buy and a sell of equal size printed at one price with no book change, call it wash and drop it from any capacity number.
- If a trade walked through several levels and those levels disappeared, that is a real taker. Count it, and do not pretend you can also take that liquidity; someone already did.
- If the trade is tagged as a liquidation, it is real size, and it is the last size you want to use as proof that you can clip 25,000. The forced order is racing you.
The snapshots lie a little. Anything that fills and replenishes inside 100ms looks like a ghost, and some genuine market-makers cancel-replace that fast. We sampled a few minutes of 10ms books later and the wash share moved by a couple of points, not by a factor of two. I can live with a couple of points. I cannot live with treating the exchange volume ticker as a liquidity census.
AggTrades already collapse one taker hitting several makers at one price. They do not collapse two subaccounts washing. If your "clean" feed is aggregated trades, you still have this problem. You just have it with fewer rows.
And the 24h volume on the ticker API is the same dirty sum, delayed, which is why research agents love it. One field. Always present. Looks like a feature. It is a marketing statistic that happens to be in the market-data protobuf.
Thursday: the week in one table
Five sessions, same clip, same 20× rule, same fill model in the backtest. Paper fill ratio is filled notional over intended notional for the orders we actually sent that session. Monday paper is blank because we were still in the backtest.
| Session | Exchange tape | Book-moving | Wash-like | Liq. prints | Filter said go | Paper fill |
|---|---|---|---|---|---|---|
| Mon | $38.4m | $22.6m | $13.5m | $2.3m | 41 / 48 signals | n/a |
| Tue | $41.1m | $24.2m | $15.2m | $1.7m | 36 / 40 | 0.61 |
| Wed | $29.6m | $20.1m | $8.1m | $1.4m | 22 / 31 | 0.77 |
| Thu | $44.8m | $25.6m | $17.4m | $1.8m | 39 / 44 | 0.54 |
| Fri | $33.2m | $22.3m | $9.4m | $1.5m | 28 / 35 | 0.71 |
Wash-like prints were 34% of the week's tape. They were not spread evenly. Thursday, the session the original filter liked most, was the dirtiest. That is the failure mode I care about. The gate does not just overstate liquidity on average. It opens wider on the days the wash bots are working, because those days are when the raw number looks healthiest.
We reran the backtest with book-moving volume only, same 20× multiple. Signals dropped from 198 in the week to 121. A lot of the discarded ones sat in those Thursday-style hours where the tape was loud and the top of book was 1,100 USDT. The original Sharpe used 198 full fills. I am not going to quote the new one as if it were a finding about the strategy. It is a finding about the fill model: once you stop granting 25,000 USDT at a price the book did not have, a chunk of the "edge" is just completed orders that would have been remainders.
Friday: what we actually ship, and what I'd skip
The gate we kept is boring. Rolling book-moving notional, counted only from prints that took size off a level, has to clear 20× the clip, and we measure it on the bars before the decision bar so the current minute's wash cannot vote. Liquidation prints get a separate cap: if more than a quarter of the recent book-moving volume is liquidations, the strategy stands down. Forced flow is real size racing you.
Impact got the same diet. The old model inferred a Kyle lambda from exchange volume and our clip. Feed it wash and lambda goes down, because the market looks deep. Feed it book-moving volume and the same clip is a much larger share of what actually changed the book. Paper slippage on the next few sessions sat closer to that uglier number. Closer, not identical. We still do not see queue, and a 100ms photo still misses some genuine size.
What I would skip next time is the whole first Monday. Do not start from the ticker, do not start from 24h volume, do not let the agent put raw dollar volume in the feature store and in the gate as if those were two ideas. They are one dirty series, used twice. Build the book-consistent tape first, even if that means you research for two days on a single mid-cap before you have a universe. A universe filtered on wash is a list of coins that are good at looking busy.
The other skip: any fill model that treats a passed volume check as a fill. Tuesday's leftover was the strategy. The filter can only answer "has anyone been trading." It cannot answer "will your 25,000 rest or vanish." That second question needs the book, a remainder, and a time-in-force, which is a different diary, and the one we should have written before this one.
← All posts


