Metadata-Version: 2.4
Name: topstep-backtest
Version: 0.1.0
Summary: Event-driven backtesting framework for Topstep Trading Combine strategies, with backtest/live parity against topstep-sdk.
Author-email: Tarric Sookdeo <tarricsookdeo@outlook.com>
License-Expression: MIT
License-File: LICENSE
Keywords: backtesting,combine,futures,prop-firm,topstep,trading
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: msgspec<1.0,>=0.18
Requires-Dist: ta-lib<0.8,>=0.7.1
Requires-Dist: topstep-sdk<0.2,>=0.1.2
Requires-Dist: tzdata>=2024.1; sys_platform == 'win32'
Provides-Extra: data
Requires-Dist: pandas>=2.2; extra == 'data'
Provides-Extra: dev
Requires-Dist: hypothesis>=6.100; extra == 'dev'
Requires-Dist: pandas>=2.2; extra == 'dev'
Requires-Dist: pyright>=1.1.380; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff<0.17,>=0.16; extra == 'dev'
Description-Content-Type: text/markdown

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

```python
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

```bash
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.

```python
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):

```python
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):

```bash
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.
