Event-Driven Backtesting dengan Python - Bagian III

Penulis:Kebaikan, Dibuat: 2019-03-23 11:22:28, Diperbarui:

Dalam dua artikel sebelumnya dari seri ini kami membahas apa sistem backtesting event-driven dan hierarki kelas untuk objek Event.

Salah satu tujuan kami dengan sistem perdagangan yang didorong oleh acara adalah untuk meminimalkan duplikasi kode antara elemen backtesting dan elemen eksekusi langsung. Idealnya, akan optimal untuk menggunakan metodologi generasi sinyal dan komponen manajemen portofolio yang sama untuk pengujian historis dan perdagangan langsung. Untuk ini bekerja objek Strategi yang menghasilkan Sinyal, dan objek Portofolio yang menyediakan Order berdasarkan mereka, harus menggunakan antarmuka yang identik dengan umpan pasar untuk berjalan historis dan langsung.

Hal ini memotivasi konsep hierarki kelas berdasarkan objek DataHandler, yang memberikan semua subkelas antarmuka untuk menyediakan data pasar ke komponen yang tersisa dalam sistem. Dengan cara ini setiap subkelas data handler dapat swapped out, tanpa mempengaruhi strategi atau perhitungan portofolio.

Subkelas contoh spesifik dapat mencakup HistoricCSVDataHandler, QuandlDataHandler, SecuritiesMasterDataHandler, InteractiveBrokersMarketFeedDataHandler dll. Dalam tutorial ini kita hanya akan mempertimbangkan pembuatan pengendali data CSV historis, yang akan memuat data CSV intraday untuk ekuitas dalam set bar Open-Low-High-Close-Volume-OpenInterest. Ini kemudian dapat digunakan untuk drip feed pada basis bar-by-bar data ke dalam kelas Strategi dan Portfolio pada setiap detak jantung sistem, sehingga menghindari bias lookahead.

Tugas pertama adalah untuk mengimpor perpustakaan yang diperlukan. khususnya kita akan mengimpor panda dan abstrak alat kelas dasar. karena DataHandler menghasilkan MarketEvents kita juga perlu mengimporevent.pyseperti yang dijelaskan dalam tutorial sebelumnya:

# data.py

import datetime
import os, os.path
import pandas as pd

dari abc import ABCMeta, metode abstrak

dari impor acara MarketEvent DataHandler adalah kelas dasar abstrak (ABC), yang berarti bahwa tidak mungkin untuk menginstansikan sebuah instance secara langsung. Hanya subclass yang dapat diinstansikan. Alasan untuk ini adalah bahwa ABC menyediakan antarmuka yang harus diikuti oleh semua subclass DataHandler berikutnya sehingga memastikan kompatibilitas dengan kelas lain yang berkomunikasi dengan mereka.

Kami memanfaatkanMetakelasproperti untuk membiarkan Python tahu bahwa ini adalah ABC. Selain itu kita menggunakan dekorator @abstractmethod untuk membiarkan Python tahu bahwa metode akan ditimpa dalam subkelas (ini identik dengan metode virtual murni di C ++).

Kedua metode yang menarik adalah get_latest_bars dan update_bars. Yang pertama mengembalikan N bar terakhir dari timestamp detak jantung saat ini, yang berguna untuk perhitungan bergulir yang diperlukan dalam kelas Strategi. Metode terakhir menyediakan mekanisme drip feed untuk menempatkan informasi bar pada struktur data baru yang sangat melarang bias lookahead. Perhatikan bahwa pengecualian akan muncul jika upaya instansiasi kelas terjadi:

# data.py

class DataHandler(object):
    """
    DataHandler is an abstract base class providing an interface for
    all subsequent (inherited) data handlers (both live and historic).

    The goal of a (derived) DataHandler object is to output a generated
    set of bars (OLHCVI) for each symbol requested. 

    This will replicate how a live strategy would function as current
    market data would be sent "down the pipe". Thus a historic and live
    system will be treated identically by the rest of the backtesting suite.
    """

    __metaclass__ = ABCMeta

    @abstractmethod
    def get_latest_bars(self, symbol, N=1):
        """
        Returns the last N bars from the latest_symbol list,
        or fewer if less bars are available.
        """
        raise NotImplementedError("Should implement get_latest_bars()")

    @abstractmethod
    def update_bars(self):
        """
        Pushes the latest bar to the latest symbol structure
        for all symbols in the symbol list.
        """
        raise NotImplementedError("Should implement update_bars()")

Dengan DataHandler ABC yang ditentukan langkah selanjutnya adalah membuat pengendali untuk file CSV historis. Secara khusus HistoricCSVDataHandler akan mengambil beberapa file CSV, satu untuk setiap simbol, dan mengubahnya menjadi kamus panda DataFrames.

Data handler membutuhkan beberapa parameter, yaitu Event Queue untuk mendorong informasi MarketEvent, jalur absolut file CSV dan daftar simbol.

# data.py

class HistoricCSVDataHandler(DataHandler):
    """
    HistoricCSVDataHandler is designed to read CSV files for
    each requested symbol from disk and provide an interface
    to obtain the "latest" bar in a manner identical to a live
    trading interface. 
    """

    def __init__(self, events, csv_dir, symbol_list):
        """
        Initialises the historic data handler by requesting
        the location of the CSV files and a list of symbols.

        It will be assumed that all files are of the form
        'symbol.csv', where symbol is a string in the list.

        Parameters:
        events - The Event Queue.
        csv_dir - Absolute directory path to the CSV files.
        symbol_list - A list of symbol strings.
        """
        self.events = events
        self.csv_dir = csv_dir
        self.symbol_list = symbol_list

        self.symbol_data = {}
        self.latest_symbol_data = {}
        self.continue_backtest = True       

        self._open_convert_csv_files()

Ini secara implisit akan mencoba membuka file dengan format SYMBOL.csv dimana simbol adalah simbol ticker. Format file cocok dengan yang disediakan oleh vendor DTN IQFeed, tetapi mudah dimodifikasi untuk menangani format data tambahan. Pembukaan file ditangani oleh metode _open_convert_csv_files di bawah ini.

Salah satu manfaat menggunakan panda sebagai data store secara internal dalam HistoricCSVDataHandler adalah bahwa indeks semua simbol yang dilacak dapat digabungkan bersama. Hal ini memungkinkan titik data yang hilang untuk dipadatkan ke depan, ke belakang atau diinterpolasi dalam kesenjangan ini sehingga ticker dapat dibandingkan pada basis bar-to-bar. Ini diperlukan untuk strategi mean-reversing, misalnya. Perhatikan penggunaan metode union dan reindex saat menggabungkan indeks untuk semua simbol:

# data.py

    def _open_convert_csv_files(self):
        """
        Opens the CSV files from the data directory, converting
        them into pandas DataFrames within a symbol dictionary.

        For this handler it will be assumed that the data is
        taken from DTN IQFeed. Thus its format will be respected.
        """
        comb_index = None
        for s in self.symbol_list:
            # Load the CSV file with no header information, indexed on date
            self.symbol_data[s] = pd.io.parsers.read_csv(
                                      os.path.join(self.csv_dir, '%s.csv' % s),
                                      header=0, index_col=0, 
                                      names=['datetime','open','low','high','close','volume','oi']
                                  )

            # Combine the index to pad forward values
            if comb_index is None:
                comb_index = self.symbol_data[s].index
            else:
                comb_index.union(self.symbol_data[s].index)

            # Set the latest symbol_data to None
            self.latest_symbol_data[s] = []

        # Reindex the dataframes
        for s in self.symbol_list:
            self.symbol_data[s] = self.symbol_data[s].reindex(index=comb_index, method='pad').iterrows()

Metode _get_new_bar membuat generator untuk menyediakan versi berformat dari data bar. Ini berarti bahwa panggilan berikutnya ke metode akan menghasilkan bar baru sampai data simbol mencapai akhir:

# data.py

    def _get_new_bar(self, symbol):
        """
        Returns the latest bar from the data feed as a tuple of 
        (sybmbol, datetime, open, low, high, close, volume).
        """
        for b in self.symbol_data[symbol]:
            yield tuple([symbol, datetime.datetime.strptime(b[0], '%Y-%m-%d %H:%M:%S'), 
                        b[1][0], b[1][1], b[1][2], b[1][3], b[1][4]])

Metode abstrak pertama dari DataHandler yang akan diimplementasikan adalah get_latest_bars. Metode ini hanya menyediakan daftar N bar terakhir dari struktur data latest_symbol_data. Pengaturan N=1 memungkinkan pengambilan bar saat ini (dibungkus dalam daftar):

# data.py

    def get_latest_bars(self, symbol, N=1):
        """
        Returns the last N bars from the latest_symbol list,
        or N-k if less available.
        """
        try:
            bars_list = self.latest_symbol_data[symbol]
        except KeyError:
            print "That symbol is not available in the historical data set."
        else:
            return bars_list[-N:]

Metode terakhir, update_bars adalah metode abstrak kedua dari DataHandler. Ini hanya menghasilkan MarketEvent yang ditambahkan ke antrian saat menambahkan bar terbaru ke data_simbol_terbaru:

# data.py

    def update_bars(self):
        """
        Pushes the latest bar to the latest_symbol_data structure
        for all symbols in the symbol list.
        """
        for s in self.symbol_list:
            try:
                bar = self._get_new_bar(s).next()
            except StopIteration:
                self.continue_backtest = False
            else:
                if bar is not None:
                    self.latest_symbol_data[s].append(bar)
        self.events.put(MarketEvent())

Dengan demikian kita memiliki DataHandler-derived object, yang digunakan oleh komponen yang tersisa untuk melacak data pasar.

Dalam artikel berikutnya kita akan membahas hierarki kelas Strategi dan menjelaskan bagaimana strategi dapat dirancang untuk menangani beberapa simbol, sehingga menghasilkan beberapa SignalEvents untuk objek Portfolio.


Lebih banyak