σμ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 Academyロードマップ
Ctrl K
AO
+1.2k 人の学習者
ログイン参加する ↗
ロードマップ
AO
+1.2k 人の学習者
ログイン
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python ロードマップ›モジュール 5 · ポートフォリオ最適化›トピック 5.1·~45 分中級

共分散行列と相関行列

ロードマップ上の位置モジュール 5/11 · トピック 1/5
5.1 共分散行列と相関行列
—ラボスコア— / 100合計XP—連続日数—

1つの資産から多数の資産へ

ここまでは1本のリターン系列を見てきました。ポートフォリオは多くの資産を持ち、そのリスクは各資産のリスクだけでなく、それらが*一緒に*どう動くかにも左右されます。2つの資産の共分散は、平均からの偏差の積の平均で、一緒に上がり下がりしやすければ正、一方が上がるとき他方が下がるなら負です。n 個の資産には n * n 個の共分散があり、共分散行列として並べます。行列は対称で、対角には分散が入ります。

import numpy as np

# Five days of returns for two assets: shape (days, assets)
returns = np.array([[0.01, 0.02], [-0.02, -0.01], [0.03, 0.01], [0.00, -0.02], [0.01, 0.03]])

centered = returns - returns.mean(axis=0)            # subtract each column's mean
cov = centered.T @ centered / (len(returns) - 1)     # (assets x days) @ (days x assets) -> (assets x assets)
print(np.round(cov, 6))
print(np.allclose(cov, np.cov(returns, rowvar=False)))     # True

標本共分散行列

形状が (days, assets) のリターン表なら、行列全体は線形代数の1行で書けます。各列の平均を引き(axis=0、日にわたる平均)、中心化した表の転置に自分自身を掛けます。centered.T @ centered の (i, j) 成分は、日にわたる x_i * x_j の合計です。標本分散と同じく days - 1 で割ります。これが np.cov(returns, rowvar=False) や DataFrame.cov() の計算で、np.cov は資産が行に並ぶことを想定しているため rowvar=False が必要です。

相関:単位のない共分散

共分散が 0.0004 と言われても、2つの資産のボラティリティ次第なので意味がよく分かりません。2つの標準偏差の積で割るとスケールが消え、常に-1から1の間の相関が得られます:corr[i, j] = cov[i, j] / (std[i] * std[j])、std は対角の平方根です。コードでは np.outer(std, std) で積の表を作り、1回の割り算で行列全体を直すと、対角は1になります。相関は2つの資産がどれだけ連動するかを示し、リスクの式に必要なのはスケールを保った共分散です。

# From covariance to correlation and annualised volatilities
import numpy as np

returns = np.array([[0.01, 0.02], [-0.02, -0.01], [0.03, 0.01], [0.00, -0.02], [0.01, 0.03]])
cov = np.cov(returns, rowvar=False)

std = np.sqrt(np.diag(cov))
corr = cov / np.outer(std, std)
print(np.round(corr, 4))
print(np.round(np.sqrt(np.diag(cov) * 252), 4))      # daily variances scaled to a year: volatility grows with sqrt(252)

この行列は本物たりうるか

適当な数表は共分散行列ではありません。対称で、かつ半正定値でなければなりません。任意のウェイト w について w' C w はポートフォリオの分散なので、負にはなりません。同じことですが、負の固有値がないということです。推定された行列は合格しますが、手で書き換えたものや、出所の違う相関を寄せ集めたもの(例:A~B = 0.9、B~C = 0.9、A~C = -0.9)は不合格です。対称行列用で実数の固有値を返す np.linalg.eigvalsh で確かめ、丸め誤差のため小さな許容値を設けます。

# A validity check: symmetry and no negative eigenvalue
import numpy as np

def is_valid_cov(matrix, tol=1e-10):
    symmetric = bool(np.all(np.abs(matrix - matrix.T) <= tol))
    return symmetric and bool(np.linalg.eigvalsh(matrix).min() >= -tol)

good = np.array([[1.0, 0.5, 0.2], [0.5, 1.0, 0.3], [0.2, 0.3, 1.0]])
bad = np.array([[1.0, 0.9, -0.9], [0.9, 1.0, 0.9], [-0.9, 0.9, 1.0]])
print(is_valid_cov(good), np.round(np.linalg.eigvalsh(good), 3))
print(is_valid_cov(bad), np.round(np.linalg.eigvalsh(bad), 3))
# Few days, many assets: the sample covariance matrix is singular
import numpy as np

rng = np.random.default_rng(0)
returns = rng.normal(0, 0.01, size=(5, 8))              # 5 days, 8 assets
cov = np.cov(returns, rowvar=False)
print(np.linalg.matrix_rank(cov), "of", cov.shape[0])  # 4 of 8: some portfolios seem to have zero variance
観測は少なく、資産は多い500銘柄の行列には12万5,250個の異なる成分があり、それを250日ほどのデータから推定します。日数が資産数より少ないと標本行列は特異になり、分散がゼロに見えるポートフォリオが現れますが、どんなオプティマイザもそれを信じるべきではありません。実務では行列を単純な目標へ縮小するか、ファクターモデルを使います。推定は過去の平均でもあり、平穏な時期と危機の時期では共分散が大きく異なります。
  • (days, assets) の表の共分散行列は centered.T @ centered / (days - 1)。対称で、対角は分散
  • 相関は cov / np.outer(std, std)。単位がなく-1から1の間、対角は1。分散は periods 倍、ボラティリティはその平方根倍で年率化する
  • 有効な共分散行列は対称で負の固有値がない(np.linalg.eigvalsh)。日数が少なすぎる推定や手作業の修正はこれを壊しうる

次はポートフォリオのリターンと分散です。ウェイトとこの行列からポートフォリオのリスクを求めます。

ラボ:採点される内容

  • cov_matrix で共分散の表を作る25点
  • corr_from_cov で相関に直す25点
  • annualised_vols で各資産のボラティリティを読む25点
  • is_valid_cov で本物の共分散行列になりうるか確かめる25点
エクスプローラー · lab-82-1
▾ lab-82-1
…
bash · ブラウザ内
learner@quantcraft:~/lab-82-1$
Pythonは未読み込みPythonLn 1, Col 1Spaces: 4UTF-8

このトピックをリセットしますか?

lab-82-1 の内容はすべてスターターファイルに戻り、ターミナルも消去されます。現在の解答は失われ、元に戻せません。

リセットされるもの
  • main.py → スターターコード
  • ターミナル履歴とテスト出力
  • 未提出の変更
保持されるもの
  • ラボの最高スコア
  • XPと連続日数
  • 講義の進捗とメモ

コードを実行するにはログインしてください。

レクチャーはすべて無料で読めます。コードの実行、テスト、ラボの提出にはアカウントの作成かログインが必要です。

  • ブラウザ内IDEとbashターミナル。インストール不要
  • 即時採点、XP、ロードマップでの現在地
アカウントを作成ログイン
または
GitHubで続ける