Quant-Faktoren validieren: Bucket Spread, Rank IC und mehrere Horizonte
Beim Aufbau eines Aktien-Rankings oder Scoring-Modells ist es am einfachsten, noch einen Faktor hinzuzufügen.
Momentum, RSI, Relative Strength, Abstand zum 52-Wochen-Hoch, Volatilität, Volumen: Sobald etwas im Backtest halbwegs nützlich aussieht, lässt sich schnell eine weitere Spalte ergänzen und gewichten. Die schwierigere Frage kommt danach: Ordnet dieser Faktor künftige Renditen tatsächlich sinnvoll, oder sah er nur in einem bestimmten Zeitraum gut aus?
Beim Aufräumen meines eigenen Quant-Research-Workflows ist mir erneut aufgefallen, wie leicht man sich von Trefferquote oder einem einzelnen Forward-Return-Horizont täuschen lässt. Ein Faktor kann nach fünf Tagen gut aussehen und nach einem oder drei Tagen ein anderes Bild zeigen. Wenn dieselben Signale täglich ausgewertet werden, wächst die Zahl der Zeilen schnell, obwohl sich die Forward Returns stark überlappen. Die scheinbar große Stichprobe enthält dann weniger unabhängige Information, als die Zeilenzahl suggeriert.
Deshalb habe ich keine alten IC- oder Spread-Werte aus internen Notizen übernommen, sondern eine kleine öffentliche Reproduktion neu gerechnet. Die Daten stammen aus einem öffentlichen Repository und enthalten adjustierte Schlusskurse von fünf ETFs: sector_prices.csv. Die Datei ist auf Blob-SHA 0a442a9260c2ad64e6cd8c01af49328eb7619367 fixiert. Verwendet wurden SPY, XLK, XLF, XLE und XLV sowie einige einfache 20-Tage-Preisfaktoren und Forward Returns für 1D / 3D / 5D.
Das Ergebnis war kein sauberer „bester Faktor“. Genau das war nützlich.
Beim 20-Tage-Momentum war der High-Low Median Spread in der dünn gesampelten Anchor-Stichprobe für 1D und 5D positiv, für 3D aber leicht negativ. Auch der Low-Volatility-Faktor wechselte je nach Horizont die Richtung. Noch interessanter: Ein Faktor „20-Tage Relative Strength gegenüber SPY“ erzeugte exakt dieselbe Querschnittsrangfolge wie das rohe 20-Tage-Momentum.
Diese Unsauberkeiten sind näher an echter Research-Arbeit als ein Bericht, in dem überall grüne Zahlen stehen.
Faktorwerte am Signalzeitpunkt einfrieren
Bei der Faktorvalidierung ist für mich heute die Zeitgrenze wichtiger als die Formel.
Angenommen, das System bewertet an einem bestimmten Tag mehrere Aktien und speichert momentum_20d. Gespeichert werden sollte der Wert, den das System im Moment der Entscheidung tatsächlich gesehen hat. Erst wenn die Outcomes für 1D, 3D und 5D feststehen, prüft man, ob dieser Wert Ranking-Information enthielt.
Also:
decision / snapshot -> factor value at t -> realized return at t + N
Nicht drei Tage später mit den heutigen Daten und dem heutigen Code erneut einen vermeintlich „historischen“ Faktor berechnen.
Beides sieht ähnlich aus, ist aber nicht dasselbe. Datenanbieter können Historien korrigieren. Adjustierungen für Splits oder Dividenden können sich ändern. Das Anlageuniversum und die Faktordefinition können sich ändern. Auch die Querschnittsgruppe für eine Standardisierung kann später eine andere sein. Bei nachträglicher Neuberechnung validiert man leicht „die heutige Version der Vergangenheit“ statt dessen, was das System damals wirklich gesehen hat.
Alphalens folgt im Kern derselben Struktur: Factor Values werden mit späteren Forward Returns ausgerichtet, Renditen nach Quantilen analysiert und ein Information Coefficient berechnet. Die Dokumentation nennt auch ein konkretes Look-ahead-Risiko: Werden zukünftige Renditen selbst zur Berechnung eines z-Scores verwendet, um Ausreißer zu filtern, fließt Zukunftsinformation bereits in die Datenbereinigung ein.
Wenn mein Produktionssystem irgendwann Gewichte automatisch anpasst, möchte ich diese Grenze strenger ziehen als in einem normalen Research-Notebook: Sobald ein Factor Snapshot im Outcome Tracking liegt, dürfen spätere Modellversionen ihn nicht mehr überschreiben.
Die öffentliche Reproduktion hier nutzt keine Produktionsdatenbank. Signal-time Factor und Forward Return werden aus derselben fixierten historischen Preisdaten-Datei erzeugt. Das reicht, um die Rechenlogik zu demonstrieren, beweist aber nicht, dass ein Produktionssystem frei von Point-in-Time-Problemen ist. Diese Aussagen müssen getrennt bleiben.
Bucket Spread und Rank IC beantworten unterschiedliche Fragen
Wenn ein Faktor vor allem zum Ranking dient, starte ich ungern mit der Frage nach der Trefferquote.
Eine Trefferquote komprimiert zu viel Information in ein Boolean. +0,01 % und +8 % zählen beide als Gewinn. Platz 1 und Platz 5 können ebenfalls beide als Gewinn gelten. Für eine Strategieübersicht ist das brauchbar, aber es beantwortet nicht direkt, ob höhere Faktorwerte systematisch mit besseren späteren Ergebnissen zusammenhängen.
Ich nutze hier zwei einfache Größen.
Die erste ist der Bucket Spread. An jedem Signal Date werden die Faktorwerte im Querschnitt gerankt, in Low / Mid / High geteilt und anschließend die späteren Renditen der High- und Low-Gruppen verglichen:
Bucket Spread = median(return_high) - median(return_low)
Wenn ein „höher ist besser“-Faktor wirklich Ranking-Information enthält, sollte die High-Gruppe idealerweise stärker sein als die Low-Gruppe. Ein positiver Spread bedeutet aber nur, dass sich die beiden Enden in dieser Stichprobe getrennt haben. Er ist nicht automatisch statistisch signifikant, sagt nichts über Transaktionskosten aus und garantiert keine monotone Mid-Gruppe.
Die zweite Größe ist Rank IC. Dafür verwende ich die Spearman Rank Correlation zwischen Faktorrang und Rang der späteren Rendite:
Rank IC = Spearman(factor_rank, forward_return_rank)
SciPys spearmanr misst nichtparametrisch monotone Zusammenhänge und liegt zwischen -1 und 1. Bei vielen Faktoren interessiert mich eher, ob die Ordnung Information trägt, als ob zwischen Faktorwert und späterer Rendite ein linearer Zusammenhang besteht.
Die Kernberechnung der Reproduktion ist bewusst klein:
mom20 = px / px.shift(20) - 1
fwd = px.shift(-horizon) / px - 1
x = mom20.loc[dt].dropna()
y = fwd.loc[dt].dropna()
common = x.index.intersection(y.index)
bucket = pd.qcut(
x.loc[common].rank(method="first"),
3,
labels=["low", "mid", "high"],
)
rho, _ = spearmanr(
x.loc[common].values,
y.loc[common].values,
)
spread = (
y.loc[common][bucket == "high"].median()
- y.loc[common][bucket == "low"].median()
)
Das tatsächlich ausgeführte Skript ist als Evidenz im Content Lab fixiert; der Lauf endete mit Exit Code 0. Es ging hier nicht darum, ein neues Quant-Framework zu bauen, sondern die zentrale Validierungslogik so klein zu machen, dass sie nicht hinter komplexer Strategielogik verschwindet.
Ändert sich der Horizont, bewegt sich auch die Aussage
Um Überschneidungen bei Forward Returns etwas zu reduzieren, verwende ich für die Hauptdarstellung jeden fünften Handelstag als Anchor Date. Das ergibt 14 Termine mit jeweils fünf ETFs, also 70 Querschnittsbeobachtungen pro Factor × Horizon.
Der Spread unten ist High Bucket Median minus Low Bucket Median, in Prozentpunkten. Rank IC ist der Mittelwert der querschnittlichen Spearman ICs über die Anchor Dates.
| Factor | Horizon | N | Median Spread | Mean Rank IC |
|---|---|---|---|---|
| 20D Momentum | 1D | 70 | +0.583 pp | +0.179 |
| 20D Momentum | 3D | 70 | -0.071 pp | +0.014 |
| 20D Momentum | 5D | 70 | +0.667 pp | +0.214 |
| Distance to 20D High | 1D | 70 | +0.249 pp | +0.089 |
| Distance to 20D High | 3D | 70 | +0.259 pp | +0.151 |
| Distance to 20D High | 5D | 70 | +0.529 pp | +0.252 |
| Low Volatility 20D | 1D | 70 | -0.029 pp | +0.029 |
| Low Volatility 20D | 3D | 70 | +0.296 pp | +0.014 |
| Low Volatility 20D | 5D | 70 | -0.224 pp | -0.207 |
| Relative Strength 20D vs SPY | 1D | 70 | +0.583 pp | +0.179 |
| Relative Strength 20D vs SPY | 3D | 70 | -0.071 pp | +0.014 |
| Relative Strength 20D vs SPY | 5D | 70 | +0.667 pp | +0.214 |
Das Letzte, was ich mit dieser Tabelle tun möchte, ist die größte Zahl einzukreisen und den Faktor für „gültig“ zu erklären.
Beim 20D Momentum sieht 5D mit +0.667 pp Spread und +0.214 Mean Rank IC sehr verlockend aus. Der 3D Spread liegt aber bei -0.071 pp und der Rank IC fast bei null. Es gibt kein sauberes Muster „je länger der Horizont, desto besser der Faktor“.
Low Volatility ist noch unruhiger. Der 3D Median Spread ist positiv, 5D dreht auf -0.224 pp und der Mean Rank IC auf -0.207. Zeige ich nur die 3D-Spalte, wirkt der Faktor positiv und interessant. Bei 5D sieht die Geschichte anders aus.
Das ist für mich der praktische Wert von Multi-Horizon Outcome Tracking. Es geht nicht darum, 1D, 3D und 5D schnell zu einem hübscheren Gesamtscore zusammenzugewichten, sondern zuerst sichtbar zu machen, wie empfindlich die Aussage gegenüber dem Horizon ist.
Zusätzlich habe ich eine täglich überlappende Stichprobe behalten. Sie enthält 66 Signal Dates und 330 Factor/Outcome-Zeilen und wirkt damit deutlich größer.
Das Problem: 3D- und 5D-Forward-Returns überlappen stark.
Die Ergebnisse ändern sich tatsächlich. Beim 20D Momentum liegt der 3D Median Spread in der daily-overlapping Stichprobe bei etwa +0.578 pp, in der Every-Fifth-Trading-Day-Stichprobe dagegen bei -0.071 pp.
Ich interpretiere daraus nicht, welche Stichprobe „die Wahrheit“ ist. 14 Anchor Dates sind weiterhin wenig und ein Fünf-Tage-Abstand entfernt nicht jede Abhängigkeit. Der wichtigere Punkt ist: Wenn sich die Schlussfolgerung bei einer vernünftigen Änderung des Sampling-Schemas umdreht, sollte ein Research-Bericht nicht nur mit einer großen Gesamtzahl von Zeilen argumentieren.
Vor mehr Faktoren prüfen, ob sie dieselbe Ranginformation tragen
Ein Ergebnis ließ mich zunächst an einen Fehler im Code denken.
Ich hatte gleichzeitig:
momentum_20d
relative_strength_20d_vs_spy
Der zweite Faktor ist definiert als:
asset_20d_return - SPY_20d_return
Nach dem Lauf waren alle Bucket-, Spread- und Rank-IC-Werte identisch.
Der Code war korrekt.
An einem gegebenen Datum ist der 20-Tage-Return von SPY für alle Assets dieselbe Konstante:
RS_i = Momentum_i - C_t
Zieht man im Querschnitt von jedem Asset dasselbe C_t ab, ändert sich die Rangfolge nicht. Wenn Bucket und Rank IC nur von diesem Rang abhängen, enthalten beide Definitionen in dieser Konstruktion dieselbe Ranking-Information.
Diese Beobachtung war für mich wertvoller als ein weiterer Wert wie „IC = 0.21“.
Ein Multi-Factor-Modell kann sehr leicht Scheinkomplexität sammeln: andere Namen, anders aussehende Formeln, aber faktisch dieselbe Richtungsinformation. Zwei stark redundante Faktoren mit getrennten Gewichten liefern nicht zwei Belege. Derselbe Beleg bekommt nur zwei Stimmen.
Das bedeutet ausdrücklich nicht, dass Relative Strength generell gleich Momentum ist. Gegenüber einer Branche, einem dynamischen Benchmark, auf Residual Returns, mit Rolling-Beta-Anpassung oder einem anderen Lookback kann sich die Rangfolge ändern. Die Gleichheit hier folgt nur aus einer sehr konkreten Konstruktion: Am selben Tag wird von jedem Asset derselbe SPY-20D-Return abgezogen.
Vor dem Tuning von Gewichten möchte ich deshalb zuerst Factor Ranks, Korrelationen und Bucket Assignments vergleichen: Fügt der neue Faktor wirklich neue Information hinzu oder nur einen neuen Namen?
Ein einzelner positiver IC darf keine Live-Gewichte verändern
Wenn ein Research-System später ein Modell automatisch tunen soll, möchte ich Abstand zwischen „Faktor entdeckt“ und „beeinflusst Produktion“ lassen.
Ein Kandidat kann zuerst in observe laufen: Der Wert am Signalzeitpunkt wird gespeichert, beeinflusst aber keine Entscheidung. Nach Settlement der Outcomes werden Bucket Spread, Rank IC, Coverage und Stichprobenverteilung über mehrere Horizonte geprüft.
Sieht das weiter interessant aus, kann der Faktor in shadow wechseln. Shadow darf hypothetische Gewichte oder Entscheidungen erzeugen, ändert aber nicht das active model.
Mindestens prüfe ich:
- ausreichende Stichprobengröße und Non-null Coverage;
- eine nachvollziehbare Low / Mid / High-Struktur statt nur eines zufälligen Abstands zwischen den Enden;
- erklärbares Verhalten über 1D / 3D / 5D statt widersprüchlicher Richtungen;
- Stabilität in anderen Zeiträumen oder Regimes;
- Redundanz mit vorhandenen Faktoren;
- Empfindlichkeit gegenüber Sampling- und Dedup-Regeln;
- wie viele Faktoren, Fenster und Parameter während der Research-Phase ausprobiert wurden.
Der letzte Punkt wird besonders leicht vergessen. Factor Research ist oft eine große Menge an Hypothesentests. Wer 50 Indikatoren, sechs Lookbacks und vier Horizons testet und nur das schönste Ergebnis auswählt, erhöht die Chance auf einen attraktiven Treffer auch bei schwachem Grundsignal. Harvey, Liu und Zhu behandeln dieses Multiple-Testing-Problem in … and the Cross-Section of Expected Returns.
Bucket Spread und Rank IC sind für mich deshalb keine Eintrittskarte in das Modell, sondern Basisdiagnostik. Sie zeigen Ranking-Information, Richtungsstabilität, Horizon-Flips und Redundanz. Bis zur Aussage „mit echtem Geld handelbar“ fehlen aber weiterhin Stabilität, Transaktionskosten, Out-of-Sample-Validierung, Regime-Abhängigkeit, Portfolio-Exposures und Risikokontrolle.
Auch dieses öffentliche Experiment nutzt nur fünf ETFs und 14 dünn verteilte Anchor Dates. Es beweist kein Production Alpha. Es zeigt, dass sich die Validierungskette klein reproduzieren lässt – und dass viele sauber wirkende Faktoraussagen deutlich unsicherer werden, sobald Horizon, Sampling und Rank Redundancy sichtbar gemacht werden.
Diese Unsicherheit möchte ich behalten.
Ein Faktor sollte nicht deshalb ins Modell kommen, weil ein einzelner Research-Bericht eine schöne Zahl zeigt. Besser ist, wenn er längeres Outcome Tracking und mehrere unangenehme Prüfungen überlebt.