mirror of
https://github.com/manifoldbt/manifoldbt.git
synced 2026-08-24 14:38:04 +00:00
release: v0.19.0
This commit is contained in:
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user