LoginSignup
3
6

More than 3 years have passed since last update.

backtestingを使用した投資自動売買botの性能評価

Posted at

モチベーション

前回の記事では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にて確認できます

3
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
6