Portfolio Optimization
Source: examples/portfolio_opt/README.md
Select a portfolio of exactly B assets from N candidates to maximise expected return while penalising higher-order risk cross-interactions.
QUBO formulation
- Input: N asset returns, cubic risk interactions
(i, j, k, sigma), budget B - Model: N binary variables.
x_i = 1if asset i is selected. - Objective:
-sum(r_i * x_i) + sum(sigma_ijk * x_i * x_j * x_k)– first term maximises return (minimising its negation), second penalises correlated three-asset risk interactions. - Constraints: budget
sum(x_i) = B(EQUALITY with unit coefficients, penalty 200)
Encoding strategy
Return terms are linear: ADDLINE(i, -r_i) per asset.
Cubic risk terms (i, j, k, sigma) are degree-reduced:
REDUCE(i, j, P_AUX) -> w(Rosenberg enforcement forw = x_i * x_j)ADDQUAD(w, k, sigma)(sigma * w * x_k = sigma * x_i * x_j * x_k)
Budget constraint builds uniform-coefficient index/coeff vecs then calls
EQUALITY with target = B and penalty = 200. EQUALITY is emitted after all
objective (body) actions because it lands in the constraint section.
DSL methods used
model.reduce(var_a, var_b, p_aux)– HOBO degree reduction for cubic risk termsproblem.vec()– allocate index/coefficient vecs for the budget constraintmodel.apply_equality(indices, coeffs, target, penalty)– budget EQUALITY
Pipeline overview
- CP (
xqcp) – generate random returns and cubic risk interactions, declare binary variables, degree-reduce risk terms via REDUCE, and add a budget EQUALITY constraint. - Assemble –
.xqasmtext to bytecode viaxquad.asm - Encode – run encoder on chosen XQVM to produce the XQMX model
- Sample – solver runs SA/QPU/GPU over the model
- Verify – verifier checks budget constraint and computes energy
- Decode – decoder extracts the selected assets
Usage
uv run python examples/portfolio_opt/runner.py --seed 42
uv run python examples/portfolio_opt/runner.py --n 6 --budget 3 --interpreter rust
| Flag | Default | Description |
|---|---|---|
--n | 5 | Number of assets |
--budget | 2 | Number of assets to select |
--solver | dwave-cpu | Solver backend (see Choosing a solver) |
--interpreter | python | XQVM backend: python or rust |
--seed | 42 | Random seed |
-o | stdout | Write JSON result to file |
Choosing a solver
| Name | Hardware | Install |
|---|---|---|
dwave-cpu | CPU (default) | pip install xquad |
dwave-qpu | D-Wave Leap account | pip install xquad[dwave] |
cuda-gpu | NVIDIA CUDA GPU | pip install xquad[cuda] |
metal-gpu | Apple Silicon (macOS) | pip install xquad[metal] |
See GPU/QPU installation for driver prerequisites and xqsa solver quick-starts for per-solver parameter tuning.
Non-default solvers will not reproduce the canonical output (different
RNG/hardware). example-smoke always runs dwave-cpu.
The canonical output and its invariants are defined in the source README.