σμ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 8 · Modelos estocásticos e aprendizado de máquina›Tópico 8.10·~1 hAvançado

Validação walk-forward

Posição no roteiroMódulo 8 de 11 · Tópico 10 de 10
8.10 Validação walk-forward
—Nota do lab— / 100XP total—Sequência—

Testar como você teria negociado

Uma divisão aleatória em linhas de treino e teste, como no aprendizado de máquina comum, falha em séries temporais: o treino contém dias posteriores aos de teste, e dias vizinhos são correlacionados, então o teste é em parte uma cópia do treino. A validação walk-forward respeita o tempo. Treina-se num bloco do passado, testa-se no bloco seguinte, desliza-se a janela e repete-se. Cada previsão vem de um modelo que nunca viu o futuro, exatamente como no trading real.

import numpy as np

n, train_size, test_size = 10, 4, 2
start = 0
while start + train_size + test_size <= n:
    train = np.arange(start, start + train_size)
    test = np.arange(start + train_size, start + train_size + test_size)
    print("train", train.tolist(), "test", test.tolist())
    start += test_size                                   # slide forward by one test block

Móvel ou crescente, e o embargo

Dois sabores. Uma janela móvel mantém fixo o tamanho do treino, descartando as linhas mais antigas: adapta-se a mudanças de regime com custo constante. Uma janela crescente começa sempre na linha 0, então o treino cresce: usa todas as observações, mas dados antigos podem estar obsoletos. Um terceiro ingrediente é a lacuna (ou embargo): se uma variável ou o alvo olha h dias para trás ou para a frente, as linhas logo antes do período de teste compartilham informação com ele, então se pulam algumas linhas entre treino e teste.

def walk_forward_splits(n, train_size, test_size, gap=0):
    splits, start = [], 0
    while start + train_size + gap + test_size <= n:
        train = list(range(start, start + train_size))
        test = list(range(start + train_size + gap, start + train_size + gap + test_size))
        splits.append((train, test))
        start += test_size
    return splits

for train, test in walk_forward_splits(12, 5, 2, gap=1):
    print(train, "->", test)                         # the row between them is skipped

Pontuar cada dobra

Para cada dobra ajusta-se nas linhas de treino e pontua-se nas de teste, obtendo um erro por dobra em vez de um só número: sua dispersão diz quão estável o modelo é no tempo. Uma referência útil é a previsão trivial, aqui a média de treino para cada dia de teste. Mostre os erros dobra a dobra: um modelo bom na média mas péssimo num regime não é igual a um consistentemente razoável.

import numpy as np

def walk_forward_splits(n, train_size, test_size):
    out, start = [], 0
    while start + train_size + test_size <= n:
        out.append((np.arange(start, start + train_size), np.arange(start + train_size, start + train_size + test_size)))
        start += test_size
    return out

rng = np.random.default_rng(2)
returns = rng.normal(0.0004, 0.01, size=300)
errors = []
for train, test in walk_forward_splits(len(returns), 100, 50):
    errors.append(np.mean((returns[test] - returns[train].mean()) ** 2))
print(np.round(np.array(errors) * 1e4, 3))                      # squared error in basis points squared, fold by fold

Detectar um vazamento

Uma divisão só é limpa se toda linha de treino vem estritamente antes de toda linha de teste. Verificar train.max() < test.min() cabe numa linha e pega o bug mais comum: um índice desordenado ou embaralhado, um corte deslocado em um, uma divisão tirada de uma função de biblioteca que embaralha por padrão. Ponha essa verificação dentro do código que constrói as dobras, para que um vazamento pare a execução em vez de produzir um resultado lisonjeiro.

import numpy as np

def leaks_future(train_idx, test_idx):
    return bool(np.max(train_idx) >= np.min(test_idx))

train = np.arange(0, 100)
print(leaks_future(train, np.arange(100, 120)))                     # False: clean
shuffled = np.random.default_rng(1).permutation(120)                # what a shuffling splitter does
print(leaks_future(shuffled[:100], shuffled[100:]))                 # True: training rows after test rows
Menos dobras não significam mais evidênciaBlocos de teste sobrepostos reutilizam os mesmos dias, e cada dobra continua vindo de um só mercado. Uma estratégia boa em 7 de 8 dobras pode dever isso a um evento compartilhado. Reporte a dispersão dos resultados por dobra, não só a média, e trate o resultado walk-forward como sua melhor estimativa honesta, que ainda encolherá ao vivo. Nunca ajuste o modelo nas dobras para depois citar as mesmas dobras como prova.
  • Walk-forward: treinar num bloco do passado, testar no seguinte, deslizar; cada linha de teste vem depois de todas as suas linhas de treino.
  • Janelas móveis mantêm o tamanho do treino fixo; as crescentes começam na linha 0; uma lacuna (embargo) separa treino e teste quando as variáveis cobrem vários dias.
  • Pontue cada dobra separadamente e olhe a dispersão; verifique train.max() < test.min(); nunca ajuste nas dobras que reporta.

A seguir: o livro de ordens limitadas, a estrutura de dados onde os preços realmente se formam.

Lab: o que é avaliado

  • Deslizar uma janela pelo tempo com walk_forward_splits25 pts
  • Manter todo o histórico com expanding_splits25 pts
  • Testar uma previsão dobra a dobra com forecast_mse25 pts
  • Detectar um vazamento com leaks_future25 pts
Explorador · lab-111-1
▾ lab-111-1
…
bash · no navegador
learner@quantcraft:~/lab-111-1$
Python ainda não carregadoPythonLn 1, Col 1Spaces: 4UTF-8

Redefinir este tópico?

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