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

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

我々は,過去数ヶ月をQuantStartでPythonとパンダを使用して様々な取引戦略のバックテストに費やした.パンダのベクトル化された性質は,大規模なデータセット上の特定の操作が非常に高速であることを保証する.しかし,これまで研究したベクトル化されたバックテストの形態は,取引実行がシミュレーションされる方法にいくつかの欠点があります.このシリーズでは,Pythonを使用してイベント主導のバックテスト環境を構築することによって,歴史的な戦略シミュレーションに対するより現実的なアプローチについて議論します.

イベント駆動ソフトウェア

このようなバックテストの開発に深入する前に,イベント駆動システムの概念を理解する必要があります. ビデオゲームはイベント駆動ソフトウェアの自然な用例を提供し,探求するための簡単な例を提供します. ビデオゲームには,リアルタイム設定で高フレームレートで相互に相互作用する複数のコンポーネントがあります. これはイベントループまたはゲームループとして知られる"無限"ループ内の計算の全セットを実行することによって処理されます.

ゲームループの各クリックで,ゲーム内の対応する以前のアクションによって生成された最新のイベントを受信するために機能が呼び出されます.イベントの性質に応じて,キープレスまたはマウスクリックを含む後続的なアクションが実行され,ループを終了するか,追加のイベントを生成します. プロセスはその後も続きます. 以下はいくつかの例の偽コードです:

while True:  # Run the loop forever
    new_event = get_new_event()   # Get the latest event

    # Based on the event type, perform an action
    if new_event.type == "LEFT_MOUSE_CLICK":
        open_menu()
    elif new_event.type == "ESCAPE_KEY_PRESS":
        quit_game()
    elif new_event.type == "UP_KEY_PRESS":
        move_player_north()
    # ... and many more events

    redraw_screen()   # Update the screen to provide animation
    tick(50)   # Wait 50 milliseconds

コードは,新しいイベントを継続的にチェックし,それらのイベントに基づいてアクションを実行しています.特に,コードが継続的にループされ,イベントがチェックされているため,リアルタイムレスポンスハンドリングの錯覚を可能にします. 明らかになるように,これは,高周波取引シミュレーションを実行するために必要なものです.

なぜ 出来事 に 基づく バックテスト を する の です か

イベント駆動システムではベクトル化方法よりも多くの利点があります.

  • コード再利用 - イベント駆動バックテストは,設計上,構成要素の最小限の切り替えで,歴史的バックテストとライブ取引の両方に使用できます.これは,統計分析を行うためにすべてのデータが一度に利用可能であるベクトル化バックテストでは当てはまりません.
  • Lookaheadバイアス - イベント駆動バックテストでは,市場データの受信は行動しなければならない"イベント"として扱われ,イベント駆動バックテストは市場データで"ドリップフィード"することができ,オーダー管理とポートフォリオシステムがどのように振る舞うかを複製できます.
  • 現実主義 - イベント駆動バックテストは,注文の実行方法と取引コストの大幅なカスタマイゼーションを可能にします.カスタム交換ハンドラーが構築できるため,基本的な市場と制限オーダー,およびオープン市場 (MOO) および閉鎖市場 (MOC) を処理することは簡単です.

イベント駆動システムには多くの利点があるが,よりシンプルなベクトリ化システムに対して2つの大きな欠点がある.第一に,実装とテストがはるかに複雑である.バグの導入の可能性を高める"動く部品"が多くある.この適切なソフトウェアテスト方法論を緩和するために,テスト駆動開発などの使用が可能である.

2つ目は,ベクトル化されたシステムと比較して実行が遅い.数学的な計算を行うときに最適なベクトル化された演算は利用できない.これらの制限を克服する方法については,次の記事で説明します.

イベント駆動バックテストの概要

バックテストシステムにイベント駆動アプローチを適用するには,特定のタスクを処理するコンポーネント (またはオブジェクト) を定義する必要があります.

  • イベント - イベントは,イベント駆動システムの基本的なクラスユニットである. イベントループ内でどのように処理されるかを決定するタイプ (MARKET,SIGNAL,ORDER,FILLなど) を含む.
  • イベントキュー - イベントキューは,他のソフトウェアによって生成されるすべてのイベントサブクラスのオブジェクトを保存する内蔵 Python キューオブジェクトです.
  • DataHandler (データハンドラー) - DataHandlerは,歴史的なデータやライブデータの両方を処理するためのインターフェースを提供する抽象的なベースクラス (ABC) である.これは戦略とポートフォリオモジュールが両方のアプローチの間に再利用できるため,かなりの柔軟性を提供します. DataHandlerはシステムの毎回のハートビートで新しいマーケットイベントを生成します (以下参照).
  • 戦略 - 戦略はまた,市場データを収集し,最終的にポートフォリオオブジェクトによって利用される対応シグナルイベントを生成するためのインターフェースを提供するABCである.シグナルイベントには,ティッカーシンボル,方向 (LONGまたはSHORT) およびタイムスタンプが含まれます.
  • ポートフォリオ - これは,戦略の現在のおよび次のポジションに関連したオーダー管理を処理するABCです.また,セクター露出とポジションサイジングを含むポートフォリオ全体のリスク管理を行います.より洗練された実装では,これはリスクマネジメントクラスに委任できます.ポートフォリオはキューからSignalEventsを取り,キューに追加されるオーダーイベントを生成します.
  • ExecutionHandler - ExecutionHandlerは,ブローカージへの接続をシミュレートする.ハンドラーの仕事は,キューからオーダーイベントを取り,シミュレーション方法または肝臓ブローカージへの実際の接続を通じて実行することです.オーダーが実行されると,ハンドラーはフィールイベントを作成し,料金,佣金,スリップ (モデル化した場合) を含む実際に取引されたことを記述します.
  • ループ - これらのすべてのコンポーネントは,すべてのイベントタイプを正しく処理し,適切なコンポーネントにルーティングするイベントループに包まれている.

これは,取引エンジンの非常に基本的なモデルである.特にポートフォリオの使用方法に関して,拡張する大きな余地がある.また,異なるトランザクションコストモデルも,独自のクラス階層に抽象化され得る.この段階では,このシリーズ記事の中で不必要な複雑さを導入しているため,我々は現在それについてさらに議論しない.後のチュートリアルでは,追加のリアリズムを含むため,システムを拡張する可能性があります.

Python のコードのスニペットは,バックテストが実際にどのように動作するかを示しています.コードには2つのループがあります.外側のループは,バックテストに心拍を伝えるために使用されます.ライブ取引では,これは新しい市場データがアンケートされる頻度です.バックテスト戦略では,バックテストがドリップフィード形式で提供された市場データを使用しているため,これは厳密に必要ではありません (bars.update_bars (()) ラインを参照してください).

内ループは,実際にイベントキューオブジェクトからイベントを処理する.特定のイベントはそれぞれのコンポーネントに委任され,その後,新しいイベントがキューに追加される.イベントキューが空いているとき,ハートビートループは継続する:

# Declare the components with respective parameters
bars = DataHandler(..)
strategy = Strategy(..)
port = Portfolio(..)
broker = ExecutionHandler(..)

while True:
    # Update the bars (specific backtest code, as opposed to live trading)
    if bars.continue_backtest == True:
        bars.update_bars()
    else:
        break
    
    # Handle the events
    while True:
        try:
            event = events.get(False)
        except Queue.Empty:
            break
        else:
            if event is not None:
                if event.type == 'MARKET':
                    strategy.calculate_signals(event)
                    port.update_timeindex(event)

                elif event.type == 'SIGNAL':
                    port.update_signal(event)

                elif event.type == 'ORDER':
                    broker.execute_order(event)

                elif event.type == 'FILL':
                    port.update_fill(event)

    # 10-Minute heartbeat
    time.sleep(10*60)

これは,イベント駆動バックテストがどのように設計されるのかの一般的な概要です. 次の記事では,イベントクラス階層について議論します.


もっと