Research

Portfolio Construction for AI Agents: Modern Portfolio Theory Meets Crypto

Purple Flea Team · March 6, 2026 · 9 min read

Introduction

Harry Markowitz's 1952 paper on Modern Portfolio Theory introduced a deceptively simple idea: you should maximize expected return for a given level of risk by diversifying across assets that do not move in lockstep. The math behind this insight — the efficient frontier, mean-variance optimization, the Sharpe ratio — has shaped institutional portfolio management for over seven decades.

Applying MPT to crypto is complicated by one well-documented problem: in a crisis, everything drops together. The correlation between BTC and ETH is typically around 0.85 during normal market conditions, but approaches 1.0 during liquidation cascades. Despite this limitation, MPT provides the most rigorous mathematical framework available for agent portfolio construction — and when supplemented with correlation stress testing, it produces substantially better outcomes than naive equal-weighting.

Core MPT Concepts for Developers

Before writing any code, the key mathematical primitives:

Data Collection via Purple Flea API

Portfolio optimization requires historical return data for each asset. Purple Flea's market history endpoint provides daily OHLCV data for all supported assets, making it the simplest data source for agent portfolio construction:

import requests
import numpy as np

PF_BASE = "https://api.purpleflea.com"
HEADERS = {"Authorization": "Bearer pf_live_your_key_here"}

def get_historical_returns(symbols: list, days: int = 90) -> dict:
    """
    Fetch historical log return series for each symbol.

    Returns:
        dict: {symbol: np.array of daily log returns}
    """
    returns = {}
    for symbol in symbols:
        r = requests.get(
            f"{PF_BASE}/v1/markets/{symbol}/history",
            params={"days": days, "interval": "1d"},
            headers=HEADERS
        ).json()

        prices = [d["close"] for d in r["candles"]]
        if len(prices) < 2:
            continue

        # Log returns are preferred for multi-period compounding
        log_returns = np.diff(np.log(prices))
        returns[symbol] = log_returns

    return returns

# Example usage
symbols = ["BTC", "ETH", "stETH", "USDC"]
returns = get_historical_returns(symbols, days=90)
print({k: len(v) for k, v in returns.items()})
# {"BTC": 89, "ETH": 89, "stETH": 89, "USDC": 89}

Building the Correlation Matrix

With return series for each asset, compute the Pearson correlation matrix. This tells the optimizer how much diversification benefit each pair provides:

def compute_correlation_matrix(returns: dict) -> tuple:
    """
    Builds a numpy correlation matrix from return series.
    Returns: (correlation_matrix, list_of_symbols)
    """
    symbols = list(returns.keys())
    # Align all return series to the same length (use minimum)
    min_len = min(len(v) for v in returns.values())
    aligned = np.array([returns[s][-min_len:] for s in symbols])  # shape: (n_assets, n_days)

    corr_matrix = np.corrcoef(aligned)
    return corr_matrix, symbols

corr, syms = compute_correlation_matrix(returns)
print("BTC/ETH correlation:", corr[syms.index("BTC"), syms.index("ETH")])
# Output: ~0.84

The high correlation between BTC and ETH means holding both provides less diversification than their individual expected returns suggest. The optimizer will naturally underweight the more volatile of the two relative to a naive equal-weight approach, unless you constrain minimum holdings for each.

Mean-Variance Optimization

The optimization problem: find the portfolio weights that maximize the Sharpe ratio, subject to weights summing to 1 and all weights being non-negative (no short selling). This is a convex quadratic programming problem solvable with scipy.optimize:

from scipy.optimize import minimize

def optimize_sharpe(returns: dict, risk_free_rate: float = 0.05/252) -> dict:
    """
    Find portfolio weights maximizing Sharpe ratio.

    risk_free_rate: daily risk-free rate (e.g., 5% annual / 252 trading days)
    """
    symbols = list(returns.keys())
    min_len = min(len(v) for v in returns.values())
    ret_matrix = np.array([returns[s][-min_len:] for s in symbols])

    mean_returns = ret_matrix.mean(axis=1)
    cov_matrix = np.cov(ret_matrix)
    n = len(symbols)

    def neg_sharpe(weights):
        port_return = np.dot(weights, mean_returns)
        port_vol = np.sqrt(weights.T @ cov_matrix @ weights)
        if port_vol == 0:
            return 0
        return -(port_return - risk_free_rate) / port_vol

    constraints = [{"type": "eq", "fun": lambda w: np.sum(w) - 1}]
    bounds = [(0.0, 1.0)] * n
    x0 = np.ones(n) / n  # Start from equal weights

    result = minimize(neg_sharpe, x0, method="SLSQP",
                      bounds=bounds, constraints=constraints,
                      options={"maxiter": 1000, "ftol": 1e-9})

    optimal_weights = result.x
    return {sym: round(float(w), 4) for sym, w in zip(symbols, optimal_weights)}

weights = optimize_sharpe(returns)
# {"BTC": 0.22, "ETH": 0.18, "stETH": 0.14, "USDC": 0.46}

The Crypto Correlation Challenge

The MPT optimizer assumes correlations are stable over time. In crypto, they are not. During normal periods, BTC/ETH correlation averages 0.84. During the March 2020 crash, the Luna collapse, and the FTX implosion, essentially every crypto asset moved toward correlation 1.0 simultaneously — eliminating the theoretical diversification benefit at exactly the moment you needed it most.

Two practical responses to this:

Practical Portfolio for a $10K Agent

Given the optimization results and the practical need to maintain some minimum allocation to each strategy, a realistic starting portfolio for a $10K agent looks like:

Asset Weight Expected Annual Return Approx. Sharpe
BTC 25% 40% 1.2
ETH 20% 35% 1.1
stETH 15% 38% (price + 4% yield) 1.15
USDC / RWA 25% 5% (T-bill yield) 3.5
Active Trading PnL 15% Variable Variable

The USDC/RWA bucket serves a dual purpose: it provides a high-Sharpe anchor that improves the overall portfolio Sharpe ratio, and it provides dry powder for the active trading allocation when the agent identifies high-conviction opportunities.

Dynamic Rebalancing Triggers

The portfolio should be rebalanced according to one of three trigger types. Multiple triggers can fire simultaneously; the agent should process the most urgent first:

Portfolio Manager Implementation

The PortfolioManager class wraps the optimization and rebalancing logic into a simple interface your agent can call on a schedule:

class PortfolioManager:
    def __init__(
        self,
        target_weights: dict,
        rebalance_threshold: float = 0.05,
        vol_threshold: float = 0.25,
        api_key: str = None
    ):
        self.targets = target_weights
        self.threshold = rebalance_threshold
        self.vol_threshold = vol_threshold
        self.headers = {"Authorization": f"Bearer {api_key}"}

    def needs_rebalance(self, current_weights: dict) -> tuple[bool, str]:
        """
        Returns (should_rebalance, reason).
        """
        for asset, target in self.targets.items():
            drift = abs(current_weights.get(asset, 0) - target)
            if drift > self.threshold:
                return True, f"{asset} drifted {drift:.1%} from target {target:.1%}"

        # Check portfolio volatility
        port_vol = self._get_portfolio_vol(current_weights)
        if port_vol > self.vol_threshold:
            return True, f"Portfolio vol {port_vol:.1%} exceeds threshold {self.vol_threshold:.1%}"

        return False, "Portfolio within tolerance"

    def rebalance(self) -> dict:
        current = self._get_current_weights()
        should_rebalance, reason = self.needs_rebalance(current)

        if not should_rebalance:
            return {"action": "none", "reason": reason}

        # Calculate trades needed
        trades = []
        total_value = self._get_total_value()
        for asset, target in self.targets.items():
            current_pct = current.get(asset, 0)
            delta_pct = target - current_pct
            if abs(delta_pct) > 0.01:  # Minimum 1% trade
                trades.append({
                    "asset": asset,
                    "direction": "buy" if delta_pct > 0 else "sell",
                    "amount_usd": abs(delta_pct) * total_value,
                })

        result = requests.post(
            f"{PF_BASE}/v1/portfolio/rebalance",
            json={"trades": trades},
            headers=self.headers
        ).json()
        return {"action": "rebalanced", "trades": trades, "tx": result, "trigger": reason}

    def _get_portfolio_vol(self, weights: dict) -> float:
        """Calculate portfolio realized volatility from constituent vols and correlations."""
        returns = get_historical_returns(list(weights.keys()), days=7)
        if not returns:
            return 0.0
        corr, syms = compute_correlation_matrix(returns)
        w = np.array([weights.get(s, 0) for s in syms])
        vols = np.array([returns[s].std() for s in syms]) * np.sqrt(252)
        cov = np.outer(vols, vols) * corr
        port_vol = float(np.sqrt(w.T @ cov @ w))
        return port_vol

Conclusion

Modern Portfolio Theory, despite being developed decades before crypto existed, gives AI agents a mathematically rigorous framework for portfolio construction. The key adaptations for crypto are: stress-testing the correlation matrix against crisis-period data, including non-correlated assets (RWAs, stablecoins) to reduce tail risk, and implementing dynamic rebalancing that responds to volatility regime changes rather than running on a fixed schedule alone.

Execute your optimized portfolio allocations using /trading-api for spot and perpetuals, /crypto-wallet-api for multi-chain custody, and /staking-api for the liquid staking component. Run the optimization monthly to account for changing correlation regimes.

Related reading: For the risk layer that sits above portfolio construction, see A Risk Framework for AI Agent DeFi Operations. For protocol-level exposure management once you have a portfolio, see How AI Agents Can Manage a DAO Treasury.