Skip to main content

Event-driven backtesting framework for Topstep Trading Combine strategies, with backtest/live parity against topstep-sdk.

Project description

topstep-backtest

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

Its defining property is backtest/live parity with the companion topstep-sdk: a strategy is written once against structural protocols that both the deterministic SimBroker and the live AsyncTopstepClient satisfy. Its core differentiator is a first-class prop-firm rule engine: the two-state trailing Maximum Loss Limit, optional Daily Loss Limit, consistency target, position caps, and session flatten are enforced in real time (including intrabar forced liquidation with adverse slippage), not scored after the fact.

class SmaCross(SymbolStrategy):
    def __init__(self, contract_id: str) -> None:
        super().__init__(contract_id)
        self.fast = self.use(Sma(20))  # TA-Lib, driven bar by bar, causal by construction
        self.slow = self.use(Sma(50))
        self.cross = self.use(Cross(self.fast, self.slow))

    async def on_bar(self, bar: Bar) -> None:  # gated until every use()d indicator is ready
        if self.cross.up and self.position.flat:
            await self.buy(  # sugar over ctx.orders — the topstep-sdk surface
                2,
                stop_loss_ticks=40,
                take_profit_ticks=80,  # signed-tick OCO bracket
            )

Install

pip install topstep-backtest
pip install "topstep-backtest[data]"   # adds pandas, for DataFrame input

Requires 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.

from datetime import date
from decimal import Decimal

from topstep_backtest import AccountSize, Backtest, SymbolStrategy
from topstep_backtest.core.instruments import spec_for_symbol
from topstep_backtest.data.synthetic import synthetic_bars
from topstep_backtest.indicators import Cross, Sma

MNQ = "CON.F.US.MNQ.U26"


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

    async def on_bar(self, bar) -> None:
        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()


bars = synthetic_bars(
    contract_id=MNQ,
    spec=spec_for_symbol("MNQ"),
    start_day=date(2026, 5, 4),
    days=5,
    seed=7,
    start_price=Decimal("23000.00"),
    bars_per_day=120,
    vol_ticks=12,
)
print(Backtest(bars, SmaCross(MNQ), account=AccountSize.S50K).run())

Feeding your own data (e.g. normalized Databento candles):

from topstep_backtest.data.wrangler import bars_from_dataframe

bars = bars_from_dataframe(
    df,
    contract_id="CON.F.US.MNQ.U26",
    spec=spec,
    unit=AggregateBarUnit.MINUTE,
    unit_number=1,
    stamp="open",
)  # declare what your timestamps mean!

bars_from_records is the same loader without the pandas dependency.

Status and limitations

Pre-alpha (0.1.0). The engine core is well covered — exact-Decimal money on the tick grid, FIFO lot accounting, a structurally enforced no-look-ahead firewall, and byte-identical reruns — but read these before trusting a number:

  • The rule and fee constants are NOT calibrated against a live account. They are researched, source-cited config (docs/topstep-rules.md §9 has 8 unchecked boxes). Treat a PASSED/FAILED verdict as a diagnostic, not an answer, and distrust any result landing within a tick or a fee of a limit.
  • No exchange holiday calendar ships with this package. Bars on market holidays and past early-close halts are not detected, flagged or filtered anywhere — filter them upstream. (A built-in calendar was removed in 0.1.0: it disagreed with CME on several dates a year and silently discarded tradable sessions, which is worse than not having one.)
  • One contract per run, inside a single front-month window. There is no continuous-contract stitching, and no roll detector. A multi-month export spanning a quarterly roll is silently merged into one price series.
  • Tier-0 bar fills only. Market orders fill at the next bar's open and default to zero slippage, so a strategy whose edge is thinner than roughly 8–10 ticks per round turn is inside the model's error bars. Event windows (08:30 ET releases and the like) are not honestly modelled at bar resolution.
  • Untested end to end: multi-symbol runs, dll_enabled=True, and non-quarter-tick products (CL, GC).

What is proven is structural parity — pyright-strict conformance plus a signature-diff test against the live SDK. The behavioural gate (an intent-sequence test against a recording live broker) is not built yet; see docs/ROADMAP.md.

What works today

  • Rule kernel (rules/) — the canonical Topstep Combine rulebook: EOD-ratcheting trailing MLL with permanent lock at the starting balance, real-time breach on realized+unrealized equity, optional DLL (flatten-and-lock, not a fail), consistency (best_day ≤ 50% × total), fixed 5/10/15-mini position caps — golden-fixtured against the documented worked examples.
  • Deterministic engine (engine/, clock/) — single time-ordered loop, TestClock, strict ts_init ordering assertions, bit-for-bit reproducible runs.
  • SimBroker (execution/) — full order lifecycle (market/limit/stop/trailing, signed-tick OCO brackets), netting + exact-Decimal P&L, gateway-parity APIError rejections tallied on the result, intrabar fill-vs-breach resolution along one pessimistic price path, forced liquidation whose loss can exceed the floor.
  • Tier-0 fills (fills/) — pessimistic bar fills: adverse-extreme-first path, next-bar-open for close signals, gap-through at the open, ≥1-tick stop slippage, trade-through (not touch) limit fills; per-side/per-instrument Topstep fee stack, correctable via Backtest(..., fee_model=TopstepFees(overrides={...})).
  • Data layer (data/) — pandas/records → validated Bar streams (explicit open/close stamping kills the off-by-one-bar look-ahead), tick-grid validation, session/halt/weekend checks, deterministic synthetic generator.
  • Indicators (indicators/) — every indicator is TA-Lib: TalibIndicator drives 152 of its 161 functions bar-by-bar (typed Sma/Ema/Rsi/Atr/Macd/BBands/… wrappers, plus a pure-Python Cross), so no formula is re-implemented here to drift; the nine refused are the ones that could only fail silently. Streaming values are bit-identical to a batch run, and the bounded history window is a parity decision — preload history_bars bars live and the live number equals the sim number. Parity begins at warm (the window is full), not at ready (a value exists).
  • Parity seam (protocols.py) — pyright-strict conformance tests prove AsyncTopstepClient and SimBroker satisfy the same Broker protocol, plus a signature-diff test against the live SDK that fails the suite on drift.

Development

From a checkout (the sibling topstep-sdk repo is expected at ../topstep-sdk; set UV_NO_SOURCES=1 to resolve it from PyPI instead):

uv sync --extra dev
uv run pytest && uv run ruff check . && uv run ruff format --check . && uv run pyright
cd examples && uv run python run_combine.py   # end-to-end combine verdict

Documentation

The source repository is private, so there is no public issue tracker or docs site. Everything below ships inside the sdistpip download --no-binary :all: topstep-backtest and unpack it, or read the copies in your environment.

  • docs/GUIDE.md — start here: the user guide (data in, writing strategies, wiring runs, reading results, verification checklist).
  • docs/TUTORIAL_EMA_CROSSOVER.md — an in-depth walkthrough building one strategy end to end.
  • docs/STRATEGY_API.md — the strategy dialect reference, including the indicator surface.
  • docs/topstep-rules.md — the rulebook being enforced, with sources, confidence levels, and a verify-before-trusting checklist.
  • docs/DESIGN.md — architecture contract (parity seam, no-look-ahead invariants, fill tiers). Its §4 layout is the target architecture, not the current tree.
  • AGENTS.md — dense maintainer/agent reference and the real module map.
  • examples/ — runnable: run_real_data.py (your CSV/Parquet → verdict), run_combine.py (synthetic end-to-end), ema_cross.py, sma_cross.py, talib_macd.py.

Roadmap

Analytics + Monte-Carlo pass-probability → live adapter + calibration → L1/L2/MBO fill tiers. Funded-account (XFA) modeling is deliberately parked. See docs/ROADMAP.md in the sdist.

Stack

Python 3.12+ · topstep-sdk · msgspec · TA-Lib · uv · ruff · pyright (strict) · pytest + hypothesis. Canonical timezone: ET (America/New_York); internal hot path is int-ns UTC; all money is exact Decimal on the tick grid — indicator values cross from float64 into Decimal and are deliberately not tick-snapped (an indicator level is not a tradeable price).

License

MIT — see LICENSE.

Unofficial. Not affiliated with, endorsed by, or sponsored by Topstep, LLC or ProjectX Trading, LLC. "Topstep" is a trademark of its respective owner and is used here only to identify the evaluation program this tool models. Rule numbers researched July 2026 — re-verify against Topstep's help center before trusting a pass verdict (see the checklist in docs/topstep-rules.md §9).

Not financial advice. This software simulates a trading evaluation and can be wrong. You are solely responsible for any capital you risk.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

topstep_backtest-0.1.0.tar.gz (261.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

topstep_backtest-0.1.0-py3-none-any.whl (98.4 kB view details)

Uploaded Python 3

File details

Details for the file topstep_backtest-0.1.0.tar.gz.

File metadata

  • Download URL: topstep_backtest-0.1.0.tar.gz
  • Upload date:
  • Size: 261.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for topstep_backtest-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e0492d3cc2b8d38fa7d5df5b7f42f65febd0788b0e25b9f97c333251c5a24c7f
MD5 845fdb1c059ad01831535f3d80414cfa
BLAKE2b-256 ebf0294a8ee88bf00190c2de483f6d4130d06c459c809c7feb3125bf6c6cd9e2

See more details on using hashes here.

Provenance

The following attestation bundles were made for topstep_backtest-0.1.0.tar.gz:

Publisher: release.yml on tarricsookdeo/topstep-backtest

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file topstep_backtest-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for topstep_backtest-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3c4f47c65e9dfc72cbd5b5a3735f2d02b6c080f488051ceb61e9eb048d03cbef
MD5 81dca4f97ce20648732dfa378854f8d7
BLAKE2b-256 e539bda7226265e3e454a07fe45ad0447b92d73e857c776bfe6aa8319656b382

See more details on using hashes here.

Provenance

The following attestation bundles were made for topstep_backtest-0.1.0-py3-none-any.whl:

Publisher: release.yml on tarricsookdeo/topstep-backtest

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page