Probar como habrías operado
Una división aleatoria en filas de entrenamiento y prueba, como en el aprendizaje automático ordinario, falla con series temporales: el entrenamiento contiene días posteriores a los de prueba y los días vecinos están correlacionados, así que la prueba es en parte una copia del entrenamiento. La validación walk-forward respeta el tiempo. Se entrena con un bloque del pasado, se prueba en el bloque siguiente, se desliza la ventana y se repite. Cada predicción la hace un modelo que nunca vio el futuro, exactamente como en el 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óvil o creciente, y el embargo
Dos variantes. Una ventana móvil mantiene fija la longitud de entrenamiento, descartando las filas más antiguas: se adapta a los cambios de régimen con coste constante. Una ventana creciente empieza siempre en la fila 0, así que el entrenamiento crece: usa todas las observaciones, pero los datos viejos pueden estar obsoletos. Un tercer ingrediente es el hueco (o embargo): si una variable o el objetivo mira h días atrás o adelante, las filas justo antes del periodo de prueba comparten información con él, así que se saltan algunas filas entre entrenamiento y prueba.
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 skippedPuntuar cada pliegue
Para cada pliegue se ajusta con las filas de entrenamiento y se puntúa con las de prueba, de modo que se obtiene un error por pliegue en vez de un solo número: su dispersión dice cuán estable es el modelo en el tiempo. Una referencia útil es el pronóstico trivial, aquí la media de entrenamiento para cada día de prueba. Muestra los errores pliegue a pliegue: un modelo bueno de media pero terrible en un régimen no es lo mismo que uno regularmente decente.
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 una fuga
Una división es limpia solo si toda fila de entrenamiento va estrictamente antes de toda fila de prueba. Comprobar train.max() < test.min() cabe en una línea y atrapa el error más común: un índice sin ordenar o mezclado, un corte desplazado en uno, una división tomada de una función de biblioteca que mezcla por defecto. Pon esa comprobación dentro del código que construye los pliegues, para que una fuga detenga la ejecución en vez de dar un resultado halagador.
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: entrenar con un bloque del pasado, probar en el siguiente, deslizar; cada fila de prueba va después de todas sus filas de entrenamiento.
- Las ventanas móviles mantienen fijo el tamaño de entrenamiento; las crecientes empiezan en la fila 0; un hueco (embargo) separa entrenamiento y prueba cuando las variables abarcan varios días.
- Puntúe cada pliegue por separado y mire la dispersión; compruebe
train.max() < test.min(); nunca ajuste con los pliegues que informa.
A continuación: el libro de órdenes limitadas, la estructura de datos donde se forman realmente los precios.
Lab: qué se califica
- Deslizar una ventana por el tiempo con
walk_forward_splits25 pts - Conservar todo el historial con
expanding_splits25 pts - Probar un pronóstico pliegue a pliegue con
forecast_mse25 pts - Detectar una fuga con
leaks_future25 pts