
以前の記事では、イベント指向のバックテストシステムとは何かについて説明し、そのために開発する必要があるクラス階層を整理し、そのようなシステムが過去のテストと取引所での「ライブ」作業において市場データをどのように使用するかについて説明しました。
今日は、ポートフォリオ内のポジションの追跡を担当し、着信信号に基づいて限られた数の株式で注文を生成するNaivePortfolioオブジェクトについて説明します。
位置の追跡と注文の操作
注文管理システムは、イベント指向のバックテスターの最も複雑なコンポーネントの1つです。 その役割は、現在の市場ポジションとその市場価値を追跡することです。 したがって、バックテスターの対応するコンポーネントから取得したデータに基づいて、位置の残差値が計算されます。
ポートフォリオコンポーネントは、ポジションの分析に加えて、リスク要因を考慮し、ポジションのサイズを決定する手法を適用できる必要があります。これは、ブローカーのトレーディングシステムを通じて市場に送信される注文を最適化するために必要です。
Portfolio
オブジェクトは、
SignalEvent
オブジェクトを処理し、
SignalEvent
オブジェクトを生成し、
OrderEvent
オブジェクトを解釈して位置を更新できる必要があります。 したがって、Portfolioオブジェクトが通常、コード行の点でバックテストシステムの最も膨大な要素であることは驚くことではありません。
実装
新しいportfolio.pyファイルを作成し、必要なライブラリをインポートしましょう-以前に使用した抽象基本クラスの同じ実装です。 整数次数を生成するには、数学ライブラリからフロア関数をインポートする必要があります。
FillEvent
と
OrderEvent
も必要です
OrderEvent
オブジェクトはそれぞれを処理します。
# portfolio.py import datetime import numpy as np import pandas as pd import Queue from abc import ABCMeta, abstractmethod from math import floor from event import FillEvent, OrderEvent
Portfolioの抽象基本クラスと、2つの抽象メソッド
update_signal
および
update_fill
ます。 前者はイベントキューから取得された新しい取引シグナルを処理し、後者は処理オブジェクトのエンジンから受信した実行済み注文に関する情報を処理します。
# portfolio.py class Portfolio(object): """ Portfolio : , , 5 , 30 , 60 . """ __metaclass__ = ABCMeta @abstractmethod def update_signal(self, event): """ SignalEvent . """ raise NotImplementedError("Should implement update_signal()") @abstractmethod def update_fill(self, event): """ FillEvent. """ raise NotImplementedError("Should implement update_fill()")
この記事の主なオブジェクトは、
NaivePortfolio
クラスです。 これは、ポジションと予約済みファンドのサイズを計算し、取引注文を簡単な方法で処理するために作成されました-特定の株式数でそれらを証券取引システムに送信するだけです。 現実の世界では、すべてがより複雑ですが、このような単純化は、ポートフォリオ注文処理システムがイベント指向製品でどのように機能するかを理解するのに役立ちます。
NaivePortfolio
は初期資本が必要です-例では、100,000ドルに設定されています。 また、仕事を開始する日時を設定する必要があります。
ポートフォリオには、
all_positions
および
current_positions
が含まれ
current_positions
。 最初の要素は、市場イベントのタイムスタンプによって記録された以前のすべてのポジションのリストを保存します。 ポジションは、単に金融商品の額です。 マイナスのポジションは、株式が「ショート」で売却されることを意味します。 2番目の要素には、バーの最終更新の現在の位置を含む辞書が格納されます。
ポートフォリオは、ポジションを担当する要素に加えて、オープンポジション(保有)の現在の市場価値に関する情報を保存します。 この場合の「現在の市場価値」とは、現在のバーから得られる終値を意味します。これは近似値ですが、現時点では非常に妥当です。
all_holdings
要素には、すべてのポジションのコストの履歴リストが格納され、
current_holdings
は値の最新の辞書が格納されます。
# portfolio.py class NaivePortfolio(Portfolio): """ NaivePortfolio (.. -) / , . BuyAndHoldStrategy. """ def __init__(self, bars, events, start_date, initial_capital=100000.0): """ . ( , ). Parameters: bars - The DataHandler object with current market data. events - The Event Queue object. start_date - The start date (bar) of the portfolio. initial_capital - The starting capital in USD. """ self.bars = bars self.events = events self.symbol_list = self.bars.symbol_list self.start_date = start_date self.initial_capital = initial_capital self.all_positions = self.construct_all_positions() self.current_positions = dict( (k,v) for k, v in [(s, 0) for s in self.symbol_list] ) self.all_holdings = self.construct_all_holdings() self.current_holdings = self.construct_current_holdings()
次のconstruct_all_positionsメソッドは、各金融商品の辞書を作成し、各値をゼロに設定してから、日付と時刻のキーを追加します。 Python辞書ジェネレーターを使用しました。
# portfolio.py def construct_all_positions(self): """ , start_date , . """ d = dict( (k,v) for k, v in [(s, 0) for s in self.symbol_list] ) d['datetime'] = self.start_date return [d]
construct_all_hldings
メソッドは上記のメソッドと似ていますが、利用可能な資金、手数料、取引後の口座の残高、支払った手数料の合計、利用可能な資産の合計額(オープンポジションとマネー)の追加キーを追加します。 ショートポジションは「マイナス」とみなされます。
starting cash
と
total account
の値は、初期資本と同じです
# portfolio.py def construct_all_holdings(self): """ , start_date , . """ d = dict( (k,v) for k, v in [(s, 0.0) for s in self.symbol_list] ) d['datetime'] = self.start_date d['cash'] = self.initial_capital d['commission'] = 0.0 d['total'] = self.initial_capital return [d]
construct_current_holdings
メソッドは、辞書をリストに「ラップ」しないことを除いて、前のメソッドとほとんど同じです。
# portfolio.py def construct_current_holdings(self): """ , . """ d = dict( (k,v) for k, v in [(s, 0.0) for s in self.symbol_list] ) d['cash'] = self.initial_capital d['commission'] = 0.0 d['total'] = self.initial_capital return d
各「ハートビート」で、つまり
DataHandler
オブジェクトからの市場データの要求ごとに、ポートフォリオは保有ポジションの現在の市場価値を更新する必要があります。 実際の取引シナリオでは、この情報は仲介システムから直接ダウンロードして解析できますが、バックテストシステムの場合、これらの値は個別に計算する必要があります。
残念ながら、ビッド/アスクのスプレッドと流動性により、「現在の市場価値」などは存在しません。 したがって、保有する資産の量に「価格」を乗じて評価する必要があります。 この例では、以前に受け取ったバーの終値が使用されます。日中の取引戦略では、これはかなり現実的なアプローチですが、1日以上の期間の取引では、始値が次のバーの始値と大きく異なる可能性があるため、すべてがそれほど妥当ではありません。
update_timeindex
メソッド
update_timeindex
、新しいポジションの現在の値を処理します。 彼は市場データプロセッサから最新の価格を取得し、現在のポジションを表す新しいディクショナリのディクショナリを作成し、「新しい」ポジションを「現在の」ポジションと同一視します。 このスキーマは、
FillEvent
ときにのみ変更されます。 その後、メソッド
all_positions
現在の位置
all_positions
セットを
all_positions
リストに
all_positions
ます。 次に、現在値の値が同様の方法で更新されますが、市場価値は、現在のポジションの数に最後のバーの終値を掛けることによって計算されます(
self.current_positions[s] * bars[s][0][5]
)。 取得された新しい値は、
all_holdings
リストに追加されます。
# portfolio.py def update_timeindex(self, event): """ . , .. (OLHCVI). MarketEvent . """ bars = {} for sym in self.symbol_list: bars[sym] = self.bars.get_latest_bars(sym, N=1) # Update positions dp = dict( (k,v) for k, v in [(s, 0) for s in self.symbol_list] ) dp['datetime'] = bars[self.symbol_list[0]][0][1] for s in self.symbol_list: dp[s] = self.current_positions[s] # Append the current positions self.all_positions.append(dp) # Update holdings dh = dict( (k,v) for k, v in [(s, 0) for s in self.symbol_list] ) dh['datetime'] = bars[self.symbol_list[0]][0][1] dh['cash'] = self.current_holdings['cash'] dh['commission'] = self.current_holdings['commission'] dh['total'] = self.current_holdings['cash'] for s in self.symbol_list: # Approximation to the real value market_value = self.current_positions[s] * bars[s][0][5] dh[s] = market_value dh['total'] += market_value # Append the current holdings self.all_holdings.append(dh)
update_positions_from_fill
メソッドは、
FillEvent
何で
FillEvent
(買いまたは売り)を正確に判別し、対応する数の共有を追加または削除することでcurrent_positions辞書を更新します。
# portfolio.py def update_positions_from_fill(self, fill): """ FillEvent , . Parameters: fill - The FillEvent object to update the positions with. """ # Check whether the fill is a buy or sell fill_dir = 0 if fill.direction == 'BUY': fill_dir = 1 if fill.direction == 'SELL': fill_dir = -1 # self.current_positions[fill.symbol] += fill_dir*fill.quantity
対応する
update_holdings_from_fill
メソッド
update_holdings_from_fill
上記のメソッド
update_holdings_from_fill
似ていますが、 保有値を更新します。 実行コストをシミュレートするために、このメソッドは
FillEvent
関連付けられた価格を使用しません。 これはなぜですか? バックテスト環境では、執行価格は実際には不明であるため、想定する必要があります。 したがって、行使価格は「現在の市場価格」(最後のバーの終値)として設定されます。 特定の商品の現在のポジションの値は、執行価格に注文の有価証券の数を乗じたものと等しくなります。
行使価格を決定した後、保有の現在価値、利用可能な資金、および合計額を更新できます。 一般委員会も更新されます。
# portfolio.py def update_holdings_from_fill(self, fill): """ FillEvent holdings . : fill - FillEvent, . """ # Check whether the fill is a buy or sell fill_dir = 0 if fill.direction == 'BUY': fill_dir = 1 if fill.direction == 'SELL': fill_dir = -1 # Update holdings list with new quantities fill_cost = self.bars.get_latest_bars(fill.symbol)[0][5] # Close price cost = fill_dir * fill_cost * fill.quantity self.current_holdings[fill.symbol] += cost self.current_holdings['commission'] += fill.commission self.current_holdings['cash'] -= (cost + fill.commission) self.current_holdings['total'] -= (cost + fill.commission)
次に、抽象基本クラス
Portfolio
の抽象
update_fill
メソッドが実装されます。 前の2つのメソッド
update_positions_from_fill
と
update_holdings_from_fill
実行するだけ
update_holdings_from_fill
:
# portfolio.py def update_fill(self, event): """ FillEvent. """ if event.type == 'FILL': self.update_positions_from_fill(event) self.update_holdings_from_fill(event)
Portfolioオブジェクトは、FillEventイベントを発生させるだけでなく、
SignalEvent
シグナルイベントを受信したときに
OrderEvent
生成する
OrderEvent
があります。 generate_naive_orderメソッドは、シグナルを使用してロングまたはショートポジション、ターゲット金融商品を開き、目的の資産の100株で対応する注文を送信します。 ここで100は任意の値です。 実際の取引の過程で、リスク管理システムまたはポジション値を計算するモジュールによって決定されます。 ただし、
NaivePortfolio
では、リスク管理なしで信号を受信した直後に「
NaivePortfolio
」注文を送信できます。
このメソッドは、ロングポジションとショートポジションのオープン、および現在の数量と特定の金融商品に基づいたそれらの終了を処理します。 次に、対応する
OrderEvent
オブジェクトが
OrderEvent
ます。
# portfolio.py def generate_naive_order(self, signal): """ OrderEvent . : signal - SignalEvent. """ order = None symbol = signal.symbol direction = signal.signal_type strength = signal.strength mkt_quantity = floor(100 * strength) cur_quantity = self.current_positions[symbol] order_type = 'MKT' if direction == 'LONG' and cur_quantity == 0: order = OrderEvent(symbol, order_type, mkt_quantity, 'BUY') if direction == 'SHORT' and cur_quantity == 0: order = OrderEvent(symbol, order_type, mkt_quantity, 'SELL') if direction == 'EXIT' and cur_quantity > 0: order = OrderEvent(symbol, order_type, abs(cur_quantity), 'SELL') if direction == 'EXIT' and cur_quantity < 0: order = OrderEvent(symbol, order_type, abs(cur_quantity), 'BUY') return order
update_signal
メソッドは、上記のメソッドを呼び出すだけで、生成された順序をイベントキューに追加します。
# portfolio.py def update_signal(self, event): """ SignalEvent . """ if event.type == 'SIGNAL': order_event = self.generate_naive_order(event) self.events.put(order_event)
NaivePortfolio
の最後の方法は、資本曲線を生成することです。 利益の情報を含むストリームが作成されます。これは、戦略のパフォーマンスを計算するのに役立ち、その後、曲線は割合に基づいて正規化されます。 初期請求書サイズは1.0に設定されています。
# portfolio.py def create_equity_curve_dataframe(self): """ pandas DataFrame all_holdings. """ curve = pd.DataFrame(self.all_holdings) curve.set_index('datetime', inplace=True) curve['returns'] = curve['total'].pct_change() curve['equity_curve'] = (1.0+curve['returns']).cumprod() self.equity_curve = curve
Portfolio
オブジェクトは、イベント指向のバックテスター全体の最も複雑な側面です。 複雑さにもかかわらず、ここでのポジションの処理は非常に単純なレベルで実装されています。
次の記事では、イベント指向の歴史的テストシステムの最後の部分である、
OrderEvent
オブジェクトを使用して
OrderEvent
を作成する
ExecutionHandler
オブジェクトについて
FillEvent
します。
継続するには...
PSHabréのブログで既に、トレーディングシステムの開発のさまざまな段階を検討しました 。 ITinvestとパートナーは、このトピックに関するオンラインコースを実施しています。