NEROX/Portfolio Optimization
Problem Type

Portfolio Optimization

Select assets and allocations that maximize expected return for a given risk tolerance — with discrete constraints that classic solvers can't handle.

Why QUBO for portfolio selection?

Classical mean-variance optimization (Markowitz) assumes continuous weights. Real portfolios have discrete constraints: minimum lot sizes, maximum position counts (cardinality constraints), sector exposure limits, and integer share quantities. These constraints make the problem combinatorially hard and incompatible with quadratic programming solvers.

NEROX encodes discrete portfolio selection as a QUBO and solves it with GPU annealing, handling all integer and combinatorial constraints natively.

Mean-variance QUBO formulation

python
import nerox
import numpy as np

# Asset universe
n_assets = 100
n_select = 10      # exactly 10 assets in portfolio

# Historical return estimates and covariance matrix
mu = np.random.randn(n_assets) * 0.08 + 0.06   # expected annual return
Sigma = np.cov(np.random.randn(n_assets, 252))   # covariance from 252 trading days

# Risk aversion parameter (higher = more conservative)
risk_aversion = 2.0

client = nerox.Client()
job = client.optimize.portfolio(
    expected_returns=mu,
    covariance=Sigma,
    risk_aversion=risk_aversion,
    n_assets_to_select=n_select,   # cardinality constraint
    solver="gpu",
)

result = job.wait()
selected = np.where(result.solution)[0]
print(f"Selected assets: {selected}")
print(f"Portfolio return: {mu[selected].mean():.3f}")
print(f"Portfolio risk (std): {np.sqrt(result.objective):.4f}")

Adding sector constraints

python
# Limit exposure to any single sector
sectors = np.array([0,0,0,1,1,1,2,2,2,...])   # sector label per asset
max_per_sector = 3

job = client.optimize.portfolio(
    expected_returns=mu,
    covariance=Sigma,
    risk_aversion=risk_aversion,
    n_assets_to_select=n_select,
    sector_labels=sectors,
    max_assets_per_sector=max_per_sector,
    solver="gpu",
)

Index tracking

Construct a sparse portfolio that closely tracks a benchmark index using a subset of assets. Supply the index weights and NEROX minimizes tracking error subject to a cardinality limit.

python
index_weights = np.ones(n_assets) / n_assets   # equal-weight index

job = client.optimize.portfolio(
    covariance=Sigma,
    index_weights=index_weights,
    n_assets_to_select=20,         # track index with 20 stocks
    objective="tracking_error",
    solver="gpu",
)

Supported constraints

Cardinality — select exactly K assets
Minimum / maximum weight per asset
Sector exposure limits (max N assets per sector)
Long-only or long-short portfolios
Turnover constraints (max deviation from current portfolio)
Integer lot sizes (discrete share quantities)