σμ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 AcademySkills
Ctrl K
AO
+1,2 Tsd. Lernende
AnmeldenBeitreten ↗
Skills
AO
+1,2 Tsd. Lernende
Anmelden
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python-Skill›Modul 8 · Stochastische Modelle und Machine Learning›Thema 8.10·~1 Std.Fortgeschritten

Walk-Forward-Validierung

Position in der SkillModul 8 von 11 · Thema 10 von 10
8.10 Walk-Forward-Validierung
—Lab-Punkte— / 100XP gesamt—Serie—

So testen, wie man gehandelt hätte

Eine zufällige Aufteilung in Trainings- und Testzeilen wie im üblichen maschinellen Lernen scheitert bei Zeitreihen: Das Training enthält Tage nach den Testtagen, und benachbarte Tage sind korreliert, der Test ist also teils eine Kopie des Trainings. Die Walk-forward-Validierung respektiert die Zeit. Man trainiert auf einem Block der Vergangenheit, testet auf dem direkt folgenden Block, schiebt das Fenster weiter und wiederholt. Jede Vorhersage stammt von einem Modell, das die Zukunft nie gesehen hat, genau wie im Live-Handel.

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

Rollierend oder wachsend, und der Embargo

Zwei Varianten. Ein rollierendes Fenster hält die Trainingslänge fest und wirft die ältesten Zeilen ab: Es passt sich Regimewechseln an und kostet gleichbleibend. Ein wachsendes Fenster beginnt immer bei Zeile 0, das Training wächst also: Es nutzt jede Beobachtung, aber alte Daten können veraltet sein. Eine dritte Zutat ist die Lücke (der Embargo): Schaut ein Merkmal oder das Ziel h Tage zurück oder voraus, teilen Zeilen kurz vor dem Testzeitraum Information mit ihm, man überspringt daher einige Zeilen zwischen Training und 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

Jeden Fold bewerten

Für jeden Fold passt man auf den Trainingszeilen an und bewertet auf den Testzeilen, erhält also je Fold einen Fehler statt einer einzigen Zahl: Ihre Streuung zeigt, wie stabil das Modell über die Zeit ist. Eine nützliche Basislinie ist die triviale Prognose, hier der Trainingsmittelwert für jeden Testtag. Geben Sie die Fehler Fold für Fold aus: Ein im Schnitt gutes, aber in einem Regime schreckliches Modell ist nicht dasselbe wie ein durchgehend ordentliches.

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

Ein Leck aufspüren

Eine Aufteilung ist nur sauber, wenn jede Trainingszeile strikt vor jeder Testzeile liegt. Die Prüfung train.max() < test.min() braucht eine Zeile und fängt den häufigsten Fehler: einen unsortierten oder gemischten Index, einen um eins verschobenen Slice, eine Aufteilung aus einer Bibliotheksfunktion, die standardmäßig mischt. Setzen Sie diese Prüfung in den Code, der die Folds baut, damit ein Leck den Lauf stoppt, statt ein schmeichelhaftes Ergebnis zu liefern.

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
Weniger Folds sind nicht mehr EvidenzÜberlappende Testblöcke verwenden dieselben Tage erneut, und jeder Fold stammt weiterhin aus einem einzigen Markt. Eine Strategie, die in 7 von 8 Folds gut ist, verdankt das womöglich einem gemeinsamen Ereignis. Berichten Sie die Streuung der Fold-Ergebnisse, nicht nur den Mittelwert, und sehen Sie das Walk-forward-Ergebnis als Ihre beste ehrliche Schätzung, die live noch schrumpft. Stimmen Sie das Modell nie auf den Folds ab, um dann dieselben Folds als Beweis zu zitieren.
  • Walk-forward: auf einem Block der Vergangenheit trainieren, auf dem nächsten testen, weiterschieben; jede Testzeile liegt nach all ihren Trainingszeilen.
  • Rollierende Fenster halten die Trainingsgröße fest; wachsende beginnen bei Zeile 0; eine Lücke (Embargo) trennt Training und Test, wenn Merkmale mehrere Tage umfassen.
  • Bewerten Sie jeden Fold einzeln und betrachten Sie die Streuung; prüfen Sie train.max() < test.min(); stimmen Sie nie auf den berichteten Folds ab.

Als Nächstes: das Limit-Orderbuch, die Datenstruktur, in der Preise tatsächlich entstehen.

Lab: Was bewertet wird

  • Mit walk_forward_splits ein Fenster durch die Zeit schieben25 Pkt.
  • Mit expanding_splits die ganze Historie behalten25 Pkt.
  • Mit forecast_mse eine Prognose Fold für Fold testen25 Pkt.
  • Mit leaks_future ein Leck aufspüren25 Pkt.
Explorer · lab-111-1
▾ lab-111-1
…
bash · im Browser
learner@quantcraft:~/lab-111-1$
Python noch nicht geladenPythonLn 1, Col 1Spaces: 4UTF-8

Dieses Thema zurücksetzen?

Alles in lab-111-1 wird auf die Startdateien zurückgesetzt und das Terminal geleert. Ihre aktuelle Lösung geht verloren – das lässt sich nicht rückgängig machen.

Wird zurückgesetzt
  • main.py → Startcode
  • Terminal-Verlauf und Testausgabe
  • Nicht eingereichte Änderungen
Bleibt erhalten
  • Beste Lab-Punktzahl
  • Ihre XP und Ihre Serie
  • Vorlesungsfortschritt und Notizen

Melde dich an, um deinen Code auszuführen.

Alle Lektionen kannst du kostenlos lesen. Erstelle ein Konto oder melde dich an, um Code auszuführen, Tests zu starten und dieses Lab abzugeben.

  • IDE im Browser mit Bash-Terminal — nichts zu installieren
  • Sofortige Bewertung, XP und dein Platz auf der Skill
Konto erstellenAnmelden
ODER
Weiter mit GitHub