Markowitz-portefeuilletheorie voor Crypto: Van Nul tot Held
Optimale cryptoportefeuilles bouwen met Python - want YOLO is geen strategie
Markowitz-portefeuilletheorie: wiskundige optimalisatie toegepast op digitale activa om het rendement te maximaliseren voor een gegeven risiconiveau.
Inleiding: waarom je cryptoportefeuille wiskunde nodig heeft (niet alleen buikgevoel)
Hé crypto degens! 👋
Weet je nog die keer dat je je hele stack in DOGE gooide omdat Elon iets had getweet? Of toen je tijdens de laatste crash in paniek alles verkocht? Ja, we zijn er allemaal geweest. Vandaag gaan we het hebben over iets dat je portefeuille (en je gemoedsrust) zou kunnen redden: de Markowitz-portefeuilletheorie.
Harry Markowitz won hier in 1990 letterlijk een Nobelprijs voor. Het basisidee? Je kunt je portefeuille wiskundig optimaliseren om het best mogelijke rendement te behalen bij elk gegeven risiconiveau. Het is alsof je een GPS hebt voor je investeringen in plaats van geblinddoekt te rijden.
Het kernconcept: risico versus rendement (de eeuwige dans)
Voordat we in de code duiken, moeten we begrijpen waar we mee te maken hebben:
- Verwacht rendement: hoeveel geld je verwacht te verdienen
- Risico (volatiliteit): hoeveel de waarde van je portefeuille schommelt
- Correlatie: hoe vergelijkbaar verschillende activa samen bewegen
De magie gebeurt wanneer je activa combineert die niet perfect synchroon bewegen. Wanneer Bitcoin crasht, houden sommige DeFi-tokens misschien beter stand. Dat is diversificatie die voor jou werkt.
Risico versus rendement: het balanceren van volatiele activa met hoog rendement met een stabiele basis om het optimale geometrisch gemiddelde rendement te bereiken.
Onze Python-omgeving opzetten
Eerst het belangrijkste - laten we onze tools klaarmaken:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.optimize import minimize
import yfinance as yf
import warnings
warnings.filterwarnings('ignore')
plt.style.use('dark_background')
sns.set_palette("husl")
Niveau 1: Baby steps - Eenvoudige portefeuillewiskunde
Laten we beginnen met de basis. We berekenen het rendement en risico voor een eenvoudige portefeuille met 2 activa.
def get_crypto_data(symbols, period="1y"):
"""
Fetch crypto data from Yahoo Finance
symbols: list of crypto symbols (e.g., ['BTC-USD', 'ETH-USD'])
period: time period for data
"""
data = yf.download(symbols, period=period)['Adj Close']
return data
crypto_symbols = ['BTC-USD', 'ETH-USD']
prices = get_crypto_data(crypto_symbols)
returns = prices.pct_change().dropna()
print("Daily Returns Preview:")
print(returns.head())
Laten we nu enkele basis portefeuillemetrics berekenen:
def portfolio_performance(weights, returns):
"""
Calculate portfolio return and volatility
weights: array of portfolio weights
returns: dataframe of asset returns
"""
portfolio_return = np.sum(returns.mean() * weights) * 252
portfolio_vol = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 252, weights)))
return portfolio_return, portfolio_vol
weights_5050 = np.array([0.5, 0.5])
ret_5050, vol_5050 = portfolio_performance(weights_5050, returns)
print(f"50/50 Portfolio:")
print(f"Expected Annual Return: {ret_5050:.2%}")
print(f"Annual Volatility: {vol_5050:.2%}")
print(f"Sharpe Ratio: {ret_5050/vol_5050:.3f}")
Niveau 2: Serieus worden - De efficiënte grens
Nu wordt het spannend! De efficiënte grens toont ons alle mogelijke optimale portefeuilles. Elk punt vertegenwoordigt het best mogelijke rendement voor een gegeven risiconiveau.
def generate_random_portfolios(returns, num_portfolios=10000):
"""
Generate random portfolio combinations
"""
num_assets = len(returns.columns)
results = np.zeros((4, num_portfolios))
for i in range(num_portfolios):
weights = np.random.random(num_assets)
weights /= np.sum(weights) # Normalize to sum to 1
portfolio_return, portfolio_vol = portfolio_performance(weights, returns)
sharpe_ratio = portfolio_return / portfolio_vol
results[0,i] = portfolio_return
results[1,i] = portfolio_vol
results[2,i] = sharpe_ratio
results[3,i:] = weights
return results
results = generate_random_portfolios(returns)
portfolio_results = pd.DataFrame({
'Returns': results[0],
'Volatility': results[1],
'Sharpe_Ratio': results[2]
})
plt.figure(figsize=(12, 8))
scatter = plt.scatter(portfolio_results['Volatility'],
portfolio_results['Returns'],
c=portfolio_results['Sharpe_Ratio'],
cmap='viridis', alpha=0.6)
plt.colorbar(scatter, label='Sharpe Ratio')
plt.xlabel('Volatility (Risk)')
plt.ylabel('Expected Return')
plt.title('Efficient Frontier - Random Portfolios')
plt.show()
De efficiënte grens: de curve die het maximaal mogelijke verwachte rendement weergeeft voor een gegeven risiconiveau.
Niveau 3: Optimalisatiemeester - De perfecte portefeuille vinden
Willekeurige steekproeven zijn leuk, maar we willen de wiskundig optimale oplossing. Tijd om het zware geschut in te zetten - scipy-optimalisatie!
def negative_sharpe_ratio(weights, returns, risk_free_rate=0.02):
"""
Calculate negative Sharpe ratio (we minimize this)
"""
portfolio_return, portfolio_vol = portfolio_performance(weights, returns)
sharpe = (portfolio_return - risk_free_rate) / portfolio_vol
return -sharpe
def minimize_volatility(weights, returns):
"""
Calculate portfolio volatility (we minimize this)
"""
_, portfolio_vol = portfolio_performance(weights, returns)
return portfolio_vol
def portfolio_return_objective(weights, returns):
"""
Calculate portfolio return (we maximize this)
"""
portfolio_return, _ = portfolio_performance(weights, returns)
return -portfolio_return # Negative because we minimize
def optimize_portfolio(returns, objective='sharpe', target_return=None):
"""
Optimize portfolio based on different objectives
"""
num_assets = len(returns.columns)
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1}) # Weights sum to 1
bounds = tuple((0, 1) for _ in range(num_assets)) # No short selling
initial_guess = num_assets * [1. / num_assets]
if objective == 'sharpe':
result = minimize(negative_sharpe_ratio, initial_guess,
args=(returns,), method='SLSQP',
bounds=bounds, constraints=constraints)
elif objective == 'min_vol':
result = minimize(minimize_volatility, initial_guess,
args=(returns,), method='SLSQP',
bounds=bounds, constraints=constraints)
elif objective == 'target_return':
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1},
{'type': 'eq', 'fun': lambda x: portfolio_performance(x, returns)[0] - target_return})
result = minimize(minimize_volatility, initial_guess,
args=(returns,), method='SLSQP',
bounds=bounds, constraints=constraints)
return result
max_sharpe = optimize_portfolio(returns, 'sharpe')
min_vol = optimize_portfolio(returns, 'min_vol')
print("🎯 Maximum Sharpe Ratio Portfolio:")
for i, symbol in enumerate(crypto_symbols):
print(f"{symbol}: {max_sharpe.x[i]:.3f}")
ret_sharpe, vol_sharpe = portfolio_performance(max_sharpe.x, returns)
print(f"Return: {ret_sharpe:.2%}, Volatility: {vol_sharpe:.2%}")
print(f"Sharpe Ratio: {ret_sharpe/vol_sharpe:.3f}\n")
print("🛡️ Minimum Volatility Portfolio:")
for i, symbol in enumerate(crypto_symbols):
print(f"{symbol}: {min_vol.x[i]:.3f}")
ret_minvol, vol_minvol = portfolio_performance(min_vol.x, returns)
print(f"Return: {ret_minvol:.2%}, Volatility: {vol_minvol:.2%}")
Niveau 4: Multi-asset waanzin - Een echte cryptoportefeuille
Laten we dit opschalen naar een echte cryptoportefeuille met meerdere activa:
crypto_portfolio = ['BTC-USD', 'ETH-USD', 'BNB-USD', 'ADA-USD', 'SOL-USD', 'DOT-USD']
prices_multi = get_crypto_data(crypto_portfolio, period="2y")
returns_multi = prices_multi.pct_change().dropna()
correlation_matrix = returns_multi.corr()
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='RdYlBu_r', center=0)
plt.title('Crypto Asset Correlation Matrix')
plt.show()
def efficient_frontier(returns, num_portfolios=50):
"""
Calculate the efficient frontier
"""
ret_range = np.linspace(returns.mean().min()*252, returns.mean().max()*252, num_portfolios)
efficient_portfolios = []
for target_ret in ret_range:
try:
result = optimize_portfolio(returns, 'target_return', target_ret)
if result.success:
ret, vol = portfolio_performance(result.x, returns)
efficient_portfolios.append([ret, vol, result.x])
except:
continue
return np.array(efficient_portfolios)
efficient_port = efficient_frontier(returns_multi)
plt.figure(figsize=(14, 10))
random_results = generate_random_portfolios(returns_multi, 5000)
plt.scatter(random_results[1], random_results[0],
c=random_results[2], cmap='viridis', alpha=0.3, s=10)
if len(efficient_port) > 0:
plt.plot(efficient_port[:,1], efficient_port[:,0], 'r-', linewidth=3, label='Efficient Frontier')
max_sharpe_multi = optimize_portfolio(returns_multi, 'sharpe')
min_vol_multi = optimize_portfolio(returns_multi, 'min_vol')
ret_sharpe_multi, vol_sharpe_multi = portfolio_performance(max_sharpe_multi.x, returns_multi)
ret_minvol_multi, vol_minvol_multi = portfolio_performance(min_vol_multi.x, returns_multi)
plt.scatter(vol_sharpe_multi, ret_sharpe_multi, marker='*', color='gold', s=500, label='Max Sharpe')
plt.scatter(vol_minvol_multi, ret_minvol_multi, marker='*', color='red', s=500, label='Min Volatility')
plt.colorbar(label='Sharpe Ratio')
plt.xlabel('Volatility (Risk)')
plt.ylabel('Expected Return')
plt.title('Multi-Asset Crypto Portfolio Optimization')
plt.legend()
plt.show()
print("🚀 Optimal Multi-Asset Allocations:")
print("\nMaximum Sharpe Ratio Portfolio:")
sharpe_weights = pd.Series(max_sharpe_multi.x, index=crypto_portfolio).sort_values(ascending=False)
for asset, weight in sharpe_weights.items():
if weight > 0.01: # Only show significant allocations
print(f"{asset}: {weight:.1%}")
print(f"\nPortfolio Metrics:")
print(f"Expected Return: {ret_sharpe_multi:.1%}")
print(f"Volatility: {vol_sharpe_multi:.1%}")
print(f"Sharpe Ratio: {ret_sharpe_multi/vol_sharpe_multi:.2f}")
Multi-asset diversificatie: een robuuste portefeuille bouwen door niet-gecorreleerde cryptoactiva te combineren tot een stabiele geometrische structuur.
Niveau 5: Geavanceerde technieken - Black-Litterman en risicopariteit
Voor de echte portefeuilleoptimalisatie-ninja's implementeren we een aantal geavanceerde technieken:
def risk_parity_portfolio(returns):
"""
Risk Parity Portfolio - each asset contributes equally to portfolio risk
"""
def risk_contribution(weights, cov_matrix):
portfolio_vol = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
marginal_contrib = np.dot(cov_matrix, weights) / portfolio_vol
contrib = weights * marginal_contrib
return contrib
def risk_parity_objective(weights, cov_matrix):
contrib = risk_contribution(weights, cov_matrix)
target_contrib = np.ones(len(weights)) / len(weights)
return np.sum((contrib - target_contrib)**2)
num_assets = len(returns.columns)
cov_matrix = returns.cov() * 252
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
bounds = tuple((0.001, 1) for _ in range(num_assets))
initial_guess = num_assets * [1. / num_assets]
result = minimize(risk_parity_objective, initial_guess,
args=(cov_matrix,), method='SLSQP',
bounds=bounds, constraints=constraints)
return result
risk_parity_result = risk_parity_portfolio(returns_multi)
print("⚖️ Risk Parity Portfolio:")
rp_weights = pd.Series(risk_parity_result.x, index=crypto_portfolio).sort_values(ascending=False)
for asset, weight in rp_weights.items():
print(f"{asset}: {weight:.1%}")
ret_rp, vol_rp = portfolio_performance(risk_parity_result.x, returns_multi)
print(f"\nRisk Parity Metrics:")
print(f"Expected Return: {ret_rp:.1%}")
print(f"Volatility: {vol_rp:.1%}")
print(f"Sharpe Ratio: {ret_rp/vol_rp:.2f}")
def backtest_portfolio(weights, prices):
"""
Simple backtest of portfolio performance
"""
returns = prices.pct_change().dropna()
portfolio_returns = (returns * weights).sum(axis=1)
cumulative_returns = (1 + portfolio_returns).cumprod()
total_return = cumulative_returns.iloc[-1] - 1
annualized_return = (1 + total_return) ** (252 / len(portfolio_returns)) - 1
annualized_vol = portfolio_returns.std() * np.sqrt(252)
sharpe_ratio = annualized_return / annualized_vol
max_dd = (cumulative_returns / cumulative_returns.expanding().max() - 1).min()
return {
'total_return': total_return,
'annualized_return': annualized_return,
'annualized_volatility': annualized_vol,
'sharpe_ratio': sharpe_ratio,
'max_drawdown': max_dd,
'cumulative_returns': cumulative_returns
}
strategies = {
'Max Sharpe': max_sharpe_multi.x,
'Min Volatility': min_vol_multi.x,
'Risk Parity': risk_parity_result.x,
'Equal Weight': np.ones(len(crypto_portfolio)) / len(crypto_portfolio)
}
plt.figure(figsize=(14, 8))
for name, weights in strategies.items():
backtest_results = backtest_portfolio(weights, prices_multi)
plt.plot(backtest_results['cumulative_returns'], label=f"{name} (Sharpe: {backtest_results['sharpe_ratio']:.2f})")
plt.title('Portfolio Strategy Backtests')
plt.xlabel('Date')
plt.ylabel('Cumulative Returns')
plt.legend()
plt.yscale('log')
plt.grid(True, alpha=0.3)
plt.show()

*Algoritmische backtesting: historische prestaties simuleren om theoretische optimalisatiemodellen te valideren.*
performance_summary = pd.DataFrame()
for name, weights in strategies.items():
results = backtest_portfolio(weights, prices_multi)
performance_summary[name] = [
f"{results['annualized_return']:.1%}",
f"{results['annualized_volatility']:.1%}",
f"{results['sharpe_ratio']:.2f}",
f"{results['max_drawdown']:.1%}"
]
performance_summary.index = ['Annual Return', 'Annual Volatility', 'Sharpe Ratio', 'Max Drawdown']
print("\n📊 Strategy Performance Summary:")
print(performance_summary)
De realitycheck: wat Markowitz je niet vertelt
Voordat je all-in gaat op wiskundige optimalisatie, hier een paar harde waarheden over crypto:
1. Historische prestaties zijn geen garantie voor de toekomst De cryptomarkt is jong en chaotisch. Die correlaties die je hebt berekend? Die kunnen van de ene op de andere dag omslaan wanneer regelgeving verandert of wanneer de volgende grote hack plaatsvindt.
2. Transactiekosten zijn belangrijk Het herbalanceren van je portefeuille kost geld. In DeFi kunnen gasfees flink in je winst bijten. Houd hier rekening mee in je strategie.
3. Liquiditeitsproblemen Niet alle crypto's zijn even liquide. Die smallcap altcoin ziet er misschien geweldig uit in je optimalisatie, maar probeer hem maar eens te verkopen tijdens een crash.
4. Regimewisselingen Cryptomarkten hebben verschillende "regimes" - bullmarkten, bearmarkten, zijwaartse markten. Wat in de ene werkt, werkt misschien niet in de andere.
Praktische implementatietips
def practical_portfolio_rebalancing(target_weights, current_weights, threshold=0.05):
"""
Only rebalance when weights drift beyond threshold
"""
weight_diff = np.abs(target_weights - current_weights)
needs_rebalancing = np.any(weight_diff > threshold)
if needs_rebalancing:
print("🔄 Rebalancing needed!")
for i, (target, current) in enumerate(zip(target_weights, current_weights)):
if abs(target - current) > threshold:
print(f"Asset {i}: {current:.1%} → {target:.1%}")
else:
print("✅ Portfolio within tolerance, no rebalancing needed")
return needs_rebalancing
current_allocation = np.array([0.35, 0.25, 0.15, 0.10, 0.10, 0.05])
target_allocation = max_sharpe_multi.x
practical_portfolio_rebalancing(target_allocation, current_allocation)
Conclusie: jouw toolkit voor portefeuilleoptimalisatie
Je hebt nu een complete toolkit voor het optimaliseren van cryptoportefeuilles:
- Basisberekeningen voor risico en rendement
- Visualisatie van de efficiënte grens
- Wiskundige optimalisatie voor verschillende doelstellingen
- Geavanceerde strategieën zoals risicopariteit
- Backtesting-framework om je strategieën te valideren
- Praktische overwegingen voor implementatie in de echte wereld
Belangrijkste inzichten
- Diversificatie is een gratis lunch - de enige gratis lunch bij investeren
- Optimaliseer op basis van je risicotolerantie - maximale Sharpe ratio is niet altijd het beste voor jou
- Herbalanceer systematisch, maar overhandel niet
- Blijf bescheiden - modellen zijn hulpmiddelen, geen glazen bollen
- Begin eenvoudig en voeg complexiteit toe naarmate je leert
Onthoud: in crypto kunnen zelfs de beste wiskundige modellen niet voorspellen wanneer Elon over Dogecoin gaat tweeten of wanneer de volgende exchange gehackt wordt. Gebruik portefeuilletheorie als je fundament, maar houd altijd wat kruit droog en investeer nooit meer dan je je kunt veroorloven te verliezen.
Ga nu heen en optimaliseer verantwoordelijk! 🚀
Verder lezen
Code-repository
Alle code uit deze tutorial is beschikbaar op GitHub: https://github.com/suenot/markowitz
Veel plezier met optimaliseren! 📈
Auteurs
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.