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/FAILEDverdict 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, strictts_initordering 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-parityAPIErrorrejections 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 viaBacktest(..., fee_model=TopstepFees(overrides={...})). - Data layer (
data/) — pandas/records → validatedBarstreams (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:TalibIndicatordrives 152 of its 161 functions bar-by-bar (typedSma/Ema/Rsi/Atr/Macd/BBands/… wrappers, plus a pure-PythonCross), 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 — preloadhistory_barsbars live and the live number equals the sim number. Parity begins atwarm(the window is full), not atready(a value exists). - Parity seam (
protocols.py) — pyright-strict conformance tests proveAsyncTopstepClientandSimBrokersatisfy the sameBrokerprotocol, 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 sdist — pip 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e0492d3cc2b8d38fa7d5df5b7f42f65febd0788b0e25b9f97c333251c5a24c7f
|
|
| MD5 |
845fdb1c059ad01831535f3d80414cfa
|
|
| BLAKE2b-256 |
ebf0294a8ee88bf00190c2de483f6d4130d06c459c809c7feb3125bf6c6cd9e2
|
Provenance
The following attestation bundles were made for topstep_backtest-0.1.0.tar.gz:
Publisher:
release.yml on tarricsookdeo/topstep-backtest
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
topstep_backtest-0.1.0.tar.gz -
Subject digest:
e0492d3cc2b8d38fa7d5df5b7f42f65febd0788b0e25b9f97c333251c5a24c7f - Sigstore transparency entry: 2276506148
- Sigstore integration time:
-
Permalink:
tarricsookdeo/topstep-backtest@0c3643cbff4cc71569f4ec0e022d62aabfb9437b -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/tarricsookdeo
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0c3643cbff4cc71569f4ec0e022d62aabfb9437b -
Trigger Event:
push
-
Statement type:
File details
Details for the file topstep_backtest-0.1.0-py3-none-any.whl.
File metadata
- Download URL: topstep_backtest-0.1.0-py3-none-any.whl
- Upload date:
- Size: 98.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3c4f47c65e9dfc72cbd5b5a3735f2d02b6c080f488051ceb61e9eb048d03cbef
|
|
| MD5 |
81dca4f97ce20648732dfa378854f8d7
|
|
| BLAKE2b-256 |
e539bda7226265e3e454a07fe45ad0447b92d73e857c776bfe6aa8319656b382
|
Provenance
The following attestation bundles were made for topstep_backtest-0.1.0-py3-none-any.whl:
Publisher:
release.yml on tarricsookdeo/topstep-backtest
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
topstep_backtest-0.1.0-py3-none-any.whl -
Subject digest:
3c4f47c65e9dfc72cbd5b5a3735f2d02b6c080f488051ceb61e9eb048d03cbef - Sigstore transparency entry: 2276506451
- Sigstore integration time:
-
Permalink:
tarricsookdeo/topstep-backtest@0c3643cbff4cc71569f4ec0e022d62aabfb9437b -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/tarricsookdeo
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0c3643cbff4cc71569f4ec0e022d62aabfb9437b -
Trigger Event:
push
-
Statement type: