Software design¶
qis is the base analytics and reporting layer in a wider quantitative-finance stack. Its design
centers on labelled pandas objects, explicit statistical conventions, and a stable top-level API.
This page explains where functionality belongs and which contracts an extension must preserve.
Layers and data flow¶
The typical workflow moves through five layers:
prices / NAVs / synthetic fixtures
|
v
returns, estimators, and performance statistics
|
v
target schedules -> held units -> realised portfolio history
|
v
ex-ante and ex-post risk, attribution, and benchmark analysis
|
v
tables, plots, and factsheet reports
The layers share DataFrame and Series objects with a DatetimeIndex. Labels are retained
through calculations so a covariance matrix, weight vector, contribution table, and report can be
aligned and audited without reconstructing instrument order from an array.
qis.perfstatsowns return conversion, annualisation, performance statistics, drawdowns, and regime analysis.qis.modelsowns reusable estimators such as EWMA, regressions, bootstrap methods, and unsmoothing models.qis.portfolioturns target weights into a held-unit portfolio history and exposes attribution, risk, and reporting objects.qis.plotssupplies the matplotlib/seaborn visual layer used directly and by factsheets.qis.market_datacontains vendor-neutral transformations. Optional fetchers are integrations, not a prerequisite for the analytics core.
The focused guides describe the user-facing contracts in more detail: performance and Sharpe conventions, portfolio backtesting, tracking error and risk, and factsheets and reporting.
Public API boundary¶
The supported import surface is re-exported from qis.__init__ and recorded as a literal in
qis.api.PUBLIC_API. qis.api.CORE_API identifies the documented capability-oriented subset used
by downstream packages, examples, and guides. The generated API reference follows
those records rather than the physical module tree, so an internal module move does not change the
documented import path.
Before adding a new export, decide whether the operation is a stable cross-package primitive or an
internal helper. A new public name creates a compatibility obligation for optimalportfolios,
trendfollowing, and other consumers. Synchronize the literal export record with
python tools/sync_public_api.py whenever the top-level surface changes.
Numerical contracts¶
Several conventions are structural rather than implementation details:
A decision made at time t is applied over [t, t+1]; estimation inside a backtest is point-in-time and must not use later observations.
Between rebalances, QIS holds instrument units. Realised weights drift with asset prices; a weighted average of returns is not a replacement for the backtest.
Return type, sampling frequency, and annualisation are explicit. Use
qis.to_returnswith an explicitis_log_returnschoice instead of inferring a convention from context.Sharpe statistics have separately labelled arithmetic, geometric, and excess-return conventions. Excess variants require the corresponding rates data in
PerfParams.Missing starts, interior gaps, stale observations, and delisted tails are distinct states. The incomplete-history guide documents their policies.
A test for a numerical change should compare against a reference computed through a different path. Tests for estimation windows, weight normalisation, annualisation, unsmoothing, or resampling also need an explicit check for look-ahead or convention drift.
Optional backends¶
The core installation is offline and importable without data vendors, database drivers, Plotly,
PyBloqs, or PyArrow. Optional dependencies are declared as extras and imported inside the function
that needs them. A module dedicated to one optional backend may import it at module level only when
the top-level qis import cannot reach that module.
CI enforces this boundary statically with Ruff’s TID253 rule and dynamically by running the full
suite from a clean core wheel. New optional functionality should include a clear missing-dependency
error and a test that skips, rather than fails, when the extra is absent.
Package-stack boundary¶
Dependencies flow toward QIS, not back from it. Portfolio construction and optimisation belong in
optimalportfolios, generic sparse factor models
in factorlasso, and Bloomberg data acquisition in
bbg-fetch. Reimplementing those layers here would
create two conventions for one concept and a dependency cycle.
Verification and packaging¶
The default pytest suite runs against a core installation on every supported Python version and on Linux, Windows, and macOS at the primary interpreter. A separate locked lane exercises the data and I/O extras with a line-coverage ratchet. Static checks defend import direction, optional-dependency boundaries, naming conventions, and production docstring coverage.
Automated tests stay in source-adjacent tests/ packages. Interactive or local-data diagnostics
stay equally close to their implementation, but under run_local/ as <subject>_run.py. Those
runners use Locals plus run_local(local=...); production modules never import them and wheels
do not distribute them.
The wheel job builds the artifact users receive, verifies its tests and package documentation, installs it into a clean environment, runs the shipped suite outside the checkout, and executes the offline quickstart against the installed artifact. Contributor commands matching these lanes are in the repository’s CONTRIBUTING.md.