The best of a hundred coin flips looks like talent
Try enough strategies and one of them will look brilliant, even when every one is a coin toss. The number of tries is part of the result, and it is the part nobody prints.
Take a fair coin. Each step you win one unit on heads and lose one on tails, for 250 steps. That is a strategy with no edge at all, and any result it shows is luck.
Now make a hundred of them, and keep the best.
What luck looks like at scale#
One such strategy ends the 250 steps at zero or thereabouts, give or take about 16 units. Roughly half of them finish above zero. Nothing odd about that. But the best of many is a different animal. This table comes from simulating the coins (the code is below) and shows the median result of the best strategy, over thousands of simulated runs:
| Strategies tried | Typical best result, in units |
|---|---|
| 1 | 0 |
| 10 | +24 |
| 100 | +38 |
| 1,000 | +50 |
A hundred fair coins, picked afterwards, give you a line that gained 38 units over 250 steps with nothing in it but chance. On a chart it looks like a strategy. Add the usual story about why it works and it looks like a good one.
import random
def best_of(n: int, steps: int = 250, seed: int = 1) -> int:
rng = random.Random(seed)
finals = []
for _ in range(n):
finals.append(sum(1 if rng.random() < 0.5 else -1 for _ in range(steps)))
return max(finals)Where the tries hide#
Nobody sets out to test a hundred coins. The tries accumulate:
- Every parameter you sweep is a try. Ten lookback lengths times ten thresholds is a hundred.
- Every rule you add after seeing a bad stretch is a try.
- Every market, timeframe or pair you test and then drop is a try.
- Every idea you abandoned last month but remember is a try.
The number that ends up in the write-up is the winner. The number of tries, which is the thing that tells you how impressed to be, is nowhere.
What helps#
- Count the tries, the abandoned ones included, and write the count next to the result.
- Compare with luck, not with zero. Run the same sizing, stops and fees on random entries, many seeds. The result to beat is the best of that many random strategies, not a flat line. Why every strategy has to beat a coin toss does this in detail.
- Keep data you never touched while searching, and look at it once, at the end.
- Roll forward. Fit on one window, judge on the next. A strategy that only works on the window it was fitted to has told you nothing. See walk-forward testing.
- Prefer few knobs. Every knob is another place for chance to hide.
- Be suspicious of smooth, steep curves. Real strategies have flat patches and bad months.
The lab has the machine: slide the number of strategies up and watch the cyan line climb while every one of the lines has the same nothing inside.
This is how trading software can be built. It is not investment advice. Trading crypto assets, especially with leverage, carries a risk of losing money, and results from tests or simulations do not guarantee anything in live markets. The studio builds trading software and tooling. It does not manage anyone's money, hold client funds or trade on anyone's behalf.
Slippage is a fee nobody prints
The exchange prints its fee. It does not print the spread, the thin book or the stop that fills worse than its trigger. A test that leaves them out is flattering itself.
Start from the task, not the strategy
A trading bot project begins with what the bot is for, what it may lose and what it must never do. The signal comes last, and it is the smallest part of the work.
A stop that lives in your bot is not a stop
When the process crashes, the server reboots or the connection drops, a stop that exists only in the bot's memory does not exist. The stop belongs on the exchange, at entry.