Markowitz-Portfoliotheorie für Krypto: Vom Anfänger zum Experten

Optimale Krypto-Portfolios mit Python aufbauen - denn YOLO ist keine Strategie. Lerne, wie du die mit dem Nobelpreis ausgezeichnete Portfoliotheorie mit praktischen Python-Codebeispielen auf Krypto-Investments anwendest.

Optimale Krypto-Portfolios mit Python aufbauen - denn YOLO ist keine Strategie

Markowitz Portfolio Theory Markowitz-Portfoliotheorie: Mathematische Optimierung, angewendet auf digitale Assets, um die Rendite bei einem gegebenen Risikoniveau zu maximieren.


Einleitung: Warum dein Krypto-Portfolio Mathematik braucht (nicht nur Bauchgefühl)

Hey Crypto-Degens! 👋

Erinnerst du dich noch daran, wie du deinen gesamten Stack in DOGE geworfen hast, weil Elon getwittert hat? Oder als du beim letzten Crash panisch alles verkauft hast? Ja, das kennen wir alle. Heute sprechen wir über etwas, das dein Portfolio (und deinen Verstand) retten könnte: die Markowitz-Portfoliotheorie.

Harry Markowitz hat dafür 1990 tatsächlich den Nobelpreis gewonnen. Die Grundidee? Man kann sein Portfolio mathematisch optimieren, um die bestmögliche Rendite für ein beliebiges Risikoniveau zu erzielen. Es ist wie ein GPS für deine Investments, statt blind durch die Gegend zu fahren.

Das Kernkonzept: Risiko vs. Rendite (der ewige Tanz)

Bevor wir in den Code eintauchen, sollten wir verstehen, womit wir es zu tun haben:

  • Erwartete Rendite: Wie viel Geld du zu verdienen erwartest
  • Risiko (Volatilität): Wie stark der Wert deines Portfolios schwankt
  • Korrelation: Wie ähnlich sich verschiedene Assets zueinander bewegen

Die Magie entsteht, wenn du Assets kombinierst, die sich nicht perfekt synchron bewegen. Wenn Bitcoin abstürzt, halten sich manche DeFi-Token vielleicht besser. Das ist Diversifikation, die für dich arbeitet.

Risk vs Return Risiko vs. Rendite: Ausbalancieren volatiler Assets mit hoher Rendite und einer stabilen Grundlage, um die optimale geometrische mittlere Rendite zu erreichen.

Unsere Python-Umgebung einrichten

Zuerst das Wichtigste - machen wir unsere Werkzeuge bereit:

# Essential libraries for our crypto portfolio optimization
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')

# Set up plotting style
plt.style.use('dark_background')
sns.set_palette("husl")

Stufe 1: Erste Schritte - Einfache Portfolio-Mathematik

Beginnen wir mit den Grundlagen. Wir berechnen Rendite und Risiko für ein einfaches Portfolio aus zwei Assets.

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

# Get data for Bitcoin and Ethereum
crypto_symbols = ['BTC-USD', 'ETH-USD']
prices = get_crypto_data(crypto_symbols)

# Calculate daily returns
returns = prices.pct_change().dropna()
print("Daily Returns Preview:")
print(returns.head())

Jetzt berechnen wir ein paar grundlegende Portfolio-Kennzahlen:

def portfolio_performance(weights, returns):
    """
    Calculate portfolio return and volatility
    weights: array of portfolio weights
    returns: dataframe of asset returns
    """
    # Portfolio return (annualized)
    portfolio_return = np.sum(returns.mean() * weights) * 252

    # Portfolio volatility (annualized)
    portfolio_vol = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 252, weights)))

    return portfolio_return, portfolio_vol

# Example: 50% BTC, 50% ETH
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}")

Stufe 2: Es wird ernst - Die Effizienzgrenze

Jetzt geht's ans Eingemachte! Die Effizienzgrenze zeigt uns alle möglichen optimalen Portfolios. Jeder Punkt steht für die bestmögliche Rendite bei einem gegebenen Risikoniveau.

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):
        # Generate random weights
        weights = np.random.random(num_assets)
        weights /= np.sum(weights)  # Normalize to sum to 1

        # Calculate portfolio metrics
        portfolio_return, portfolio_vol = portfolio_performance(weights, returns)
        sharpe_ratio = portfolio_return / portfolio_vol

        # Store results
        results[0,i] = portfolio_return
        results[1,i] = portfolio_vol
        results[2,i] = sharpe_ratio
        results[3,i:] = weights

    return results

# Generate 10,000 random portfolios
results = generate_random_portfolios(returns)

# Create a dataframe for easier handling
portfolio_results = pd.DataFrame({
    'Returns': results[0],
    'Volatility': results[1],
    'Sharpe_Ratio': results[2]
})

# Plot the efficient frontier
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 Die Effizienzgrenze: Die Kurve, die die maximal mögliche erwartete Rendite für ein gegebenes Risikoniveau darstellt.

Stufe 3: Optimierungs-Meister - Das perfekte Portfolio finden

Zufallsstichproben machen Spaß, aber wir wollen die mathematisch optimale Lösung. Zeit, die großen Geschütze aufzufahren - scipy-Optimierung!

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 and bounds
    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
    initial_guess = num_assets * [1. / num_assets]

    if objective == 'sharpe':
        # Maximize Sharpe ratio
        result = minimize(negative_sharpe_ratio, initial_guess,
                         args=(returns,), method='SLSQP',
                         bounds=bounds, constraints=constraints)

    elif objective == 'min_vol':
        # Minimize volatility
        result = minimize(minimize_volatility, initial_guess,
                         args=(returns,), method='SLSQP',
                         bounds=bounds, constraints=constraints)

    elif objective == 'target_return':
        # Minimize volatility for 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

# Find optimal portfolios
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%}")

Stufe 4: Multi-Asset-Wahnsinn - Ein echtes Krypto-Portfolio

Skalieren wir das jetzt auf ein richtiges Krypto-Portfolio mit mehreren Assets:

# Expand to more cryptocurrencies
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()

# Check correlations
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
    """
    # Calculate range of target returns
    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)

# Calculate efficient frontier
efficient_port = efficient_frontier(returns_multi)

# Plot everything together
plt.figure(figsize=(14, 10))

# Random portfolios
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)

# Efficient frontier
if len(efficient_port) > 0:
    plt.plot(efficient_port[:,1], efficient_port[:,0], 'r-', linewidth=3, label='Efficient Frontier')

# Optimal portfolios
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()

# Display optimal allocations
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-Diversifikation: Aufbau eines robusten Portfolios durch Kombination unkorrelierter Krypto-Assets zu einer stabilen geometrischen Struktur.

Stufe 5: Fortgeschrittene Techniken - Black-Litterman und Risikoparität

Für die wahren Ninjas der Portfoliooptimierung implementieren wir jetzt einige fortgeschrittene Techniken:

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

# Calculate Risk Parity portfolio
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)

    # Calculate cumulative returns
    cumulative_returns = (1 + portfolio_returns).cumprod()

    # Performance metrics
    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
    }

# Backtest our strategies
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)
*Algorithmisches Backtesting: Simulation historischer Performance zur Validierung theoretischer Optimierungsmodelle.*


# Performance summary table
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)

Der Realitätscheck: Was Markowitz dir nicht verrät

Bevor du voll auf mathematische Optimierung setzt, hier ein paar harte Wahrheiten über Krypto:

1. Vergangene Performance ≠ Zukünftige Ergebnisse Der Kryptomarkt ist jung und chaotisch. Die Korrelationen, die du berechnet hast? Die können sich über Nacht umkehren, wenn sich Regulierungen ändern oder der nächste große Hack passiert.

2. Transaktionskosten spielen eine Rolle Das Rebalancing deines Portfolios kostet Geld. In DeFi können Gas-Gebühren dir das Mittagessen wegfressen. Berücksichtige das in deiner Strategie.

3. Liquiditätsprobleme Nicht alle Kryptowährungen sind gleich liquide. Dieser Small-Cap-Altcoin sieht in deiner Optimierung vielleicht super aus, aber versuch mal, ihn während eines Crashs zu verkaufen.

4. Regimewechsel Kryptomärkte haben verschiedene "Regime" - Bullenmärkte, Bärenmärkte, Seitwärtsmärkte. Was in dem einen funktioniert, funktioniert vielleicht nicht im anderen.

Praktische Umsetzungstipps

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

# Example usage
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)

Fazit: Dein Toolkit für Portfoliooptimierung

Du hast jetzt ein komplettes Toolkit für die Optimierung von Krypto-Portfolios:

  1. Grundlegende Berechnungen für Risiko und Rendite
  2. Effizienzgrenzen-Visualisierung
  3. Mathematische Optimierung für verschiedene Zielsetzungen
  4. Fortgeschrittene Strategien wie Risikoparität
  5. Backtesting-Framework zur Validierung deiner Strategien
  6. Praktische Überlegungen für die Umsetzung in der realen Welt

Die wichtigsten Erkenntnisse

  • Diversifikation ist ein Free Lunch - das einzige Free Lunch beim Investieren
  • Optimiere entsprechend deiner Risikotoleranz - maximale Sharpe Ratio ist nicht immer das Beste für dich
  • Rebalanciere systematisch, aber übertreibe es nicht mit dem Handeln
  • Bleib bescheiden - Modelle sind Werkzeuge, keine Kristallkugeln
  • Fang einfach an und füge nach und nach Komplexität hinzu

Denk dran: Selbst die besten mathematischen Modelle können in Krypto nicht vorhersagen, wann Elon über Dogecoin twittert oder wann die nächste Börse gehackt wird. Nutze die Portfoliotheorie als Fundament, aber halte immer etwas Pulver trocken und investiere nie mehr, als du dir leisten kannst zu verlieren.

Und jetzt raus mit dir und verantwortungsvoll optimieren! 🚀

Weiterführende Lektüre

Code-Repository

Der gesamte Code aus diesem Tutorial ist auf GitHub verfügbar: https://github.com/suenot/markowitz

Viel Erfolg beim Optimieren! 📈