こんにちは。TATです。
今日のテーマは「PythonのBacktesting.pyの使い方を解説!日本株でバックテストをしてみる」です。
PythonのBacktesting.pyというライブラリを使って、日本株のバックテストをしてみたので、その時の学びを備忘録がてらまとめておこうと思います。
基本的な使い方やつまづいた点や注意点など、Backtesting.pyの使い方をまとめます。
また、例として日本株のデータを使ってバックテストを実行してみます。
株やFXを行う際、バックテストはとても重要です。
考案した投資戦略が過去相場でにおいてどれくらいの利益を上げる事ができたのかを把握しておけば、今後のトレードに対してある程度のリスクを考慮する事ができます。
期待通りの結果にならなければ、戦略を見直したりパラメータを調整したりして改善することもできます。
いきなり実践トレードはリスクが高いので、まずはバックテストで過去相場における結果をシミュレーションしておく事が大事です。
Backtesting.pyはとってもシンプルなコードでバックテストを実行する事ができるのでおすすめです。
Backtesting.pyの基本的な使い方
まずはBacktesting.pyの基本的な使い方を見ておきます。
サンプルコードを走らせてみる
公式サイトにサンプルコードがあるので、こちらをそのまま走らせてみます。
from backtesting import Backtest, Strategy from backtesting.lib import crossover from backtesting.test import SMA, GOOG class SmaCross(Strategy): n1 = 10 n2 = 20 def init(self): close = self.data.Close self.sma1 = self.I(SMA, close, self.n1) self.sma2 = self.I(SMA, close, self.n2) def next(self): if crossover(self.sma1, self.sma2): self.buy() elif crossover(self.sma2, self.sma1): self.sell() bt = Backtest(GOOG, SmaCross, cash=10000, commission=.002, exclusive_orders=True) output = bt.run() bt.plot()
上記のコードを走らせると、バックテストを実行できます。
チャートは拡大・縮小したり、ホバーすると該当データが表示されたり、インタラクティブに触る事ができます。
チャートでバックテストの結果を時系列で確認できる
ここではGoogleの株価データを使って、移動平均線(10日と20日)のゴールデンクロスとデッドクロスを使って取引しています。
現金1万ドルで取引を開始して、手数料を0.2%に設定しています。
結果のチャートでは、取引結果を時系列に確認する事ができます。
ここからパフォーマンスが良い期間と悪い期間を把握して、戦略を改善したり、パラメータを調整したり、いろいろと活用できます。
結果の概要を確認する
outputを表示すると、バックテストの結果概要を確認できます。
print(output)
Returnが最終損益で、ここでは582.22%の利益です。
ただ、Buy & Hold Returnが約700%なので、ずっとホールドしていた方が利益が高かった事がわかります。
結果概要の項目の意味をまとめました
そのほかにもいろいろなデータを確認する事ができます。
こちらにできる限りまとめました。
Start | バックテスト開始日時 |
End | バックテスト終了日時 |
Duration | バックテスト期間 |
Exposure Time [%] | ポジションを保有していた期間の割合 |
Equity Final [$] | 最終的な資金 |
Equity Peak [$] | 資金の最大値 |
Return [%] | 損益 |
Buy & Hold Retun [%] | バイ&ホールドしていた場合の損益 |
Return (Ann.) [%] | 年間損益 |
Volatility (Ann.) [%] | 年間損益のボラティリティ |
Sharp Ratio | シャープレシオ リターンとリスク(標準偏差)の比から計算される 値が大きい方がいい |
Sortino Ratio | ソルティノレシオ 下落時のリスクを考慮して、リスクに見合ったリターンが得られるのかを判断する指標 値が大きい方がいい |
Calmar Ratio | カルマーレシオ 年間利益率と最大損失率の比で計算される 値が大きい方がいい |
Max. Drawdown [%] | 最大下落率 |
Avg. Drawdown [%] | 平均下落率 |
Max. Drawdown Duration | 最大下落期間 |
Max. Drawdown Duration | 平均下落期間 |
# Trades | 取引回数 |
Win Rate [%] | 勝率 |
Best Trade [%] | 1回の取引における最大利益 |
Worst Trade [%] | 1回の取引における最大損失 |
Avg. Trade [%] | 1回の取引のおける平均損益 |
Max. Trade Duration | 最大取引期間 |
Avg. Trade Duration | 平均取引期間 |
Profit Factor | 利益と損失の比 プラスなら利益が期待できる |
Expectancy [%] | 期待値 |
SQN | SystemQualityNumberの略らしい。。。 取引システムの分類(評価)方法の1つ目安の数字まとめました。とりあえず3くらいを目指せばいい。 # 1.6-1.9:平均以下 # 2.0-2.4:平均 # 2.5-2.9:良い # 3.0-5.0:素晴らしい # 5.1-6.9:最高 # 7.0~:神レベル |
_strategy | 使用したStrategyの関数名 |
_equity_curve | 資産推移データ |
_trades | 各取引データ |
コードの構成
サンプルコードと結果が確認できたので、次にコードの中身を確認していきましょう。
サンプルコードでは、backtestingのStrategyで取引戦略を定義して、Backtestでバックテストを実行しています。
また、テクニカル分析としてSMAで移動平均線を計算して、crossoverでゴールデンクロスとデッドクロスを判定しています。
GOOGはGoogleの株価データです。
結果を見てもわかるように、この株価データは古いので、最新の株価データでバックテストを行う際には、データを自分で用意する必要があります。
Strategyで取引戦略を作成
Stragetyで取引戦略のクラスを作成します。
クラスには、initとnextがあります。
initでインディケーターなどを指定
initは使用するインディケーターなどを指定します。
self.I(関数名、引数)でインディケーターを定義することができます。
ここで定義したものは、結果のチャートにも表示されます。
nextでは売買条件を指定
nextでは売買条件を指定する事ができます。
if文で条件を指定して、条件を満たしたらbuyで買い、sellで売りを注文する事ができます。
ここでよく使うものは次の3つかと思います。
3つの注文
- self.buy(): 買い
- self.sell(): 売り
- self.position.close(): 保有ポジションの手仕舞い
指値や逆指値などの売買条件も指定できる
また売買時にはいろいろな条件を指定することもできます。
売買条件
- size: 売買数 (日本株は100株単位なので、ここを100単位で指定する必要がある)
- limit: 指値
- stop: 逆指値
- tp: 利食い注文( take profitの略)
- sl: 損切り注文(stop lossの略)など
これらの売買条件を利用する際には、self.buy()やself.sell()で指定してあげればOKです。
例:self.buy(limit=1180, tp=1560, sl=1000)
Backtestでバックテストを実行
Backtestを使ってバックテストのクラスを定義して、run()でバックテストを実行します。
クラスの定義
Backtestのクラスを定義する際、必ず必要になるデータは次の2つです。
必須データ
- data:株価データ(サンプルコードではGOOG)
- strategy:Stretegyのクラス(サンプルコードではSmaCross)
株価データはindexを日時データとして、カラム名をOpen, High, Low, Close(順番は問わない)で指定する必要があります。小文字とかだとエラーになります。
そのほかにも指定できるデータがあります。
オプションデータ
- cash: バックテスト開始時の現金 デフォルトは10,000
- commission: 手数料 デフォルトで0.0 手数料が0.4%ならCommissionは0.004に設定する
- exclusive_orders: Trueなら売買条件を満たした当日の終値で注文、Falseなら翌日の始値で注文 デフォルトはFalse
- hedging: デフォルトはFalse Trueなら両建て注文を行う
- margin: 信用取引の設定 デフォルトは1(信用取引なし) 0.5なら2倍、0.1で10倍
run()で実行して結果を確認
Backtestのクラスを定義したら、run()でバックテスト実施して結果を確認する事ができます。
run()の返り値では結果概要が返ってきます。
plot()でチャートを表示します。
結果を見ながら適宜戦略を見直したりパラメータを調整したりしてパフォーマンスの改善を試みる事ができます。
Backtestのoptimize()でパラメータを最適化する
最後に最適化の機能も紹介しておきます。
上記のサンプルコードにはありませんが、Backtesting.pyには、optimize()というパラメータを最適化してくれる機能があります。
n1とn2を最適化してみる
こんな感じで、それぞれのパラメータの範囲を指定すると、利益が最大となる組み合わせを探してくれます。
ここではn1とn2をそれぞれ5日〜100日の範囲で5日刻みで検証しています。
output=bt.optimize(n1=range(5, 100, 5), n2=range(5, 100, 5)) print(output)
結局、先ほどと同じ結果のn1=10, n2=20が最も良い結果となりました。
bt.plot()
パラメータの最適化の際には便利です。
最適な組み合わせを効率的に見つけ出す事ができます。
最適化されたパラメータを確認する
outputの_strategyでパラメータを確認することができます。
パラメータが多いと、ここがきちんと表示できなくなってしまいます。
先ほどの結果だと、n1が10であることは確認できますが、n2の値は確認できません。
チャートで確認することもできますが、いちいちチャートで表示するのは面倒です。
この場合、_strategyから直接各パラメータにアクセスする事ができます。
print(output._strategy)
もし_strategyの値がきちんと表示されない場合はこのように直接データにアクセスして表示してみてください。
日本株でバックテストを実行してみる
Bactesting.pyの使い方が確認できたので、次に日本株のデータを使ってバックテストを行ってみます。
日本株の株価データの準備
まずは株価データの準備です。
例としてトヨタ自動車の2017年〜2021年の5年分の株価データを用意しました。
株価データの取得方法についてはこちらの記事で解説しています。
ここではpandas_datareaderを使って株価データを取得しました。
from pandas_datareader import data df = data.DataReader("7203.T","yahoo", start="2017-01-01", end="2021-12-31") df.head()
これで株価データの準備はOKです。
【日本株対応】Pythonで株価のローソク足データを取得する方法まとめ【CSV、ライブラリ、スクレイピング】
続きを見る
売買戦略
次に売買戦略を考えます。
ここでは次の2つの戦略を試してみます。
売買戦略
- 2つの移動平均線のゴールデンクロスとデッドクロスを使った戦略(サンプルコードと同じもの)
- 3つの移動平均線(短期、中期、長期)を使った戦略: 長期移動平均線でトレンドを把握して短期途中期移動平均線で売買タイミングを判定する
1つ目はサンプルコードと同じものです。
日本株(トヨタ)のデータに置き換えて試してみます。
2つ目は3つの移動平均線を使った戦略です。
長期の移動平均線でトレンドを把握しながら、短期と中期の移動平均線で売買タイミングを探っていきます。
こちらを実装する際には、Backtestingのcrossoverは利用せずに、ゴールデンクロスとデッドクロスを判定するロジックから自分で作ってみます。
オリジナル戦略でバックテストを実装したい場合は、こちらのコードを参考にしていただけたらと思います。
戦略1: 2つの移動平均線のゴールデンクロスとデッドクロスを使った戦略
戦略1はサンプルコードにあったものと同じです。
先ほどはGoogleの株価データで試しましたが、次はトヨタの株価データを使って試してみます。
GOOGをdfに置き換えればOkです。
from backtesting import Backtest, Strategy from backtesting.lib import crossover from backtesting.test import SMA, GOOG, EURUSD class SmaCross(Strategy): n1 = 10 n2 = 20 def init(self): close = self.data.Close self.sma1 = self.I(SMA, close, self.n1) self.sma2 = self.I(SMA, close, self.n2) def next(self): if crossover(self.sma1, self.sma2): self.buy() elif crossover(self.sma2, self.sma1): self.sell() bt = Backtest(df, SmaCross, cash=10000, commission=.002, exclusive_orders=True) output = bt.run() bt.plot(filename="backtesting_7203_strategy1.html")
損失が目立ちますね。
結果の概要も見てみます。
print(output)
最終損益は−50%というひどい結果になりました。
基本的に、移動平均線のゴールデンクロスやデッドクロスを使うトレードではトレンドが大事になってきます。
特にレンジ相場ではうまく機能しないケースが多いです。
トヨタの株価を見ると、ずっと横ばいでパッとしません。
後半は上がっていますが、それ以前は横ばいです。
こういったレンジ相場だと、この戦略は厳しいです。
別の戦略を使うか、レンジ相場では取引しないロジックを加えるなどの工夫が必要になります。
戦略2: 3つの移動平均線(短期、中期、長期)を使った戦略
次に、3つの移動平均線を使った戦略についてバックテストを行ってみます。
戦略の詳細
僕の方で適当に考えた戦略をこちらにまとめます。
売買戦略詳細
- 移動平均線は10日、20日、100日を使う
- 100日移動平均線の傾きでトレンドを把握して、上向きで上昇トレンド、下向きなら下落トレンドと判定する
- 10日、20日移動平均線が100日移動平均線を上回っている状態でゴールデンクロスが発生したらエントリー
- エントリーは直近ローソク足の高値で指値、直近終値の8%下落で損切り、15%で利食いで注文
- 10日、20日移動平均線でデッドクロスが発生したらポジションを強制的にクローズ
Strategyを実装 〜なるべく関数を自作してみる〜
次にStrategyを実装していきます。
ここでは先ほど使ったcrossoverみたいな関数は使わずに、別途ゴールデンクロスやデッドクロスを判定する関数を作ったりしながら、できる限り自作していきます。
オリジナル戦略でバックテストを行いたい場合は、ここで紹介するコードが少しはお役に立てるかもです。
from backtesting import Backtest, Strategy import pandas as pd import numpy as np def moving_average(x, n): return pd.Series(x).rolling(window=n).mean() def find_cross(short, long): diff = np.where(pd.Series(short) - pd.Series(long)>0, 1, 0) return pd.Series(diff).diff() def trend(x): return np.where(pd.Series(x).diff()>0, 1, 0) class Strategry(Strategy): n1 = 10 n2 = 20 n3 = 100 sl = 0.92 tp = 1.15 def init(self): close = self.data.Close self.sma1 = self.I(moving_average, close, self.n1) self.sma2 = self.I(moving_average, close, self.n2) self.sma3 = self.I(moving_average, close, self.n3) self.cross = self.I(find_cross, self.sma1, self.sma2) self.trend = self.I(trend, self.sma3) def next(self): price = self.data.Close[-1] limit = self.data.High[-1] if self.cross==1 and self.trend==1 and self.sma1>self.sma3 and self.sma2 > self.sma3: self.buy(limit=limit, sl=price*self.sl, tp=price*self.tp, size=100) elif self.cross==-1: self.position.close()
これで準備完了です。
移動平均線を計算するmoving_averange、ゴールデンクロス・デッドクロスを判定するfind_cross、トレンドを判定するtrendという関数を定義しました。
エントリーでは、andで複数の条件を考慮しています。
日本株なので売買単位を100株として、損切りラインや利食いラインの設定も行いました。
バックテストを実行
それではバックテストを実行します。
キャッシュは100万円にしました。
手数料は先ほどと同じく0.2%です。
bt = Backtest(df, Strategry, cash=1000000, commission=.002, exclusive_orders=False) output = bt.run() bt.plot()
プラスが多いような感じもしますが、結果の概要を見るとわずかにプラスでした。
+1.7511%なんでほぼお金増えないですね。。。
print(output)
optimizeで長期の移動平均線を最適化してみる
次に少しパラメータを調整してみます。
長期の移動平均線の値をいじってみましょう。
元々は100日としていましたが、50日~200日で10日刻みで検証してみました。
output = bt.optimize(n3=range(50, 200, 10)) bt.plot()
結果を見ると長期の移動平均線は60日になっていますね。
結果概要を見てみましょう。
print(output)
最終損益は、2.84189%、、、あまり改善していませんね。
先ほどに比べると損益は増えましたが、それでもまだまだです。
まだまだ改善の余地しかなさそうですw
資産推移を可視化する
最後に資産推移も見ておきましょう。
outputの_equity_curveには、資産推移データが入っています。
output._equity_curve.head()
このEquityカラムをグラフにしてあげると、資産推移を確認できます。
output._equity_curve["Equity"].plot()
ご覧の通り、ほぼ横ばいですね。
結果は残念ですが、とりあえずbacktesting.pyの使い方の参考になれていれば嬉しいです。
まとめ
本記事では、Pythonでバックテストを実行できるライブラリであるBacktesting.pyについて解説し、日本株によるバックテストを実際に実装してみました。
指値や逆指値などの売買条件を自由に設定でき、バックテストの結果もチャートで細かく分析できるのでとても便利です。
考案した売買戦略をいきなり実践で使うのはリスクが高いので、まずはBacktesting.pyなどのライブラリを活用してバックテストを試してみることをお勧めします。
この結果を踏まえて、パラメータを調整したり、別の戦略を考えたり、実践前にある程度作戦を練る事ができます。
このように、Backtesting.pyを使えば、株のバックテストを簡単に実装する事ができます。
オリジナルの戦略でもバックテストできるので、みなさんもいろいろな戦略で試してみてはいかがでしょうか。
そしていけてる戦略見つけたら教えてくださいw
ここまで読んでくださり、ありがとうございました。