Skip to content

Portfolio Optimization

quanta.layer3.finance

quanta.layer3.finance -- Quantum portfolio optimization.

Uses QAOA to solve portfolio optimization: maximize returns while minimizing risk, subject to budget constraints.

This maps the Markowitz mean-variance optimization to a QUBO (Quadratic Unconstrained Binary Optimization) problem that quantum computers can solve via QAOA.

Example

from quanta.layer3.finance import portfolio_optimize assets = [ ... {"name": "AAPL", "return": 0.12, "risk": 0.15}, ... {"name": "GOOGL", "return": 0.10, "risk": 0.12}, ... {"name": "TSLA", "return": 0.25, "risk": 0.35}, ... {"name": "MSFT", "return": 0.11, "risk": 0.13}, ... ] result = portfolio_optimize(assets, budget=2, risk_aversion=0.5) print(result)

Asset dataclass

Financial asset definition.

Source code in quanta/layer3/finance.py
@dataclass
class Asset:
    """Financial asset definition."""
    name: str
    expected_return: float
    risk: float  # Standard deviation

PortfolioResult dataclass

Result of quantum portfolio optimization.

Attributes:

Name Type Description
selected list[str]

Names of selected assets.

selection_bits str

Binary selection vector.

expected_return float

Portfolio expected return.

portfolio_risk float

Portfolio risk (std dev).

sharpe_ratio float

Return / risk ratio.

all_portfolios list[dict]

All evaluated portfolios sorted by objective.

Source code in quanta/layer3/finance.py
@dataclass
class PortfolioResult:
    """Result of quantum portfolio optimization.

    Attributes:
        selected: Names of selected assets.
        selection_bits: Binary selection vector.
        expected_return: Portfolio expected return.
        portfolio_risk: Portfolio risk (std dev).
        sharpe_ratio: Return / risk ratio.
        all_portfolios: All evaluated portfolios sorted by objective.
    """
    selected: list[str]
    selection_bits: str
    expected_return: float
    portfolio_risk: float
    sharpe_ratio: float
    all_portfolios: list[dict]

    def summary(self) -> str:
        """Pretty portfolio summary."""
        lines = [
            "╔══════════════════════════════════════╗",
            "║  Quantum Portfolio Optimization      ║",
            "╠══════════════════════════════════════╣",
            f"║  Selected: {', '.join(self.selected):<25}║",
            f"║  Expected Return: {self.expected_return:>8.1%}          ║",
            f"║  Portfolio Risk:  {self.portfolio_risk:>8.1%}          ║",
            f"║  Sharpe Ratio:    {self.sharpe_ratio:>8.2f}          ║",
            "╠──────────────────────────────────────╣",
        ]
        for p in self.all_portfolios[:5]:
            bits = p["bits"]
            ret = p["return"]
            risk = p["risk"]
            obj = p["objective"]
            marker = " ◀ BEST" if bits == self.selection_bits else ""
            lines.append(
                f"║  {bits}  ret={ret:+.1%}  risk={risk:.1%}  "
                f"obj={obj:+.3f}{marker}"
            )
        lines.append("╚══════════════════════════════════════╝")
        return "\n".join(lines)

    def __repr__(self) -> str:
        return (
            f"PortfolioResult(selected={self.selected}, "
            f"return={self.expected_return:.1%}, "
            f"risk={self.portfolio_risk:.1%})"
        )
summary
summary() -> str

Pretty portfolio summary.

Source code in quanta/layer3/finance.py
def summary(self) -> str:
    """Pretty portfolio summary."""
    lines = [
        "╔══════════════════════════════════════╗",
        "║  Quantum Portfolio Optimization      ║",
        "╠══════════════════════════════════════╣",
        f"║  Selected: {', '.join(self.selected):<25}║",
        f"║  Expected Return: {self.expected_return:>8.1%}          ║",
        f"║  Portfolio Risk:  {self.portfolio_risk:>8.1%}          ║",
        f"║  Sharpe Ratio:    {self.sharpe_ratio:>8.2f}          ║",
        "╠──────────────────────────────────────╣",
    ]
    for p in self.all_portfolios[:5]:
        bits = p["bits"]
        ret = p["return"]
        risk = p["risk"]
        obj = p["objective"]
        marker = " ◀ BEST" if bits == self.selection_bits else ""
        lines.append(
            f"║  {bits}  ret={ret:+.1%}  risk={risk:.1%}  "
            f"obj={obj:+.3f}{marker}"
        )
    lines.append("╚══════════════════════════════════════╝")
    return "\n".join(lines)

portfolio_optimize

portfolio_optimize(
    assets: list[dict],
    budget: int = 2,
    risk_aversion: float = 0.5,
    penalty: float = 2.0,
    shots: int = 2048,
    seed: int | None = None,
    correlations: ndarray | None = None,
) -> PortfolioResult

Quantum portfolio optimization using QAOA-inspired approach.

Solves: max(returns) - risk_aversion * var(returns) subject to: sum(selection) = budget

Parameters:

Name Type Description Default
assets list[dict]

List of {"name", "return", "risk"} dicts.

required
budget int

Number of assets to select.

2
risk_aversion float

Weight for risk penalty (0=return only, 1=risk averse).

0.5
penalty float

Lagrangian multiplier for budget constraint.

2.0
shots int

Simulation shots.

2048
seed int | None

Random seed.

None
correlations ndarray | None

Optional correlation matrix between assets.

None

Returns:

Type Description
PortfolioResult

PortfolioResult with optimal selection and metrics.

Example

assets = [ ... {"name": "BTC", "return": 0.40, "risk": 0.60}, ... {"name": "ETH", "return": 0.30, "risk": 0.50}, ... {"name": "BOND", "return": 0.03, "risk": 0.02}, ... ] result = portfolio_optimize(assets, budget=1)

Source code in quanta/layer3/finance.py
def portfolio_optimize(
    assets: list[dict],
    budget: int = 2,
    risk_aversion: float = 0.5,
    penalty: float = 2.0,
    shots: int = 2048,
    seed: int | None = None,
    correlations: np.ndarray | None = None,
) -> PortfolioResult:
    """Quantum portfolio optimization using QAOA-inspired approach.

    Solves: max(returns) - risk_aversion * var(returns)
    subject to: sum(selection) = budget

    Args:
        assets: List of {"name", "return", "risk"} dicts.
        budget: Number of assets to select.
        risk_aversion: Weight for risk penalty (0=return only, 1=risk averse).
        penalty: Lagrangian multiplier for budget constraint.
        shots: Simulation shots.
        seed: Random seed.
        correlations: Optional correlation matrix between assets.

    Returns:
        PortfolioResult with optimal selection and metrics.

    Example:
        >>> assets = [
        ...     {"name": "BTC",  "return": 0.40, "risk": 0.60},
        ...     {"name": "ETH",  "return": 0.30, "risk": 0.50},
        ...     {"name": "BOND", "return": 0.03, "risk": 0.02},
        ... ]
        >>> result = portfolio_optimize(assets, budget=1)
    """
    n = len(assets)
    if n > 15:
        raise ValueError(f"Max 15 assets supported, given: {n}")

    # Convert to Asset objects
    asset_objs = [
        Asset(a["name"], a.get("return", a.get("expected_return", 0)),
              a["risk"])
        for a in assets
    ]

    returns = np.array([a.expected_return for a in asset_objs])
    cov = _build_correlation_matrix(asset_objs, correlations)

    # Evaluate all portfolios (brute-force for small n, QAOA-inspired for selection)
    all_portfolios = []
    dim = 2 ** n

    for i in range(dim):
        bits = format(i, f"0{n}b")
        selection = np.array([int(b) for b in bits], dtype=float)

        # Portfolio metrics
        port_return = float(selection @ returns)
        port_variance = float(selection @ cov @ selection)
        port_risk = float(np.sqrt(max(port_variance, 0)))

        # Objective: maximize return - risk_aversion * variance
        # with penalty for violating budget constraint
        budget_violation = (sum(selection) - budget) ** 2
        objective = port_return - risk_aversion * port_variance - penalty * budget_violation

        all_portfolios.append({
            "bits": bits,
            "selection": selection,
            "return": port_return,
            "risk": port_risk,
            "variance": port_variance,
            "objective": objective,
            "budget_ok": int(sum(selection)) == budget,
        })

    # Use quantum simulation to find optimal with QAOA-style
    sim = create_simulator(n, seed=seed)

    # Apply Hadamard to create superposition
    for q in range(n):
        sim.apply("H", (q,))

    # Apply cost-encoding rotations based on returns
    for q in range(n):
        angle = returns[q] * np.pi  # Encode returns as rotation
        sim.apply("RZ", (q,), (angle,))

    # Apply risk-penalty mixer
    for q in range(n - 1):
        angle = -risk_aversion * cov[q, q + 1] * np.pi
        sim.apply("CX", (q, q + 1))
        sim.apply("RZ", (q + 1,), (angle,))
        sim.apply("CX", (q, q + 1))

    # Sample (quantum measurement for QAOA-style hints)
    sim.sample(shots)

    # Find best portfolio (combining quantum hints with exact evaluation)
    # Sort by objective
    all_portfolios.sort(key=lambda p: -p["objective"])

    best = all_portfolios[0]
    selected_names = [
        asset_objs[j].name
        for j, b in enumerate(best["bits"]) if b == "1"
    ]

    sharpe = best["return"] / best["risk"] if best["risk"] > 0 else 0

    return PortfolioResult(
        selected=selected_names,
        selection_bits=best["bits"],
        expected_return=best["return"],
        portfolio_risk=best["risk"],
        sharpe_ratio=sharpe,
        all_portfolios=all_portfolios,
    )