モチベーション
前回の記事ではOANDA REST APIを使用したFXの過去データ収集を行いました。
今回はそのデータをもとに、投資自動売買botの戦略評価の方法について記載します。
TL;DR → backtestingのドキュメント
やり方
環境構築
単純に↓だけです
pip install backtesting
説明いらない人向けコード
usd_jpy_4h
というDBに前回の記事で取得したデータが入っている想定で記載します。
またテクニカル分析用のパッケージであるtalib
も使用しています。こちらもpipで簡単に落とせます。
talibのドキュメントはこちら
import talib as ta
import pandas as pd
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
import db_access
def get_raw_data(query):
engine = db_access.get_engine()
return pd.read_sql(query, engine)
def get_macd(close, fastperiod=6, slowperiod=13, signalperiod=4):
macd, macdsignal, macdhist = ta.MACD(close, fastperiod=fastperiod, slowperiod=slowperiod, signalperiod=signalperiod)
return macd, macdsignal, macdhist
query = "select distinct * from usd_jpy_4h where datetime between '2019/09/01' and '2020/03/01' order by datetime"
raw_data = get_raw_data(query)
df = raw_data[["datetime","open","high","low","close","volume"]].copy()
df.columns = ['Datetime', 'Open', 'High', 'Low', 'Close', 'Volume']
df['Datetime'] = pd.to_datetime(df['Datetime'])
df = df.reset_index().set_index('Datetime')
class MacdStrategy(Strategy):
macd_fastperiod = 6
macd_slowperiod = 13
macd_signalperiod = 4
profit_pips = 50
loss_pips = 50
profit = profit_pips * 0.01
loss = loss_pips * 0.01
def init(self):
self.macd, self.signal, self.hist = self.I(get_macd, self.data.Close, self.macd_fastperiod, self.macd_slowperiod, self.macd_signalperiod)
def next(self):
if (crossover(self.signal, self.macd)):
self.buy(sl=self.data.Close[-1] - self.loss, tp=self.data.Close[-1] + self.profit)
elif (crossover(self.macd, self.signal)):
self.sell(sl=self.data.Close[-1] + self.loss, tp=self.data.Close[-1] - self.profit)
bt = Backtest(df, MacdStrategy, cash=100000, commission=.00004)
output = bt.run()
print(output)
output
Start 2019-09-01 20:00:00
End 2020-02-28 20:00:00
Duration 180 days 00:00:00
Exposure [%] 74.9074
Equity Final [$] 98647
Equity Peak [$] 101404
Return [%] -1.35297
Buy & Hold Return [%] 1.81654
Max. Drawdown [%] -2.7724
Avg. Drawdown [%] -0.518637
Max. Drawdown Duration 162 days 00:00:00
Avg. Drawdown Duration 21 days 18:00:00
# Trades 66
Win Rate [%] 50
Best Trade [%] 0.457344
Worst Trade [%] -0.68218
Avg. Trade [%] -0.0215735
Max. Trade Duration 10 days 00:00:00
Avg. Trade Duration 2 days 02:00:00
Expectancy [%] 0.275169
SQN -0.538945
Sharpe Ratio -0.0655224
Sortino Ratio -0.107821
Calmar Ratio -0.00778153
_strategy MacdStrategy
dtype: object
コード説明
inputデータ作成
raw_data = get_raw_data(query)
df = raw_data[["datetime","open","high","low","close","volume"]].copy()
df.columns = ['Datetime', 'Open', 'High', 'Low', 'Close', 'Volume']
df['Datetime'] = pd.to_datetime(df['Datetime'])
df = df.reset_index().set_index('Datetime')
ここでDBにあるデータを取得し、backtesting用の整形しています。
backtestinではpandasのDataframeで、カラム名が'Open', 'High', 'Low', 'Close', 'Volume'(なくてもOK)、となっていることを期待してます。
またdatetime型でindexをつけておくことを推奨しています。
https://kernc.github.io/backtesting.py/doc/examples/Quick%20Start%20User%20Guide.html
You bring your own data. Backtesting ingests data as a pandas.DataFrame with columns 'Open', 'High', 'Low', 'Close', and (optionally) 'Volume'. Such data is easily obtainable (see e.g. pandas-datareader, Quandl, findatapy, ...). Your data frames can have other columns, but these are necessary. DataFrame should ideally be indexed with a datetime index (convert it with pd.to_datetime()), otherwise a simple range index will do.
売買戦略作成
class MacdStrategy(Strategy):
macd_fastperiod = 6
macd_slowperiod = 13
macd_signalperiod = 4
profit_pips = 50
loss_pips = 50
profit = profit_pips * 0.01
loss = loss_pips * 0.01
def init(self):
self.macd, self.signal, self.hist = self.I(get_macd, self.data.Close, self.macd_fastperiod, self.macd_slowperiod, self.macd_signalperiod)
def next(self):
if (crossover(self.signal, self.macd)):
self.buy(sl=self.data.Close[-1] - self.loss, tp=self.data.Close[-1] + self.profit)
elif (crossover(self.macd, self.signal)):
self.sell(sl=self.data.Close[-1] + self.loss, tp=self.data.Close[-1] - self.profit)
売買戦略はfrom backtesting import Strategy
を継承して作成します。
今回はテクニカル指標の1つであるMacdがクロスした際に売買を行う戦略でプログラムを書いています。
投資戦略においては、ある時系列曲線が別の曲線と交差した際に発火する戦略が多いです。crossover()
を使用することでその戦略を簡単に記載することができます。
https://kernc.github.io/backtesting.py/doc/backtesting/lib.html#backtesting.lib.crossover
他にも便利な機能がたくさんあるのでぜひドキュメントを見てみてください。
変数の最適化
stats = bt.optimize(
macd_fastperiod = [6, 8, 10, 12],
macd_slowperiod = [13, 19, 26],
macd_signalperiod = [4, 7, 9],
profit_pips = [30, 50, 70, 90],
loss_pips = [20, 30, 40, 50]
)
Backtest.optimize()
を使用することで、売買戦略でしようした変数に対して最適な値・組み合わせをテストすることができます。
ここでは手である程度決めた値を定義していますが[i for i in range(10)]
みたいな形で定義しても良いかもしれません(処理重くなりそうですが)
最適パラメータはoptimize()実行後、stats._strategy
にて確認できます