この記事では、機械学習を用いてFX自動売買の精度を高める実践的方法をご紹介します。
機械学習モデルのサンプルコードを提示して、なんとなく将来価格を予測してグラフを描いて終わり、というわけではなく、学習したモデルを実際のトレード戦略に取り入れてバックテストを行うところまで進みます。
また、機械学習モデルの予測対象としては、単純な価格予測ではなく、一定の売買戦略に基づいた勝敗を目的変数(y)としますので、目的変数の定め方の参考にもなるかと思います。
LightGBMモデルを用いた機械学習において、
- インプット(特徴量)の調整
- 目的変数(y)の設定
- 学習用データの調整
- 学習したモデル使って売買フラグを生成する
- 損益シミュレーションを行ってバックテスト結果を示す
という一連の流れまで例示しますので、FX自動売買で機械学習を取り入れたいという方におすすめです。
この記事で作成される成果物
まずは、この記事で作成される成果物について取り上げます。
理想的な損益グラフ
例えば、以下のような理想的な損益グラフを描けるEAがあったら素晴らしいですね。
ただし、これはあくまで理想であって、このような”理想的な損益グラフを描ける結果”を”目的変数(機械学習で予測したい対象)”に設定して学習を行っていくというのが、今回試してみることです。
バックテスト結果(機械学習なし)
例えば、機械学習モデルを全く使わずにテクニカル指標による売買判断のみで損益シミュレーションを行った結果は以下の通りになりました。
バックテスト結果(機械学習あり)
そして、LightGBMという機械学習モデルを用いて勝率を高める試みを行いました。その結果は以下の通りです。
以上のことを、この記事では各ステップ毎に、もう少し具体的に説明していきます。
学習用データの準備から結果検証までの流れ
以下、今回行う機械学習全体の流れを順を追って説明していきます。
LightGBMとは
LightGBMは、勾配ブースティング決定木 (Gradient Boosting Decision Tree, GBDT) を実装した機械学習ライブラリの一つです。高速で精度が高く、大規模なデータにも対応できるため、多くの機械学習問題で広く用いられています。今回は、このモデルをFXの自動売買に応用してみるという試みです。
テクニカル指標の計算
今回使用する売買判断基準は、以下の記事でも紹介しているADM-EA(分解モンテカルロ法によるロット数調整を取り入れた非ナンピン型EA)をベースにします。Inputデータの準備部分については、より詳しく説明していますので、ぜひ合わせてご覧ください。
テクニカル指標としては、ADM-EAと同様にMA、DMI、ATRの3つを使用します。
以下のような関数を定義して作成します。
def calc_indicators(df):
#四本値の取得
open = df['Open']
high = df['High']
low = df['Low']
close = df['Close']
#テクニカル指標の計算
df['MA'] = ta.sma(close, timeperiod=14)
df['DMP'] = ta.adx(high, low, close)["DMP_14"]
df['DMN'] = ta.adx(high, low, close)["DMN_14"]
df['ATR'] = ta.atr(high, low, close, timeperiod=14)
return df
目的変数(y)の設定
FX自動売買の機械学習において目的変数(y)をどのように設定するのかというのは重要な部分です。単純に価格自体を目的変数にするということも可能ですが、それを実際のトレード戦略に採用するとなかなか上手くいかないことも多いです。
そこで今回は、以下のように考えてみます。
- 今、買いポジションを取ったら、将来的に利確になるかロスカットになるか
- 今、売りポジションを取ったら、将来的に利確になるかロスカットになるか
tickデータおよびbarデータを用いれば、過去時点の将来の結果というのは取得出来るので、その勝敗結果を答えとして設定し、それを予測ターゲットとするわけです。
以下は、tickデータとbarデータを連結してyを設定する関数のコード抜粋です。
if (tick_bid[i] - tick_ATR_H1[i] * sl_factor > bar_Low_M1[M1_j] - tick_spread[i] or
tick_bid[i] + tick_ATR_H1[i] * tp_factor < bar_High_M1[M1_j] - tick_spread[i]):
tick_buy_y[i] = 0
else:
for k in range(M1_j+1, len_bar_M1):
if (tick_bid[i] - tick_ATR_H1[i] * sl_factor > bar_Low_M1[k] - tick_spread[i] and
tick_bid[i] + tick_ATR_H1[i] * tp_factor < bar_High_M1[k] - tick_spread[i]):
tick_buy_y[i] = 0
break
elif tick_bid[i] - tick_ATR_H1[i] * sl_factor > bar_Low_M1[k] - tick_spread[i]:
tick_buy_y[i] = 2
break
elif tick_bid[i] + tick_ATR_H1[i] * tp_factor < bar_High_M1[k] - tick_spread[i]:
tick_buy_y[i] = 1
break
else:
tick_buy_y[i] = 0
現在時刻の価格(bid)をベースに、その時刻以降の1分足の高値(High)と安値(Low)を順番に見ていって、利確幅とロスカット幅にどちらに先に到達するかを確認しています。これにより、その瞬間にbuyポジションをエントリーするということが、将来的に勝ちと負けのどちらにつながるかを決定します。勝ちであればy=1、負けであればy=2です。1分以内にどちらの幅にも到達してしまうなど、判定不能な場合はy=0として引き分けとします。
y=0,1,2のいずれかを取るので三値分類モデルを作成して機械学習を行なっていくことになります。
そうして、以下のようなデータを作成します。これが学習用の目的変数(y)データになります。
tickデータのtimeごとにbuy_yとsell_yという目的変数がセットされています。例えば1レコード目を見ますと、buy_y=1(ここでbuyエントリーしたら将来的に利確)、sell_y=2(ここでsellエントリーしたら将来的にロスカット)となっています。このbuy_yやsell_yを予測するのが機械学習モデルの役割になります。
学習用データの選定
今回は、2022年1月〜2022年12月までの1年間を学習データとして用いて、2023年1月〜2023年5月までの5ヶ月間に学習したモデルを適用して検証を行なっています。
1年分というと学習データとして少なそうに感じますが、tickデータ自体は1ヶ月分だけで500万レコードくらいありますので、1年分ですと約6,000万レコードにもなります。学習データは多ければ良いというのはありますが、学習効率も意識する必要はあるので、どのようにデータを選定するかというのは課題になります。
そこで、テクニカル指標を用いて、”ある条件”を満たす場合のデータに限定することにしました。学習するデータ自体をテクニカル指標であらかじめ限定してしまうんですね。この方法で果たして効果が現れるのかというのも今回の検証内容になります。
”ある条件”というのは、以下の条件です。(buyモデルの場合)
- MA(1分足)よりも現在価格が0.060だけ下に乖離している
- 1分足、5分足、15分足、1時間足の全てで、+DM > -DMとなっている
該当部分のコードを抜粋すると以下の通りです。
entry_range = 0.060
df = df[df['MA_M1'] - df['bid'] > entry_range]
df = df[df['DMP_M1'] > df['DMN_M1']]
df = df[df['DMP_M5'] > df['DMN_M5']]
df = df[df['DMP_M15'] > df['DMN_M15']]
df = df[df['DMP_H1'] > df['DMN_H1']]
なお、これは、MAとDMIを用いて買いフラグを立てるという、テクニカル指標を用いて売買判断を行う工程とほぼ同じです。つまり、テクニカル指標としては買いフラグが立っている中で、さらに機械学習によって売買タイミングを厳選して勝率を高めようというのが今回の試みになります。
モデルの学習
調整した学習用データを用いて、実際に学習を行います。これまでの話を踏まえて、今回実際に学習に用いたのは以下のようなデータです。
特徴量として、bid, ask, spreadという価格情報そのものに加えて、MA, DMI(+DM, -DM), ATRといったテクニカル指標を用意しています。
目的変数はbuyモデル学習用のbuy_yとsellモデル学習用のsell_yをそれぞれ用意しています。つまり、buyエントリー用のモデルとsellエントリー用のモデルを別々に作成して学習させます。
先述の通り学習データを選定しているため、2022年1月〜2022年12月までの1年間で総レコード数は373,536になっています。ちなみにこれはbuyエントリーの条件で厳選した場合です。sellエントリー用の学習データはテクニカル指標の条件を変更して別途用意した上で学習させます。
ハイパーパラメータの設定
LightGBMではハイパーパラメータの設定が必要ですが、以下はその設定部分のコードの抜粋です。
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)
params = {
'objective': 'multiclass',
'num_class': 3,
'metric': 'multi_logloss',
'num_leaves': 20,
'learning_rate': 0.01,
'feature_fraction': 0.8,
'bagging_fraction': 0.7,
'bagging_freq': 2,
'verbose': -1
}
gbm = lgb.train(params,
lgb_train,
num_boost_round=10000,
valid_sets=[lgb_train, lgb_eval],
early_stopping_rounds=50,
init_model=gbm)
モデルの精度向上にはハイパーパラメータの調整というのも重要ですが、ここではひとまずその詳細には踏み込みません。
学習したモデルでバックテストを行い検証
学習したモデルで、tickデータにbuy_y_predとsell_y_predを追加します。ついでに、buy_y_npredとsell_y_npredという比較用の変数も追加します。
buy_y, sell_y
目的変数(y)としても使用した、将来の1分足の値から予測したほぼ正解の結果です。例えば、y=1であれば、「このタイミングで取引すればロスカット前に利確できるだろう」というものです。ただし、少し簡易的に作成している部分がありますので、実際にはtickの微妙な動きで想定外の結果になる可能性があります。
buy_y_pred, sell_y_pred
機械学習モデルで予測した結果(0,1,2いずれかの値)をセットしています。例えば、y_pred=1であれば、「このタイミングで取引すればロスカット前に利確できるだろう」とモデルが予測していることになります。この値がyと一致していればいるほど、理想に近い取引が実現出来ることになります。yとの合致度合いでモデルの精度を直接評価することも可能です。
buy_y_npred, sell_y_npred
これは、テクニカル指標の条件を満たしたら1になるフラグです。このフラグを用いて損益シミュレーションを行うと、モデルによる予測は行わず、テクニカル指標だけで売買判断を行うのと同じ状態になる想定です。機械学習によってどの程度勝率が上昇したかどうかを比較するために用意しています。
コードの抜粋
該当部分のコードも抜粋しておきます。
y_pred = gbm.predict(X, num_iteration=gbm.best_iteration)
df = pd.DataFrame(y_pred, columns=['Draw', 'Win', 'Lose'])
df['buy_y_pred'] = 0.0
df['buy_y_npred'] = 1.0
df.loc[df['Win'] > 0.52, 'buy_y_pred'] = 1
df.loc[df['Lose'] > 0.52, 'buy_y_pred'] = 2
このようにLightGBMで予測を行うと、y_predは確率(0~1までの値)で出力されます。その結果を受けて、Win(利確予測)やLose(ロスカット予測)の確率が52%(0.52)より大きい場合のみ、1または2のフラグを立てるという処理を行なっています。少しだけ精度を高める工夫になりますが、0.52という基準が最適かどうかは議論の余地ありです。0.52より大きくすればより精度は高まるかもしれませんが、取引機会が減ってしまう可能性があります。
バックテスト方法
上記のさまざまな準備を経て、用意したデータおよび学習済みモデルを用いて、バックテストを行いました。ここでは詳細は省略しますが、PythonでFX自動売買のバックテスト(損益シミュレーション)を行う方法については、以下の記事を参考にしてください。
以下、バックテスト結について利益額や勝率も含めて詳細に見てみます。
目的変数(y)を用いた損益グラフ
目的変数(y)を使ってバックテストを行なってみたところ、以下の通りとなりました。ほぼ勝率100%で結果が分かっている状態で取引するので必ず勝てるはず、というのを実際に確かめるための検証のようなものです。
期間: | 2023年1月〜2023年5月 | |||||||||||
総損益: | 153,076円 | |||||||||||
総利益: | 158,297円 | |||||||||||
総損失: | 5,221円 | |||||||||||
取引数: | 206回(勝率 96.60%) | |||||||||||
プロフィットファクター: | 30.32 |
理想的と言いつつ、勝率が100%となっていなくて、微妙に負けています。これは各種調整の過程で生じる誤差とでも思っておいてください。ただ、勝率96%でプロフィットファクター30.32というだけで十分理想的な水準かと思います。これは、yを正しく予測することが出来れば勝率や利益率を高めることが出来るという大前提を確認していることにもなります。
テクニカル指標のみで売買した損益グラフ
次に、テクニカル指標のみ(y_npred)で損益シミュレーションを行った結果は以下の通りになりました。
期間: | 2023年1月〜2023年5月 | |||||||||||
総損益: | 7,220円 | |||||||||||
総利益: | 88,323円 | |||||||||||
総損失: | 81,103円 | |||||||||||
取引数: | 182回(勝率 51.65%) | |||||||||||
プロフィットファクター: | 1.09 |
最後の方で大きめの損失が発生しています。今回のトレード戦略のベースにもなっているADM-EAでは、連敗の頻度が高いとロット数が積み重なる傾向にあるので、おそらくその影響でしょう。全体として勝率が50%超でも、一時的に勝率が悪くなる時期があると起こる現象です。勝率が高ければ高いほど起こりにくくなります。
テクニカル指標+機械学習で売買した損益グラフ
そして、テクニカル指標+機械学習(y_pred)でバックテストを行なった結果が以下の通りです。
期間: | 2023年1月〜2023年5月 | |||||||||||
総損益: | 25,496円 | |||||||||||
総利益: | 72,586円 | |||||||||||
総損失: | 47,090円 | |||||||||||
取引数: | 140回(勝率 57.86%) | |||||||||||
プロフィットファクター: | 1.54 |
勝率は57.86%、プロフィットファクターは1.54というところまで改善しました。先ほどの例のように最後の方で損失がふくらむようなこともなく、極端な連敗は避けられていそうに見えます。それなりに良好な結果が出たのではないでしょうか。
まとめ
今回行った機械学習全体の流れについて改めてまとめます。
- モデルはLightGBMを採用
- 特徴量は、価格情報(bid, ask, spread)とテクニカル指標(MA, DMI, ATR)を用意
- 目的変数(y)は、エントリーしたら将来的に利確とロスカットのどちらになるかで設定
- テクニカル指標による売買判断を予め入れておく(エントリー条件に当てはまったデータだけで学習)
- 2022年1月〜12月までの1年間のtickデータで学習
- 学習したモデルを2023年1月〜5月までのデータに適用してバックテスト
結果としては、機械学習を取り入れない場合の取引数は182回で、機械学習を取り入れると140回に厳選されました。その結果の勝率も向上していて、損益グラフの形状も視覚的に改善し、なかなかの結果に仕上がりました。
しかし、これだけで完全に機械学習が機能していると言い切るわけにもいきません。さらなる課題としては以下のようなものが考えられます。
- 他の期間でも同様の検証を行なってみて同じような効果が得られるか確認する。
- 他の通貨ペアでも同様の効果が得られるか検証する。
- 学習期間をさらに拡大してみて、バックテスト結果がどのように変化するか確認する。
- ハイパーパラメータを調整してみて、モデルの精度の変化を観察する。
- LightGBM以外の機械学習モデルも試してみる。
- 実際のトレードに適用してフォワードテストを行う。
検証してみたいことはそれなりに多いので、また試して報告したいと思います。
それでは、今回の検証は以上です。
今回使用した機械学習のコード例
今回使用したコードをそのまま提供しようと思っていたのですが、LightGBMのアップデートがあったせいか少し仕様が変わり、そのまま当記事の内容をそのまま再現出来なくなってしまいました。
改めて、本記事と同様の内容についてより詳細にコード全文を掲載した記事を以下に準備しました。是非、合わせてご覧ください。