bt's README carries anchors for two sections it does not contain, and two progress bars in one dependency list
bt - flexible backtesting for Python
At a glance
- What is it?
- A backtesting framework that composes strategies from four kinds of algorithm and hands statistics to a separate library by the same author. The first example uses two straight lines for prices, the build compiles part of the package with Cython, and the type checker is labelled advisory and wired into no aggregate target.
- Who is it for?
- bt fits a researcher who wants to express a strategy as a composition of small named algorithms rather than as a bespoke loop, and who is comfortable reading the API overview rather than a tutorial. Three things to check before you build on it.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 5, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The first example is two straight lines, and the README says results do not predict performance
The quick example is built so that it runs without downloading market data. The prices are generated rather than loaded:
prices = pd.DataFrame(
{
"asset_a": np.linspace(100, 120, 252),
"asset_b": np.linspace(100, 110, 252),
},
index=pd.bdate_range("2020-01-01", periods=252),
)So two assets, 252 business days from the start of 2020, one climbing linearly from 100 to 120 and the other from 100 to 110. There is no volatility, no drawdown, no correlation and no volume in that data, which makes the example reproducible and also makes it incapable of showing the thing a backtest is usually for.
The README is direct about the limit. It says to replace the synthetic prices with your own data to explore a strategy, and that backtest results depend on data quality and modelling assumptions and do not predict future performance.
That sentence is the appropriate reading of anything this library prints. A backtest on two straight lines is a demonstration that the plumbing works, and the README does not pretend otherwise.
The four algorithms in the example are the four items in the feature list
The strategy in the quick example is four algorithm objects and nothing else:
strategy = bt.Strategy(
"equal_weight",
[
bt.algos.RunMonthly(),
bt.algos.SelectAll(),
bt.algos.WeighEqually(),
bt.algos.Rebalance(),
],
)
result = bt.run(bt.Backtest(strategy, prices))
result.display()Those four map one to one onto the categories the feature list names. Scheduling decides when the strategy runs, here monthly. Security selection chooses what to hold, here everything. Weighting decides how much, here equally. Rebalancing decides when positions change, and it is the step that makes the monthly schedule mean anything.
That decomposition is the design. Anything a strategy needs that is not one of those four concerns is either another algorithm in the same list or code outside the library, and the documentation is organised the same way, with separate pages for the algorithms and for combining securities and nested strategies into portfolio trees.
The fourth feature, modelling trading costs, is the one the quick example leaves out. Commissions and transaction cost models are configured on the backtest rather than in the algorithm list, which means a result computed without them looks better than the same strategy would trade.
The result object is inspected through a single display call, and the comparison surface the README advertises is returns, weights, transactions and drawdowns.
Six heading anchors, two of which point at sections that are not in the file
The README uses explicit anchor elements rather than relying on generated heading identifiers, and six of them are declared. Two are stacked on top of each other with nothing between them: a quick example anchor immediately followed by a simple strategy backtest anchor.
The other two point at sections that do not exist. There is an anchor for modifying a strategy, and the heading that follows it is the documentation index. There is an anchor for a roadmap, and the heading that follows it is Contribute. Neither section is present.
That is a small thing with a practical consequence. Any link into this README that targets the modifying-a-strategy anchor, or the roadmap anchor, lands on a heading about something else. It also suggests the file was shortened at some point and the anchors were left behind.
The badge row at the top has a matching problem. It contains three badges, and two of them point at the same address, the project's PyPI page, so the package index is advertised twice.
What the README does contain is accurate, as far as it goes. It identifies the library as flexible backtesting for Python, gives the install command, walks one example, and links five documentation pages covering a first strategy tutorial, the algorithms, portfolio trees, examples and an API overview.
Statistics come from a different library, and the dependency list has two progress bars
The library has three runtime dependencies, and the first one does more work than its name suggests. `ffn>=1.1.2` is the separate statistics and charting library by the same author, and the README says plainly that performance statistics and charts are provided through it. Returns, drawdowns and the tables you would compare strategies with are computed there, not here.
The other two are `pyprind>=2.11` and `tqdm>=4`, which are two progress bar libraries in the same list. Both are widely used and both do the same job, so a backtest run can plausibly render two bars.
None of the three is pinned to an exact version, which is the opposite of what several other projects in this batch do, and the floors are old: a 2.11 for one progress bar and a 4 for the other.
The dependency arrangement has one consequence worth naming. Because statistics arrive from a separate package with its own version line, the numbers in a result table are produced by code that this repository does not contain and does not test. A change in how a statistic is computed would arrive as a dependency bump, not as a release of this library.
The build compiles part of the package, and the example directory mixes notebooks with scripts
The build system lists four requirements, and they are not interchangeable. `hatchling` is the backend, `setuptools` is present alongside it, and `hatch-cython` and `Cython>=0.29.25` together mean part of the package is compiled rather than pure Python.
A C extension has consequences beyond the build. It means platform-specific artefacts, which is why `cibuildwheel` appears in the development dependencies, and it means an install from source needs a compiler. The project version is written directly into the packaging file rather than derived from tags, and it currently reads 1.3.0, which matches the newest release tag.
That tag is dated the same day as the most recent commit, with the commit coming a few hours later, so the branch head and the release are aligned rather than one running ahead of the other.
The examples directory holds eleven files and mixes two formats. Most are notebooks: an equal risk contribution example, a probability of target example, a strategy combination example, a target volatility example, a buy and hold notebook, cost models, fixed income, and two trend notebooks. Two are plain Python scripts instead, a buy and hold script and a pairs trading script. One example, buy and hold, exists in both forms, so it is the only strategy here that can be read either as prose or as code.
Benchmarks live in their own top-level directory, separate from the tests.
The type checker is labelled advisory and is not in any aggregate target
The build file is long, listing its own phony targets, and one detail in it is worth reading carefully. There is a target that checks types with a separate checker, and its comment says advisory.
Advisory is doing real work in that comment. Look at the aggregation: a target named checks depends only on the distribution check, and a target named check depends on checks. So the aggregate check target runs the distribution inspection and nothing else. The type checker is not in it, and it is not in any other aggregate target either.
The rest of the tooling is coherent. Python is linted and formatted with ruff, invoked as a module rather than through a console script. Documentation is checked for formatting and for spelling by two separate tools, both pointed at the README and the documentation sources explicitly rather than at a glob. The test target runs pytest against the tests directory, the coverage target adds coverage with a terminal report missing lines and an XML report, and the benchmark target runs the separate benchmarks directory in benchmark-only mode.
Two conventions in the file are unusual. One target installs the development extra by passing the packaging file itself to the installer as a requirements file. Another builds with isolation turned off.
There are also aliases, with one target forwarding to the linters and another forwarding to the formatters, so the same work has two names.
Copier provenance, a Beta label at version 1.3, and an http homepage next to two https ones
Three small facts describe how the project is put together.
The first is provenance. The root contains a Copier answers file, and the development guide's own list of what it covers includes Copier template updates. A Copier answers file records the answers given to a project template, so this repository is generated from one, and updating it is a documented part of development rather than something the README mentions.
The second is the maturity label. The classifiers record the development status as Beta, at version 1.3.0. The Python floor is 3.9, with classifiers running from 3.9 through 3.13. Beta alongside a specific minor version is a combination that means the API is expected to move within the one series.
The third is a small inconsistency in addresses. The home page recorded in the repository metadata uses plain http, while both the packaging metadata and the README link the same documentation site over https. A reader who takes the metadata literally gets an insecure address for a page that is available securely one character away.
The development dependencies also contain three documentation tools rather than one: a Markdown formatter, and two separate publishing tools with their own version floors. Alongside the type checker and the linter, that is a fairly wide tool surface for a package whose runtime dependency list is three entries long.
Editorial conclusion
bt fits a researcher who wants to express a strategy as a composition of small named algorithms rather than as a bespoke loop, and who is comfortable reading the API overview rather than a tutorial. Three things to check before you build on it. The library states that results depend on data quality and modelling assumptions and do not predict future performance, and its own first example runs on two linearly interpolated price series, so the framework is a composition tool rather than evidence about a strategy. Statistics and charts are not in this package at all; they arrive through a separate library by the same author, which means the two versions have to agree. And type checking is present but marked advisory and excluded from the aggregate check target, so the annotations are a suggestion rather than a guarantee.
Frequently asked questions
Can you backtest my trading strategy?
Yes, that is what the library is for: it combines strategy logic with historical price data, tracks portfolio positions and transactions, and reports statistics through a separate library by the same author. The README adds that results depend on data quality and modelling assumptions and do not predict future performance.
What are some good backtesting libraries for Python?
The repository does not list alternatives or comparisons. What it states about itself is the composition model: strategies are built from algorithms for scheduling, security selection, weighting and rebalancing, can be nested into portfolio trees, can carry commissions and transaction cost models, and report returns, weights, transactions and drawdowns through the ffn library.
How do I install bt?
`pip install bt` from PyPI, with Python 3.9 or newer. The README points to an installation guide for details. For development, the build file's develop target installs the develop extra with uv, and that extra carries the build backend, Cython, the test and lint tools, and the distribution checking tool.
Does bt include charts and performance statistics?
Not in this package. The README says performance statistics and charts are provided through ffn, which is the first of the three runtime dependencies, and the project URLs and repository point at the same author's separate repository for it. A change in how a statistic is computed would therefore arrive as a dependency update rather than as a release of bt.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pmorissette-bt)