Backtesting.py
Two thousand lines, one OHLC series, and no idea what a funding payment is.
by kernc
Last updated
What it is
A Python backtester in about two thousand lines. You hand Backtest a single pandas DataFrame
with Open, High, Low, Close and optionally Volume, and a Strategy subclass with an
init() that computes indicators and a next() that runs once per bar. It returns a pandas
Series of statistics and a trades DataFrame, and bt.plot() draws an interactive Bokeh chart with
every entry and exit marked on the candles.
It was written for any instrument with candles and it shows: the engine knows about price, size, commission and spread, and nothing else. That is a real virtue when you are testing a spot rotation and a real problem when you are testing a perp.
The maintainer publishes under the GitHub account kernc; there is no company behind it and
nothing is sold.
Pricing
Free, and there is no paid anything. The package is on PyPI as backtesting, the repository is
under the AGPL, and the only money link in the README is a GitHub Sponsors badge for the
maintainer. There is no hosted tier, no data bundle and no commercial licence on offer, which is
why this card carries no price table at all.
The AGPL is the part with a cost attached. Running it locally for your own research is the uncomplicated case. Putting it behind a web service other people use is the case the network clause was written for, and it is worth reading before you build a product on top.
Data & coverage
It ships no data and has no notion of a venue. You supply the candles; the library never makes a
network call. Three sample series come with the package for the tutorials — GOOG, EURUSD and
BTCUSD in backtesting.test — and they are examples, not a feed.
The input contract is strict in a useful way: the four OHLC columns must be present and must not contain NaNs, and the index has to be either a datetime index or a monotonic range index. Extra columns are passed through to the strategy, which is how people attach funding rates, order-book imbalance or sentiment as a signal — but they are signals, not something the accounting understands.
Fills happen at the next bar's open by default, or at the current bar's close with
trade_on_close=True. There is no intrabar path: a bar that touches both your stop and your take
profit is resolved by the engine's ordering rules, not by what actually happened inside the
minute. On a 1m crypto series that matters more than it does on daily equities.
Integrations
Effectively none, by design. The dependencies are numpy, pandas and bokeh, Python 3.9 or newer,
and the library is indicator-agnostic — self.I() wraps any function that returns an array, so
TA-Lib, pandas-ta or your own numpy works the same way. The parameter optimiser has a grid mode
and a model-based mode built on SAMBO.
There are no exchange connectors, no broker adapters and no live loop. Whatever pulls the candles and whatever places the orders are your problem, and the strategy you tested is not the code that will run.
Limitations
No funding, at all. Grep the engine for funding, perpetual or liquidation and you get
nothing. The only cost inputs are spread and commission, which is a flat rate, a
(fixed, relative) pair or a callable on order size and price. For a perp strategy held across
funding boundaries that is not an approximation, it is a missing leg, and it flatters carry-negative
strategies for as long as you leave it out.
No maintenance margin and no mark-price liquidation. The docstring is blunt about it: "margin
is the required margin (ratio) of a leveraged account. No difference is made between initial and
maintenance margins." One number sets leverage and nothing checks it again. The simulation stops
only when total equity reaches zero, at which point the broker closes everything and zeroes the
rest of the equity curve. An exchange would have taken the position long before that, at a mark
price this engine does not have.
The defaults cost nothing. spread=0, commission=0, margin=1. A first run of any
high-turnover idea will look wonderful and mean nothing. Set both before you read a single
statistic.
One instrument per backtest. MultiBacktest runs the same strategy over several DataFrames in
parallel to compare results per ticker; it is not a portfolio. Cross-pair basis, a funding-carry
pair trade, or anything that sizes one leg against another cannot be expressed.
Whole units, unless you rescale. The base class refuses fractional sizes and warns when a close
exceeds your starting cash. FractionalBacktest multiplies the whole series by fractional_unit,
one satoshi by default, and divides volume by it — workable, and a wrapper you have to know
exists.
It has stopped before. PyPI has 0.3.3 in December 2021 and then nothing until January 2025, when 0.4.0 and 0.5.0 were uploaded twenty seconds apart. Development since has been steady — eight tagged releases across 2025 and 2026 — but the gap is in the record, and it is the reason to check the dates yourself rather than trust a "actively maintained" line anywhere, including this one.
Alternatives
If the strategy is spot and the instrument is one pair, the small surface here is the feature. If it is a perp, the question is not which library is nicer to write against but which one charges funding and liquidates at a mark price — the rest of this category is where to look.
Specs
- Interfaces
- Python
- Export
- None
- Asset classes
- Spot
- Chains
- —
- Venues
- —
- KYC required
- No
- Platforms
- Library
- AI features
- None
- Capabilities
- Charting, Backtesting
- Pricing verified
- Capabilities verified
- Coverage verified
Also worth comparing
- Barter — Rust trading engine and market-data streams — live execution is a trait you implement.
- HftBacktest — Tick-by-tick backtesting that models order queue position and feed and order latency.
- LEAN — The engine behind QuantConnect, Apache-2.0 and runnable on your own machine.
- Lumibot — One Python strategy that backtests and then trades live, across eight CCXT exchanges.
- NautilusTrader — Event-driven Rust engine that settles perpetual funding at the venue boundary.
- Jesse — Python backtesting and research framework whose live-trading half is a paid plugin.
On these shelves
Background
How this part of the industry works, rather than which product to pick.
- What a crypto backtest silently assumes — Every engine ships a fill rule and a cost model with defaults. What those defaults assume about fees, funding and liquidation, and which engines model which.
FAQ
Is backtesting.py still maintained in 2026?
Yes, with a caveat about its history. The last release is 0.6.6, published to PyPI on 2026-07-22, and the last commit on master is 2026-08-05. The repository is not archived. But PyPI shows nothing at all between 0.3.3 in December 2021 and January 2025, so the project has gone quiet for years once already.
Can backtesting.py backtest perpetual futures?
Not properly. The engine has no concept of a funding rate, a mark price or a maintenance-margin liquidation — the words do not appear in the source. You can simulate a levered position with the margin parameter, but the funding leg that usually decides a perp strategy simply is not charged.
Does backtesting.py handle Bitcoin prices out of the box?
Only if you rescale. The default Backtest class trades whole units against 10,000 in starting cash, so a five-figure BTC price triggers a warning and places nothing. The FractionalBacktest wrapper rescales the series to satoshis, which is the intended workaround.
What licence is backtesting.py under?
The LICENSE.md file in the repository is the plain GNU Affero General Public License v3 text. The AGPL network clause is the part to read if you plan to run it behind a hosted service rather than on your own machine.