σμ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 AcademyRutas
Ctrl K
AO
+1,2 k estudiantes
Iniciar sesiónUnirse ↗
Rutas
AO
+1,2 k estudiantes
Iniciar sesión
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Ruta de Python›Módulo 8 · Modelos estocásticos y aprendizaje automático›Tema 8.10·~1 hAvanzado

Validación walk-forward

Posición en la rutaMódulo 8 de 11 · Tema 10 de 10
8.10 Validación walk-forward
—Puntuación del lab— / 100XP total—Racha—

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 block

Mó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 skipped

Puntuar 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 fold

Detectar 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
Menos pliegues no es más evidenciaLos bloques de prueba solapados reutilizan los mismos días, y cada pliegue sigue viniendo de un solo mercado. Una estrategia buena en 7 de 8 pliegues puede deberlo a un evento compartido. Informa la dispersión de los resultados por pliegue, no solo la media, y trata el resultado walk-forward como tu mejor estimación honesta, que aún se reducirá en real. Nunca ajustes el modelo con los pliegues para luego citar esos mismos pliegues como prueba.
  • 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
Explorador · lab-111-1
▾ lab-111-1
…
bash · en el navegador
learner@quantcraft:~/lab-111-1$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

Todo lo que hay en lab-111-1 vuelve a los archivos iniciales y se limpia el terminal. Tu solución actual se perderá: no se puede deshacer.

Se restablecerá
  • main.py → código inicial
  • Historial del terminal y salida de pruebas
  • Cambios sin enviar
Se conservará
  • Mejor puntuación del lab
  • Tu XP y tu racha
  • Progreso de la lección y notas

Inicia sesión para ejecutar tu código.

Todas las clases se pueden leer gratis. Crea una cuenta o inicia sesión para ejecutar código, lanzar las pruebas y enviar este lab.

  • IDE en el navegador con terminal bash — sin instalar nada
  • Corrección instantánea, XP y tu lugar en la ruta
Crear una cuentaIniciar sesión
O
Continuar con GitHub