イベント駆動バックテスト Python - Part III

作者: リン・ハーン優しさ作成日: 2019-03-23 11:22:28, 更新日:

このシリーズの前の2つの記事では,イベント駆動バックテストシステムが何であるか,およびイベントオブジェクトのクラス階層について議論しました.この記事では,歴史的なバックテストの文脈とライブ取引実行の両方で,市場のデータがどのように利用されているかを検討します.

イベント駆動取引システムにおける我々の目標の1つは,バックテスト要素とライブ実行要素の間のコードの重複を最小限に抑えることである.理想的には,歴史的なテストとライブ取引の両方に同じ信号生成方法論とポートフォリオ管理コンポーネントを使用することが最適である.これを行うために,シグナルを生成する戦略オブジェクトと,それらをベースにしたオーダーを提供するポートフォリオオブジェクトは,歴史的およびライブ実行の両方の市場フィードに同一のインターフェースを使用する必要があります.

これは,DataHandlerオブジェクトに基づくクラス階層の概念を動機づけ,すべてのサブクラスにシステム内の残りのコンポーネントに市場データを提供するためのインターフェースを提供します.この方法で,戦略またはポートフォリオ計算に影響を与えることなく,サブクラスのデータハンドラーを"交換"することができます.

特定の例サブクラスは,HistoricCSVDataHandler,QuandlDataHandler,SecuritiesMasterDataHandler,InteractiveBrokersMarketFeedDataHandlerなどを含む.このチュートリアルでは,本日のCSVデータハンドラーの作成のみを考慮し,オープン・ロー・ハイ・クローズ・ボリューム・オープン・インテレストセットのバーで株式のCSVデータをロードします.この方法により,バーごとにデータを戦略とポートフォリオクラスにドリップフィードにすることができます.

必要なライブラリをインポートすることです.特にパンダと抽象的なベースクラスのツールをインポートします. DataHandler が MarketEvents を生成するので,event.py前回のチュートリアルで説明したように

# data.py

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

abcから輸入 ABCMeta,抽象方法

イベントインポートから MarketEvent DataHandlerは抽象的なベースクラス (ABC) で,インスタンスを直接インスタンシ化することは不可能である.サブクラスのみインスタンシ化することができる.この理由は,ABCがすべての次世代のDataHandlerサブクラスが遵守しなければならないインターフェースを提供し,それによってそれらと通信する他のクラスとの互換性を確保することである.

我々は利用するメタクラスこのプロパティは,Python が ABC であることを知らせる.さらに @abstractmethod デコラターを用いて,Python がサブクラスでメソッドがオーバーライドされることを知らせる (これは C++ の純粋仮想メソッドと同一である).

興味のある2つの方法は get_latest_bars と update_bars です.前者は現在のハートビートタイムスタンプから最後の N つのバーを返します.これは戦略クラスに必要なローリング計算に有用です.後者は,Lookaheadバイアスを厳格に禁止する新しいデータ構造にバー情報を配置するための"ドリップフィード"メカニズムを提供します.クラスのインスタンシャライゼーションが試みられた場合,例外が生じることに注意してください:

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

DataHandler ABC が指定された場合,次のステップは,歴史的な CSV ファイルのためのハンドラーを作成することです.特に,HistoricCSVDataHandler は複数の CSV ファイル,各シンボルの 1 つを取り,それらをパンダの DataFrames の辞書に変換します.

データ処理には, MarketEvent 情報をプッシュするイベントキュー,CSV ファイルの絶対パス,シンボルのリストなど,いくつかのパラメータが必要です.

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

ファイルは,DTN IQFeed ベンダーが提供したファイルと一致するが,追加のデータ形式に対応するために簡単に変更される.ファイルを開くのは下記の _open_convert_csv_files 方法によって処理される.

パンダをHistoricCSVDataHandler内の内部データストアとして使用する利点の1つは,追跡されているすべてのシンボルのインデックスが合併できるということです.これは,欠けているデータポイントを前方,後ろ方,またはこれらのギャップ内で挿入できるようにします.これは,バー対バーのベースでティッカーを比較することができます.これは,例えば,平均逆転戦略に必要です.すべてのシンボルのインデックスを組み合わせるときの組合と再インデックス方法の使用に注意してください:

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

_get_new_bar メソッドは,バーデータのフォーマットされたバージョンを提供するためのジェネレータを作成します.これは,メソッドへの次の呼び出しが,シンボルのデータが終了するまで新しいバーを生成することを意味します:

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

DataHandler から実装される最初の抽象メソッドは get_latest_bars です.このメソッドは,単に最新_symbol_data 構造から最後の N つのバーのリストを提供します. N=1 を設定すると,現在のバー (リストに包まれた) を取得できます:

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

最終メソッド,update_barsは,DataHandlerの2番目の抽象メソッドです.これは,最新のバーを最新_symbol_dataに追加する際にキューに追加される MarketEvent を生成します.

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

DataHandlerから派生したオブジェクトが,残りのコンポーネントによって市場データを追跡するために使用されます. 戦略,ポートフォリオ,実行Handlerのオブジェクトはすべて現在の市場データを必要とします. したがって,ストレージの重複を避けるために中央集権することが意味があります.

次の記事では,Strategy クラスの階層を考慮し,複数のシンボルに対応するために戦略を設計する方法について説明します. これにより,ポートフォリオオブジェクトの複数のSignalEventsが生成されます.


もっと