An agent holding 50 positions across Polymarket, Kalshi, and sportsbooks isn’t managing 50 independent bets — it’s managing a portfolio. Portfolio variance is σ²_p = w’Σw, where Σ is the covariance matrix of outcomes. Diversification across uncorrelated bets reduces variance by 1/n without reducing expected return. Mean-variance optimization tells the agent exactly how much capital to allocate to each market given correlation structure.

Why This Matters for Agents

A single-market agent uses Kelly Criterion to size each bet independently. That works when the agent has one open position. The moment it holds positions across multiple markets simultaneously — 15 Polymarket contracts, 8 Kalshi markets, 20 sportsbook wagers — Kelly in isolation is wrong. It ignores the correlation between outcomes.

This is Layer 2 — Wallet. Portfolio theory governs how an agent’s wallet infrastructure allocates capital across concurrent positions. An agent holding YES on “Democrats win Senate” and YES on “Democrats win House” has highly correlated exposure — both bets move together. Without portfolio-level risk management, the agent thinks it has diversified across two bets when it actually has concentrated exposure to a single factor (Democratic performance). The covariance matrix quantifies this, and mean-variance optimization produces position sizes that account for it. This is the mathematical bridge between single-bet sizing and whole-portfolio risk management in the Agent Betting Stack.

The Math

Portfolio Return and Variance

An agent holds positions in n markets. Let wᵢ be the fraction of bankroll allocated to market i, edgeᵢ be the expected edge on market i (EV per dollar risked), and Xᵢ be the binary outcome (1 if the bet wins, 0 if it loses).

The portfolio’s expected return is:

E[R_p] = Σᵢ wᵢ × edgeᵢ

where edgeᵢ = pᵢ × payoffᵢ - costᵢ (the expected value per dollar).

The portfolio variance — the quantity we want to minimize or constrain — is:

σ²_p = w' Σ w = ΣᵢΣⱼ wᵢ wⱼ σᵢⱼ

where Σ is the n × n covariance matrix, w is the weight vector [w₁, w₂, …, wₙ], and σᵢⱼ = Cov(Xᵢ, Xⱼ) is the covariance between outcomes i and j.

Covariance of Binary Outcomes

Each bet outcome Xᵢ is a Bernoulli random variable with probability pᵢ. Its variance is:

Var(Xᵢ) = pᵢ(1 - pᵢ)

The covariance between two outcomes:

Cov(Xᵢ, Xⱼ) = E[XᵢXⱼ] - E[Xᵢ]E[Xⱼ] = P(Xᵢ=1 ∧ Xⱼ=1) - pᵢpⱼ

And the correlation coefficient:

ρᵢⱼ = Cov(Xᵢ, Xⱼ) / (σᵢ × σⱼ)
     = (P(Xᵢ=1 ∧ Xⱼ=1) - pᵢpⱼ) / √(pᵢ(1-pᵢ) × pⱼ(1-pⱼ))

where σᵢ = √(pᵢ(1 - pᵢ)).

The Diversification Theorem

For n equal-weight uncorrelated bets (ρᵢⱼ = 0 for all i ≠ j, wᵢ = 1/n), each with variance σ²:

σ²_p = Σᵢ (1/n)² × σ² = n × (1/n²) × σ² = σ²/n

Portfolio standard deviation = σ/√n. Expected return is unchanged: E[R_p] = (1/n) × n × edge = edge.

Fifty uncorrelated bets with 2% edge each produce:

  • Same expected return as one bet: 2%
  • 1/√50 ≈ 14.1% of the volatility of a single bet

This is the mathematical argument for multi-market agents. Diversification is a free lunch when correlations are zero.

When Correlations Are Not Zero

Real betting outcomes are correlated. The portfolio variance for two bets with correlation ρ:

σ²_p = w₁²σ₁² + w₂²σ₂² + 2w₁w₂ρσ₁σ₂
Correlation regimeρEffect on portfolio variance
Perfectly correlated+1.0No diversification — variance adds linearly
Highly correlated+0.7 to +0.9Minimal diversification benefit
Low correlation+0.05 to +0.15Strong diversification benefit
Uncorrelated0.0Maximum diversification benefit (σ²/n)
Negatively correlated-0.3 to -0.1Super-diversification — variance drops below σ²/n

Empirical Correlation Regimes in Betting

Different bet types exhibit predictable correlation structures:

Correlation Map — Betting Outcome Types
──────────────────────────────────────────────────────
Same-party political markets (Polymarket):   ρ ≈ 0.70 - 0.90
  "Dems win Senate" × "Dems win House"

Same-conference NFL games, same week:        ρ ≈ 0.05 - 0.10
  "Chiefs -7" × "Bills -3"

Same-sport different leagues:                ρ ≈ 0.00 - 0.03
  "Lakers ML" × "Man City ML"

Weather-correlated totals (outdoor):         ρ ≈ 0.20 - 0.40
  "Bears/Packers Over 42" × "Vikings/Lions Over 44"

Cross-sport, cross-region:                   ρ ≈ 0.00
  "PSG to win Ligue 1" × "Dodgers -1.5"

Crypto market × political outcome:           ρ ≈ 0.10 - 0.30
  "BTC above $100k by Dec" × "Trump wins election"
──────────────────────────────────────────────────────

The Efficient Frontier

The efficient frontier is the set of portfolios that maximize expected return for each level of variance (or equivalently, minimize variance for each level of return). Portfolios below the frontier are strictly dominated — an agent can do better without taking more risk.

The optimization problem:

Maximize:    E[R_p] = w'μ
Subject to:  w'Σw ≤ σ²_target
             Σwᵢ ≤ 1          (can't bet more than bankroll)
             0 ≤ wᵢ ≤ w_max   (position limits)

where μ = [edge₁, edge₂, …, edgeₙ] is the edge vector and Σ is the covariance matrix.

The Betting Sharpe Ratio

The Sharpe ratio analog for betting portfolios:

S = E[R_p] / σ_p = w'μ / √(w'Σw)

This is edge per unit of volatility. Higher is better. An agent should maximize Sharpe, not just maximize edge — a portfolio with 5% edge and 20% volatility (S = 0.25) is worse risk-adjusted than one with 3% edge and 6% volatility (S = 0.50).

Simultaneous Kelly and Portfolio Theory

The Kelly Criterion generalizes to simultaneous bets. For a portfolio of n bets with covariance matrix Σ and edge vector μ, the simultaneous Kelly allocation is:

f* = Σ⁻¹ μ

where Σ⁻¹ is the inverse of the covariance matrix. This reduces to standard Kelly f* = edge / variance for a single bet, and correctly accounts for correlation between bets. When two bets are positively correlated, Σ⁻¹ reduces both allocations compared to treating them independently.

Worked Examples

Example 1: Political Market Correlation on Polymarket

An agent holds three Polymarket positions in the 2026 midterm cycle:

Market A: "Republicans win Senate"       — YES at $0.57, model says 64%
Market B: "Republicans win House"        — YES at $0.62, model says 68%
Market C: "Democrats win AZ Governor"    — YES at $0.44, model says 51%

Edge calculations (EV formula from the expected value guide):

Edge_A = 0.64 × (1/0.57 - 1) × 0.57 - (1-0.64) × 0.57 = 0.64 - 0.57 = 0.07
Edge_B = 0.68 - 0.62 = 0.06
Edge_C = 0.51 - 0.44 = 0.07

Estimated correlations:

  • ρ(A, B) = 0.82 (same party, federal races — highly correlated)
  • ρ(A, C) = -0.45 (opposite party, partially offsetting)
  • ρ(B, C) = -0.40 (opposite party, partially offsetting)

Variances: σ²_A = 0.64 × 0.36 = 0.2304, σ²_B = 0.68 × 0.32 = 0.2176, σ²_C = 0.51 × 0.49 = 0.2499.

The covariance matrix:

        A        B        C
A   0.2304   0.1638  -0.1082
B   0.1638   0.2176  -0.0934
C  -0.1082  -0.0934   0.2499

where Cov(A,B) = ρ(A,B) × σ_A × σ_B = 0.82 × 0.4800 × 0.4665 = 0.1638.

Naive allocation (equal weight, w = [1/3, 1/3, 1/3]):

σ²_p = w'Σw = (1/9)(0.2304 + 0.2176 + 0.2499 + 2×0.1638 + 2×(-0.1082) + 2×(-0.0934))
     = (1/9)(0.6979 + 0.3276 - 0.2164 - 0.1868)
     = (1/9)(0.6223)
     = 0.0691
σ_p  = 0.263
E[R]  = (0.07 + 0.06 + 0.07)/3 = 0.0667
Sharpe = 0.0667 / 0.263 = 0.254

Optimized allocation (from mean-variance optimizer below): w = [0.15, 0.12, 0.35].

The optimizer reduces weight on A and B (highly correlated) and increases weight on C (negatively correlated with both). The negative correlation with C acts as a hedge, reducing portfolio variance substantially.

Example 2: Sportsbook Diversification

An agent bets NFL Week 12 on BetOnline with five positions:

Bet 1: Chiefs -7 at -110    → edge 3.2%   (model spread: -8.5)
Bet 2: Bills -3 at -110     → edge 2.1%   (model spread: -4.0)
Bet 3: Cowboys ML at +140   → edge 1.8%   (model win prob: 48%)
Bet 4: Packers/Bears O42 at -110 → edge 2.5% (model total: 44.1)
Bet 5: Raiders +6 at -110   → edge 1.5%   (model spread: +4.5)

These are all same-sport, same-day bets. Estimated pairwise correlations:

      B1     B2     B3     B4     B5
B1   1.00   0.08   0.05   0.03   0.06
B2   0.08   1.00   0.04   0.02   0.05
B3   0.05   0.04   1.00   0.01   0.07
B4   0.03   0.02   0.01   1.00   0.12
B5   0.06   0.05   0.07   0.12   1.00

Low correlations (ρ < 0.15 for all pairs). Near-maximum diversification benefit. Equal-weight portfolio variance is approximately σ²/5 — portfolio volatility is 45% of single-bet volatility with 100% of the expected return.

Compare this to an agent holding 5 NFC East game spreads: ρ ≈ 0.15-0.25 due to shared divisional dynamics, weather region, and schedule effects. Same number of bets, significantly less diversification.

Implementation

import numpy as np
from scipy.optimize import minimize
from dataclasses import dataclass


@dataclass
class BetPosition:
    """A single bet in the portfolio."""
    name: str
    edge: float        # expected edge per dollar risked
    win_prob: float    # estimated true probability
    market_price: float  # current market implied probability


def build_covariance_matrix(
    positions: list[BetPosition],
    correlation_matrix: np.ndarray
) -> np.ndarray:
    """
    Build the covariance matrix from win probabilities and correlations.

    For binary outcomes, Var(X) = p(1-p) and
    Cov(Xi, Xj) = rho_ij * sqrt(pi*(1-pi)) * sqrt(pj*(1-pj))

    Args:
        positions: List of BetPosition objects
        correlation_matrix: n x n matrix of pairwise correlations

    Returns:
        n x n covariance matrix
    """
    n = len(positions)
    stdevs = np.array([
        np.sqrt(pos.win_prob * (1 - pos.win_prob)) for pos in positions
    ])

    cov_matrix = np.zeros((n, n))
    for i in range(n):
        for j in range(n):
            cov_matrix[i, j] = correlation_matrix[i, j] * stdevs[i] * stdevs[j]

    return cov_matrix


def portfolio_stats(
    weights: np.ndarray,
    edges: np.ndarray,
    cov_matrix: np.ndarray
) -> dict:
    """
    Compute portfolio expected return, variance, and Sharpe ratio.

    Args:
        weights: Position size fractions (sum <= 1.0)
        edges: Expected edge per dollar for each position
        cov_matrix: Covariance matrix of outcomes

    Returns:
        Dict with expected_return, variance, volatility, sharpe
    """
    expected_return = weights @ edges
    variance = weights @ cov_matrix @ weights
    volatility = np.sqrt(variance)
    sharpe = expected_return / volatility if volatility > 0 else 0.0

    return {
        "expected_return": expected_return,
        "variance": variance,
        "volatility": volatility,
        "sharpe": sharpe
    }


def optimize_portfolio(
    positions: list[BetPosition],
    cov_matrix: np.ndarray,
    max_position: float = 0.25,
    max_total: float = 1.0,
    target_sharpe: bool = True
) -> dict:
    """
    Mean-variance optimization for a betting portfolio.

    Maximizes Sharpe ratio (edge/volatility) subject to position constraints.

    Args:
        positions: List of BetPosition objects
        cov_matrix: n x n covariance matrix
        max_position: Maximum fraction per single bet (default 25%)
        max_total: Maximum total allocation (default 100% of bankroll)
        target_sharpe: If True, maximize Sharpe. If False, maximize return
            for given variance.

    Returns:
        Dict with optimal weights, portfolio stats, and comparison to naive
    """
    n = len(positions)
    edges = np.array([pos.edge for pos in positions])

    # Objective: minimize negative Sharpe ratio
    def neg_sharpe(w):
        ret = w @ edges
        var = w @ cov_matrix @ w
        if var <= 0:
            return 1e10
        return -ret / np.sqrt(var)

    # Constraints
    constraints = [
        {"type": "ineq", "fun": lambda w: max_total - np.sum(w)}  # sum <= max_total
    ]

    # Bounds: each weight between 0 and max_position
    bounds = [(0, max_position)] * n

    # Initial guess: equal weight
    w0 = np.full(n, min(max_total / n, max_position))

    result = minimize(
        neg_sharpe,
        w0,
        method="SLSQP",
        bounds=bounds,
        constraints=constraints
    )

    optimal_weights = result.x
    optimal_stats = portfolio_stats(optimal_weights, edges, cov_matrix)

    # Compare to naive equal-weight
    naive_weights = np.full(n, min(max_total / n, max_position))
    naive_stats = portfolio_stats(naive_weights, edges, cov_matrix)

    return {
        "optimal_weights": {pos.name: round(w, 4) for pos, w in zip(positions, optimal_weights)},
        "optimal_stats": optimal_stats,
        "naive_stats": naive_stats,
        "sharpe_improvement": (
            (optimal_stats["sharpe"] - naive_stats["sharpe"]) / naive_stats["sharpe"] * 100
            if naive_stats["sharpe"] > 0 else 0
        )
    }


def compute_efficient_frontier(
    positions: list[BetPosition],
    cov_matrix: np.ndarray,
    max_position: float = 0.25,
    n_points: int = 50
) -> list[dict]:
    """
    Compute the efficient frontier: minimum variance for each return level.

    Args:
        positions: List of BetPosition objects
        cov_matrix: n x n covariance matrix
        max_position: Maximum fraction per single bet
        n_points: Number of points on the frontier

    Returns:
        List of dicts with return, variance, volatility, weights
    """
    n = len(positions)
    edges = np.array([pos.edge for pos in positions])

    # Find return range
    min_return = 0.0
    max_return = float(np.max(edges) * max_position * n)

    target_returns = np.linspace(min_return + 0.001, max_return * 0.95, n_points)
    frontier = []

    for target_ret in target_returns:
        def portfolio_variance(w):
            return w @ cov_matrix @ w

        constraints = [
            {"type": "ineq", "fun": lambda w: 1.0 - np.sum(w)},
            {"type": "ineq", "fun": lambda w, tr=target_ret: w @ edges - tr}
        ]
        bounds = [(0, max_position)] * n
        w0 = np.full(n, min(1.0 / n, max_position))

        result = minimize(
            portfolio_variance,
            w0,
            method="SLSQP",
            bounds=bounds,
            constraints=constraints
        )

        if result.success:
            var = result.fun
            frontier.append({
                "expected_return": target_ret,
                "variance": var,
                "volatility": np.sqrt(var),
                "weights": result.x.tolist()
            })

    return frontier


def simultaneous_kelly(
    edges: np.ndarray,
    cov_matrix: np.ndarray,
    fraction: float = 0.25
) -> np.ndarray:
    """
    Simultaneous Kelly: optimal growth-rate allocation for correlated bets.

    f* = fraction * Sigma^{-1} @ mu

    Args:
        edges: Edge vector (expected profit per dollar)
        cov_matrix: Covariance matrix
        fraction: Kelly fraction (0.25 = quarter-Kelly, 1.0 = full Kelly)

    Returns:
        Optimal allocation vector (negative values mean don't bet)
    """
    try:
        cov_inv = np.linalg.inv(cov_matrix)
    except np.linalg.LinAlgError:
        cov_inv = np.linalg.pinv(cov_matrix)

    kelly_weights = fraction * cov_inv @ edges

    # Floor at zero — don't take negative positions unless shorting is possible
    kelly_weights = np.maximum(kelly_weights, 0)

    return kelly_weights


# --- Full worked example ---

if __name__ == "__main__":
    # Political market portfolio on Polymarket
    positions = [
        BetPosition("GOP Senate", edge=0.07, win_prob=0.64, market_price=0.57),
        BetPosition("GOP House", edge=0.06, win_prob=0.68, market_price=0.62),
        BetPosition("Dem AZ Gov", edge=0.07, win_prob=0.51, market_price=0.44),
    ]

    # Correlation matrix (estimated from partisan alignment)
    corr_matrix = np.array([
        [ 1.00,  0.82, -0.45],
        [ 0.82,  1.00, -0.40],
        [-0.45, -0.40,  1.00]
    ])

    cov_matrix = build_covariance_matrix(positions, corr_matrix)

    print("=== Covariance Matrix ===")
    for i, pos in enumerate(positions):
        row = "  ".join(f"{cov_matrix[i,j]:8.4f}" for j in range(len(positions)))
        print(f"  {pos.name:12s}  {row}")

    print("\n=== Portfolio Optimization ===")
    result = optimize_portfolio(positions, cov_matrix, max_position=0.35)

    print(f"\nOptimal weights:")
    for name, weight in result["optimal_weights"].items():
        print(f"  {name:12s}: {weight:.1%}")

    print(f"\nOptimal portfolio:")
    print(f"  Expected return: {result['optimal_stats']['expected_return']:.2%}")
    print(f"  Volatility:      {result['optimal_stats']['volatility']:.2%}")
    print(f"  Sharpe ratio:    {result['optimal_stats']['sharpe']:.3f}")

    print(f"\nNaive equal-weight:")
    print(f"  Expected return: {result['naive_stats']['expected_return']:.2%}")
    print(f"  Volatility:      {result['naive_stats']['volatility']:.2%}")
    print(f"  Sharpe ratio:    {result['naive_stats']['sharpe']:.3f}")

    print(f"\nSharpe improvement: {result['sharpe_improvement']:.1f}%")

    # Simultaneous Kelly
    edges = np.array([pos.edge for pos in positions])
    kelly_alloc = simultaneous_kelly(edges, cov_matrix, fraction=0.25)

    print("\n=== Quarter-Kelly Allocation ===")
    for pos, alloc in zip(positions, kelly_alloc):
        print(f"  {pos.name:12s}: {alloc:.1%}")

    # Compare: what happens if agent ignores correlation?
    print("\n=== Independent Kelly (ignoring correlation) ===")
    for pos in positions:
        indep_kelly = 0.25 * pos.edge / (pos.win_prob * (1 - pos.win_prob))
        print(f"  {pos.name:12s}: {indep_kelly:.1%}")

    print("\nThe independent Kelly OVER-allocates to correlated bets A and B.")
    print("Simultaneous Kelly reduces their weights and increases the hedge (C).")

Limitations and Edge Cases

Correlation estimation is the weakest link. The entire framework assumes you know the covariance matrix. In practice, you estimate it from historical data, implied probability co-movements, or structural reasoning. Small errors in ρ produce large errors in optimal weights — a 0.10 error in ρ for two large positions shifts the optimal allocation meaningfully. Use robust estimators (shrinkage, Ledoit-Wolf) when working with more than 10 positions.

Binary outcomes violate normality assumptions. Markowitz assumes returns are normally distributed. Betting outcomes are Bernoulli (0 or 1). The mean-variance framework still works for portfolio construction, but confidence intervals and VaR calculations need adjustment. Monte Carlo simulation handles this correctly — simulate thousands of joint outcomes using the correlation structure rather than relying on normal approximations.

Correlation is not constant. Political market correlations shift as election day approaches. Weather correlations disappear in domed stadiums. An agent’s covariance matrix should be a living estimate, not a fixed parameter. Re-estimate weekly or after major information events.

Small portfolios don’t benefit much. With 3-5 bets, the optimizer can overfit to noise in the correlation estimates. Below 10 positions, simple heuristics (cap correlated exposure at 2× single-bet Kelly, diversify across sectors) often outperform formal optimization.

Transaction costs and liquidity. Mean-variance optimization ignores the cost of entering and exiting positions. On Polymarket, moving $50,000 into a thin market will shift the price against you. The optimizer says “allocate 25% here” but doesn’t account for the $3,000 in slippage to get filled. Layer in slippage estimates from the microstructure guide before executing.

The covariance matrix must be positive semi-definite. If you estimate correlations pairwise from different data sources, the resulting matrix may not be PSD. Use numpy.linalg.cholesky to test; if it fails, apply nearest-PSD correction via the Higham algorithm before optimizing.

FAQ

How do you calculate correlation between sports bets?

Correlation between sports bets is the Pearson correlation coefficient between binary outcomes (1 = win, 0 = loss) across historical events. Same-conference NFL games on the same day show ρ ≈ 0.05-0.10 due to shared weather and travel conditions. Same-party political markets on Polymarket correlate at ρ ≈ 0.7-0.9. Use implied probability time series from the Polymarket CLOB or Kalshi API as a proxy when historical resolution data is sparse.

What is the efficient frontier for sports betting?

The efficient frontier is the set of portfolio allocations that maximize expected edge for a given level of variance. Compute it using mean-variance optimization: maximize w’μ subject to w’Σw ≤ target_variance and position constraints, where w is the weight vector, μ is the edge vector, and Σ is the covariance matrix. Portfolios below the frontier are suboptimal — you can get more edge for the same risk, or the same edge with less risk.

How does diversification reduce risk in prediction market betting?

Diversification reduces portfolio variance without reducing expected return when bets are uncorrelated. For n uncorrelated bets of equal size with identical variance σ², portfolio variance drops to σ²/n. Fifty uncorrelated Polymarket positions with 2% edge each produce the same expected return as one position but with 1/√50 ≈ 14% of the volatility. This is the core argument for multi-market agents over single-market specialists.

How does portfolio theory connect to Kelly Criterion bet sizing?

Kelly Criterion sizes individual bets optimally in isolation. Portfolio theory extends this to simultaneous bets by accounting for correlation. The simultaneous Kelly solution is f* = Σ⁻¹μ, where Σ⁻¹ is the inverse covariance matrix and μ is the edge vector. Without correlation adjustment, an agent holding 10 correlated political bets is taking far more risk than Kelly intended. See the Kelly Criterion guide for the single-bet derivation.

What is the Sharpe ratio equivalent for sports betting agents?

The betting Sharpe ratio analog is the edge-to-volatility ratio: S = E[edge] / σ_portfolio. A portfolio with 3% expected edge and 8% volatility has S = 0.375. Higher is better. Unlike financial Sharpe ratios, betting Sharpe uses edge (not excess return over risk-free rate) because bet capital is locked only during the event window, not continuously invested.

What’s Next

Portfolio theory tells an agent how to allocate across markets. The next step is understanding what happens when correlation assumptions fail in multi-leg bets:

  • Next in the series: Correlation Risk in Parlays — what happens when “independent” parlay legs are secretly correlated, and how sportsbooks exploit this.
  • Validate with simulation: Monte Carlo Simulation for Prediction Markets — simulate 10,000 portfolio outcomes to stress-test your covariance assumptions.
  • Single-bet sizing foundation: The Kelly Criterion — the single-bet optimal sizing that portfolio theory generalizes.
  • Wallet infrastructure: The Agent Wallet Comparison covers how Layer 2 wallets enforce the position limits that portfolio optimization produces.
  • Find your edge first: The Arbitrage Calculator identifies cross-platform mispricings that become inputs to your portfolio optimizer.
  • Track the vig: The Vig Index shows real-time sportsbook overrounds — lower vig means higher edge inputs to your portfolio model.