← Terug naar artikelen
September 25, 2025
5 min leestijd

Markowitz-portefeuilletheorie voor Crypto: Van Nul tot Held

Markowitz-portefeuilletheorie voor Crypto: Van Nul tot Held
#portfolio optimization
#Markowitz
#crypto
#Python
#quantitative finance
#risk management
#diversification
#efficient frontier
#Sharpe ratio
#algorithmic trading
📊
Part 1 of 5 · Collection
Portfolio Construction & Risk

Optimale cryptoportefeuilles bouwen met Python - want YOLO is geen strategie

Markowitz Portfolio Theory 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.

Risk vs Return 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()

Efficient Frontier 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 Portfolio Optimization 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()

![Portfolio Strategy Backtests](/images/blog/markowitz-backtest.webp)
*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:

  1. Basisberekeningen voor risico en rendement
  2. Visualisatie van de efficiënte grens
  3. Wiskundige optimalisatie voor verschillende doelstellingen
  4. Geavanceerde strategieën zoals risicopariteit
  5. Backtesting-framework om je strategieën te valideren
  6. 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! 📈

Disclaimer: De informatie in dit artikel is uitsluitend bedoeld voor educatieve en informatieve doeleinden en vormt geen financieel, beleggings- of handelsadvies. Het handelen in cryptovaluta brengt een aanzienlijk risico op verlies met zich mee.

Auteurs

Eugen Soloviov
Eugen Soloviov

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.

Newsletter

Blijf de markt voor

Abonneer je op onze nieuwsbrief voor exclusieve AI-handelsinzichten, marktanalyses en platformupdates.

We respecteren je privacy. Je kunt je op elk moment afmelden.