Momentos Britney

El mercado no perdona los descuidos. Puede pasar de la más beatífica de las calmas a la más horrible de las tempestades en menos que canta un gallo y pillarle por sorpresa apalancado hasta las trancas.

Son esos instantes los que llamo “Momentos Britney”: de pronto las cosas se le van de las manos, parece que todos confabulan en su contra, no hay lugar dónde ocultarse, la presión se vuelve insoportable y asediado por la locura sólo tiene ganas de gritar: ¡STOP! ¡Que el mundo pare de girar y me dejen un rato tranquilo!

Por suerte nosotros no tendremos que sufrir lo de la pobre Princess of Pop ya que en el entorno financiero contamos con las Órdenes de Stop precisamente para esas situaciones.

El brillante Javier Molina cuenta con un post introductorio acerca de dichas operaciones, y mi socio Carlos Doblado ha hablado largo y tendido sobre cómo fijar objetivos. En lugar de repetir tan sabios consejos (lea y juzgue por usted mismo) en este artículo me voy a centrar en cómo podemos integrar las órdenes de stop en nuestro entorno de backtesting y en sus sinergias (o ausencia de ellas) con nuestro algoritmos de operaciones.

Para los ejemplos descargue el archivo StopStrategy.py que es una extensión de las estrategias planteadas en nuestro capítulo anterior sobre indicadores financieros, si acaba de llegar por favor consúltelo para entender cómo funcionan los parámetros de cabecera.

Stop a Secas

PyAlgoTrade nos proporciona una manera muy simple de establecer stops para nuestras posiciones usando la función exitStop(). Podemos activar dicha función tan pronto como se ha confirmado nuestra operación de entrada, y lo único que hemos de recordar es que hemos de establecer un diferente precio objetivo si la operación es de largos (objetivo por debajo del precio actual) o de cortos (objetivo por encima del precio actual).

def onEnterOk(self, position):
    order = position.getEntryOrder()
    if order.getAction() == broker.Order.Action.BUY:
        self.logOp("COMPRA", order)
        position.exitStop(order.getExecutionInfo().getPrice() * (1 - self._stopPer), True)
    else:
        self.logOp("VENTA CORTA", order)
        position.exitStop(order.getExecutionInfo().getPrice() * (1 + self._stopPer), True)

El último parámetro, True, se encarga de mantener el stop activo hasta que salte o decidamos cerrar la posición, lo cual nos presenta un pequeño reto ya que el broker de backtesting, al igual que muchos reales, sólo permite que tengamos una orden activa por posición. Eso significa si en un momento queremos colocar nuestra propia orden de salida, deberemos antes cancelar el stop.

def prepareExit(self, position):
    order = position.getExitOrder()
    if not order:
        position.exitMarket()
    elif isinstance(order, broker.StopOrder):
        position.cancelExit()

En este código de ejemplo usamos isinstance para determinar rápidamente si la orden existente es un stop (hereda de broker.StopOrder) para diferenciarla de otras órdenes activas que tal vez no queremos cancelar.

Una vez cancelExit() se ha completado recibiremos una confirmación inmediata mediante el evento onExitCancelled(), en cuyo momento podemos reiterar nuestra intención de salida:

def onExitCanceled(self, position):
    position.exitMarket()

No se preocupe, como la confirmación es inmediata la nueva orden de salida se activa durante la misma barra, no se produce ningún retraso de mercado al proceder de esta manera.

britney1

Trailing Stops

A veces en lugar de colocar un precio objetivo fijo para nuestros stops, preferiremos que este se adapte dinámicamente a la evolución del activo, activándose sólo si su cotización cae por debajo de un determinado umbral desde su cota más alta (o baja, en el caso de un corto). Esto es lo que se conoce como un Trailing Stop y ya que no recede una vez alcanzada una cota técnicamente nos permite convertir un stop loss en un take profit, asegurándonos un nivel de retorno en las operaciones exitosas.

PyAlgoTrade no cuenta por defecto con trailing stops pero su arquitectura de código abierto nos permite introducirlos con facilidad salvando un pequeño bache. Primero, modelaremos nuestra orden como una clase que hereda de backtesting.StopOrder (más rica en funcionalidad que su ancestro broker.StopOrder).

import math
from pyalgotrade import broker
from pyalgotrade.broker import backtesting
class TrailingStopOrder(backtesting.StopOrder):
    def __init__(self, action, position, pricePer, quantity, instrumentTraits):
        backtesting.StopOrder.__init__(self, action, position.getInstrument(), 0, math.fabs(quantity), instrumentTraits)
        if action == broker.Order.Action.SELL:
            pricePer = 1 - pricePer
        else:
            pricePer = 1 + pricePer
        self._pricePer = pricePer
        self._position = position
        self._refPrice = 0

    def getStopPrice(self):
        lastPrice = self._position.getLastPrice()
        if self.getAction() == broker.Order.Action.SELL:
            if self._refPrice < lastPrice:
                self._refPrice = lastPrice
        else:
            if self._refPrice > lastPrice:
                self._refPrice = lastPrice
        return self._refPrice * self._pricePer

Este código está adaptado tanto a largos como cortos, fíjese que usamos math.fabs() para obtener la cantidad absoluta de acciones ya que los cortos se guardan con valores negativos (“debemos” esa cantidad de títulos). Mediante position.getLastPrice() vamos siguiendo la evolución de la cotización, sólo cambiando el precio de referencia si se obtiene una cota superior.

Ahora el bache: las funciones que nos permitirían registrar la orden de stop como __submitAndRegisterOrder() son privadas dentro de la posición; normalmente fuera de nuestro alcance. Aquí es donde la increíble flexibilidad de Python viene a nuestro auxilio, ya que podemos acceder a dicha funcionalidad simplemente anteponiendo el prefijo de la clase (_Position)realmente se trata de métodos ocultos, no privados:  Python sugiere, recomienda, pero no obliga.

stopOrder = TrailingStopOrder(broker.Order.Action.SELL, position, self._stopPer, position.getShares(), order.getInstrumentTraits())
stopOrder.setGoodTillCanceled(True)
position._Position__submitAndRegisterOrder(stopOrder)
position._Position__exitOrder = stopOrder

Resuenan en mi memoria los consejos de Paul Graham, fundador de Viaweb en Boston (que se vendió por 49 millones de dólares a Yahoo en 1998)  y posteriormente trasladó a Silicon Valley su firma de venture capital Y Combinator revolucionando el sector del capital riesgo. Con Paul me unen tanto los estudios de filosofía y letras como la pasión por una cultura de programación abierta: “el mayor debate en el diseño de lenguajes está probablemente entre los que piensan que este debe evitar las estupideces, y aquellos que piensan que a un programador se le debe permitir hacer lo que quiera”, porque la estupidez de uno puede ser la genialidad de otro. Por eso Java es una mierda y existen en el mundo cosas como Node, Python y Ruby.

britney3

Resultados

El fichero StopStrategy.py contiene todo el código de arriba integrado para que pueda practicar con los nuevos stops simplemente modificando las primeras líneas:

SCENARIO = 1
DBFEED = False
TRAILING = False

Cambie DBFEED para usar base de datos o archivos separado por comas, TRAILING para usar stops de tipo fijo o dinámico, y finalmente SCENARIO para ejecutar una de las tres diferentes estrategias disponibles (Benchmark, Básica o Avanzada).

Cuando lleven a cabo sus propios experimentos, verán que de hecho la mayor parte del tiempo obtenemos peores resultados. En el Benchmark, está claro, es un suicidio porque la metodología nos vuelve a meter en el mercado nada más salir, pero también en las otras estrategias la presencia de stops deteriora, en lugar de mejorar, los resultados obtenidos previamente.

¿A qué se debe la sorpresa? En general, si colocamos los stops demasiado lejos del precio no tienen ningún efecto, pero si los colocamos demasiado cerca uno de los frecuentes bandazos correctivos nos puede fácilmente sacar del mercado y así perdernos el rally posterior. O dicho de otra forma:

Para poder ganar hay que estar dispuesto a arriesgar y, frecuentemente, a perder.

Lo cual nos lleva a la que tal vez sea la lección más importante que podemos obtener: los stops no aportan mucho a nuestras estrategias porque ellas ya cuentan con mejores técnicas de salida del mercado frente a eventualidades “cocidas” dentro su metodología operativa: estamos en una buena situación porque los stops “en bruto” nos ofrecen pocas sinergias frente al trabajo que llevan a cabo nuestras reglas algorítmicas.

britney2

Conclusión

¿Fácil, verdad? A diferencia de Brit conseguimos superar nuestros problemas sin necesidad de raparnos al cero, acosar a la prensa a paraguazos ni casarnos en el Little White Chapel de Las Vegas con nuestro amigo de la infancia para divorciarnos días después.

De ahora en adelante recuerde las ventajas, pero también los peligros, de utilizar stops en su operativa. Bien pueden salvarle la retaguardia durante esas vacaciones en el pueblo con escasa cobertura, pero aún mejor si cuenta con un seguimiento cercano de la evolución de las cotizaciones y una metodología inteligente para sacarle del mercado cuando toca.

Más allá de eso tal vez quiere utilizar estos nuevos “superpoderes” adquiridos y crear sus propios stops más elaborados. En el archivo de arriba le he dejado un SessionOpenStopOrder, que se pega al precio de apertura como referencia, para que pueda usarlos de plantilla en sus experimentos.

Ya que hemos asentado unas sólidas bases, en la siguiente entrega vamos a introducirnos en el muy interesante mundo de la optimización de sistemas. ¡Aprovechen la pausa, disfruten este sol veraniego y por favor no olviden compartir el artículo!

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