- Finance
Citigroup: QAOA for Multi-Period Portfolio Optimization and Quantum PCA
Citigroup
Citigroup tested QAOA for multi-period portfolio optimization with transaction cost constraints and implemented quantum PCA using phase estimation, assessing resource requirements for practical quantum advantage in fixed income and equity portfolio management.
- Key Outcome
- QAOA was competitive with classical heuristics on 8-asset instances. Multi-period constraints significantly increased qubit requirements. Quantum PCA on a 4x4 covariance matrix reproduced classical PCA eigenvalues. Resource analysis showed full quantum advantage requires fault-tolerant hardware far from current capability. Citi published technical findings in partnership with IBM.
The Problem
Static mean-variance optimization selects the best portfolio for a single period. Real asset managers rebalance monthly or quarterly, incurring transaction costs each time. Multi-period portfolio optimization finds the sequence of portfolios over T periods that maximizes total return net of transaction costs, a problem that grows combinatorially with T, N (assets), and position constraints.
Citigroup’s quant division explored two applications: QAOA for multi-period optimization with explicit transaction cost penalties, and quantum PCA for extracting risk factors from the covariance matrix of asset returns.
Multi-Period QUBO with Transaction Costs
Binary variable x_{t,i} = 1 means asset i is held in period t. The QUBO objective minimizes negative return plus risk minus transaction cost penalties for position changes between periods.
import numpy as np
from qiskit_optimization import QuadraticProgram
from qiskit_optimization.converters import QuadraticProgramToQubo
from qiskit_algorithms import QAOA
from qiskit_algorithms.optimizers import COBYLA
from qiskit.primitives import Sampler
from qiskit_optimization.algorithms import MinimumEigenOptimizer
from itertools import combinations
n_assets, n_periods, n_select = 8, 2, 3
txn_cost, risk_aversion = 0.01, 2.0
np.random.seed(7)
returns = np.random.normal(0.08, 0.15, (n_periods, n_assets))
cov = np.eye(n_assets) * 0.02 + np.random.rand(n_assets, n_assets) * 0.005
cov = (cov + cov.T) / 2
qp = QuadraticProgram(name="MultiPeriodPortfolio")
for t in range(n_periods):
for i in range(n_assets):
qp.binary_var(name=f"x_{t}_{i}")
linear_terms, quadratic_terms = {}, {}
for t in range(n_periods):
for i in range(n_assets):
linear_terms[f"x_{t}_{i}"] = (
linear_terms.get(f"x_{t}_{i}", 0) - returns[t, i]
)
for i in range(n_assets):
for j in range(i, n_assets):
vi, vj = f"x_{t}_{i}", f"x_{t}_{j}"
coeff = risk_aversion * cov[i, j] * (1 if i == j else 2)
key = (vi, vj) if i != j else (vi, vi)
quadratic_terms[key] = quadratic_terms.get(key, 0) + coeff
# XOR penalty for transaction costs: x_curr + x_prev - 2 * x_curr * x_prev
for t in range(1, n_periods):
for i in range(n_assets):
vp, vc = f"x_{t-1}_{i}", f"x_{t}_{i}"
linear_terms[vc] = linear_terms.get(vc, 0) + txn_cost
linear_terms[vp] = linear_terms.get(vp, 0) + txn_cost
quadratic_terms[(vp, vc)] = (
quadratic_terms.get((vp, vc), 0) - 2 * txn_cost
)
qp.minimize(linear=linear_terms, quadratic=quadratic_terms)
for t in range(n_periods):
qp.linear_constraint(
linear={f"x_{t}_{i}": 1 for i in range(n_assets)},
sense="==", rhs=n_select, name=f"card_{t}"
)
qubo = QuadraticProgramToQubo().convert(qp)
print(f"QUBO variables: {qubo.get_num_vars()}")
qaoa = QAOA(sampler=Sampler(), optimizer=COBYLA(maxiter=400), reps=2)
result = MinimumEigenOptimizer(qaoa).solve(qubo)
print(f"QAOA objective: {result.fval:.4f}")
Quantum PCA via Phase Estimation
Quantum PCA uses quantum phase estimation to extract eigenvalues of the return covariance matrix, potentially exponentially faster than classical PCA given quantum-accessible data.
from qiskit import QuantumCircuit
from qiskit.circuit.library import PhaseEstimation
from qiskit.quantum_info import Operator
from qiskit_aer import AerSimulator
from scipy.linalg import expm
sigma = np.array([
[0.040, 0.012, 0.008, 0.005],
[0.012, 0.025, 0.006, 0.003],
[0.008, 0.006, 0.030, 0.009],
[0.005, 0.003, 0.009, 0.020],
])
print("Classical eigenvalues:", np.round(np.linalg.eigvalsh(sigma)[::-1], 5))
sigma_norm = sigma / np.trace(sigma)
t = np.pi
unitary_op = Operator(expm(1j * t * sigma_norm))
n_eval, n_state = 4, 2
qpe_circuit = QuantumCircuit(n_eval + n_state, n_eval)
qpe_circuit.compose(
PhaseEstimation(num_evaluation_qubits=n_eval, unitary=unitary_op),
inplace=True
)
qpe_circuit.measure(range(n_eval), range(n_eval))
counts = AerSimulator().run(qpe_circuit, shots=4096).result().get_counts()
phases = {}
for bits, cnt in counts.items():
ev = int(bits, 2) / (2 ** n_eval) / t * np.trace(sigma)
phases[ev] = phases.get(ev, 0) + cnt
for ev, cnt in sorted(phases.items(), key=lambda x: -x[1])[:4]:
print(f" {ev:.5f} ({cnt} shots)")
Results
QAOA at p=2 found portfolios within 5-8% of brute-force optimal on 8-asset, 2-period instances. Multi-period constraints added ~30% more QUBO variables, pushing qubit counts to 18-22. QPE reproduced the two largest classical eigenvalues accurately. The critical barrier for quantum PCA at scale is quantum RAM: loading a 500-asset covariance matrix requires hardware that does not yet exist, eliminating the exponential speedup. Citi flagged the transaction cost QUBO formulation as the primary reusable contribution.
Learn more: Qiskit Reference