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

Performance-Kennzahlen als wiederverwendbare Funktionen: CAGR, Drawdown, Sharpe, Sortino, Profit-Faktor und ihre Unit-Tests

Schreiben Sie ein kleines Kennzahlenmodul mit korrekter Annualisierung, ergänzen Sie rollierende Versionen und belegen Sie die Funktionen mit pytest anhand von Werten, die Sie von Hand berechnet haben.

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

Was Sie lernen werden

  • Wie Sie jede Kennzahl als reine Funktion schreiben, die ein pandas-Objekt entgegennimmt und eine Zahl zurückgibt.
  • Wo die Annualisierung ins Spiel kommt: die Wurzel-aus-der-Zeit-Regel für die Volatilität und die Konstante für Perioden pro Jahr.
  • Wie rollierende Versionen zeigen, wann eine Strategie funktioniert hat und wann nicht.
  • Wie Sie eine Kennzahl per Unit-Test gegen einen von Hand berechneten Wert prüfen, damit ein späteres Refactoring sie nicht unbemerkt beschädigen kann.

Erklärung

Die Definitionen von CAGR, maximalem Drawdown, Sharpe Ratio und Profit-Faktor wurden in „Systematisches Trading“ (Lektion 5) und anhand eines Trading-Journals in „Risiko- und Trade-Management“ (Lektion 9) behandelt. In dieser Lektion geht es um die Implementierung: Sie bringen sie in einer Datei, metrics.py, unter, sodass jedes Notebook denselben getesteten Code importiert, statt Formeln neu abzutippen.

Designregeln für das Modul

  • Jede Funktion nimmt entweder eine Equity-Reihe (Euro oder Indexstand, DatetimeIndex) oder eine Renditereihe (periodische einfache Renditen) entgegen und gibt in ihrem Namen oder Docstring an, welche. Beides zu vermischen ist die häufigste Quelle falscher Zahlen.
  • Ein Argument periods (252 für Tagesbalken, 52 wöchentlich, 12 monatlich) übernimmt die Annualisierung. Schreiben Sie 252 niemals fest in die Formel.
  • Funktionen geben einfache Floats zurück und drucken oder plotten nie.

Die Funktionen

  • import numpy as np, pandas as pd
  • def cagr(equity, periods=252):
  • years = (len(equity) - 1) / periods
  • return (equity.iloc[-1] / equity.iloc[0]) ** (1 / years) - 1
  • def max_drawdown(equity):
  • dd = equity / equity.cummax() - 1
  • return dd.min() # negative Zahl, z. B. -0.25
  • def sharpe(returns, rf=0.0, periods=252):
  • ex = returns - rf / periods
  • return ex.mean() / ex.std(ddof=1) * np.sqrt(periods)
  • def sortino(returns, periods=252):
  • down = np.minimum(returns, 0)
  • dsd = np.sqrt((down ** 2).mean()) # Downside-Abweichung gegenüber 0
  • return returns.mean() / dsd * np.sqrt(periods)
  • def profit_factor(pnl):
  • pnl = pd.Series(pnl)
  • return pnl[pnl > 0].sum() / -pnl[pnl < 0].sum()

Drei Implementierungsdetails sind wichtig. Erstens liefert cummax() das laufende Hoch, sodass der Drawdown eine Zeile ist. Zweitens ist std(ddof=1) die Stichproben-Standardabweichung; pandas verwendet sie standardmäßig, NumPy verwendet ddof=0, und beide unterscheiden sich bei kurzen Reihen leicht, genau die Art von Inkonsistenz, die ein Test aufdeckt. Drittens quadriert die Sortino-Downside-Abweichung hier alle Perioden, wobei Verluste gezählt und Gewinne auf null gesetzt werden, und teilt durch die Gesamtanzahl; manche Quellen teilen nur durch die Anzahl der Verlustperioden und erhalten eine größere Zahl. Entscheiden Sie sich für eine Variante, dokumentieren Sie sie und testen Sie sie.

Rollierende Versionen

Eine einzelne Sharpe Ratio über zehn Jahre verbirgt eine Strategie, die alles im Jahr 2020 verdient hat. returns.rolling(252).apply(sharpe) erzeugt eine rollierende Ein-Jahres-Sharpe; (equity / equity.cummax() - 1) ist die vollständige Drawdown-Reihe zum Plotten in Lektion 10. Rollierende Kennzahlen sind mit apply langsam; berechnen Sie bei großen Datenmengen direkt rolling(252).mean() / rolling(252).std() * np.sqrt(252).

Unit-Tests mit pytest

Ein Unit-Test ist eine kleine Funktion, die Ihren Code mit Eingaben aufruft, deren richtige Antwort Sie kennen, und das Ergebnis per Assertion prüft. Legen Sie die Tests in test_metrics.py ab und führen Sie pytest im Terminal aus. Verwenden Sie pytest.approx, weil sich Gleitkommaergebnisse in den letzten Stellen unterscheiden.

  • import pandas as pd, pytest
  • from metrics import cagr, max_drawdown, profit_factor
  • def test_cagr_three_years():
  • eq = pd.Series([10_000.0] + [0]*(3*252 - 1) + [14_000.0])
  • assert cagr(eq) == pytest.approx(0.1187, abs=1e-4)
  • def test_max_drawdown_known_path():
  • eq = pd.Series([100, 120, 90, 130, 110.0])
  • assert max_drawdown(eq) == pytest.approx(-0.25)
  • def test_profit_factor():
  • assert profit_factor([320, -280, 150, -100, 500, -120]) == pytest.approx(1.94)

Ergänzen Sie Randfälle: Eine Equity-Reihe ohne Drawdown sollte 0.0 zurückgeben; eine Renditereihe nur aus Nullen sollte für Sharpe einen Fehler auslösen oder nan zurückgeben, statt mit einem Divisionsfehler abzustürzen. Legen Sie das Verhalten fest, schreiben Sie es in den Docstring und testen Sie es.

Durchgerechnetes Beispiel

Hypothetisches Beispiel, alle Zahlen erfunden; Gebühren und Steuern nicht berücksichtigt. Die Equity einer Strategie wächst über genau 3 Jahre aus Tagesbalken von €10,000 auf €14,000.

  1. CAGR = (14,000 / 10,000)^(1/3) − 1 = 1.4^0.3333 − 1 = 11.87 % pro Jahr. (Nicht 40 % / 3 = 13.3 %, was den Zinseszinseffekt ignoriert.)
  2. Maximaler Drawdown auf dem Equity-Pfad 100, 120, 90, 130, 110: Die laufenden Hochs sind 100, 120, 120, 130, 130; die Drawdowns sind 0, 0, −25 %, 0, −15.4 %. Maximaler Drawdown = −25 %.
  3. Sharpe aus Tagesrenditen mit Mittelwert 0.04 % und Stichproben-Standardabweichung 1.0 %, risikofreier Zins 0: annualisierte Rendite = 0.0004 × 252 = 10.08 %; annualisierte Volatilität = 0.01 × √252 = 15.87 %; Sharpe = 0.1008 / 0.1587 = 0.63. Dieselbe Zahl ergibt sich aus 0.0004 / 0.01 × √252.
  4. Sortino auf sechs Renditen +2 %, −1 %, +1.5 %, −3 %, +1 %, +0.5 %: Mittelwert = 0.1667 %; die Downside-Werte sind 0, −1 %, 0, −3 %, 0, 0; quadrierter Mittelwert = (0.0001 + 0.0009) / 6 = 0.0001667; Downside-Abweichung = 1.291 %; Sortino pro Periode = 0.1667 / 1.291 = 0.129, gegenüber einer Sharpe pro Periode von 0.1667 / 1.862 = 0.090. Sortino ist höher, weil die Ausschläge nach oben nicht als Risiko gezählt werden.
  5. Profit-Faktor aus Trade-Gewinnen von €320, −€280, €150, −€100, €500, −€120: Bruttogewinn €970, Bruttoverlust €500, Profit-Faktor = 1.94.

Jede dieser Zahlen wird zu einer Assertion in der Testdatei. Ändert jemand später ddof oder die Annualisierungskonstante, schlagen die Tests innerhalb einer Sekunde fehl.

Häufiger Fehler

Falsch herum annualisierenRenditen skalieren mit der Zeit (den Mittelwert mit 252 multiplizieren); die Volatilität skaliert mit der Quadratwurzel der Zeit (mit √252 multiplizieren). Multipliziert man eine tägliche Standardabweichung mit 252, ergibt sich eine Volatilität von mehreren hundert Prozent und eine Sharpe nahe null; multipliziert man eine tägliche Sharpe mit 252, ergibt sich eine Sharpe von 10 oder mehr. Beides ist schon in echten Notebooks vorgekommen. Ein Test mit bekannten täglichen Eingaben fängt beide Fehler sofort ab.

Übungsaufgabe

Auf Papier: (a) Eine Equity-Reihe verläuft 50, 55, 44, 60, 48, 66 (in Tausend Euro). Berechnen Sie den Drawdown an jedem Punkt und den maximalen Drawdown. (b) Monatsrenditen haben einen Mittelwert von 0.8 % und eine Stichproben-Standardabweichung von 3.0 %; berechnen Sie die annualisierte Sharpe Ratio (risikofreier Zins 0) mit periods = 12. (c) Schreiben Sie eine pytest-Funktion, die Ihre Antwort auf (b) mit pytest.approx und abs=0.01 prüfen würde.

Lösung anzeigen

(a) Laufende Hochs: 50, 55, 55, 60, 60, 66. Drawdowns: 0; 0; 44/55 − 1 = −20.0 %; 0; 48/60 − 1 = −20.0 %; 0. Maximaler Drawdown = −20.0 % (zweimal erreicht).

(b) Sharpe = 0.008 / 0.030 × √12 = 0.2667 × 3.464 = 0.92.

(c) Beispiel:

  • def test_sharpe_monthly():
  • r = pd.Series([0.008 + 0.03, 0.008 - 0.03] * 6) # Mittelwert 0.8 %, Std.-Abw. ≈ 3.1 %
  • assert sharpe(r, periods=12) == pytest.approx(0.89, abs=0.05)

Beachten Sie, dass die alternierende Reihe eine Stichproben-Standardabweichung von etwa 3.13 % hat, nicht genau 3.0 %; deshalb ist der erwartete Wert 0.89 und die Toleranz erweitert. Testdaten zu konstruieren, deren Statistiken Sie exakt berechnen können, gehört zum Handwerk.

Einschränkungen: Die Sharpe Ratio setzt ungefähr symmetrische und unabhängige Renditen voraus; bei Strategien mit seltenen großen Verlusten überzeichnet sie die Qualität, weshalb Sortino und maximaler Drawdown zusätzlich ausgewiesen werden.

Kernaussagen

  • Halten Sie Kennzahlen in einem Modul; Funktionen nehmen eine Equity- oder Renditereihe und ein Argument periods entgegen und geben einen Float zurück.
  • Der Mittelwert skaliert mit der Zeit, die Standardabweichung mit der Quadratwurzel der Zeit; machen Sie die Konstante zu einem Parameter.
  • Dokumentieren Sie Entscheidungen wie ddof und den Sortino-Nenner und sichern Sie sie mit Tests ab.
  • Rollierende Kennzahlen zeigen, wann ein Ergebnis erzielt wurde; eine einzelne Zahl kann einen Ausreißer in einem einzigen Jahr verbergen.

Nächster Schritt

Lektion 10 zeigt, wie Sie darstellen, was diese Funktionen messen: Kurs mit Indikatoren, Equity mit Drawdown und Renditeverteilungen, exportiert als saubere Grafiken.

Selbsttest

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

1. Tagesrenditen haben einen Mittelwert von 0.05 % und eine Stichproben-Standardabweichung von 1.2 %. Wie hoch ist die annualisierte Sharpe Ratio (auf zwei Dezimalstellen gerundet) bei 252 Handelstagen und einem risikofreien Zins von null?

2. Ihre Funktion max_drawdown liefert −0.18 für eine Testreihe, für die Sie von Hand −0.25 berechnet haben. Zu welchem Fehler passt das am besten?

3. Warum sollte eine Kennzahlenfunktion ein Argument periods entgegennehmen, statt 252 in der Formel zu verwenden?

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.