Ereignisgesteuertes Backtesting mit Python - Teil II

Schriftsteller:Gutes, Erstellt: 2019-03-23 09:13:33, aktualisiert:

Im letzten Artikel beschrieben wir das Konzept eines ereignisgesteuerten Backtesters. Der Rest dieser Artikelserie konzentriert sich auf jede der einzelnen Klassenhierarchien, aus denen das Gesamtsystem besteht. In diesem Artikel werden wir Ereignisse und wie sie verwendet werden können, um Informationen zwischen Objekten zu kommunizieren, betrachten.

Wie im vorherigen Artikel erläutert, verwendet das Handelssystem zwei While-Schleifen - eine äußere und eine innere. Die innere While-Schleife verarbeitet die Erfassung von Ereignissen aus einer In-Memory-Warteschlange, die dann zur anschließenden Aktion an die entsprechende Komponente weitergeleitet werden.

  • MarketEvent - Dies wird ausgelöst, wenn die äußere While-Schleife einen neuen heartbeat beginnt. Es tritt auf, wenn das DataHandler-Objekt eine neue Aktualisierung der Marktdaten für Symbole erhält, die derzeit verfolgt werden. Es wird verwendet, um das Strategie-Objekt auszulösen, das neue Handelssignale generiert. Das Ereignisobjekt enthält einfach eine Identifizierung, dass es sich um ein Marktereignis handelt, ohne andere Struktur.
  • SignalEvent - Das Objekt Strategie nutzt Marktdaten, um neue SignalEvents zu erstellen. Das SignalEvent enthält ein Tickersymbol, einen Zeitstempel für den Zeitpunkt seiner Erstellung und eine Richtung (lang oder kurz).
  • OrderEvent - Wenn ein Portfolioobjekt SignalEvents empfängt, bewertet es sie im breiteren Kontext des Portfolios in Bezug auf Risiko und Positionsgröße. Dies führt schließlich zu OrderEvents, die an einen ExecutionHandler gesendet werden.
  • FillEvent - Wenn ein ExecutionHandler ein OrderEvent empfängt, muss er den Auftrag ausführen.

Die übergeordnete Klasse heißt Event. Sie ist eine Basisklasse und bietet keine Funktionalität oder spezifische Schnittstelle. In späteren Implementierungen werden die Event-Objekte wahrscheinlich eine größere Komplexität entwickeln und somit werden wir das Design solcher Systeme durch die Erstellung einer Klassenhierarchie zukunftssicher machen.

# 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

Das MarketEvent erbt von Event und liefert wenig mehr als eine Selbstidentifikation, dass es sich um ein MARKET-Typ-Event handelt.

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

Ein SignalEvent benötigt ein Tickersymbol, einen Zeitstempel für die Generierung und eine Richtung, um ein Portfolio-Objekt zu informieren.

# 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

Der OrderEvent ist etwas komplexer als ein SignalEvent, da er zusätzlich zu den oben genannten Eigenschaften von SignalEvent ein Quantity-Feld enthält. Die Menge wird durch die Portfolio-Einschränkungen bestimmt. Darüber hinaus verfügt der OrderEvent über eine Print_order() -Methode, die bei Bedarf zur Ausgabe der Informationen an die Konsole verwendet wird.

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

Das FillEvent ist das Ereignis mit der größten Komplexität. Es enthält einen Zeitstempel für den Zeitpunkt, an dem eine Bestellung ausgeführt wurde, das Symbol der Bestellung und den Austausch, an dem sie ausgeführt wurde, die Menge der gehandelten Aktien, den tatsächlichen Kaufpreis und die entstandene Provision.

Die Provision wird unter Verwendung der Interactive Brokers Provisionen berechnet. Für US API-Bestellungen beträgt diese Provision mindestens 1,30 USD pro Bestellung, mit einem Pauschalsatz von entweder 0,013 USD oder 0,08 USD pro Aktie, je nachdem, ob die Handelsgröße unter oder über 500 Stück liegt.

# 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

In dem nächsten Artikel der Serie werden wir prüfen, wie eine Markt-DataHandler-Klassenhierarchie entwickelt werden kann, die sowohl historische Backtesting als auch Live-Handel über die gleiche Klassenoberfläche ermöglicht.


Mehr