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:
- Expected return: The probability-weighted average of possible returns over a period. In practice, estimated from historical returns with a forward-looking adjustment for trend.
- Variance and standard deviation: The statistical measure of return dispersion. Higher variance means higher volatility means higher risk (for the same expected return).
- Correlation matrix: An N x N matrix where each cell shows how two assets move together. Values range from -1 (perfect negative correlation, ideal for hedging) to +1 (perfect positive correlation, no diversification benefit). Typical crypto correlations: BTC/ETH ~0.85, BTC/stETH ~0.95, BTC/DeFi index ~0.75, BTC/stablecoins ~0.00.
- Sharpe ratio:
(portfolio_return - risk_free_rate) / portfolio_volatility. This is the efficiency metric — how much return does the portfolio generate per unit of risk. A Sharpe above 1.0 is considered good; above 2.0 is excellent. - Efficient frontier: The set of portfolios that offer the highest expected return for each level of volatility. Any portfolio not on the efficient frontier is suboptimal — you could get more return for the same risk, or the same return for less risk.
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:
- Stress-test the correlation matrix: Run the optimizer using a crisis-period correlation matrix (all crypto-crypto correlations set to 0.95) and compare the resulting weights to the normal-period optimum. The crisis weights will be much more defensive. Use a blend of both.
- Include non-correlated assets: Tokenized T-bills (BUIDL, USYC) have near-zero correlation to crypto price movements. Even a 25% allocation to RWAs substantially reduces portfolio volatility during a crypto downturn, at the cost of some upside participation in bull markets.
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:
- Time-based: Rebalance weekly at Sunday midnight UTC. This captures mean reversion from weekly crypto cycles without excessive transaction costs.
- Threshold-based: Rebalance immediately when any asset's weight drifts more than 5 percentage points from its target. This prevents single-asset runups from creating dangerous concentration.
- Volatility-based: When the portfolio's realized 7-day volatility exceeds 25% annualized, shift 10% from the highest-risk asset to USDC until volatility subsides. This is the agent's automatic risk-off mechanism.
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.