Métricas de Backtesting

Le recomiendo encarecidamente que vea la película The Big Short. Sin duda para entender qué demonios sucedió en el 2007 con el hundimiento del sector hipotecario subprime que arrastró consigo a toda la economía, mientras se deleita con fantásticas actuaciones por parte de Steve Carell, Ryan Gosling y Brad Pitt.  Pero sobre todo para vislumbrar la agonía a la que se puede ver abocada un inversor. Incluso con un “buen” trade.

Pues fundamentalmente la posición corta que adoptan consiste en la siguiente apuesta: un flujo constante de pérdidas, durante un periodo de tiempo que puede abarcar años, con sólo una posibilidad de ganancia en un futuro incierto. Trate de imaginarse a usted en dicha situación. Su capital goteando día tras día, progresivamente desapareciendo por la alcantarilla y con él su autoestima. Las dudas impidiéndole dormir por la noche. ¿Me habré equivocado? ¿Me he dejado algo? ¿Realmente soy tan idiota como todos piensan?

Eventualmente los protagonistas de la historia ganan, y ganan muchísimo. Pero podemos constatar en el personaje interpretado, de forma brillante, por Christian Bale que incluso ese éxito se ha cobrado un precio enorme, que la tremenda presión sufrida ha pulverizado una parte de Michael Burry para siempre.

Seamos por tanto conscientes de que no basta con saber cuánto dinero hace su sistema, si no también cómo gana ese dinero. En qué orden. Con qué frecuencia. A qué precio. Porque más allá del aspecto financiero estos factores tienen una tremenda importancia psicológica para los seres humanos, que hemos heredado determinadas expectativas en los ritmos de pérdida y ganancia desde nuestro pasado ancestral. Y pueden convertir el camino al cielo en un infierno en la tierra.

pressure

En este post vamos a completar ese conjunto de factores que usted definitivamente debe tener en cuenta para conocer a fondo su metodología de trading. Afortunadamente nuestro entorno de backtesting PyAlgoTrade cuenta con StrategyAnalyzers para todos ellos, así que nuestro trayecto en esta ocasión será bien sosegado.

Les invito a que visiten mi introducción al backtesting si necesitan ponerse al día, y les dejo aquí para descarga el código fuente con todas las métricas de las que vamos a hablar, implementadas sobre el ejemplo del S&P 500 sobre el que trabajamos en nuestros posts previos: SpxStrategyFull.py y SpxBenchmarkFull.py.

Pérdidas

El término inglés de Drawdown no tiene fácil traducción, ni siquiera como “racha de pérdidas”. Realmente engloba dos conceptos igualmente relevantes:

  • La pérdida máxima, que es la cantidad más grande de dinero que se puede perder en su sistema, entrando y saliendo de él en los peores momentos posibles.
  • La pérdida más larga, que es la cantidad más grande de tiempo (medido normalmente en días) con pérdidas ininterrumpidas.

La implementación en PyAlgoTrade sigue el patrón que vimos con el ratio de Sharpe:

from pyalgotrade.stratanalyzer import drawdown

drawDownAnalyzer = drawdown.DrawDown()
myStrategy.attachAnalyzer(drawDownAnalyzer)
# ejecutar la estrategia
print "Pérdida Máxima: " + str(drawDownAnalyzer.getMaxDrawDown())
print "Pérdida Más Larga: " + str(drawDownAnalyzer.getLongestDrawDownDuration())

Dése cuenta de que ambos son conceptos muy relacionados pero que que no tienen por qué coincidir en la misma racha de pérdidas. Puede darse el caso de que su metodología experimente una pérdida muy larga en el tiempo, pero de cantidades muy pequeñas, que en conjunto son menores que las pérdidas acumuladas en una caída violenta y repentina.

Por otro lado piense también de que aquí estamos hablando de valores máximos, no medios como en la mayoría de los otros casos. Si asumimos una distribución normal puede extrapolar valores medios aproximados, pero si se trata de un resultado atípico tal vez no sea significativo de su sistema. Por ejemplo, si su periodo de análisis incluye la catástrofe del hundimiento subprime puede que le convenga completar el análisis realizando otro backtesting excluyendo dicho periodo para estudiar el segundo drawdown.

Operaciones

Si no quiere escucharme en nada más durante este post, por favor al menos hágame caso en esto. Es una tendencia humana general focalizarse en los beneficios, pero lo peor que puede hacer es valorar su sistema a través de la rentabilidad final (e.g. $7 millones). Ni siquiera si lo valora como un porcentaje (e.g. 267%). Ni siguiera si lo anualiza (e.g. 35% anual). ¿Por qué?

Porque se trata de una rentabilidad acumulada. Imagine que el primer día usted gana 10% sobre 100%. Cuando el segundo día gane otro 10%, no lo hará sobre 100%, sino sobre 110%. Y así progresivamente. Esto implica que si por suerte obtiene fuertes ganancias al principio, el efecto composición le garantizará un retorno digno incluso si lo hace comparativamente mal el resto del tiempo. A la inversa, si su sistema tiene grandes pérdidas al principio le costará recuperarse aunque lo haga muy bien más adelante. Y ambos efectos podrían ser fortuitos, para nada representativos del comportamiento general de su metodología de inversión.

Por eso, para evaluar tal vez le interesará más comparar medias sobre valores simples, no compuestos, de sus trades, como:

  • La ganancia media por operación.
  • La perdida media por operación.
  • El número de operaciones en las que gana, pierde, o se queda igual.

Podemos expresar esto en PyAlgoTrade con el StrategyAnalyzer correspondiente y un poco de trabajo por nuestro lado calculando las medias:

from pyalgotrade.stratanalyzer import trades
from pyaglotrade.utils import stats

tradesAnalyzer = trades.Trades() 
myStrategy.attachAnalyzer(tradesAnalyzer)
# ejecutar la estrategia
meanProfit = stats.mean(tradesAnalyzer.getProfits())
print "Ganancia Media: " + str(meanProfit) 
meanLoss = stats.mean(tradesAnalyzer.getLosses()) 
print "Pérdida Media: " + str(meanLoss)
print "Núm Ops Igual: " + str(tradesAnalyzer.getEvenCount())
print "Núm Ops Gano: " + str(tradesAnalyzer.getProfitableCount())
print "Núm Ops Pierdo: " + str(tradesAnalyzer.getUnprofitableCount())

DataSeries

Es muy aconsejable que además de analizar nuestro sistema por operación, lo hagamos por periodo (día, mes, año, dependiendo de nuestra política de inversión), y para ello podemos acudir al ReturnAnalyzer que guarda para nosotros toda la serie histórica de retornos.

Aquí conviene una nota de precaución: internamente PyAlgoTrade utiliza un tipo de objeto llamado DataSeries para procesar, precisamente, series de datos: desde los feeds de Yahoo hasta el análisis de los rendimientos. Una vez han alcanzado un tamaño determinado (por defecto, 1024 elementos) dichas DataSeries empiezan a eliminar valores antiguos según van entrando valores nuevos (se comportan como un sistema FIFO, “First In First Out”, para aquellos adeptos a logística), lo cual es extremadamente conveniente si nuestro universo de análisis incluye cientos de miles de acciones manipuladas en cada tick intradiario, que una vez procesado se puede descartar sin consecuencias. Pero para analizar el retorno de toda una serie de más de 1024 elementos (sesiones) se nos quedará pequeño.

from pyalgotrade.stratanalyzer import returns

returnsAnalyzer = returns.Returns()
myStrategy.attachAnalyzer(returnsAnalyzer)
# ejecutar la estrategia
print len(returnsAnalyzer.getReturns())   # el resultado es 1024!

Podemos convenientemente usar setMaxLen() para modificar el tamaño de la cola ANTES de ejecutar la estrategia, o bien usando un número muy grande (como 1000000) o bien ajustándolo al número de años multiplicado por 260 (dejando un poco de margen ya que hay unas 252 sesiones de trading por año). Ahora ya podemos analizar la rentabilidad media global, la ganancia media y la pérdida media por periodo.

from pyalgotrade.stratanalyzer import returns
from pyalgotrade.utils import stats

returnsAnalyzer = returns.Returns()
myStrategy.attachAnalyzer(returnsAnalyzer)
returnsAnalyzer.getReturns().setMaxLen(300000)
# ejecutar la estrategia
allRet = returnsAnalyzer.getReturns()
print len(allRet)   # ahora tiene el tamaño correcto
print "Rent Media: " + str(stats.mean(allRet))
posRet = []
negRet = []
allRet = returnsAnalyzer.getReturns()
for ret in allRet:
    if ret > 0:
        posRet.append(ret)
    elif ret < 0:
        negRet.append(ret)
print "Ganancia Media: " + str(stats.mean(posRet))
print "Pérdida Media: " + str(stats.mean(negRet))

No se preocupe por la cola de los DataSeries en los cálculos que ya hicimos antes: ni el ratio de Sharpe, ni el drawdown ni los trades se ven afectados por ello ya que procesan la información sobre la marcha y la pueden descartar sin problemas.

Por otro lado el genial autor de PyAlgoTrade, Gabriel Martín Becedillas, me ha comentado que a partir de la próxima versión (0.18) será posible modificar el tamaño máximo de forma global así:

from pyalgotrade import dataseries
dataseries.DEFAULT_MAX_LEN = 2000

Resumen

En definitiva, seamos conscientes de que no basta con saber cuánto dinero hace su sistema, ni siquiera “ajustado al riesgo”. Porque tal vez el que no puede “ajustarse al riesgo” sea usted, y abandone una metodología ganadora después de una racha de pérdidas psicológicamente abrumadora.

Como mínimo lo que me gusta ver para poder valorar y entender bien mi metodología de trading es:

  • Mi rentabilidad ajustada al riesgo (ratio de Sharpe)
  • Cuánto puedo llegar a perder, y por cuánto tiempo (drawdown).
  • Mi ganancia y pérdida media, por periodo y por operación.
  • Mi riesgo medio.

Aquí no hay verdades absolutas: tiene que sincerarse consigo mismo y evaluar si este es un sistema con el que usted se siente confortable, para evitar sorpresas más adelante.

Confío en que haya disfrutado de este post, no dude en hacerme llegar sus sugerencias para temas futuros. ¡Gracias por leerme y por compartirlo en sus redes sociales!

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s