σμ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 6 · Backtesting und Handelskosten›Thema 6.3·~50 Min.Fortgeschritten

Look-ahead-Bias

Position in der SkillModul 6 von 11 · Thema 3 von 6
6.3 Look-ahead-Bias
—Lab-Punkte— / 100XP gesamt—Serie—

Zu viel wissen

Look-ahead-Bias entsteht, wenn ein Backtest Informationen nutzt, die zum Entscheidungszeitpunkt nicht verfügbar waren. Er ist der häufigste Grund, warum eine Strategie auf dem Papier glänzt und live Geld verliert. Selten ist es eine einzelne offensichtliche Zeile; er versteckt sich in der Ausrichtung (ein Signal vom heutigen Schluss, gehandelt zu diesem Schluss), in über die ganze Stichprobe berechneten Statistiken, in später revidierten Daten und in rückblickend gewählten Indizes. Das Gegenmittel ist eine Gewohnheit: Frage bei jeder Zahl einer Entscheidung, wann sie bekannt wurde.

import numpy as np

rng = np.random.default_rng(5)
returns = rng.normal(0, 0.01, size=250)             # pure noise: no strategy can really make money here

signal = returns.copy()                              # "signal" = the return of the same day
position = np.sign(signal)

biased = np.prod(1 + position * returns) - 1         # trades on the return it is about to earn
honest_position = np.concatenate([[0.0], position[:-1]])
honest = np.prod(1 + honest_position * returns) - 1
print(round(float(biased), 2), round(float(honest), 2))        # 6.0 0.01: a 600% "return" on noise, against 1%

Ausrichtung: die Ein-Tages-Verzögerung

Das Mittel gegen Ausrichtungs-Bias ist eine Verzögerung: Ein an Tag t berechneter Wert darf erst ab Tag t + 1 genutzt werden. In Arrays ist das eine Verschiebung mit Nullen am Anfang, out[t] = signal[t - periods]. Der Test ist grob. Eine Rauschreihe hat keinen Vorteil, also schummelt jede Regel, die mit Rauschen ein Vermögen macht. Vergleichen Sie die verzerrte und die verzögerte Version auf denselben Daten: Der Abstand ist die Größe des Bias.

# lag_signal: delay a series, with zeros where there is nothing to look back at
import numpy as np

def lag_signal(signal, periods=1):
    out = np.zeros(len(signal))
    if periods < len(signal):
        out[periods:] = signal[:len(signal) - periods]
    return out

s = np.array([1.0, -1.0, 1.0, 1.0, -1.0])
print(lag_signal(s))             # [ 0.  1. -1.  1.  1.]
print(lag_signal(s, 2))          # [ 0.  0.  1. -1.  1.]
# trailing_mean: the mean of the last `window` values, using only today and the past
import numpy as np

def trailing_mean(values, window):
    total = np.cumsum(values)
    out = np.full(len(values), np.nan)
    out[window - 1:] = (total[window - 1:] - np.concatenate([[0.0], total[:len(values) - window]])) / window
    return out

x = np.array([1.0, 2.0, 6.0, 7.0])
print(trailing_mean(x, 2))                         # [ nan 1.5 4.  6.5]
print(trailing_mean(x[:3], 2))                     # [ nan 1.5 4. ]: adding day 4 changed nothing before it

Statistiken müssen mit der Zeit wachsen

Das zweite Leck ist subtil. Eine Reihe mit Mittelwert und Standardabweichung der ganzen Stichprobe zu standardisieren, nutzt Zahlen, die erst am Ende existieren: Frühe Werte werden mit Informationen aus der Zukunft skaliert. Nehmen Sie nur die Vergangenheit: ein gleitendes Fenster (die letzten n Werte bis heute) oder ein wachsendes (alles bis heute). Der Test für jedes solche Merkmal: Nur über die ersten k Tage berechnet, muss es den ersten k Werten der vollen Rechnung entsprechen.

# Whole-sample z-scores leak the future; expanding ones do not
import numpy as np

x = np.array([1.0, 3.0, 2.0, 8.0, 4.0])
whole = (x - x.mean()) / x.std()

n = np.arange(1, len(x) + 1)
mean = np.cumsum(x) / n
var = np.cumsum(x ** 2) / n - mean ** 2
with np.errstate(divide="ignore", invalid="ignore"):
    expanding = (x - mean) / np.sqrt(var)
expanding[0] = np.nan

print(np.round(whole, 3))
print(np.round(expanding, 3))
# Prefix test: computing on x[:3] gives the first three expanding values, but not the first three whole-sample ones
print(np.round((x[:3] - x[:3].mean()) / x[:3].std(), 3))
Weitere Orte, an denen sich die Zukunft verstecktSpäter um Splits bereinigte Kurse, Jahresabschlüsse mit dem Periodenende statt dem Veröffentlichungsdatum, eine Indexmitgliederliste von heute, ein auf die ganze Stichprobe angepasstes und dann auf einem Teil getestetes Modell, auf dem Testzeitraum abgestimmte Hyperparameter. Jedes Mal derselbe Fehler: eine Zahl, die vor dem Zeitpunkt genutzt wurde, an dem sie bekannt war.
  • Look-ahead-Bias ist jede Nutzung von Information vor ihrer Bekanntheit; die Ausrichtung ist die klassische Form: Ein Signal von Tag t wird ab Tag t + 1 gehandelt.
  • Merkmale dürfen nur aus der Vergangenheit gebaut werden: gleitende oder wachsende Fenster, nie Mittelwert oder Standardabweichung der ganzen Stichprobe.
  • Präfixtest: Ein Merkmal oder Backtest über die ersten k Tage muss den ersten k Ergebnissen des vollen Laufs entsprechen; eine Rauschreihe darf kein Geld verdienen.

Als Nächstes: Transaktionskosten, das Erste, wofür ein sauberer Backtest zahlen muss.

Lab: Was bewertet wird

  • Ein Signal mit lag_signal verzögern25 Pkt.
  • Mit biased_vs_honest den Schwindel messen25 Pkt.
  • Mit trailing_mean nur die Vergangenheit mitteln25 Pkt.
  • Mit expanding_zscore ohne Spähen standardisieren25 Pkt.
Explorer · lab-89-1
▾ lab-89-1
…
bash · im Browser
learner@quantcraft:~/lab-89-1$
Python noch nicht geladenPythonLn 1, Col 1Spaces: 4UTF-8

Dieses Thema zurücksetzen?

Alles in lab-89-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