release: v0.19.0

This commit is contained in:
github-actions[bot]
2026-08-23 13:31:37 +00:00
parent a5f51e2fde
commit 44f8ed1a91
43 changed files with 2666 additions and 205 deletions
+33 -89
View File
@@ -8,14 +8,7 @@
</p>
<p align="center">
<a href="https://discord.gg/bvU6Wjc72d"><img src="https://img.shields.io/badge/Discord-Join%20the%20community-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Join the ManifoldBT Discord" height="34"></a>
</p>
<p align="center">
<a href="https://pypi.org/project/manifoldbt/"><img src="https://img.shields.io/pypi/v/manifoldbt?logo=pypi&logoColor=white&color=2f6fed" alt="PyPI"></a>
<img src="https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white" alt="Python 3.9+">
<img src="https://img.shields.io/badge/core-Rust-dea584?logo=rust&logoColor=white" alt="Rust core">
<a href="https://github.com/manifoldbt/manifoldbt/actions/workflows/bench-vs-vectorbt.yml"><img src="https://img.shields.io/badge/benchmarks-public%20CI-2ea44f?logo=githubactions&logoColor=white" alt="Benchmarks in public CI"></a>
<a href="https://discord.gg/bvU6Wjc72d"><img src="https://img.shields.io/badge/Discord-join%20the%20community-5865F2?logo=discord&logoColor=white" alt="Discord"></a>
</p>
<p align="center">
@@ -33,7 +26,7 @@ sequential fill simulation with realistic fees, slippage, funding and look-ahead
## Why ManifoldBT
- **Fast**: 10M bars in 329 ms. 79x faster than vectorbt, and 311x once you also want drawdown and Sharpe. [Measured in public CI](#performance), every run linked.
- **Fast**: 10M bars in 317 ms. 78x faster than vectorbt, 308x once you also want drawdown and Sharpe, ~3,500x faster than backtrader. [Measured in public CI](#performance), every run linked.
- **Expressive**: fluent DSL with 30+ indicators, conditional logic, cross-asset references
- **Rigorous**: Monte Carlo, walk-forward, parameter sweeps, lookahead detection, exposure diagnostics
- **Portable**: `pip install`, no Rust toolchain needed. Works on Python 3.9+.
@@ -107,13 +100,26 @@ store = mbt.import_csv("EURUSD_1m.csv", symbol="EURUSD", symbol_id=1,
interval="1m", asset_class="forex")
```
**Exchange connectors**: Binance, Bybit, Hyperliquid, dYdX, Bitstamp (free); Databento, Massive (Pro):
**Market data connectors**: Binance, Bybit, Hyperliquid, dYdX, Bitstamp, Yahoo Finance (free);
Databento, Massive (Pro):
```python
store = mbt.ingest(provider="binance", symbol="BTCUSDT", symbol_id=1,
start="2024-01-01T00:00:00Z", end="2025-01-01T00:00:00Z")
```
Yahoo Finance covers stocks, ETFs, indices (`^GSPC`), FX (`EURUSD=X`), futures
(`ES=F`) and crypto (`BTC-USD`) without an API key. Prices are dividend-adjusted,
like `yfinance`'s `auto_adjust=True`; pass `dataset="raw"` for unadjusted quotes.
Yahoo's own history limits apply: 1m over the last 30 days, 1h over ~2 years,
daily back to the listing date.
```python
store = mbt.ingest(provider="yahoo", symbol="AAPL", symbol_id=1, interval="1d",
asset_class="equity",
start="2015-01-01T00:00:00Z", end="2026-01-01T00:00:00Z")
```
Or from the CLI:
```bash
@@ -121,59 +127,6 @@ manifoldbt import-csv data.csv --symbol EURUSD --symbol-id 1 --interval 1m
manifoldbt ingest --provider binance --symbol BTCUSDT --symbol-id 1 --start ... --end ...
```
## Higher timeframes
Declare the timeframes you want alongside the simulation one, then read them
with `mbt.tf(...)`. Columns are forward-filled onto the simulation grid, and a
bar's value only becomes readable once that bar has closed, so there is no
look-ahead.
```python
config = mbt.BacktestConfig(
...,
bar_interval=Interval.minutes(1), # simulate on 1m
extra_timeframes={"1h": Interval.hours(1)}, # also resample to 1h
)
h1 = mbt.tf("1h")
h1.close # the last closed hourly close, held across the minute bars
```
For an **indicator** on a higher timeframe, use `.apply(...)`. It evaluates the
expression on that timeframe's own grid, so the period counts in *its* bars:
```python
from manifoldbt.indicators import close, sma
band = mbt.tf("1h").apply(sma(close, 20)) # mean of 20 HOURLY closes
```
> Careful: `sma(mbt.tf("1h").close, 20)` is **not** the same thing. That reads
> the step-held hourly series on the simulation grid, so the period counts in
> simulation bars: on a 1m simulation it is a 20-*minute* smoothing of an hourly
> staircase. Use `.apply(...)` whenever you want an indicator *of* the higher
> timeframe.
## Sweeping a choice, not just a number
`mbt.param(...)` sweeps numbers. `mbt.choice(...)` sweeps *expressions*: the
selector becomes a grid axis, and each combination resolves to its branch before
the simulation runs, so the branches it did not pick cost nothing.
```python
band = mbt.choice("band", {
"30m": mbt.tf("30m").apply(sma(close, mbt.param("len"))),
"1h": mbt.tf("1h").apply(sma(close, mbt.param("len"))),
"2h": mbt.tf("2h").apply(sma(close, mbt.param("len"))),
})
sweep = mbt.run_sweep(strategy, {"band": ["30m", "1h", "2h"],
"len": range(10, 210, 10)}, config, store)
```
The branches can hold any expression, so the same mechanism sweeps which
exogenous column to use, which asset to reference, or which indicator to apply.
## Examples
| # | Example | What it shows |
@@ -206,30 +159,26 @@ engine from PyPI the way a user would, generates its own data, checks that the
engines produced the **same result**, and only then reports how long each took:
a workload they disagree on gets no published timing at all.
**Latest run: [#13](https://github.com/manifoldbt/manifoldbt/actions/runs/32469701489)**
ran on Linux x86_64, 4 vCPU (AMD EPYC 7763), Python 3.12, manifoldbt 0.18.0 /
vectorbt 0.28.4 / raptorbt 0.9.0, 3 interleaved repetitions, medians reported.
**Latest run: [#11](https://github.com/manifoldbt/manifoldbt/actions/runs/32396472073)**
ran on Linux x86_64, 4 vCPU, Python 3.12, manifoldbt 0.17.3 / vectorbt 0.28.4 /
raptorbt 0.9.0, 3 interleaved repetitions.
| Workload | Bars | ManifoldBT | vectorbt | raptorbt |
|---|---:|---:|---:|---:|
| SMA crossover | 10M | **327 ms** | 26.12 s (x79) | 913 ms (x2.8) |
| ...with drawdown, Sharpe, Sortino, volatility | 10M | **329 ms** | 102.38 s (**x311**) | 909 ms (x2.8) |
| ...with a 5 bps fee and 2 bps slippage | 10M | **337 ms** | 26.08 s (x79) | not supported |
| EMA + RSI filter, 5 bps fee | 1M | **57 ms** | 2.35 s (x40) | not supported |
| Five assets in one book | 1M | **148 ms** | 2.54 s (x17) | not supported |
| Stop-loss and take-profit bracket | 10M | **934 ms** | 26.24 s (x28) | 916 ms (**x1.0**) |
| SMA crossover | 10M | **317 ms** | 24.75 s (x78) | 878 ms (x2.8) |
| ...with drawdown, Sharpe, Sortino, volatility | 10M | **317 ms** | 97.46 s (**x308**) | 894 ms (x2.8) |
| ...with a 5 bps fee and 2 bps slippage | 10M | **316 ms** | 24.53 s (x78) | not supported |
| EMA + RSI filter, 5 bps fee | 1M | **52 ms** | 2.21 s (x41) | not supported |
| Five assets in one book | 1M | **140 ms** | 2.34 s (x17) | not supported |
The second row is the one worth reading twice. Asking for a performance summary
costs ManifoldBT nothing measurable, because it computes one during the run
whether you read it or not, and costs vectorbt 102 seconds, because it defers
the equity curve until a risk metric needs it and then has to build one.
whether you read it or not, and costs vectorbt 73 seconds, because it defers the
equity curve until a risk metric needs it and then has to build one.
The last two rows are the ones where ManifoldBT does worst, and they are
published for that reason. Broadcasting a column per asset is close to free for
vectorbt, while walking five books is not free for anything. And on a
stop-loss/take-profit bracket, raptorbt is level with us: the intra-bar check
that decides which of the two triggers first is a sequential walk in both
engines, so there is no vectorization left to win with.
The fifth row is the one where ManifoldBT does worst, and it is published for
that reason: broadcasting a column per asset is close to free for vectorbt,
while walking five books is not free for anything.
### Parameter sweeps
@@ -254,14 +203,9 @@ The method, the parity gate and the known divergences are written up in
backtrader runs the same EMA(12/26) + RSI(14) strategy on 500K 1-minute bars in
**46,944 ms**, against **13 ms** for ManifoldBT: a factor of **3,556**. Measured
with `benchmarks/bench_vs_competitors.py`, median of 3 runs, on a developer
machine and not the CI runner, so it is not comparable line-for-line with the
table above.
It sits outside the CI suite because its event-driven fills produce a different
PnL, and the parity gate publishes no timing for engines that did not do the
same work. Treat it as an order of magnitude, not a benchmark: the two engines
are not doing the same thing.
with `benchmarks/bench_vs_competitors.py`, median of 3 runs. It sits outside the
CI suite because its event-driven fills produce a different PnL, and the parity
gate publishes no timing for engines that did not do the same work.
### How it compares
@@ -289,7 +233,7 @@ Full API reference, indicator list, configuration guide, and best practices:
| Monte Carlo | 1K sims | Unlimited |
| Walk-Forward | - | Anchored + Rolling |
| Parameter Stability | - | Yes |
| Crypto connectors (Binance, Bybit, Hyperliquid) | Yes | Yes |
| Free connectors (Binance, Bybit, Hyperliquid, dYdX, Bitstamp, Yahoo) | Yes | Yes |
| Databento & Massive connectors | - | Yes |
| Safety checks (lookahead, exposure) | - | Yes |
| Tearsheets & export | - | Yes |