投資のためのデータサイエンス

個人の投資活動に役立つデータ分析にまつわる話題を綴ります。

Backtesting.pyによるバックテストの実行

本ブログでは主として株式投資に役立つデータ分析を扱っていますが、株価データをもとに株式の売買戦略を決めるにあたっては、その戦略のパフォーマンスの評価が重要になります。過去の株価データを用いてその売買戦略を適用した結果どのような損益になるかを評価する手法がバックテストです。

Pythonでバックテストを行えるツールはいくつかあるようですが、最も有名なものは"Backtesting.py"ライブラリでしょう。これに関しては海外のみならず日本語の記事も非常に多くあります。ここでは"Backtesting.py"ライブラリを用いた基本的なバックテストの方法について述べます。

まず必要なライブラリをインポートします。

import pandas as pd
import yfinance as yf
import talib as ta
from datetime import datetime, timedelta
import numpy as np
from pandas_datareader import data as pdr
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import SMA, GOOG, EURUSD
import warnings #警告をコントロールするライブラリを追加
warnings.filterwarnings("ignore") #警告を非表示に設定

次に、日本の株価データを取得します。

# Define the stock symbol and timeframe
symbol = '9432.T' # 9432はNTT
end_date = datetime(2023, 9, 30, 0, 0, 0)
start_date = end_date - timedelta(days=1800)  # 5 years before today
yf.pdr_override()
# yahooサイトからデータをダウンロード
stock_data = pdr.get_data_yahoo(symbol, start_date, end_date)

次に必要になるのは取引戦略のクラスの作成です。これはBacktesting.pyに備わっているStrategyというクラスを継承する形で記述します。クラスの中には指標を定義する関数init()と、売買戦略を定義する関数next()を記述します。ここでは指標の計算にTA-Libライブラリを用いていますが、自分で関数を定義することもできます。ここでの売買戦略は、長短移動平均ゴールデンクロスで買い、買いポジションがある時のデッドクロスで売りです。空売りは行わないので、売りについてはsell()でなくposition_close()を用いています。

class TalibCross(Strategy):
    tp1 = 10
    tp2 = 20
    def init(self):
        close = self.data.Close
        self.sma1 = self.I(ta.SMA, close, self.tp1)
        self.sma2 = self.I(ta.SMA, close, self.tp2)
    def next(self):
        if crossover(self.sma1, self.sma2):
            self.buy()
        elif crossover(self.sma2, self.sma1):
            self.position.close()    

次に、バックテスト実行のためのBacktestインスタンスを生成します。

bt = Backtest(stock_data, TalibCross, cash=1000000)

そして、run()で生成したインスタンスを実行し、結果を出力します(出力結果は一部のみ)。

output = bt.run()
print(output)

plot()メソッドでインタラクティブなチャートが表示されます。

bt.plot()

もう一つの重要な機能が「最適化」です。移動平均の窓の長さや(0-100という値をとる指標について)しきい値といったパラメータの値を動かして、目的関数を最適化するパラメータ値を探ります。以下では2つの移動平均のウィンドウの長さを動かして、出力される指標の一つである'Sharpe Ratio'を最大にするパラメータ値を探索しています。constraint=で探索時の制約条件を設けています。

output = bt.optimize(
    tp1 = range(4, 20, 1),
    tp2 = range(14, 30, 1),
    constraint=lambda p: p.tp1 < p.tp2,
    maximize = 'Sharpe Ratio')
print(output)

以下では最適化されたパラメータの値を確認しています。

print(output._strategy)

バックテストによるチューニングが将来の儲けには必ずしも結びつくわけではありませんが、売買戦略を評価するための強力なツールであるとはいえます。