Backtesting beyond win rate
A strategy with a 70% win rate can lose money forever. A strategy that wins a third of the time can compound for years. Win rate is the number beginners optimise and experienced traders barely read — here is what to read instead.
Win rate answers one question: how often were you right. It says nothing about how much you made when you were right, how much you lost when you were wrong, or whether the losses arrived politely spaced out or all in one week that would have ended you.
Those are the questions that decide whether a strategy is tradeable. NexForge computes 24 metrics per backtest, and this is what the ones that matter are actually for.
Expectancy, not win rate
Expectancy is the mean R-multiple across every trade — the average amount you make per unit risked. It folds win rate and payoff into one number, which is why it is the first thing to look at and win rate is not.
A strategy that wins 35% of the time with 3R winners and 1R losers has an expectancy of +0.4R. A strategy that wins 70% of the time with 0.5R winners and 1.5R losers has an expectancy of −0.1R. The second one feels wonderful and loses money on every trade.
Profit factor, and what null means
Profit factor is gross wins divided by gross losses. Above 1 makes money, below 1 does not, and anything above about 2 on a long sample is worth a hard look for a bug.
In our implementation it can also be null, which is deliberate:
profitFactor: grossLoss > 0
? grossWin / grossLoss
: wins.length ? null : 0,A strategy with no losing trades has no meaningful profit factor — dividing by zero would give infinity, which reads as “perfect” when it actually means “the sample is too short to have lost yet”. Returning null forces the interface to say “not enough data” rather than print a number that flatters a fluke.
Drawdown is the number that ends accounts
Maximum drawdown is the deepest peak-to-trough fall in the equity curve. It matters more than return, because return is what you get if you are still trading, and drawdown is what decides whether you are.
A backtest showing 40% annual return and 35% max drawdown is not a good strategy that you should size down. It is a strategy that will, at some point, show you a third of your account gone and ask you to keep going. Most people do not, and they quit at the bottom, which is the only way to convert a paper drawdown into a permanent loss.
Sharpe, with an asterisk we are not hiding
NexForge reports Sharpe and Sortino. Both come with a caveat that most tools leave out:
sharpe: number | null; // per-trade, annualization-freeSortino is the same idea with only downside deviation in the denominator, on the reasoning that upside volatility is not a risk anyone wants protecting from.
The fill model, stated plainly
Every backtester makes assumptions about how orders fill, and those assumptions decide the result more than the strategy does. Ours:
* Bar-close backtester: position flips execute on the close of
* the signal bar. 1 unit position sizing, fees in basis points
* per side. Long and short supported.- Fills happen at the close of the signal bar.Not at the high, not at some idealised mid. If the rule triggers on a bar, you get that bar's close.
- Fees are charged on both sidesand subtracted inside P&L, defaulting to 4 basis points per side. Entry and exit both cost.
- An open position at the end is force-closed on the last bar, so its result is counted. This is the one that catches people out — a backtester that leaves the final losing position open simply excludes it from the metrics, and quietly reports a better strategy than you have.
- Position sizing is one unit. No compounding, no pyramiding. Which matters more than it sounds, as the next section explains.
None of this models slippage, partial fills, or the fact that your size may not be available at the price you tested. Those gaps are real, and they all point the same direction: live is worse than the test.
The Monte Carlo that refuses to answer
Monte Carlo simulation reshuffles your trade sequence thousands of times to see how much of the result was luck of ordering. It is a genuinely useful robustness check, and it is also routinely misrepresented.
Ours returns finalP5, finalP50 and finalP95 — and under this engine those three numbers are always identical. Not approximately. Exactly.
* Not the outcome. With fixed position sizing every ordering sums
* the same P&Ls, and addition commutes, so `finalP5`, `finalP50`
* and `finalP95` are always the same number [...] a test pins the
* invariance so nobody presents them as a distribution again.Addition commutes. If you take the same set of trades in a different order and position size does not change, the total is the same total. A “distribution of final outcomes” from a fixed-size Monte Carlo is not a distribution — it is one number printed three times, and any tool showing you a fan chart of final equity under fixed sizing is showing you an artifact.
What genuinely varies is the path. When losses happen to cluster, the equity curve digs deeper before recovering. So the outputs worth reading are maxDrawdownP95 — how bad the drawdown gets in the unlucky-but-plausible ordering — and riskOfRuinPct, the share of orderings that would have breached a loss you could not survive.
That is the real question anyway. Not “how much might I make”, but “how bad does the road get, and would I still be here at the end of it”.
What no backtest can tell you
All 24 metrics describe what a set of rules would have done against recorded data. That is useful and it is not a prediction. The full disclaimer lists why in detail — overfitting, look-ahead bias, survivorship, unmodelled slippage, regime change — but the short version is that a backtest is prepared with the benefit of hindsight even when nobody is consciously cheating.
Which is why the number we care most about is not in the backtest at all. It is the gap between what a strategy tested at and what it actually did once real fills landed against it — and that lives in the journal, not here.
Nexlot is in construction and testing — no public sign-ups, nothing for sale, and every bot on testnet. Join the waitlist to hear when that changes.