
前回の記事では、イベント指向のバックテストシステムとは何かについて説明し、そのために開発する必要があるクラス階層を整理しました。 今日は、このようなシステムが歴史的テストのコンテキストと取引所の「ライブ」ワークの両方で市場データをどのように使用するかについてお話します。
市場データを操作する
イベント指向のトレーディングシステムを作成する際のタスクの1つは、履歴データのテストと実際のトレーディングのコンテキストで、同じタスクに対して異なるコードを記述する必要性を最小限に抑えることです。 理想的には、これらの各ケースで統一された信号生成とポートフォリオ管理の方法論を使用する必要があります。 これを実現するには、取引シグナル(
Signals
)を生成する
Strategy
オブジェクトと、それらに基づいて注文を生成する
Portfolio
オブジェクトは、履歴テストとリアルタイム操作の両方のコンテキストで市場データにアクセスするために同じインターフェースを使用する必要があります。
この必要性から、
DataHandler
オブジェクトに基づくクラス階層の概念が出現しました。この概念は、市場データをシステムの他のコンポーネントに転送するためのインターフェースをサブクラスに提供します。 この構成では、サブクラスのハンドラーを単に「破棄」することができ、これは戦略およびポートフォリオ処理を担当するコンポーネントの作業には影響しません。
これらのサブクラスには、
HistoricCSVDataHandler
、
QuandlDataHandler
、
SecuritiesMasterDataHandler
、
InteractiveBrokersMarketFeedDataHandler
などがあります。 ここでは、バーの形式(低、高、終値、ボリューム取引量、OpenInterest建玉)で1日以内に対応する金融データのCSVファイルを読み込む履歴データを含むCSVハンドラーの作成のみを検討します。 このデータに基づいて、システムの各「ハートビート」(ハートビート)を使用して、さまざまな歪みを回避できるコンポーネント
Strategy
および
Portfolio
を使用して詳細な分析を既に実行できます。
最初のステップは、必要なライブラリ、特にパンダと抽象基本クラスをインポートすることです。
DataHandler
は
DataHandler
イベントを生成するため、
DataHandler
もインポートする必要があります。
# data.py import datetime import os, os.path import pandas as pd from abc import ABCMeta, abstractmethod from event import MarketEvent
DataHandler
は抽象基本クラス(ABA)です。つまり、直接インスタンス化することはできません。 これは、サブクラスを使用してのみ実行できます。 この理由は、ABAが使用する必要のある基礎となるDataHandlerサブクラスのインターフェイスを提供し、相互作用可能な他のクラスとの互換性を提供することです。
Pythonが抽象基本クラスを処理していることを「理解」するために、
_metaclass_
プロパティを使用します。
@abstractmethod
デコレータは、メソッドがサブクラスでオーバーライドされること
@abstractmethod
示します(C ++の完全仮想メソッドとまったく同じです)。
興味のある2つのメソッドは
get_latest_bars
と
update_bars
です。 最初のものは、システムの「ハートビート」の現在のタイムスタンプから最後のNバーを返します。これは、
Strategy
クラスの計算を実行するのに役立ちます。 後者の方法は、新しいデータ構造にバー情報を重ね合わせるための分析メカニズムを提供し、予測の歪みを完全に排除します。 クラスをインスタンス化しようとすると、例外がスローされます。
# data.py class DataHandler(object): """ DataHandler — , ( ) () DataHandler (OLHCVI) . , , . . """ __metaclass__ = ABCMeta @abstractmethod def get_latest_bars(self, symbol, N=1): """ N latest_symbol , . """ raise NotImplementedError("Should implement get_latest_bars()") @abstractmethod def update_bars(self): """ . """ raise NotImplementedError("Should implement update_bars()")
DataHandler
クラスを説明した後
DataHandler
次のステップは、履歴CSVファイルのハンドラーを作成することです。
HistoricCSVDataHandler
は、多くのCSVファイル(金融商品ごとに1つ)を
DataFrames
し、それらをパンダ用の
DataFrames
フレーム
DataFrames
変換します。
ハンドラーには、
MarketEvent
市場情報を公開する
Event Queue
、CSVファイルへの絶対パス、ツールのリストなど、いくつかのパラメーターが必要です。 クラスの初期化は次のようになります。
# data.py class HistoricCSVDataHandler(DataHandler): """ HistoricCSVDataHandler CSV- «» , . """ def __init__(self, events, csv_dir, symbol_list): """ CSV- . , 'symbol.csv', symbol — . : events - . csv_dir - CSV-. symbol_list - . """ 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()
彼は「SYMBOL.csv」形式のファイルを開こうとします。この形式では、SYMBOLは楽器のティッカーです。 ここで使用される形式は、提案されているDTN IQFeedデータプロバイダーと同じですが、他の形式で動作するように簡単に変更できます。 ファイルを開くことは、_open_convert_csv_filesメソッドによって処理されます。
パンダを使用してHistoricCSVDataHandler内にデータを保存することの利点の1つは、すべての監視対象機器のインデックスをマージできることです。 これにより、欠落したデータでさえ補間することができます。これは、機器のツールごとの比較に役立ちます(戦略平均復帰に必要な場合があります)。 インストゥルメントのインデックスを組み合わせる場合、
union
メソッドと
reindex
メソッドが使用されます。
# data.py def _open_convert_csv_files(self): """ CSV- , pandas DataFrames . , DTN IQFeed, . """ comb_index = None for s in self.symbol_list: # CSV- , 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'] ) # «» 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): """ - : (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]])
DataHndler
から実装する最初の抽象メソッドは
get_latest_bars
です。
latest_symbol_data
構造体の最後のNバーをリストするだけです。 N = 1に設定すると、現在のバーを取得できます。
# data.py def get_latest_bars(self, symbol, N=1): """ N latest_symbol, Nk, . """ 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番目の抽象メソッド
DataHandler
。 最新のバーが
latest_symbol_data
追加されるとキューに入れられるイベント(
MarketEvent
)を生成します。
# data.py def update_bars(self): """ . """ 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
があります。これは、システムの他のコンポーネントが市場データを追跡するために使用する専用オブジェクト
DataHandler
。
Stragety
、
Portfolio
および
ExecutionHandler
オブジェクトが機能するには、現在の市場情報が必要であるため、ストレージの重複の可能性を回避するために中央で作業するのが理にかなっています。
情報から取引シグナルまで:戦略
Strategy
オブジェクトは、マーケットデータの処理に関連するすべての計算をカプセル化して、
Portfolio
オブジェクトへの助言信号を作成します。 イベント指向のバックテスターの開発のこの段階では、テクニカル分析で使用されるインジケーターやフィルターの概念はありません。 それらを実装するには、別のデータ構造を作成できますが、これはすでにこの記事の範囲外です。
戦略の階層は比較的単純です
SignalEvents
オブジェクトを作成するための単一の仮想メソッドを持つ抽象基本クラスで構成されます。 戦略階層を作成するには、NumPy、pandas、Queue、abstract base tools、SignalEventをインポートする必要があります。
# strategy.py import datetime import numpy as np import pandas as pd import Queue from abc import ABCMeta, abstractmethod from event import SignalEvent
抽象基本クラス
Strategy
は、仮想メソッド
calculate_signals
定義し
calculate_signals
。 市場データの更新に基づいて
SignalEvent
オブジェクトの作成を処理するために使用されます。
# strategy.py class Strategy(object): """ Strategy — , () . Strategy (OLHCVI), DataHandler. , — Strategy , . """ __metaclass__ = ABCMeta @abstractmethod def calculate_signals(self): """ . """ raise NotImplementedError("Should implement calculate_signals()")
抽象基本クラス
Strategy
の定義
Strategy
非常に単純です。
Strategy
オブジェクトでサブクラスを使用する最初の例は、買いと保留の戦略を使用し、対応する
BuyAndHoldStrategy
クラスを作成すること
BuyAndHoldStrategy
。 彼は特定の日に特定の株式を買い、その地位を保持します。 したがって、共有ごとに1つの信号のみが生成されます。
コンストラクター(
__init__
)は、barsマーケットデータハンドラーとイベントイベントキューオブジェクトの存在を必要とします。
# strategy.py class BuyAndHoldStrategy(Strategy): """ , . Strategy . """ def __init__(self, bars, events): """ buy and hold. : bars - DataHandler, events - . """ self.bars = bars self.symbol_list = self.bars.symbol_list self.events = events # , True self.bought = self._calculate_initial_bought()
BuyAndHoldStrategy
戦略を初期化すると、
bought
ディクショナリには、Falseに設定された各インストゥルメントのキーのセットが含まれます。 特定の楽器が購入されると(ロングポジションが開かれる)、キーはTrueポジションに転送されます。 これにより、
Strategy
オブジェクトはポジションが開いているかどうかを理解できます。
# strategy.py def _calculate_initial_bought(self): """ bought False. """ bought = {} for s in self.symbol_list: bought[s] = False return bought
このメソッドに
calculate_signals
、仮想メソッド
calculate_signals
実装されています。 このメソッドはリスト内のすべての楽器を調べ、バーハンドラーから最後のバーを取得します。 次に、ツールが「購入」されたかどうか(それによって市場にいるかどうか)をチェックし、
SignalEvent
シグナルオブジェクトが
SignalEvent
ます。 次に、それはイベントキューに配置され、購入した辞書は対応する情報で更新されます(購入したツールの場合はTrue)。
# strategy.py def calculate_signals(self, event): """ "Buy and Hold" . , . : event - MarketEvent. """ if event.type == 'MARKET': for s in self.symbol_list: bars = self.bars.get_latest_bars(s, N=1) if bars is not None and bars != []: if self.bought[s] == False: # (Symbol, Datetime, Type = LONG, SHORT or EXIT) signal = SignalEvent(bars[0][0], bars[0][1], 'LONG') self.events.put(signal) self.bought[s] = True
これは非常に単純な戦略ですが、イベント指向の戦略の階層の性質を実証するには十分です。 次の記事では、ペア取引など、より複雑な戦略を検討します。 また、次の記事では、ポジション(損益、PnL)ごとに損益を追跡する
Portfolio
階層の作成について説明し
Portfolio
。
継続するには...
PSHabréのブログでは、すでにトレーディングシステムの開発のさまざまな段階について検討しました。 このテーマに関するオンラインコースがあります。