DEV Community

NoMad Paolo
NoMad Paolo

Posted on

I ran 20,000 Monte Carlo simulations to find the best position size. The 'average' result is a trap.

Most traders obsess over win rate and entries. But there's one parameter that decides whether a profitable strategy makes you rich or wipes you out — and it isn't either of those. It's position size: how much of your account you risk on each trade.

I didn't want to hand-wave this, so I wrote a Monte Carlo simulation. Take one fixed, positive-expectancy strategy, change only the risk per trade, and run 20,000 parallel accounts through 200 trades each. The result is one of the most counterintuitive things in trading math — and it's a clean little NumPy exercise. Code is included; run it yourself.

The setup: a strategy that should print money

Let's define an edge:

  • Win rate: 45%
  • Reward:risk: 2:1 (a win gains twice what a loss costs)
  • Expectancy per trade: 0.45 × 2 − 0.55 × 1 = +0.35R

That's positive. On paper this strategy makes money. The question is not whether to trade it — it's how much to bet each time.

Why a winning strategy can still go to zero

Returns compound multiplicatively, and that asymmetry is brutal. Lose 50% and you need +100% just to get back to even. Lose 65% and you need +186%. A long enough losing streak — which will happen to everyone — can dig a hole deep enough that the math can no longer climb out, even with a positive edge.

So the real question becomes: at what risk-per-trade does a winning edge survive long enough to actually pay off?

The simulation

Each "trader" starts at $10,000, risks a fixed fraction of their current equity on every trade (so wins and losses compound), and we track two things per account: the final balance, and the worst drawdown it ever experienced along the way.

import numpy as np

np.random.seed(42)

N_SIMS   = 20_000     # parallel accounts
N_TRADES = 200        # trades per account
START    = 10_000     # starting balance
WIN_PROB = 0.45       # 45% win rate
WIN_R    = 2.0        # win = +2R
LOSS_R   = 1.0        # loss = -1R

def simulate(risk_frac):
    equity = np.full(N_SIMS, START, dtype=float)
    peak   = equity.copy()
    max_dd = np.zeros(N_SIMS)
    for _ in range(N_TRADES):
        wins = np.random.random(N_SIMS) < WIN_PROB
        pnl  = np.where(wins, risk_frac * WIN_R, -risk_frac * LOSS_R)
        equity *= (1 + pnl)                 # compound
        peak    = np.maximum(peak, equity)
        max_dd  = np.maximum(max_dd, 1 - equity / peak)
    return equity, max_dd

for r in [0.01, 0.02, 0.05, 0.10, 0.15, 0.20, 0.25]:
    eq, dd = simulate(r)
    print(f"risk {r:>4.0%} | "
          f"median final ${np.median(eq):>12,.0f} | "
          f"P(DD>=50%) {np.mean(dd >= 0.5):>4.0%} | "
          f"median max DD {np.median(dd):>4.0%}")
Enter fullscreen mode Exit fullscreen mode

The whole thing is vectorized across all 20,000 accounts at once, so it runs in well under a second.

The results — the seductive part

Risk / trade Median final balance P(≥50% drawdown) Median worst drawdown
1% $19,674 0% 9%
2% $36,972 0% 18%
5% $188,338 19% 41%
10% $1,238,763 94% 67%
15% $3,097,302 100% 84%
20% $3,100,429 100% 93%
25% $1,273,363 100% 98%

Read the median final balance column. More risk → more money. At 2% you median ~$37k. At 20%, ~$3.1M — almost a hundred times more. If you stopped reading here, you'd crank the risk to the moon.

The twist

Now look at what that "average" costs.

At 20% risk, 100% of accounts suffered a drawdown of 50% or worse, and the median account's worst drawdown was 93%. The $3.1M median sits on top of a path that, at some point, took almost everything away from almost everyone.

Why the average lies: survivorship bias

That $3.1M median is a number you can never actually collect. To reach it, you'd have to sit through a 93% drawdown without flinching — but at 93% down, one of three things has already happened:

  1. You got a margin call and the exchange closed you.
  2. You capitulated and turned off the strategy (no human watches 93% evaporate and stays calm).
  3. Your risk desk / fund pulled the plug.

The mean and median final balance quietly assume you survive every path. Real accounts don't. The metric that actually matters isn't expected final value — it's P(surviving the path).

The median even turns on you

Notice the bottom row: at 25% risk you median less than at 15–20%. Past a certain fraction, over-betting erodes even the typical outcome — volatility drag gives back more than size adds. That tipping point isn't arbitrary; it has a name.

Enter Kelly

The growth-optimal fraction is given by the Kelly criterion:

# f* = (p·b − q) / b,  with p=win prob, q=1−p, b=reward:risk
kelly = (WIN_PROB * WIN_R - (1 - WIN_PROB)) / WIN_R
# = (0.45·2 − 0.55) / 2 = 0.175  → 17.5%
Enter fullscreen mode Exit fullscreen mode

Full Kelly (17.5% here) maximizes long-run growth — and you just saw what its drawdowns look like (84% at 15%). That's why practitioners trade fractional Kelly — a half or a quarter of it — and why "risk 1–2% per trade" is the rule of thumb that keeps real humans in the game.

1–2% isn't timid. It's the only band where the median is healthy and survival is near-certain (0% of accounts hit a 50% drawdown). Everything to the right looks better on a spreadsheet and ends in liquidation.

The takeaway

Optimize for surviving the path, not for the average outcome.

A smaller edge you can hold through beats a bigger one that liquidates you before it ever pays off. Win rate and entries get all the attention; position size quietly decides who's still trading next year.


I packaged this exact math into a few free calculators — position size, liquidation price, and risk of ruin — at riskdesks.com, no signup. Or just copy the code above and try to break it: change the win rate, the R:R, the number of trades, and watch where the "average" stops being something you could ever actually keep.

If you run it and get an interesting result, drop it in the comments — I'm curious what edge/sizing combos people are actually using.

Top comments (0)