← Retour aux articles
September 25, 2025
5 min de lecture

Théorie de portefeuille de Markowitz pour la crypto : de zéro à héros

Théorie de portefeuille de Markowitz pour la crypto : de zéro à héros
#portfolio optimization
#Markowitz
#crypto
#Python
#quantitative finance
#risk management
#diversification
#efficient frontier
#Sharpe ratio
#algorithmic trading
📊
Part 1 of 5 · Collection
Portfolio Construction & Risk

Construire des portefeuilles crypto optimaux avec Python - parce que le YOLO n'est pas une stratégie

Markowitz Portfolio Theory Théorie de portefeuille de Markowitz : optimisation mathématique appliquée aux actifs numériques pour maximiser le rendement pour un niveau de risque donné.


Introduction : pourquoi votre portefeuille crypto a besoin de maths (pas juste de feeling)

Salut les degens de la crypto ! 👋

Tu te souviens de cette fois où tu as mis tout ton stack dans DOGE parce qu'Elon avait tweeté ? Ou quand tu as tout vendu en panique lors du dernier crash ? Oui, on est tous passés par là. Aujourd'hui, on va parler de quelque chose qui pourrait sauver ton portefeuille (et ta santé mentale) : la théorie de portefeuille de Markowitz.

Harry Markowitz a littéralement remporté un prix Nobel pour ça en 1990. L'idée de base ? Tu peux optimiser mathématiquement ton portefeuille pour obtenir le meilleur rendement possible pour n'importe quel niveau de risque donné. C'est comme avoir un GPS pour tes investissements au lieu de conduire les yeux bandés.

Le concept fondamental : risque vs rendement (la danse éternelle)

Avant de plonger dans le code, comprenons ce à quoi nous avons affaire :

  • Rendement attendu : combien d'argent tu espères gagner
  • Risque (volatilité) : à quel point la valeur de ton portefeuille fluctue
  • Corrélation : à quel point différents actifs évoluent de manière similaire

La magie opère quand tu combines des actifs qui ne bougent pas parfaitement en synchronisation. Quand le Bitcoin s'effondre, certains tokens DeFi tiennent peut-être mieux le coup. C'est la diversification qui travaille pour toi.

Risk vs Return Risque vs rendement : équilibrer des actifs volatils à haut rendement avec une base stable pour atteindre le rendement moyen géométrique optimal.

Configuration de notre environnement Python

Commençons par le commencement - préparons nos outils :

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 : premiers pas - mathématiques de portefeuille simples

Commençons par les bases. On va calculer le rendement et le risque pour un portefeuille simple à 2 actifs.

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

Maintenant calculons quelques métriques de portefeuille de base :

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 : on passe aux choses sérieuses - la frontière efficiente

Maintenant on entre dans le vif du sujet ! La frontière efficiente nous montre tous les portefeuilles optimaux possibles. Chaque point représente le meilleur rendement possible pour un niveau de risque donné.

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 La frontière efficiente : la courbe représentant le rendement attendu maximal possible pour un niveau de risque donné.

Niveau 3 : maître de l'optimisation - trouver le portefeuille parfait

L'échantillonnage aléatoire, c'est amusant, mais on veut la solution mathématiquement optimale. Il est temps de sortir l'artillerie lourde - l'optimisation 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 = ({'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 : folie multi-actifs - un vrai portefeuille crypto

Passons à l'échelle supérieure avec un vrai portefeuille crypto multi-actifs :

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 Diversification multi-actifs : construire un portefeuille robuste en combinant des actifs crypto non corrélés en une structure géométrique stable.

Niveau 5 : techniques avancées - Black-Litterman et parité des risques

Pour les vrais ninjas de l'optimisation de portefeuille, implémentons quelques techniques avancées :

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)
*Backtesting algorithmique : simulation de la performance historique pour valider les modèles d'optimisation théoriques.*


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)

Retour à la réalité : ce que Markowitz ne vous dit pas

Avant de foncer tête baissée dans l'optimisation mathématique, voici quelques vérités dures sur la crypto :

1. Les performances passées ≠ résultats futurs Le marché crypto est jeune et chaotique. Ces corrélations que tu as calculées ? Elles peuvent s'inverser du jour au lendemain quand les réglementations changent ou quand le prochain gros piratage survient.

2. Les frais de transaction comptent Rééquilibrer ton portefeuille coûte de l'argent. En DeFi, les frais de gas peuvent te dévorer tes gains. Prends ça en compte dans ta stratégie.

3. Problèmes de liquidité Toutes les cryptos ne sont pas également liquides. Cette altcoin à petite capitalisation a peut-être l'air super dans ton optimisation, mais essaie de la vendre pendant un crash.

4. Changements de régime Les marchés crypto ont différents « régimes » - marchés haussiers, baissiers, en range. Ce qui fonctionne dans l'un peut ne pas fonctionner dans l'autre.

Conseils pratiques de mise en œuvre

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)

Conclusion : votre boîte à outils pour l'optimisation de portefeuille

Tu disposes maintenant d'une boîte à outils complète pour l'optimisation de portefeuille crypto :

  1. Calculs de base du risque et du rendement
  2. Visualisation de la frontière efficiente
  3. Optimisation mathématique pour différents objectifs
  4. Stratégies avancées comme la parité des risques
  5. Framework de backtesting pour valider tes stratégies
  6. Considérations pratiques pour une mise en œuvre réelle

Points clés à retenir

  • La diversification est un repas gratuit - le seul repas gratuit en investissement
  • Optimise selon ta tolérance au risque - le Sharpe maximum n'est pas toujours ce qu'il y a de mieux pour toi
  • Rééquilibre de façon systématique, mais sans sur-trader
  • Reste humble - les modèles sont des outils, pas des boules de cristal
  • Commence simple et ajoute de la complexité au fur et à mesure

Souviens-toi : en crypto, même les meilleurs modèles mathématiques ne peuvent pas prédire quand Elon va tweeter à propos du Dogecoin ou quand le prochain exchange se fera pirater. Utilise la théorie de portefeuille comme fondation, mais garde toujours de la poudre sèche et n'investis jamais plus que ce que tu peux te permettre de perdre.

Maintenant, va optimiser de façon responsable ! 🚀

Pour aller plus loin

Dépôt de code

Tout le code de ce tutoriel est disponible sur GitHub : https://github.com/suenot/markowitz

Bonne optimisation ! 📈

blog.disclaimer

Authors

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

Gardez une longueur d'avance sur le marché

Abonnez-vous à notre newsletter pour des insights exclusifs sur le trading IA, des analyses de marché et des mises à jour de la plateforme.

Nous respectons votre vie privée. Désabonnement possible à tout moment.