পাইথনের সাথে ইভেন্ট-চালিত ব্যাকটেস্টিং - অংশ VIII

লেখক:ভাল, তৈরিঃ 2019-03-26 16:38:59, আপডেটঃ

ইভেন্ট-চালিত ব্যাকটেস্টার নিয়ে আমরা আলোচনা শুরু করার পর থেকে কিছু সময় হয়ে গেছে। এই নিবন্ধে আমরা আলোচনা শুরু করেছি। ষষ্ঠ অংশে আমি একটি স্ট্যান্ড-ইন এক্সিকিউশন হ্যান্ডলার মডেল কীভাবে কোড করব তা বর্ণনা করেছি যা একটি historicalতিহাসিক ব্যাকটেস্টিং পরিস্থিতিতে কাজ করেছিল। এই নিবন্ধে আমরা একটি লাইভ ট্রেডিং সিস্টেমের দিকে এগিয়ে যাওয়ার জন্য সংশ্লিষ্ট ইন্টারেক্টিভ ব্রোকার্স এপিআই হ্যান্ডলার কোড করব।

আমি পূর্বে ট্রেডার ওয়ার্কস্টেশন ডাউনলোড এবং একটি ইন্টারেক্টিভ ব্রোকার্স ডেমো অ্যাকাউন্ট তৈরি করার পাশাপাশি IbPy ব্যবহার করে IB API এর একটি মৌলিক ইন্টারফেস তৈরি করার বিষয়ে আলোচনা করেছি। এই নিবন্ধটি ইভেন্ট-চালিত সিস্টেমে মৌলিক IbPy ইন্টারফেসটি আবৃত করবে, যাতে এটি যখন লাইভ মার্কেট ফিডের সাথে জুটিবদ্ধ হয়, তখন এটি একটি স্বয়ংক্রিয় এক্সিকিউশন সিস্টেমের ভিত্তি গঠন করবে।

IBExecutionHandler শ্রেণীর মূল ধারণা (নীচে দেখুন) হল ইভেন্ট সারি থেকে অর্ডার ইভেন্ট ইনস্ট্যান্স গ্রহণ করা এবং তারপরে IbPy লাইব্রেরি ব্যবহার করে ইন্টারেক্টিভ ব্রোকার্স অর্ডার এপিআই এর বিরুদ্ধে সরাসরি তাদের কার্যকর করা। ক্লাসটি এপিআই এর মাধ্যমে ফিরে প্রেরিত সার্ভার প্রতিক্রিয়া বার্তাগুলিও পরিচালনা করবে। এই পর্যায়ে, একমাত্র পদক্ষেপটি সংশ্লিষ্ট 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 ক্লাস সংজ্ঞায়িত।সূচনাকনস্ট্রাক্টর প্রথমত ইভেন্টের সারি সম্পর্কে জ্ঞান প্রয়োজন। এটি অর্ডার_রুটিংয়ের স্পেসিফিকেশনও প্রয়োজন, যা আমি SMART এ ডিফল্ট করেছি। আপনার যদি নির্দিষ্ট এক্সচেঞ্জের প্রয়োজনীয়তা থাকে তবে আপনি এগুলি এখানে নির্দিষ্ট করতে পারেন। ডিফল্ট মুদ্রাটিও মার্কিন ডলারে সেট করা হয়েছে।

পদ্ধতির মধ্যে আমরা একটি fill_dict অভিধান তৈরি করি, যা পরে FillEvent উদাহরণ তৈরিতে ব্যবহারের জন্য প্রয়োজন। আমরা ইন্টারেক্টিভ ব্রোকার এপিআইতে আমাদের সংযোগ তথ্য সংরক্ষণ করতে একটি tws_conn সংযোগ অবজেক্টও তৈরি করি। আমাদের একটি প্রাথমিক ডিফল্ট অর্ডার_আইডিও তৈরি করতে হবে, যা সদৃশতা এড়াতে পরবর্তী সমস্ত আদেশের ট্র্যাক রাখে। অবশেষে আমরা বার্তা হ্যান্ডলারগুলি নিবন্ধন করি (যা আমরা নীচে আরও বিশদভাবে সংজ্ঞায়িত করব):

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

আইবি এপিআই একটি বার্তা ভিত্তিক ইভেন্ট সিস্টেম ব্যবহার করে যা আমাদের ক্লাসকে নির্দিষ্ট বার্তাগুলিতে নির্দিষ্ট উপায়ে প্রতিক্রিয়া জানাতে দেয়, ইভেন্ট-চালিত ব্যাকটেস্টারের মতোই। আমি _error_handler পদ্ধতির মাধ্যমে টার্মিনালে আউটপুটের বাইরে (সংক্ষিপ্ততার উদ্দেশ্যে) কোনও বাস্তব ত্রুটি হ্যান্ডলিং অন্তর্ভুক্ত করি নি।

_reply_handler পদ্ধতি, অন্যদিকে, একটি FillEvent ইনস্ট্যান্স তৈরি করা প্রয়োজন কিনা তা নির্ধারণ করতে ব্যবহৃত হয়। পদ্ধতিটি জিজ্ঞাসা করে যে একটি openOrder বার্তা প্রাপ্ত হয়েছে কিনা এবং এই নির্দিষ্ট অর্ডারআইডি জন্য আমাদের 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 এর ডিফল্ট পোর্ট এবং 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 এ রেখেছি, তবে একটি আরও পরিশীলিত পদ্ধতি হ'ল সর্বশেষ উপলব্ধ আইডির জন্য আইবি অনুসন্ধান করা এবং এটি ব্যবহার করা। আপনি সর্বদা ট্রেডার ওয়ার্কস্টেশন > গ্লোবাল কনফিগারেশন > এপিআই সেটিংস প্যানেলের মাধ্যমে বর্তমান এপিআই অর্ডার আইডি পুনরায় সেট করতে পারেনঃ

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

আইবিপি ব্যবহারের পূর্ববর্তী টিউটোরিয়ালে যেমনটি হয়েছিল, আমাদের একটি চুক্তি উদাহরণ তৈরি করতে হবে এবং তারপরে এটি একটি অর্ডার উদাহরণের সাথে যুক্ত করতে হবে, যা আইবি এপিআইতে প্রেরণ করা হবে। নিম্নলিখিত পদ্ধতি, 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, জোড়ার দ্বিতীয় উপাদান, যথা অর্ডার ইনস্ট্যান্স তৈরি করে। এটি একটি অর্ডার প্রকার (যেমন বাজার বা সীমা), বাণিজ্য করার জন্য সম্পদের পরিমাণ এবং একটি action (ক্রয় বা বিক্রয়) প্রত্যাশা করে। এটি অর্ডার ইনস্ট্যান্স ফেরত দেয়ঃ

# 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

একটি নির্দিষ্ট অর্ডার আইডির জন্য ফিলইভেন্ট ইনস্ট্যান্সের সদৃশতা এড়ানোর জন্য, আমরা নির্দিষ্ট অর্ডার আইডিগুলির সাথে মেলে এমন কীগুলি সঞ্চয় করতে fill_dict নামে একটি অভিধান ব্যবহার করি। যখন একটি ফিল তৈরি করা হয় তখন একটি নির্দিষ্ট অর্ডার আইডির জন্য একটি এন্ট্রিটির filled কীটি সত্য হিসাবে সেট করা হয়। যদি পরবর্তী 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 পদ্ধতিটি ওভাররাইড করতে থাকে। এই পদ্ধতিটি আসলে আইবি এপিআই দিয়ে অর্ডার স্থাপন করে।

আমরা প্রথমে এই পদ্ধতিতে প্রাপ্ত ইভেন্টটি আসলে একটি অর্ডার ইভেন্ট কিনা তা পরীক্ষা করি এবং তারপরে তাদের নিজ নিজ পরামিতি সহ চুক্তি এবং অর্ডার অবজেক্টগুলি প্রস্তুত করি। একবার উভয়ই তৈরি হয়ে গেলে সংযোগ অবজেক্টের IbPy পদ্ধতি placeOrder একটি সম্পর্কিত order_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

এই ক্লাসটি ইন্টারেক্টিভ ব্রোকার্স এক্সিকিউশন হ্যান্ডলারের ভিত্তি গঠন করে এবং সিমুলেটেড এক্সিকিউশন হ্যান্ডলারের পরিবর্তে ব্যবহার করা যেতে পারে, যা কেবল ব্যাকটেস্টিংয়ের জন্য উপযুক্ত। তবে, আইবি হ্যান্ডলারের ব্যবহারের আগে, ব্যাকটেস্টার সিস্টেমের historicalতিহাসিক ডেটা ফিড হ্যান্ডলারের প্রতিস্থাপনের জন্য একটি লাইভ মার্কেট ফিড হ্যান্ডলার তৈরি করা প্রয়োজন। এটি ভবিষ্যতের একটি নিবন্ধের বিষয় হবে।

এই ভাবে আমরা ব্যাকটেস্ট এবং লাইভ সিস্টেম থেকে যতটা সম্ভব পুনরায় ব্যবহার করছি যাতে কোড swap out কমিয়ে আনা যায় এবং এইভাবে উভয় আচরণ একই, যদি না একই হয়।


আরো