Un simple Trading Estrategia en Tirolesa

Desarrollemos un sencillo trading estrategia usando dos promedios móviles simples ahora que hemos instalado Zipline. Esta estrategia simple se llama estrategia de promedio móvil dual.

La mejor manera de explicar la estrategia de doble media móvil (DMA) es con un ejemplo. Una media móvil simple es el precio medio de los últimos x número de trading períodos. Trading los períodos pueden ser semanales, diarios, por horas, etc. Para calcular un promedio móvil simple (SMA) de 50 días, sumaríamos los precios de cierre de los 50 días anteriores y los dividiríamos por 50, que nuevamente es el número total de días.

Ahora que entendemos qué es un promedio móvil simple, analicemos la estrategia DMA. Si calculamos tanto la SMA de 50 días como la SMA de 200 días, podemos determinar la tendencia del precio. Cuando el promedio móvil de 50 días cruza por encima del promedio móvil de 200 días, la tendencia es alcista y la estrategia diría comprar. Cuando el promedio móvil de 50 días cruza por debajo del promedio móvil de 200 días, la tendencia se considera bajista y la estrategia establece que debemos apostar a que el precio caerá aún más. ¿Funciona en la práctica? ¡Vamos a averiguar!

Configurar cuaderno Jupyter

Configuremos nuestro espacio de trabajo y ejecutemos el cuaderno Jupyter. Cree un directorio para almacenar sus archivos y active su entorno Zipline usando conda donde env_zipline es lo que llamó su entorno conda.

$ mkdir workspace
$ cd workspace
$ conda activate env_zipline
$ jupyter notebook

Jupyter debería abrirse en un navegador y verse como el siguiente. Deberá hacer clic en Nuevo y luego en Python 3 para crear un nuevo cuaderno.

Una vez que tenga un nuevo cuaderno abierto, podemos ingresar comandos en cada celda de Jupyter. Puede seguir el código a continuación o descargar mi cuaderno Jupyter si está familiarizado con Jupyter y desea acelerar las cosas.

Importar tirolesa

Lo primero que vamos a hacer es cargar zipline usando Jupyter %magic y luego importaremos zipline. Después de la segunda línea, presione shift enter, lo que ejecutará la celda en lugar de simplemente comenzar una nueva línea.

%load_ext zipline
import zipline

Ahora que hemos importado zipline, agreguemos las diversas bibliotecas y métodos que usaremos. Puede encontrar una lista completa de los métodos de tirolesa en el Referencia de la API de Zipline, Fecha y hora y pytz son necesarios para establecer fechas y horas para cuando nuestro algoritmo comienza y finaliza.

from zipline.api import order_target_percent, record, symbol, set_benchmark, get_open_orders
from datetime import datetime
import pytz

Zipline tiene dos funciones que necesitamos definir:

  • inicializar
  • manejar_datos
  • Inicializar tirolesa

    La inicialización se ejecuta una vez. La variable de contexto es obligatoria. El contexto es persistente y se puede usar en todo nuestro algoritmo, como verá pronto. También pasamos Apple a set_benchmark. Esto agregará una serie a nuestros resultados para que podamos comparar el rendimiento de nuestro algoritmo con nuestro punto de referencia seleccionado.

    def initialize(context):
            context.i = 0
            context.asset = symbol('AAPL')
            set_benchmark(symbol('AAPL'))

    Datos de la manija de la tirolesa

    Después de que nuestro algoritmo se haya inicializado, llamará a handle_data. Al definir handle_data, debemos pasarle la variable de contexto de arriba y los datos con los que trabajar. handle_data se llama una vez para cada evento, que definimos al llamar ejecutar_algoritmo. Usaremos los datos del identificador del ejemplo anterior, la mayoría de los cuales se toman del Inicio rápido de tirolesa.

    def handle_data(context, data):
            # Skip first 200 days to get full windows
            context.i += 1
            if context.i < 200:
                            return
            # Compute averages
            # data.history() has to be called with the same params
            # from above and returns a pandas dataframe.
            short_mavg = data.history(context.asset, 'price', bar_count=50, frequency="1d").mean()
            long_mavg = data.history(context.asset, 'price', bar_count=200, frequency="1d").mean()
    
            # Trading logic
            open_orders = get_open_orders()
    
            if context.asset not in open_orders:
                    if short_mavg > long_mavg:
                            # order_target orders as many shares as needed to
                            # achieve the desired number of shares.
                            order_target_percent(context.asset, 1.0)
                    elif short_mavg < long_mavg:
                            order_target_percent(context.asset, 0.0)
    
            # Save values for later inspection
            record(AAPL=data.current(context.asset, 'price'),
                            short_mavg=short_mavg,
                            long_mavg=long_mavg)

    Para calcular la media móvil de 200 días, necesitamos los 200 días anteriores. Es por eso que nos saltamos 200 días antes de calcular nuestros promedios móviles y ejecutar nuestro trading lógica. Además, debemos estar en el día 201 para calcular el promedio móvil de 200 días para trading ya que no sabríamos cuál es el precio de cierre de hoy. Finalmente, observe cómo estamos usando el contexto para guardar el número del día y mantiene su estado a través de cada llamada handle_data.

    Ahora que nos hemos saltado los primeros 200 días, calculemos los promedios móviles simples. Data.history devuelve una serie, marco de datos o panel de pandas según los datos que le pasemos. En nuestro caso, dado que estamos pasando un solo activo, obtendremos una serie y el método de la media devolverá un valor flotante del promedio móvil simple.

    # Compute averages
    # data.history() has to be called with the same params
    # from above and returns a pandas dataframe.
    short_mavg = data.history(context.asset, 'price', bar_count=50, frequency="1d").mean()
    long_mavg = data.history(context.asset, 'price', bar_count=200, frequency="1d").mean()

    Con nuestros promedios móviles, ahora podemos crear nuestro trading lógica. Si la media móvil de 50 días está por encima de la de 200 días, utilizaremos el 100 % de nuestro dinero para comprar Apple. Si la media móvil de 50 días cae por debajo de la de 200 días, venderemos todas nuestras acciones. Podemos pasar un flotante entre 1.0 y -1.0 donde un valor negativo indica que deseamos vender en corto la acción. Notará que antes de realizar un pedido, verifico si ya tenemos operaciones abiertas. Si no hago esto, podríamos realizar un pedido antes de que se complete nuestro pedido anterior, lo que nos haría comprar demasiadas acciones.

    # Trading logic
    open_orders = get_open_orders()
    
    if context.asset not in open_orders:
            if short_mavg > long_mavg:
                    # order_target orders as many shares as needed to
                    # achieve the desired number of shares.
                    order_target_percent(context.asset, 1.0)
            elif short_mavg < long_mavg:
                    order_target_percent(context.asset, 0.0)

    Necesitamos decirle a Zipline qué valores queremos para propósitos de análisis. A medida que avanzamos hacia conjuntos de datos más grandes, registrar cada valor simplemente no es razonable. Usamos la función de registro para realizar un seguimiento del precio de Apple y nuestros promedios móviles para cada día. Si está familiarizado con Python, la sintaxis puede parecer un poco extraña. AAPL no es una variable. Es la cadena de texto que le estamos diciendo al registro que use.

    # Save values for later inspection
    record(AAPL=data.current(context.asset, 'price'),
                    short_mavg=short_mavg,
                    long_mavg=long_mavg)

    Analizar el rendimiento

    Inicializamos nuestro algoritmo y definimos handle_data. Después de ejecutar handle_data, ordenará los valores y registrará los datos. Ahora es el momento de ejecutar Zipline y ver cómo se desempeñó nuestra estrategia. Podemos ejecutar Zipline de varias maneras. Puede agregar la siguiente magia en Jupyter para ejecutar Zipline.

    %%zipline --start 2000-1-1 --end 2017-12-31

    Podemos usar el método run_algorithm explícitamente. El método tiene muchas opciones, así que le sugiero que lea el referencia de la API run_algorithm. El método devolverá el rendimiento de nuestro algoritmo en un marco de datos.

    start = datetime(2000, 1, 1, 0, 0, 0, 0, pytz.utc)
    end = datetime(2017, 12, 31, 0, 0, 0, 0, pytz.utc)
    
    perf = zipline.run_algorithm(start=start,
                                    end=end,
                                    initialize=initialize,
                                    capital_base=10000,
                                    handle_data=handle_data)

    Analicemos el rendimiento de nuestro algoritmo usando Pyfolio. Importaremos pyfolio y numpy para poder usarlos. entonces usamos pf.utils.extract_rets_pos_txn_from_zipline y extraiga el benchmark_period_return para obtener los datos que necesitamos. Pyfolio requiere que todos nuestros datos estén en rendimientos de período y benchmark_period_return, que tiene un nombre deficiente, es en realidad un rendimiento de período acumulativo. Necesitamos convertir benchmark_period_return de un rendimiento acumulativo a un rendimiento de período. Profundicemos un poco más en esto, ya que es importante comprender cómo calcular los rendimientos.

    No puede simplemente restar las diferencias entre los rendimientos acumulados para obtener los rendimientos diarios a medida que se capitalizan. Por ejemplo, imagine un escenario en el que invertimos $ 1.00 y creció un 50 % el día uno y perdió un 50 % el día dos, creció un 50 % el día tres y perdió un 50 % el día cuatro. ¿Cuánto dinero nos quedaría?

    La respuesta no es $1.00 como se muestra aquí:

    $1,00 * (1+0,5) * (1-0,5) * (1+0,5) * (1-0,5) = $0,5625

    Los rendimientos acumulados serían 0,5625.

    Podemos lidiar con este problema y obtener rendimientos compuestos utilizando cualquiera de las fórmulas de conversión a continuación. En la primera fórmula, convertimos nuestros rendimientos en rendimientos logarítmicos para calcular la diferencia entre ellos y luego deshacemos la conversión utilizando la fórmula exponencial. En la segunda fórmula, que puede parecer más intuitiva para algunos, divide el segundo rendimiento acumulativo entre el primer rendimiento acumulativo y luego resta uno. Vea el siguiente ejemplo y tome nota de cómo obtenemos los retornos_diarios de los retornos_acumulativos.

    import pandas as pd
    
    # We need to be able to calculate the daily returns from the cumulative returns
    daily_returns = pd.Series([0.5, -0.5, 0.5, -0.5])
    cumulative_returns = pd.Series([0.5, -0.25, 0.125, 0.5625])
    
    # Two different formulas to calculate daily returns
    print((1 + cumulative_returns) / (1 + cumulative_returns.shift()) -1)
    print((np.exp(np.log(cumulative_returns + 1).diff()) - 1))
    
    # Recreate daily returns manually for example purposes
    print(daily_returns.head(1))
    print((1 - 0.25) / (1.5) - 1)
    print((1 + 0.125) / (1 - 0.25) - 1)
    print((1 + 0.5625) / (1 + 0.125 ) - 1)
    0         NaN
    1   -0.500000
    2    0.500000
    3    0.388889
    dtype: float64
    0         NaN
    1   -0.500000
    2    0.500000
    3    0.388889
    dtype: float64
    0    0.5
    dtype: float64
    -0.5
    0.5
    0.38888888888888884

    Una vez que tenemos los datos calculados correctamente, creamos la hoja de corte para analizar nuestro algoritmo.

    import pyfolio as pf
    import numpy as np
    
    # Extract algo returns and benchmark returns
    returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(perf)
    benchmark_period_return = perf['benchmark_period_return']
    
    # Convert benchmark returns to daily returns
    #daily_returns = (1 + benchmark_period_return) / (1 + benchmark_period_return.shift()) - 1
    daily_benchmark_returns = np.exp(np.log(benchmark_period_return + 1.0).diff()) - 1
    
    # Create tear sheet
    pf.create_full_tear_sheet(returns, positions=positions, transactions=transactions, benchmark_rets=daily_benchmark_returns)

    Como puede ver, Pyfolio genera mucha información para que podamos analizar nuestro algoritmo. 61Calmar ratio0.41Estabilidad0.70Reducción máxima-22.4%Relación Omega1.17Relación Sortino0.89Skew0.10Kurtosis11.86Ratio de cola1.08Valor diario en riesgo-2.1%Apalancamiento bruto1.00Rotación diaria0.7%Alfa0.02Beta0.42Peores períodos de reducciónReducción neta en %Fecha picoValley dateRecovery dateDuration022.452015-02-232015-08-24NaTNaN118.832012-10-162013-09-162013-11-29294212.322013-12-232014-01-302014-04-2590310.922014-11-262015-01-162015-02-045146.812014 -09-022014-10-162014-10-2338Eventos de estrésmeanminmaxEZB IR Event0.00%0.00%0.00%Abr140.46%-1.57%8.19%Oct140.31%-1.56%2.71%Fall2015-0.07%-6.11%5.73%Recovery -0.06%-6.20%6.93%Nueva normalidad0.07%-7.98%8.19%Las 10 posiciones largas más importantes de todos los tiemposmaxsidAAPL100.07%Las 10 posiciones cortas más importantes de todos los tiemposmaxsidLas 10 posiciones más importantes de todos los tiemposmaxsidAAPL100.07%

    Deja un comentario