σμ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 3 · Probabilidade e simulação Monte Carlo›Tópico 3.3·~45 minIntermediário

Caudas pesadas e testes de normalidade

Posição no roteiroMódulo 3 de 11 · Tópico 3 de 12
3.3 Caudas pesadas e testes de normalidade
—Nota do lab— / 100XP total—Sequência—

O que o sino promete

Muitos modelos de risco supõem que os retornos seguem uma distribuição normal, porque ela torna tudo calculável com uma média e um desvio padrão. A normal é muito precisa sobre os extremos. A probabilidade de estar a mais de k desvios padrão da média, em qualquer direção, é erfc(k / sqrt(2)): 31,7% para 1, 4,55% para 2, 0,27% para 3, 0,0063% para 4. Um dia de 4 sigmas aconteceria então cerca de uma vez em 15 800 dias de negociação, mais ou menos uma vez em 63 anos, e um dia de 5 sigmas uma vez em vários milhares de anos.

import math
import numpy as np

for k in (1, 2, 3, 4, 5):
    probability = math.erfc(k / math.sqrt(2))      # P(|Z| > k) for a normal variable
    print(k, f"{probability:.2e}")
# 1 3.17e-01
# 2 4.55e-02
# 3 2.70e-03
# 4 6.33e-05
# 5 5.73e-07

print(round(1 / math.erfc(4 / math.sqrt(2))))                  # 15787   trading days between 4-sigma days, if returns were normal
print(round(1 / math.erfc(5 / math.sqrt(2)) / 252))            # 6922    years between 5-sigma days

Os mercados têm caudas pesadas

Os mercados reais produzem dias de 4 e 5 sigmas várias vezes numa carreira, em 1987, 2008, 2020. Suas distribuições de retornos têm caudas pesadas: mais dias extremos do que o sino permite (e um centro mais alto e estreito). Uma forma simples de ver isso é a razão de cauda: a parcela observada de retornos além de k desvios padrão dividida pela parcela que uma normal daria. Uma razão perto de 1 significa caudas normais; 5, extremos cinco vezes mais frequentes; em dados diários com k = 4 pode chegar a centenas. Modelos de risco que supõem normalidade subestimam justamente os dias que mais importam.

returns = np.array([0.010, 0.012, 0.008, 0.011, 0.009, 0.010, 0.012, -0.060, 0.010, 0.009])

def tail_ratio(x, k):
    observed = np.mean(np.abs(x - x.mean()) > k * x.std(ddof=1))     # share beyond k standard deviations
    return observed / math.erfc(k / math.sqrt(2))                    # divided by what a normal would give

print(round(float(tail_ratio(returns, 2)), 2))    # 2.2   the one crash is 2.2 times too frequent at 2 sigma (a tiny sample, though)

O teste de Jarque-Bera

Um teste formal pergunta se a amostra poderia plausivelmente vir de uma distribuição normal. A estatística de Jarque-Bera combina as duas medidas de forma do tópico anterior: JB = n / 6 * (S² + K² / 4), com S a assimetria e K a curtose em excesso. Numa amostra normal as duas ficam perto de 0 e JB é pequeno. Sob normalidade, JB segue uma qui-quadrado com 2 graus de liberdade, cuja cauda tem a forma fechada exp(-JB / 2): é o valor-p. Se for menor que 0,05, rejeita-se a normalidade. O valor crítico é JB = 5,99. Nossa amostra de 10 dias com uma queda tem JB = 22,4 e valor-p de 0,00001.

x = returns
n = len(x)
deviations = x - x.mean()
m2, m3, m4 = (np.mean(deviations ** k) for k in (2, 3, 4))
skew, kurt = m3 / m2 ** 1.5, m4 / m2 ** 2 - 3

jb = n / 6 * (skew ** 2 + kurt ** 2 / 4)
p_value = math.exp(-jb / 2)
print(round(float(jb), 2), p_value < 0.05)     # 22.37 True   normality rejected
print(f"{p_value:.1e}")                         # 1.4e-05
# The p-value falls fast as the statistic grows: 5.99 is the 5% critical value
for jb in (0, 2, 5.99, 10, 22.37):
    print(jb, round(math.exp(-jb / 2), 5))
# 0 1.0
# 2 0.36788
# 5.99 0.05004
# 10 0.00674
# 22.37 1e-05
Rejeitar a normalidade não é o fimCom centenas de retornos diários, o teste rejeita a normalidade para quase todos os ativos, então uma rejeição é esperada. O que importa é o quanto as caudas diferem: use a razão de cauda para dimensionar o problema e depois faça testes de estresse com quedas reais, uma distribuição de caudas mais pesadas como a t de Student, ou simulação histórica, em vez de confiar em um sino.
  • Uma variável normal está a mais de k desvios com probabilidade erfc(k / sqrt(2)): 4,55% para 2, 0,0063% para 4.
  • A razão de cauda, parcela observada dividida pela normal, mostra as caudas pesadas: os mercados ficam muito acima de 1 a 4 sigmas.
  • Jarque-Bera: n / 6 * (S**2 + K**2 / 4); o valor-p é exp(-JB / 2); abaixo de 0,05, rejeita-se a normalidade (JB acima de 5,99).

A seguir: os testes de hipóteses: o teste t, para decidir se um retorno médio é realmente diferente de zero.

Lab: o que é avaliado

  • Medir o quanto uma amostra se afasta da normal com jarque_bera25 pts
  • Transformar a estatística em valor-p com normality_p_value25 pts
  • Calcular o que o sino promete com normal_tail_probability25 pts
  • Contar quanto mais pesadas são as caudas com tail_ratio25 pts
Explorador · lab-56-1
▾ lab-56-1
…
bash · no navegador
learner@quantcraft:~/lab-56-1$
Python ainda não carregadoPythonLn 1, Col 1Spaces: 4UTF-8

Redefinir este tópico?

Tudo em lab-56-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