Pruebas de retroceso basadas en eventos con Python - Parte IV

El autor:La bondad, Creado: 2019-03-25 14:24:46, Actualizado:

La discusión de la implementación de backtesting impulsada por eventos ha considerado previamente el bucle de eventos, la jerarquía de clases de eventos y el componente de manejo de datos.

Un objeto de Estrategia encapsula todos los cálculos en los datos de mercado que generan señales de asesoramiento a un objeto de cartera. En esta etapa en el desarrollo de backtester impulsado por eventos no hay concepto de un indicador o filtro, como los que se encuentran en el comercio técnico. Estos también son buenos candidatos para crear una jerarquía de clases, pero están más allá del alcance de este artículo.

La jerarquía de estrategia es relativamente simple ya que consiste en una clase base abstracta con un solo método virtual puro para generar objetos SignalEvent.

# strategy.py

import datetime
import numpy as np
import pandas as pd
import Queue

desde abc importar ABCMeta, método abstracto

desde la importación de eventos SignalEvent La clase base abstracta Strategy simplemente define un método virtual puro calculate_signals. En las clases derivadas se utiliza para manejar la generación de objetos SignalEvent basados en actualizaciones de datos de mercado:

# strategy.py

class Strategy(object):
    """
    Strategy is an abstract base class providing an interface for
    all subsequent (inherited) strategy handling objects.

    The goal of a (derived) Strategy object is to generate Signal
    objects for particular symbols based on the inputs of Bars 
    (OLHCVI) generated by a DataHandler object.

    This is designed to work both with historic and live data as
    the Strategy object is agnostic to the data source,
    since it obtains the bar tuples from a queue object.
    """

    __metaclass__ = ABCMeta

    @abstractmethod
    def calculate_signals(self):
        """
        Provides the mechanisms to calculate the list of signals.
        """
        raise NotImplementedError("Should implement calculate_signals()")

La definición de la Estrategia ABC es sencilla. Nuestro primer ejemplo de subclasificación del objeto Estrategia utiliza una estrategia de compra y retención para crear la clase BuyAndHoldStrategy. Esto simplemente se hace largo en un valor determinado en una fecha determinada y lo mantiene dentro de la cartera. Por lo tanto, solo se genera una señal por valor.

El constructor (Iniciar) requiere que el procesador de datos del mercado de barras y el objeto de la cola de eventos:

# strategy.py

class BuyAndHoldStrategy(Strategy):
    """
    This is an extremely simple strategy that goes LONG all of the 
    symbols as soon as a bar is received. It will never exit a position.

    It is primarily used as a testing mechanism for the Strategy class
    as well as a benchmark upon which to compare other strategies.
    """

    def __init__(self, bars, events):
        """
        Initialises the buy and hold strategy.

        Parameters:
        bars - The DataHandler object that provides bar information
        events - The Event Queue object.
        """
        self.bars = bars
        self.symbol_list = self.bars.symbol_list
        self.events = events

        # Once buy & hold signal is given, these are set to True
        self.bought = self._calculate_initial_bought()

Al iniciar la estrategia BuyAndHold, el miembro del diccionario comprado tiene un conjunto de claves para cada símbolo que están todas configuradas en False. Una vez que el activo ha sido longed, este se establece en True.

# strategy.py

    def _calculate_initial_bought(self):
        """
        Adds keys to the bought dictionary for all symbols
        and sets them to False.
        """
        bought = {}
        for s in self.symbol_list:
            bought[s] = False
        return bought

El método virtual puro calculate_signals se implementa concretamente en esta clase. El método recorre todos los símbolos de la lista de símbolos y recupera la última barra del manipulador de datos de barras. Luego verifica si ese símbolo ha sido comprado (es decir, si estamos en el mercado para este símbolo o no) y si no crea un solo objeto SignalEvent. Esto se coloca en la cola de eventos y el diccionario comprado se actualiza correctamente a True para esta clave de símbolo en particular:

# strategy.py

    def calculate_signals(self, event):
        """
        For "Buy and Hold" we generate a single signal per symbol
        and then no additional signals. This means we are 
        constantly long the market from the date of strategy
        initialisation.

        Parameters
        event - A MarketEvent object. 
        """
        if event.type == 'MARKET':
            for s in self.symbol_list:
                bars = self.bars.get_latest_bars(s, N=1)
                if bars is not None and bars != []:
                    if self.bought[s] == False:
                        # (Symbol, Datetime, Type = LONG, SHORT or EXIT)
                        signal = SignalEvent(bars[0][0], bars[0][1], 'LONG')
                        self.events.put(signal)
                        self.bought[s] = True

Esta es claramente una estrategia simple, pero es suficiente para demostrar la naturaleza de una jerarquía de estrategias impulsadas por eventos. En artículos posteriores consideraremos estrategias más sofisticadas como un comercio de pares.


Más.