Ein vollständiges Research-Notebook-Projekt von den Daten bis zum Bericht, mit einer Code-Review-Checkliste gegen Look-Ahead-Bias und Überanpassung
Organisieren Sie eine vollständige Strategiestudie als reproduzierbares Projekt, führen Sie sie von den Rohdaten bis zu einem schriftlichen Ergebnis durch und prüfen Sie den Code mit einer Checkliste, die die Fehler aufdeckt, die Backtests schönfärben.
Was Sie lernen werden
- Eine Ordner- und Notebook-Struktur, die eine Studie in sechs Monaten für Sie selbst oder für jeden anderen reproduzierbar macht.
- Die Reihenfolge der Arbeit: Daten, Bereinigung, Features, Signale, Backtest, Kennzahlen, Out-of-Sample-Prüfung, Bericht.
- Eine zeilenweise Code-Review-Checkliste für Look-Ahead-Bias, Survivorship-Bias und Überanpassung.
- Wie Sie ein Ergebnis ehrlich aufschreiben, einschließlich dessen, was ausprobiert wurde und nicht funktioniert hat.
Erklärung
Out-of-Sample-Tests, Parameterplateaus und die Kosten vieler Versuche wurden in „Systematisches Trading“ (Lektionen 6 und 7) behandelt. In dieser Lektion geht es um das Engineering, das diese Ideen tragfähig macht: eine Projektstruktur, einen festen Arbeitsablauf und ein Review, das eine andere Person wiederholen könnte.
Projektstruktur
project/data/raw/ # Downloads, nie von Hand bearbeitetdata/clean/ # Parquet-Dateien, erzeugt von 01_data.ipynbnotebooks/ # 01_data, 02_features, 03_backtest, 04_reportsrc/ # indicators.py, engine.py, metrics.py (Lektionen 6, 8, 9)tests/ # test_metrics.py, test_engine.pyconfig.yaml # Ticker, Datumsangaben, Parameter, Kostenannahmenrequirements.txt # festgeschriebene Versionen: pandas==2.x, numpy==…
Wiederverwendeter Code liegt in src/; Notebooks rufen nur Funktionen auf und zeigen Ergebnisse. Schreiben Sie die Paketversionen fest, denn ein pandas-Update kann eine Standardeinstellung ändern (Frequenz-Strings, Resampling-Beschriftungen) und Ihre Zahlen verschieben. Setzen Sie für jede Zufallskomponente einen Seed und halten Sie das Download-Datum der Daten im Bericht fest.
Der Arbeitsablauf in der richtigen Reihenfolge
- Zuerst die Hypothese. Ein Satz ökonomischer Begründung und die exakten Regeln, bevor Sie Daten anfassen („Systematisches Trading“, Lektion 2). Legen Sie die Aufteilung in In-Sample und Out-of-Sample jetzt fest und öffnen Sie die Out-of-Sample-Datei erst in Schritt 7.
- Daten. Herunterladen, roh speichern, Quelle und Adjustierungsmethode dokumentieren (Lektion 4). Eine Liste der „heutigen Indexmitglieder“, die zehn Jahre zurück getestet wird, ist Survivorship-Bias.
- Bereinigen und validieren. Lücken, Duplikate, Nullkurse, Zeitzonen; in
data/clean/speichern. - Features und Signale mit den getesteten Funktionen aus
src/; jedes Feature auf Balken t nutzt nur Daten bis t. - Backtest mit der Engine aus Lektion 8, einschließlich des Kostenmodells („Systematisches Trading“, Lektion 8).
- Kennzahlen und Charts (Lektionen 9 und 10) auf dem In-Sample-Zeitraum, mit einem kleinen Parameterraster, nicht mit einer Suche nach der besten Zelle.
- Out-of-Sample einmal ausführen, mit eingefrorenen Parametern. Halten Sie das Ergebnis fest, wie auch immer es ausfällt.
- Bericht: Hypothese, Daten, Regeln, Kosten, beide Ergebnistabellen, Charts, Anzahl der getesteten Varianten, Entscheidung.
Code-Review-Checkliste
Prüfen Sie Ihr Notebook so, als wäre es das eines anderen; jede Zeile ist eine Ja-oder-Nein-Frage.
| Bereich | Prüfung | Typische Korrektur |
|---|---|---|
| Timing | Wird jedes Signal um einen Balken verschoben, bevor es eine Position festlegt? | pos = signal.shift(1) |
| Timing | Verwenden rollierende Fenster center=False (die Standardeinstellung) und kein bfill()? |
Zentrierung und Rückwärtsauffüllung entfernen |
| Timing | Werden Normalisierungen (Z-Scores, Ränge, Scaler) nur auf vergangenen Daten angepasst? | Expandierende oder rollierende Anpassung |
| Daten | Ist das Anlageuniversum so definiert, wie es zum jeweiligen Datum bekannt war? | Zeitpunktgenaue Listen der Indexbestandteile oder ein ehrlicher Vorbehalt |
| Daten | Werden adjustierte Kurse für Renditen und, wo relevant, unadjustierte für Stop-Abstände verwendet? | Beide Spalten behalten |
| Ausführungen | Werden Kurslücken zur Eröffnung ausgeführt, nicht zum Stop- oder Signalkurs? | min(open, stop) |
| Kosten | Werden Provisionen, Spread und Slippage pro Trade und pro Seite angesetzt? | Kostenmodell in der Konfiguration |
| Überanpassung | Wie viele Parametersätze, Regeln und Universen wurden ausprobiert? Steht die Anzahl im Bericht? | Jeden Lauf protokollieren |
| Überanpassung | Liefern benachbarte Parameter ähnliche Ergebnisse (ein Plateau)? | Heatmap des Rasters |
| Überanpassung | Wurden Out-of-Sample-Daten mehr als einmal angefasst? | Falls ja, sind sie jetzt In-Sample; neue Daten suchen |
| Reproduzierbarkeit | Reproduziert „Restart kernel and run all“ die Zahlen im Bericht? | Versteckten Zustand und Zellreihenfolge korrigieren |
Faustregel für die Signifikanz: Die t-Statistik einer annualisierten Sharpe Ratio beträgt ungefähr Sharpe × √Jahre. Eine Sharpe von 0.8 über vier Jahre ergibt 1.6, unter der üblichen Schwelle von etwa 2, und das noch bevor die Zahl der getesteten Varianten berücksichtigt wird; unter zwanzig Versuchen ist zu erwarten, dass der beste rein zufällig gut aussieht.
Durchgerechnetes Beispiel
Hypothetisches Beispiel, alle Zahlen erfunden. Ein Trader testet „Kauf bei einem 100-Tage-Ausbruch, Ausstieg über einen Trailing-Stop von 3 × ATR“ auf 30 Large-Cap-Aktien, 2014–2025, mit 0.1 % Kosten pro Seite.
- Aufteilung. In-Sample 2014–2021 (8 Jahre), Out-of-Sample 2022–2025 (4 Jahre), festgelegt vor dem ersten Lauf.
- Raster. Ausbruchslängen 50, 100, 150 × ATR-Multiplikatoren 2, 3, 4 = 9 Varianten, protokolliert. Die In-Sample-Sharpe reicht von 0.6 bis 1.1, die beste Zelle liegt bei (100, 3) = 1.1; ihre Nachbarn liegen bei 0.9 und 1.0, ein Plateau, also wird die Wahl beibehalten.
- Review. Die Checkliste deckt auf, dass die ATR-Spalte
rolling(14).mean()auf der True Range von heute für den heutigen Stop verwendet hat. Die Engine wird so geändert, dass sie die ATR des vorherigen Balkens nutzt (Lektion 8). Die In-Sample-Sharpe fällt von 1.1 auf 1.0; der maximale Drawdown bleibt unverändert bei −22 %. - Out-of-Sample. Einmal ausgeführt: Sharpe 0.6, CAGR 7.2 %, maximaler Drawdown −19 %, 118 Trades. Signifikanz: 0.6 × √4 = 1.2; der In-Sample-Wert ergab 1.0 × √8 = 2.8. Das Out-of-Sample-Ergebnis ist positiv, für sich genommen aber statistisch schwach.
- Entscheidung im Bericht. „Die Regel hat Out-of-Sample etwa 60 % ihrer In-Sample-Sharpe behalten (0.6 / 1.0), auf einem Plateau, 9 Varianten getestet, ein Look-Ahead-Fehler behoben. Die Evidenz ist bescheiden; der nächste Schritt sind sechs Monate Paper-Trading mit dem Skript aus Lektion 12, kein echtes Kapital.“ Gebühren modelliert; Steuern und Finanzierung nicht berücksichtigt.
Häufiger Fehler
Übungsaufgabe
Auf Papier: Prüfen Sie dieses Notebook-Fragment und das darauf folgende Ergebnis.
df['z'] = (df.close - df.close.mean()) / df.close.std()df['signal'] = df.z < -1.5df['pos'] = df.signal.astype(int)df['ret'] = df.close.pct_change() * df.pos
In-Sample-Sharpe 2.4 über 6 Jahre; Out-of-Sample-Sharpe −0.3 über 2 Jahre. (a) Benennen Sie zwei Look-Ahead-Probleme im Code. (b) Berechnen Sie die ungefähren t-Statistiken für die In-Sample- und die Out-of-Sample-Sharpe. (c) Schreiben Sie die vier Zeilen so um, dass sie frei von Look-Ahead sind (oder beschreiben Sie die Änderungen in Worten, wenn Ihnen das lieber ist).
Lösung anzeigen
(a) Erstens verwenden df.close.mean() und df.close.std() die gesamte Stichprobe einschließlich zukünftiger Kurse, sodass der Z-Score an jedem Datum den zukünftigen Durchschnitt „kennt“. Zweitens ist pos nicht verschoben: Die heutige Rendite wird mit einem Signal erzielt, das aus dem heutigen Schlusskurs berechnet wurde.
(b) In-Sample: 2.4 × √6 = 5.9; Out-of-Sample: −0.3 × √2 = −0.4. Der hohe In-Sample-Wert wurde durch die Fehler erzeugt, und deshalb ist er eingebrochen.
(c) Verwenden Sie ein rollierendes oder expandierendes Fenster und eine Verschiebung:
m = df.close.rolling(250).mean(); s = df.close.rolling(250).std()df['z'] = (df.close - m) / sdf['signal'] = df.z < -1.5df['pos'] = df.signal.shift(1).fillna(False).astype(int)df['ret'] = df.close.pct_change() * df.pos
Einschränkungen: Das Entfernen von Look-Ahead macht den Test ehrlich, nicht profitabel; die korrigierte Regel braucht weiterhin Kosten, eine Engine mit Stops und einen neuen Out-of-Sample-Zeitraum.
Kernaussagen
- Halten Sie wiederverwendbaren Code mit Tests in
src/, schreiben Sie Versionen fest und sorgen Sie dafür, dass „alles ausführen“ den Bericht reproduziert. - Legen Sie Hypothese, Regeln und die Aufteilung in In-Sample und Out-of-Sample vor dem ersten Backtest fest; führen Sie Out-of-Sample einmal aus.
- Prüfen Sie Timing, Daten, Ausführungen, Kosten und die Zahl der Versuche mit einer Checkliste; protokollieren Sie jede getestete Variante.
- Geben Sie die Signifikanz grob als Sharpe × √Jahre an, dazu die Zahl der Varianten und die Entscheidung, einschließlich „nicht genügend Evidenz“.
Nächster Schritt
Damit ist „Python für Trader & Investoren“ abgeschlossen; der nächste Kurs, „Prop Firms, Funded Accounts & Trading als Business“, befasst sich mit Evaluierungsregeln, Drawdown-Arten und der praktischen sowie der deutschen steuerlichen Seite des Tradings als Business, wobei ein getesteter, per Skript automatisierter Prozess wie der, den Sie hier aufgebaut haben, der Ausgangspunkt ist.
Selbsttest
Beantworten Sie die Fragen und prüfen Sie dann Ihre Antworten. Sie können es so oft wiederholen, wie Sie möchten.
Automatischer Selbsttest zur eigenen Wiederholung. Ihre Antworten werden in Ihrem Browser geprüft und von uns weder eingesehen noch bewertet. Es ist keine Prüfung und kein Abschluss.
Ihr Fortschritt
„Erledigt“ bedeutet nur, dass Sie die Lektion als abgeschlossen markiert haben. Es ist kein Test Ihres Verständnisses. Fortschritt und Notizen werden nur in diesem Browser auf diesem Gerät gespeichert — nicht in einem Konto.