pypy.com/

Python、Unity、FX自動化などを勉強しています。あと、コーラと車も好きです。そこらへんについて、たまに記事を書きます。

pythonでfxのバックテストと最適化を行う方法

こんにちは、今日は僕が最近はまっているpythonでのfx解析について、少し書きたいと思います。

自動取引で利益を追求する際は、取引のアルゴリズムももちろん大事なのですが、そこについて書かれている方はたくさんいらっしゃると思うので、アルゴリズムを決めた後の最適化処理をpythonで行う方法について書きます。

使うのはbacktestingというライブラリで、機能はそこまで豊富ではないのですが、使いやすいのでお勧めです。

まず、ライブラリのインストールですが、これは普通にコマンドプロンプトなどで

pip install backtesting

と打てば、インストールできます。

次に、今回はバックテストを行うので、過去データを持ってきます。
データはどこから入手してもいいのですが、apiを使ってとってきやすいoandaのデータを使います。
今回は詳しく書きませんが、以下のようにして、データを取得できます。

import oandapy
import pandas as pd
import configparser

# 設定
config = configparser.ConfigParser()
config.read('C:/config/config_v1.txt')  # パスの指定
account_id = config['oanda']['account_id']
api_key = config['oanda']['api_key']

# APIへ接続
oanda = oandapy.API(environment="practice", access_token=api_key)

# 過去レートを取得 
# 定数
TARGET = "USD_JPY"  # 通貨ペア
TIME_CONF = "H1"     # 時間足 (5秒足:'S5', 10秒足:'S10', 1分足:'M1', 15分足:'M15', 1時間足:'H1', 日足:'D' 等)
COUNT = 5000           # count
COUNT2 = 1          # count * count2 分データを取得
 
end = None
df = None
 
for i in range(COUNT2):
    print(i)
    if i == 0 and end is None:
        hist = oanda.get_history(
            instrument=TARGET, granularity=TIME_CONF, count=COUNT)
    else:
        hist = oanda.get_history(instrument=TARGET, granularity=TIME_CONF, end=end, count=COUNT)
 
    hist = hist["candles"]
    end = hist[0]['time']   
    df_new = pd.DataFrame(hist)
    if df is None:
        df = df_new
    else:
        df = pd.concat([df_new, df],ignore_index=True)

これで、過去のデータが取得できましたので、次はライブラリに適する形にデータを整理します。

from datetime import datetime

df["time"] = pd.to_datetime(df["time"])
# backtesting.pyに必要なデータだけ取り出す
df = df[["time","openAsk","highAsk","lowAsk","closeAsk","volume"]].copy()
# backtesting.pyのデータに合わせてcolumns名を変更
df.columns = ['', 'Open', 'High', 'Low', 'Close', 'Volume']


次にテクニカル指標用の関数を定義します。
テクニカル指標の計算は自分でしてもよいのですが、talibというライブラリを使うと簡単に計算できます。
今回はボリンジャーバンドadxを使います。
以下のように書いておきます。

import talib as ta

def bb(array, n):
    gain=pd.DataFrame(array)
    gain.columns=['close']
    upper2, middle, lower2 = ta.BBANDS(gain.close, n,2,2,0)
    gain['bb_upper'] = upper2
    gain['bb_lower'] = lower2
    return gain['bb_upper'],gain['bb_lower']

def adx(array,n):
    gain=pd.DataFrame(array)
    gain=gain.T
    gain.columns=['close','high','low']
    gain['adx'] = ta.ADX(gain['high'],gain['low'],gain['close'],n)
    return gain['adx']


つぎに、バックテスト用のアルゴリズムを書きます。
今回は、単純にトレンドが弱い(レンジ相場)時にボリンジャーバンドの±2σの範囲外になったら、逆張りをします。
以下のように書きます。

from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import SMA, GOOG
from pandas.core import resample

class bb_band(Strategy):
    n = 25
    n2 = 20
    diff1 = 30
    adx_h = 20
    
    def init(self):
        self.bb_upper2,self.bb_lower2 = self.I(bb,self.data.Close,self.n)
        self.adx=self.I(adx,[self.data.Close,self.data.High,self.data.Low],self.n2)

    def next(self):
        diff=0.01*self.diff1
        #ポジション確認(long:1,nun:0,short:-1)
        position=0
        if(self.position):
            if(self.position.is_long):
                position=1
                if(self.position.open_price+diff<self.data.Close):
                    self.position.close
                    position=0
            elif(self.position.is_short):
                position=-1
                if(self.position.open_price-diff>self.data.Close):
                    self.position.close
                    position=0
        #レンジ相場であることを確認
        if(self.adx<self.adx_h):
            #+σ2より大きいとき,売り
            if(self.data.Close>self.bb_upper2):
                if(position==0):
                    self.sell()
                if(position==1):
                    self.position.close
                    self.sell()
            #-σ2より小さいとき、買い
            elif(self.data.Close<self.bb_lower2):
                if(position==0):
                    self.buy()
                if(position==-1):
                    self.position.close
                    self.buy()

あとは、実行するだけです。
以下の感じに書きます。

bt = Backtest(df, bb_band, cash=10000, commission=.002)
output = bt.run()
print(output)

結果が以下のように出力されます。

Start                             0
End                            4999
Duration                       4999
Exposure [%]                96.4993
Equity Final [$]            8709.97
Equity Peak [$]               10000
Return [%]                 -12.9003
Buy & Hold Return [%]       2.36359
Max. Drawdown [%]          -12.9943
Avg. Drawdown [%]               NaN
Max. Drawdown Duration          NaN
Avg. Drawdown Duration          NaN
# Trades                         70
Win Rate [%]                41.4286
Best Trade [%]              2.20541
Worst Trade [%]            -1.48432
Avg. Trade [%]             -0.17448
Max. Trade Duration             262
Avg. Trade Duration         68.9143
Expectancy [%]             0.394612
SQN                        -2.69706
Sharpe Ratio              -0.315865
Sortino Ratio             -0.422461
Calmar Ratio             -0.0134275
_strategy                  bb_band
dtype: object

Equity Final [$] 8709.97
となっていて、これが最終的に持っている資金なので、損をしますね。
これを最適化してみます。

output2=bt.optimize(n=range(2, 40, 5),n2=range(2, 40, 5),diff1=range(10, 50, 10), adx_h=range(10, 50, 5))
print(output2)
bt.plot()

こんな感じでbt.optimizeで最適化できます。
最後の行のbt.plot()でグラフを出せます。
結果は以下の通りです。

  output2=bt.optimize(n=range(2, 40, 5),n2=range(2, 40, 5),diff1=range(10, 50, 10), adx_h=range(10, 50, 5))
Start                                                         0
End                                                        4999
Duration                                                   4999
Exposure [%]                                            58.7317
Equity Final [$]                                        10522.3
Equity Peak [$]                                         10533.3
Return [%]                                              5.22268
Buy & Hold Return [%]                                   2.36359
Max. Drawdown [%]                                      -2.32512
Avg. Drawdown [%]                                     -0.371903
Max. Drawdown Duration                                      810
Avg. Drawdown Duration                                  87.2128
# Trades                                                      2
Win Rate [%]                                                100
Best Trade [%]                                          2.09388
Worst Trade [%]                                         1.44702
Avg. Trade [%]                                          1.77045
Max. Trade Duration                                        1900
Avg. Trade Duration                                        1468
Expectancy [%]                                              NaN
SQN                                                     5.79478
Sharpe Ratio                                             3.8707
Sortino Ratio                                               NaN
Calmar Ratio                                           0.761442
_strategy                 bb_band(n=17,n2=2,diff1=10,adx_h=40)
dtype: object

f:id:SaidaTaisei:20191013003129p:plain

当たり前ですが、最適化処理をする前よりいい結果が出ました。

こんな感じで、バックテストと最適化について書きましたが、他にもバックテストのできるライブラリはあるので、次は違うのも使ってみたいなと考えています。