English
preview
Vereinheitlichte Validierungspipeline gegen Backtest-Overfitting

Vereinheitlichte Validierungspipeline gegen Backtest-Overfitting

MetaTrader 5Statistik und Analyse |
22 6
Patrick Murimi Njoroge
Patrick Murimi Njoroge

Einführung

Jeder algorithmische Trader stößt irgendwann auf einen Backtest, der zu gut aussieht, um wahr zu sein. Die Equity-Kurve gleicht einer nahezu perfekten Treppe, die zur oberen rechten Ecke des Diagramms ansteigt. Die Sharpe-Ratio ist außergewöhnlich. Drawdowns sind flach und kurz.

Die Strategie scheitert dann sofort, sobald sie live eingesetzt wird.

Dieses Ergebnis ist so verbreitet, dass sich in der quantitativen Forschungsgemeinschaft dafür sogar ein eigener Ausdruck eingebürgert hat. Der Übeltäter ist fast immer eine Form von Overfitting: Der Algorithmus hat das historische Rauschen eines spezifischen Datensatzes gelernt, anstatt eine dauerhafte, in die Zukunft übertragbare Marktstruktur. Weniger bekannt ist, dass Overfitting kein einzelnes Phänomen ist. Es tritt über verschiedene Kanäle auf, von denen jeder eine andere Gegenmaßnahme erfordert. Ein Anwender, der nur eine Schutzmaßnahme einsetzt – die gebräuchlichste ist eine einfache Train/Test-Aufteilung –, bleibt gegenüber den anderen weiterhin anfällig.

Dieser Artikel untersucht drei der rigorosesten Werkzeuge, die zur Bekämpfung von Overfitting bei der Entwicklung algorithmischer Strategien verfügbar sind: Validation-within-Validation (V-in-V), wie von Timothy Masters formuliert; Combinatorially Purged Cross-Validation (CPCV), entwickelt von Marcos Lopez de Prado; und Combinatorially Symmetric Cross-Validation (CSCV), eingeführt von Bailey und Lopez de Prado. Jedes adressiert eine eigene Fehlerquelle. Zusammen bilden sie eine umfassende Verteidigung gegen die folgenreichsten Formen der statistischen Selbsttäuschung in der quantitativen Forschung.

Abb. 1. Die vereinheitlichte Forschungspipeline. CPCV bereinigt zeitliche Leckagen innerhalb individueller Auswertungen; V-in-V steuert den Entwicklungsprozess; CSCV bietet eine abschließende quantitative Prüfung des Auswahlprozesses


Teil I: Das Spaghetti-an-die-Wand-Problem

Die Versuchung der Brute-Force-Methode

Der sogenannte Spaghetti-an-die-Wand-Ansatz bezieht sich auf die Praxis, eine enorme Anzahl von Indikatoren, Regeln und Parameterkombinationen anhand eines historischen Datensatzes zu testen, in der Hoffnung, dass etwas hängen bleibt. Moderne Rechenleistung macht diesen Ansatz trivial einfach in der Ausführung und katastrophal einfach, ihn falsch zu interpretieren.

Das Problem ist auch mathematischer Natur. Wenn Sie genügend Tests an einem beliebigen endlichen Datensatz durchführen, wird allein der Zufall Ergebnisse hervorbringen, die statistisch signifikant erscheinen. Dies ist kein Versagen von Intelligenz oder Absicht – es ist eine strukturelle Konsequenz wiederholter Hypothesentests. Jeder zusätzliche Test, der mit denselben Daten durchgeführt wird, erhöht die Wahrscheinlichkeit, dass der letztendliche Gewinner ein statistisches Artefakt und kein echter Marktvorteil ist.

Die primären Gefahren

Data Snooping Bias. Wenn dieselben historischen Daten wiederholt verwendet werden, um verschiedene Hypothesen zu testen, steigt mit jedem Test die Wahrscheinlichkeit, dass der letztlich bestabschneidende Kandidat lediglich ein Glückstreffer aus dem Zufall ist. Eine Strategie, die aus zehntausend Kandidaten auf demselben Datensatz ausgewählt wurde, hat ganz andere probabilistische Implikationen als eine, die aus nur fünf ausgewählt wurde.

Überanpassung an historische Daten. Ein Algorithmus, dem erlaubt wurde, frei über historische Daten zu optimieren, wird sich bis zu einem gewissen Grad an das spezifische konkrete Rauschmuster dieses bestimmten Preispfads anpassen. Er wird bei dem, was bereits geschehen ist, gut abschneiden, gerade weil er gelernt hat, bei dem, was bereits geschehen ist, gut abzuschneiden. Das Rauschen der Vergangenheit ist nicht das Signal der Zukunft.

Freiheitsgrade des Forschers. Selbst ein Forscher, der in gutem Glauben handelt, akkumuliert durch den gewöhnlichen Entwicklungsprozess Voreingenommenheit. Jede Modellierungsentscheidung, die auch nur indirekt durch die beobachtete Leistung auf Testdaten beeinflusst wird – Merkmalsauswahl, Parametergrenzen, Regelstruktur –, stellt einen Freiheitsgrad dar, der die scheinbare Signifikanz des Endergebnisses aufbläht. Diese Form der Kontamination ist in jeder einzelnen Entscheidung unsichtbar, wird aber in der Gesamtheit schwerwiegend.

Der „Spaghetti-an-die-Wand“-Ansatz ist nicht von Grund auf unzulässig. Eine breite explorative Suche kann echte Vorteile zutage fördern. Die entscheidende Frage ist, ob der Forscher Schutzmaßnahmen angewendet hat, die streng genug sind, um echte Vorteile von den statistischen Artefakten zu unterscheiden, die eine solche Suche zwangsläufig hervorbringt. Ohne alle drei hier beschriebenen Verteidigungsschichten kann er das nicht.


Teil II: Validation-within-Validation

Der verborgene Fehler bei Standard-Walkforward-Tests

Das Standard-Walkforward-Framework erscheint oberflächlich betrachtet als eine vollständige Lösung. Sie trainieren Ihre Strategie mit historischen Daten, testen sie in einem nachfolgenden Out-of-Sample-Zeitraum, erklären sie bei Erfolg für gültig und gehen zum Live-Einsatz über. Das Framework ist im Prinzip solide. Das Problem liegt in der Umsetzung.

Jedes Mal, wenn ein Forscher die Validierungsleistung beobachtet und diese Beobachtung nutzt, um eine Modellierungsentscheidung zu treffen – das Anpassen eines Parameters, das Reduzieren eines Merkmalsatzes, das Auswählen zwischen konkurrierenden Regelvarianten –, wird der Out-of-Sample-Zeitraum faktisch Teil des Trainingsdatensatzes. Der Entscheidungsprozess des Forschers hat auf diese Daten zugegriffen.

Dies ist das, was Masters als die Anhäufung von Freiheitsgraden des Forschers bezeichnet, und es ist wohl die gefährlichste Form des Overfittings, da sie unsichtbar ist. Die Datenpartitionen sehen auf dem Papier sauber aus. Der Code ist korrekt strukturiert. Doch die iterativen Reaktionen des Forschers auf die beobachtete Out-of-Sample-Leistung haben eine Verzerrung eingeführt, die durch Standardpartitionierung weder erkannt noch korrigiert werden kann.

Die Drei-Schichten-Architektur

Die Lösung von Masters besteht darin, drei streng partitionierte Datenpools zu erstellen, von denen jeder einem bestimmten Zweck dient, wobei jede Phase die Informationsintegrität des Datensatzes nur an der dafür vorgesehenen Stelle nutzt.

Gesamte historische Daten
│
├── ÄUSSERER TRAININGSSATZ (~60 %)    ← Hier findet eine umfassende explorative Suche statt
│
├── INNERER VALIDIERUNGSSATZ (~20 %)  ← Hier erfolgt die Vorauswahl der Kandidaten
│                                       (Absichtlich nur einer begrenzten Anzahl von Kandidaten zugänglich gemacht)
│
└── ENDGÜLTIGER TESTSATZ (~20 %)      ← Wird genau einmal geöffnet, nachdem die Entscheidung endgültig getroffen wurde

Das äußere Trainingsset ist der Ort, an dem die vollständige Brute-Force-Suche durchgeführt wird. Tausende Regeln, Indikatoren und Parameterkombinationen werden hier getestet. Jeder Permutationstest, jeder Regularisierungsdurchlauf und jede explorative Analyse werden innerhalb dieser Grenze durchgeführt. Der Forscher kann die Leistung hier frei beobachten, da diese Daten explizit für die Exploration vorgesehen sind.

Das innere Validierungsset ist der Ort, an dem die Kandidatenauswahl stattfindet. Die in die engere Wahl gezogenen Überlebenden aus Phase 1 werden diesen bisher ungesehenen Daten ausgesetzt. Diejenigen, die stark abfallen, werden als wahrscheinlich überangepasst eliminiert. Diese Stufe wird sparsam eingesetzt – der Forscher legt sich auf eine kleine Anzahl von Finalistenkonfigurationen fest, bevor er sie hier einsetzt.

Das Final Test Set ist unantastbar. Es wird genau einmal geöffnet, nachdem sich der Forscher schriftlich vollständig auf eine einzige, endgültige Strategiekonfiguration festgelegt hat. Das Ergebnis dieser einzigen Auswertung ist die veröffentlichte Leistungskennzahl. Jeder Versuch, dieses Ergebnis für weitere Anpassungen zu nutzen, macht den Test ungültig.

Dreischichtige Datenpartitionierung

Abb. 2. Die dreischichtige Datenpartitionierung. Jede Zone dient einem festgelegten Zweck. Das Final Test Set ist unantastbar, bis eine vollständige schriftliche Festlegung auf eine einzige Strategie erfolgt ist

Verankerte Walkforward-Erweiterung

Das Kernproblem, das es löst. Eine Drei-Wege-Datenpartitionierung (60/20/20) ist im Prinzip statistisch fundiert, aber in der Praxis sehr anspruchsvoll. Bei fünf Jahren täglicher Daten könnten Sie 1.250 Beobachtungen haben. Ein Final Test Set von 20% liefert Ihnen 250 Bars – kaum genug, um eine Sharpe-Ratio mit aussagekräftigen Konfidenzintervallen zu schätzen, und bei weitem zu wenig, um etwas Belastbares über Robustheit über verschiedene Marktregime hinweg zu sagen. Die Verkleinerung der Partition, um mehr Testdaten zu erhalten, verlagert das Problem lediglich auf den Trainingsdatensatz.

Wie es sich vom standardmäßigen rollierenden Walkforward unterscheidet. Der Standard-Walkforward legt die Größe des Trainingsfensters fest und verschiebt es nach vorn. Der Trainingsdatensatz rollt – alte Daten fallen am linken Rand weg, während neue Daten am rechten Rand hinzugefügt werden. Dies bedeutet, dass die Strategie jedes Mal von Grund auf neu auf einer anderen, gleich großen Stichprobe geschätzt wird. Frühe Marktregime werden schließlich verworfen.

Der verankerte Walkforward – eine weitverbreitete Technik, die von Masters und anderen klar beschrieben wird – legt den Start des Trainingsdatensatzes fest und erweitert ihn im Zeitverlauf. Der Trainingsdatensatz beginnt immer am gleichen historischen Ursprung. Jedes aufeinanderfolgende Fenster fügt weitere Daten hinzu. Die Fenster für den Final Test und die innere Validierung rücken zeitlich vor, behalten aber ihre proportionalen Größen im Verhältnis zu den bisher insgesamt verwendeten Daten bei.

Die einzelnen Ergebnisse des Final Tests werden nicht selektiv herausgegriffen. Sie werden als einheitliche Beweisgrundlage bewertet. Eine Strategie, die ihren Vorteil über die Mehrheit der Testfenster hinweg beibehält, hat ihre Robustheit über verschiedene Marktregime und Zeiträume hinweg bewiesen – eine weitaus stärkere Aussage, als sie ein einzelnes Testergebnis liefern kann.

Warum es funktioniert – fünf ineinandergreifende Gründe

  1. Jedes Fenster des Final Tests ist tatsächlich out-of-sample. Die Strategiekonfiguration, die auf jedes Testfenster angewendet wird, wurde festgelegt, bevor dieses Fenster geöffnet wurde. Die zeitliche Begrenzung ist strikt.
  2. Die Testergebnisse sind weitgehend unabhängig. Da jedes Fenster des Final Tests einen anderen, sich nicht überschneidenden Kalenderzeitraum abdeckt, sind die einzelnen Testergebnisse weitgehend unabhängige Beobachtungen der Marktleistung – obwohl die sich überschneidenden Trainingsdaten eine gewisse serielle Korrelation bei den Parameterschätzungen erzeugen, die bei der Aggregation der Ergebnisse berücksichtigt werden sollte.
  3. Verankerung akkumuliert Informationen, anstatt sie zu verwerfen. Rolling Walkforward verwirft frühe Daten, wenn das Fenster vorrückt. Verankerung behält alle historischen Daten im Trainingsdatensatz bei, während das Fenster wächst.
  4. Mehrere Testergebnisse offenbaren Regime-Sensitivität. Eine Strategie, die ihren Vorteil über alle Testfenster hinweg beibehält, hat sich unter allen Marktbedingungen bewährt, die über den gesamten historischen Zeitraum aufgetreten sind. Eine Strategie, die in einigen Fenstern gut abschneidet und in anderen versagt, offenbart eine echte Regime-Sensitivität – eine Erkenntnis, die ein einzelnes Testfenster vollständig verschleiern würde.
  5. Die dreischichtige Integrität bleibt in jedem Fenster erhalten. Jedes Fenster behält seine eigene innere Validierungsgrenze bei. Die Entwicklungsprozess-Disziplin, die V-in-V sinnvoll macht, wird nicht gelockert – sie wird wiederholt angewendet.

Der Nachteil – und warum er weniger wichtig ist, als es scheint

Die einzelnen Testfenster sind nicht vollständig unabhängig. Spätere Fenster teilen sich den Großteil ihrer Trainingsdaten mit früheren, was zu einer seriellen Korrelation bei den Parameterschätzungen führt, die zur Erzeugung des Ergebnisses jedes Fensters verwendet werden. Die Testergebnisse sollten daher qualitativ bewertet werden – als Gesamtevidenz, die untersucht, ob der Vorteil bestehen bleibt und ob sein Ausmaß ungefähr konsistent ist –, anstatt sie zu einer einzigen gepoolten Statistik mit einer fälschlicherweise aufgeblähten Stichprobengröße zusammenzufassen.

Standard-Walkforward-Erweiterung

Abb. 3. Standard-Walkforward-Erweiterung

Verankerte Walkforward-Erweiterung

Abb. 4. Verankerte Walkforward-Erweiterung – drei Fenster zeigen, wie der Trainingsdatensatz wächst, während sich die innere Validierung und der Final Test nach vorn verschieben

Umsetzung

Der folgende Code zeigt, wie die vollständige dreischichtige V-in-V-Pipeline unter Verwendung von PurgedWalkForwardCV strukturiert wird. Die äußeren Trainingsdaten sind das, was Sie an PurgedWalkForwardCV übergeben; die innere Validierung und die Datensätze des Final Test Sets werden zuvor manuell zurückgehalten.

import numpy as np
import pandas as pd
from sklearn.base import clone

from cross_validation import PurgedWalkForwardCV

def vin_v_anchored_walkforward(
    X: pd.DataFrame,
    y: pd.Series,
    t1: pd.Series,
    estimator,
    n_splits: int = 5,
    inner_val_pct: float = 0.20,
    final_test_pct: float = 0.20,
    pct_embargo: float = 0.01,
    scorer=None,
) -> dict:
    """
    Three-layer V-in-V pipeline with anchored expanding-window walkforward.

    Data is partitioned once, strictly in temporal order:
        [─── Outer Training (60%) ───][─ Inner Val (20%) ─][─ Final Test (20%) ─]

    The outer training set is further split by PurgedWalkForwardCV into
    n_splits expanding windows, each producing its own purged train/test pair.
    The inner validation set is touched only for shortlisting; the final test
    set is opened exactly once after a written commitment to a single config.

    Parameters
    ----------
    X, y, t1 : aligned DataFrame, Series, Series
    estimator  : sklearn-compatible estimator
    n_splits   : number of anchored walkforward windows within outer training
    inner_val_pct, final_test_pct : fractions of total data reserved
    pct_embargo : embargo fraction passed to PurgedWalkForwardCV
    scorer     : callable(y_true, y_pred) -> float, or None (returns raw preds)

    Returns
    -------
    dict with keys:
        outer_scores    – per-window score within outer training
        inner_val_score – score on the inner validation set (shortlisting only)
        final_test_score– score on the final test set (opened once, at the end)
        outer_cv        – the fitted PurgedWalkForwardCV object
    """
    n = len(X)

    # ── 1. Partition the data into three zones ───────────────────────────────
    outer_end   = int(n * (1.0 - inner_val_pct - final_test_pct))
    val_end     = int(n * (1.0 - final_test_pct))

    X_outer,     y_outer,     t1_outer     = X.iloc[:outer_end],  y.iloc[:outer_end],  t1.iloc[:outer_end]
    X_inner_val, y_inner_val, t1_inner_val = X.iloc[outer_end:val_end], y.iloc[outer_end:val_end], t1.iloc[outer_end:val_end]
    X_final,     y_final                   = X.iloc[val_end:],    y.iloc[val_end:]

    # ── 2. Phase 1 — exhaustive search within outer training ─────────────────
    # PurgedWalkForwardCV(expanding_window=True) anchors the start at index 0
    # and grows the training window forward — this IS anchored walkforward.
    outer_cv = PurgedWalkForwardCV(
        n_splits=n_splits,
        t1=t1_outer,
        pct_embargo=pct_embargo,
        expanding_window=True,   # ← anchored, not rolling
    )

    outer_scores = []
    outer_models = []

    for train_idx, test_idx in outer_cv.split(X_outer, y_outer):
        model = clone(estimator)
        model.fit(X_outer.iloc[train_idx], y_outer.iloc[train_idx])
        preds = model.predict(X_outer.iloc[test_idx])

        score = scorer(y_outer.iloc[test_idx], preds) if scorer else None
        outer_scores.append(score)
        outer_models.append(model)

    # At this point the researcher reviews outer_scores, shortlists candidates,
    # and selects a small number of finalist configurations. The inner
    # validation set is NOT touched yet.

    # ── 3. Phase 2 — shortlist on inner validation (sparingly) ───────────────
    # Only finalist models are exposed here. This set must not be used
    # to refine parameters — observation terminates candidate selection.
    finalist_model = outer_models[-1]   # placeholder: researcher selects this
    val_preds      = finalist_model.predict(X_inner_val)
    inner_val_score = scorer(y_inner_val, val_preds) if scorer else None

    # ── 4. Phase 3 — final test (opened exactly once) ────────────────────────
    # The researcher commits in writing to finalist_model before this line.
    # Any adjustment after seeing this result invalidates the test.
    final_preds      = finalist_model.predict(X_final)
    final_test_score = scorer(y_final, final_preds) if scorer else None

    return {
        "outer_scores":     outer_scores,
        "inner_val_score":  inner_val_score,
        "final_test_score": final_test_score,
        "outer_cv":         outer_cv,
    }

# ── Example usage ─────────────────────────────────────────────────────────
if __name__ == "__main__":
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.metrics import accuracy_score

    dates  = pd.date_range("2018-01-01", periods=1000, freq="B")
    X_demo = pd.DataFrame(np.random.randn(1000, 5), index=dates)
    y_demo = pd.Series(np.random.randint(0, 2, 1000), index=dates)
    t1_demo = pd.Series(dates + pd.Timedelta(days=5), index=dates)

    results = vin_v_anchored_walkforward(
        X=X_demo, y=y_demo, t1=t1_demo,
        estimator=RandomForestClassifier(n_estimators=50, random_state=42),
        n_splits=5,
        scorer=accuracy_score,
    )

    print("Outer window scores:", results["outer_scores"])
    print("Inner validation:  ", results["inner_val_score"])
    print("Final test score:  ", results["final_test_score"])


Teil III: Kombinatorisch bereinigte Kreuzvalidierung

Das Problem, das CPCV lösen sollte

Validation-within-Validation setzt voraus, dass die einzelnen Bewertungen, die innerhalb des äußeren Trainingssets durchgeführt werden, selbst sauber sind. Es steuert den Entwicklungsprozess des Forschungsprozesses, überprüft jedoch nicht die interne Validität jeder Trainings-/Testbewertung.

Dies ist das Problem, das CPCV, entwickelt von Marcos Lopez de Prado in Advances in Financial Machine Learning, lösen sollte. Die Kontamination, die es adressiert, ist nicht verhaltensbedingt, sondern strukturell und in den statistischen Eigenschaften der Finanzzeitreihen selbst eingebettet.

Warum Finanzdaten anders sind

Die standardmäßige k-fache Kreuzvalidierung – das Arbeitspferd des maschinellen Lernens – geht davon aus, dass Beobachtungen unabhängig und identisch verteilt sind. Finanzzeitreihen verletzen diese Annahme grundlegend und auf vielfältige, sich überschneidende Weise.

Betrachten Sie einen Featuresatz, der einen gleitenden 20-Tage-Durchschnitt enthält. Die am Tag 21 berechnete Beobachtung teilt 19 ihrer 20 zugrunde liegenden Datenpunkte mit der an Tag 20 berechneten Beobachtung. Wenn diese beiden Beobachtungen verschiedenen Folds zugewiesen werden – eine zum Training, eine zum Testen –, hat der Test-Fold effektiv fast alle Trainingsdaten gesehen, die die entsprechende Trainingsbeobachtung erzeugt hat. Die Grenze zwischen Training und Test ist eine Illusion.

Dieses Problem wird durch die Labelbildung noch verschärft. Im Bereich Financial Machine Learning werden Beobachtungen typischerweise basierend auf dem Ergebnis einer zukünftigen Haltedauer mit Labels versehen. Ein Label, das durch die Rendite über die nächsten 10 Tage gebildet wird, erzeugt eine Abhängigkeit zwischen der mit Labels versehenen Beobachtung und den 10 nachfolgenden Kursbars. Diese nachfolgenden Bars werden mit ziemlicher Sicherheit an anderer Stelle im Datensatz als Feature-Inputs erscheinen und einen Leckagekanal schaffen, den ein Standard-Purging nicht erfasst.

Abb. 5 veranschaulicht die k Trainings-/Testsplits, die durch einen k-fachen CV durchgeführt werden, wobei k = 5 ist. In diesem Schema:

  1. Der Datensatz wird in k Teilmengen aufgeteilt.
  2. Für i = 1,...,k:
    • (a) Der ML-Algorithmus wird auf allen Teilmengen außer i trainiert.
    • (b) Der angepasste ML-Algorithmus wird an i getestet.



Abb. 5. Train/Test-Splits in einem fünffachen CV-Schema

Die zwei Purging-Mechanismen

Purging: Nachdem ein Test-Fold zugewiesen wurde, entfernt CPCV aus dem Trainingsset alle Beobachtungen, deren Label-Bildungsfenster zeitlich mit dem Testzeitraum überlappt. Dies ist nicht einfach eine Frage der Trennung von Zeiträumen – das Purging muss den spezifischen Horizont berücksichtigen, über den das Label jeder Beobachtung berechnet wurde.

Embargoing: Ein Puffer von Beobachtungen, der unmittelbar auf das Test-Fold folgt, wird ebenfalls aus dem Training entfernt. Features, die auf diesen Post-Test-Beobachtungen berechnet wurden, könnten in den Testzeitraum zurückblicken und einen Reverse-Leakage-Kanal erzeugen. Das Embargo-Fenster wird auf den längsten Feature-Lookback im Feature-Set bemessen.

Embargo für Trainingsbeobachtungen nach dem Test

Abb. 6. Purging von Trainingsbeobachtungen vor dem Test und Embargo für Trainingsbeobachtungen nach dem Test

Die kombinatorischen Pfade

Über das Purging und Embargoing hinaus führt CPCV eine strukturelle Verbesserung gegenüber der Standard-k-Fold-Validierung ein. Anstatt einer einzigen Train/Test-Aufteilung generiert es alle möglichen Kombinationen von Fold-Zuweisungen. Diese Splits werden dann zu vollständigen Backtest-Pfaden rekombiniert.

Betrachten Sie T Beobachtungen, die ohne Mischen in N Gruppen unterteilt sind, wobei die Gruppen n = 1, ... , N − 1 die Größe ⌊T∕N⌋ haben, die N-te Gruppe die Größe T − ⌊T∕N⌋ (N − 1) hat und ⌊.⌋ die Floor- oder Integer-Funktion ist. Für ein Testset der Größe k Gruppen ist die Anzahl der möglichen Trainings-/Test-Splits

Kombinatorische Expansion

Da jede Kombination k getestete Gruppen umfasst, ist die Gesamtzahl der getesteten Gruppen . Und da wir alle möglichen Kombinationen berechnet haben, sind diese getesteten Gruppen gleichmäßig über alle N verteilt (jede Gruppe gehört zur gleichen Anzahl von Trainings- und Testsets). Die Implikation ist, dass wir aus k-großen Testsets auf N Gruppen eine Gesamtzahl von Pfaden 𝜑 [N, k] backtesten können,

... Es gibt C(6, 4) = 15 Splits, indexiert als S1, … ,S15. Für jeden Split lässt die Abbildung ... die Gruppen, die das Trainingsset bilden, unmarkiert. Jede Gruppe ist Teil von 𝜑 [6, 2] = 5 Testmengen, daher ermöglicht uns dieses Trainings-/Test-Aufteilungsschema die Berechnung von 5 Backtest-Pfaden.

Abb. 7 zeigt die Zuweisung jeder getesteten Gruppe zu einem Backtest-Pfad. Zum Beispiel ist Pfad 1 das Ergebnis der Kombination der Prognosen aus (G1, S1), (G2, S1), (G3, S2), (G4, S3), (G5, S4) und (G6, S5). Pfad 2 ist das Ergebnis der Kombination der Prognosen aus (G1, S2), (G2, S6), (G3, S6), (G4, S7), (G5, S8) und (G6, S9) und so weiter.

Diese Pfade werden generiert, indem der Klassifikator für jede Kombination auf einem Teil 𝜃 = 1 − k∕N der Daten trainiert wird. Obwohl es theoretisch möglich ist, auf einem Teil 𝜃 < 1∕2 zu trainieren, gehen wir in der Praxis davon aus, dass k ≤ N∕2 ist. Der Datenanteil im Trainingsset 𝜃 nimmt mit N → T zu, nimmt aber mit k → N∕2 ab. Die Anzahl der Pfade 𝜑 [N, k] nimmt mit N → T und mit k → N∕2 zu. Im Grenzwert wird die größte Anzahl an Pfaden erreicht, indem N = T und k = N∕2 = T∕2 gesetzt werden, auf Kosten des Trainings des Klassifikators mit nur der Hälfte der Daten für jede Kombination (𝜃 = 1∕2).

CPCV-Backtest-Pfade

Abb. 7. Zuweisung von Testgruppen zu jedem der 5 Pfade


López de Prado, 2018, S. 164-165

Bei N=6 Gruppen und k=2 Testgruppen fügt jeder Pfad die Out-of-Sample-Segmente aus einer Teilmenge der Aufteilungen zusammen, um die gesamte Zeitachse genau einmal abzudecken, was C(6,2) × k / N = 5 vollständige Pfade ergibt. Es sind diese Pfade – nicht die einzelnen Aufteilungen –, die die korrekte Grundlage für die Berechnung von Sharpe-Quotienten und die Bewertung der Robustheit der Verteilung bilden.

Diese Verteilung ist an sich informativ. Eine Strategie mit einer schmalen, positiven Verteilung der Pfadergebnisse über alle kombinatorischen Fold-Zuweisungen hinweg hat ihre Robustheit über viele verschiedene zeitliche Konfigurationen hinweg bewiesen. Eine Strategie, deren Performance über Pfade hinweg stark variiert, ist fragil, unabhängig von ihrer durchschnittlichen Performance.

Bar-Chart

Abb. 8: Bar-Chart von 5 CPCV-Pfad-Sharpe-Quotienten im direkten Vergleich für eine robuste und eine fragile Strategie

Umsetzung

import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier

from combinatorial import CombinatorialPurgedCV, CPCVAnalyzer, optimal_folds_number

# ── 1. Configure the CV generator ────────────────────────────────────────
# Use optimal_folds_number to find N and k that meet your targets.
# Here: aim for ~600 obs in each training set and 5 backtest paths.
n_folds, n_test_folds = optimal_folds_number(
    n_observations=1000,
    target_train_size=600,
    target_n_test_paths=5,
)
print(f"n_folds={n_folds}, n_test_folds={n_test_folds}")

cv = CombinatorialPurgedCV(
    n_folds=n_folds,
    n_test_folds=n_test_folds,
    t1=t1_outer,          # pd.Series: index=event start, values=event end
    pct_embargo=0.01,     # removes 1% of obs after each test block
)

print(cv.summary(X_outer))
# Number of Observations      1000
# Total Number of Folds          6
# Number of Test Folds           2
# Number of Test Paths           5
# Number of Training Combinations 15

# ── 2. Fit and predict across all combinatorial splits ───────────────────
# CPCVAnalyzer handles the parallel execution and path recombination.
analyzer = CPCVAnalyzer(
    estimator=RandomForestClassifier(n_estimators=100, random_state=42),
    cv_gen=cv,
    close_prices=close_prices,   # pd.Series of price for MtM Sharpe calculation
)

recombined_preds = analyzer.fit_predict(X_outer, y_outer)


# ── 3. Inspect the distribution of path Sharpe ratios ───────────────────
# Each path is an independent backtest covering the full timeline once.
# A tight, positive distribution signals robustness.
metrics = analyzer.get_distribution_metrics(primary_sides=sides)

path_sharpes = metrics.xs("binary", level="method")["mtm_sharpe"]
print("Path Sharpe ratios:")
print(path_sharpes.to_string())
print(f"Mean: {path_sharpes.mean():.3f}  Std: {path_sharpes.std():.3f}")


# ── 4. Visualise train/test fold assignments ─────────────────────────────
# Run split() fully first so index_train_test_ is complete.
_ = [s for s in cv.split(X_outer)]   # exhaust generator
fig = cv.plot_train_test_folds()
fig.show()


Teil IV: Kombinatorisch symmetrische Kreuzvalidierung

Von Schutz zu Messung

Während V-in-V und CPCV schützende Mechanismen sind – sie reduzieren Overfitting durch Strukturierung des Forschungsprozesses und Bereinigung von Datenlecks –, ist CSCV primär ein Diagnose- und Messinstrument. Ihr Ergebnis ist eine einzelne Zahl: die Wahrscheinlichkeit für Backtest-Overfitting (PBO).

CSCV wurde von David Bailey und Marcos Lopez de Prado als eine Methode zur Quantifizierung des Grades eingeführt, in dem ein Strategieauswahlprozess Ergebnisse liefert, die eher auf In-Sample-Optimierung als auf echter Out-of-Sample-Vorhersagekraft beruhen. Die PBO ist eine falsifizierbare, kommunizierbare Aussage, kein qualitatives Urteil. Sie kann in jeder Phase der Forschungspipeline angewendet werden, in der eine Kandidatenmenge existiert – nicht ausschließlich nach der V-in-V-Shortlisting-Phase –, was sie zu einem flexiblen Prüfungsinstrument macht.

Die Mechanik

Das Verfahren beginnt mit der Unterteilung der historischen Daten in S gleiche Teilmengen, typischerweise zwischen 8 und 16. Alle C(S, S/2) Möglichkeiten, diese Teilmengen in eine Trainingshälfte und eine Testhälfte aufzuteilen, werden dann generiert. Für jede Aufteilung wird die Strategie mit der besten In-Sample-Performance identifiziert. Ihr Rang unter allen Strategien in der Out-of-Sample-Hälfte derselben Aufteilung wird dann aufgezeichnet.

CSCV Schritt 1

Abb. 9. CSCV Schritt 1: Die Daten werden in S gleiche Teilmengen unterteilt. Alle C(S, S/2) Möglichkeiten, die Hälfte der In-Sample (IS, grün) und die Hälfte der Out-of-Sample (OOS, rot) zuzuordnen, werden dann aufgezählt

CSCV Schritt 2

Abb. 10. CSCV Schritt 2: Für jede Aufteilung wird die beste In-Sample (IS)-Strategie identifiziert (S3★), dann wird ihr Rang in der Out-of-Sample (OOS)-Hälfte aufgezeichnet. Hier belegt sie den 4. Platz von 6 – unter dem Median – was zum PBO-Wert beiträgt.

CSCV Schritt 3

Abb. 11. CSCV Schritt 3: Der Anteil der Aufteilungen, bei denen die beste In-Sample-Strategie unter dem Out-of-Sample-Median rangierte, ist die PBO-Schätzung. Ein PBO von 0,38 deutet auf einen signifikanten Selektionsbias im Forschungsprozess hin

PBO-Interpretation

Abb. 12. PBO-Interpretation. Ein PBO nahe 0 zeigt an, dass der Auswahlprozess zuverlässig die tatsächlich besten Out-of-Sample-Strategien identifiziert. Ein PBO nahe 0.5 zeigt an, dass die beste In-Sample-Strategie Out-of-Sample im Wesentlichen zufällig eingestuft wird. Der Schwellenwert für „akzeptabel“ variiert je nach Kontext und Größe der Kandidatenmenge – es ist eine Faustregel, keine formale statistische Grenze

Was CSCV nicht tut

CSCV behebt kein Overfitting. Es bereinigt keine zeitlichen Lecks, kontrolliert nicht die Freiheitsgrade des Forschers und strukturiert nicht den Entwicklungsprozess. Ein hoher PBO-Wert sagt Ihnen, dass Ihr Auswahlprozess unzuverlässig ist – er sagt Ihnen nicht, wie Sie ihn zuverlässig machen können.

CSCV ist am effektivsten, wenn es als abschließende Prüfung eingesetzt wird: Nach der Zusammenstellung Ihrer Kandidaten-Shortlist mithilfe des V-in-V-Frameworks erzeugt es einen PBO-Wert für den Auswahlprozess als Ganzes. Ein niedriger PBO-Wert bietet eine quantitative, kommunizierbare Sicherheit, dass der Auswahlprozess eher Signale als Rauschen erzeugte. Da CSCV jedoch auf jeder beliebigen Sammlung von Strategie-Testergebnissen arbeitet, kann es gleichermaßen in früheren Phasen angewendet werden, beispielsweise zur Prüfung des Kandidatengenerierungsprozesses innerhalb des Outer Training Set.

[!] Hinweis zu den Schwellenwerten: Das ursprüngliche Papier von Bailey & Lopez de Prado legt keinen universellen PBO-Schwellenwert für „akzeptable“ Ergebnisse fest. Werte wie 0.1 oder 0.2 sind informelle Faustregeln, die von der Anzahl der getesteten Kandidaten, der Länge der historischen Stichprobe und der Risikotoleranz des Anwenders bei der Auswahl abhängen. Sie sollten nicht als formale statistische Kriterien behandelt werden.


Umsetzung

CSCV verwendet CombinatorialPurgedCV direkt wieder. Da CSCV alle Teilmengen symmetrisch behandelt – es gibt keinen zeitlichen Pfeil zwischen In-Sample- und Out-of-Sample-Hälften –, setzen Sie pct_embargo=0.0 und n_test_folds=n_folds//2. Dies erzeugt genau die oben beschriebenen symmetrischen C(S, S/2)-Splits.

"""
Probability of Backtest Overfitting (PBO) — Bailey & Lopez de Prado (2014).

Uses CombinatorialPurgedCV with pct_embargo=0.0 and n_test_folds=n_folds//2
to generate the C(S, S/2) symmetric IS/OOS splits required by CSCV.
"""
from math import comb
from typing import Callable, Dict, List

import numpy as np
import pandas as pd
from scipy.stats import norm

from combinatorial import CombinatorialPurgedCV

def compute_pbo(
    returns_matrix: pd.DataFrame,
    t1: pd.Series,
    n_folds: int = 8,
    metric: Callable = None,
) -> Dict:
    """
    Compute the Probability of Backtest Overfitting (PBO).

    Parameters
    ----------
    returns_matrix : pd.DataFrame, shape (T, N)
        Columns are candidate strategies; rows are time-ordered observations.
        Values should be per-period returns (or any scalar performance measure
        that is comparable across strategies at a given time step).

    t1 : pd.Series
        Event end-times aligned with returns_matrix.index. Because CSCV is
        symmetric (no temporal arrow), pct_embargo is set to 0.0, so t1 is
        used only to satisfy the CombinatorialPurgedCV interface — you may
        pass t1 = pd.Series(index, index=index) as a neutral value.

    n_folds : int, default=8
        Number of equal subsets S. Must be even. C(S, S/2) splits are
        generated. Bailey & Lopez de Prado recommend 8–16.

    metric : callable(returns: pd.Series) -> float, optional
        Aggregates a column of returns into a scalar performance measure.
        Defaults to the Sharpe ratio (mean/std).

    Returns
    -------
    dict with keys:
        pbo          – float in [0, 1]: estimated probability of overfitting
        n_splits     – total number of IS/OOS splits evaluated (= C(S, S/2))
        below_median – number of splits where best-IS ranked below OOS median
        oos_ranks    – list of normalised OOS ranks for the best-IS strategy
                       (0 = best OOS, 1 = worst OOS)
        logit_sr     – logit-transformed OOS score (if in (0,1)) for best-IS strategy per split
    """
    if n_folds % 2 != 0:
        raise ValueError(f"n_folds must be even for symmetric CSCV; got {n_folds}.")

    n_test_folds = n_folds // 2          # ← symmetric split: half IS, half OOS

    if metric is None:
        def metric(r: pd.Series) -> float:
            """Sharpe ratio."""
            return r.mean() / r.std(ddof=1) if r.std() > 0 else 0.0

    # ── Build the CSCV generator ──────────────────────────────────────────
    # pct_embargo=0.0  → no embargo (CSCV is symmetric, not temporal)
    # n_test_folds=n_folds//2 → exactly half the folds form the OOS set
    cv = CombinatorialPurgedCV(
        n_folds=n_folds,
        n_test_folds=n_test_folds,   # = n_folds // 2
        t1=t1,
        pct_embargo=0.0,             # ← required for symmetric CSCV
    )

    n_strategies = returns_matrix.shape[1]
    below_median = 0
    oos_ranks    = []
    logit_sr     = []

    # ── Iterate over all C(n_folds, n_folds//2) symmetric splits ────────
    for train_idx, test_idx_list in cv.split(returns_matrix):
        test_idx = np.concatenate(test_idx_list)

        # IS half: compute metric for every strategy
        is_scores = returns_matrix.iloc[train_idx].apply(metric, axis=0)

        # OOS half: compute metric for every strategy
        oos_scores = returns_matrix.iloc[test_idx].apply(metric, axis=0)

        # Best IS strategy
        best_is_col = is_scores.idxmax()

        # Rank of that strategy OOS (0-based; 0 = best OOS performer)
        oos_sorted = oos_scores.rank(ascending=False)   # rank 1 = best
        oos_rank   = oos_sorted[best_is_col]               # 1-indexed

        # Normalise rank to [0, 1]: 0 = best OOS, 1 = worst OOS
        norm_rank = (oos_rank - 1) / (n_strategies - 1) if n_strategies > 1 else 0.0
        oos_ranks.append(float(norm_rank))

        # Below median? (norm_rank > 0.5 means worse than median)
        if norm_rank > 0.5:
            below_median += 1

        # Logit-transformed OOS score (if in (0,1)) for the best-IS strategy
        oos_sr = float(oos_scores[best_is_col])
        logit_sr.append(np.log(oos_sr / (1 - oos_sr)) if 0 < oos_sr < 1 else np.nan)

    n_splits = cv.n_splits
    pbo      = below_median / n_splits

    return {
        "pbo":          pbo,
        "n_splits":     n_splits,
        "below_median": below_median,
        "oos_ranks":    oos_ranks,
        "logit_sr":     [v for v in logit_sr if not np.isnan(v)],
    }

# ── Example usage ─────────────────────────────────────────────────────────
if __name__ == "__main__":
    # Simulate 50 strategy return streams over 500 time steps
    rng     = np.random.default_rng(42)
    dates   = pd.date_range("2020-01-01", periods=500, freq="B")
    returns = pd.DataFrame(
        rng.normal(0.0001, 0.01, size=(500, 50)),
        index=dates,
        columns=[f"strategy_{i}" for i in range(50)],
    )

    # Neutral t1: event start == event end (no label horizon, no purging effect)
    t1_neutral = pd.Series(dates, index=dates)

    result = compute_pbo(
        returns_matrix=returns,
        t1=t1_neutral,
        n_folds=8,      # → n_test_folds=4, C(8,4)=70 splits
    )

    print(f"PBO  : {result['pbo']:.3f}")
    print(f"Splits evaluated : {result['n_splits']}")
    print(f"Below median (IS best → OOS below median): {result['below_median']}")

    # Interpret: if returns are pure noise, PBO ≈ 0.5
    # A strategy with genuine edge should push PBO toward 0


Teil V: Eine einheitliche Pipeline

Zuordnung der Techniken zu den Forschungsphasen

Die Beziehung zwischen diesen drei Techniken wird deutlich, wenn sie den Phasen des V-in-V-Entwicklungsprozesses zugeordnet werden. Sie konkurrieren nicht miteinander. Sie nehmen unterschiedliche Positionen in der Pipeline ein und schützen vor unterschiedlichen Bedrohungen.

Technik

Rolle in der Pipeline

Adressierte Bedrohung

Ausgabe

CPCV

Innerhalb des äußeren Trainingsbereichs (Phase 1)

Zeitliche Leckage innerhalb jeder Trainings-/Testauswertung

Saubere Leistungspfade pro Auswertung

V-in-V

Steuert den gesamten Entwicklungsprozess

Über Iterationen hinweg akkumulierte Freiheitsgrade des Forschers

Entwicklungsprozessstruktur mit drei geschützten Zonen

CSCV

Nach der Vorauswahl der Kandidaten (Phase 2) oder in jeder Phase, in der eine Kandidatenmenge existiert

Selektionsbias bei der Kandidatenmenge

PBO – eine falsifizierbare Wahrscheinlichkeitsschätzung

Lösen sie das gleiche Problem?

Die drei Verfahren sind insofern miteinander verwandt, als sie alle existieren, weil naive Trainings-/Test-Splits bei Finanzdaten zu optimistischen Leistungsschätzungen führen. Sie adressieren jedoch grundlegend unterschiedliche Mechanismen dieses gemeinsamen Problems.

Validation-within-Validation adressiert die Verunreinigung durch Verhaltensweisen, die sich über den gesamten Entwicklungsprozess hinweg ansammeln, wenn ein menschlicher Forscher iterative Modellierungsentscheidungen trifft. Diese Kontamination wird vom Forscher eingebracht und würde nicht existieren, wenn von Anfang an eine einzige, nicht-iterative Strategiewahl getroffen würde.

Combinatorially Purged Cross-Validation adressiert strukturelle Kontamination, die in der zeitlichen Abhängigkeit von Finanzbeobachtungen eingebettet ist. Diese Kontamination existiert in den Daten selbst und würde selbst dann bestehen bleiben, wenn der Forscher überhaupt keine iterativen Entscheidungen treffen würde – sie ist eine mathematische Eigenschaft von Finanzzeitreihen.

Combinatorially Symmetric Cross-Validation quantifiziert die Wahrscheinlichkeit, dass der Auswahlprozess als Ganzes ein überangepasstes Ergebnis geliefert hat, und liefert eine falsifizierbare, kommunizierbare Aussage über die Zuverlässigkeit der gewählten Strategie, die keines der beiden anderen Verfahren liefert.

Die Durchführung von CPCV ohne die strukturelle Disziplin von V-in-V liefert zwar saubere Einzelbewertungen, lässt aber die Freiheitsgrade des Forschers unkontrolliert. Ein Entwickler könnte CPCV durch fünfzig Iterationen der Strategieverfeinerung laufen lassen, jedes Ergebnis beobachten und jede Beobachtung die nächste Modellierungsentscheidung beeinflussen lassen. Jede einzelne Bewertung wäre sauber, aber der Forschungsprozess als Ganzes wäre kontaminiert.

Umgekehrt bewahrt die Durchführung von V-in-V ohne CPCV innerhalb von Phase 1 den Final Test Set perfekt, kann aber einen Kandidaten aus Phase 1 hervorbringen, der auf der Grundlage von durch Leckagen aufgeblähten Bewertungen ausgewählt wurde. Die dreischichtige Struktur schützt den Final Test Set, aber der Kandidatenmechanismus, der ihn speist, ist beeinträchtigt.

Das Weglassen von CSCV bedeutet, dass der Anwender keine quantitative Schätzung darüber hat, wie zuverlässig der Auswahlprozess war. Sie haben sich zwar gegen bekannte Leckagekanäle geschützt, verfügen aber über keine prüfbare, kommunizierbare Aussage über die Wahrscheinlichkeit, dass die gewählte Strategie einen echten Vorteil darstellt und nicht nur ein Glückstreffer ist.

Welches Verfahren ist am wichtigsten?

Wenn man gezwungen ist, nach der Schwere der Verzerrung zu priorisieren, die entsteht, wenn jedes Verfahren isoliert weggelassen wird, ist die folgende Reihenfolge ein vernünftiger Ausgangspunkt – wobei Anwender beachten sollten, dass dies ein redaktionelles Urteil und kein gesichertes empirisches Ergebnis ist und die relative Bedeutung jedes Verfahrens mit der Länge des Datensatzes, der Komplexität der Merkmale und der Intensität des Forschungsprogramms variiert.

1. CPCV – Nicht verhandelbar für Finanzzeitreihen. Die standardmäßige k-fache Kreuzvalidierung ist für Finanzdaten nicht nur suboptimal; sie ist strukturell falsch. Die i.i.d.-Annahme, die sie erfordert, wird von praktisch jedem Finanzmerkmalsatz verletzt. Zeitliche Leckagen blähen Leistungsschätzungen auf eine Weise auf, die nachträglich nicht mehr korrigiert werden kann.

2. V-in-V – Nicht verhandelbar für jedes iterative Forschungsprogramm. In der Praxis beinhaltet jede ernsthafte Strategieentwicklungsbemühung wochen- oder monatelange iterative Verfeinerungen. Ohne die strukturelle Disziplin von V-in-V stellt jede Beobachtung der Out-of-Sample-Performance, die eine nachfolgende Entscheidung beeinflusst, nicht berücksichtigte Freiheitsgrade des Forschers dar. In einem langen, intensiven Forschungsprogramm kann dieser kumulierte Effekt jede einzelne Quelle von Datenlecks in den Schatten stellen – was ein starkes Argument dafür liefert, dass V-in-V in der Praxis genauso wichtig oder wichtiger als CPCV sein kann.

3. CSCV – Einzigartig wertvoll für die Kommunikation von Ergebnissen. Sein quantitativer PBO-Output bietet etwas, das die anderen beiden Techniken nicht können: eine einzelne, vertretbare Zahl, die die Zuverlässigkeit des gesamten Auswahlprozesses zusammenfasst. Dies macht es besonders wertvoll in institutionellen Kontexten, in denen Forschung überprüft, von Fachkollegen validiert oder an Stakeholder kommuniziert werden muss, die die vollständige Methodik nicht einsehen können.


Schlussfolgerung

Der „Spaghetti-an-die-Wand“-Ansatz zur Strategiefindung muss nicht intellektuell unseriös sein. Eine breite explorative Suche über große Indikatorräume kann echte Marktvorteile zutage fördern, vorausgesetzt, der Forscher hat eine Validierungsinfrastruktur konstruiert, die rigoros genug ist, um echte Signale von dem statistischen Rauschen zu trennen, das eine Brute-Force-Suche zwangsläufig erzeugt.

Keine einzelne Technik ist ausreichend. V-in-V, CPCV und CSCV adressieren jeweils einen unterschiedlichen Fehlermodus, arbeiten in einer anderen Phase der Forschungspipeline und bieten eine andere Form von Schutz oder Messung. Der Einsatz einer Technik ohne die anderen lässt erhebliche Angriffsflächen offen.

CPCV stellt sicher, dass jede Train/Test-Auswertung innerhalb der explorativen Phase frei von zeitlichen Informationslecks ist. V-in-V stellt sicher, dass der Entwicklungsprozess als Ganzes keine Freiheitsgrade des Forschers ansammelt, die die scheinbare Signifikanz der endgültigen Strategieauswahl aufblähen. CSCV liefert eine quantitative, prüfbare Schätzung der Wahrscheinlichkeit, dass der Auswahlprozess selbst einen echten Vorteil hervorgebracht hat und nicht ein optimiertes Artefakt der verfügbaren Daten.

Zusammen transformieren diese drei Techniken die Sharpe-Ratio (oder jede andere Leistungskennzahl) eines Backtests von einer Zahl, die widerspiegelt, was ein Forscher zu finden hoffte, in eine Zahl, die repräsentiert, was die Strategie tatsächlich voraussichtlich liefern wird.


Code-Referenz

Die Implementierungsbeispiele in diesem Artikel stützen sich auf fünf Dateien. Die folgende Tabelle fasst ihre Rollen und Beziehungen zusammen.

Datei Zweck Wichtige Inhalte Abhängigkeiten
cross_validation.py Grundbausteine für Core Purging und Walk-Forward-Kreuzvalidierung. Grundlage für alle übergeordneten Komponenten. ml_get_train_times – vektorisiertes ereignisbasiertes Purging, PurgedKFold – standardmäßiges Purged K-Fold, PurgedWalkForwardCV – verankertes (expandierendes) oder rollierendes Walk-Forward mit Purging und Embargo, ml_cross_val_score – CV-Runner für eine einzelne Metrik, analyze_cross_val_scores – CV-Runner für mehrere Metriken mit Aufschlüsselung der Konfusionsmatrix. Keine (Basis-Modul)
combinatorial.py Engine für kombinatorisch bereinigte Kreuzvalidierung. Generiert alle C(N,k) Trainings-/Test-Splits, rekombiniert diese zu φ[N,k] vollständigen Backtest-Pfaden und berechnet die vollständige Verteilung der Leistungskennzahlen auf Pfadebene. CombinatorialPurgedCV – sklearn-kompatibler CV-Generator mit Purging, Embargo, Pfadrekombination und Visualisierung der Fold-Aufteilung, CPCVAnalyzer – paralleles Fit/Predict und MtM-Sharpe-Verteilung über alle Pfade, optimal_folds_number – findet das (N, k)-Paar, das am besten zur Ziel-Trainingsgröße und Pfadanzahl passt. cross_validation.py (importiert ml_get_train_times)
anchored_walkforward.py Implementiert den vollständigen dreistufigen V-in-V-Entwicklungsprozess unter Verwendung eines verankerten, expandierenden Fensters. Erzwingt eine strikte Trennung der äußeren Trainings-, inneren Validierungs- und Final Test-Zonen. vin_v_anchored_walkforward – unterteilt Daten in drei zeitliche Zonen, führt PurgedWalkForwardCV(expanding_window=True) innerhalb der äußeren Zone aus, legt den inneren Validierungssatz nur für die Vorauswahl offen und öffnet den Final Test Set genau einmal nach einer schriftlichen Festlegung. cross_validation.py
cpcv_usage.py Anwendungsbeispiel, das zeigt, wie CPCV innerhalb der äußeren Trainingszone einer V-in-V-Pipeline konfiguriert und ausgeführt wird, die Sharpe-Verteilung über Pfade hinweg untersucht und die Methoden zur Visualisierung der Fold-Aufteilung korrekt aufgerufen werden. Demonstriert optimal_folds_number, CombinatorialPurgedCV.summary, CPCVAnalyzer.fit_predict, get_distribution_metrics sowie die korrekte Aufrufreihenfolge für plot_train_test_index und plot_train_test_folds. combinatorial.py
pbo.py Berechnet die Wahrscheinlichkeit einer Backtest-Überanpassung (PBO) unter Verwendung von CombinatorialPurgedCV, konfiguriert für symmetrische CSCV-Splits. Akzeptiert eine beliebige Matrix von Strategierenditen sowie eine Leistungskennzahl und gibt die PBO-Schätzung zusammen mit OOS-Rangdiagnostiken pro Split zurück. compute_pbo – setzt pct_embargo=0.0 und n_test_folds=n_folds//2, um die für CSCV erforderlichen symmetrischen C(S, S/2)-Splits zu erzeugen; identifiziert die beste In-Sample-Strategie pro Split; zeichnet deren normalisierten Out-of-Sample-Rang auf; und aggregiert den Anteil der Splits, bei denen sie unter dem OOS-Median rangierte. combinatorial.py

Weiterführende Literatur

  • Masters, T. (1995). Advanced Algorithms for Neural Networks. John Wiley & Sons.
  • Masters, T. (2013). Permutation and Randomization Tests for Trading System Development. Self-published.
  • Lopez de Prado, M. (2018). Advances in Financial Machine Learning. John Wiley & Sons.
  • Bailey, D. H., & Lopez de Prado, M. (2014). The Deflated Sharpe Ratio: Correcting for Selection Bias, Backtest Overfitting and Non-Normality. Journal of Portfolio Management.
  • Bailey, D. H., Borwein, J., Lopez de Prado, M., & Zhu, Q. J. (2014). Pseudo-Mathematics and Financial Charlatanism: The Effects of Backtest Overfitting on Out-of-Sample Performance. Notices of the AMS.
  • White, H. (2000). A Reality Check for Data Snooping. Econometrica, 68(5), 1097–1126.

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/21603

Beigefügte Dateien |
cross_validation.zip (175.21 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (6)
Stanislav Korotky
Stanislav Korotky | 14 März 2026 in 13:35

Oh, ich bin mit meinen Gedanken etwas spät dran, Warren Giddings hat bereits gute Argumente vorgebracht. ;-)

Insbesondere möchte ich erwähnen, dass eine sehr wichtige Metaoptimierung in dem Artikel außer Acht gelassen wurde – nämlich die Anpassung der In-Sample-Fenster- und Vorwärtsschrittgrößen. Denn Walkforward ist nicht auf „Rolling“ und „Anchored“ beschränkt, sondern es gibt auch die Cluster-Walkforward-Optimierung.

Daher sollten alle beschriebenen Methoden sozusagen in einer weiteren, senkrechten Dimension von IS/OOS-Größenkombinationen erneut angewendet und im Testzeitraum überprüft werden.

Vasiliy Sokolov
Vasiliy Sokolov | 16 März 2026 in 10:03
Tolle Arbeit! Vielen Dank!
Patrick Murimi Njoroge
Patrick Murimi Njoroge | 18 Apr. 2026 in 23:00
Warren Giddings bewegen wir uns weg von „manuellen Pipelines“ hin zuautomatisierten Teststudios v . Das Ziel ist nicht nur, eine Pipeline zu haben, sondern eine standardisierte, praxiserprobte Umgebung, in der der menschliche Faktor vollständig aus dem Validierungsprozess entfernt wird. Ohne diese Automatisierung und Standardisierung ist selbst die „einheitlichste“ Pipeline nur ein weiteres Werkzeug für P-Hacking.
Hallo Warren,

vielen Dank für dein Feedback. Ich stimme allen von dir angesprochenen Punkten voll und ganz zu, und der Aufbau einer solchen automatisierten Pipeline ist der Kern meiner Serie

„MetaTrader 5 Machine Learning Blueprint“. Dieser Artikel sollte den Lesern, die sich noch nie viele Gedanken über die Art von Überanpassung gemacht haben, die ich in diesem Artikel thematisiert habe, die Augen öffnen. Aus diesem Grund habe ich ihn bewusst getrennt von den anderen Artikeln der ML-Blueprint-Reihe verfasst.

Patrick Murimi Njoroge
Patrick Murimi Njoroge | 18 Apr. 2026 in 23:04
Stanislav Korotky #:

Ach, ich bin mit meinen Gedanken etwas spät dran – Warren Giddings hat bereits gute Argumente vorgebracht. ;-)

Insbesondere möchte ich darauf hinweisen, dass eine sehr wichtige Meta-Optimierung in dem Artikel außer Acht gelassen wurde – nämlich die Anpassung der In-Sample-Fenster- und Vorwärtsschrittgrößen. Denn Walkforward ist nicht nur durch „Rolling“ und „Anchored“ begrenzt, sondern es gibt auch die Cluster-Walkforward-Optimierung.

Daher sollten alle beschriebenen Methoden sozusagen in einer weiteren, senkrechten Dimension von IS/OOS-Kombinationen der Größen neu aufgerufen und im Testzeitraum überprüft werden.

Hallo Stanislav,

absolut richtig. Wie ich Warren bereits gesagt habe, soll dieser Artikel eine Einführung in die Konzepte sein, mit denen wir Überanpassung abmildern können. Diese werden in meiner Reihe „MetaTrader 5 Machine Learning Blueprint“ ausführlicher behandelt.
Patrick Murimi Njoroge
Patrick Murimi Njoroge | 18 Apr. 2026 in 23:06
Vasiliy Sokolov #:
Tolle Arbeit! Vielen Dank!
Gern geschehen! Danke, dass du dir die Zeit genommen hast, es zu lesen.
Die Übertragung der Trading-Signale in einem universalen Expert Advisor. Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
In diesem Artikel wurden die verschiedenen Möglichkeiten beschrieben, um die Trading-Signale von einem Signalmodul des universalen EAs zum Steuermodul der Positionen und Orders zu übertragen. Es wurden die seriellen und parallelen Interfaces betrachtet.
Vom Einsteiger zum Experten: Optimierung von Liquiditätsstrategien durch strukturelle Bestätigung über mehrere Zeitrahmen in MQL5 Vom Einsteiger zum Experten: Optimierung von Liquiditätsstrategien durch strukturelle Bestätigung über mehrere Zeitrahmen in MQL5
Die Abstimmung von Liquiditätsstrukturen höherer Zeitrahmen mit Umkehrmustern niedrigerer Zeitrahmen kann sowohl die Wahrscheinlichkeit als auch die Richtung der nächsten Preisbewegung stark beeinflussen. Durch die Integration struktureller Liquiditätszonen aus höheren Zeitrahmen mit präzisen Umkehrbestätigungen auf niedrigeren Zeitrahmen können Trader das Einstiegs-Timing und die Qualität ihrer Trades insgesamt verbessern. Dieser Artikel zeigt, wie man liquiditätsbasierte Handelsstrategien durch strukturelle Bestätigung auf höheren Zeitrahmen verstärkt – und wie man diesen Ansatz effektiv mit MQL5 umsetzt.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
Entwicklung eines Toolkits zur Price-Action-Analyse (Teil 64): Manuell gezeichnete Trendlinien mit der automatisierten Überwachung synchronisieren Entwicklung eines Toolkits zur Price-Action-Analyse (Teil 64): Manuell gezeichnete Trendlinien mit der automatisierten Überwachung synchronisieren
Die Überwachung manuell eingezeichneter Trendlinien erfordert eine ständige Beobachtung des Charts, wodurch wichtige Kursinteraktionen übersehen werden können. In diesem Artikel wird ein Expert Advisor zur Überwachung von Trendlinien vorgestellt, der manuell gezeichnete Trendlinien mit einer automatisierten Überwachungslogik in MQL5 synchronisiert und Warnmeldungen ausgibt, wenn sich der Kurs einer überwachten Linie nähert, diese berührt oder durchbricht.