Es ist schon eine Weile her, dass wir den Event-Driven-Backtester betrachtet haben, den wir in diesem Artikel diskutiert haben. In Teil VI habe ich beschrieben, wie man ein Stand-in-ExecutionHandler-Modell programmiert, das für eine historische Backtesting-Situation funktioniert. In diesem Artikel werden wir den entsprechenden Interactive Brokers API-Handler programmieren, um zu einem Live-Handelssystem zu gelangen.
Ich habe zuvor diskutiert, wie man Trader Workstation herunterlädt und ein Interactive Brokers Demo-Konto erstellt, sowie wie man eine grundlegende Schnittstelle zur IB API mit IbPy erstellt.
Die wesentliche Idee der IBExecutionHandler-Klasse (siehe unten) besteht darin, OrderEvent-Instanzen aus der Ereigniswarteschlange zu empfangen und sie dann direkt gegen die Interactive Brokers-Order-API mit der IbPy-Bibliothek auszuführen. Die Klasse wird auch die
Die Klasse selbst könnte möglicherweise ziemlich komplex werden, mit Ausführungsoptimierungslogik sowie anspruchsvoller Fehlerbearbeitung.
Wie immer besteht die erste Aufgabe darin, die Python-Datei zu erstellen und die notwendigen Bibliotheken zu importieren. Die Datei heißt ib_execution.py und befindet sich im gleichen Verzeichnis wie die anderen ereignisgesteuerten Dateien.
Wir importieren die notwendigen Date/Time-Handling-Bibliotheken, die IbPy-Objekte und die spezifischen Event-Objekte, die von IBExecutionHandler verarbeitet werden:
# ib_execution.py
import datetime
import time
from ib.ext.Contract import Contract
from ib.ext.Order import Order
from ib.opt import ibConnection, message
from event import FillEvent, OrderEvent
from execution import ExecutionHandler
Wir definieren nun die IBExecutionHandler Klasse.InitialisierungKonstruktor erfordert zunächst Kenntnis der Ereigniswarteschlange. Es erfordert auch die Spezifikation von order_routing, die ich standardmäßig auf
Innerhalb der Methode erstellen wir ein fill_dict-Wörterbuch, das später für die Verwendung bei der Generierung von FillEvent-Instanzen benötigt wird. Wir erstellen auch ein tws_conn-Verbindungsgegenstand, um unsere Verbindungsinformationen an die Interactive Brokers API zu speichern. Wir müssen auch eine anfängliche Standard-Order_id erstellen, die alle nachfolgenden Aufträge verfolgt, um Duplikate zu vermeiden. Schließlich registrieren wir die Nachrichtenbehandler (die wir unten detaillierter definieren):
# ib_execution.py
class IBExecutionHandler(ExecutionHandler):
"""
Handles order execution via the Interactive Brokers
API, for use against accounts when trading live
directly.
"""
def __init__(self, events,
order_routing="SMART",
currency="USD"):
"""
Initialises the IBExecutionHandler instance.
"""
self.events = events
self.order_routing = order_routing
self.currency = currency
self.fill_dict = {}
self.tws_conn = self.create_tws_connection()
self.order_id = self.create_initial_order_id()
self.register_handlers()
Die IB API nutzt ein nachrichtenbasiertes Ereignissystem, das es unserer Klasse ermöglicht, auf bestimmte Weise auf bestimmte Nachrichten zu reagieren, ähnlich wie der ereignisgesteuerte Backtester selbst.
Die _reply_handler-Methode hingegen wird verwendet, um festzustellen, ob eine FillEvent-Instanz erstellt werden muss. Die Methode fragt, ob eine
Wenn eine
# ib_execution.py
def _error_handler(self, msg):
"""
Handles the capturing of error messages
"""
# Currently no error handling.
print "Server Error: %s" % msg
def _reply_handler(self, msg):
"""
Handles of server replies
"""
# Handle open order orderId processing
if msg.typeName == "openOrder" and \
msg.orderId == self.order_id and \
not self.fill_dict.has_key(msg.orderId):
self.create_fill_dict_entry(msg)
# Handle Fills
if msg.typeName == "orderStatus" and \
msg.status == "Filled" and \
self.fill_dict[msg.orderId]["filled"] == False:
self.create_fill(msg)
print "Server Response: %s, %s\n" % (msg.typeName, msg)
Die folgende Methode, create_tws_connection, erstellt eine Verbindung zur IB-API mit dem Objekt IbPy ibConnection. Sie verwendet einen Standardport von 7496 und eine StandardclientId von 10. Sobald das Objekt erstellt wurde, wird die Connect-Methode zur Ausführung der Verbindung aufgerufen:
# ib_execution.py
def create_tws_connection(self):
"""
Connect to the Trader Workstation (TWS) running on the
usual port of 7496, with a clientId of 10.
The clientId is chosen by us and we will need
separate IDs for both the execution connection and
market data connection, if the latter is used elsewhere.
"""
tws_conn = ibConnection()
tws_conn.connect()
return tws_conn
Um getrennte Aufträge zu verfolgen (zur Verfolgung von Füllungen) wird die folgende Methode create_initial_order_id verwendet. Ich habe sie standardmäßig auf
# ib_execution.py
def create_initial_order_id(self):
"""
Creates the initial order ID used for Interactive
Brokers to keep track of submitted orders.
"""
# There is scope for more logic here, but we
# will use "1" as the default for now.
return 1
Die folgende Methode, register_handlers, registriert einfach die oben definierten Fehler- und Antwortbehandlungsmethoden mit der TWS-Verbindung:
# ib_execution.py
def register_handlers(self):
"""
Register the error and server reply
message handling functions.
"""
# Assign the error handling function defined above
# to the TWS connection
self.tws_conn.register(self._error_handler, 'Error')
# Assign all of the server reply messages to the
# reply_handler function defined above
self.tws_conn.registerAll(self._reply_handler)
Wie beim vorherigen Tutorial zum Gebrauch von IbPy müssen wir eine Contract-Instanz erstellen und dann mit einer Order-Instanz paaren, die an die IB API gesendet wird. Die folgende Methode, create_contract, generiert die erste Komponente dieses Paares. Es erwartet ein Tickersymbol, einen Wertpapiertyp (z. B. Aktien oder Futures), einen Austausch / primären Austausch und eine Währung. Es gibt die Contract-Instanz zurück:
# ib_execution.py
def create_contract(self, symbol, sec_type, exch, prim_exch, curr):
"""
Create a Contract object defining what will
be purchased, at which exchange and in which currency.
symbol - The ticker symbol for the contract
sec_type - The security type for the contract ('STK' is 'stock')
exch - The exchange to carry out the contract on
prim_exch - The primary exchange to carry out the contract on
curr - The currency in which to purchase the contract
"""
contract = Contract()
contract.m_symbol = symbol
contract.m_secType = sec_type
contract.m_exchange = exch
contract.m_primaryExch = prim_exch
contract.m_currency = curr
return contract
Die folgende Methode, create_order, erzeugt die zweite Komponente des Paares, nämlich die Order-Instanz. Sie erwartet einen Auftragstyp (z. B. Markt oder Limit), eine Menge des zu handelnden Vermögenswerts und eine
# ib_execution.py
def create_order(self, order_type, quantity, action):
"""
Create an Order object (Market/Limit) to go long/short.
order_type - 'MKT', 'LMT' for Market or Limit orders
quantity - Integral number of assets to order
action - 'BUY' or 'SELL'
"""
order = Order()
order.m_orderType = order_type
order.m_totalQuantity = quantity
order.m_action = action
return order
Um das Duplizieren von FillEvent-Instanzen für eine bestimmte Auftrags-ID zu vermeiden, verwenden wir ein Wörterbuch namens fill_dict, um Schlüssel zu speichern, die mit bestimmten Auftrags-IDs übereinstimmen. Wenn ein Füll erzeugt wurde, wird der
# ib_execution.py
def create_fill_dict_entry(self, msg):
"""
Creates an entry in the Fill Dictionary that lists
orderIds and provides security information. This is
needed for the event-driven behaviour of the IB
server message behaviour.
"""
self.fill_dict[msg.orderId] = {
"symbol": msg.contract.m_symbol,
"exchange": msg.contract.m_exchange,
"direction": msg.order.m_action,
"filled": False
}
Die folgende Methode, create_fill, erstellt tatsächlich die FillEvent-Instanz und platziert sie in der Ereigniswarteschlange:
# ib_execution.py
def create_fill(self, msg):
"""
Handles the creation of the FillEvent that will be
placed onto the events queue subsequent to an order
being filled.
"""
fd = self.fill_dict[msg.orderId]
# Prepare the fill data
symbol = fd["symbol"]
exchange = fd["exchange"]
filled = msg.filled
direction = fd["direction"]
fill_cost = msg.avgFillPrice
# Create a fill event object
fill = FillEvent(
datetime.datetime.utcnow(), symbol,
exchange, filled, direction, fill_cost
)
# Make sure that multiple messages don't create
# additional fills.
self.fill_dict[msg.orderId]["filled"] = True
# Place the fill event onto the event queue
self.events.put(fill_event)
Nun, da alle vorhergehenden Methoden implementiert wurden, bleibt es, die execute_order-Methode von der abstrakten Basisklasse ExecutionHandler zu überschreiben.
Wir überprüfen zunächst, ob das Ereignis, das dieser Methode zugegangen ist, tatsächlich ein OrderEvent ist, und bereiten dann die Objekte Contract und Order mit ihren jeweiligen Parametern vor. Sobald beide erstellt sind, wird die IbPy-Methode placeOrder des Verbindungsobjekts mit einer zugehörigen order_id aufgerufen.
Es ist äußerst wichtig, die time.sleep(1) Methode anzurufen, um sicherzustellen, dass die Bestellung tatsächlich an IB weitergeleitet wird.
Schließlich erhöhen wir die Order-ID, um sicherzustellen, dass wir keine doppelten Aufträge:
# ib_execution.py
def execute_order(self, event):
"""
Creates the necessary InteractiveBrokers order object
and submits it to IB via their API.
The results are then queried in order to generate a
corresponding Fill object, which is placed back on
the event queue.
Parameters:
event - Contains an Event object with order information.
"""
if event.type == 'ORDER':
# Prepare the parameters for the asset order
asset = event.symbol
asset_type = "STK"
order_type = event.order_type
quantity = event.quantity
direction = event.direction
# Create the Interactive Brokers contract via the
# passed Order event
ib_contract = self.create_contract(
asset, asset_type, self.order_routing,
self.order_routing, self.currency
)
# Create the Interactive Brokers order via the
# passed Order event
ib_order = self.create_order(
order_type, quantity, direction
)
# Use the connection to the send the order to IB
self.tws_conn.placeOrder(
self.order_id, ib_contract, ib_order
)
# NOTE: This following line is crucial.
# It ensures the order goes through!
time.sleep(1)
# Increment the order ID for this session
self.order_id += 1
Diese Klasse bildet die Basis eines Interactive Brokers-Ausführungshandlers und kann anstelle des simulierten Ausführungshandlers verwendet werden, der nur für Backtesting geeignet ist. Bevor der IB-Handler jedoch verwendet werden kann, muss ein Live-Market-Feed-Handler erstellt werden, um den historischen Datenfeed-Handler des Backtester-Systems zu ersetzen. Dies wird Gegenstand eines zukünftigen Artikels sein.
Auf diese Weise verwenden wir so viel wie möglich aus den Backtest- und Live-Systemen, um sicherzustellen, dass der Code