Portfolio Optimization¶
qufin provides both classical and quantum portfolio optimization methods that share a common interface.
Classical Methods¶
Mean-Variance (Markowitz)¶
Solves the classic Markowitz problem using CVXPY with support for cardinality constraints.
from qufin.portfolio.classical.mean_variance import mean_variance, Objective
result = mean_variance(
mu, cov,
objective=Objective.MIN_VARIANCE, # or MAX_SHARPE, MAX_RETURN
cardinality=10, # max 10 assets
long_only=True, # no short selling
max_weight=0.15, # position limit
)
Objectives:
MIN_VARIANCE— Minimum variance portfolioMAX_SHARPE— Maximum Sharpe ratio (Cornuejols-Tutuncu transformation)MAX_RETURN— Maximum return subject to variance constraint
Black-Litterman¶
Combines market equilibrium with investor views.
from qufin.portfolio.classical.black_litterman import black_litterman
result = black_litterman(
cov=cov,
market_caps=caps,
P=np.array([[1, -1, 0, 0, 0]]), # Asset 0 outperforms Asset 1
Q=np.array([0.02]), # by 2%
)
# result.posterior_mu, result.posterior_cov
Hierarchical Risk Parity (HRP)¶
Machine-learning-based allocation using hierarchical clustering.
from qufin.portfolio.classical.hrp import hrp
result = hrp(returns, linkage_method="single")
# result.weights, result.cluster_order
Risk Parity¶
Equal risk contribution portfolio.
from qufin.portfolio.classical.risk_parity import risk_parity
result = risk_parity(cov, budget=None) # None = equal budget
# result.weights, result.risk_contributions
Quantum Methods¶
QUBO Formulation¶
All quantum portfolio optimizers work on a QUBO (Quadratic Unconstrained Binary Optimization) matrix.
from qufin.portfolio.qubo import PortfolioQUBO
qubo = PortfolioQUBO(
mu=mu, cov=cov,
gamma=1.0, # risk aversion
cardinality=5, # select 5 assets
sector_map={0: [0,1,2], 1: [3,4]},
sector_caps={0: 3, 1: 2},
turnover_penalty=0.01,
budget_penalty=1e4,
encoding="one_hot", # or "binary"
)
Q = qubo.build_matrix() # (n_qubits x n_qubits) QUBO matrix
QAOA Portfolio Optimizer¶
from qufin.portfolio.optimizers.qaoa import QAOAPortfolio, QAOAConfig
from qufin.backends.qiskit_backend import QiskitAerBackend
config = QAOAConfig(
p=3, # QAOA depth (layers)
mixer="xy_ring", # Hamming-weight-preserving mixer
cardinality=5, # must match QUBO cardinality
cvar_alpha=0.5, # CVaR tail optimization
shots=8192,
seed=42,
)
solver = QAOAPortfolio(qubo, config, QiskitAerBackend(seed=42))
result = solver.run()
Mixer options:
| Mixer | Preserves Hamming Weight | Circuit Depth | Best For |
|---|---|---|---|
x |
No | O(n) | Unconstrained |
xy_ring |
Yes | O(n) | Cardinality-constrained |
xy_full |
Yes | O(n^2) | Small problems, better mixing |
grover |
Yes | O(n) | Hard constraints |
VQE Portfolio Optimizer¶
Hardware-efficient ansatz with CVaR objective.
from qufin.portfolio.optimizers.vqe import VQEPortfolio, VQEConfig
config = VQEConfig(
reps=3,
entanglement="linear",
cvar_alpha=0.5,
shots=8192,
)
solver = VQEPortfolio(qubo, config, backend)
result = solver.run()
Exhaustive Solver (Small Problems)¶
Brute-force optimal solution for validation (up to 20 qubits).
from qufin.portfolio.optimizers.exhaustive import exhaustive_solve
result = exhaustive_solve(qubo, return_all=True)
# result.best_bitstring, result.best_objective, result.all_objectives
Advanced Portfolio Methods¶
Multi-Period Optimization¶
Optimize across multiple time periods with turnover penalties:
from qufin.portfolio.optimizers.multi_period import (
multi_period_optimize, MultiPeriodConfig,
)
config = MultiPeriodConfig(turnover_penalty=0.01, holding_cost=0.001)
result = multi_period_optimize(mu_series, cov_series, config=config)
# result.allocations, result.turnovers, result.total_objective
Factor Models (Fama-French)¶
Estimate factor exposures and decompose risk:
from qufin.portfolio.classical.factor_models import build_factor_model, risk_decomposition
model = build_factor_model(asset_returns, factor_returns, window=252)
# model.expected_returns, model.cov, model.exposures
decomp = risk_decomposition(weights, model.exposures, model.factor_cov)
print(f"Systematic: {decomp['systematic_pct']:.1%}")
Robust Portfolio (Worst-Case CVaR)¶
Optimize under parameter uncertainty:
from qufin.portfolio.optimizers.robust import RobustPortfolioOptimizer
optimizer = RobustPortfolioOptimizer(backend=backend)
result = optimizer.optimize(mu, cov, uncertainty_radius=0.1)
Szegedy Quantum Walk¶
Portfolio optimization via quantum walk on asset correlation graph:
from qufin.portfolio.optimizers.quantum_walk import SzegedyWalkOptimizer, SzegedyWalkConfig
config = SzegedyWalkConfig(n_steps=100, n_assets=15)
optimizer = SzegedyWalkOptimizer(backend=backend, config=config)
result = optimizer.optimize(mu, cov)
Sector Rotation¶
VQC-based regime detection for sector allocation:
from qufin.portfolio.sector_rotation import SectorRotator, RegimeDetector
detector = RegimeDetector(n_regimes=3)
rotator = SectorRotator(detector=detector)
allocation = rotator.allocate(market_data)
Comparison Example¶
from qufin.backtesting import BacktestEngine
engine = BacktestEngine(returns, train_window=252, test_window=63)
strategies = {
"Min Variance": lambda mu, cov: mean_variance(mu, cov).weights,
"Risk Parity": lambda mu, cov: risk_parity(cov).weights,
"HRP": lambda mu, cov: hrp_from_cov(cov).weights,
"Equal Weight": lambda mu, cov: np.ones(len(mu)) / len(mu),
}
results = engine.compare(strategies)
table = engine.comparison_table(results)