Backtesting orientado por eventos com Python - Parte II

Autora:Bem-estar, Criado: 2019-03-23 09:13:33, Atualizado:

No último artigo, descrevemos o conceito de um backtester orientado por eventos. O restante desta série de artigos se concentrará em cada uma das hierarquias de classes separadas que compõem o sistema geral.

Como discutido no artigo anterior, o sistema de negociação utiliza dois loops while - um externo e um interno. O loop while interno lida com a captura de eventos de uma fila na memória, que são então encaminhados para o componente apropriado para ação subsequente.

  • MarketEvent - Isso é acionado quando o loop while externo começa um novo heartbeat. Isso ocorre quando o objeto DataHandler recebe uma nova atualização de dados de mercado para quaisquer símbolos que estão sendo rastreados atualmente. Ele é usado para acionar o objeto Estratégia gerando novos sinais de negociação. O objeto evento contém simplesmente uma identificação de que é um evento de mercado, sem outra estrutura.
  • SignalEvent - O objeto Strategy utiliza dados de mercado para criar novos SignalEvents. O SignalEvent contém um símbolo de ticker, um carimbo de hora para quando foi gerado e uma direção (longa ou curta). Os SignalEvents são utilizados pelo objeto Portfolio como conselhos sobre como negociar.
  • OrderEvent - Quando um objeto de carteira recebe SignalEvents, ele os avalia no contexto mais amplo da carteira, em termos de risco e dimensionamento de posição.
  • FillEvent - Quando um ExecutionHandler recebe um OrderEvent, ele deve transacionar a ordem. Uma vez que uma ordem foi transacionada, ele gera um FillEvent, que descreve o custo de compra ou venda, bem como os custos da transação, como taxas ou deslizamento.

A classe pai é chamada Event. É uma classe base e não fornece nenhuma funcionalidade ou interface específica. Em implementações posteriores, os objetos Event provavelmente desenvolverão maior complexidade e, portanto, estamos protegendo o projeto de tais sistemas criando uma hierarquia de classes.

# 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

O MarketEvent herda do Event e fornece pouco mais do que uma autoidentificação de que é um 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'

Um evento de sinal requer um símbolo de ticker, um carimbo de tempo para geração e uma direção para informar um objeto de carteira.

# 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

O OrderEvent é um pouco mais complexo do que um SignalEvent, pois contém um campo de quantidade além das propriedades acima mencionadas do SignalEvent. A quantidade é determinada pelas restrições do Portfólio. Além disso, o OrderEvent tem um método print_order ((), usado para expor as informações para o console, se necessário.

# 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)

O FillEvent é o Evento com a maior complexidade. Ele contém um carimbo de tempo para quando uma ordem foi preenchida, o símbolo da ordem e da troca em que foi executada, a quantidade de ações transacionadas, o preço real da compra e a comissão incorrida.

A comissão é calculada usando as comissões dos Interactive Brokers. Para ordens da API dos EUA, esta comissão é de 1,30 USD mínimo por ordem, com uma taxa fixa de 0,013 USD ou 0,08 USD por ação, dependendo do tamanho do comércio ser inferior ou superior a 500 unidades de ações.

# 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

No próximo artigo da série vamos considerar como desenvolver uma hierarquia de classes DataHandler de mercado que permita tanto backtesting histórico quanto negociação ao vivo, através da mesma interface de classe.


Mais.