Im letzten Artikel der Event-Driven Backtester-Serie haben wir eine grundlegende ExecutionHandler-Hierarchie betrachtet. In diesem Artikel werden wir diskutieren, wie die Leistung einer Strategie nach dem Backtest mit der zuvor konstruierten Eigenkapitalkurve DataFrame im Portfolio-Objekt bewertet werden kann.
Wir haben die Sharpe-Ratio bereits in einem früheren Artikel erörtert.
Hierbei ist Ra der Renditestrom der Eigenkapitalkurve und Rb ein Benchmark, z. B. ein geeigneter Zinssatz oder Eigenkapitalindex.
Die maximale Auslastung und die Auslastungsdauer sind zwei zusätzliche Maßnahmen, die Anleger häufig zur Bewertung des Risikos in einem Portfolio verwenden.
In diesem Artikel werden wir die Sharpe-Ratio, den maximalen Drawdown und die Drawdown-Dauer als Maß für die Portfolioleistung für die Verwendung in der Python-basierten Event-Driven Backtesting-Suite implementieren.
Die erste Aufgabe besteht darin, eine neue Datei zu erstellenperformance.pyWie bei den meisten unserer berechnungsintensiven Klassen müssen wir NumPy und Pandas importieren:
# performance.py
import numpy as np
import pandas as pd
Es ist zu beachten, dass die Sharpe-Ratio ein Risiko-Rendite-Maß ist (in der Tat ist es nur einer von vielen!).
Normalerweise wird dieser Wert auf 252 gesetzt, was die Anzahl der Handelstage in den USA pro Jahr ist. Wenn Ihre Strategie jedoch innerhalb der Stunde handelt, müssen Sie den Sharpe anpassen, um ihn korrekt zu verjähren. Daher müssen Sie die Perioden auf 252
Die Funktion create_sharpe_ratio arbeitet auf einem Objekt der Pandas-Serie namens Returns und berechnet einfach das Verhältnis des Mittelwerts der Periodenprozentsatzrenditen und der Periodenprozentsatzrenditenstandardabweichungen, die durch den Periodenfaktor skaliert werden:
# performance.py
def create_sharpe_ratio(returns, periods=252):
"""
Create the Sharpe ratio for the strategy, based on a
benchmark of zero (i.e. no risk-free rate information).
Parameters:
returns - A pandas Series representing period percentage returns.
periods - Daily (252), Hourly (252*6.5), Minutely(252*6.5*60) etc.
"""
return np.sqrt(periods) * (np.mean(returns)) / np.std(returns)
Während die Sharpe-Ratio das Risiko (definiert durch die Standardabweichung des Vermögenspfades) pro Renditeinheit bestimmt, wird der
Die Funktion create_drawdowns unten gibt tatsächlich sowohl den maximalen Drawdown als auch die maximale Drawdown-Dauer an. Der erstere ist der oben erwähnte größte Spitzen-Trop-Rückgang, während der letztere als die Anzahl der Perioden definiert wird, in denen dieser Rückgang auftritt.
Bei der Interpretation der Auslastungsdauer ist eine gewisse Feinheit erforderlich, da sie Handelszeiten zählt und daher nicht direkt in eine zeitliche Einheit wie
Die Funktion beginnt mit der Erstellung von zwei Objekten der Pandas-Serie, die den Drawdown und die Dauer bei jedem Handel
Der Drawdown ist dann einfach die Differenz zwischen der aktuellen HWM und der Eigenkapitalkurve. Wenn dieser Wert negativ ist, wird die Dauer für jeden Bars erhöht, bis die nächste HWM erreicht ist. Die Funktion gibt dann einfach das Maximum für jede der beiden Reihen zurück:
# performance.py
def create_drawdowns(equity_curve):
"""
Calculate the largest peak-to-trough drawdown of the PnL curve
as well as the duration of the drawdown. Requires that the
pnl_returns is a pandas Series.
Parameters:
pnl - A pandas Series representing period percentage returns.
Returns:
drawdown, duration - Highest peak-to-trough drawdown and duration.
"""
# Calculate the cumulative returns curve
# and set up the High Water Mark
# Then create the drawdown and duration series
hwm = [0]
eq_idx = equity_curve.index
drawdown = pd.Series(index = eq_idx)
duration = pd.Series(index = eq_idx)
# Loop over the index range
for t in range(1, len(eq_idx)):
cur_hwm = max(hwm[t-1], equity_curve[t])
hwm.append(cur_hwm)
drawdown[t]= hwm[t] - equity_curve[t]
duration[t]= 0 if drawdown[t] == 0 else duration[t-1] + 1
return drawdown.max(), duration.max()
Um diese Leistungsindikatoren zu nutzen, benötigen wir ein Mittel, um sie nach einem Backtest zu berechnen, d.h. wenn eine geeignete Eigenkapitalkurve verfügbar ist!
Da die Performance-Maße auf einer Portfolio-Basis berechnet werden, ist es sinnvoll, die Performance-Berechnungen an eine Methode auf der Portfolio-Klassen-Hierarchie anzuhängen, die wir in diesem Artikel diskutiert haben.
Die erste Aufgabe besteht darin, sich zu öffnen.portfolio.pywie im vorherigen Artikel beschrieben und die Leistungsfunktionen importieren:
# portfolio.py
.. # Other imports
from performance import create_sharpe_ratio, create_drawdowns
Da Portfolio eine abstrakte Basisklasse ist, möchten wir eine Methode an eine ihrer abgeleiteten Klassen anhängen, die in diesem Fall NaivePortfolio sein wird. Daher werden wir eine Methode namens output_summary_stats erstellen, die auf der Portfolio-Equity-Kurve wirkt, um die Sharpe- und Drawdown-Informationen zu generieren.
Die Methode ist einfach: Sie nutzt einfach die beiden Leistungsmessungen und wendet sie direkt auf die Eigenkapitalkurve DataFrame an und liefert die Statistiken in Form einer Liste von Tupeln in formfreundlicher Weise:
# portfolio.py
..
..
class NaivePortfolio(object):
..
..
def output_summary_stats(self):
"""
Creates a list of summary statistics for the portfolio such
as Sharpe Ratio and drawdown information.
"""
total_return = self.equity_curve['equity_curve'][-1]
returns = self.equity_curve['returns']
pnl = self.equity_curve['equity_curve']
sharpe_ratio = create_sharpe_ratio(returns)
max_dd, dd_duration = create_drawdowns(pnl)
stats = [("Total Return", "%0.2f%%" % ((total_return - 1.0) * 100.0)),
("Sharpe Ratio", "%0.2f" % sharpe_ratio),
("Max Drawdown", "%0.2f%%" % (max_dd * 100.0)),
("Drawdown Duration", "%d" % dd_duration)]
return stats
Es ist klar, dass dies eine sehr einfache Performance-Analyse für ein Portfolio ist.performance.pyund sie dann nach Bedarf in output_summary_stat einbeziehen.