Skip to content

Repository files navigation

myst
html_meta
description
Multi-asset portfolio construction and rolling backtesting in Python, with offline examples, constrained optimization, covariance models and analytics.

optimalportfolios

Author: Artur Sepp / First recorded: 2023-07-08

Source: OptimalPortfolios. Software citation: CITATION.cff. Analytics and holdings simulation use qis; cite its software record.

Explore the analytics gallery for reproducible synthetic examples with sample dates, conventions, producer links and reviewed provenance.

Production multi-asset portfolio construction and rolling backtesting in Python — from point-in-time covariance and alpha estimation through constrained optimisation, rebalancing, transaction costs, and reporting.

Install: pip install optimalportfolios · Import: optimalportfolios · Status: Stable

PyPI Python CI Docs License Downloads Monthly Open In Colab

Papers: Sepp, A. (2023), Optimal Allocation to Cryptocurrencies in Diversified Portfolios, Risk Magazine — SSRN 4217841 · Sepp, A., Ossa, I. and Kastenholz, M. (2026), Robust Optimization of Strategic and Tactical Asset Allocation for Multi-Asset Portfolios, The Journal of Portfolio Management, 52(4), 86–120 · Sepp, A., Hansen, E. and Kastenholz, M. (2026), Capital Market Assumptions and Strategic Asset Allocation Using Multi-Asset Tradable Factors — SSRN 6785958. See References.


Why optimalportfolios

PyPortfolioOpt, Riskfolio-Lib, and skfolio all provide substantial portfolio optimisation capabilities. Their documented design centres emphasise, respectively, compact classical allocation, breadth across risk measures and portfolio families, and scikit-learn-compatible model selection. optimalportfolios is organised around a different primary abstraction: a dated state transition from estimates and current holdings to constrained targets and realised backtests.

optimalportfolios solves the production problem end-to-end: estimate covariance → compute alpha signals → optimise with constraints → rebalance on schedule → backtest with transaction costs — all in a single roll-forward pipeline that handles incomplete data, mixed-frequency assets, and illiquid positions.

Key differentiators

  • Production multi-asset pipeline. Factor-model covariance, risk-budgeted strategic allocation, alpha signals, tracking-error-constrained tactical allocation and a rolling backtest share one dated roll-forward state, with equities rebalancing monthly and alternatives quarterly. See the ROSAA case study.
  • HCGL factor covariance. Sparse, structured covariance for heterogeneous universes, fitted by factorlasso and assembled point in time by FactorCovarEstimator across return frequencies. See factor covariance with HCGL.
  • Cluster-aware risk allocation. Clusters, sectors or asset classes become asset-level risk budgets, static or dated, and canonical HRP runs from a supplied linkage. See hierarchical risk parity and cluster risk budgets.
  • Drift-aware rolling backtests. Turnover limits and cost penalties act on the drifted holdings rather than the previous target (OptimiserConfig.use_drifted_weights_0, default True). See rolling backtests.
  • Incomplete and illiquid histories. Assets enter when their history suffices, missing prices get zero weight, and rebalancing indicators freeze illiquid positions. See incomplete histories and frozen positions.
  • Research-backed. The reference implementation of the ROSAA framework in The Journal of Portfolio Management; every number on the methodology pages is asserted by a script that CI runs. See research papers.

Five-minute quickstart

The production quickstart is the authoritative source for the first-use workflow. It runs entirely offline on the multi-asset fixture shipped in the wheel and writes no files:

pip install optimalportfolios
python examples/getting_started/production_quickstart.py

For a zero-setup trial, open the mechanically checked mirror in Colab. The notebook installs the latest PyPI release, prints its version, and adds no notebook dependency to the package.

The script uses a documented six-asset slice, a point-in-time 24-month EWMA covariance estimator, quarterly constrained minimum-variance weights, a one-month implementation lag, and 10 basis points of transaction costs. It prints the data range, rolling-weight dimensions, final weights, final NAV, and measured runtime. The rendered quickstart documentation includes this same file directly, so the example and documentation cannot drift.

A minimal executable example

The script above remains the authoritative first-use workflow. The shorter version below exists so that the README's own code is executed rather than trusted:

import qis
from optimalportfolios import (
    Constraints,
    EwmaCovarEstimator,
    PortfolioObjective,
    compute_rolling_optimal_weights,
)
from optimalportfolios.tests.data.multiasset import load_multiasset_data

prices = load_multiasset_data().prices.iloc[-120:, :4]
time_period = qis.TimePeriod(prices.index[0], prices.index[-1])

# estimate covariance → optimise → get rolling weights
estimator = EwmaCovarEstimator(returns_freq='ME', span=24, rebalancing_freq='QE')
covar_dict = estimator.fit_rolling_covars(prices=prices, time_period=time_period)
weights = compute_rolling_optimal_weights(prices=prices,
                                          portfolio_objective=PortfolioObjective.MAX_DIVERSIFICATION,
                                          constraints=Constraints(is_long_only=True),
                                          time_period=time_period,
                                          covar_dict=covar_dict)

# backtest with transaction costs
portfolio = qis.backtest_model_portfolio(prices=prices.loc[weights.index[0]:], weights=weights,
                                         rebalancing_costs=0.001, ticker='MaxDiv')

print(f"assets: {list(weights.columns)}")
print(f"rebalance dates: {len(weights.index)}")
print(f"long only: {bool((weights >= -1e-6).all().all())}")
print(f"fully invested: {bool(weights.sum(axis=1).round(6).eq(1.0).all())}")
print(f"nav name: {portfolio.nav.name}")
assets: ['Global Bonds', 'Global IG Bonds', 'US Treasuries', 'US TIPs']
rebalance dates: 39
long only: True
fully invested: True
nav name: MaxDiv

readme_test.py executes the block above and diffs its output against that result fence, so this example cannot drift from what the package actually does. Structural facts are asserted rather than weights: a solver-version change may move an allocation by 1e-9, but it must not change the rebalance schedule, break long-only, or stop the book being fully invested.

The committed multi-asset fixture keeps this example offline. The same pipeline supports price panels with NaNs and different start dates, while preserving roll-forward estimation (no hindsight bias) and drift-aware turnover accounting.

Design scope

The solvers use quadratic and conic objectives (variance, tracking error, Sharpe ratio, diversification ratio, CARA utility); non-quadratic risk measures such as CVaR, MAD or drawdown constraints are out of scope, and Riskfolio-Lib or skfolio cover them. Each solver lives in its own module and plugs into the rolling backtester through one dispatch function: the software-design guide explains these boundaries, and the package comparison records the versioned evidence for the field comparison.

When to use it — and when not

Use optimalportfolios when you need a dated roll-forward pipeline from point-in-time covariance and alpha estimates to constrained targets, scheduled rebalancing, and drift-aware backtests with transaction costs across incomplete or mixed-frequency multi-asset panels.

Choose another package when the core problem is a non-quadratic risk measure such as CVaR, MAD, or drawdown constraints; the package comparison points to Riskfolio-Lib and skfolio for those workflows. Use factorlasso directly when you need its standalone sparse multi-output factor-model estimator rather than portfolio construction.

Package overview

Subpackage What it holds Articles
covar_estimation EWMA and HCGL factor covariance estimators, the qis.RiskModel adapter, covariance reports covariance estimators, factor covariance, risk contributions and betas
alphas Momentum, low-beta, carry, residual and managers' alpha signals, AlphasData, profiling and diagnostics alpha signals, signal diagnostics
optimization The rolling dispatcher, the general, risk-allocation, SAA and TAA solvers, Constraints, OptimiserConfig and solver diagnostics choosing an objective, constraints, solver outcomes
universe UniverseData and its transforms, such as unsmoothing universe data
utils Risk contributions, benchmark betas, weight drift, NaN filtering, Gaussian mixtures risk contributions and betas
reports Result plots, marginal backtests and optional PyBloqs reports analytics gallery

The software-design guide draws the module imports, and the API reference lists every public object.

Analytics at a glance

Area Current user-facing analytics
Alpha construction Momentum, low beta, risk-adjusted carry, managers alpha, residual momentum, residual reversal and rolling EWMA means; fixed-group and time-varying cluster scoring are supported.
Alpha evaluation Rank-portfolio profiling, cross-backtests, AlphasData, IC/IR panels, component diagnostics and comparison tables.
Covariance and dependence Current/rolling EWMA and HCGL sparse factor covariance; Pearson, Spearman and Gerber dependence choices, configurable correlation-distance transforms through factorlasso, and current/rolling covariance diagnostic reports.
Risk-cluster analytics Persistent cluster lineage, births/deaths/splits/merges and report tables/figures through factorlasso.cluster_lineage (analyze_cluster_lineage() and run_cluster_lineage_report()); the analyze_risk_clusters() and run_risk_label_report() aliases in optimalportfolios.covar_estimation.risk_labelling are deprecated.
General optimisation Minimum variance, quadratic utility, maximum Sharpe, maximum diversification, CARA Gaussian-mixture utility and minimum tracking error.
Risk allocation Constrained risk budgeting, point-in-time group risk budgets, date-varying rolling budgets, group Euler-risk attribution and external-linkage hierarchical risk parity.
SAA and TAA optimisation Minimum variance at target return, maximum return at target volatility, alpha over tracking error and alpha at target portfolio return.
Constraints and implementation Instrument/group bounds, exposure, turnover, tracking error, target return/volatility, benchmark-relative sector/style/beta limits, frozen holdings and current-to-model eligibility corridors.
Solver controls and diagnostics One covariance factorization per compatible CVXPY solve, input-contract validation, structured OptimizationOutcome/ConstraintResidual output, infeasibility diagnosis and run-level warning summaries.
Portfolio and risk results PortfolioOptimisationResult provides weights/trades, volatility, turnover, tracking error, factor/residual risk, group attribution, factor exposures, efficient-frontier data and report tables using qis.RiskModel.
Universe, backtest and reporting Validated UniverseData, metadata/group-loadings persistence and transforms, drift-aware rolling weights, transaction-cost backtests through qis, efficient-frontier plots, marginal portfolio backtests and optional PyBloqs HTML/PDF reports.

This table groups the analytics by workflow. The exact package-root import inventory and callable signatures are maintained in the API reference.

Architecture: factorlasso vs optimalportfolios

factorlasso is the domain-agnostic sparse factor-model estimator, with sign constraints, prior-centred regularisation and HCGL clustering; it knows nothing about asset returns, frequencies or rebalancing. optimalportfolios adds the finance layer: estimate_lasso_factor_covar_data() computes factor returns from prices, fits the factor model to the asset returns of each frequency and annualises the decomposition, and FactorCovarEstimator runs it on a rolling schedule. See factor covariance with HCGL.

Cluster-aware risk allocation

See hierarchical risk parity and cluster risk budgets for group risk budgets and hierarchical risk parity, including date-by-asset cluster labels, and the risk-budgeting guide for the solver that group budgets feed.

Alpha signals module

See the alpha signals guide for the signal catalogue, mixed-frequency inputs, cluster scoring, and the AlphasData container.

Table of contents

  1. Why optimalportfolios
  2. Package overview
  3. Cluster-aware risk allocation
  4. Alpha signals module
  5. Installation
  6. Portfolio Optimisers
  7. Examples
  8. Updates
  9. Disclaimer

Installation

Install from PyPI:

pip install optimalportfolios

After installing pytest, verify the installed wheel with python -m pytest --pyargs optimalportfolios.

Upgrade with:

pip install --upgrade optimalportfolios

Clone the repository with:

git clone https://github.com/ArturSepp/OptimalPortfolios.git

The core package supports Python >=3.10; pyproject.toml is the source of truth for dependency floors, and the installation guide describes the locked environment. Optional extras keep network-data and reporting integrations out of the core installation:

Extra Adds
data yfinance for free-data example loaders.
reports pybloqs for HTML/PDF report backends.
docs Sphinx, Furo and MyST for documentation builds.

For both runtime integrations, install optimalportfolios[data,reports]. There is no jupyter or dev extra; tests and static checks are the PEP 735 test and lint dependency groups, and CONTRIBUTING.md describes the contributor environment.

Portfolio optimisers

See the optimisation module guide for solver architecture, constraints, backends, and configuration. The rolling backtest guide covers rebalancing and transaction costs; supported examples provide complete runnable workflows.

Examples

The examples/ folder is organised by purpose. The examples and recipes guide maps every task to its article, canonical script and standalone examples, with each example's offline, network or local-data lane:

examples/
├── getting_started/       Canonical offline quickstart and its notebook mirror
├── data/                  Shared Yahoo loaders and local universe builders
├── solvers/               Objective-specific examples
├── backtests/             Complete rolling workflows
├── comparisons/           Comparisons of methods or configurations
├── covar_estimation/      Covariance and factor-model examples
├── alphas/                Signal profiling
├── reports/               Manually prepared portfolio reports
├── docs/                  Canonical scripts of the documentation pages
└── figures/               Existing documentation previews

Recommended reading order for newcomers

  1. Quickstart — the smallest offline portfolio workflow.
  2. examples/backtests/multiasset_saa.py — another offline workflow with group metadata and objective choices.
  3. examples/data/universe.py and examples/backtests/minimal_backtest.py — downloaded prices and reporting.
  4. examples/solvers/min_variance.py and examples/solvers/minimum_tracking_error.py — covariance-based construction.
  5. examples/solvers/tracking_error.py — benchmark-relative allocation with a signal.
  6. examples/comparisons/optimisers.py — how objectives differ on the same universe.

Highlighted demos

Optimal portfolio backtest

examples/backtests/minimal_backtest.py fetches eight ETFs, estimates an EWMA covariance, solves maximum diversification each quarter, backtests with transaction costs and writes a qis factsheet. The previews in this section are offline teaching exhibits on a fixed synthetic sample ending 31 December 2025, produced by the scripts named with each; the shared provenance record records their inputs, configuration, software versions and visual review.

Synthetic maximum-diversification portfolio growth and drawdowns Synthetic maximum-diversification target weights and contributions to annualized volatility

Customised reporting

PortfolioData from qis plots NAV, weights and return scatters. The preview from portfolio_reports.py shows quarterly target weights and realised trading costs.

Synthetic portfolio target weights and quarterly trading costs

Parameter sensitivity backtest

examples/comparisons/parameter_sensitivity.py backtests one method across estimation parameters; the preview from span_sensitivity.py compares five EWMA spans.

Synthetic maximum-diversification performance and trading costs across EWMA spans

Multi-optimiser cross-backtest

examples/comparisons/optimisers.py runs several objectives through compute_rolling_optimal_weights(); the preview from optimiser_comparison.py compares minimum variance, maximum diversification and equal risk budgets on shared inputs.

Synthetic net performance and trading costs for three covariance-only objectives

Multi-covariance-estimator backtest

examples/comparisons/covar_estimators.py backtests one objective with several covariance estimators; the preview from covariance_comparison.py compares six estimators on one known-factor simulation, which does not establish an estimator ranking.

Synthetic minimum-variance performance and covariance errors for six estimators

Drift-policy comparison (new in v5.3.1)

examples/comparisons/drift_policy.py compares OptimiserConfig.use_drifted_weights_0 = True (the default) with False under a binding turnover budget; see turnover and transaction costs.

Optimal allocation to cryptocurrencies

The paper's replication code is in papers/crypto_allocation_risk_2023; the cryptocurrency case study reports its design and results and runs the four methods offline.

Robust optimisation of strategic and tactical asset allocation

The paper's example is in papers/robust_optimisation_jpm_2026; the ROSAA case study reports the framework, its study design and results, and runs the same configuration offline.

Updates

See the changelog for release history and migration notes.

Acknowledgments

  • Thomas Schmelzer, creator of Jebel-Quant/rhiza, for substantial contributions to test coverage, cross-platform CI/CD, dependency auditing, example validation, packaging, and built-wheel verification.

References

Sepp A. (2023), "Optimal Allocation to Cryptocurrencies in Diversified Portfolios", Risk Magazine, October 2023, 1-6. Available at https://ssrn.com/abstract=4217841

Sepp A., Ossa I., and Kastenholz M. (2026), "Robust Optimization of Strategic and Tactical Asset Allocation for Multi-Asset Portfolios", The Journal of Portfolio Management, 52(4), 86-120. Paper link

Sepp A., Hansen E., and Kastenholz M. (2026), "Capital Market Assumptions and Strategic Asset Allocation Using Multi-Asset Tradable Factors", Under revision at the Journal of Portfolio Management. Available at https://ssrn.com/abstract=6785958

Ecosystem

This package is part of an open-source Python stack for quantitative finance. The ArturSepp profile is the canonical full catalogue:

Package Purpose
qis Performance analytics, factsheets, and visualisation
optimalportfolios (this package) Portfolio construction and backtesting
factorlasso Sparse factor models and factor covariance estimation
bbg-fetch Bloomberg data fetching
option-chain-analytics Point-in-time option-chain normalisation, reconstruction, querying, and visualisation
vanilla-option-pricers Vectorised vanilla option pricers and implied volatility fitters
stochvolmodels Stochastic volatility pricing analytics
trendfollowing Trend-following systems: closed-form theory and replication
privateassets Money-weighted multi-factor alpha from private-asset cash flows
goal-based-allocation Dynamic MV allocation under regime-switching jump-diffusions

Within the stack, optimalportfolios directly depends on qis for analytics and reporting and on factorlasso for sparse factor covariance estimation. The profile catalogue explains the other packages and their distinct boundaries.

Feedback & contributing

  • Bug: use the bug-report form with the package version, Python/platform, a minimal public-data reproducer, and expected versus actual output.
  • Feature: use the feature-request form and describe the user goal, current workaround, and smallest useful API. In particular: which constraint, report, or portfolio workflow cannot be expressed today?
  • Question or methodology: search or open an issue and name the paper, example, or convention involved.
  • Contribution: follow CONTRIBUTING.md; focused work is listed under good first issue and help wanted.

Project decisions, maintenance expectations, release policy, and best-effort support routes are documented in GOVERNANCE.md.

Citation

A machine-readable citation is available in CITATION.cff.

If you use optimalportfolios in your research, please cite it as:

@software{sepp2026optimalportfolios,
  author={Sepp, Artur},
  title={optimalportfolios: point-in-time multi-asset portfolio construction and rolling backtesting in Python},
  year={2026},
  version={7.9.0},
  url={https://github.com/ArturSepp/OptimalPortfolios}
}
@article{sepp2023,
  title={Optimal allocation to cryptocurrencies in diversified portfolios},
  author={Sepp, Artur},
  journal={Risk Magazine},
  pages={1--6},
  month={October},
  year={2023},
  url={https://ssrn.com/abstract=4217841}
}
@article{sepp2026rosaa,
  author={Sepp, Artur and Ossa, Ivan and Kastenholz, Mika},
  title={Robust Optimization of Strategic and Tactical Asset Allocation for Multi-Asset Portfolios},
  journal={The Journal of Portfolio Management},
  volume={52},
  number={4},
  pages={86--120},
  year={2026}
}
@article{sepphansenkastenholz2026,
  title={Capital Market Assumptions and Strategic Asset Allocation Using Multi-Asset Tradable Factors},
  author={Sepp, Artur and Hansen, Emilie H. and Kastenholz, Mika},
  journal={Working Paper},
  year={2026}
}

License

MIT — see LICENSE.txt.

Disclaimer

OptimalPortfolios package is distributed FREE & WITHOUT ANY WARRANTY under the MIT License.

See the LICENSE.txt in the release for details.

Use the dedicated routes in Feedback & contributing for bugs, feature requests, and methodology questions.

Releases

Packages

Used by

Contributors

Languages