σμ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 AcademyParcours
Ctrl K
AO
+1,2 k apprenants
ConnexionRejoindre ↗
Parcours
AO
+1,2 k apprenants
Connexion
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Parcours Python›Module 8 · Modèles stochastiques et apprentissage automatique›Sujet 8.10·~1 hAvancé

Validation walk-forward

Position dans le parcoursModule 8 sur 11 · Sujet 10 sur 10
8.10 Validation walk-forward
—Score du lab— / 100XP total—Série—

Tester comme on aurait négocié

Un découpage aléatoire en lignes d'entraînement et de test, comme en apprentissage automatique ordinaire, échoue sur les séries temporelles : l'entraînement contient des jours postérieurs aux jours de test, et les jours voisins sont corrélés, si bien que le test est en partie une copie de l'entraînement. La validation walk-forward respecte le temps. On entraîne sur un bloc du passé, on teste sur le bloc qui le suit, on fait glisser la fenêtre et on recommence. Chaque prévision vient d'un modèle qui n'a jamais vu le futur, exactement comme en trading réel.

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

Glissant ou croissant, et l'embargo

Deux variantes. Une fenêtre glissante garde la longueur d'entraînement fixe en écartant les plus anciennes lignes : elle s'adapte aux changements de régime à coût constant. Une fenêtre croissante part toujours de la ligne 0, donc l'entraînement grossit : elle utilise toutes les observations, mais les vieilles données peuvent être périmées. Un troisième ingrédient est l'écart (ou embargo) : si une variable ou la cible regarde h jours en arrière ou en avant, les lignes juste avant la période de test partagent de l'information avec elle ; on saute donc quelques lignes entre entraînement et test.

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

Noter chaque pli

Pour chaque pli, on ajuste sur les lignes d'entraînement et on note sur les lignes de test, ce qui donne une erreur par pli plutôt qu'un seul nombre : leur dispersion dit la stabilité du modèle dans le temps. Une référence utile est la prévision triviale, ici la moyenne d'entraînement pour chaque jour de test. Affichez les erreurs pli par pli : un modèle bon en moyenne mais terrible dans un régime n'est pas équivalent à un modèle régulièrement correct.

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

Détecter une fuite

Un découpage n'est propre que si chaque ligne d'entraînement précède strictement chaque ligne de test. Vérifier train.max() < test.min() tient en une ligne et attrape le bug le plus courant : un index non trié ou mélangé, une tranche décalée d'un cran, un découpage issu d'une fonction de bibliothèque qui mélange par défaut. Placez cette vérification dans le code qui construit les plis, pour qu'une fuite arrête l'exécution au lieu de produire un résultat flatteur.

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
Moins de plis ne veut pas dire plus de preuvesDes blocs de test qui se chevauchent réutilisent les mêmes jours, et chaque pli vient toujours d'un seul marché. Une stratégie bonne sur 7 plis sur 8 peut le devoir à un événement commun. Rapportez la dispersion des résultats par pli, pas seulement la moyenne, et voyez le résultat walk-forward comme votre meilleure estimation honnête, qui se dégradera encore en réel. N'ajustez jamais le modèle sur les plis pour ensuite citer les mêmes plis comme preuve.
  • Walk-forward : entraîner sur un bloc du passé, tester sur le bloc suivant, faire glisser ; chaque ligne de test vient après toutes ses lignes d'entraînement.
  • Les fenêtres glissantes gardent la taille d'entraînement fixe ; les fenêtres croissantes partent de la ligne 0 ; un écart (embargo) sépare entraînement et test quand les variables couvrent plusieurs jours.
  • Notez chaque pli séparément et regardez la dispersion ; vérifiez train.max() < test.min() ; n'ajustez jamais sur les plis que vous rapportez.

À suivre : le carnet d'ordres, la structure de données où les prix se forment réellement.

Lab : ce qui est noté

  • Faire glisser une fenêtre dans le temps avec walk_forward_splits25 pts
  • Garder tout l'historique avec expanding_splits25 pts
  • Tester une prévision pli par pli avec forecast_mse25 pts
  • Détecter une fuite avec leaks_future25 pts
Explorateur · lab-111-1
▾ lab-111-1
…
bash · dans le navigateur
learner@quantcraft:~/lab-111-1$
Python pas encore chargéPythonLn 1, Col 1Spaces: 4UTF-8

Réinitialiser ce sujet ?

Tout le contenu de lab-111-1 revient aux fichiers de départ et le terminal est vidé. Votre solution actuelle sera perdue : cette action est irréversible.

Sera réinitialisé
  • main.py → code de départ
  • Historique du terminal et sortie des tests
  • Modifications non soumises
Sera conservé
  • Meilleur score du lab
  • Votre XP et votre série
  • Progression du cours et notes

Connectez-vous pour exécuter votre code.

Tous les cours sont gratuits à lire. Créez un compte ou connectez-vous pour exécuter du code, lancer les tests et soumettre ce lab.

  • IDE dans le navigateur avec terminal bash — rien à installer
  • Correction instantanée, XP et votre place dans le parcours
Créer un compteSe connecter
OU
Continuer avec GitHub