このシリーズの前の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が生成されます.