Kiểm tra ngược dựa trên sự kiện với Python - Phần VIII

Tác giả:Tốt, Tạo: 2019-03-26 16:38:59, Cập nhật:

Đã một thời gian kể từ khi chúng tôi xem xét backtester dựa trên sự kiện, mà chúng tôi bắt đầu thảo luận trong bài viết này. Trong Phần VI tôi đã mô tả cách mã hóa mô hình ExecutionHandler stand-in hoạt động cho tình huống backtesting lịch sử.

Trước đây tôi đã thảo luận về cách tải Trader Workstation và tạo tài khoản demo Interactive Brokers cũng như cách tạo giao diện cơ bản cho IB API bằng IbPy. Bài viết này sẽ gói giao diện IbPy cơ bản vào hệ thống điều khiển sự kiện, để khi được ghép nối với nguồn cấp dữ liệu thị trường trực tiếp, nó sẽ tạo thành cơ sở cho một hệ thống thực thi tự động.

Ý tưởng cơ bản của lớp IBExecutionHandler (xem bên dưới) là nhận các trường hợp OrderEvent từ hàng đợi sự kiện và sau đó thực thi chúng trực tiếp đối với API lệnh Interactive Brokers bằng thư viện IbPy.

Tuy nhiên, tôi đã chọn để giữ nó tương đối đơn giản để bạn có thể nhìn thấy các ý tưởng chính và mở rộng nó theo hướng phù hợp với phong cách giao dịch cụ thể của bạn.

Thực hiện Python

Như thường lệ, nhiệm vụ đầu tiên là tạo tập tin Python và nhập các thư viện cần thiết.

Chúng tôi nhập các thư viện xử lý ngày / giờ cần thiết, các đối tượng IbPy và các đối tượng sự kiện cụ thể được xử lý bởi 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

Bây giờ chúng ta xác định lớp IBExecutionHandler.initconstructor đầu tiên yêu cầu kiến thức về hàng đợi sự kiện. Nó cũng yêu cầu chỉ định của order_routing, mà tôi đã mặc định là SMART. Nếu bạn có yêu cầu trao đổi cụ thể, bạn có thể chỉ định chúng ở đây. Tiền tệ mặc định cũng đã được đặt thành Đô la Mỹ.

Trong phương thức này chúng ta tạo một từ điển fill_dict, cần thiết sau này để sử dụng trong việc tạo các trường hợp FillEvent. Chúng ta cũng tạo một đối tượng kết nối tws_conn để lưu trữ thông tin kết nối của chúng ta với API Interactive Brokers. Chúng ta cũng phải tạo một định dạng ban đầu order_id, theo dõi tất cả các lệnh tiếp theo để tránh trùng lặp. Cuối cùng chúng ta đăng ký các trình xử lý tin nhắn (mà chúng ta sẽ xác định chi tiết hơn dưới đây):

# 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 sử dụng một hệ thống sự kiện dựa trên tin nhắn cho phép lớp của chúng tôi phản ứng theo những cách cụ thể với một số tin nhắn nhất định, tương tự như chính backtester dựa trên sự kiện.

Phương pháp _reply_handler, mặt khác, được sử dụng để xác định xem một trường hợp FillEvent có cần được tạo ra hay không. Phương pháp hỏi liệu một thông báo openOrder đã được nhận và kiểm tra xem một mục trong fill_dict của chúng tôi cho orderId cụ thể này đã được đặt chưa. Nếu không thì một mục được tạo.

Nếu nó nhìn thấy một thông báo orderStatus và thông báo cụ thể đó cho biết một lệnh đã được hoàn thành, sau đó nó gọi create_fill để tạo FillEvent. Nó cũng xuất thông điệp đến thiết bị đầu cuối cho mục đích ghi nhật ký / gỡ lỗi:

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

Phương pháp sau, create_tws_connection, tạo kết nối với IB API bằng đối tượng IbPy ibConnection. Nó sử dụng cổng mặc định là 7496 và ID khách hàng mặc định là 10. Một khi đối tượng được tạo, phương thức kết nối được gọi để thực hiện kết nối:

# 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

Để theo dõi các đơn đặt hàng riêng biệt (với mục đích theo dõi điền) phương pháp sau create_initial_order_id được sử dụng. Tôi đã mặc định nó là 1, nhưng một cách tiếp cận phức tạp hơn sẽ là th truy vấn IB cho ID có sẵn mới nhất và sử dụng đó. Bạn luôn có thể đặt lại ID đơn đặt hàng API hiện tại thông qua Trader Workstation > Global Configuration > API Settings panel:

# 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

Phương pháp sau, register_handlers, chỉ đơn giản là đăng ký các phương thức xử lý lỗi và trả lời được xác định ở trên với kết nối 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)

Như với hướng dẫn trước về việc sử dụng IbPy, chúng ta cần tạo một trường hợp hợp đồng và sau đó ghép nối nó với một trường hợp Order, sẽ được gửi đến API IB. Phương pháp sau, create_contract, tạo thành phần đầu tiên của cặp này. Nó mong đợi một biểu tượng ticker, một loại chứng khoán (ví dụ cổ phiếu hoặc tương lai), một sàn giao dịch / sàn giao dịch chính và một loại tiền tệ. Nó trả về trường hợp hợp đồng:

# 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

Phương pháp sau, create_order, tạo thành phần thứ hai của cặp, cụ thể là trường hợp Order. Nó mong đợi một loại lệnh (ví dụ: thị trường hoặc giới hạn), một lượng tài sản để giao dịch và một action (mua hoặc bán). Nó trả về trường hợp Order:

# 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

Để tránh trùng lặp các trường hợp FillEvent cho một ID đơn đặt hàng cụ thể, chúng tôi sử dụng một từ điển gọi là fill_dict để lưu trữ các khóa phù hợp với các ID đơn đặt hàng cụ thể. Khi một fill đã được tạo ra, khóa filled của một mục nhập cho một ID đơn đặt hàng cụ thể được đặt thành True. Nếu một tin nhắn Server Response tiếp theo được nhận từ IB nói rằng một lệnh đã được điền (và là một tin nhắn trùng lặp) nó sẽ không dẫn đến một việc điền mới. Phương pháp sau create_fill_dict_entry thực hiện điều này:

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

Phương pháp sau, create_fill, thực sự tạo ra thực tế FillEvent và đặt nó vào hàng đợi sự kiện:

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

Bây giờ tất cả các phương thức trên đã được thực hiện, nó vẫn còn để ghi đè phương thức execute_order từ lớp cơ sở trừu tượng ExecutionHandler.

Trước tiên, chúng ta kiểm tra xem sự kiện được nhận vào phương thức này thực sự là một OrderEvent và sau đó chuẩn bị các đối tượng Contract và Order với các tham số tương ứng của chúng.

Nó là cực kỳ quan trọng để gọi phương thức time.sleep ((1) để đảm bảo lệnh thực sự đi qua IB. Việc loại bỏ dòng này dẫn đến hành vi không nhất quán của API, ít nhất trên hệ thống của tôi!

Cuối cùng, chúng ta tăng số ID đơn đặt hàng để đảm bảo chúng ta không trùng lặp các đơn đặt hàng:

# 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

Lớp này tạo thành cơ sở của trình xử lý thực thi Interactive Brokers và có thể được sử dụng thay cho trình xử lý thực thi mô phỏng, chỉ phù hợp với backtesting. Tuy nhiên, trước khi trình xử lý IB có thể được sử dụng, cần phải tạo một trình xử lý nguồn cấp dữ liệu thị trường trực tiếp để thay thế trình xử lý nguồn cấp dữ liệu lịch sử của hệ thống backtester. Đây sẽ là chủ đề của một bài viết trong tương lai.

Bằng cách này, chúng tôi đang tái sử dụng càng nhiều càng tốt từ các hệ thống backtest và live để đảm bảo rằng mã swap out được giảm thiểu và do đó hành vi trên cả hai là tương tự, nếu không giống hệt nhau.


Thêm nữa