The Bold Play That Passes the Funded Challenge

By  ·  October 3, 2026

A funded-account evaluation is a simple game with strict rules. At one large futures prop firm, the $50,000 evaluation asks for $3,000 of profit before the account falls $2,000 below its best close, and it watches every tick. Pass, and the firm gives you a funded account; fail, and you pay for another try. The question traders argue about most is how big to trade, and it has an answer even for a trader with no edge at all.

Probability has an old answer. Lester Dubins and Leonard Savage proved in 1965 that a gambler facing a game tilted against him, who needs to reach a goal before going broke, does best by playing boldly: few, large bets rather than many small ones. Costs tilt every retail trade against the trader, so the evaluation is exactly their game. This notebook works out what the mathematics allows, then replays the firm’s own rules, tick limit and all, on three years of one-minute Nasdaq-100 futures bars, for a trader who trades all day and for one who places a single bold bracket a day.

The answer. For a trader with no edge, trading all day passes at best 10.4% of 500 attempts, at five micros, and less at any larger size. One bracket a day at the full size, 16 points wide, passes 25.8%, two and a half times as often and close to a fair coin under the same rules (28.8%). The sizes that pass best are set by the rules’ arithmetic, not by skill, so a pass says little about an edge.

The Rules

The rules are those of Topstep’s $50K Trading Combine as its help centre stated them on 3 October 2026:

  • Profit target: $3,000.
  • Maximum Loss Limit: $2,000 below the highest end-of-day balance. It rises with each new high close, never falls, and stops rising once it reaches the $50,000 starting balance. It is checked in real time on open profit and loss: a touch ends the attempt.
  • Consistency: the best single day must be at most 55% of the total profit; if it is more, the target rises until it is not. One great day cannot pass on its own.
  • Size: at most 5 E-mini Nasdaq-100 contracts or 50 micros.
  • Costs: $3.78 a round turn for an E-mini and $1.22 for a micro (exchange, regulatory and commission together).

An E-mini is worth $20 a point and a micro $2, both moving in ticks of a quarter point (CME). Sizes below are counted in micros, so 50 means five E-minis; any ten micros are traded as one E-mini, which is cheaper. The bars are the regular session, 9:30 AM to 4:00 PM Eastern, from a public-domain Kaggle dataset of one-minute Nasdaq-100 futures bars; the 729 complete sessions from 27 December 2022 to 11 December 2025 are saved in the notebook’s data folder, with the shortened holiday sessions left out. The dataset itself stops partway through the evening of 11 December 2025, at a spreadsheet’s limit of 1,048,576 rows.

The Rules and Three Years of Bars

import numpy as np
import pandas as pd

# Topstep $50K Trading Combine, as its help centre stated it on 3 October 2026
START, TARGET, MLL, CONSISTENCY = 50_000, 3_000, 2_000, 0.55
MAX_MICROS = 50                    # 5 E-minis or 50 micros
POINT, TICK = 2.0, 0.25            # dollars a point for one micro (an E-mini: $20)
FEE_MINI, FEE_MICRO = 3.78, 1.22   # round turn

def fees(k):
    # k micros' worth, as E-minis where possible: ten micros = one cheaper E-mini
    return FEE_MINI * (k // 10) + FEE_MICRO * (k % 10)

bars = pd.read_csv("data/nq_rth.csv")
DAYS = bars["session"].unique()
O, H, L, C = (bars[x].to_numpy(float).reshape(-1, 390)
              for x in ("open", "high", "low", "close"))
ND = len(DAYS)
print(f"{ND} sessions, {DAYS[0]} to {DAYS[-1]}, 390 one-minute bars each")
print(f"typical one-minute range {np.median(H - L):.2f} points, "
      f"typical session range {np.median(H.max(1) - L.min(1)):.2f} points")
print(f"one micro: ${POINT:.0f} a point; the $2,000 limit is "
      f"{MLL / (POINT * MAX_MICROS):.0f} points at the full 50 micros")

# 729 sessions, 2022-12-27 to 2025-12-11, 390 one-minute bars each
# typical one-minute range 9.50 points, typical session range 222.25 points
# one micro: $2 a point; the $2,000 limit is 20 points at the full 50 micros

The Ceiling for a Trader With No Edge

Suppose a trader’s account moved like a fair game, with no edge and no costs. Then its expected value never changes, and that holds at the moment the attempt ends too: this is the optional stopping theorem. If the attempt ends with a gain $G$ when it passes and a loss $\ell$ when it fails, then $p\,G = (1-p)\,\ell$, so the pass rate is

$$p = \frac{\ell}{G + \ell}.$$
$(1)$

With a floor that never moves, the loss is the full $2,000 and the gain the $3,000 target, so a trader with no edge passes 40% of the time, whatever size he trades. A floor that trails the running peak does worse, because the account can fail after climbing. For a continuous account with drift $\mu$ and volatility $\sigma$ per unit of time, the peak it reaches before its first drawdown of $d$ has an exponential distribution, a result of Howard Taylor (1975) and John Lehoczky (1977). Writing $\theta = 2\mu/\sigma^2$, the chance of reaching a gain $a$ before a drawdown $d$ is

$$P_{\text{trail}} = \exp\!\Big(-\frac{a\,\theta}{e^{\theta d} – 1}\Big) \;\longrightarrow\; e^{-a/d} = e^{-1.5} = 22.3\% \quad \text{when } \theta \to 0,$$
$(2)$

and for the floor that never moves, $P_{\text{static}} = (1 – e^{\theta b})/(e^{-\theta a} – e^{\theta b}) \to b/(a+b)$.

Size enters through $\theta$. Trading $k$ times as large multiplies the drift by $k$ and the variance by $k^2$, so $\theta$ falls by a factor $k$. With an edge ($\theta > 0$) a larger size wastes it: the pass rate falls towards the no-edge value. With costs ($\theta < 0$) a larger size dilutes them, and the pass rate rises towards the no-edge value. That is bold play, in Dubins and Savage’s sense, and these formulas are its continuous form. The cell below checks both formulas against a simulation of small fair-ish steps.

Two Formulas Against a Simulation

def static_pass(theta, a=TARGET, b=MLL):
    if abs(theta) < 1e-12:
        return b / (a + b)
    return (1 - np.exp(theta * b)) / (np.exp(-theta * a) - np.exp(theta * b))

def trailing_pass(theta, a=TARGET, d=MLL):
    rate = 1 / d if abs(theta) < 1e-12 else theta / np.expm1(theta * d)
    return np.exp(-a * rate)

def simulate(mu, sd, trailing, n=20_000, seed=7):
    rng = np.random.default_rng(seed)
    x, peak = np.zeros(n), np.zeros(n)
    alive, passed = np.ones(n, bool), np.zeros(n, bool)
    while alive.any():
        x[alive] += mu + sd * rng.standard_normal(alive.sum())
        peak = np.maximum(peak, x)
        floor = (peak - MLL) if trailing else -MLL
        won, lost = alive & (x >= TARGET), alive & (x <= floor)
        passed |= won
        alive &= ~(won | lost)
    return passed.mean()

sd = 25.0                        # dollars a step: small against the $2,000 limit
print("drift a step   theta     fixed floor: formula  simulated"
      "   trailing: formula  simulated")
for mu in (-0.5, 0.0, 0.5):
    th = 2 * mu / sd**2
    print(f"{mu:+11.2f}   {th:+.5f}   {static_pass(th):19.4f}"
          f"  {simulate(mu, sd, False):9.4f}   {trailing_pass(th):17.4f}"
          f"  {simulate(mu, sd, True):9.4f}")

# drift a step   theta     fixed floor: formula  simulated   trailing: formula  simulated
#       -0.50   -0.00160                0.0079     0.0070              0.0067     0.0066
#       +0.00   +0.00000                0.4000     0.3942              0.2231     0.2215
#       +0.50   +0.00160                0.9596     0.9573              0.8155     0.8220
The pass rate against size for an account that moves like a Brownian motion, $50 of risk a trade per micro, at three edges per micro and per trade. Left, a loss limit that never moves; right, one that trails the peak. With costs (orange) the pass rate rises with size, with an edge (teal) it falls…
Figure 1. The pass rate against size for an account that moves like a Brownian motion, $50 of risk a trade per micro, at three edges per micro and per trade. Left, a loss limit that never moves; right, one that trails the peak. With costs (orange) the pass rate rises with size, with an edge (teal) it falls, and both approach the no-edge value (white, dashed), 40% for the fixed floor and 22.3% for the trailing one. The trailing floor takes close to half of the fixed floor’s chance away from every trader.

Trading All Day on Three Years of Nasdaq Futures

The formulas assume an account that moves smoothly and a floor that trails continuously. The real rules differ in three ways that matter: the floor trails only the end-of-day balance and stops at $50,000; the consistency rule forbids passing on one day; and trades come in lumps, which at a large size can be bigger than the whole loss limit.

So the rules are replayed on the bars. The trader has no edge by construction: he enters at the open of a bar in a direction chosen by a coin, with a stop and a target 25 points away, and enters again on the next bar after each exit, all session, flat by 3:55 PM. A target fills only when the price trades a tick through it; a stop fills a tick worse than its price; if one bar touches both, the stop is taken first. Every attempt starts on one of the first 500 sessions with its own coin, and at every size the same trades are scored, with the account checked against the floor at each trade’s worst open loss.

Brackets, Attempts and the Rules

LAST = 384                         # the 3:55 PM bar: flat at its close

def brackets(S):
    # an entry at every bar's open, long (+1) and short (-1):
    # the exit bar, the P&L in points and the worst open loss in points
    out = {}
    entry = O[:, :LAST + 1]
    for side in (+1, -1):
        exit_bar = np.full(entry.shape, LAST)
        pnl, worst = np.zeros(entry.shape), np.zeros(entry.shape)
        done = np.zeros(entry.shape, bool)
        for j in range(LAST + 1):
            bar = np.arange(LAST + 1) + j
            live = (bar <= LAST) & ~done
            b = np.minimum(bar, LAST)
            against = (entry - L[:, b]) if side > 0 else (H[:, b] - entry)
            towards = (H[:, b] - entry) if side > 0 else (entry - L[:, b])
            worst = np.where(live, np.maximum(worst, np.minimum(against, S)),
                             worst)
            stop = live & (against >= S)
            target = live & ~stop & (towards >= S + TICK)
            pnl = np.where(stop, -S - TICK, np.where(target, S, pnl))
            exit_bar = np.where(stop | target, b, exit_bar)
            done |= stop | target
        at_close = side * (C[:, LAST][:, None] - entry)   # still open at 3:55
        out[side] = (exit_bar, np.where(done, pnl, at_close), worst)
    return out

def all_day(B, first, seed, horizon=229):
    # one attempt: a coin for every entry, again on the bar after each exit
    coin = np.random.default_rng(seed)
    trades = []
    for d in range(first, min(ND, first + horizon)):
        i = 0
        while i <= LAST:
            exit_bar, pnl, worst = B[1 if coin.random() < 0.5 else -1]
            trades.append((d - first, pnl[d, i], worst[d, i]))
            i = exit_bar[d, i] + 1
    return np.array(trades)

def one_a_day(B, first, seed, horizon=229):
    # one attempt: one bracket a day, at the session's first bar
    coin = np.random.default_rng(seed)
    trades = []
    for d in range(first, min(ND, first + horizon)):
        exit_bar, pnl, worst = B[1 if coin.random() < 0.5 else -1]
        trades.append((d - first, pnl[d, 0], worst[d, 0]))
    return np.array(trades)

def attempt(trades, k):
    # the rules at k micros: (1, day) passed, (0, day) failed, (-1, None) open
    day = trades[:, 0].astype(int)
    pl = POINT * k * trades[:, 1] - fees(k)
    worst_open = -POINT * k * trades[:, 2] - fees(k)
    after = START + np.cumsum(pl)
    before = after - pl
    n = day.max() + 1
    close = np.full(n, -np.inf)
    last = np.r_[np.flatnonzero(np.diff(day)), len(day) - 1]
    close[day[last]] = after[last]
    best_close = np.maximum.accumulate(close)
    trail = np.r_[START, best_close[:-1]] - MLL
    floor = np.minimum(START, np.maximum(START - MLL, trail))
    breach = np.flatnonzero(before + worst_open <= floor[day])
    fail_day = day[breach[0]] if breach.size else None
    daily = np.bincount(day, weights=pl, minlength=n)
    total, best = np.cumsum(daily), np.maximum.accumulate(daily)
    ok = np.flatnonzero(total >= np.maximum(TARGET, best / CONSISTENCY))
    pass_day = ok[0] if ok.size else None
    if pass_day is not None and (fail_day is None or pass_day < fail_day):
        return 1, int(pass_day)
    return (0, int(fail_day)) if fail_day is not None else (-1, None)

B25 = brackets(25.0)
FIRST = range(500)
attempts = [all_day(B25, s, seed=s) for s in FIRST]
pts = np.concatenate([a[:, 1] for a in attempts])
per_session = np.mean([len(a) / (a[-1, 0] + 1) for a in attempts])
print(f"{len(FIRST)} attempts, {sum(len(a) for a in attempts):,} trades, "
      f"{per_session:.1f} a session")
print(f"a trade averages {pts.mean():+.3f} points (sd {pts.std():.2f}); "
      f"{np.mean(pts > 0):.1%} are winners")

# 500 attempts, 4,523,644 trades, 39.5 a session
# a trade averages -0.431 points (sd 24.91); 49.4% are winners

All Day, at Every Size

SIZES = [1, 2, 3, 5, 8, 10, 15, 20, 30, 40, 50]
allday = {}
print("micros   passed   failed   median sessions to pass")
for k in SIZES:
    r = [attempt(a, k) for a in attempts]
    res = np.array([x[0] for x in r])
    days = [x[1] + 1 for x in r if x[0] == 1]
    allday[k] = res
    med = np.median(days) if days else float("nan")
    print(f"{k:6d}   {np.mean(res == 1):6.1%}   {np.mean(res == 0):6.1%}   "
          f"{med:10.0f}")

# micros   passed   failed   median sessions to pass
#      1     0.6%    99.4%           60
#      2     5.6%    94.4%           18
#      3     8.8%    91.2%            9
#      5    10.4%    89.6%            6
#      8     8.4%    91.6%            5
#     10     8.2%    91.8%            4
#     15     5.4%    94.6%            4
#     20     5.4%    94.6%            4
#     30     3.4%    96.6%            4
#     40     2.8%    97.2%            4
#     50     2.4%    97.6%            4
Trading all day, replayed on the bars: the share of 500 attempts that pass at each size, with 95% intervals, against the two formulas fed the trades' own average and spread. The formulas, for an account that moves smoothly, promise a pass rate that keeps rising with size; the replay peaks near five…
Figure 2. Trading all day, replayed on the bars: the share of 500 attempts that pass at each size, with 95% intervals, against the two formulas fed the trades’ own average and spread. The formulas, for an account that moves smoothly, promise a pass rate that keeps rising with size; the replay peaks near five micros and then falls, because past that size a single 25-point trade is a large part of the loss limit and the consistency rule makes the account keep trading after a good day. Up to five micros the replay follows the trailing-floor formula; the formulas step up at 10, 20 and 30 micros, where ten micros become one cheaper E-mini.

One Bold Bracket a Day

Bold play means few bets, so the second trader places one bracket a day, at the session’s first bar, at the full 50 micros, and lets it run to its stop, its target or 3:55 PM. The only choice left is the width of the bracket, which sets the stake: at five E-minis every point is $100.

The rules do the rest of the arithmetic. Two winning days are the fewest that can pass, since each must then be half of the total, under the 55% limit. They pass only if two wins clear $3,000 after costs, which takes a bracket of at least 16 points: two 15-point wins come to $2,977, and a third win is needed. A bracket of 20 points would risk the whole $2,000 limit on the first trade. For each width the replay is set beside a fair coin, an account that moves like a Brownian motion with no edge inside the same bracket, under the same rules and costs, and beside the optional stopping ceiling for that width. The coin must move continuously: a winning trade can first go against the account, and once a losing day has brought the floor within a few points, that dip is enough to end the attempt.

One a Day, at Every Bracket

def fair_coin(S, k, rng, horizon=229):
    # the same bracket and rules for an account that moves like a Brownian motion
    # with no edge: inside a trade it reaches the target (a tick beyond S) before
    # the stop or the loss limit, whichever is nearer, at the gambler's-ruin odds
    balance, best_close, best_day, total = START, START, 0.0, 0.0
    for _ in range(horizon):
        floor = min(START, max(START - MLL, best_close - MLL))
        room = (balance - fees(k) - floor) / (POINT * k)   # points it may fall
        down = min(S, room)
        if rng.random() < down / (down + S + TICK):
            day = POINT * k * S - fees(k)
        elif room <= S:
            return False                   # the loss limit, touched in real time
        else:
            day = -POINT * k * (S + TICK) - fees(k)
        balance += day
        total += day
        best_close, best_day = max(best_close, balance), max(best_day, day)
        if total >= max(TARGET, best_day / CONSISTENCY):
            return True
    return False

WIDTHS = list(range(8, 21))
daily, coin_rate, ceiling = {}, {}, {}
print("bracket   stake    win     2 wins    passed   fair coin   ceiling")
for S in WIDTHS:
    B = brackets(float(S))
    res = np.array([attempt(one_a_day(B, f, seed=10_000 + f), MAX_MICROS)[0]
                    for f in FIRST])
    daily[S] = res
    win = POINT * MAX_MICROS * S - fees(MAX_MICROS)
    rng = np.random.default_rng(S)
    coin_rate[S] = np.mean([fair_coin(S, MAX_MICROS, rng)
                            for _ in range(20_000)])
    n_wins = max(2, int(np.ceil(TARGET / win)))   # consistency: two days at least
    ceiling[S] = MLL / (n_wins * win + MLL)
    stake = POINT * MAX_MICROS * S
    print(f"{S:5d} pt  ${stake:5.0f}  ${win:7.2f}  ${2 * win:8.2f}"
          f"   {np.mean(res == 1):6.1%}    {coin_rate[S]:6.1%}"
          f"    {ceiling[S]:6.1%}")
print()
for S in (8, 12, 16):              # the first minute, where these trades start
    both = np.mean((H[:, 0] - O[:, 0] >= S + TICK) & (O[:, 0] - L[:, 0] >= S))
    print(f"the first minute reaches both sides of a bracket {S} points wide "
          f"on {both:.1%} of sessions")

# bracket   stake    win     2 wins    passed   fair coin   ceiling
#     8 pt  $  800  $ 781.10  $ 1562.20     3.6%     20.3%     39.0%
#     9 pt  $  900  $ 881.10  $ 1762.20     7.4%     21.6%     36.2%
#    10 pt  $ 1000  $ 981.10  $ 1962.20     8.8%     19.8%     33.8%
#    11 pt  $ 1100  $1081.10  $ 2162.20    17.2%     25.3%     38.1%
#    12 pt  $ 1200  $1181.10  $ 2362.20    16.4%     24.2%     36.1%
#    13 pt  $ 1300  $1281.10  $ 2562.20    16.4%     22.9%     34.2%
#    14 pt  $ 1400  $1381.10  $ 2762.20    15.8%     21.6%     32.6%
#    15 pt  $ 1500  $1481.10  $ 2962.20    16.2%     20.2%     31.0%
#    16 pt  $ 1600  $1581.10  $ 3162.20    25.8%     28.8%     38.7%
#    17 pt  $ 1700  $1681.10  $ 3362.20    24.4%     28.7%     37.3%
#    18 pt  $ 1800  $1781.10  $ 3562.20    24.4%     27.1%     36.0%
#    19 pt  $ 1900  $1881.10  $ 3762.20    22.4%     24.9%     34.7%
#    20 pt  $ 2000  $1981.10  $ 3962.20    22.4%     24.4%     33.5%
#
# the first minute reaches both sides of a bracket 8 points wide on 26.5% of sessions
# the first minute reaches both sides of a bracket 12 points wide on 9.7% of sessions
# the first minute reaches both sides of a bracket 16 points wide on 3.4% of sessions
One bracket a day at 50 micros, replayed on the bars (orange, 500 attempts, 95% intervals), against a fair coin under the same rules (teal) and the optional stopping ceiling for a trader with no edge (white, dashed). The jump between 15 and 16 points is the target's arithmetic: from 16 points two…
Figure 3. One bracket a day at 50 micros, replayed on the bars (orange, 500 attempts, 95% intervals), against a fair coin under the same rules (teal) and the optional stopping ceiling for a trader with no edge (white, dashed). The jump between 15 and 16 points is the target’s arithmetic: from 16 points two winning days pass. The grey band is the best that trading all day reached at any size.

What the Evaluation Rewards

The Findings

the case                                      passes   from
no edge, a floor that never moves              40.0%   optional stopping
no edge, a floor that trails every tick        22.3%   Taylor and Lehoczky
all day, best size (5 micros)                  10.4%   500 attempts on the bars
all day, full size (50 micros)                  2.4%   500 attempts on the bars
one bracket a day, 16 points at 50 micros      25.8%   500 attempts on the bars
a fair coin, same bracket and rules            28.8%   20,000 simulated attempts
  1. Bold beats busy. One bracket a day passes two and a half times as often as trading all day at its best size, because costs and bad luck grow with the number of bets. That is Dubins and Savage’s bold play.
  2. Trading all day, bigger is worse. Up to five micros the pass rate rises, as the formulas say. Beyond that each 25-point trade is a large lump of the $2,000 limit and the consistency rule keeps the account trading after its best days, so 50 micros passes only 2.4%.
  3. The rules’ arithmetic picks the winning sizes. From 11 points three winning days clear the target after costs, and from 16 points two, so the pass rate jumps at both widths. Narrower brackets fail at the open: the first minute alone reaches both sides of an 8-point bracket on 26.5% of sessions.
  4. The firm’s floor is gentler than the formula’s. It trails only the day’s close and stops at $50,000, which is why a bold daily bracket can beat the 22.3% of a floor that trails every tick; its own ceiling, from optional stopping, is 38.7%.
  5. A pass is weak evidence of skill. A fair coin passes 28.8% of the time at the right size. A trader who does have an edge should do the opposite of the bold player and trade small and often, where the formulas show an edge doing the most for the pass rate.

Sources

  1. L. E. Dubins and L. J. Savage, How to Gamble If You Must: Inequalities for Stochastic Processes, McGraw-Hill, New York, 1965.
  2. H. M. Taylor, “A stopped Brownian motion formula”, The Annals of Probability 3 (1975) 234–246.
  3. J. P. Lehoczky, “Formulas for stopped diffusion processes with stopping times based on the maximum”, The Annals of Probability 5 (1977) 601–607.
  4. D. Williams, Probability with Martingales, Cambridge University Press, Cambridge, 1991 (the optional stopping theorem).
  5. Topstep help centre, read 3 October 2026: “What is the Maximum Loss Limit?”, “Consistency at Topstep”, “Trading Combine® Parameters” and “TopstepX™ — Commissions and Fees” (help.topstep.com).
  6. CME Group, contract specifications of the E-mini and Micro E-mini Nasdaq-100 futures (cmegroup.com).
  7. TGT Analytics, “NQ Futures – 1min Bar 2022 2025”, Kaggle dataset tgtanalytics/nq-futures-1min-bar-2022-2025, CC0: Public Domain.

Working on a pricing model or risk system? Let’s talk.