Tests arrière basés sur des événements avec Python - Partie II

Auteur:La bonté, Créé: 2019-03-23 09:13:33, mis à jour:

Dans l'article précédent, nous avons décrit le concept d'un backtester axé sur les événements. Le reste de cette série d'articles se concentrera sur chacune des hiérarchies de classes distinctes qui composent le système global.

Comme indiqué dans l'article précédent, le système de trading utilise deux boucles while - une externe et une interne.

  • MarketEvent - Ceci est déclenché lorsque la boucle while externe commence un nouveau heartbeat. Cela se produit lorsque l'objet DataHandler reçoit une nouvelle mise à jour des données du marché pour tous les symboles actuellement suivis. Il est utilisé pour déclencher l'objet Strategy générant de nouveaux signaux de trading. L'objet événement contient simplement une identification qu'il s'agit d'un événement de marché, sans autre structure.
  • SignalEvent - L'objet Stratégie utilise les données du marché pour créer de nouveaux SignalEvents. Le SignalEvent contient un symbole de ticker, un horodatage pour le moment où il a été généré et une direction (longue ou courte). Les SignalEvents sont utilisés par l'objet Portfolio comme conseils sur la façon de trader.
  • OrderEvent - Lorsqu'un objet de portefeuille reçoit SignalEvents, il les évalue dans le contexte plus large du portefeuille, en termes de risque et de dimensionnement des positions.
  • FillEvent - Lorsqu'un ExecutionHandler reçoit un OrderEvent, il doit effectuer l'ordre. Une fois qu'un ordre a été effectué, il génère un FillEvent, qui décrit le coût d'achat ou de vente ainsi que les coûts de transaction, tels que les frais ou le glissement.

La classe mère s'appelle Event. C'est une classe de base et ne fournit aucune fonctionnalité ou interface spécifique. Dans les implémentations ultérieures, les objets Event développeront probablement une plus grande complexité et nous protégeons ainsi la conception de tels systèmes en créant une hiérarchie 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

L'événement MarketEvent hérite de l'événement et fournit peu plus qu'une auto-identification qu'il s'agit d'un événement de type 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 événement SignalEvent nécessite un symbole de ticker, un horodatage pour sa génération et une direction pour informer un objet Portfolio.

# 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

L'OrdreEvent est légèrement plus complexe qu'un SignalEvent car il contient un champ de quantité en plus des propriétés susmentionnées de SignalEvent. La quantité est déterminée par les contraintes de portefeuille.

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

L'événement FillEvent est l'événement le plus complexe. Il contient un horodatage du moment où un ordre a été exécuté, le symbole de l'ordre et l'échange sur lequel il a été exécuté, la quantité d'actions négociées, le prix réel de l'achat et la commission engagée.

La commission est calculée en utilisant les commissions des Interactive Brokers. Pour les commandes US API, cette commission est de 1,30 USD minimum par commande, avec un taux forfaitaire de 0,013 USD ou 0,08 USD par action selon que la taille de la transaction est inférieure ou supérieure à 500 unités d'actions.

# 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

Dans le prochain article de la série, nous allons examiner comment développer une hiérarchie de classes DataHandler de marché qui permet à la fois le backtesting historique et le trading en direct, via la même interface de classe.


Plus de