イベント駆動バックテスト Python - Part VIII

作者: リン・ハーン優しさ, 作成日:2019-03-26 16:38:59, 更新日:

この記事で議論を始めたイベント駆動バックテストを考慮してしばらく経ちました.第6部分では,歴史的なバックテスト状況で機能したスタンドインExecutionHandlerモデルをコードする方法について説明しました. この記事では,ライブ取引システムに向かって移動するために対応するインタラクティブブローカー API ハンドラーをコードします.

トレーダーワークステーションをダウンロードし,Interactive Brokers デモアカウントを作成する方法,IBPyを使用して IB API に基本的なインターフェースを作成する方法については,これまで議論してきました.本記事では,基本の IbPy インターフェースをイベント駆動システムにまとめ,ライブマーケットフィードとペア化すると,自動実行システムの基盤を形成します.

IBExecutionHandlerクラスの基本アイデアは,イベントキューからOrderEventインスタンスを受信し,その後,IBPyライブラリを使用してInteractive Brokersオーダー APIに対して直接実行することです.このクラスは,APIを通じて返信される"サーバーレスポンス"メッセージも処理します.この段階では,対応するFillEventインスタンスを作成するだけで,イベントキューに返信されます.

実行最適化論理と洗練されたエラー処理により,クラスはかなり複雑になり得ます.しかし,私はそれを比較的シンプルに保つことを選択しました. そうすれば,主なアイデアを見ることができ,あなたの特定の取引スタイルに合う方向に拡張することができます.

Python の実装

通常のように,最初のタスクは Python ファイルを作成し,必要なライブラリをインポートすることです.このファイルは ib_execution.py と呼ばれ,他のイベント駆動ファイルと同じディレクトリに存在します.

必要な日時処理ライブラリ, IbPy オブジェクト,IBExecutionHandler によって処理される特定のイベント オブジェクトをインポートします:

# 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

IBExecutionHandler のクラスでinitこのコンストラクタには,まずイベントキューの知識が必要です.また,order_routingの仕様も必要です.私はそれをSMARTにデフォルトで設定しました.特定の交換要件がある場合は,ここでそれらを指定できます.デフォルト通貨はUSドルにも設定されています.

この方法では, FillEvent インスタンスを生成するために後で使用するために必要な fill_dict 辞書を作成します.また,Interactive Brokers API に接続情報を格納するために tws_conn 接続オブジェクトを作成します.また,ダブルを避けるために,すべての次のコマンドを追跡する初期デフォルト order_id を作成する必要があります.最後に,メッセージハンドラーを登録します (以下より詳細に定義します):

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

IB API はメッセージベースのイベントシステムを利用し,私たちのクラスはイベント駆動バックテストと同じような方法で特定のメッセージに特定の方法で応答することを可能にします. _error_handler メソッドを通じてターミナルへの出力以外には,実際のエラー処理 (簡潔さのために) を含んでいません.

_reply_handler メソッドは, FillEvent インスタンスの作成が必要かどうかを決定するために使用されます.メソッドは, openOrder メッセージが受信されたかどうかを尋ね,この特定の orderId のfill_dict に既にエントリが設定されているかどうかをチェックします.そうでない場合は作成されます.

命令が完了したと示すメッセージが表示され, FillEvent を作成するために create_fill を呼び出します.また,ログ/デバッグ目的のために端末にメッセージを出力します:

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

以下の方法, create_tws_connection は, IbPy ibConnection オブジェクトを使用して IB API に接続を作成します. デフォルトポートは 7496 で,デフォルトクライアントID は 10 です. オブジェクトが作成されると,接続メソッドは接続を実行するために呼び出されます:

# 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

別々の注文を追跡するために (追跡フィール目的のために) 次の方法 create_initial_order_id を使用します.私はそれをデフォルトで 1 に設定しましたが,より洗練されたアプローチは,最新の利用可能な ID の IB 問い合わせであり,それを使用します.あなたは常にトレーダーワークステーション > グローバル構成 > API 設定パネルを通じて現在の API 注文 ID をリセットすることができます:

# 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

次のメソッド register_handlers は,単に TWS 接続で上記で定義されたエラーと応答処理メソッドを登録します.

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

IbPy を使う前のチュートリアルと同様に,契約インスタンスを作成し,それをオーダーインスタンスとペアにし,それをIB API に送信します.次の方法 create_contract は,このペアの最初のコンポーネントを生成します.これは,ティッカーシンボル,証券タイプ (例えばストックまたはフューチャー),取引所/プライマリ取引所および通貨を期待します.契約インスタンスを返します:

# 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

次の方法 create_order は,ペアの第2のコンポーネント,すなわちオーダーインスタンスを生成します. オーダータイプ (例えば市場または制限),取引する資産の量,およびアクション (購入または販売) を期待します. オーダーインスタンスを返します:

# 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

特定のオーダーIDの FillEvent インスタンスの重複を避けるために,特定のオーダーIDにマッチするキーを格納するために,fill_dict という辞書を使用します. Fill が生成された場合,特定のオーダーIDのエントリの filled キーが True に設定されます. IB から次の Server Response メッセージが受信され,オーダーが満たされている (そして重複メッセージである) というメッセージが受信されれば,新しいフィールにつながらない. 次の方法 create_fill_dict_entry はこれを実行します:

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

次の方法 create_fill で FillEvent インスタンスを作成し,イベントキューに配置します

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

前述のメソッドがすべて実装されているので,ExecutionHandler抽象ベースクラスからexecute_orderメソッドをオーバーライドする必要があります.このメソッドは実際にIB APIで注文配置を行います.

まず,このメソッドに受信されているイベントが実際にOrderEventであることを確認し,それぞれパラメータを持つContraktとOrderオブジェクトを準備します. 両方が作成されたら,接続オブジェクトのIbPyメソッドplaceOrderは,関連するorder_idで呼び出されます.

time.sleep ((1) 方法を呼び出すことが非常に重要です.この行を削除すると,少なくとも私のシステムでは,APIの不一致な振る舞いが起こります.

注文を重複しないようにします.

# 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

このクラスは,インタラクティブ・ブローカーズ実行ハンドラーの基礎を形成し,バックテストにのみ適したシミュレーション実行ハンドラーの代わりに使用することができる. IBハンドラーは,使用される前に,バックテストシステムの歴史的なデータフィードハンドラーの置き換えにライブマーケットフィードハンドラーを作成する必要があります.これは将来の記事のテーマになります.

この方法により,バックテストとライブシステムから可能な限り多くを再利用し,コードの"スワップアウト"を最小限に抑え,両方の動作が類似している,または同一でないことを保証します.


もっと