σμ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.1·~1 hAvancé

Un backtester vectorisé

Position dans le parcoursModule 6 sur 11 · Sujet 1 sur 6
6.1 Un backtester vectorisé
—Score du lab— / 100XP total—Série—

Rejouer l'histoire

Un backtest demande ce qu'une règle de trading aurait rapporté sur des données passées. La version vectorisée travaille sur des tableaux entiers : pas de boucle sur les jours, juste un signal par jour, une position par jour et une multiplication avec les rendements. Elle est courte, rapide et facile à vérifier, ce qui en fait le premier outil pour trier des idées. Ses limites (ni ordres, ni liquidités, ni exécutions partielles) sont exactement ce qu'ajoute le sujet suivant.

import numpy as np

returns = np.array([0.01, 0.02, -0.01, 0.03])           # the asset's daily returns
signal = np.array([2.5, 0.4, -1.0, 0.0])                # any score: positive = buy, negative = sell

position = np.sign(signal)                               # +1, +1, -1, 0
print(position)

Le décalage d'un jour

Un signal calculé avec la clôture d'aujourd'hui ne peut être négocié qu'après cette clôture : il gagne le rendement de demain, pas celui d'aujourd'hui. Dans les tableaux, c'est un décalage : strategy[t] = position[t - 1] * returns[t], sans position avant le premier jour. L'oublier permet à la stratégie de négocier sur un rendement qu'elle connaît déjà, et le backtest paraît magnifique. Le décalage tient en une ligne, la plus importante de toute la fonction.

# Yesterday's position earns today's return
import numpy as np

returns = np.array([0.01, 0.02, -0.01, 0.03])
position = np.array([1.0, 1.0, -1.0, 0.0])

strategy = np.zeros(len(returns))
strategy[1:] = position[:-1] * returns[1:]
print(strategy)                                    # [ 0.    0.02 -0.01 -0.03]
print(position * returns)                          # the same-day version: [ 0.01  0.02  0.01  0. ] looks better, and is cheating

Capital et statistiques

Les rendements quotidiens se composent : la valeur après chaque jour est le capital initial multiplié par le produit cumulé des 1 + rendement. De cette courbe de capital, trois nombres résument un essai. Le rendement total est la valeur finale moins 1. Le ratio de Sharpe est le rendement quotidien moyen divisé par son écart-type, multiplié par sqrt(periods) pour l'annualiser. Le drawdown maximal est la pire chute depuis un sommet précédent, equity / running max - 1, c'est-à-dire la perte qu'un investisseur aurait vécue.

# Equity curve and the three statistics for a short run
import numpy as np

strategy = np.array([0.0, 0.02, -0.01, -0.03, 0.04])
equity = np.cumprod(1 + strategy)
print(np.round(equity, 5))
print(round(float(equity[-1] - 1), 5))                                        # total return
print(round(float(strategy.mean() / strategy.std(ddof=1) * np.sqrt(252)), 3))   # annualised Sharpe
print(round(float((equity / np.maximum.accumulate(equity) - 1).min()), 5))      # maximum drawdown
# A moving-average rule on a simulated price path, with its statistics
import numpy as np

rng = np.random.default_rng(7)
returns = rng.normal(0.0004, 0.01, size=500)
prices = 100 * np.cumprod(1 + returns)

fast = np.convolve(prices, np.ones(10) / 10, mode="valid")[-(len(prices) - 49):]      # 10-day mean, aligned
slow = np.convolve(prices, np.ones(50) / 50, mode="valid")                            # 50-day mean
position = np.zeros(len(prices))
position[49:] = np.sign(fast - slow)

strategy = np.zeros(len(returns))
strategy[1:] = position[:-1] * returns[1:]
equity = np.cumprod(1 + strategy)
drawdown = equity / np.maximum.accumulate(equity) - 1
print(round(float(equity[-1] - 1), 4), round(float(strategy.mean() / strategy.std(ddof=1) * np.sqrt(252)), 4), round(float(drawdown.min()), 4))
Un backtest vectorisé est un filtre, pas un verdictIl suppose que l'on négocie toute la position à la clôture, au prix vu, gratuitement. Le trading réel a des coûts, du glissement et des limites, vus dans les sujets suivants, et essayer beaucoup de règles sur le même historique en trouvera une qui paraît bonne par hasard. Un résultat prometteur est une raison de tester davantage, pas de trader.
  • Position = signe du signal ; le rendement de la stratégie est position[t - 1] * returns[t] : le rendement d'aujourd'hui revient à la décision d'hier.
  • Le capital est start * cumprod(1 + r) ; rendement total, Sharpe annualisé et drawdown maximal le résument.
  • Les backtests vectorisés sont des filtres rapides : ils ignorent coûts et exécutions, et de nombreux essais sur un même historique produisent de la chance.

À suivre : un backtester événementiel, rejouer l'histoire barre par barre avec liquidités, ordres et exécutions.

Lab : ce qui est noté

  • Transformer un signal en position avec signal_to_position25 pts
  • Gagner la position d'hier avec strategy_returns25 pts
  • Composer les jours avec equity_curve25 pts
  • Résumer l'essai avec backtest_stats25 pts
Explorateur · lab-87-1
▾ lab-87-1
…
bash · dans le navigateur
learner@quantcraft:~/lab-87-1$
Python pas encore chargéPythonLn 1, Col 1Spaces: 4UTF-8

Réinitialiser ce sujet ?

Tout le contenu de lab-87-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