# qis measures portfolios you already backtested instead of generating your strategy

> qis is a Python library for performance analytics, backtesting of externally supplied weight schedules, ex-ante and ex-post tracking error, and factsheet reporting, published in the Journal of Open Source Software. Its distinguishing choice is that it never produces a target allocation for you, so every reported number depends on conventions you state yourself.

**ArturSepp/QuantInvestStrats** — Performance analytics, portfolio backtesting, risk analysis, and factsheet reporting in Python

- Repository: https://github.com/ArturSepp/QuantInvestStrats
- Website: https://quantinveststrats.readthedocs.io
- Stars: 642 · Forks: 77
- Language: Python
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/artursepp-quantinveststrats

## The library's stated reason for existing: stated conventions

qis describes itself as performance analytics, portfolio backtesting, risk analysis and factsheet reporting in Python, and it names its audience as quantitative portfolio managers, researchers and investors who analyse portfolio performance and backtest investment strategies.

The interesting part of the README is the sentence that explains why the library exists. It says reported results can differ when return frequency, risk-free-rate treatment and rebalancing conventions are left unstated, and that the library makes those conventions explicit and uses shared calculations across analytics, backtests and reports.

That is a real problem in investment analysis. Two teams can hold the same positions, run the same weights, and report different returns, because one computes returns daily and the other monthly, one annualises the risk-free rate from a constant and the other from a curve, and one rebalances on a schedule while the other rebalances when a band is crossed. Without a stated convention the numbers are not comparable, and often not even reproducible.

The structural claim follows from that. qis separates strategy research from portfolio accounting. Your strategy logic stays in your code, and the library takes instrument prices and externally computed weights and turns them into drift-aware portfolio histories, performance attribution, risk analysis and reproducible reports. The README describes the domain as covering time-series and cross-sectional performance, drift-aware portfolio histories, ex-ante and ex-post risk, and reproducible reports, and it puts the responsibility plainly: bring your own strategy logic and weight targets, and qis measures, backtests, analyses and reports them.

The project is published in the Journal of Open Source Software, has a Read the Docs site, and is marked as beta.

## Five modules with a deliberately increasing dependency path

The package is split into five main modules, and the README says the dependency path increases sequentially across them, which tells you how the library is meant to be consumed.

qis.utils holds low-level utilities for pandas, NumPy and datetime work, and it is the layer you depend on if you want the array and frame helpers without anything else. qis.perfstats computes performance statistics and attribution, including returns and volatilities. qis.plots provides the plotting and visualisation APIs. qis.models contains statistical models including filters and regressions. qis.portfolio is the high-level module for analysis, simulation, backtesting and reporting of quantitative strategies.

The ordering matters because it is a dependency discipline. A script that only needs a volatility estimate should not pull in the reporting stack, and a portfolio backtest should not have to reimplement the return arithmetic that the performance statistics use. The README states the point directly: the same conventions serve the whole package stack, so a number computed in a backtest and the same number computed in an analytics call come from one implementation.

The source tree under src holds the package, the examples directory is organised into sixteen topic folders, from getting_started and perfstats through regimes, models, portfolios and factsheets, and there is a separate data_fetching folder for the parts that need a provider. The repository also carries a paper.md and paper.bib for the JOSS submission, a CHANGELOG, a GOVERNANCE file and a CITATION.cff.

## The backtester consumes weights and never invents them

The most consequential design decision in the package is stated as a boundary. The backtester does not generate a strategy's target schedule.

The mechanism has two parts. generate_static_weights_schedule() can create live-universe-aware target schedules from desired allocations, and backtest_model_portfolio() then consumes prices and the supplied weights, holds units between rebalancings, and applies explicit transaction costs. The README points at a worked backtesting example and at an offline allocation workflow for the two halves.

The distinction is between producing an allocation and accounting for one. Most backtesting frameworks own both, which means the numbers you read are a product of the framework's rebalancing assumptions as much as of your idea. Here the allocation logic is yours, and the accounting is the library's, which means a result can be reproduced by anyone with the same prices, the same weights and the same stated conventions.

Transaction costs are explicit rather than implied, and the units are held between rebalancings rather than re-weighted back to target on every date. That combination, costs applied at a defined point and holdings persisting between rebalances, is what a portfolio manager actually experiences, and it is also what makes the output comparable with a real book.

The documentation names a worked example and an offline workflow, which is where you should look to see the conventions spelled out for a concrete case rather than inferring them from the function signature.

## RiskModel puts ex-ante tracking error on the same footing as ex-post

Risk and tracking-error analytics are consolidated in qis.portfolio.risk, and the public qis.RiskModel is the point-in-time weights-and-covariance layer.

What that layer covers is a long list, and the list is the point. Ex-ante tracking error, standalone group risk, factor exposures, benchmark beta and loadings, systematic and residual tracking-error decomposition, and Euler marginal tracking-error contributions. The loading-matrix interface also supports fractional, overlapping and signed standalone sleeves, without reducing them to categorical groups, which is a real difference for a multi-strategy book where a sleeve can be long two things and short one.

The ex-post side works from portfolio and benchmark NAVs or return differences. compute_ewma_realised_tracking_error produces a conditional annualised series, while compute_te_ir_errors and compute_info_ratio_table produce whole-sample tracking error and information ratio.

The ex-ante and ex-post distinction is the part to internalise. Ex-ante asks what your risk is now, given current weights and a covariance estimate. Ex-post asks what your tracking error has been, given history. A manager who reports one and calls it the other will be asked about it eventually, and the point-in-time layer here is deliberately separate from the historical series for that reason.

The same module handles the harder portfolio shapes. The stress module combines funded holdings, signed intrinsic calls and puts, futures and custom payoffs under an assigned RiskModel, preserves source marks and shared underlying risk, splits factor-family scenarios, ranks historical months and exports the standard report with additive Euler risk tables. Single-factor observed-index models support native point changes such as VIX, currency-independent regressors and portfolio prediction R-squared, and the repository ships an offline example you can run with python -m examples.portfolios.instrument_portfolio_stress --case vix.

## Factsheets come in four archetypes with optional PyBloqs rendering

Reporting is treated as a first-class output, not an afterthought, and the README lists four report archetypes: multi-asset, strategy, strategy versus benchmark, and multi-strategy parameter sweeps. Each produces a reproducible multi-page factsheet, with optional PyBloqs HTML and PDF rendering.

The four archetypes map onto real deliverables. A multi-asset factsheet answers what happened in each asset class. A strategy factsheet describes one strategy. A strategy versus benchmark factsheet answers the question a client actually asks, which is whether the added value justified the added risk. A multi-strategy parameter sweep produces the grid you need when you are choosing between configurations rather than describing one.

Reproducibility is the claim that matters, and it follows from the same place as everything else in the package. If the conventions are stated and the calculations are shared, then regenerating a factsheet from the same prices and weights gives the same document, and a factsheet can be re-derived months later when someone asks where a number came from.

The README points at a factsheet gallery and at an offline reporting quickstart, and the examples directory has a dedicated factsheets folder, so the intended starting point is a document you can read rather than an API you have to infer from source.

## The documentation is checked against the code

The README makes a claim about documentation quality that is unusual enough to be worth reading closely, because it is the kind of thing that is easy to claim and hard to do.

It says the core dependency list is checked against pyproject.toml. README Python blocks are parsed for unresolved names. Repository examples are checked for public symbols and introspectable keyword arguments. And examples without a data vendor are run. Network-backed examples remain subject to their providers, which is an honest limit rather than a claim of full coverage.

The mechanism is straightforward and it is the kind of thing a continuous integration job does well. Parsing README code blocks for names that do not resolve catches the most common form of documentation rot, which is a snippet that was edited to match a refactor and never re-run. Checking examples for public symbols catches a rename that left the docs behind. Running the examples that need no external data catches the rest.

The repository structure backs the claim. There is a .github directory, a .githooks directory, a CONTRIBUTING file with a development setup section, a tools directory, a .zenodo.json for archiving, and a workflow that runs checks. The pyproject.toml declares a CI badge for the main branch.

The one thing to be careful about is that this checking covers code, not numbers. A documentation check cannot tell you that an example's performance figure was computed with a different return frequency than the text claims. The stated-conventions work is what protects the numbers, and the CI work protects the code.

## Where qis stops, and what it points you to instead

The README has a section on when to use the library and when not to, and it is short enough to quote closely. Use qis for performance and risk statistics on price panels, for backtests of supplied weight schedules with costs, for ex-ante and ex-post tracking error, for regime-conditional analytics, and for factsheets for backtested or live strategies.

Then the boundaries. For portfolio construction the README points to a separate project, optimalportfolios, and for Bloomberg data it points to a separately installed companion package called bbg-fetch, which it explicitly says is not a qis extra. And the summary line is unambiguous: qis is a research and reporting library, not an execution system.

That last point has teeth in practice. If you want order routing, position management or broker reconciliation, none of it is here. If you need Bloomberg, you install a second package, and a second package brings its own data access and its own failure modes.

The beta status matters too. The pyproject.toml records Development Status 4 - Beta, and the version there reads 5.41.0 while the most recent published tags listed for the repository are v5.37.0 and v5.36.1, with the last push on 2026-10-20. A rapid release cadence on a package with a large public surface is fine for research use and something to watch if you depend on exact numeric output over time, so pin a version and record it with your results.

The licence is MIT, and the package also publishes a Zenodo record, which matters if you need a citable archive rather than a moving tag.

## Conclusion

Adopt qis if you already have instrument prices and target weights and want the accounting, attribution, tracking error and reporting done with conventions you can state and reproduce, since the package exists to make return frequency, risk-free rate treatment and rebalancing rules explicit rather than implicit. Do not adopt it if you want a portfolio constructor, because the README directs that to a separate package called optimalportfolios, and do not adopt it as an execution system, since qis is described as a research and reporting library. Verify first that the version you install matches the code you read, because the pyproject.toml records 5.41.0 while the most recent published tags listed are v5.37.0 and v5.36.1, that the drift handling and rebalancing assumptions match your own accounting, and that the examples you copy run against a data vendor you actually have, since the repository ships examples across sixteen directories and the documentation notes that examples without a data vendor are run while network-backed ones are not.

## FAQ

### What is qis and who is it for?

qis is a Python library for performance analytics, portfolio backtesting, risk analysis and factsheet reporting, aimed at quantitative portfolio managers, researchers and investors. The README says it separates strategy research from portfolio accounting, so you supply instrument prices and target weights and the library does the measurement, backtesting, analysis and reporting.

### Does qis generate trading signals or target allocations?

No. The README is explicit that the backtester does not generate a strategy's target schedule. generate_static_weights_schedule() can build a live-universe-aware schedule from desired allocations, and backtest_model_portfolio() then consumes prices and supplied weights, holds units between rebalancings and applies explicit transaction costs.

### What is the difference between ex-ante and ex-post tracking error in qis?

The point-in-time qis.RiskModel layer covers ex-ante tracking error, factor exposures, benchmark beta, loadings and Euler marginal contributions, while the ex-post analytics work from portfolio and benchmark NAVs or return differences, with compute_ewma_realised_tracking_error, compute_te_ir_errors and compute_info_ratio_table.

### How do I install qis and is it stable?

The package is installed with pip install qis and imported as qis, and the README marks the status as beta, with the pyproject.toml recording Development Status 4 - Beta. It requires Python 3.10 or later, and the version in pyproject.toml reads 5.41.0 while the most recent published tags are v5.37.0 and v5.36.1.

### Can qis produce a factsheet for a strategy compared with a benchmark?

Yes. The README lists four report archetypes: multi-asset, strategy, strategy versus benchmark, and multi-strategy parameter sweeps, each producing a reproducible multi-page factsheet with optional PyBloqs HTML and PDF rendering. The repository includes a factsheets example folder and a factsheet gallery in the documentation.

## Sources

- [ArturSepp/QuantInvestStrats on GitHub](https://github.com/ArturSepp/QuantInvestStrats)
- [License: MIT](https://github.com/ArturSepp/QuantInvestStrats/blob/main/LICENSE)
- [Project website](https://quantinveststrats.readthedocs.io)
- [README](https://github.com/ArturSepp/QuantInvestStrats/blob/main/README.md)
- [Releases](https://github.com/ArturSepp/QuantInvestStrats/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/artursepp-quantinveststrats
