En el último artículo de la serie Event-Driven Backtester consideramos una jerarquía básica de ExecutionHandler. En este artículo vamos a discutir cómo evaluar el rendimiento de una estrategia después de la prueba de retroceso utilizando la curva de equidad DataFrame construida previamente en el objeto Portfolio.
En un artículo anterior ya hemos considerado la proporción de Sharpe, en el que expongo que la proporción de Sharpe (anualizada) se calcula mediante:
Donde Ra es el flujo de rendimientos de la curva de renta variable y Rb es un índice de referencia, como un tipo de interés o un índice de renta variable apropiado.
La duración máxima de la absorción y la duración de la absorción son dos medidas adicionales que los inversores utilizan a menudo para evaluar el riesgo en una cartera.
En este artículo implementaremos la relación Sharpe, el descenso máximo y la duración del descenso como medidas del rendimiento de la cartera para su uso en el conjunto de pruebas de retroceso basado en eventos basado en Python.
La primera tarea es crear un nuevo archivoperformance.py, que almacena las funciones para calcular la relación Sharpe y la información de extracción.
# performance.py
import numpy as np
import pandas as pd
La relación Sharpe es una medida del riesgo y de la recompensa (de hecho, es una de muchas) y tiene un solo parámetro, el del número de períodos a ajustar al escalar al valor anualizado.
Por lo general, este valor se establece en 252, que es el número de días de negociación en los EE.UU. por año. Sin embargo, si su estrategia se negocia dentro de la hora, debe ajustar el Sharpe para anulizarlo correctamente. Por lo tanto, debe establecer períodos en 252
La función create_sharpe_ratio opera en un objeto de la serie pandas llamado rendimientos y simplemente calcula la relación entre la media de los rendimientos porcentuales del período y las desviaciones estándar de rendimiento porcentuales del período escaladas por el factor de períodos:
# 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)
Mientras que el índice de Sharpe caracteriza la cantidad de riesgo (definido por la desviación estándar de la trayectoria del activo) que se asume por unidad de rendimiento, el
La función create_drawdowns a continuación proporciona en realidad tanto el descenso máximo como la duración máxima del descenso. El primero es la mayor caída de pico a fondo mencionada anteriormente, mientras que el segundo se define como el número de períodos en los que se produce esta caída.
Se requiere cierta sutileza en la interpretación de la duración de extracción, ya que cuenta períodos de negociación y, por lo tanto, no se puede traducir directamente a una unidad temporal como
La función comienza creando dos objetos de la serie pandas que representan el descenso y la duración en cada operación
La reducción es entonces simplemente la diferencia entre el HWM actual y la curva de equidad. Si este valor es negativo, entonces la duración se incrementa para cada barra que ocurre hasta que se alcanza el siguiente HWM. La función entonces simplemente devuelve el máximo de cada una de las dos series:
# 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()
Para utilizar estas medidas de rendimiento necesitamos un medio para calcularlas después de haber realizado una prueba de retroceso, es decir, cuando se dispone de una curva de equidad adecuada.
También necesitamos asociar el cálculo con una jerarquía de objetos particular. Dado que las medidas de rendimiento se calculan sobre la base de una cartera, tiene sentido adjuntar los cálculos de rendimiento a un método en la jerarquía de clases de cartera que discutimos en este artículo.
La primera tarea es abrirseportfolio.pycomo se discutió en el artículo anterior e importar las funciones de rendimiento:
# portfolio.py
.. # Other imports
from performance import create_sharpe_ratio, create_drawdowns
Dado que Portfolio es una clase base abstracta, queremos adjuntar un método a una de sus clases derivadas, que en este caso será NaivePortfolio. Por lo tanto, crearemos un método llamado output_summary_stats que actuará sobre la curva de equidad de la cartera para generar la información de Sharpe y drawdown.
El método es sencillo: simplemente utiliza las dos medidas de rendimiento y las aplica directamente a la curva de equidad de DataFrame, produciendo las estadísticas como una lista de tuples de una manera amigable con el formato:
# 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 evidente que este es un análisis de rendimiento muy simple para una cartera. No considera el análisis a nivel de transacción u otras medidas de riesgo/recompensación.performance.pyy luego incorporarlos en output_summary_stats según sea necesario.