Pruebas de retroceso basadas en eventos con Python - Parte II

El autor:La bondad, Creado: 2019-03-23 09:13:33, Actualizado:

En el último artículo describimos el concepto de un backtester impulsado por eventos. El resto de esta serie de artículos se concentrará en cada una de las jerarquías de clases separadas que componen el sistema general. En este artículo consideraremos los eventos y cómo se pueden usar para comunicar información entre objetos.

Como se discutió en el artículo anterior, el sistema de negociación utiliza dos bucles de tiempo - uno externo y uno interno. El bucle de tiempo interno maneja la captura de eventos de una cola en la memoria, que luego se dirigen al componente apropiado para la acción posterior.

  • MarketEvent - Esto se activa cuando el bucle while externo comienza un nuevo heartbeat. Ocurre cuando el objeto DataHandler recibe una nueva actualización de los datos del mercado para cualquier símbolo que se esté rastreando actualmente. Se utiliza para activar el objeto Estrategia que genera nuevas señales comerciales. El objeto de evento simplemente contiene una identificación de que es un evento de mercado, sin otra estructura.
  • SignalEvent - El objeto Estrategia utiliza datos de mercado para crear nuevos SignalEvents. El SignalEvent contiene un símbolo de ticker, una marca de tiempo para cuando se generó y una dirección (larga o corta). Los SignalEvents son utilizados por el objeto Cartera como asesoramiento sobre cómo operar.
  • OrderEvent - Cuando un objeto de cartera recibe SignalEvents, los evalúa en el contexto más amplio de la cartera, en términos de riesgo y tamaño de posición.
  • FillEvent - Cuando un ExecutionHandler recibe un OrderEvent, debe realizar la transacción. Una vez que se ha realizado una transacción, genera un FillEvent, que describe el costo de compra o venta, así como los costos de transacción, como tarifas o deslizamiento.

La clase madre se llama Event. Es una clase base y no proporciona ninguna funcionalidad o interfaz específica. En implementaciones posteriores los objetos Event probablemente desarrollarán una mayor complejidad y, por lo tanto, estamos a prueba del futuro el diseño de tales sistemas mediante la creación de una jerarquía de clases.

# event.py

class Event(object):
    """
    Event is base class providing an interface for all subsequent 
    (inherited) events, that will trigger further events in the 
    trading infrastructure.   
    """
    pass

El MarketEvent hereda de Event y proporciona poco más que una autoidentificación de que es un evento de tipo MARKET.

# event.py

class MarketEvent(Event):
    """
    Handles the event of receiving a new market update with 
    corresponding bars.
    """

    def __init__(self):
        """
        Initialises the MarketEvent.
        """
        self.type = 'MARKET'

Un evento de señal requiere un símbolo de ticker, una marca de tiempo para la generación y una dirección para informar a un objeto de cartera.

# event.py

class SignalEvent(Event):
    """
    Handles the event of sending a Signal from a Strategy object.
    This is received by a Portfolio object and acted upon.
    """
    
    def __init__(self, symbol, datetime, signal_type):
        """
        Initialises the SignalEvent.

        Parameters:
        symbol - The ticker symbol, e.g. 'GOOG'.
        datetime - The timestamp at which the signal was generated.
        signal_type - 'LONG' or 'SHORT'.
        """
        
        self.type = 'SIGNAL'
        self.symbol = symbol
        self.datetime = datetime
        self.signal_type = signal_type

El OrderEvent es ligeramente más complejo que un SignalEvent, ya que contiene un campo de cantidad además de las propiedades antes mencionadas de SignalEvent. La cantidad está determinada por las restricciones de la cartera. Además, el OrderEvent tiene un método print_order ((), utilizado para emitir la información a la consola si es necesario.

# event.py

class OrderEvent(Event):
    """
    Handles the event of sending an Order to an execution system.
    The order contains a symbol (e.g. GOOG), a type (market or limit),
    quantity and a direction.
    """

    def __init__(self, symbol, order_type, quantity, direction):
        """
        Initialises the order type, setting whether it is
        a Market order ('MKT') or Limit order ('LMT'), has
        a quantity (integral) and its direction ('BUY' or
        'SELL').

        Parameters:
        symbol - The instrument to trade.
        order_type - 'MKT' or 'LMT' for Market or Limit.
        quantity - Non-negative integer for quantity.
        direction - 'BUY' or 'SELL' for long or short.
        """
        
        self.type = 'ORDER'
        self.symbol = symbol
        self.order_type = order_type
        self.quantity = quantity
        self.direction = direction

    def print_order(self):
        """
        Outputs the values within the Order.
        """
        print "Order: Symbol=%s, Type=%s, Quantity=%s, Direction=%s" % \
            (self.symbol, self.order_type, self.quantity, self.direction)

El FillEvent es el Evento con mayor complejidad. Contiene una marca de tiempo para cuando se cumplió una orden, el símbolo de la orden y el intercambio en el que se ejecutó, la cantidad de acciones transactadas, el precio real de la compra y la comisión incurrida.

La comisión se calcula utilizando las comisiones de Interactive Brokers. Para las órdenes de API de EE.UU. esta comisión es de 1,30 USD mínimo por orden, con una tasa fija de 0,013 USD o 0,08 USD por acción dependiendo de si el tamaño de la operación es inferior o superior a 500 unidades de acciones.

# event.py

class FillEvent(Event):
    """
    Encapsulates the notion of a Filled Order, as returned
    from a brokerage. Stores the quantity of an instrument
    actually filled and at what price. In addition, stores
    the commission of the trade from the brokerage.
    """

    def __init__(self, timeindex, symbol, exchange, quantity, 
                 direction, fill_cost, commission=None):
        """
        Initialises the FillEvent object. Sets the symbol, exchange,
        quantity, direction, cost of fill and an optional 
        commission.

        If commission is not provided, the Fill object will
        calculate it based on the trade size and Interactive
        Brokers fees.

        Parameters:
        timeindex - The bar-resolution when the order was filled.
        symbol - The instrument which was filled.
        exchange - The exchange where the order was filled.
        quantity - The filled quantity.
        direction - The direction of fill ('BUY' or 'SELL')
        fill_cost - The holdings value in dollars.
        commission - An optional commission sent from IB.
        """
        
        self.type = 'FILL'
        self.timeindex = timeindex
        self.symbol = symbol
        self.exchange = exchange
        self.quantity = quantity
        self.direction = direction
        self.fill_cost = fill_cost

        # Calculate commission
        if commission is None:
            self.commission = self.calculate_ib_commission()
        else:
            self.commission = commission

    def calculate_ib_commission(self):
        """
        Calculates the fees of trading based on an Interactive
        Brokers fee structure for API, in USD.

        This does not include exchange or ECN fees.

        Based on "US API Directed Orders":
        https://www.interactivebrokers.com/en/index.php?f=commission&p=stocks2
        """
        full_cost = 1.3
        if self.quantity <= 500:
            full_cost = max(1.3, 0.013 * self.quantity)
        else: # Greater than 500
            full_cost = max(1.3, 0.008 * self.quantity)
        full_cost = min(full_cost, 0.5 / 100.0 * self.quantity * self.fill_cost)
        return full_cost

En el próximo artículo de la serie vamos a considerar cómo desarrollar una jerarquía de clases de mercado DataHandler que permita tanto backtesting histórico y comercio en vivo, a través de la misma interfaz de clase.


Más.