Skip to content
Back to Academy

How Quantum Portfolio Optimization Helps Finance

Build a cardinality-constrained portfolio selector with a real three-body risk term and run it on Kipu's quantum optimizer. Part 1 of two.

TutorialIntermediate~20 minPart 1 of 2

Educational disclaimer. This is a hands-on starter tutorial in the beautiful field of quantum computing applications for finance, meant for learning. The numbers are illustrative and the models deliberately simplified; nothing here is complete or constitutes financial advice.


The rise of Quantum Computing in Finance

Picking a portfolio is a simple selection problem. You want high expected return, but that comes at the cost of higher risk. The goal is to balance risk-reward and diversify enough to balance out worst-case scenarios. Buying everything is not always an option: mandates, transaction costs, and tracking budgets force you to hold exactly k of n candidates. That last constraint, called a cardinality constraint, is what turns a textbook optimization into a hard combinatorial problem.

The number of ways to choose 125 items out of 250 is larger than the number of atoms in the observable universe. Classical solvers handle this with clever heuristics and branch-and-bound, and they do well up to a point. But the moment the objective stops being a clean convex function (real correlation structure, integer lot sizes, sector caps, higher-order interactions between holdings), the search space gets rugged and the heuristics stop working.

Aside: why model assumptions matter (the 2008 Gaussian-copula story)

The 2008 financial crisis was, in part, initiated by a mathematical model. Much of the credit-derivatives market priced correlated default risk with the Gaussian copula, a formula that compressed the joint behaviour of thousands of loans into a single pairwise correlation number. It was elegant, fast, and catastrophically wrong when the market panicked. Years before the crash even its inventor warned of its shortcomings. By 2009 Felix Salmon called it "The Formula That Killed Wall Street."

In a nutshell, a Gaussian model is pinned down entirely by means and pairwise covariances. So by construction it encodes no higher-order co-movement at all. Three or more assets moving together could convey a much stronger market signal than any combination of pairs. Correlations that appeared small and were treated as independent in calm markets snapped toward 1, diversification assumptions failed, and everything came crashing down together in September 2008: a so-called tail dependence failure mode. Classical optimization has long been pushed toward pairwise-only models because higher-order terms are expensive to add computationally. However, real markets are full of precisely that higher-order interlinked structure.

The regulatory aftermath of 2008 still shapes finance today. The Basel III reforms, and the Fundamental Review of the Trading Book (FRTB) within them, force banks to hold capital against exactly this kind of correlated market risk. Assembling a book that meets those capital rules at minimum cost is itself a constrained optimization, an adjacent problem we return to in the applications below.

Introduction to the quantum portfolio optimization problem

Let's be precise about what gap quantum closes. It does not make a risk model correct (garbage in, garbage out still holds). What it changes is tractability: a HUBO, the higher-order generalization of a QUBO, lets you write three-body and higher interactions directly, one variable per asset, and hand them to the optimizer as-is. The model structure can respect interdependence instead of the pairwise approximation. Now, quantum computers are limited in size and error budget. That means we are bound to smaller, representative instances of many problems. Over the course of the decade this will change dramatically, with many problems becoming viable around 2030, when many currently separate technologies merge into a fault-tolerant quantum computer.

This and many other reasons motivate the exploration of QPUs in the financial context. Not because quantum computers try every combination at once (they don't), but because a quantum optimizer is very good at exploring interlinked data structures. These usually produce rugged energy landscapes where classical solvers, like greedy and local-search methods, miss good low-cost configurations. Kipu and IonQ ran a first hardware demonstration of exactly this, a 20-asset portfolio on a trapped-ion device, back in 2023 (arXiv:2308.15475). A follow-up study in 2026 (arXiv:2602.23976) scaled the problem to 250 assets.

You may recognize the method if you work on…

  • Portfolio and index construction. Build a book under sector caps, lot sizes, and tracking-error limits.
  • Energy trading and dispatch. Select a constrained set of contracts whose payoffs co-move through fuel prices, weather, and load.
  • Risk-budgeted hedging. Cover exposure without doubling up on the same underlying factor.
  • Feature selection for factor models and ML trading signals. Kipu and IonQ ran exactly this higher-order problem on trapped-ion hardware (arXiv:2604.26834).
  • Regulatory capital optimization (Basel III / FRTB-SA). Assemble a book that meets capital rules at minimum cost. Kipu cut regulatory capital consumption by more than 10% on real bank portfolios (Capital Saving case study).

In this lab we will build the smallest version of this problem, run it on Kipu's quantum optimizer, and put it to the test with a three-body term below. Part 2 then scales the very same kernel to a 100-asset universe.


Step 1: Formulate for Kipu's Quantum Optimizer

Formulation is one of the most important steps in solving an optimization problem with a quantum computer. Its size (number of variables), density (non-zero terms), and order (linear, quadratic, cubic, ...) drive the circuit depth: the amount of sequential operations, or "length", of a quantum program. Current Quantum Processing Units are constrained in size (number of qubits) and error budget (hardware layout / qubit connectivity and 2-qubit-gate errors). Circuit depth is a decisive factor in whether a program can run within a given QPU's error budget.

1. Formulate

Encode an 8-asset portfolio as a HUBO, with a real three-body risk term a QUBO cannot express.

2. Run on a quantum optimizer

Submit it to Iskay on IBM Quantum, or Miray on the Kipu Quantum Hub. Same engine, your choice.

3. Read & verify

Decode the pick, brute-force check the optimum, and see the value on the risk/return frontier.

Changing the formulation to a problem- and hardware-specific one can lead to much better results. Many industry quantum teams engage in this exercise, often collaboratively, to achieve "Quantum Readiness". Even assuming perfect quantum computers, two questions remain: do you have suitable problems in your organisation, and can you formulate them so they are solvable? Sometimes useful problems are not runnable on current hardware, so it is worth checking regularly for upgrades.

1a. State the problem in numbers

We will pick 4 assets out of 8 S&P 500 stocks, trading expected return against the risk of holding correlated pairs. This problem is small enough to check by brute force, but structured enough to show the behaviour and understand how to scale it.

python
tickers = ["AAPL", "MSFT", "JPM", "XOM", "JNJ", "PG", "NVDA", "CVX"]
sector  = ["Tech", "Tech", "Financials", "Energy", "Health", "Staples", "Tech", "Energy"]

# Expected annual return, in percent (illustrative)
returns = [18.0, 16.0, 12.0, 11.0, 8.0, 7.0, 22.0, 10.0]

# High pairwise correlations: holding both concentrates risk
correlation = {
    (0, 1): 0.70,  # AAPL - MSFT   (mega-cap tech)
    (0, 6): 0.65,  # AAPL - NVDA
    (1, 6): 0.68,  # MSFT - NVDA
    (3, 7): 0.88,  # XOM  - CVX     (energy)
    (4, 5): 0.55,  # JNJ  - PG      (defensives)
}

k = 4   # hold exactly four names

1b. Encode it as a HUBO

Kipu's optimizer minimizes an objective written as a dictionary of variable interactions (a HUBO, the higher-order generalization of a QUBO). Each binary variable x_i is 1 if asset i is held, 0 if not. Four relationships influence the objective function:

  1. Reward return. A negative linear term -return_i for each asset, so the minimizer prefers holding high-return assets.
  2. Penalize correlated pairs. A positive quadratic term on each correlated pair. Prefer not to hold both.
  3. Model higher-order risk. A positive three-body term on the mega-cap tech triple (AAPL, MSFT, NVDA), so holding all three together costs more than the three pairwise penalties alone. This tries to model the joint-sector-crash co-movement from the opening section, and it is the term a QUBO cannot express efficiently. BF-DCQO handles these higher-order terms natively, with no quadratization and no auxiliary qubits, which is the subject of its own paper (arXiv:2409.04477).
  4. Enforce the count. A cardinality penalty (Σ x_i − k)² that is zero only when exactly k names are held, scaled large enough to dominate the return and risk terms.
The full objective, and how it becomes one HUBO dictionary

1. Add the four terms. Summed together, the four relationships above are a single objective the optimizer minimizes over the held/not-held vector x (each x_i ∈ {0, 1}):

H(x) = − Σ_i r_i x_i + λ Σ_{i<j} ρ_ij x_i x_j + Σ t_ijk x_i x_j x_k + P (Σ_i x_i − k)²

reading left to right: the return reward (1), the pairwise risk penalty (2), the three-body penalty (3), and the cardinality penalty (4), with return r_i, risk weight λ = risk_penalty, pairwise correlations ρ_ij, three-body penalty t, and cardinality weight P = card_penalty.

2. Flatten it to one dictionary. Kipu's optimizer does not take H(x) as algebra; it takes a flat dictionary keyed by the variable tuple each coefficient multiplies: () for the constant, (i,) for one variable, (i, j) for a pair, (i, j, k) for a triple. Three of the four terms already have that shape, return is linear (i,), pairwise risk is (i, j), the three-body term is (i, j, k), so they drop straight in.

3. Expand the one term that doesn't fit. (Σ x_i − k)² is a squared sum, not yet a sum of tuples. Expanding it (using x_i² = x_i for binary variables)

(Σ x_i − k)² = k² + (1 − 2k)·Σ x_i + 2·Σ_{i<j} x_i x_j

spreads it across three tuple types at once: a constant , a linear part on every (i,), and a pairwise part on every (i, j). Those land on the same keys the return and risk terms already use, so the contributions add rather than replace:

  • Linear key (AAPL): return reward −18.0 + count term P·(1 − 2k) = −350.0−368.0.
  • Pairwise key (AAPL·MSFT): correlation risk λ·ρ_01 = 7.0 + count term 2P = 100.0107.0.

So every coefficient the optimizer sees is just one of these sums. The code below assembles them in that order, constant, linear, pairwise, then the single three-body key.

python
risk_penalty = 10.0   # weight on correlation risk
card_penalty = 50.0   # must dominate return + risk so "exactly k" always wins
n = len(returns)

hubo = {"()": card_penalty * k**2}                      # constant term

for i, r in enumerate(returns):
    hubo[f"({i},)"] = -r + card_penalty * (1 - 2 * k)   # reward return, count term

for i in range(n):
    for j in range(i + 1, n):
        rho = correlation.get((i, j), 0.0)
        hubo[f"({i}, {j})"] = risk_penalty * rho + card_penalty * 2

# Higher-order term: holding ALL of AAPL, MSFT, NVDA together is worse
# than the three pairwise penalties suggest (joint-crash co-movement).
# A QUBO cannot represent this; a HUBO takes it as one more dictionary key.
hubo["(0, 1, 6)"] = 20.0

Encoding rule. Keep the cardinality penalty roughly 10× larger than the return-plus-risk range. Too small and the optimizer "cheats" by holding three or five names to dodge a correlation penalty; too large and the correlation-risk signal is drowned out. The constraint and the objective have to stay in balance.

1c. A trap a naive strategy might fall into

Before running quantum workflows, we compare to a naive and greedy baseline.

python
greedy = sorted(range(n), key=lambda i: -returns[i])[:k]
print([tickers[i] for i in sorted(greedy)])
# -> ['AAPL', 'MSFT', 'JPM', 'NVDA']

Three of those four (AAPL, MSFT, NVDA) are exactly the correlated tech triple we penalized. The naive pick maximizes headline return and walks straight into both the pairwise penalties and the three-body term. It concentrates risk in a single sector. A good portfolio optimizer refuses to do that.

Optional: formulate with the Qiskit optimization mapper

Hand-building the dictionary is the best way to understand the encoding. On a large instance with many constraints (sector caps, lot sizes, turnover limits) you do not want to hand-expand (Σ x − k)² and rebalance penalty weights each time. Qiskit ships a modeling addon, qiskit-addon-opt-mapper, that lets you state the problem declaratively (variables, objective, constraints) and emits the QUBO/HUBO for you. It also reads docplex and LP files, so an existing classical model comes across largely unchanged. For an example see Part 2.


Step 2: Run it on Kipu's Quantum Hub or IBM Qiskit Functions

Both platforms feature the same Quantum Optimizer by Kipu Quantum.

Option A: Iskay on IBM Quantum

Iskay is Kipu's optimizer delivered as a Qiskit Function on the IBM Quantum Platform. If your stack is IBM, get in touch with your IBM Engagement Manager to get access.

python
from qiskit_ibm_catalog import QiskitFunctionsCatalog  # pip install qiskit-ibm-catalog

catalog = QiskitFunctionsCatalog(
    token=IBM_TOKEN, channel="ibm_quantum_platform", instance=INSTANCE_CRN,
)
iskay = catalog.load("kipu-quantum/iskay-quantum-optimizer")

job = iskay.run(
    problem=hubo,
    problem_type="binary",
    backend_name="ibm_fez",
    options={"shots": 2000, "num_iterations": 4},
)

result = job.result()
print(result["solution_info"]["cost"], result["solution"])
# Tip: increase shots and num_iterations for better results, or reduce for faster runtime.

Option B: Miray on the Kipu Quantum Hub

Miray features the same BF-DCQO engine (arXiv:2405.13898) on the Kipu Quantum Hub, with a free simulator tier you can run with a one-line switch to hardware versions later. QHub account required. Related documentation pages: Quickstart (account + first run), Service SDK (the HubServiceClient below), access tokens (access_key_id / secret_access_key), and using a service (subscribe to the simulator).

python
from qhub.service.client import HubServiceClient  # pip install qhub-service

client = HubServiceClient(
    service_endpoint=SERVICE_ENDPOINT,      # Miray Advanced Quantum Optimizer - Simulator
    access_key_id=ACCESS_KEY_ID,
    secret_access_key=SECRET_ACCESS_KEY,
)

execution = client.run(request={
    "problem": hubo,
    "problem_type": "binary",
    "shots": 2000,
    "num_iterations": 6,
    "num_greedy_passes": 1,
})
execution.wait_for_final_state()

response = execution.result()
print(response.result["cost"], response.result["mapped_solution"])
# Tip: increase shots and num_iterations for better results, or reduce for faster runtime.

Step 3: Read & verify

A Kipu Hub service returns a result object, not just a bitstring. You read it with response.result["key"]: cost is the objective value the optimizer reached, mapped_solution is the held/not-held assignment keyed by your original asset index, and bitstring is the same solution in post-transpilation qubit order. The next three steps decode that object, brute-force check it, and plot what it bought you.

3a. Read the result

This is the actual output from running the Option B code above on the Miray Advanced Quantum Optimizer (Simulator) on the Kipu Quantum Hub, 2000 shots, 6 iterations, one greedy pass. Runtime: about 3 minutes.

output
cost = -56.5
mapped_solution = {0:1, 1:0, 2:1, 3:1, 4:0, 5:0, 6:1, 7:0}

Decode the selected names (variables set to 1):

python
sol = response.result["mapped_solution"]      # keys are string asset indices
selected = [tickers[int(i)] for i in sol if sol[i] == 1]
print(selected)
# -> ['AAPL', 'JPM', 'XOM', 'NVDA']

Let's have a look at what the optimizer did (lower cost is better):

StrategyHoldingsCostWhat happened
Naive (top-4 by return)AAPL · MSFT · JPM · NVDA−27.7holds the full tech triple
Quantum optimizerAAPL · JPM · XOM · NVDA−56.5diversified across 4 sectors

Keeping the two highest-return tech bets (NVDA at 22%, AAPL at 18%) but dropping the redundant third (MSFT, correlated 0.68 with NVDA and 0.70 with AAPL) in favor of JPM (financials) and XOM (energy) opens a 28.8-point gap. The optimizer preserved its return appetite while keeping modelled risk less concentrated. That is the value proposition of portfolio optimization.

3b. Verification. Never trust an optimizer you did not check

For a problem this small you can confirm the global optimum by brute force, and you always should before trusting any solver, classical or quantum. The energy function below recomputes the same objective the optimizer minimized. Complexity grows quickly, though, and favors quantum computers long-term.

python
from itertools import product

def energy(x):
    e = card_penalty * k**2
    for i in range(n):
        e += (-returns[i] + card_penalty * (1 - 2 * k)) * x[i]
    for i in range(n):
        for j in range(i + 1, n):
            rho = correlation.get((i, j), 0.0)
            e += (risk_penalty * rho + card_penalty * 2) * x[i] * x[j]
    e += 20.0 * x[0] * x[1] * x[6]   # the three-body tech-triple term
    return e

# 1. Cardinality holds
assert sum(sol[str(i)] for i in range(n)) == k

# 2. Reported cost matches a local recompute
x = tuple(sol[str(i)] for i in range(n))
assert abs(energy(x) - response.result["cost"]) < 1e-3

# 3. It is the true global optimum
best = min(product([0, 1], repeat=n), key=energy)
assert energy(best) == energy(x)
print("Verified: global optimum, exactly k held, energy consistent.")

All three checks pass on the verified run: exactly 4 names held, energy −56.5 matching the reported cost, and the selection equal to the brute-force optimum.

3c. Comparison. The risk/return frontier

A single number ("cost −56.5") does not show why the pick is good. Plot every feasible portfolio in risk/return space and the answer is visual. There are only 70 ways to choose 4 of 8, so we can draw all of them:

python
import itertools
import matplotlib.pyplot as plt

# Kipu brand colors (tokens.css)
GOLD, AMARANTH, BG, FG = "#b38f12", "#a52469", "#EDECEC", "#0f1319"

def port_return(bits):
    return sum(returns[i] for i in range(n) if bits[i])

def port_risk(bits):
    r = sum(rho for (i, j), rho in correlation.items() if bits[i] and bits[j])
    if bits[0] and bits[1] and bits[6]:
        r += 20.0 / risk_penalty            # three-body term, same risk scale
    return r

pts = []
for combo in itertools.combinations(range(n), k):
    bits = [1 if i in combo else 0 for i in range(n)]
    pts.append((port_risk(bits), port_return(bits)))

quantum = [1, 0, 1, 1, 0, 0, 1, 0]                          # the verified solution
gcombo  = sorted(range(n), key=lambda i: -returns[i])[:k]   # the naive pick
greedy  = [1 if i in gcombo else 0 for i in range(n)]

fig, ax = plt.subplots(figsize=(7, 5))
fig.patch.set_facecolor(BG); ax.set_facecolor(BG)
ax.scatter([p[0] for p in pts], [p[1] for p in pts], c="#cfcdcd", s=30, label="all portfolios")
ax.scatter(port_risk(quantum), port_return(quantum), c=GOLD, s=190, edgecolors="white", lw=1.5, zorder=3, label="Kipu quantum optimum")
ax.scatter(port_risk(greedy),  port_return(greedy),  facecolors="none", edgecolors=GOLD, s=190, lw=2.4, zorder=2, label="naive (top-4 return)")
ax.set_xlabel("Correlation risk score (lower is safer)", color=FG)
ax.set_ylabel("Expected return (%)", color=FG)
ax.set_title("4-of-8 portfolios: risk vs return", color=FG, fontweight="bold", loc="left")
for s in ("top", "right"): ax.spines[s].set_visible(False)
ax.legend(frameon=False)
plt.show()

The Pareto-efficient portfolios (no other has more return at less risk) come out to just three points:

RiskReturnPortfolioRole
0.0053JPM / XOM / JNJ / NVDAminimum-risk efficient
0.6563AAPL / JPM / XOM / NVDAquantum optimum (the knee)
4.0368AAPL / MSFT / JPM / NVDAnaive pick (max-return corner)

A honest reading is more nuanced than "the optimizer wins." The naive pick is not irrational: it sits on the frontier, at the maximum-return corner. It takes on large risk for the last bit of return: going from the quantum optimum to the naive pick buys +5 points of return for +3.4 points of risk, while the step into the optimum bought +10 return for +0.65 risk. Up to that point each unit of risk is still buying a lot of return. The risk appetite we encoded (risk_penalty = 10) controls exactly where on that curve the optimizer lands.


Your move

  1. Run the lab. Open the Kipu Quantum Hub, subscribe to the free Miray Advanced Quantum Optimizer (Simulator), and run the Option B code above. It is free and finishes in minutes.
  2. Swap in your universe. Replace the 8 tickers with your candidate list, or real data with return and correlation estimates. The encoding does not change.
  3. Scale it past the simulator. A single direct call tops out around 20 assets. Part 2: scaling to the S&P 500 decomposes a 100-asset universe into hardware-sized clusters and stitches them back together, the pipeline Kipu and IonQ used to reach 250 names on trapped-ion hardware.
  4. Bring your problem. If you run a finance or energy-trading book and want to assess practical fit on current hardware, the Kipu Quantum Academy is built for you.

Documentation: Kipu Quantum Hub | Hub docs | Iskay on IBM Quantum | Kipu Quantum Academy Research: Large-scale portfolio optimization on a trapped-ion quantum computer (arXiv:2602.23976) | BF-DCQO, the optimizer's algorithm (arXiv:2405.13898) | BF-DCQO for higher-order (HUBO) optimization (arXiv:2409.04477) | DCQO portfolio optimization on IonQ, 20 assets (arXiv:2308.15475)

Tested live on the Kipu Quantum Hub Miray simulator: the Step 3 run returned the verified global optimum (cost −56.5, AAPL/JPM/XOM/NVDA). The decomposition pipeline that scales this to 100 and 250 assets is built and verified in Part 2.

Happy Optimizing 🚀

Last tested on the Kipu Quantum Hub · 25 June 2026

Ready to build?

Run a finance or energy-trading book and want to assess practical fit on current hardware? Get in touch, or tell us if a step did not run for you.