σμ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 4 · Precificação de renda fixa e opções›Tópico 4.15·~55 minIntermediário

Árvores binomiais

Posição no roteiroMódulo 4 de 11 · Tópico 15 de 16
4.15 Árvores binomiais
—Nota do lab— / 100XP total—Sequência—

Uma ação que só pode subir ou descer

Black–Scholes é uma fórmula; uma árvore binomial é um procedimento, e pode precificar o que a fórmula não consegue. Divide-se o tempo até o vencimento em passos pequenos. A cada passo a ação sobe um fator u ou cai um fator d, nada mais. Após n passos há n + 1 preços possíveis. Os pagamentos são conhecidos no vencimento e cada nó anterior vale a média descontada dos dois nós a que leva, de modo que a árvore se preenche de trás para frente até o presente.

# One step: a stock at 100 goes to 110 or 90. What is a call with strike 100 worth?
import math

spot, strike, rate, dt = 100, 100, 0.05, 1.0
u, d = 1.10, 0.90
p = (math.exp(rate * dt) - d) / (u - d)             # the probability that makes the stock grow at the rate
up_value, down_value = max(spot * u - strike, 0), max(spot * d - strike, 0)
price = math.exp(-rate * dt) * (p * up_value + (1 - p) * down_value)
print(round(p, 4), round(up_value, 4), down_value)   # 0.7564 10.0 0
print(round(price, 4))                               # 7.1947

A probabilidade não é uma previsão

O número p é escolhido para que a ação cresça, em média, à taxa livre de risco: p * u + (1 - p) * d = exp(rate * dt), o que dá p = (exp(rate * dt) - d) / (u - d). É a probabilidade neutra ao risco: um artifício que faz uma carteira replicante valer sua esperança descontada, não uma crença sobre para onde a ação vai. Para reproduzir a volatilidade observada, a árvore de Cox–Ross–Rubinstein (CRR) usa u = exp(sigma * sqrt(dt)) e d = 1 / u, de modo que uma alta seguida de uma queda volta ao mesmo preço e a árvore se recombina: n passos dão só n + 1 nós finais.

# The CRR parameters and the prices at expiry of a 3-step tree
import math

def crr_parameters(rate, sigma, t, steps):
    dt = t / steps
    u = math.exp(sigma * math.sqrt(dt))
    d = 1 / u
    p = (math.exp(rate * dt) - d) / (u - d)
    return u, d, p

def terminal_prices(spot, u, d, steps):
    return [spot * u ** j * d ** (steps - j) for j in range(steps + 1)]

u, d, p = crr_parameters(0.05, 0.2, 1, 3)
print(round(u, 4), round(d, 4), round(p, 4))                     # 1.1224 0.8909 0.5438
print([round(price, 2) for price in terminal_prices(100, u, d, 3)])

Trabalhar de trás para frente

No vencimento cada nó é apenas o pagamento, max(S - K, 0) para uma call. Uma coluna antes, cada valor é exp(-rate * dt) * (p * value_up + (1 - p) * value_down), e a coluna perde um nó. Após n varreduras resta um único valor, o preço de hoje. Em Python uma coluna é uma lista, e uma varredura é uma compreensão de lista sobre pares vizinhos. Com mais passos o preço converge para o de Black–Scholes e a árvore reproduz o que diz a fórmula.

# The tree price of a European call approaches the Black-Scholes price as steps grow
import math

def norm_cdf(x):
    return 0.5 * (1 + math.erf(x / math.sqrt(2)))

def bs_call(spot, strike, rate, sigma, t):
    d1 = (math.log(spot / strike) + (rate + 0.5 * sigma ** 2) * t) / (sigma * math.sqrt(t))
    return spot * norm_cdf(d1) - strike * math.exp(-rate * t) * norm_cdf(d1 - sigma * math.sqrt(t))

def european_call_tree(spot, strike, rate, sigma, t, steps):
    dt = t / steps
    u = math.exp(sigma * math.sqrt(dt))
    d = 1 / u
    p = (math.exp(rate * dt) - d) / (u - d)
    disc = math.exp(-rate * dt)
    values = [max(spot * u ** j * d ** (steps - j) - strike, 0) for j in range(steps + 1)]
    for _ in range(steps):
        values = [disc * (p * values[j + 1] + (1 - p) * values[j]) for j in range(len(values) - 1)]
    return values[0]

print("Black-Scholes", round(bs_call(100, 100, 0.05, 0.2, 1), 4))
for steps in (1, 5, 25, 100, 400):
    print(steps, round(european_call_tree(100, 100, 0.05, 0.2, 1, steps), 4))

O exercício antecipado

A força real da árvore é a opção que a fórmula não consegue precificar: uma opção americana pode ser exercida a qualquer momento. Em cada nó o titular compara dois valores, a esperança descontada de manter e o pagamento de exercer agora, e fica com o maior. Esse único max extra em cada nó é toda a mudança no algoritmo. Para uma call sobre ação sem dividendos o exercício antecipado nunca compensa, mas para uma put pode: uma put bem dentro do dinheiro vale mais em caixa hoje, então a put americana custa mais que a europeia.

# An American put against a European put: the early-exercise premium
import math

def tree(spot, strike, rate, sigma, t, steps, american):
    dt = t / steps
    u = math.exp(sigma * math.sqrt(dt))
    d = 1 / u
    p = (math.exp(rate * dt) - d) / (u - d)
    disc = math.exp(-rate * dt)
    values = [max(strike - spot * u ** j * d ** (steps - j), 0) for j in range(steps + 1)]
    for i in range(steps - 1, -1, -1):
        values = [disc * (p * values[j + 1] + (1 - p) * values[j]) for j in range(i + 1)]
        if american:
            values = [max(values[j], strike - spot * u ** j * d ** (i - j)) for j in range(i + 1)]
    return values[0]

for spot in (80, 100, 120):
    european = tree(spot, 100, 0.05, 0.2, 1, 200, False)
    american = tree(spot, 100, 0.05, 0.2, 1, 200, True)
    print(spot, round(european, 4), round(american, 4), "premium", round(american - european, 4))
Lento e geral vence rápido e específicoPara opções europeias simples a fórmula fechada é instantânea e exata, então nunca se usaria uma árvore. A árvore se justifica quando o pagamento tem uma decisão (exercício antecipado), uma barreira ou um dividendo discreto: acrescenta-se a regra em cada nó. O preço é o tempo: n passos custam cerca de n**2 / 2 atualizações de nós, e a convergência não é suave (o erro oscila conforme o strike cai entre os nós), por isso as árvores são verificadas comparando vários números de passos.
  • A cada passo a ação sobe u ou desce d; a escolha CRR u = exp(sigma * sqrt(dt)), d = 1 / u recombina a árvore, então n passos têm n + 1 nós finais.
  • Precificar de trás para frente: pagamentos no vencimento e depois exp(-rate * dt) * (p * up + (1 - p) * down) por nó; p é a probabilidade neutra ao risco, não uma previsão.
  • Muitos passos convergem para Black–Scholes; uma opção americana acrescenta um max(continuação, exercício) por nó, e é por isso que as árvores existem.

A seguir: os métodos de diferenças finitas, precificar resolvendo numa grade a equação diferencial parcial da opção.

Lab: o que é avaliado

  • Definir os movimentos de alta e baixa com crr_parameters25 pts
  • Listar os preços no vencimento com terminal_prices25 pts
  • Percorrer a árvore de trás para frente com european_call_tree25 pts
  • Permitir o exercício antecipado com american_put_tree25 pts
Explorador · lab-80-1
▾ lab-80-1
…
bash · no navegador
learner@quantcraft:~/lab-80-1$
Python ainda não carregadoPythonLn 1, Col 1Spaces: 4UTF-8

Redefinir este tópico?

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