Dans le dernier article de la série Event-Driven Backtester, nous avons examiné une hiérarchie de base d'ExecutionHandler. Dans cet article, nous allons discuter de la façon d'évaluer la performance d'une stratégie après le backtest en utilisant la courbe d'équité DataFrame construite précédemment dans l'objet Portfolio.
Nous avons déjà examiné le ratio Sharpe dans un article précédent, dans lequel j'explique que le ratio Sharpe (annualisé) est calculé par:
où Ra est le flux de rendement de la courbe des capitaux propres et Rb est un indice de référence, tel qu'un taux d'intérêt ou un indice des capitaux propres appropriés.
Le tirage maximal et la durée du tirage sont deux mesures supplémentaires que les investisseurs utilisent souvent pour évaluer le risque dans un portefeuille.
Dans cet article, nous allons mettre en œuvre le ratio Sharpe, le tirage maximum et la durée du tirage en tant que mesures de la performance du portefeuille pour une utilisation dans la suite de backtesting basée sur Python.
La première tâche consiste à créer un nouveau fichierperformance.py, qui stocke les fonctions pour calculer le rapport Sharpe et les informations de tirage.
# performance.py
import numpy as np
import pandas as pd
Il convient de noter que le ratio Sharpe est une mesure du rapport risque/rendement (en fait, il s'agit d'une des nombreuses mesures) qui ne comporte qu'un seul paramètre, celui du nombre de périodes à adapter lors de la mise à l'échelle vers la valeur annualisée.
En règle générale, cette valeur est fixée à 252, qui est le nombre de jours de négociation aux États-Unis par an. Cependant, si votre stratégie se négocie dans l'heure, vous devez ajuster le Sharpe pour l'annuler correctement. Ainsi, vous devez définir des périodes à 252
La fonction create_sharpe_ratio fonctionne sur un objet de série panda appelé rendements et calcule simplement le rapport entre la moyenne des rendements en pourcentage de la période et les écarts types de rendement en pourcentage de la période échelonnés par le facteur de périodes:
# 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)
Alors que le ratio de Sharpe caractérise la quantité de risque (définie par l'écart type de la trajectoire des actifs) qui est prise par unité de rendement, le
La fonction create_drawdowns ci-dessous fournit en fait à la fois le tirage maximal et la durée maximale de tirage.
L'interprétation de la durée de mise à jour nécessite une certaine subtilité, car elle compte les périodes de négociation et ne peut donc pas être directement traduite en unité temporelle telle que
La fonction commence par créer deux objets de la série panda représentant le tirage et la durée à chaque transaction
Le drawdown est alors simplement la différence entre le HWM actuel et la courbe d'équité. Si cette valeur est négative, la durée est augmentée pour chaque barre jusqu'à ce que le HWM suivant soit atteint. La fonction renvoie alors simplement le maximum de chacune des deux séries:
# 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()
Pour pouvoir utiliser ces mesures de performance, nous avons besoin d'un moyen de les calculer après un backtest, c'est-à-dire lorsqu'une courbe de rentabilité appropriée est disponible!
Nous devons également associer le calcul à une hiérarchie d'objets particulière. Étant donné que les mesures de performance sont calculées sur une base de portefeuille, il est logique d'attacher les calculs de performance à une méthode sur la hiérarchie de classe de portefeuille dont nous avons parlé dans cet article.
La première tâche est de s'ouvrirportfolio.pycomme discuté dans l'article précédent et importer les fonctions de performance:
# portfolio.py
.. # Other imports
from performance import create_sharpe_ratio, create_drawdowns
Puisque Portfolio est une classe de base abstraite, nous voulons attacher une méthode à l'une de ses classes dérivées, qui dans ce cas sera NaivePortfolio.
La méthode est simple: elle utilise simplement les deux mesures de performance et les applique directement à la courbe d'équité DataFrame, produisant les statistiques sous forme de liste de tuples de manière conviviale:
# 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
Il est clair que c'est une analyse de performance très simple pour un portefeuille.performance.pyet ensuite les intégrer dans output_summary_stat selon les besoins.