Skip to content

topstep-backtest

An event-driven backtesting framework for developing futures strategies that can profitably pass the Topstep Trading Combine.

Most backtesters answer "would this have made money". That is the wrong question for a funded-account evaluation, where a strategy with a positive expectancy still fails if it draws down 2,001 dollars on the wrong afternoon, or banks 60% of its profit on one day. This one answers the question the evaluation actually asks.


What makes it different

Backtest/live parity. A strategy is written once against structural protocols that both the deterministic SimBroker and the live AsyncTopstepClient from topstep-sdk satisfy. The same class runs in a backtest or against the real gateway, unchanged.

A first-class prop-firm rule engine. The two-state trailing Maximum Loss Limit, optional Daily Loss Limit, consistency target, position caps and 16:10 ET session flatten are enforced in real time — including intrabar forced liquidation with adverse slippage — not scored after the fact. A run that would have been closed out at 11:04 is closed out at 11:04, and everything after it never happens.

Determinism you can diff. Money is exact Decimal on the tick grid, lots are FIFO, and two runs over the same inputs produce byte-identical results. Every indicator is TA-Lib driven bar by bar, so no formula here can drift from the reference implementation.

A report you can replay. report.show() opens a self-contained HTML tearsheet — the tape with every fill marked, equity against the trailing MLL floor, every statistic with its basis stated. Pass record=True and it grows a bar-by-bar replay: step through the run and watch every decision with the broker's answer, the working orders, the indicator values and the running statistics as they accumulated — computed by the same code as the final report, so they cannot disagree with it. Recording is observation only; the results are byte-identical with it on or off.

Sessions on two switches, not one. On a 24h tape you scope each indicator's input data (use(Atr(14), session=NEW_YORK)) independently of when the strategy may trade (trade_sessions=(NEW_YORK,)). A continuous trend filter can sit beside a session-scoped volatility measure — the single "trade this session" flag most backtesters offer silently changes both, and starves the indicators it filters. Windows are defined in each region's own timezone, so daylight saving comes from the IANA database rather than a table that goes stale for three weeks every spring.


Install

pip install topstep-backtest
pip install "topstep-backtest[data]"   # adds pandas + pyarrow: DataFrame and Parquet input

Python 3.12+. TA-Lib is a core dependency and ships wheels for common platforms; on others you will need the TA-Lib C library first.

A complete backtest

from topstep_backtest import AccountSize, Backtest, SymbolStrategy
from topstep_backtest.indicators import Cross, Sma


class SmaCross(SymbolStrategy):
    def __init__(self, contract_id: str) -> None:
        super().__init__(contract_id)
        self.fast = self.use(Sma(10))  # TA-Lib SMA
        self.slow = self.use(Sma(30))
        self.cross = self.use(Cross(self.fast, self.slow))  # framework helper

    async def on_bar(self, bar) -> None:  # gated until every indicator is ready
        if self.cross.up and self.position.flat:
            await self.buy(1, stop_loss_ticks=40, take_profit_ticks=80)
        elif self.cross.down and self.position.is_long:
            await self.close()


print(Backtest(bars, SmaCross(MNQ), account=AccountSize.S50K).run())

Run this end to end →


Where to go next

  • Quickstart

    Install, run a backtest on synthetic bars, and point it at your own data. Fifteen minutes, no prior context assumed.

  • The workflow

    The stages in the order the questions become answerable — data, mechanics, attempts, distribution, guards, price — and the gate each must pass before the next number means anything.

  • Reading the report

    What every number in the output means, what basis it is on, and which ones lie to you if you read them alone — through to the tearsheet and the bar-by-bar replay. Start here if you have a result and are not sure what it says.

  • Tutorial: EMA crossover

    A worked strategy, line by line, from an empty file to a rule-checked verdict — with the reasoning behind each seam.

  • Beyond one backtest

    One backtest is one sample. Monte Carlo, walk-forward, PBO and deflated Sharpe — and what each of them can and cannot rescue.

  • Topstep rules

    The rules as implemented, each with its citation and its calibration status. Read this before trusting any verdict.

  • API reference

    Generated from the live docstrings. Every metric's basis is documented on the metric.


Read this before you trust a number

This is an unofficial simulation and is not affiliated with Topstep. Two caveats travel with every result it produces, and both are printed on the report itself rather than buried here:

The rule and fee constants are cited configuration, not calibration. They come from published documentation, not from reconciliation against a live funded account. A verdict is a diagnostic, not an authoritative pass/fail. See the rules document for what is verified and what is not.

Tier-0 fidelity means bars, not ticks. Fills are resolved along one deliberately pessimistic intrabar path rather than a real tape. That makes intrabar figures conservative estimates, not reproductions — and it means a strategy whose edge lives inside the bar cannot be evaluated here at all. Reading the report sets out where a backtest here gives a silently wrong answer, which is the section worth reading before the ones with the appealing numbers in them.