Topstep rule reference (for the rule engine)¶
Researched July 2026. These numbers drive rules/params.py and the rule kernel. Treat
them as cited config, not constants — parameterize by account size and creation date
and re-verify medium/low-confidence rows against Topstep's live help center before trusting
a pass verdict. Confidence and sources are noted per item.
Timezone: the project's canonical timezone is ET (
America/New_York), so all times below are ET, with Topstep's native CT in parentheses for cross-referencing their help center. ET = CT + 1h always (CT and ET share the identical US DST schedule — no DST edge case between them). Internally the engine stores int-ns UTC; ET governs only where the day/session boundaries land and how times display.⚠️ The single biggest correctness item: the Maximum Loss Limit is a two-state trailing drawdown — the floor ratchets on END-OF-DAY closed balance, but a breach is checked in REAL TIME on realized+unrealized equity. Modeling it as an intraday-trailing peak silently emulates Apex, not Topstep. See §2.
1. Trading Combine — parameters per account size¶
| Account | Profit Target | Max Loss Limit (trailing) | Daily Loss Limit (optional) | Max position |
|---|---|---|---|---|
| $50K (Small) | $3,000 | $2,000 | $1,000 | 5 minis / 50 micros |
| $100K (Medium) | $6,000 | $3,000 | $2,000 | 10 minis / 100 micros |
| $150K (Large) | $9,000 | $4,500 | $3,000 | 15 minis / 150 micros |
Micros count 10:1 vs minis. Only these three sizes are offered. (Note: the commonly cited
"target = 1.5 × MLL / DLL = 0.5 × MLL" ratios hold only for the $50K — at $100K/$150K the
table's dollar figures give target = 2 × MLL and DLL = 2/3 × MLL. The dollar figures are
canonical, not any ratio.) (high confidence — Topstep
help center: trading-combine-parameters, what-is-the-maximum-loss-limit; profit-target
dollar figures corroborated by third parties + the 1.5× ratio rather than a single official
numeric page.)
Pass condition:
eod_balance ≥ start + profit_target (reach AND MAINTAIN at end of day — not an intraday touch)
AND best_single_day ≤ 0.50 × total_profit (consistency)
AND MLL never breached
2. Maximum Loss Limit — the two-state trailing drawdown (critical)¶
State A — floor ratchet (end of day only):
- The floor = min(starting_balance, peak_end_of_day_balance − MLL).
- Updates only at end of day on realized/closed balance. Intraday spikes in
open/unrealized profit do not move it. It never moves down.
- Locks permanently at the starting balance once EOD balance reaches start + MLL.
- $50K example: floor starts at $48,000; after a day closing +$500 → floor $48,500;
a later −$500 day (back to $50,500 balance) leaves floor at $48,500 (never down);
once EOD balance hits $52,000, floor locks at $50,000 (the starting balance) —
note this lock happens before the $53,000 profit-target balance.
State B — breach check (real time):
- Monitored every tick on live equity = realized closed balance + open unrealized P&L.
- Touch the floor at any point intraday → account liquidated immediately via market order
(so realized loss typically exceeds the floor due to liquidation slippage — model a
distinct ForcedLiquidation path).
(high confidence — what-is-the-maximum-loss-limit.) Open: the exact clock instant of
the EOD snapshot is not pinned to the minute (practically the daily settlement around
16:10 / 17:00 ET ≈ 15:10 / 16:00 CT).
3. Daily Loss Limit — optional¶
- Amounts $1,000 / $2,000 / $3,000 (= 0.5 × MLL). OPTIONAL and off by default — Topstep removed the default DLL on TopstepX in Aug 2024; a trader may set a Personal DLL or Trailing Personal DLL.
- Resets at 18:00 ET (17:00 CT); the trading day runs 18:00 ET → 16:10 ET next day.
- Hitting the DLL is not a violation: positions flatten, pending orders cancel, no new trades until 18:00 ET next session; the account stays eligible.
- Model as a configurable toggle (default OFF). When ON, treat as real-time daily net P&L incl. open positions (medium confidence — official page doesn't explicitly state realized-vs-unrealized; calibrate against a real account with a Personal DLL set).
(source: daily-loss-limit-in-the-trading-combine-and-express-funded-account.)
4. Consistency rule ("Consistency Target")¶
- Single best day of profit must stay ≤ 50% of the Profit Target. If exceeded, the
required target inflates — you must keep trading until
best_day ≤ 0.50 × total_profit. - Formula Topstep gives:
Best Day Profit / Total Profit = Best Day %(must be ≤ 50%). - Per-size best-day caps to avoid inflating the target: $1,500 / $3,000 / $4,500.
- Implies a ~2-day minimum to pass. Losses don't reset the best day.
(high confidence — consistency-at-topstep.)
CALIBRATED 2026-08-03 — what counts as a "trading day" (confirmed by Tarric against a real account; previously the largest open question in this section):
| Question | Answer | Where it lives |
|---|---|---|
| Activity or profit? | Activity. A day counts if you traded, regardless of P&L. | CombineKernel.on_trade_activity sets a flag; no P&L gate |
| Minimum profit for a day to count? | None. A losing day and a +$5 day both count. | same — it is a bool |
| Does a forced exit count? | Yes. A 16:10 flatten or an MLL liquidation is activity. | sim_broker.py::_close_position_at calls on_trade_activity |
| What is "activity"? | A FILL. A submitted order that never trades is not a trading day. | on_trade_activity is called only from the two fill paths, never at placement |
| When does the counter roll? | 18:00 ET, same boundary as the DLL and EOD-MLL resets. | core/time.py::trading_day_of |
The engine already matched all five; nothing changed but the confidence. Pinned by
test_a_forced_flatten_still_counts_the_trading_day and
test_an_unfilled_order_does_not_count_the_trading_day.
Still open here: is a "day's profit" gross or net of fees? realized closed P&L or EOD-balance delta? Both still move the verdict.
5. Sessions, flatten, position cap¶
- Flatten by 16:10 ET (15:10 CT) (or the product's earlier close). Risk managers begin auto-flatten ~16:08 ET; open positions & pending orders auto-cancel at 16:10 ET; daily break 16:10 → 18:00 ET. Trading day / all resets (DLL, EOD-MLL ratchet) key off 18:00 ET (17:00 CT).
- Products with earlier closes: CBOT grains ~14:20 ET (13:20 CT), CME ag ~14:05 ET (13:05 CT) — exit before their own close.
-
Position cap is fixed at 5/10/15 minis (50/100/150 micros, 10:1) for the whole Combine — no scaling in the Combine (the balance-based Scaling Plan is Express-Funded-only).
-
Exchange holidays and holiday half-sessions are NOT modelled. There is no holiday calendar in the engine (the computed one was removed 2026-07-28 — it disagreed with CME on about five dates a year and silently dropped tradable sessions). A holiday bar is treated as an ordinary weekday bar: the daily 16:10 flatten and 18:00 reset still fire, the validator raises nothing, and only weekends are refused as closed. Filter holidays and early closes out of the input data upstream.
(high — when-and-what-products-can-i-trade, trading-combine-parameters.) Open: does
the live cap count working orders toward the limit, or only net position? (model net +
working, reject over-cap with APIError(AccountViolation=4); verify live.)
6. Express Funded Account (XFA) — the funded phase — ⏸️ DEFERRED / PARKED REFERENCE¶
Out of current scope. We are combine-focused for now; the funded phase is "another time." This section is a parked reference so the rules aren't lost — do not build against it yet. It drops in later as a new
RuleSetbehind the same seam, with zero changes to the combine path.
Structurally different from the Combine — its own RuleSet:
- Starts at $0 balance (the "$50K/$100K/$150K" label is buying power only). No profit target — the objective is banking winning days and taking payouts.
- Trailing MLL buffer = same $2,000 / $3,000 / $4,500. Starts at
−buffer, trails on EOD balance, locks at $0 net once balance first reaches the buffer. - After the FIRST payout the MLL is permanently set to $0 → remaining balance is the hard
floor with zero buffer (the single biggest "keep the account" trap; a distinct
on_first_payouttransition). - DLL same amounts, optional, fixed once set.
- Scaling Plan gates max contracts by end-of-day balance; a step-up takes effect next session only (e.g. $50K: 2 lots <$1,500, 3 at $1,500+, 5 at $2,000+, topping at the combine max 5/10/15). (mid-tier 100K/150K counts: low confidence — official numbers are a chart image; third-party transcriptions conflict.)
Payout paths (choose one): | Path | Requirement | Consistency | Caps | |---|---|---|---| | Standard | 5 winning days each ≥ $150 net | none | lower (per size/path, date-dependent) | | Consistency | 3 trading days (≥1 trade/day) | largest day ≤ 40% of total net | higher |
- 90/10 profit split (accounts created on/after Jan 12, 2026 are 90/10 from dollar one). $125 min payout; withdraw ≤ 50% of balance per payout, capped by a per-payout cap. First payout needs only the winning-days requirement; every subsequent payout also requires being net-positive since the last payout.
- Per-payout caps changed Apr 28, 2026 → key them off account creation date.
(sources: express-funded-account-parameters, topstep-payout-policy,
what-is-the-scaling-plan, what-is-the-maximum-loss-limit. XFA 40% consistency is separate
from the Combine's 50%-of-target rule.)
A real Live Funded Account (LFA) is a rare later stage (~0.71% of XFA traders in 2025): no consistency target, no payout cap, "Dynamic Live Risk Expansion" instead of scaling. Out of scope initially.
7. Fees (calibrate before trusting micro-scalp verdicts)¶
Per-side, per-instrument, charged on entry AND exit — for micros the round-turn cost can rival the per-trade edge, so a small error flips pass↔fail.
- Topstep commissions (2025): minis ~$0.50/side, micros ~$0.25/side; NFA ~$0.01–0.02/side; a $10/contract fee applies only to automatic liquidations.
- CME exchange+clearing (non-member, per side, representative): ES/NQ/YM/RTY ~$1.38, micro equity (MES/MNQ/MYM/M2K) ~$0.35, CL ~$1.50, MCL ~$0.50, GC ~$1.65, MGC ~$1.10.
- All-in round-turn: ES-class minis ~$3.6–3.8, MES ~$1.2–1.4.
(medium confidence — topstepbrokerage.com/commissions-and-fees, TradeStation/MetroTrade
schedules; CME rates change — reconcile against the CME Fee Finder and a real account blotter.)
8. Instrument specs (reference; runtime source of truth is the SDK ContractModel.tick_size)¶
point_value = tick_value / tick_size. Store the {tick_size, tick_value, point_value} triple
and assert consistency on load. Tick sizes are heterogeneous — never hardcode per class.
| Sym | Exch | Tick | Tick $ | Point $ | RTH (ET) |
|---|---|---|---|---|---|
| ES | CME | 0.25 | 12.50 | 50 | 09:30–16:15 |
| MES | CME | 0.25 | 1.25 | 5 | 09:30–16:15 |
| NQ | CME | 0.25 | 5.00 | 20 | 09:30–16:15 |
| MNQ | CME | 0.25 | 0.50 | 2 | 09:30–16:15 |
| YM | CBOT | 1.0 | 5.00 | 5 | 09:30–16:15 |
| MYM | CBOT | 1.0 | 0.50 | 0.50 | 09:30–16:15 |
| RTY | CME | 0.10 | 5.00 | 50 | 09:30–16:15 |
| M2K | CME | 0.10 | 0.50 | 5 | 09:30–16:15 |
| CL | NYMEX | 0.01 | 10.00 | 1,000 | 09:00–14:30 |
| MCL | NYMEX | 0.01 | 1.00 | 100 | 09:00–14:30 |
| NG | NYMEX | 0.001 | 10.00 | 10,000 | 09:00–14:30 |
| GC | COMEX | 0.10 | 10.00 | 100 | 08:20–13:30 |
| MGC | COMEX | 0.10 | 1.00 | 10 | 08:20–13:30 |
| SI | COMEX | 0.005 | 25.00 | 5,000 | 08:25–13:25 |
| SIL | COMEX | 0.005 | 5.00 | 1,000 | 08:25–13:25 |
Shared Globex session: Sun 18:00 ET → Fri 17:00 ET (17:00 CT → 16:00 CT), daily maintenance halt 17:00–18:00 ET (Mon–Thu). Equity/index cash-settled quarterly (H,M,U,Z, 3rd Friday); commodities monthly/ cycle-based → a continuous-contract builder must pick the front month by volume/OI, not a fixed quarterly assumption. (high confidence — CME contract-spec pages + NinjaTrader/Ironbeam.)
9. Verification checklist (do before a pass verdict is trustworthy)¶
- [ ] Exact EOD-snapshot instant for the MLL ratchet.
- [ ] DLL basis (realized-only vs incl. unrealized) — calibrate vs a real Personal-DLL account.
- [ ] Consistency: gross vs net-of-fees day profit; target inflation. ("Trading day" definition is DONE — calibrated 2026-08-03, see §4. Activity not profit; no minimum; forced exits count; activity means filled; rolls 18:00 ET. The engine already matched on all five.)
- [ ] Position cap: net-only vs net + working orders.
- [ ] Gateway per-half-turn P&L method (FIFO vs weighted-average) — from real fills.
- [ ]
trail_pricevstrail_distancepopulation on the returnedOrderModel. - [ ] XFA scaling-plan mid-tier contract counts (100K/150K).
- [ ] Fee/exchange defaults + NFA $0.01 vs $0.02 — from a real account blotter. Apply the
correction with
Backtest(..., fee_model=TopstepFees(overrides={...}))(per-symbolFeeSchedulereplacement); no hand-wiring of broker + fill model required.