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 blockMó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 skippedPontuar 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 foldDetectar 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- 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