An equity curve came out of a generated strategy last month with a 63% drawdown in it. Deep, ugly, and then a clean recovery over the following six weeks — the kind of shape a researcher squints at and decides is survivable if you size down. I re-ran it through a margin engine that knows what a maintenance margin tier is. The account was liquidated on day 41, in the middle of that drawdown, and everything to the right of day 41 was fiction.
This is the most expensive class of backtest bug I know, because it doesn't distort your returns by a few basis points. It deletes an absorbing state from the simulation. A strategy that can go to zero and a strategy that can't are different strategies, and a backtest that never checks margin cannot tell you which one you have.
Three ways I keep seeing it done wrong.
Wrong way one: leverage as a number you multiply the returns by
The most common shape. You compute a return series from your signals, decide you'll run it at 10x, and multiply. Sometimes it's slightly more sophisticated — position size is equity * leverage / price — but the account state is still just a scalar that goes up and down. No margin balance, no notional cap, no failure condition.
The wreckage is subtle, which is why it survives review. Sharpe is scale-invariant, so your headline number doesn't budge when you change leverage from 3x to 30x, and the researcher concludes that leverage is a free dial that trades volatility for return. Max drawdown scales linearly and stays under 100% by arithmetic accident, because a series of multiplied returns approaches zero asymptotically and never crosses it. Multiply a -9% day by 12 and you get -108% in the honest version and a "-108% but the equity curve is still positive" in the naive one, depending on whether you compounded or summed. I've seen both.
What's missing is that leverage isn't applied to your returns. It's applied to your collateral, and collateral is a finite quantity that the exchange watches every second. Once it drops below the maintenance requirement the position stops being yours.
The tell: if your simulation cannot produce a total loss at any leverage, it isn't modelling leverage.
Wrong way two: a liquidation price derived from leverage alone
The second tier of sophistication adds a liquidation check, usually like this: at 20x, a 5% adverse move wipes the margin, so liquidate at entry × 0.95 for a long. Clean, intuitive, and wrong in four separate directions at once.
Perp venues don't use your leverage setting to decide when you're done. They use a maintenance margin rate that steps up with position notional, on a tier table. Approximate BTCUSDT USDⓈ-M shape, which shifts as venues revise it:
| Position notional (USDT) | Maintenance margin rate | Max leverage |
|---|---|---|
| 0 – 50,000 | 0.40% | 125x |
| 50,000 – 600,000 | 0.50% | 100x |
| 600,000 – 3,000,000 | 1.00% | 50x |
| 3,000,000 – 12,000,000 | 2.50% | 20x |
| 12,000,000 – 70,000,000 | 5.00% | 10x |
So the adverse move you can absorb on an isolated long is roughly 1/L − MMR, not 1/L. At 20x in the second tier that's 5% − 0.5% = 4.5%. Half a percent sounds like rounding. On BTC at 84,000 it's 420 dollars of price, and in a violent hour 420 dollars is the difference between a wick that took you out and one that didn't. Every wick is a coin flip and you've biased all of them in your favour.
Then the fees. Entry taker fee comes out of your margin the instant you're filled: 0.045% on 500,000 notional is 225 USDT against a 25,000 margin balance, which moves the liquidation price before the position has done anything at all. Funding does the same, continuously, and on levered notional it's much bigger than researchers expect:
A position that does nothing for thirty days — no adverse move, no trades — has burned close to a fifth of its collateral and pulled its liquidation price meaningfully closer. The third and fourth directions of wrongness: tier changes as you scale in, so a position that starts in the 0.5% tier and gets averaged up into the 1.0% tier gets a worse liquidation price than the one you computed at entry; and cross margin pools collateral across positions, so your BTC long's survival depends on what your ETH short is doing. If you're simulating cross margin as a set of independent isolated positions, you have the correlation structure of your own account backwards.
Wrong way three: resolving the liquidation on the wrong price, at the wrong moment, at the wrong fill
Say you got the trigger level right. Now: which price crosses it, when do you check, and what do you get filled at?
Liquidation triggers on mark price, which is an index of several spot venues with a smoothed basis component, deliberately built to resist a single-venue wick. Your stop-loss, depending on how you configured it, probably triggers on last traded price. These two numbers disagree exactly when it matters most. During a cascade the perp's last price can dislocate 1–2% below mark for tens of seconds. A backtest that runs both the stop and the liquidation off the same OHLCV low is modelling a venue that doesn't exist.
Both errors are live here and they don't cancel. Off last price, you liquidate on wicks the venue ignored. Off mark price, you miss the case where mark itself moved — index dislocation on a spot venue is a real way to get liquidated at a price that never printed on the perp you were trading.
Then the fill. Naive engines close the position at the liquidation price and book the loss as if it were a stop. What actually happens: the position is taken over by the liquidation engine at the bankruptcy price — the level where your margin is exactly zero, worse than the trigger — and a liquidation clearance fee is charged on top, tier-dependent and in the neighbourhood of 1% of notional on the lower tiers. At 20x, 1% of notional is 20% of your remaining margin. If the engine fills below bankruptcy the insurance fund covers the gap; on some venues, if the fund is drained, socialised loss reaches into the profitable side of the book. Your realised loss is not the drawdown to the liquidation price. It's more, and the excess is largest exactly on the days when the book was thin enough to make it worst.
And the timing question from intrabar mechanics applies here with sharper teeth than usual. If a 1-minute bar contains both your take-profit and your liquidation level, a backtest that checks exits before margin will happily book the win. The venue checks margin on every mark price update, roughly once a second, ahead of everything else you might have wanted to happen.
What the engine has to carry instead
None of this needs to be exotic. It needs to be stateful. The account is an object with a balance, not a return series, and every bar it gets updated in the order the venue would update it:
- Wallet balance and unrealised PnL, separately. Margin ratio is maintenance margin over margin balance, and margin balance includes unrealised PnL. Conflating them makes a losing position look better-collateralised than it is.
- The tier table for every symbol, versioned by date. Tiers get revised. A 2023 backtest run against 2026 tiers is a subtle look-ahead — usually a favourable one, since venues have generally loosened requirements on majors over time.
- A mark price series, not just klines. If you can't source mark price history, say so in the results and use a conservative proxy. Never silently substitute last price.
- Margin check first in the bar's event ordering, before stops, targets, signals or rebalancing.
- Bankruptcy-price fills plus the clearance fee, with the fee rate read from the same tier row as the MMR.
- An absorbing terminal state. When equity hits zero the run ends. No rebasing, no "assume the trader tops up," no continuing the series from a fresh notional.
That last one causes the most arguments. Someone always points out that a real desk would deposit more collateral, so ending the simulation is unrealistically harsh. Maybe. But "this strategy works if you keep feeding it money after it blows up" is a claim that deserves to be stated in those words and tested on purpose, with the top-ups as an explicit input, rather than smuggled in as a default. Once you write it down honestly, most people discover they didn't mean it.
The practical payoff of all this: when you run the same strategy on paper against the live venue, the margin ratio the exchange reports and the one your simulator computes should track each other within a basis point or two, all day. That agreement is checkable every minute, on every open position, for free. It's the cheapest parity test in the whole stack and almost nobody runs it — and when the two numbers drift, the exchange is right and you have a bug worth finding before the drawdown does it for you.
← All posts


