Backtesting.pyでバックテスト OANDA

はじめに

元ネタは以下の記事です。
OANDA APIのバージョンが上がっていて、V2でなければAPI接続できないため、
こちらの記事を参考にV2バージョンで実装してみました。

【fx、python】Backtesting.pyでバックテストしてみる | たぬきねこのAI&FX研究室

環境

  • Windows10
  • Python 3.8.1
  • pip 20.1.1
  • oandapyV20==0.6.3
  • pandas==1.0.1
  • Backtesting==0.1.7

Backtestingのインストール

pipでインストール

pip install backtesting

Backtesting.py - Backtest trading strategies in Python

バックテストの実装

公式のサンプルコードを元に実装していきます。

ライブラリのインポート

from datetime import datetime

import oandapyV20
import oandapyV20.endpoints.instruments as instruments
import pandas as pd
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import SMA

必要に応じてライブラリはpip等でインストールしておきます。

pip install oandapyV20 pandas

バックテスト用のデータ抽出(OANDA API

OANDA APIを用いてデータを取得します。
ここでは日足を指定します。

oanda = oandapyV20.API(environment='live', access_token='APIキー')
params = {"count": 1000, "granularity": "D"}
r = instruments.InstrumentsCandles(instrument="USD_JPY", params=params,)
oanda.request(r)

「instruments.InstrumentsCandles」がOANDA APIのV1では「get_history」に相当するAPIです。

Dataframeに変換

backtesting.pyで用いるデータはpandasのDataFrameでなければいけないので、
取得したデータをDataFrameに変換します。

# 取得したデータをpandasのDataFrameに変換
df = pd.DataFrame(r.response['candles'])


# 'time', 'Open', 'High', 'Low', 'Close', 'Volume'を抽出
data = []
for raw in r.response['candles']:
    data.append([raw['time'], float(raw['mid']['o']), float(raw['mid']['h']), float(raw['mid']['l']), float(raw['mid']['c']), float(raw['volume'])])

# リストからPandas DataFrameへ変換
df_ohlc = pd.DataFrame(data)
# backtesting.pyのデータに合わせてcolumns名を設定
df_ohlc.columns = ['time', 'Open', 'High', 'Low', 'Close', 'Volume']
# インデックスを変更する
df_ohlc = df_ohlc.set_index('time')
# 時間をdatetime型に変更
df_ohlc.index = pd.to_datetime(df_ohlc.index)

テストに使う戦略(Strategy)

公式のサンプルを使います。

from backtesting import Backtest, Strategy
from backtesting.lib import crossover

from backtesting.test import SMA

class SmaCross(Strategy):
    n1 = 10
    n2 = 30

    def init(self):
        self.sma1 = self.I(SMA, self.data.Close, self.n1)
        self.sma2 = self.I(SMA, self.data.Close, self.n2)

    def next(self):
        if crossover(self.sma1, self.sma2):
            self.buy()
        elif crossover(self.sma2, self.sma1):
            self.sell()

バックテストの実行

from backtesting import Backtest

# バックテストの実行
bt = Backtest(df_ohlc, SmaCross, cash=10000, commission=.002)
output = bt.run()
print(output)
bt.plot()

実行するとコンソールに下記のような結果が出力され、
ブラウザにグラフがプロットされます。

Start                     2016-08-15 21:00:00+00:00
End                       2020-05-28 21:00:00+00:00
Duration                         1382 days 00:00:00
Exposure [%]                                95.4414
Equity Final [$]                            8707.69
Equity Peak [$]                             11379.8
Return [%]                                 -12.9231
Buy & Hold Return [%]                       7.48993
Max. Drawdown [%]                          -23.6653
Avg. Drawdown [%]                          -2.95408
Max. Drawdown Duration           1261 days 00:00:00
Avg. Drawdown Duration            132 days 00:00:00
# Trades                                         42
Win Rate [%]                                28.5714
Best Trade [%]                              9.97924
Worst Trade [%]                            -3.57319
Avg. Trade [%]                            -0.306852
Max. Trade Duration                99 days 00:00:00
Avg. Trade Duration                32 days 00:00:00
Expectancy [%]                              1.39783
SQN                                       -0.911754
Sharpe Ratio                              -0.141554
Sortino Ratio                             -0.342624
Calmar Ratio                             -0.0129663
_strategy                                  SmaCross

f:id:katutoki:20200531211508p:plain

ソースコードの全体

from datetime import datetime

import oandapyV20
import oandapyV20.endpoints.instruments as instruments
import pandas as pd
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import SMA

# OANDA APIを用いてデータの取得
oanda = oandapyV20.API(environment='live', access_token='APIキー')
params = {"count": 1000, "granularity": "D"}
r = instruments.InstrumentsCandles(instrument="USD_JPY", params=params,)
oanda.request(r)

# 取得したデータをpandasのDataFrameに変換
df = pd.DataFrame(r.response['candles'])

# 'time', 'Open', 'High', 'Low', 'Close', 'Volume'を抽出
data = []
for raw in r.response['candles']:
    data.append([raw['time'], float(raw['mid']['o']), float(raw['mid']['h']), float(raw['mid']['l']), float(raw['mid']['c']), float(raw['volume'])])

# リストからPandas DataFrameへ変換
df_ohlc = pd.DataFrame(data)
# backtesting.pyのデータに合わせてcolumns名を設定
df_ohlc.columns = ['time', 'Open', 'High', 'Low', 'Close', 'Volume']
# インデックスを変更する
df_ohlc = df_ohlc.set_index('time')
# df_ohlc.head()

# # date型を綺麗にする
df_ohlc.index = pd.to_datetime(df_ohlc.index)
# df_ohlc.tail()


# 売買戦略
class SmaCross(Strategy):
    n1 = 10
    n2 = 30

    def init(self):
        self.sma1 = self.I(SMA, self.data.Close, self.n1)
        self.sma2 = self.I(SMA, self.data.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_ohlc, SmaCross, cash=10000, commission=.002)
output = bt.run()
print(output)
bt.plot()

おまけ

instruments.InstrumentsCandlesの引数"granularity"に指定する値を変えることによって、
様々な時間軸の情報を取得することができます。

詳細は下記ページに載っています。 レート | OANDA API

  • “S5” - 5 秒
  • “S10” - 10 秒
  • “S15” - 15 秒
  • “S30” - 30 秒
  • “M1” - 1 分
  • “M2” - 2 分
  • “M3” - 3 分
  • “M5” - 5 分
  • “M10” - 10 分
  • “M15” - 15 分
  • “M30” - 30 分
  • “H1” - 1 時間
  • “H2” - 2 時間
  • “H3” - 3 時間
  • “H4” - 4 時間
  • “H6” - 6 時間
  • “H8” - 8 時間
  • “H12” - 12 時間
  • “D” - 1 日
  • “W” - 1 週
  • “M” - 1 か月

もしgranularityパラメータが設定されなかった場合は、granularity のデフォルト値は”S5”となります。