QuantStats: Portfolio Analytics for Return Series in Python
Portfolio analytics for quants, written in Python
At a glance
- What is it?
- QuantStats computes performance and risk metrics on pandas return series and renders HTML tearsheets. It is built for systematic strategies, not for trade-level journals, and its period-based metrics are the thing to understand before adopting it.
- Who is it for?
- Adopt QuantStats if you already hold a pandas return series and want metrics and a shareable HTML tearsheet without writing the formulas yourself. Skip it if your evaluation runs on discrete trades with entry and exit prices, because its win rate, consecutive wins and payoff ratio are computed per return period, not per trade.
- Can I use it commercially?
- Yes. Apache-2.0 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 4 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 1, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem QuantStats solves for return-series work
A backtest ends with a pandas Series of periodic returns. Turning that into a defensible picture means computing Sharpe, Sortino, volatility, max drawdown, tail ratios, win rate, and a dozen more, then plotting the equity curve, the drawdown, the rolling Sharpe and the monthly heatmap. Most teams write that code once, badly, and then maintain four variants of it across notebooks. QuantStats packages the calculations and the plots behind one import, and adds a report generator that writes a complete HTML tearsheet.
The intended user is a quant or portfolio manager who works with return series and wants a standard vocabulary for describing them. The README lists the three modules plainly: quantstats.stats for metrics, quantstats.plots for visualization, quantstats.reports for metrics reports and tear sheets. The library does not fetch, clean or store data as its main job, although quantstats.utils.download_returns is shown in the quick start example.
Three modules, one return series, and a pandas extension
The data flow is shallow by design. You supply a return series (or something the library converts into one), and each function returns a number or a matplotlib figure. There is no engine, no state, no configuration object. qs.extend_pandas() attaches the metric functions as methods on pandas objects, so a Series of returns gains .sharpe(), .sortino() and the rest, which is why the README can show both qs.stats.sharpe(stock) and stock.sharpe() as equivalent calls.
The reports module is the aggregation layer. According to the README, qs.reports.html(stock, "SPY") produces an HTML tearsheet where the second argument is a benchmark that can be a pandas Series or a ticker. The same module exposes qs.reports.metrics, qs.reports.plots, qs.reports.basic and qs.reports.full, with a mode parameter accepting 'basic' or 'full'. That split matters: metrics and plots can be produced independently, so you can embed figures in a notebook without generating a full report.
The newest addition is Monte Carlo simulation. The README shows qs.stats.montecarlo(returns, sims=1000, bust=-0.20, goal=0.50) returning an object with bust_probability and goal_probability attributes and a plot() method, alongside montecarlo_cagr, montecarlo_drawdown and montecarlo_sharpe. That is a probabilistic layer on top of the same return series rather than a separate workflow.
Installing QuantStats and running a first report
The README gives two install paths. pip is the primary one, and the upgrade and no-cache flags are part of the documented command rather than an editorial addition.
$ pip install quantstats --upgrade --no-cache-dirThere is also a conda channel:
$ conda install -c ranaroussi quantstatsPython 3.10 or newer is required, and pyproject.toml pins pandas>=1.5.0, numpy>=1.24.0, scipy>=1.11.0, matplotlib>=3.7.0, seaborn>=0.13.0, tabulate>=0.9.0, yfinance>=0.2.40 and python-dateutil>=2.8.0 as runtime dependencies. Note that requirements.txt declares pandas>=2.0.0 while pyproject.toml declares pandas>=1.5.0, so a pip install -r requirements.txt is stricter than a normal install. That is a real inconsistency to be aware of if you pin dependencies from the requirements file.
A minimal first run, following the quick start, extends pandas, downloads a series and prints one metric:
import quantstats as qs
qs.extend_pandas()
stock = qs.utils.download_returns('META')
qs.stats.sharpe(stock)The README shows the output of that call as a single float, 0.7604779884378278. The download path depends on yfinance and therefore on network access to the data provider, which is the first thing that breaks in an offline environment. If you already have returns in a DataFrame, skip qs.utils and pass the series directly.
For a shareable artifact, the report call takes the series and a benchmark:
qs.reports.html(stock, "SPY")The result is an HTML file of the kind linked from the repository docs. To discover the rest of the surface without reading source, the README suggests Python's own introspection, for example help(qs.stats.conditional_value_at_risk), which prints the signature and a one-line description.
Period-based metrics are the limitation that bites traders
The README devotes a section to this and it is the most important constraint in the project. QuantStats analyzes return series, not discrete trades. Win rate is the percentage of periods with positive returns. Consecutive wins and losses are runs of positive and negative return periods. Payoff ratio is average winning period return divided by average losing period return, and profit factor is the sum of positive returns divided by the sum of negative returns.
For a systematic strategy that rebalances on a fixed schedule, those definitions line up with how the strategy is evaluated. For a discretionary trader holding multi-day positions, they do not. The README's own example: a single five-day trade spanning three positive days and two negative days is counted as three wins and two losses at the daily level. Position sizing, partial exits and overlapping trades disappear entirely in that view. If your performance review is trade-centric, QuantStats will produce numbers that look familiar and mean something different, which is worse than producing no numbers.
A second boundary is scope. The library is a measurement layer. It does not model transaction costs, slippage, borrow or financing, and it does not reconstruct positions from fills. Any of those belong upstream of the return series you hand it. The README also states plainly that full documentation is coming soon and points readers to the help() function in the meantime, so parameter behaviour beyond the docstrings is not documented in one place.
QuantStats compared with empyrical and pyfolio
The closest alternatives in this space are empyrical, which provides a similar set of risk and performance statistics, and pyfolio, which produces tear sheets. The difference is in packaging and in what each one assumes about your data.
empyrical is a metrics library with a narrower surface: you get the statistics and you build your own presentation. QuantStats bundles stats, plots and the HTML report in one package, which is why the quick start can go from an import to a rendered tearsheet in three lines. If you already have a reporting pipeline and only need Sharpe, Sortino and drawdown numbers, empyrical is the smaller dependency.
pyfolio is the closer comparison on output, since both generate a tear sheet over a return series with a benchmark. QuantStats ships the report generator inside the same package as the metrics and the plots, exposes basic and full modes, and adds the Monte Carlo functions and the pandas extension methods. pyfolio's tear sheet is built around its own analysis objects and a different set of assumptions about how returns and positions are supplied. If you want the metrics callable individually inside a notebook as well as inside a report, QuantStats' three-module split makes that straightforward; if you want the tear sheet and nothing else, the extra surface is dead weight.
Maintenance, releases and the Apache-2.0 licence
The repository is not archived, and the last push was on 2026-07-20. The most recent release is v0.0.81 from 2026-01-13, described as bugfixes for the 0.0.78 release, with v0.0.78 carrying the title 2026 Modernization Update and appearing the same day. The release before that, 0.0.77, is dated 2025-09-05. The version numbering stays in the 0.0.x range, so a minor bump can carry meaningful change, and the changelog file at the repository root is the place the project points readers to for what moved between versions.
Upgrade cost is mostly dependency drift. The runtime set is pandas, numpy, scipy, matplotlib, seaborn, tabulate, yfinance and python-dateutil, and the pandas floor in requirements.txt (2.0.0) is higher than the one in pyproject.toml (1.5.0). If you pin from requirements.txt you inherit the stricter bound. yfinance is a runtime dependency of the package even if you never call qs.utils.download_returns, which matters for air-gapped installs and for anyone who would rather not carry a market-data client in a reporting library.
The project is licensed Apache-2.0, declared both in pyproject.toml and in the LICENSE.txt file at the repository root, with the classifier License :: OSI Approved :: Apache Software License. Apache-2.0 includes an explicit patent grant and requires that notices and the licence text be preserved in distributions. That is a permissive arrangement, but whether it fits your redistribution or embedding plans is a question for your own counsel, not something this article can settle.
Editorial conclusion
Adopt QuantStats if you already hold a pandas return series and want metrics and a shareable HTML tearsheet without writing the formulas yourself. Skip it if your evaluation runs on discrete trades with entry and exit prices, because its win rate, consecutive wins and payoff ratio are computed per return period, not per trade. Before relying on it, run one series through qs.stats.sharpe and qs.reports.html and confirm the benchmark alignment and the annualization you expect.
Frequently asked questions
How do I install QuantStats?
Install it with pip using pip install quantstats --upgrade --no-cache-dir, or from conda with conda install -c ranaroussi quantstats. Python 3.10 or newer is required, along with pandas, numpy, scipy, matplotlib, seaborn, tabulate, yfinance and python-dateutil.
What is QuantStats?
QuantStats is a Python library that performs portfolio profiling over return series. It is split into quantstats.stats for metrics, quantstats.plots for visualization and quantstats.reports for metrics reports and HTML tear sheets.
How do I generate a QuantStats report?
Call qs.reports.html with your returns and a benchmark, as in qs.reports.html(stock, "SPY"), where the benchmark can be a pandas Series or a ticker. The reports module also offers qs.reports.metrics, qs.reports.plots, qs.reports.basic and qs.reports.full with basic and full modes.
Does QuantStats measure win rate per trade?
No. Win rate is the percentage of periods with positive returns, and consecutive wins and losses are runs of positive and negative return periods. A single multi-day trade can therefore register as several wins and losses at the daily level.
What does qs.extend_pandas() do in QuantStats?
It extends pandas functionality with the library's metrics, so a return series gains methods such as stock.sharpe() as an alternative to calling qs.stats.sharpe(stock). The README presents the two forms as equivalent.
Which Python versions does QuantStats support?
The README badge and pyproject.toml both state Python 3.10 or newer, and the classifiers list 3.10 through 3.13. The build backend is hatchling.
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/ranaroussi-quantstats)