파이썬으로 이벤트 기반 백테스팅 - 8부

저자:선함, 2019-03-26 16:38:59, 업데이트:

우리는 이 기사에서 논의하기 시작한 이벤트 구동 백테스터를 고려 한 지 얼마 되지 않았습니다. VI 부분에서는 역사적인 백테스팅 상황에 작동하는 스탠드 인 ExecutionHandler 모델을 코딩하는 방법을 설명했습니다. 이 기사에서는 실시간 거래 시스템으로 이동하기 위해 대응하는 인터랙티브 브로커 API 핸들러를 코딩 할 것입니다.

나는 이전에 트레이더 워크스테이션을 다운로드하고 인터랙티브 브로커 데모 계정을 생성하는 방법과 IbPy를 사용하여 IB API에 기본 인터페이스를 만드는 방법을 논의했습니다. 이 기사는 이벤트 주도 시스템으로 기본 IbPy 인터페이스를 포장하여 라이브 시장 피드와 결합하면 자동 실행 시스템의 기초를 형성 할 것입니다.

IBExecutionHandler 클래스의 본질적인 아이디어는 이벤트 큐에서 OrderEvent 인스턴스를 수신하고 IbPy 라이브러리를 사용하여 인터랙티브 브로커스 주문 API에 직접 실행하는 것입니다. 클래스는 API를 통해 다시 전송된 서버 응답 메시지를 처리할 것입니다. 이 단계에서 수행되는 유일한 작업은 해당 FillEvent 인스턴스를 생성하는 것입니다.

클래스 자체는 실행 최적화 논리뿐만 아니라 정교한 오류 처리와 함께 상당히 복잡해질 수 있습니다. 그러나, 나는 당신이 주요 아이디어를 볼 수 있도록 비교적 간단하게 유지하도록 선택했습니다. 그리고 당신의 특정 거래 스타일에 맞는 방향으로 확장합니다.

파이썬 구현

항상 그렇듯이 첫 번째 작업은 파이썬 파일을 만들고 필요한 라이브러리를 가져오는 것입니다. 이 파일은 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로 기본 설정되어 있습니다. 특정 교환 요구 사항이 있다면 여기에서 지정할 수 있습니다. 기본 통화는 또한 미국 달러로 설정되어 있습니다.

메소드 내에서 FillEvent 인스턴스를 생성하는 데 나중에 사용해야 할 fill_dict 사전을 만듭니다. 또한 인터랙티브 브로커 API에 연결 정보를 저장하기 위해 tws_conn 연결 객체를 만듭니다. 또한 중복을 피하기 위해 모든 후속 명령을 추적하는 초기 기본 명령어_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에 이미 항목이 설정되었는지 여부를 확인합니다. 그렇지 않으면 생성됩니다.

만약 orderStatus 메시지가 표시되고 특정 메시지가 주문이 완료되었다고 표시되면 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를 사용합니다. Ive 기본 설정 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는 쌍의 두 번째 구성 요소인 Order 인스턴스를 생성합니다. 명령 유형 (예를 들어 시장 또는 제한), 거래 할 자산의 양 및 action (구매 또는 판매) 을 기대합니다. 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

특정 주문 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인지 확인하고 그 다음 각각의 매개 변수와 함께 계약 및 Order 객체를 준비합니다. 둘 다 생성되면 연결 객체의 IbPy 메소드 placeOrder는 연관된 order_id로 호출됩니다.

이 라인의 제거는 API의 불일치 행동으로 이어집니다, 적어도 내 시스템에서!

마지막으로, 우리는 주문 ID를 증대하여 주문을 중복하지 않도록합니다.

# 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 핸들러를 사용할 수 있기 전에, 역 테스트 시스템의 역사적인 데이터 피드 핸들러를 대체하기 위해 라이브 시장 피드 핸들러를 만드는 것이 필요합니다. 이것은 미래의 기사의 주제입니다.

이 방법으로 우리는 백테스트와 라이브 시스템에서 가능한 한 많은 것을 재사용하여 코드 아웃을 최소화하고 따라서 양쪽의 행동은 동일하지 않은 경우도 비슷하다는 것을 보장합니다.


더 많은