topstep-backtest 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/AGENTS.md +4 -3
  2. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/CHANGELOG.md +24 -0
  3. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/PKG-INFO +35 -14
  4. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/README.md +34 -13
  5. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/docs/DESIGN.md +1 -1
  6. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/docs/TUTORIAL_EMA_CROSSOVER.md +2 -2
  7. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/.gitignore +0 -0
  8. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/LICENSE +0 -0
  9. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/data/sample_mnq_1m.csv +0 -0
  10. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/docs/INDICATORS.md +0 -0
  11. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/docs/ROADMAP.md +0 -0
  12. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/docs/topstep-rules.md +0 -0
  13. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/examples/ema_cross.py +0 -0
  14. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/examples/hand_wired.py +0 -0
  15. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/examples/run_combine.py +0 -0
  16. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/examples/run_montecarlo.py +0 -0
  17. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/examples/run_real_data.py +0 -0
  18. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/examples/run_tearsheet.py +0 -0
  19. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/examples/run_windows.py +0 -0
  20. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/examples/sma_cross.py +0 -0
  21. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/examples/talib_macd.py +0 -0
  22. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/pyproject.toml +0 -0
  23. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/__init__.py +0 -0
  24. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/_render.py +0 -0
  25. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/clock/__init__.py +0 -0
  26. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/clock/live_clock.py +0 -0
  27. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/clock/test_clock.py +0 -0
  28. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/core/__init__.py +0 -0
  29. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/core/ids.py +0 -0
  30. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/core/instruments.py +0 -0
  31. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/core/money.py +0 -0
  32. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/core/time.py +0 -0
  33. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/data/__init__.py +0 -0
  34. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/data/clean.py +0 -0
  35. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/data/continuous.py +0 -0
  36. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/data/feed.py +0 -0
  37. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/data/synthetic.py +0 -0
  38. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/data/validator.py +0 -0
  39. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/data/wrangler.py +0 -0
  40. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/engine/__init__.py +0 -0
  41. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/engine/backtest.py +0 -0
  42. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/execution/__init__.py +0 -0
  43. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/execution/rejections.py +0 -0
  44. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/execution/sim_broker.py +0 -0
  45. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/fills/__init__.py +0 -0
  46. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/fills/bar_fill.py +0 -0
  47. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/fills/fees.py +0 -0
  48. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/fills/path.py +0 -0
  49. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/harness.py +0 -0
  50. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/indicators/__init__.py +0 -0
  51. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/indicators/base.py +0 -0
  52. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/indicators/library.py +0 -0
  53. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/indicators/talib_adapter.py +0 -0
  54. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/metrics/__init__.py +0 -0
  55. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/metrics/economics.py +0 -0
  56. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/metrics/montecarlo.py +0 -0
  57. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/metrics/overfitting.py +0 -0
  58. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/metrics/stats.py +0 -0
  59. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/metrics/walkforward.py +0 -0
  60. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/metrics/windows.py +0 -0
  61. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/protocols.py +0 -0
  62. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/py.typed +0 -0
  63. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/rules/__init__.py +0 -0
  64. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/rules/kernel.py +0 -0
  65. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/rules/params.py +0 -0
  66. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/strategy/__init__.py +0 -0
  67. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/strategy/base.py +0 -0
  68. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/strategy/symbol.py +0 -0
  69. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/strategy/tracker.py +0 -0
  70. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/tearsheet/__init__.py +0 -0
  71. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/tearsheet/_assets/lightweight-charts.LICENSE +0 -0
  72. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/tearsheet/_assets/lightweight-charts.standalone.production.js +0 -0
  73. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/tearsheet/_assets/tearsheet.css +0 -0
  74. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/src/topstep_backtest/tearsheet/_assets/tearsheet.js +0 -0
  75. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/__init__.py +0 -0
  76. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/conftest.py +0 -0
  77. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/golden/__init__.py +0 -0
  78. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/golden/artifacts/verdict_failed_mll_s50k.json +0 -0
  79. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/golden/artifacts/verdict_passed_s50k.json +0 -0
  80. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/golden/test_combine_kernel.py +0 -0
  81. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/golden/test_facade_equivalence.py +0 -0
  82. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/golden/test_sugar_equivalence.py +0 -0
  83. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/golden/test_verdict_goldens.py +0 -0
  84. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/parity/__init__.py +0 -0
  85. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/parity/test_broker_conformance.py +0 -0
  86. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/property/__init__.py +0 -0
  87. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/property/test_indicator_props.py +0 -0
  88. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/property/test_kernel_props.py +0 -0
  89. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/property/test_money_props.py +0 -0
  90. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/__init__.py +0 -0
  91. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_bar_fill.py +0 -0
  92. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_clean.py +0 -0
  93. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_clock.py +0 -0
  94. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_continuous.py +0 -0
  95. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_data_feed.py +0 -0
  96. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_economics.py +0 -0
  97. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_engine.py +0 -0
  98. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_fees.py +0 -0
  99. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_harness.py +0 -0
  100. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_indicators.py +0 -0
  101. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_instruments.py +0 -0
  102. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_montecarlo.py +0 -0
  103. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_overfitting.py +0 -0
  104. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_path.py +0 -0
  105. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_sim_broker.py +0 -0
  106. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_stats.py +0 -0
  107. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_symbol_strategy.py +0 -0
  108. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_synthetic.py +0 -0
  109. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_talib_adapter_hardening.py +0 -0
  110. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_tearsheet.py +0 -0
  111. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_time.py +0 -0
  112. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_tracker.py +0 -0
  113. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_validator.py +0 -0
  114. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_walkforward.py +0 -0
  115. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_windows.py +0 -0
  116. {topstep_backtest-0.2.0 → topstep_backtest-0.2.2}/tests/unit/test_wrangler.py +0 -0
@@ -729,7 +729,7 @@ order placement — the SDK is async and the `await`s ARE the live contract. Tra
729
729
  | `self.buy(size=2, sl=…, tp=…)` | `await self.buy(2, stop_loss_ticks=40, take_profit_ticks=80)` |
730
730
  | `stats = bt.run()` → 30-key Series | `report = bt.run()` → `Report` + `.stats`/`.result`/`.trades` |
731
731
  | `optimize()` | `metrics.optimize(...)` — but read §5.9 first; it keeps every trial on purpose |
732
- | `plot()` | `report.show()` — interactive HTML tearsheet (or `report.to_html(path)` to name the file) |
732
+ | `plot()` | `report.show()` — interactive HTML tearsheet (`report.to_html(path)` to name the file; `bt.run_with_tearsheet(dir)` to get one from every run) |
733
733
 
734
734
  ## 8. Knobs — the calibration seam
735
735
 
@@ -816,8 +816,9 @@ breakdowns, higher fill tiers, an XFA rule set, and any holiday calendar.
816
816
  **Do** write code against these — they used to be on the list above and now ship: Monte-Carlo
817
817
  (§5.7), `optimize()` and the overfitting guards (§5.9), the sequential-Combine sweep (§5.8),
818
818
  the continuous-contract stitcher (§5.10), and the HTML tearsheet (`report.show()` /
819
- `report.to_html(path)`; `print(report)` remains the text render). Their signatures are in the
820
- generated table in §3.
819
+ `report.to_html(path)` / `Backtest.run_with_tearsheet(dir)`, which writes a timestamped file
820
+ per run; `print(report)` remains the text render). Their signatures are in the generated
821
+ table in §3.
821
822
 
822
823
  Parity status, precisely: **structural** conformance is proven — a pyright-strict protocol
823
824
  conformance test plus a signature diff of `SimOrderApi.place` against the SDK's
@@ -6,6 +6,30 @@ below 1.0, minor releases may contain breaking changes.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.1] — 2026-08-16
10
+
11
+ ### Fixed
12
+
13
+ - **The README claimed shipped subsystems do not exist.** Its status section told readers
14
+ there is "no walk-forward, no PBO/DSR overfitting guard, and no EV-per-attempt model" —
15
+ all three have shipped since 0.2.0 (`metrics/walkforward.py`, `metrics/overfitting.py`,
16
+ `metrics/economics.py`), and `docs/ROADMAP.md` and the site documented them correctly the
17
+ whole time. This is the rot the 0.2.0 doc pass fixed everywhere except the one file a new
18
+ reader opens first, and it was the package's PyPI project description. The bullet now keeps
19
+ the limitation that is still true — every analytic resamples the tape you supplied, so none
20
+ of them invents a regime your data never contained — without the false premise. The "Next:"
21
+ pointer drops the analytics that shipped and names what the roadmap actually lists as
22
+ remaining.
23
+ - **The HTML tearsheet was missing from the README and from the site's "Reading the report"
24
+ page** — the headline feature of 0.2.0, absent from both places a reader looks for it. Both
25
+ now cover it, including `Backtest.run_with_tearsheet(dir)`, which was documented only in the
26
+ changelog and `examples/run_tearsheet.py`. `AGENTS.md` §7 and §11 pick it up too, and the
27
+ examples list in the README no longer omits `run_tearsheet.py`.
28
+ - Version strings that describe the CURRENT state (README status, `docs/DESIGN.md`'s "proven
29
+ today", the captured provenance lines in the tutorial and `website/results.md`) said 0.1.0.
30
+ The 0.1.0 references that are historical — when the exchange calendar was removed, and why —
31
+ are left alone deliberately.
32
+
9
33
  ## [0.2.0] — 2026-08-16
10
34
 
11
35
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: topstep-backtest
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Event-driven backtesting framework for Topstep Trading Combine strategies, with backtest/live parity against topstep-sdk.
5
5
  Author-email: Tarric Sookdeo <tarricsookdeo@outlook.com>
6
6
  License-Expression: MIT
@@ -160,6 +160,21 @@ silently is how a report lies:
160
160
  how to read every figure above. The same drawdown at 5% and at 95% exposure are not the same
161
161
  risk. Alongside `equity_peak` (what a trailing floor anchors to) and the run's window.
162
162
 
163
+ The same report renders as an **interactive HTML tearsheet** — one self-contained file with
164
+ no server and no network, so it opens offline and archives next to the run:
165
+
166
+ ```python
167
+ report.to_html("tearsheet.html") # name the file
168
+ report.show() # or write a temp file and open a browser
169
+ ```
170
+
171
+ It draws the candlestick tape with every fill marked, the equity curve against the trailing
172
+ MLL floor, daily P&L and the R-multiple distribution, plus every statistic the text render
173
+ prints, basis labels included — the two renders share their formatting helpers, so they
174
+ cannot disagree. For a sheet from *every* run without naming a file each time,
175
+ `Backtest(...).run_with_tearsheet("runs")` writes `tearsheet-<UTC stamp>.html` and returns
176
+ both the report and the path.
177
+
163
178
  Then stop trusting one sample:
164
179
 
165
180
  ```python
@@ -194,7 +209,7 @@ uv run python examples/run_montecarlo.py # the same edge at two sizes, failing
194
209
 
195
210
  ## Status and limitations
196
211
 
197
- **Pre-alpha (0.1.0).** The engine core is well covered — exact-Decimal money on the tick
212
+ **Pre-alpha (0.2.0).** The engine core is well covered — exact-Decimal money on the tick
198
213
  grid, FIFO lot accounting, a structurally enforced no-look-ahead firewall, byte-identical
199
214
  reruns — but read these before trusting a number:
200
215
 
@@ -205,10 +220,11 @@ reruns — but read these before trusting a number:
205
220
  landing within a tick or a fee of a limit. This applies with *more* force to the
206
221
  Monte-Carlo pass probability: a figure printed to one decimal from unverified constants is
207
222
  precise, not accurate.
208
- - **Analytics are single-run plus resampling.** There is no walk-forward, no PBO/DSR
209
- overfitting guard, and no EV-per-attempt model. The Monte-Carlo resamples a strategy's own
210
- observed days, so it cannot invent a market regime your tape never contained and will
211
- understate tail risk on a short or single-regime sample.
223
+ - **Analytics can only resample the tape you gave them.** Walk-forward, PBO, deflated Sharpe
224
+ and the EV-per-attempt model all ship (`metrics/walkforward.py`, `metrics/overfitting.py`,
225
+ `metrics/economics.py`), but none of them escapes your sample: the Monte-Carlo resamples a
226
+ strategy's own observed days, so it cannot invent a market regime your tape never contained
227
+ and will understate tail risk on a short or single-regime sample.
212
228
  - **No exchange holiday calendar ships with this package.** Bars on market holidays and
213
229
  past early-close halts are not detected, flagged or filtered anywhere — filter them
214
230
  upstream. (A built-in calendar was removed in 0.1.0: it disagreed with CME on several
@@ -252,8 +268,13 @@ built wheel into a clean venv and smoke-tests it. **`ruff format --check` is par
252
268
 
253
269
  There is a full documentation site — a browsable version of everything below, plus a
254
270
  quickstart, a page on reading the report, and an API reference generated from the live
255
- docstrings. The source repository is private, so it is **not hosted publicly**; build and
256
- read it locally from a checkout:
271
+ docstrings:
272
+
273
+ **<https://tarricsookdeo.github.io/topstep-backtest/>**
274
+
275
+ It is rebuilt from `main` on every push. The site is public; the **source repository is
276
+ not**, so there are no "Edit this page" links and there is no public issue tracker. To read
277
+ it offline, or to preview a change before pushing it:
257
278
 
258
279
  ```bash
259
280
  uv run --extra docs mkdocs serve # http://127.0.0.1:8000
@@ -275,13 +296,13 @@ environment. There is no public issue tracker.
275
296
  and a verify-before-trusting checklist.
276
297
  - `docs/DESIGN.md` — architecture contract, for *modifying* the framework.
277
298
  - `docs/ROADMAP.md` — what is built, partial, and not started. Next: **live adapter +
278
- calibration** → remaining analytics (EV per attempt, PBO/DSR, walk-forward) → L1/L2/MBO
279
- fill tiers; funded-account (XFA) modeling is deliberately parked.
299
+ calibration** → per-year / per-regime breakdowns → L1/L2/MBO fill tiers; funded-account
300
+ (XFA) modeling is deliberately parked.
280
301
  - `examples/` — runnable: `run_real_data.py` (your CSV/Parquet → verdict), `run_combine.py`
281
- (synthetic end to end), `run_montecarlo.py` (outcome distribution + autopsy),
282
- `run_windows.py` (a long tape replayed as consecutive independent Combine attempts),
283
- `ema_cross.py`, `sma_cross.py`, `talib_macd.py`, `hand_wired.py` (what the facade
284
- assembles).
302
+ (synthetic end to end), `run_tearsheet.py` (the same run as one HTML file),
303
+ `run_montecarlo.py` (outcome distribution + autopsy), `run_windows.py` (a long tape
304
+ replayed as consecutive independent Combine attempts), `ema_cross.py`, `sma_cross.py`,
305
+ `talib_macd.py`, `hand_wired.py` (what the facade assembles).
285
306
 
286
307
  ## Stack
287
308
 
@@ -120,6 +120,21 @@ silently is how a report lies:
120
120
  how to read every figure above. The same drawdown at 5% and at 95% exposure are not the same
121
121
  risk. Alongside `equity_peak` (what a trailing floor anchors to) and the run's window.
122
122
 
123
+ The same report renders as an **interactive HTML tearsheet** — one self-contained file with
124
+ no server and no network, so it opens offline and archives next to the run:
125
+
126
+ ```python
127
+ report.to_html("tearsheet.html") # name the file
128
+ report.show() # or write a temp file and open a browser
129
+ ```
130
+
131
+ It draws the candlestick tape with every fill marked, the equity curve against the trailing
132
+ MLL floor, daily P&L and the R-multiple distribution, plus every statistic the text render
133
+ prints, basis labels included — the two renders share their formatting helpers, so they
134
+ cannot disagree. For a sheet from *every* run without naming a file each time,
135
+ `Backtest(...).run_with_tearsheet("runs")` writes `tearsheet-<UTC stamp>.html` and returns
136
+ both the report and the path.
137
+
123
138
  Then stop trusting one sample:
124
139
 
125
140
  ```python
@@ -154,7 +169,7 @@ uv run python examples/run_montecarlo.py # the same edge at two sizes, failing
154
169
 
155
170
  ## Status and limitations
156
171
 
157
- **Pre-alpha (0.1.0).** The engine core is well covered — exact-Decimal money on the tick
172
+ **Pre-alpha (0.2.0).** The engine core is well covered — exact-Decimal money on the tick
158
173
  grid, FIFO lot accounting, a structurally enforced no-look-ahead firewall, byte-identical
159
174
  reruns — but read these before trusting a number:
160
175
 
@@ -165,10 +180,11 @@ reruns — but read these before trusting a number:
165
180
  landing within a tick or a fee of a limit. This applies with *more* force to the
166
181
  Monte-Carlo pass probability: a figure printed to one decimal from unverified constants is
167
182
  precise, not accurate.
168
- - **Analytics are single-run plus resampling.** There is no walk-forward, no PBO/DSR
169
- overfitting guard, and no EV-per-attempt model. The Monte-Carlo resamples a strategy's own
170
- observed days, so it cannot invent a market regime your tape never contained and will
171
- understate tail risk on a short or single-regime sample.
183
+ - **Analytics can only resample the tape you gave them.** Walk-forward, PBO, deflated Sharpe
184
+ and the EV-per-attempt model all ship (`metrics/walkforward.py`, `metrics/overfitting.py`,
185
+ `metrics/economics.py`), but none of them escapes your sample: the Monte-Carlo resamples a
186
+ strategy's own observed days, so it cannot invent a market regime your tape never contained
187
+ and will understate tail risk on a short or single-regime sample.
172
188
  - **No exchange holiday calendar ships with this package.** Bars on market holidays and
173
189
  past early-close halts are not detected, flagged or filtered anywhere — filter them
174
190
  upstream. (A built-in calendar was removed in 0.1.0: it disagreed with CME on several
@@ -212,8 +228,13 @@ built wheel into a clean venv and smoke-tests it. **`ruff format --check` is par
212
228
 
213
229
  There is a full documentation site — a browsable version of everything below, plus a
214
230
  quickstart, a page on reading the report, and an API reference generated from the live
215
- docstrings. The source repository is private, so it is **not hosted publicly**; build and
216
- read it locally from a checkout:
231
+ docstrings:
232
+
233
+ **<https://tarricsookdeo.github.io/topstep-backtest/>**
234
+
235
+ It is rebuilt from `main` on every push. The site is public; the **source repository is
236
+ not**, so there are no "Edit this page" links and there is no public issue tracker. To read
237
+ it offline, or to preview a change before pushing it:
217
238
 
218
239
  ```bash
219
240
  uv run --extra docs mkdocs serve # http://127.0.0.1:8000
@@ -235,13 +256,13 @@ environment. There is no public issue tracker.
235
256
  and a verify-before-trusting checklist.
236
257
  - `docs/DESIGN.md` — architecture contract, for *modifying* the framework.
237
258
  - `docs/ROADMAP.md` — what is built, partial, and not started. Next: **live adapter +
238
- calibration** → remaining analytics (EV per attempt, PBO/DSR, walk-forward) → L1/L2/MBO
239
- fill tiers; funded-account (XFA) modeling is deliberately parked.
259
+ calibration** → per-year / per-regime breakdowns → L1/L2/MBO fill tiers; funded-account
260
+ (XFA) modeling is deliberately parked.
240
261
  - `examples/` — runnable: `run_real_data.py` (your CSV/Parquet → verdict), `run_combine.py`
241
- (synthetic end to end), `run_montecarlo.py` (outcome distribution + autopsy),
242
- `run_windows.py` (a long tape replayed as consecutive independent Combine attempts),
243
- `ema_cross.py`, `sma_cross.py`, `talib_macd.py`, `hand_wired.py` (what the facade
244
- assembles).
262
+ (synthetic end to end), `run_tearsheet.py` (the same run as one HTML file),
263
+ `run_montecarlo.py` (outcome distribution + autopsy), `run_windows.py` (a long tape
264
+ replayed as consecutive independent Combine attempts), `ema_cross.py`, `sma_cross.py`,
265
+ `talib_macd.py`, `hand_wired.py` (what the facade assembles).
245
266
 
246
267
  ## Stack
247
268
 
@@ -258,7 +258,7 @@ Violations are bugs, not style. Most are pinned by a named test.
258
258
  OHLCV bars are not the tape-built bars live produces. Require **Tier-1+ (quote/tick)
259
259
  validation** for any strategy whose edge is intrabar-timing-sensitive before trusting it live.
260
260
 
261
- > **What is actually proven today (0.1.0).** Rungs 1–4 hold: the sim constructs real SDK models,
261
+ > **What is actually proven today (0.2.0).** Rungs 1–4 hold: the sim constructs real SDK models,
262
262
  > and `tests/parity/` asserts — pyright-strict *and* at runtime — that both `AsyncTopstepClient`
263
263
  > and `SimBroker` satisfy `Broker`, with a keyword-level signature diff on `place`. That is
264
264
  > **structural** conformance: same call surface, same types, same `APIError` codes. Rung 5 is
@@ -655,7 +655,7 @@ daily P&L (6 closed: 1 win / 4 lose / 1 flat)
655
655
  best +84.04 (consistency ratio undefined: no net profit to share)
656
656
  stdev 40.27 <- dollars, so it compares directly against the DLL
657
657
 
658
- topstep-backtest 0.1.0 — unofficial simulation, not affiliated with Topstep. Rule and fee constants are cited config, NOT calibrated against a live account (docs/topstep-rules.md §9): treat the verdict as a diagnostic, not an authoritative pass/fail.
658
+ topstep-backtest 0.2.0 — unofficial simulation, not affiliated with Topstep. Rule and fee constants are cited config, NOT calibrated against a live account (docs/topstep-rules.md §9): treat the verdict as a diagnostic, not an authoritative pass/fail.
659
659
  ```
660
660
 
661
661
  Line by line:
@@ -681,7 +681,7 @@ Line by line:
681
681
  the rule and fee constants are not calibrated, so a screenshotted verdict
682
682
  carries its own caveat. The version is read from the installed distribution:
683
683
  this is the one line above that legitimately differs from your run (a source
684
- checkout reports a `.devN` suffix, not `0.1.0`).
684
+ checkout reports a `.devN` suffix, not `0.2.0`).
685
685
  - **`REJECTED`** — *absent here, and its absence is information.* When the
686
686
  broker refuses any order placement the render grows a `REJECTED` line above
687
687
  the day trail, counting the refusals per gateway error code. Without it a