Teori Portfolio Markowitz untuk Kripto: Dari Nol hingga Mahir

Membina portfolio kripto yang optimum dengan Python - kerana YOLO bukanlah strategi. Pelajari cara menerapkan teori portfolio pemenang Hadiah Nobel kepada pelaburan kripto dengan contoh kod Python yang praktikal.

Membina portfolio kripto yang optimum dengan Python - kerana YOLO bukanlah strategi

Markowitz Portfolio Theory Teori Portfolio Markowitz: Pengoptimuman matematik yang diterapkan pada aset digital untuk memaksimumkan pulangan bagi tahap risiko tertentu.


Pengenalan: Mengapa Portfolio Kripto Anda Memerlukan Matematik (Bukan Sekadar Naluri)

Hai para pelabur kripto! 👋

Ingat ketika anda melabur semua wang ke dalam DOGE kerana Elon menulis tweet? Atau ketika anda menjual semua aset dalam panik semasa kejatuhan terakhir? Ya, kita semua pernah melaluinya. Hari ini kita akan membincangkan sesuatu yang mungkin menyelamatkan portfolio anda (dan kewarasan anda): Teori Portfolio Markowitz.

Harry Markowitz secara harfiah memenangi Hadiah Nobel untuk perkara ini pada tahun 1990. Idea asasnya? Anda boleh mengoptimumkan portfolio anda secara matematik untuk mendapatkan pulangan terbaik yang mungkin bagi setiap tahap risiko tertentu. Ia seperti mempunyai GPS untuk pelaburan anda berbanding memandu sambil menutup mata.

Konsep Teras: Risiko vs Pulangan (Tarian Abadi)

Sebelum kita menyelami kod, mari kita fahami apa yang sedang kita hadapi:

  • Pulangan Dijangka: Berapa banyak wang yang anda jangkakan akan diperolehi
  • Risiko (Volatiliti): Sejauh mana nilai portfolio anda berubah-ubah
  • Korelasi: Sejauh mana aset-aset berbeza bergerak seiring

Keajaibannya berlaku apabila anda menggabungkan aset-aset yang tidak bergerak secara segerak. Apabila Bitcoin jatuh, mungkin beberapa token DeFi bertahan lebih baik. Itulah kepelbagaian yang bekerja untuk anda.

Risk vs Return Risiko vs. Pulangan: Mengimbangi aset berisiko tinggi berpulangan tinggi dengan asas yang stabil untuk mencapai pulangan min geometri yang optimum.

Menyediakan Persekitaran Python Kami

Pertama sekali - mari kita sediakan alatan kami:

# Perpustakaan penting untuk pengoptimuman portfolio kripto kami
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")

Tahap 1: Langkah Pertama - Matematik Portfolio Mudah

Mari kita mulakan dengan asas-asas. Kita akan mengira pulangan dan risiko untuk portfolio 2 aset yang mudah.

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())

Sekarang mari kita kira beberapa metrik portfolio asas:

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}")

Tahap 2: Menjadi Serius - Sempadan Cekap

Sekarang kita sudah sedia! Sempadan cekap menunjukkan kepada kita semua portfolio optimum yang mungkin. Setiap titik mewakili pulangan terbaik yang mungkin bagi tahap risiko tertentu.

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 Sempadan Cekap: Lengkung yang mewakili pulangan dijangka maksimum yang mungkin bagi tahap risiko tertentu.

Tahap 3: Mahir Pengoptimuman - Mencari Portfolio Sempurna

Persampelan rawak memang menyeronokkan, tetapi kita mahukan penyelesaian yang optimum secara matematik. Tiba masanya untuk mengeluarkan senjata besar - pengoptimuman scipy!

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%}")

Tahap 4: Banyak Aset - Portfolio Kripto Sebenar

Mari kita kembangkan ini kepada portfolio kripto yang betul dengan pelbagai aset:

# 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 Kepelbagaian Pelbagai Aset: Membina portfolio yang kukuh dengan menggabungkan aset kripto yang tidak berkorelasi ke dalam struktur geometri yang stabil.

Tahap 5: Teknik Lanjutan - Black-Litterman dan Pariti Risiko

Untuk para ninja pengoptimuman portfolio sejati, mari kita laksanakan beberapa teknik lanjutan:

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)
*Backtesting Algoritmik: Mensimulasikan prestasi sejarah untuk mengesahkan model pengoptimuman teoritikal.*


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

Pemeriksaan Realiti: Apa yang Markowitz Tidak Beritahu Anda

Sebelum anda bergantung sepenuhnya pada pengoptimuman matematik, berikut adalah beberapa kebenaran pahit tentang kripto:

1. Prestasi Lepas ≠ Hasil Masa Depan Pasaran kripto masih muda dan tidak menentu. Korelasi yang anda kirakan itu? Ia mungkin berubah dalam semalaman apabila peraturan berubah atau apabila penggodaman besar seterusnya berlaku.

2. Kos Transaksi Penting Mengimbangi semula portfolio anda memerlukan wang. Dalam DeFi, yuran gas boleh menghabiskan keuntungan anda. Ambil kira perkara ini dalam strategi anda.

3. Isu Kecairan Tidak semua kripto mempunyai kecairan yang sama. Altcoin bermodal kecil itu mungkin kelihatan bagus dalam pengoptimuman anda, tetapi cuba jualnya semasa kejatuhan pasaran.

4. Perubahan Rejim Pasaran kripto mempunyai "rejim" yang berbeza - pasaran lembu, pasaran beruang, pasaran ketam. Apa yang berkesan dalam satu rejim mungkin tidak berkesan dalam rejim lain.

Petua Pelaksanaan Praktikal

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)

Kesimpulan: Kit Alatan Pengoptimuman Portfolio Anda

Anda kini mempunyai kit alatan yang lengkap untuk pengoptimuman portfolio kripto:

  1. Pengiraan asas untuk risiko dan pulangan
  2. Visualisasi sempadan cekap
  3. Pengoptimuman matematik untuk objektif yang berbeza
  4. Strategi lanjutan seperti pariti risiko
  5. Rangka kerja backtesting untuk mengesahkan strategi anda
  6. Pertimbangan praktikal untuk pelaksanaan dunia sebenar

Perkara Utama yang Perlu Diingat

  • Kepelbagaian adalah makan tengah hari percuma - satu-satunya makan tengah hari percuma dalam pelaburan
  • Optimumkan berdasarkan toleransi risiko anda - nisbah Sharpe maksimum tidak selalu terbaik untuk anda
  • Imbangkan semula secara sistematik tetapi jangan berdagang secara berlebihan
  • Kekalkan kerendahan hati - model adalah alatan, bukan bola kristal
  • Mulakan dengan mudah dan tambah kerumitan seiring dengan pembelajaran anda

Ingat: Dalam kripto, walaupun model matematik terbaik tidak dapat meramalkan bila Elon akan menulis tweet tentang Dogecoin atau bila bursa seterusnya akan digodam. Gunakan teori portfolio sebagai asas anda, tetapi sentiasa simpan sebahagian wang tunai dan jangan pernah melabur lebih daripada yang anda mampu untuk kehilangan.

Kini pergilah dan optimalkan dengan bertanggungjawab! 🚀

Bacaan Lanjut

Repositori Kod

Semua kod dari tutorial ini tersedia di GitHub: https://github.com/suenot/markowitz

Selamat mengoptimumkan! 📈