I spent several weeks building a short-horizon crypto system. A live order book dashboard across ten venues, order flow indicators on top of it, then a paper trading branch to see whether the signals were worth anything.
The backtest looked survivable. Not spectacular, but the kind of result that makes you want to run one more week of data before deciding.
Then I did the arithmetic I should have done in week one, and there was nothing left to decide.
The arithmeticThree lines
The strategy was taker-side, entering and exiting inside a 60–240 second window. Fees were 0.09% per side. So a round trip costs:
2 × 0.09% = 0.18%
Now the other side of the ledger. How far does BTC actually move in 240 seconds? Take annualised volatility of 50% and scale it down:
σ(240s) = 0.50 × √(240 / 31,536,000) = 0.138%
E|move| = σ × √(2/π) = 0.110%
Put them side by side and the problem is already visible: the round-trip fee is larger than the entire expected move. Not larger than the edge — larger than the whole distance the asset travels in the window, in either direction.
The third line makes it precise. If you win m when right and lose m when wrong, the win rate p that breaks even after a round-trip cost f is:
p = 0.5 + f / (2 × E|move|)
= 0.5 + 0.0018 / (2 × 0.00110)
= 1.318
A 132% win rate. Not difficult, not demanding — impossible. You would need to be right more often than there are trades.
Once a break-even requirement exceeds 100%, the signal is irrelevant. It could be perfect and still lose. There is no amount of feature engineering, no better model, no additional venue that changes an inequality with no solution.
SensitivityWhere the wall actually is
The number above uses my parameters. The honest question is how far you'd have to move them before the strategy becomes possible — not profitable, just possible.
| Change | Value | Win rate needed |
|---|---|---|
| Baseline | 0.09% fee, 240s, 50% vol | 131.8% |
| Shorter window | 60s | 213.6% |
| Longer window | 1800s | 79.9% |
| Crisis volatility | 120% annualised | 84.1% |
| Maker rebates instead | 0.02% per side | 68.2% |
| Zero fees (fantasy) | 0.00% | 50.0% |
Two things fall out of this table, and both are more useful than the original strategy was.
The holding window was the wrong dial. My instinct had been to look for a sharper signal. But fees are fixed per round trip while the expected move grows with the square root of time — so the only structural fix is to hold longer, which is a different strategy with a different research programme, not a tweak to this one.
Fee tier dominates signal quality. Going from taker to maker moves the requirement from impossible to merely very hard. That reorders the whole project: execution comes before prediction. If I ever revisit this, the first question is whether the signal can be expressed as a resting order, and only then whether it predicts anything.
Note also what the table says about crisis volatility. At 120% annualised the requirement drops to 84% — still far out of reach, and only available during exactly the conditions where slippage, outages and adverse selection are worst. A strategy that needs a crash to be viable isn't a strategy.
MethodCost model before signal
The thing that stings is that this calculation takes ten minutes and needs no data. I could have run it before writing a single line of the dashboard.
I didn't, and the reason is worth naming: costs feel like an implementation detail. Signal feels like the interesting part. So you build the interesting part first, and by the time you get to the boring part you've spent three weeks and your judgement is no longer neutral.
Compute the break-even win rate before you build anything. If it exceeds 100%, you're done — and you're done for the price of ten minutes rather than a month.
Here it is as something you can keep:
import math
SEC_YEAR = 365*24*3600
def breakeven_win_rate(fee_per_side, hold_seconds, vol_annual):
"""
Minimum directional accuracy needed to break even on a symmetric
round-trip trade. Returns a probability -- if it is above 1.0,
stop; no signal can rescue it.
fee_per_side : e.g. 0.0009 for 9 bps
vol_annual : e.g. 0.50 for 50% annualised
"""
sigma = vol_annual * math.sqrt(hold_seconds / SEC_YEAR)
exp_move = sigma * math.sqrt(2 / math.pi)
return 0.5 + (2 * fee_per_side) / (2 * exp_move)
breakeven_win_rate(0.0009, 240, 0.50) # -> 1.318, i.e. impossible
Two caveats, because the model is deliberately generous. It assumes symmetric wins and losses, which flatters most short-horizon strategies. And it ignores slippage and adverse selection entirely — both of which push the requirement up, never down. The real number is worse than the one it gives you. That's fine: a lower bound that already says no is all you need.
Why publish thisThe dead ones are the informative ones
Almost everything published about trading strategies is a survivor. People write up what worked, or what they believe worked. The ones that died quietly are the overwhelming majority and they leave no trace, so the visible record is filtered in a way that makes every published result look better than the underlying reality.
Which means a negative result carries more information per word than a positive one. This post tells you something true about short-horizon crypto that a hundred backtest write-ups don't: at retail taker fees, the cost structure closes the strategy space before the signal is ever consulted.
There's a second reason, and it's the one I'd actually pay for if I were hiring. Anyone can find a strategy. The expensive skill is having a stopping condition and being willing to hit it. The failure mode in this field isn't missing an opportunity — it's spending eight months on something that a ten-minute calculation would have killed in week one.
The hard part was never the maths. The hard part was that I'd already built the dashboard, and it worked, and it was the most interesting thing I'd made that year. The arithmetic didn't care.