⚠ Trading ist mit einem erheblichen Verlustrisiko verbunden. Alle Inhalte dieser Website dienen ausschließlich der Bildung und stellen keine Finanzberatung dar.
Lektion 8 von 13 · Kostenlos

Ein ereignisgesteuerter Backtest: eine Schleifen-Engine mit ATR-Stops, Positionsgrößenbestimmung und einer Trade-Liste in R-Multiples

Bauen Sie in Python eine Backtest-Schleife Balken für Balken, die Positionen nach ATR-Risiko bemisst, Stops einschließlich Kurslücken berücksichtigt und jeden Trade in R-Multiples erfasst.

Autor: Moustafa HijaziInhalt geprüft: 2026-10-306 Min. LesezeitSelbstlernkurs

Was Sie lernen werden

  • Warum ein vektorisierter Backtest Stops, Teilausführungen oder risikobasierte Positionsgrößen nicht modellieren kann und wann eine Schleifen-Engine nötig ist.
  • Den Zustand, den eine ereignisgesteuerte Engine von Balken zu Balken mitführen muss: Cash, Position, Einstiegskurs, Stop-Niveau und offenen Trade.
  • Wie Sie jede Position aus Kontorisiko und ATR bemessen und das Ergebnis als R-Multiple erfassen.
  • Wie Kurslücken über einen Stop hinweg im Code ehrlich behandelt werden.

Erklärung

Der vektorisierte Backtest aus Lektion 7 berechnet die Position jedes Balkens auf einmal aus der gesamten Reihe. Das ist schnell, kann aber keine Regeln ausdrücken, die vom Pfad abhängen: Ein Stop 2 ATR unter dem Einstieg hängt davon ab, wo Sie eingestiegen sind, und die Positionsgröße hängt vom Kontowert in diesem Moment ab. Ein ereignisgesteuerter Backtest durchläuft die Balken einzeln, nutzt nur die bis zu diesem Balken verfügbaren Informationen und aktualisiert seinen Zustand genau so, wie es ein Trader tun würde.

Der Zustand und die Reihenfolge der Ereignisse

Die Engine führt eine kleine Menge von Variablen: cash, position (Anzahl der Aktien, 0 wenn flat), entry, stop, risk_per_share und eine Liste trades. Auf jedem Balken ist die Reihenfolge der Prüfungen wichtig; sie muss einmal festgelegt und dann beibehalten werden:

  1. Zuerst die Ausstiege. Ist eine Position offen und liegt das heutige Tief auf oder unter dem Stop, wird ausgestiegen. Der Ausführungskurs ist der Stop, es sei denn, die Eröffnung liegt bereits unter dem Stop (eine Kurslücke); dann ist die Eröffnung der Ausführungskurs. Genau hier kann aus einem geplanten Verlust von 1R ein Verlust von 1.4R oder mehr werden.
  2. Trailing-Stops aktualisieren anhand des heutigen Schlusskurses und der ATR.
  3. Zuletzt die Einstiege, und zwar nur zur Eröffnung des nächsten Balkens, denn ein aus dem heutigen Schlusskurs berechnetes Signal kann nicht zum heutigen Schlusskurs gehandelt werden. Im Code bedeutet das: Ein Signal auf Balken t setzt ein Flag, und die Ausführung erfolgt zu open[t+1].

Positionsgrößenbestimmung in der Schleife

Die risikobasierte Positionsgrößenbestimmung wurde in „Risiko- und Trade-Management“ (Lektion 2) und „Systematisches Trading“ (Lektion 9) behandelt. Im Code ist sie eine Zeile: size = floor(equity * risk_pct / (entry - stop)), wobei der Nenner das Risiko pro Aktie ist (hier 2 * ATR). Fügen Sie eine Obergrenze wie size * entry <= 0.25 * equity hinzu, damit eine winzige ATR keine Position erzeugen kann, die das Konto nicht tragen kann.

Trades in R erfassen

Jeder geschlossene Trade wird als Dictionary mit Datumsangaben, Kursen, Größe, Gewinn in Euro und dem R-Multiple = Gewinn / (Größe × Risiko pro Aktie) angehängt. pd.DataFrame(trades) macht aus der Liste eine Trade-Tabelle für die Kennzahlenfunktionen aus Lektion 9 und die Erwartungswertberechnung aus „Risiko- und Trade-Management“ (Lektion 1).

Eine minimale Engine

  • def run(df, risk_pct=0.01, atr_mult=2.0, start_cash=20_000):
  • cash, pos, entry, stop, rps = start_cash, 0, None, None, None
  • trades, equity, pending = [], [], False
  • for i in range(1, len(df)):
  • o, h, l, cl, atr = df.open[i], df.high[i], df.low[i], df.close[i], df.atr[i]
  • if pos > 0 and l <= stop: # Ausstieg: Stop oder Kurslücke
  • fill = min(o, stop)
  • pnl = pos * (fill - entry)
  • trades.append(dict(exit=df.index[i], pnl=pnl, R=pnl / (pos * rps)))
  • cash += pos * fill; pos = 0
  • if pending and pos == 0: # Einstieg zur Eröffnung dieses Balkens
  • entry, rps = o, atr_mult * df.atr[i-1]
  • pos = int(cash * risk_pct // rps)
  • stop = entry - rps; cash -= pos * entry; pending = False
  • if pos == 0 and df.signal[i]:
  • pending = True # Handel zur nächsten Eröffnung
  • equity.append(cash + pos * cl)
  • return pd.DataFrame(trades), pd.Series(equity, index=df.index[1:])

Zwei Details: Die für die Positionsgröße verwendete ATR ist df.atr[i-1], der am Abend vor dem Einstieg bekannte Wert, und der Stop wird gegen das Tief geprüft, nicht gegen den Schlusskurs. Ein Trailing-Stop ist eine zusätzliche Zeile, stop = max(stop, cl - rps), nach dem Ausstiegsblock. Kosten können wie in Lektion 7 von pnl abgezogen werden; der Übersichtlichkeit halber werden sie hier weggelassen.

Durchgerechnetes Beispiel

Hypothetisches Beispiel: Anfangs-Equity €20,000, Risiko 1 % pro Trade (€200), Stop bei 2 × ATR. Kurse und ATR sind erfundene runde Zahlen; Gebühren, Spread und Steuern sind nicht berücksichtigt.

  1. Trade 1. Signal am Montag; Eröffnung am Dienstag €50.00, vorherige ATR €2.50. Risiko pro Aktie = 2 × 2.50 = €5.00; Stop = €45.00. Größe = floor(200 / 5) = 40 Aktien (€2,000 Exposure, 10 % der Equity, innerhalb einer Obergrenze von 25 %). Späterer Ausstieg auf ein Signal bei €58.00: Gewinn = 40 × 8 = €320 = 320 / 200 = +1.6R.
  2. Trade 2. Gleiches Setup, Einstieg €50.00, Stop €45.00, 40 Aktien. Nachrichten über Nacht: Die Aktie eröffnet bei €43.00. Die Engine führt zu min(Eröffnung, Stop) = €43.00 aus. Verlust = 40 × 7 = €280 = −1.4R, nicht die geplanten −1.0R. Ein vektorisierter Test hätte −1.0R oder gar nichts gezeigt.
  3. Trade 3. Einstieg €50.00, Trailing-Stop; der Stop ist auf €47.50 gestiegen, als das Tief ihn berührt. Verlust = 40 × 2.50 = €100 = −0.5R.
  4. Trade-Liste. R-Multiples +1.6, −1.4, −0.5: Summe −0.3R, Durchschnitt −0.1R. Drei Trades beweisen nichts, aber die Tabelle hat die richtige Form für Hunderte.

Ein zweites Instrument bei €120 mit einer ATR von €6 ergibt eine Größe von floor(200 / 12) = 16 Aktien: dasselbe Risiko von €200, aber weniger Aktien, weil die Volatilität höher ist.

Häufiger Fehler

Zum Stop-Kurs ausführen, obwohl der Balken mit einer Lücke darüber hinweggesprungen istCode wie if l <= stop: fill = stop unterstellt stillschweigend, dass der Markt zu Ihrem Stop gehandelt hat. Bei einer Kurslücke war das nicht der Fall. Verwenden Sie für Long-Ausstiege immer min(open, stop) (und max(open, stop) für Shorts). Dieselbe Logik gilt für Einstiege mit einer Stop-Order: Ein Buy-Stop bei €52 wird zur Eröffnung ausgeführt, wenn die Eröffnung bei €54 liegt.

Übungsaufgabe

Verfolgen Sie die Engine auf Papier für einen Trade: Equity €15,000, Risiko 1.5 %, ATR am Abend vor dem Einstieg €1.80, Stop-Multiplikator 2.5, Einstieg zur nächsten Eröffnung bei €36.00. (a) Berechnen Sie Risiko pro Aktie, Stop-Niveau und Positionsgröße (abgerundet). (b) Der Ausstiegstag eröffnet bei €37.00, fällt bis auf ein Tief von €31.00 und schließt bei €32.00. Wie lauten Ausführungskurs, Gewinn in Euro und R-Multiple? (c) Würde sich das Ergebnis unterscheiden, wenn Sie den Stop gegen den Schlusskurs statt gegen das Tief prüfen würden?

Lösung anzeigen

(a) Risiko pro Trade = 0.015 × 15,000 = €225. Risiko pro Aktie = 2.5 × 1.80 = €4.50. Stop = 36.00 − 4.50 = €31.50. Größe = floor(225 / 4.50) = 50 Aktien.

(b) Die Eröffnung von €37.00 liegt über dem Stop, also keine Kurslücke; das Tief von €31.00 liegt auf oder unter €31.50, der Ausführungskurs ist also min(37.00, 31.50) = €31.50. Verlust = 50 × (31.50 − 36.00) = −€225 = −1.0R.

(c) Eine Prüfung gegen den Schlusskurs (€32.00) würde hier ebenfalls den Ausstieg auslösen, aber bei €32.00 statt €31.50, sodass der Verlust €200 (−0.89R) betrüge und besser aussähe als die Realität. Bei anderen Balken kann das Tief den Stop treffen, während sich der Schlusskurs darüber erholt; eine schlusskursbasierte Prüfung würde dann einen echten Ausstieg komplett übersehen.

Einschränkungen: Die Reihenfolge von Hoch und Tief innerhalb des Tages ist aus Tagesbalken nicht bekannt; liegen Stop und Kursziel beide innerhalb eines Balkens, nehmen Sie das schlechtere Ergebnis an.

Kernaussagen

  • Verwenden Sie eine Schleifen-Engine, wenn Regeln vom Pfad abhängen: Stops, Trailing-Stops, risikobasierte Positionsgrößen und Teilausstiege.
  • Legen Sie die Reihenfolge der Ereignisse pro Balken fest: Ausstiege, Stop-Aktualisierungen, dann Einstiege zur nächsten Eröffnung.
  • Bemessen Sie die Größe als Equity × Risiko % geteilt durch das Risiko pro Aktie, mit einer Exposure-Obergrenze; speichern Sie jeden Trade mit seinem R-Multiple.
  • Führen Sie Ausstiege bei Kurslücken zur Eröffnung aus, nicht zum Stop; genau daher kommen Verluste von mehr als 1R.

Nächster Schritt

Lektion 9 macht aus der Equity-Kurve und der Trade-Liste, die Sie jetzt erzeugen, wiederverwendbare, getestete Performance-Funktionen: CAGR, Drawdown, Sharpe, Sortino und Profit-Faktor.

Selbsttest

Beantworten Sie die Fragen und prüfen Sie dann Ihre Antworten. Sie können es so oft wiederholen, wie Sie möchten.

1. Equity €20,000, Risiko 1 % pro Trade, Stop bei 2 × ATR mit ATR = €2.50, Einstieg bei €50. Am Ausstiegstag eröffnet die Aktie bei €43, und der Stop liegt bei €45. Welchen Verlust erfasst eine ehrliche Engine?

2. Warum verwendet die Engine df.atr[i-1] statt df.atr[i], wenn sie einen Einstieg zur Eröffnung von Balken i bemisst?

3. Welches Regelwerk erfordert tatsächlich eine ereignisgesteuerte Schleife statt des vektorisierten Ansatzes aus Lektion 7?

Automatischer Selbsttest zur eigenen Wiederholung. Ihre Antworten werden in Ihrem Browser geprüft und von uns weder eingesehen noch bewertet. Es ist keine Prüfung und kein Abschluss.

Ihr Fortschritt

„Erledigt“ bedeutet nur, dass Sie die Lektion als abgeschlossen markiert haben. Es ist kein Test Ihres Verständnisses. Fortschritt und Notizen werden nur in diesem Browser auf diesem Gerät gespeichert — nicht in einem Konto.