今日は、Pythonで特定の投資ストラテジーのバックテストを行うために使えるBaseStrategyクラスなるものを実装したので、それを記していきます。
追記2022/03/22:おそらく私の独自のスクリプトを用いるよりも、既存ライブラリを用いたほうが爆速でバックテストができるのでそちらを使うことをお勧めします。
BaseStrategyクラス
BaseStrategyクラスでは、投資ストラテジーのバックテストに必要な処理を汎用的に使えるようにまとめています。(まだ課題あり)
ポイントとしては、処理の記述が面倒くさいバックテストの部分を汎用化しているので、ストラテジーをバックテストする際にラクに行えます。
コードはGitHub Gistに公開しているので、そちらでご覧いただけます。
BaseStrategyの使い方
使い方はシンプルです。
- ストラテジークラスを作成し、継承する
- バックテストをしたいタイミングでクラスを使う
といった感じで使えます。
BaseStrategyを使ったサンプルコード
BaseStrategyクラスを継承したパターンは以下の通りです。
import pandas as pd
import numpy as np
import talib as ta
from strategy.base.base_strategy import BaseStrategy
class SimpleMAStrategy(BaseStrategy):
def __init__(self, sma_length, initial_capital, force_stop):
super().__init__(initial_capital, force_stop)
self.sma_length = sma_length
def prepare(self, data=None, multiplier=1):
super().prepare()
df = data.copy()
# - 終値が移動平均線をX*MA以上を下回ったらLong
# - 終値が移動平均線をX*MA以上を上回ったらShort
df["MA"] = ta.SMA(df["close"], self.sma_length)
# Entry logic
df["long_entry_signal"] = np.where(-df["close"] + df["MA"] > df["MA"]*multiplier, 1, 0)
df["long_entry_at"] = np.where(df["long_entry_signal"], df["close"].shift(-1), 0) # long at next close price when long signal occurs
df["short_entry_signal"] = np.where(df["close"] - df["MA"] > df["MA"]*multiplier, 1, 0)
df["short_entry_at"] = np.where(df["short_entry_signal"], df["close"].shift(-1), 0) # short at next close price when short signal occurs
# Exit logic
df["long_exit_signal"] = np.where(df["close"] > df["MA"], 1, 0) # short at next close price when short signal occurs
df["long_exit_at"] = np.where(df["long_exit_signal"], df["close"].shift(-1), 0)
df["short_exit_signal"] = np.where(df["close"] < df["MA"], 1, 0) # long at next close price when long signal occurs
df["short_exit_at"] = np.where(df["short_exit_signal"], df["close"].shift(-1), 0)
return df
#使う場合
strategy = SimpleMAStrategy(initial_capital=1000, force_stop=0.1)
processed = strategy.prepare(data, multiplier=2)
result = strategy.simulate(processed)
必要箇所でBaseStrategyクラスを呼び出す場合は以下の通りです。
import pandas as pd
import numpy as np
import talib as ta
from strategy.base.base_strategy import BaseStrategy
strategy = BaseStrategy(initial_capital=1000, force_stop=0.1)
already_processed = pd.read_csv("processed_data.csv")
result = strategy.simulate(already_processed)
上記2種類の方法でご活用いただけますが、継承することを想定して作成しました。
BaseStrategyメソッドの内容
BaseStrategyクラスの内容としては、いくつかありますが一番重要なのはsimulateメソッド
です。
これを用いれば、加工済みのデータを使ってトレード戦略のシュミレーションが行えます。
注意としては、ロングポジションを保持している場合は、ショートポジションを実行しないようになっています。
なので、両建ては行えない状態です。必要な場合は、ロジックの改良が必要になります。
加工済みのデータに必要なもの
コードを見ていただければわかるかと思いますが、simulateメソッドでバックテストを行うには以下の情報が必要になります。
- long_entry_signal:買いエントリーシグナル(0, 1)
- long_exit_signal:買い手仕舞いシグナル(0, 1)
- long_entry_at:買いエントリー価格(float64)
- long_exit_at:買い手仕舞い価格(float64)
- short_entry_signal:空売りエントリーシグナル(0, 1)
- short_exit_signal:空売り手仕舞いシグナル(0, 1)
- short_entry_at:空売りエントリー価格(float64)
- short_exit_at:空売り手仕舞い価格(float64)
上記のデータを持ったDataFrameをsimulateメソッドに渡してください。
オプションとして
BaseStrategyのオプションとして、初期資金(initial capital)と強制ストップ(force_stop)の設定を行なえます。
読んで字のごとくですが、バックテストを行う際の初期資金と強制ストップ(初期資金からの損失の許容ライン)をクラスのインスタンス化時に渡して使えます。
所感・ディスカッション
BaseStrategyクラスの改善ポイントはいくつかあります。
- リファクタ(ドキュメントやリーダビリティ向上)
- iterrows()の処理が遅いので、別処理に置き換える
- 他の汎用メソッドを実装
- バグの有無確認(基本的なテストは完了済み)
上記については、随時対応していこうと思います。
まとめ
今回は自身で作成した投資ストラテジーのバックテストに使えるクラスを紹介しました。
軽く調べてもあまり出てこなかったので、自分で作ってみました。
個々のロジックが間違うと正確にバックテストできないので、少し苦労しましたが、良い経験となりました。
もし、バックテストのコードの作成に苦労されている、探されている方のお役に立てれば幸いです。