σμ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.1·~1 Std.Fortgeschritten

Ein vektorisierter Backtester

Position in der SkillModul 6 von 11 · Thema 1 von 6
6.1 Ein vektorisierter Backtester
—Lab-Punkte— / 100XP gesamt—Serie—

Die Geschichte nachspielen

Ein Backtest fragt, was eine Handelsregel mit vergangenen Daten verdient hätte. Die vektorisierte Version arbeitet mit ganzen Arrays: keine Schleife über die Tage, nur ein Signal pro Tag, eine Position pro Tag und eine Multiplikation mit den Renditen. Sie ist kurz, schnell und leicht zu prüfen, deshalb das erste Werkzeug, um Ideen zu sichten. Ihre Grenzen (keine Orders, kein Bargeld, keine Teilausführungen) sind genau das, was das nächste Thema hinzufügt.

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)

Die Verzögerung um einen Tag

Ein aus dem heutigen Schlusskurs berechnetes Signal lässt sich erst nach diesem Schluss handeln, verdient also die Rendite von morgen, nicht die von heute. In Arrays ist das eine Verschiebung: strategy[t] = position[t - 1] * returns[t], ohne Position vor dem ersten Tag. Vergisst man sie, handelt die Strategie auf eine Rendite, die sie schon kennt, und der Backtest sieht wunderbar aus. Die Verschiebung ist eine Zeile, und die wichtigste der ganzen Funktion.

# 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

Equity und Kennzahlen

Tägliche Renditen werden aufgezinst: Der Wert nach jedem Tag ist der Anfangswert mal das laufende Produkt der 1 + Rendite. Aus dieser Equity-Kurve fassen drei Zahlen einen Lauf zusammen. Die Gesamtrendite ist der Endwert minus 1. Die Sharpe-Ratio ist die mittlere Tagesrendite durch ihre Standardabweichung, mal sqrt(periods) zur Annualisierung. Der maximale Drawdown ist der schlimmste Rückgang von einem früheren Hoch, equity / running max - 1, der Verlust, den ein Anleger durchlebt hätte.

# 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))
Ein vektorisierter Backtest ist ein Filter, kein UrteilEr nimmt an, dass man die ganze Position zum Schlusskurs, zum gesehenen Preis und gratis handelt. Echter Handel hat Kosten, Slippage und Grenzen, die die folgenden Themen behandeln, und viele Regeln auf derselben Historie zu probieren findet zufällig eine, die gut aussieht. Ein vielversprechendes Ergebnis ist ein Grund weiterzutesten, nicht zu handeln.
  • Position = Vorzeichen des Signals; die Strategierendite ist position[t - 1] * returns[t]: Die heutige Rendite gehört zur gestrigen Entscheidung.
  • Die Equity ist start * cumprod(1 + r); Gesamtrendite, annualisierte Sharpe-Ratio und maximaler Drawdown fassen sie zusammen.
  • Vektorisierte Backtests sind schnelle Filter: Sie ignorieren Kosten und Ausführung, und viele Versuche auf einer Historie erzeugen Glück.

Als Nächstes: ein ereignisgesteuerter Backtester, die Historie Bar für Bar mit Bargeld, Orders und Ausführungen nachspielen.

Lab: Was bewertet wird

  • Mit signal_to_position ein Signal in eine Position umsetzen25 Pkt.
  • Mit strategy_returns die Position von gestern verdienen lassen25 Pkt.
  • Mit equity_curve die Tage aufzinsen25 Pkt.
  • Mit backtest_stats den Lauf zusammenfassen25 Pkt.
Explorer · lab-87-1
▾ lab-87-1
…
bash · im Browser
learner@quantcraft:~/lab-87-1$
Python noch nicht geladenPythonLn 1, Col 1Spaces: 4UTF-8

Dieses Thema zurücksetzen?

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