σμ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 AcademyRoteiros
Ctrl K
AO
+1,2 mil alunos
EntrarParticipar ↗
Roteiros
AO
+1,2 mil alunos
Entrar
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Roteiro de Python›Módulo 5 · Otimização de carteiras›Tópico 5.1·~45 minIntermediário

Matrizes de covariância e correlação

Posição no roteiroMódulo 5 de 11 · Tópico 1 de 5
5.1 Matrizes de covariância e correlação
—Nota do lab— / 100XP total—Sequência—

De um ativo para muitos

Tudo até aqui olhava uma única série de retornos. Uma carteira tem muitos ativos, e seu risco depende não só do risco de cada um, mas de como eles se movem *juntos*. A covariância de dois ativos é o produto médio de seus desvios em relação à média: positiva se tendem a subir e cair juntos, negativa se um sobe quando o outro cai. Para n ativos há n * n covariâncias, dispostas numa matriz de covariância, simétrica e com as variâncias na diagonal.

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

A matriz de covariância amostral

Com uma tabela de retornos de forma (days, assets), a matriz inteira é uma linha de álgebra linear. Subtrai-se a média de cada coluna (axis=0, média sobre os dias) e multiplica-se a transposta da tabela centrada por ela mesma: a entrada (i, j) de centered.T @ centered é a soma sobre os dias de x_i * x_j. Divide-se por days - 1, como numa variância amostral. É isso que np.cov(returns, rowvar=False) e DataFrame.cov() calculam; atenção ao rowvar=False, pois np.cov espera os ativos nas linhas.

A correlação: a covariância sem unidades

Uma covariância de 0.0004 diz pouco: depende de quão voláteis são os dois ativos. Dividir pelo produto dos dois desvios-padrão tira a escala e dá a correlação, sempre entre -1 e 1: corr[i, j] = cov[i, j] / (std[i] * std[j]), com std a raiz da diagonal. Em código, np.outer(std, std) constrói a tabela de produtos e uma divisão reescala a matriz toda, com uns na diagonal. A correlação diz quão unidos dois ativos se movem; a covariância, que mantém a escala, é o que as fórmulas de risco precisam.

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

Esta matriz pode ser real?

Uma tabela qualquer de números não é uma matriz de covariância. Ela deve ser simétrica e semidefinida positiva: para quaisquer pesos w, o número w' C w é a variância de uma carteira, então não pode ser negativo. De forma equivalente, nenhum autovalor é negativo. Matrizes estimadas passam, mas as editadas à mão, ou um mosaico de correlações de fontes diferentes (por exemplo A~B = 0.9, B~C = 0.9, A~C = -0.9), falham. Verifica-se com np.linalg.eigvalsh, para matrizes simétricas, que devolve autovalores reais, com uma pequena tolerância para arredondamentos.

# 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
Poucas observações, muitos ativosUma matriz para 500 ações tem 125.250 entradas distintas, estimadas com talvez 250 dias. Com menos dias que ativos, a matriz amostral é singular: alguma carteira parece ter variância zero, o que nenhum otimizador deveria acreditar. Os profissionais a encolhem em direção a um alvo simples ou usam um modelo de fatores. A estimativa também é uma média do passado: períodos calmos e de crise têm covariâncias muito diferentes.
  • A matriz de covariância de uma tabela (days, assets) é centered.T @ centered / (days - 1): simétrica, com as variâncias na diagonal.
  • A correlação é cov / np.outer(std, std): sem unidade, entre -1 e 1, uns na diagonal; anualizam-se as variâncias por periods, as volatilidades pela sua raiz.
  • Uma matriz de covariância válida é simétrica e sem autovalores negativos (np.linalg.eigvalsh); estimativas com poucos dias, ou retocadas à mão, podem violar isso.

A seguir: o retorno e a variância de uma carteira, transformar os pesos e esta matriz no risco da carteira.

Lab: o que é avaliado

  • Construir a tabela de covariâncias com cov_matrix25 pts
  • Reescalá-la em correlações com corr_from_cov25 pts
  • Ler a volatilidade de cada ativo com annualised_vols25 pts
  • Testar se pode ser real com is_valid_cov25 pts
Explorador · lab-82-1
▾ lab-82-1
…
bash · no navegador
learner@quantcraft:~/lab-82-1$
Python ainda não carregadoPythonLn 1, Col 1Spaces: 4UTF-8

Redefinir este tópico?

Tudo em lab-82-1 volta aos arquivos iniciais e o terminal é limpo. Sua solução atual será perdida — isso não pode ser desfeito.

Será redefinido
  • main.py → código inicial
  • Histórico do terminal e saída dos testes
  • Alterações não enviadas
Será mantido
  • Melhor nota do lab
  • Seu XP e sua sequência
  • Progresso da aula e anotações

Entre para executar o seu código.

Todas as aulas são gratuitas para ler. Crie uma conta ou entre para executar código, rodar os testes e enviar este lab.

  • IDE no navegador com terminal bash — nada para instalar
  • Correção instantânea, XP e o seu lugar no roteiro
Criar uma contaEntrar
OU
Continuar com o GitHub