σμ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 AcademyParcours
Ctrl K
AO
+1,2 k apprenants
ConnexionRejoindre ↗
Parcours
AO
+1,2 k apprenants
Connexion
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Parcours Python›Module 6 · Backtesting et coûts de trading›Sujet 6.3·~50 minAvancé

Biais d'anticipation

Position dans le parcoursModule 6 sur 11 · Sujet 3 sur 6
6.3 Biais d'anticipation
—Score du lab— / 100XP total—Série—

En savoir trop

Le biais d'anticipation (look-ahead) apparaît quand un backtest utilise une information indisponible au moment de la décision. C'est la raison la plus courante pour qu'une stratégie soit brillante sur le papier et perde de l'argent en réel. Il s'agit rarement d'une seule ligne évidente ; il se cache dans l'alignement (un signal de la clôture d'aujourd'hui négocié à cette clôture), dans des statistiques calculées sur tout l'échantillon, dans des données révisées plus tard, et dans des indices choisis avec le recul. Le remède est une habitude : pour chaque nombre utilisé par une décision, demandez quand il est devenu connu.

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%

L'alignement : le décalage d'un jour

Le remède au biais d'alignement est un décalage : une valeur calculée au jour t ne peut servir qu'à partir du jour t + 1. Dans les tableaux, c'est un décalage avec des zéros au début, out[t] = signal[t - periods]. Le test est brutal. Une série de bruit n'a aucun avantage, donc toute règle qui fait fortune sur du bruit triche. Comparez la version biaisée et la version décalée sur les mêmes données : l'écart est la taille du biais.

# 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

Les statistiques doivent grandir avec le temps

La deuxième fuite est subtile. Standardiser une série avec la moyenne et l'écart-type de tout l'échantillon utilise des nombres qui n'existent qu'à la fin : les premières valeurs sont mises à l'échelle avec de l'information du futur. N'utilisez que le passé : une fenêtre glissante (les n dernières valeurs jusqu'à aujourd'hui) ou croissante (tout jusqu'à aujourd'hui). Le test pour toute variable de ce genre : la calculer sur les k premiers jours seulement doit donner les k premières valeurs du calcul complet.

# 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))
D'autres endroits où se cache le futurDes prix ajustés plus tard pour des fractionnements, des états financiers datés de la fin de période plutôt que de la date de publication, une liste de composants d'indice d'aujourd'hui, un modèle ajusté sur tout l'échantillon puis testé sur une partie, des hyperparamètres réglés sur la période de test. C'est toujours la même erreur : un nombre utilisé avant le moment où il était connu.
  • Le biais d'anticipation est tout usage d'une information avant qu'elle soit connue ; l'alignement en est la forme classique : un signal du jour t se négocie à partir du jour t + 1.
  • Les variables doivent être construites avec le passé seul : fenêtres glissantes ou croissantes, jamais la moyenne ou l'écart-type de tout l'échantillon.
  • Test du préfixe : une variable ou un backtest sur les k premiers jours doit égaler les k premiers résultats de l'exécution complète ; une série de bruit ne doit pas rapporter.

À suivre : les coûts de transaction, la première chose qu'un backtest propre doit payer.

Lab : ce qui est noté

  • Retarder un signal avec lag_signal25 pts
  • Mesurer la triche avec biased_vs_honest25 pts
  • Moyenner uniquement le passé avec trailing_mean25 pts
  • Standardiser sans tricher avec expanding_zscore25 pts
Explorateur · lab-89-1
▾ lab-89-1
…
bash · dans le navigateur
learner@quantcraft:~/lab-89-1$
Python pas encore chargéPythonLn 1, Col 1Spaces: 4UTF-8

Réinitialiser ce sujet ?

Tout le contenu de lab-89-1 revient aux fichiers de départ et le terminal est vidé. Votre solution actuelle sera perdue : cette action est irréversible.

Sera réinitialisé
  • main.py → code de départ
  • Historique du terminal et sortie des tests
  • Modifications non soumises
Sera conservé
  • Meilleur score du lab
  • Votre XP et votre série
  • Progression du cours et notes

Connectez-vous pour exécuter votre code.

Tous les cours sont gratuits à lire. Créez un compte ou connectez-vous pour exécuter du code, lancer les tests et soumettre ce lab.

  • IDE dans le navigateur avec terminal bash — rien à installer
  • Correction instantanée, XP et votre place dans le parcours
Créer un compteSe connecter
OU
Continuer avec GitHub