Pengujian Kembali yang Dikendalikan Acara dengan Python - Bahagian III

Penulis:Kebaikan, Dicipta: 2019-03-23 11:22:28, Dikemas kini:

Dalam dua artikel sebelum ini, kami membincangkan sistem backtesting yang didorong oleh peristiwa dan hierarki kelas untuk objek Acara.

Salah satu matlamat kami dengan sistem perdagangan yang didorong oleh peristiwa adalah untuk meminimumkan pendua kod antara elemen backtesting dan elemen pelaksanaan langsung. Sebaik-baiknya adalah optimum untuk menggunakan metodologi penjanaan isyarat yang sama dan komponen pengurusan portfolio untuk kedua-dua ujian sejarah dan perdagangan langsung. Untuk ini berfungsi objek Strategi yang menghasilkan Isyarat, dan objek Portfolio yang menyediakan Perintah berdasarkan mereka, mesti menggunakan antara muka yang sama dengan suapan pasaran untuk kedua-dua sejarah dan berjalan secara langsung.

Ini mendorong konsep hierarki kelas berdasarkan objek DataHandler, yang memberikan semua subkelas antara muka untuk menyediakan data pasaran kepada komponen yang tersisa dalam sistem.

Subkelas contoh tertentu boleh termasuk HistoricCSVDataHandler, QuandlDataHandler, SecuritiesMasterDataHandler, InteractiveBrokersMarketFeedDataHandler dan lain-lain. Dalam tutorial ini kita hanya akan mempertimbangkan penciptaan pengendali data CSV sejarah, yang akan memuatkan data CSV intraday untuk ekuiti dalam set bar terbuka-rendah-tinggi-dekat-volume-terbuka. Ini kemudian boleh digunakan untuk drip feed pada asas bar-by-bar data ke dalam kelas Strategi dan Portfolio pada setiap denyutan jantung sistem, dengan itu mengelakkan bias lookahead.

Tugas pertama adalah untuk mengimport perpustakaan yang diperlukan. khususnya kita akan mengimport panda dan alat kelas asas abstrak. kerana DataHandler menghasilkan MarketEvents kita juga perlu mengimportevent.pyseperti yang diterangkan dalam tutorial sebelumnya:

# data.py

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

daripada abc import ABCMeta, abstrakmetod

daripada import acara MarketEvent DataHandler adalah kelas asas abstrak (ABC), yang bermaksud bahawa mustahil untuk membuat contoh secara langsung. Hanya subkelas yang boleh dibuat. Alasan untuk ini adalah bahawa ABC menyediakan antara muka yang harus dipatuhi oleh semua subkelas DataHandler seterusnya dengan itu memastikan keserasian dengan kelas lain yang berkomunikasi dengan mereka.

Kami menggunakanMeta-kelassifat untuk memberitahu Python bahawa ini adalah ABC. Di samping itu kita menggunakan dekorator @abstractmethod untuk memberitahu Python bahawa kaedah akan ditimpa dalam subkelas (ini sama dengan kaedah maya murni dalam C + +).

Kedua-dua kaedah yang menarik adalah get_latest_bars dan update_bars. Yang pertama mengembalikan N bar terakhir dari cap masa denyutan jantung semasa, yang berguna untuk pengiraan bergolak yang diperlukan dalam kelas Strategi. Kaedah terakhir menyediakan mekanisme drip feed untuk meletakkan maklumat bar pada struktur data baru yang sangat melarang bias lookahead. Perhatikan bahawa pengecualian akan dibangkitkan jika percubaan instansi kelas berlaku:

# 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 seterusnya adalah membuat pengendali untuk fail CSV bersejarah. Khususnya, HistoricCSVDataHandler akan mengambil beberapa fail CSV, satu untuk setiap simbol, dan menukarnya menjadi kamus panda DataFrames.

Pengendali data memerlukan beberapa parameter, iaitu Antrian Acara untuk memaksa maklumat MarketEvent, laluan mutlak fail CSV dan senarai 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()

Ia secara tersirat akan cuba membuka fail dengan format SYMBOL.csv di mana simbol adalah simbol ticker. Format fail sepadan dengan yang disediakan oleh vendor DTN IQFeed, tetapi mudah diubah suai untuk mengendalikan format data tambahan. Pembukaan fail dikendalikan oleh _open_convert_csv_files kaedah di bawah.

Salah satu faedah menggunakan panda sebagai storan data secara dalaman dalam HistoricCSVDataHandler ialah indeks semua simbol yang dikesan dapat digabungkan bersama. Ini membolehkan titik data yang hilang dipadatkan ke hadapan, ke belakang atau diinterpolasi dalam jurang ini sehingga penanda dapat dibandingkan pada asas bar-to-bar. Ini diperlukan untuk strategi membalikkan purata, misalnya. Perhatikan penggunaan kaedah kesatuan dan reindex apabila 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()

Kaedah _get_new_bar mencipta penjana untuk menyediakan versi berformat data bar. Ini bermakna bahawa panggilan berikutnya kepada kaedah akan menghasilkan bar baru sehingga 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]])

Kaedah abstrak pertama dari DataHandler yang akan dilaksanakan adalah get_latest_bars. Kaedah ini hanya menyediakan senarai N bar terakhir dari struktur data_simbol_terbaru. Tetapan N=1 membolehkan pengambilan bar semasa (dibalut dalam senarai):

# 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:]

Kaedah terakhir, update_bars adalah kaedah abstrak kedua dari DataHandler. Ia hanya menghasilkan MarketEvent yang ditambahkan ke bar antrian kerana ia menambahkan bar terbaru ke data_symbol_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())

Oleh itu, kita mempunyai objek yang berasal dari DataHandler, yang digunakan oleh komponen yang selebihnya untuk mengesan data pasaran. Objek Strategi, Portfolio dan ExecutionHandler semua memerlukan data pasaran semasa jadi masuk akal untuk memusatkannya untuk mengelakkan penduaan penyimpanan.

Dalam artikel seterusnya kita akan mempertimbangkan hierarki kelas Strategy dan menerangkan bagaimana strategi boleh direka untuk mengendalikan pelbagai simbol, dengan itu menjana beberapa SignalEvents untuk objek Portfolio.


Lebih lanjut