σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft AcademyParcours
Ctrl K
AO
+1,2 k apprenants
ConnexionRejoindre ↗
Parcours
AO
+1,2 k apprenants
Connexion
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Parcours Python›Module 5 · Optimisation de portefeuille›Sujet 5.3·~50 minIntermédiaire

Portefeuille à variance minimale

Position dans le parcoursModule 5 sur 11 · Sujet 3 sur 5
5.3 Portefeuille à variance minimale
—Score du lab— / 100XP total—Série—

Le mélange le plus sûr

Combiner des actifs réduit le risque, mais de combien dépend des poids. Le portefeuille de variance minimale est le choix de poids, de somme 1, qui a la plus petite variance possible. Il n'exige aucune prévision de rendement, seulement la matrice de covariance, ce qui le rend populaire : les rendements espérés sont très difficiles à estimer, les covariances bien moins. Pour deux actifs, la réponse est une formule ; pour beaucoup, c'est la solution d'un système linéaire.

# Two assets: volatility 20% and 10%, correlation 0.3
def variance(w, vol1, vol2, rho):
    return w * w * vol1 ** 2 + (1 - w) ** 2 * vol2 ** 2 + 2 * w * (1 - w) * rho * vol1 * vol2

vol1, vol2, rho = 0.20, 0.10, 0.3
w = (vol2 ** 2 - rho * vol1 * vol2) / (vol1 ** 2 + vol2 ** 2 - 2 * rho * vol1 * vol2)
print(round(w, 4), round(variance(w, vol1, vol2, rho) ** 0.5, 4))        # 0.1053 0.0979
print(round(variance(0.0, vol1, vol2, rho) ** 0.5, 4))                   # 0.1      all in the safe asset: riskier than the mix

Beaucoup d'actifs : un système linéaire

Pour minimiser w @ cov @ w sous la contrainte sum(w) = 1, on annule la dérivée du lagrangien. Le résultat est w = cov^-1 * 1 / (1' cov^-1 1) : on résout le système cov @ x = ones, puis on met x à l'échelle pour que les poids somment à 1. La variance minimale elle-même vaut 1 / (1' cov^-1 1). En code, on utilise np.linalg.solve(cov, ones) et non np.linalg.inv(cov) @ ones : c'est plus rapide et plus précis, car l'inverse n'est jamais formée.

# Minimum-variance weights of three assets
import numpy as np

cov = np.array([[0.0400, 0.0060, 0.0100],
                [0.0060, 0.0100, 0.0020],
                [0.0100, 0.0020, 0.0225]])

x = np.linalg.solve(cov, np.ones(3))
weights = x / x.sum()
print(np.round(weights, 4), round(float(weights.sum()), 4))        # [0.0192 0.7082 0.2726] 1.0
print(round(float(weights @ cov @ weights), 6), round(1 / float(x.sum()), 6))      # 0.007742 twice: the same number
# No other mix with weights summing to 1 does better: try 5000 random ones
import numpy as np

cov = np.array([[0.0400, 0.0060, 0.0100],
                [0.0060, 0.0100, 0.0020],
                [0.0100, 0.0020, 0.0225]])
x = np.linalg.solve(cov, np.ones(3))
best = x / x.sum()

rng = np.random.default_rng(0)
trial = rng.normal(size=(5000, 3))
trial = trial / trial.sum(axis=1, keepdims=True)                  # each row sums to 1
variances = np.einsum("ij,jk,ik->i", trial, cov, trial)            # w @ cov @ w for every row
print(round(float(variances.min()), 6), round(float(best @ cov @ best), 6))
print(bool(variances.min() >= best @ cov @ best))                  # True

Ventes à découvert et contraintes

La formule autorise les poids négatifs : elle peut vendre un actif à découvert pour en couvrir un autre. Beaucoup d'investisseurs ne le peuvent pas ou ne le veulent pas, et veulent des poids long uniquement. Ramener à zéro les poids négatifs puis remettre à l'échelle est un correctif rapide qui garde des poids valides, mais ce n'est pas le vrai optimum sans vente à découvert, qui exige un solveur numérique (programmation quadratique) car la contrainte w >= 0 rend le problème sous contraintes d'inégalité. Le portefeuille tronqué est au moins détenable et facile à comparer au portefeuille libre.

# The unconstrained solution shorts an asset; clip it and compare the risks
import numpy as np

cov = np.array([[0.04, 0.0342, 0.0],
                [0.0342, 0.0324, 0.0],
                [0.0, 0.0, 0.0225]])        # assets 1 and 2: volatility 20% and 18%, correlation 0.95

x = np.linalg.solve(cov, np.ones(3))
free = x / x.sum()
clipped = np.clip(free, 0, None)
clipped = clipped / clipped.sum()
print(np.round(free, 4), round(float(free @ cov @ free), 6))
print(np.round(clipped, 4), round(float(clipped @ cov @ clipped), 6))
Optimal en théorie, fragile en pratiqueLes poids utilisent l'inverse de la matrice de covariance, qui amplifie les erreurs : les actifs qui paraissent un peu moins corrélés ou moins volatils dans l'échantillon reçoivent de gros poids, souvent avec de fortes ventes à découvert, et les poids sautent quand la fenêtre de données bouge. Les praticiens rétrécissent l'estimation de la covariance, bornent les poids ou ajoutent une pénalité. Un portefeuille de variance minimale qui est excellent sur les données d'ajustement se teste sur des données ultérieures, pas sur le même échantillon.
  • La variance minimale n'exige que la matrice de covariance : pour deux actifs, (vol2**2 - rho * vol1 * vol2) / (vol1**2 + vol2**2 - 2 * rho * vol1 * vol2) dans l'actif 1.
  • En général, w = solve(cov, ones) / sum(...) et la variance minimale vaut 1 / (1' cov^-1 1) ; utilisez np.linalg.solve, pas l'inverse.
  • La réponse libre peut vendre des actifs à découvert ; tronquer à zéro puis remettre à l'échelle est un correctif rapide, pas le vrai optimum, et les poids sont sensibles aux erreurs d'estimation.

À suivre : l'optimisation moyenne-variance, ajouter les rendements espérés pour trouver le meilleur compromis entre rendement et risque.

Lab : ce qui est noté

  • Mélanger deux actifs pour un risque minimal avec two_asset_weight25 pts
  • Résoudre les poids les plus sûrs avec min_variance_weights25 pts
  • Donner le risque le plus bas avec min_variance25 pts
  • Écarter les ventes à découvert avec long_only_weights25 pts
Explorateur · lab-84-1
▾ lab-84-1
…
bash · dans le navigateur
learner@quantcraft:~/lab-84-1$
Python pas encore chargéPythonLn 1, Col 1Spaces: 4UTF-8

Réinitialiser ce sujet ?

Tout le contenu de lab-84-1 revient aux fichiers de départ et le terminal est vidé. Votre solution actuelle sera perdue : cette action est irréversible.

Sera réinitialisé
  • main.py → code de départ
  • Historique du terminal et sortie des tests
  • Modifications non soumises
Sera conservé
  • Meilleur score du lab
  • Votre XP et votre série
  • Progression du cours et notes

Connectez-vous pour exécuter votre code.

Tous les cours sont gratuits à lire. Créez un compte ou connectez-vous pour exécuter du code, lancer les tests et soumettre ce lab.

  • IDE dans le navigateur avec terminal bash — rien à installer
  • Correction instantanée, XP et votre place dans le parcours
Créer un compteSe connecter
OU
Continuer avec GitHub