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

Ein ereignisgesteuerter Backtester

Position in der SkillModul 6 von 11 · Thema 2 von 6
6.2 Ein ereignisgesteuerter Backtester
—Lab-Punkte— / 100XP gesamt—Serie—

Eine Bar nach der anderen

Ein ereignisgesteuerter Backtester verhält sich wie ein Live-System: Die Zeit schreitet Bar für Bar voran, und bei jeder Bar sieht das Programm nur die Vergangenheit, entscheidet, sendet eine Order und aktualisiert sein Konto. Er ist langsamer als ein vektorisierter, trägt aber Zustand (Bargeld, Aktien), kann Orders und Ausführungen modellieren, und derselbe Strategiecode kann später auf einem Live-Feed laufen. Die Schleife ist der Kern: ein for über die Bars, das Konto wandert von Durchlauf zu Durchlauf.

def crossover_signal(history, fast, slow):
    if len(history) < slow:
        return 0                                           # not enough history yet
    fast_mean = sum(history[-fast:]) / fast
    slow_mean = sum(history[-slow:]) / slow
    return 1 if fast_mean > slow_mean else 0

prices = [100, 101, 103, 102, 105, 107, 104, 103, 99, 98]
print(crossover_signal(prices[:3], 2, 4))                  # 0: only 3 prices
print(crossover_signal(prices[:6], 2, 4))                  # 1: the 2-bar mean is above the 4-bar mean
print(crossover_signal(prices[:9], 2, 4))                  # 0: the trend has turned

Orders verändern das Konto

Eine Order macht aus Bargeld Aktien oder umgekehrt. Bei ganzen Aktien lässt der Kauf von int(cash // price) etwas Bargeld übrig, und der Kontowert ist immer cash + shares * price. Die Orderlogik in einer kleinen Funktion execute_order zu halten, verkürzt die Schleife und erlaubt es, Gebühren und Slippage später an einer Stelle zu ergänzen. Alles zu verkaufen ist einfach cash + shares * price und null Aktien.

def execute_order(cash, shares, price, target):
    if target == 1:
        bought = int(cash // price)                        # whole shares only
        return cash - bought * price, shares + bought
    return cash + shares * price, 0

cash, shares = execute_order(1000.0, 0, 103.0, 1)
print(cash, shares, cash + shares * 103.0)                 # 73.0 9 1000.0
cash, shares = execute_order(cash, shares, 99.0, 0)
print(cash, shares)                                        # 964.0 0

Die Schleife und was sie wissen darf

Bei Bar t darf die Strategie prices[:t] nutzen, das beim vorherigen Schluss endet, und handelt zu prices[t]. Das Slicing prices[:t] hält die Zukunft draußen: Der nächste Kurs steht schlicht nicht in der Liste. Daraus folgt ein guter Test für eine Ereignis-Engine: Läuft sie nur über die ersten k Bars, müssen die Ergebnisse den ersten k Ergebnissen des vollen Laufs entsprechen. Weichen sie ab, hat die Strategie gespäht.

def crossover_signal(history, fast, slow):
    if len(history) < slow:
        return 0
    return 1 if sum(history[-fast:]) / fast > sum(history[-slow:]) / slow else 0

def execute_order(cash, shares, price, target):
    if target == 1:
        bought = int(cash // price)
        return cash - bought * price, shares + bought
    return cash + shares * price, 0

def run_backtest(prices, fast, slow, cash):
    shares = 0
    values = []
    for t, price in enumerate(prices):
        if t > 0:
            target = crossover_signal(prices[:t], fast, slow)       # only what was known at the last close
            cash, shares = execute_order(cash, shares, price, target)
        values.append(cash + shares * price)
    return values

prices = [100, 101, 103, 102, 105, 107, 104, 103, 99, 98]
full = run_backtest(prices, 2, 4, 1000.0)
print([round(v, 2) for v in full])
print(run_backtest(prices[:6], 2, 4, 1000.0) == full[:6])             # True: the future never leaks
# The benchmark: buy once at the start and hold
import random

def buy_and_hold(prices, cash):
    shares = int(cash // prices[0])
    left = cash - shares * prices[0]
    return [left + shares * price for price in prices]

random.seed(3)
prices = [100.0]
for _ in range(99):
    prices.append(round(prices[-1] * (1 + random.gauss(0.0004, 0.012)), 2))
held = buy_and_hold(prices, 10000.0)
print(round(held[0], 2), round(held[-1], 2))
Derselbe Code kann live gehenDie Schleife sieht eine Bar und handelt: genau die Form eines Live-Handelsprogramms, bei dem die nächste Bar aus einem Datenfeed statt aus einer Liste kommt. Signal-, Order- und Kontologik in getrennten Funktionen zu halten, erlaubt es, den historischen Feed gegen einen Live-Feed zu tauschen, ohne die Strategie umzuschreiben.
  • Ein ereignisgesteuerter Backtester läuft mit Zustand (Bargeld, Aktien) über die Bars; bei Bar t darf er nur prices[:t] nutzen.
  • Orders bewegen zwischen Bargeld und ganzen Aktien: Der Kontowert ist cash + shares * price; halten Sie die Orderlogik in einer Funktion.
  • Prüfen Sie auf Look-ahead: Ein Lauf über die ersten k Bars muss den ersten k Ergebnissen des vollen Laufs gleichen; vergleichen Sie mit Buy-and-Hold.

Als Nächstes: Look-ahead-Bias, der stille Fehler, der einen Backtest zu gut macht, um wahr zu sein.

Lab: Was bewertet wird

  • Mit crossover_signal zwei Durchschnitte vergleichen25 Pkt.
  • Mit execute_order die Order ausführen25 Pkt.
  • Mit run_backtest Bar für Bar durchlaufen25 Pkt.
  • Mit buy_and_hold die Vergleichsgröße setzen25 Pkt.
Explorer · lab-88-1
▾ lab-88-1
…
bash · im Browser
learner@quantcraft:~/lab-88-1$
Python noch nicht geladenPythonLn 1, Col 1Spaces: 4UTF-8

Dieses Thema zurücksetzen?

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