こんにちは、今日は僕が最近はまっている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
当たり前ですが、最適化処理をする前よりいい結果が出ました。
こんな感じで、バックテストと最適化について書きましたが、他にもバックテストのできるライブラリはあるので、次は違うのも使ってみたいなと考えています。