決定木を使ってFX予想をやってみる(分類木編)
- 機械学習の専門的な知識は不要。Pythonの初歩知識のみでOK。
- 為替データはダウンロード可能。CSVファイルで本記事から落とせます。
- 環境構築不要。ブラウザのみで実行が可能です。(Google Colab)
- ノウハウ公開的な記事ではありません。初心者向けの記事です。
- 所要時間は10分〜1時間程度
こんばんは、新米データサイエンティスト(@algon_fx)です。機械学習を活用したFXトレードのブログをやっていて、多くの方から「どの機械学習手法/アルゴリズムを使っていますか?」と質問を頂きます。
答えは非常に単純で「様々な種類を同時に何個も使ってます!」(笑)
FXトレードにも様々なスタイルがありますよね。裁量トレードやテクニカルトレード、さらにファンダメンタルなどなど。機械学習FXトレードにもスタイルは様々で、一つの予想モデルで立ち向かう方もいます。
ただ、これは個人的な経験からですが、目玉が飛び出るほど予測の精度が高いモデルを使ったとして・・それだけで安定して収益を出すのは難しいと思います。(経験談です)
何を言いたいかと言うと・・機械学習FXトレードで収益を出すには・・様々な機械学習の手法を色々なパターンと手段で実装をする必要があるんです!
って前置きがいつも通り長くなりましたが、本日の本題は「決定木を使ったFX予想」です。ツイッター界隈で機械学習FXトレードをやられている方をみると「ランダムフォレスト」を活用している方が多い様に思えます。決定木とはこのランダムフォレストの親の様な立ち位置です。
では、決定木を使って明日の為替レートが上昇するのか、下降するのかをPythonを使って予測してみましょう!
決定木とは?
決定木(読み:けっていぎ)とは、機械学習の初歩的な解析手法の一つです。機械学習には大きく分類すると「教師あり学習」と「教師なし学習」があります。決定木は教師あり学習に属する手法です。
いつも通り詳細の数式などは使わず、決定木がどのような仕組みなのかを説明します。まずは下のデータをみてください。
こちらは10日間の天気、トランプ大統領がツイッターで発言したか、さらにドル円の為替レートが前日の終値と比べて上昇/下降したかのデータです。
さて、ここで質問です。このデータをみて、天気とトランプの発言の傾向から為替レートが上昇するのか下降するのか予測が可能か考えてみて下さい。
一見すると特徴量である「天気」「発言」はターゲットである「上昇/下降」とあまり関係なさそうに見えます。簡単な分析のため上記のデータを「トランプ発言で並び替え」してみましょう。
10日間のうちトランプ大統領の発言は3回あり、7回ありませんでした。発言があった3日のターゲットをみてみると、全て「下降」しているのが解ります。つまり、「トランプが発言したらドル円は下降する確率が高い」と言うことが出来ます。
では、上のデータをトランプ発言があり(日付2、4、10)を除外して新たにデータを細かくみてみましょう。下のデータはトランプ発言が「なし」で、さらに「天気」で並び替えをしたデータです。
こちらをよく見てみると一つ気づくと思います。「トランプの発言が無かった日で天気が晴れであればドル円は上昇する確率が高い」と言うことが可能です。これは大発見です。
10日間の天気、トランプ発言、ドル円の動向を分析したところ下記の2点を学習することが出来ました。
①トランプが発言したら上昇する
②トランプが発言せず天気が晴れたら上昇する
ではこの分析の流れを簡単に可視化してみましょう。下の図を見て下さい。これは上から下へ読みます。一番上のボックスには全10日間のデータが入っています。上昇した日は4日、下降した日は6日ありました。
このデータを分析通り「トランプの発言」で分けたのが次の2層目です。「あり」とある左側の箱をみると上昇=3とあります。つまりトランプの発言があったら上昇するを可視化しているわけです。
2層目の右側の「なし」を見て下さい。ここには上昇4、下降3とあります。まだ分岐できそうなので3層目では天気で分岐をしました。結果として「晴れ」と「曇り」で綺麗にデータが分岐できています。
この表を見てみると木を逆さにしたように見えませんか?そうです!これが決定木なのです!(ドーン)
このように決定木は特徴量を分析して、特徴量の値に応じてターゲットを分岐して予測を行います。詳細は割愛しますが、どの特徴量で分岐をするのか?分岐するときの値は?などの細かい部分をアルゴリズムで計算を行う訳です。
決定木のメリット・デメリット
決定木の概要が理解できたところで、簡単に決定木をFX予想で使うメリットとデメリットについても説明します。
【メリット】
・仕組みが非常に簡単で理解しやすい
・特徴量の正規化が不要
・計算コストが低い
・分類と回帰が行える
・予測の説明が出来る!(超重要)
【デメリット】
・気をつけないと過学習する
・単純な仕組みなので精度はさほど高くない
決定木の概要を理解すると納得できますが、構造は非常にシンプルです。また特徴量の正規化が不要ですので、煩わしい作業を行わなくて良いのもメリットですね。(注:正規化について詳しくはLSTM FX予想の記事をご覧ください)
また決定木の最大のメリットとして、予測の説明が出来る部分にあります!先ほどの事例でも分かった通り、トランプ発言があったらドル円は上昇するといった具合に、なぜその予測に至ったのかを説明することができます。
機械学習のアルゴリズム/手法で、このように説明が明確に行えるは少ないです。例えばニューラルネットワークやディープラーニングは予測の説明が行えません。(参照:ディープラーニングでFX予想)
では、実際にPythonを使ってドル円の過去レートを学習して予測してみましょう。
STEP1 ライブラリとデータの読み込み
本記事ではPython 3.Xを使っています。また実行環境はJupyter Notebook(データ分析の開発環境みたいなもの)を使っています。すでに自身のPCに機械学習の一通りの環境構築を行なっている方は、そちらの環境からやって見てください。
Pythonの環境がパソコンにない方はGoogle Colabがオススメです。環境構築不要でブラウザから本記事の全てのコードの実行が可能です。Google Colabを使う方は下記のリンクをクリックして、Google Colabの立ち上げをしてください。Googleのアカウントへログインが必要です。
Google ColabでPython3のノートを新規で開く
次に本記事で使うデータセットです。毎度おなじみになりましたが、ドル円の500日分の日足データを使いましょう。私が事前にOANDA FX APIで取得して綺麗にした下記のCSVファイルのダウロードをお願いします。
では、決定木の実装で必要なライブラリをまずはインポートしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# データ処理のライブラリ import pandas as pd import numpy as np # データ可視化のライブラリ import matplotlib.pyplot as plt # 機械学習ライブラリ from sklearn.tree import DecisionTreeClassifier from sklearn import tree from sklearn.metrics import confusion_matrix from sklearn.metrics import accuracy_score |
次に決定木を可視化するgraphvizというライブラリをインストールしましょう。ローカルPCでやっている方は各自でインストールお願いします。Google Colabの方はコードセルに下記のコードを実行してください。Google Colab上にgraphvizが追加でインストールされます。
1 2 3 4 5 |
# 決定木を可視化するためのライブラリインストール !pip install graphviz !apt-get install graphviz |
graphvizがちゃんとインストールされたか確認をしましょう。インポートしてバージョンを出力してみます。
1 2 3 4 5 6 7 |
# graphvizのインポート import graphviz # バージョンの確認 print(graphviz.__version__) |
1 2 3 4 |
-- 出力 0.9 |
ちゃんとインストール出来ていますね。
では、ドル円の過去レートCSVファイルをGoogle Colabへアップロードしましょう。下記のコードを実行すると「ファイルを選択」と出ますので、そちらからusd_jpy_api.csvを選択してアップロードしてください。
1 2 3 4 5 |
# Google Colabへファイルをアップロードする from google.colab import files uploaded = files.upload() |
アップロードが成功すると下記のようなメッセージが出力されます。
1 2 3 4 5 |
-- 出力 usd_jpy_api.csv(text/csv) - 28803 bytes, last modified: n/a - 100% done Saving usd_jpy_api.csv to usd_jpy_api.csv |
これでGoogle Colabのクラウド上にファイルがアップロードされました。では、このCSVファイルをPandasで読み込んでデータフレームへ変換しましょう。
1 2 3 4 |
# CSVファイルの読み込み df = pd.read_csv('usd_jpy_api.csv') |
念のため最後の5行を出力して確認してみます。
1 2 3 4 |
# 最後の5行を出力 df.tail() |
1 2 3 4 5 6 7 8 9 |
-- 出力 time close open high low volume 495 2018/07/17 06:00:00 112.908 112.320 112.933 112.231 19645 496 2018/07/18 06:00:00 112.866 112.914 113.144 112.723 17432 497 2018/07/19 06:00:00 112.501 112.887 113.187 112.072 26697 498 2018/07/20 06:00:00 111.474 112.504 112.631 111.401 33445 499 2018/07/23 06:00:00 111.164 111.420 111.523 110.760 16040 |
大丈夫そうですね。こちらのデータですが500日のドル円の日足データです。最終日は2018年7月23日となります。それぞれの日にちの終値、始値、高値、安値に加えて取引高も含まれます。
過去の為替データをAPI経由で取得する方法は「Pythonを使ったOANDA API v1で知っておきたい10の基本操作」の記事を参照ください。
STEP2 データの前処理
無事にデータの読み込みが完了しましたので、次は決定木のアルゴリズムへ学習させるために、簡単なデータの前処理を行いましょう。
冒頭でも触れましたが、今回は当日の終値、始値、高値、安値、取引高を特徴量として、翌日の終値が上昇するか下降するかを予測します。
過去レートのデータから、まずは当日の終値と翌日の終値の差分を計算してあげましょう。
1 2 3 4 5 6 7 8 9 |
# 翌日終値 - 当日終値で差分を計算 df['close+1'] = df.close.shift(-1) df['diff'] = df['close+1'] - df['close'] df = df[:-1] # 最後の5行を確認 df.tail() |
1 2 3 4 5 6 7 8 9 |
-- 出力 time close open high low volume close+1 diff 494 2018/07/16 06:00:00 112.320 112.252 112.570 112.235 12518 112.908 0.588 495 2018/07/17 06:00:00 112.908 112.320 112.933 112.231 19645 112.866 -0.042 496 2018/07/18 06:00:00 112.866 112.914 113.144 112.723 17432 112.501 -0.365 497 2018/07/19 06:00:00 112.501 112.887 113.187 112.072 26697 111.474 -1.027 498 2018/07/20 06:00:00 111.474 112.504 112.631 111.401 33445 111.164 -0.310 |
大丈夫そうですね。下から2番目の7月19日の終値(close)をみてください。この日の終値は112.501でした。翌日の20日の終値は111.474です。その差分となる「-1.027」がdiffとして計算されています。
データ全体で上昇した日と下降した日の割合を確認してみましょう。
1 2 3 4 5 6 |
# 上昇と下降のデータ割合を確認 m = len(df['close']) print(len(df[(df['diff'] > 0)]) / m * 100) print(len(df[(df['diff'] < 0)]) / m * 100) |
1 2 3 4 5 |
-- 出力 50.70140280561122 49.298597194388776 |
上昇したのが50.7%、下降したのは49.3%の結果です。微妙に上昇した日が多いです。
次は上昇した日は「1」、下降した日は「0」となるようにデータを処理しましょう。
1 2 3 4 5 6 7 8 9 10 11 |
# 上昇したら「1」、下降したら「0」へデータを変換 mask1 = df['diff'] > 0 mask2 = df['diff'] < 0 column_name = 'diff' df.loc[mask1, column_name] = 1 df.loc[mask2, column_name] = 0 # 最初の5行を表示 df.head() |
1 2 3 4 5 6 7 8 9 |
-- 出力 time close open high low volume close+1 diff 0 2016/08/19 06:00:00 100.256 99.919 100.471 99.887 30965 100.335 1.0 1 2016/08/22 06:00:00 100.335 100.832 100.944 100.221 32920 100.253 0.0 2 2016/08/23 06:00:00 100.253 100.339 100.405 99.950 26069 100.460 1.0 3 2016/08/24 06:00:00 100.460 100.270 100.619 100.104 22340 100.546 1.0 4 2016/08/25 06:00:00 100.546 100.464 100.627 100.314 17224 101.876 1.0 |
しっかり当日から翌日の終値が高かったら「1」、低かったら「0」となっています。
ターゲットが「diff」だと混乱しやすいので、分かりやすいように「target」とカラム名を変更してあげましょう。また、特徴量として使わない不要なカラムを削除します。加えてカラムの視認性を高めるため順番を並び替えてあげましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# カラム名をtagertへ変換 df.rename(columns={"diff" : "target"}, inplace=True) # 不要なカラムを削除 del df['time'] del df['close+1'] # カラムの並び替え df = df[['target', 'close', 'open', 'high', 'low', 'volume']] # 最初の5行を出力 df.head() |
1 2 3 4 5 6 7 8 9 |
-- 出力 target close open high low volume 0 1.0 100.256 99.919 100.471 99.887 30965 1 0.0 100.335 100.832 100.944 100.221 32920 2 1.0 100.253 100.339 100.405 99.950 26069 3 1.0 100.460 100.270 100.619 100.104 22340 4 1.0 100.546 100.464 100.627 100.314 17224 |
これでみやすい学習データになりました。続いて、モデルへ学習させる「訓練データ」と予測の評価で使う「テストデータ」へ切り分けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# データセットの行数、列数を取得 n = df.shape[0] p = df.shape[1] # 訓練データとテストデータへ分割 train_start = 0 train_end = int(np.floor(0.8*n)) test_start = train_end + 1 test_end = n data_train = df[np.arange(train_start, train_end), :] data_test = df[np.arange(test_start, test_end), :] # 訓練データとテストデータのサイズを確認 print(data_train.shape) print(data_test.shape) |
1 2 3 4 5 |
-- 出力 (399, 6) (99, 6) |
コードをみると分かりますが今回は訓練データ8割、テストデータ2割に分割しています。機械学習では一般的にテストと訓練データへ分割する際にデータをシャッフルしますが、為替データの分割はシャッフルをしないほうが良いです。
最後に特徴量とターゲットにデータを切り分けてあげましょう。
1 2 3 4 5 6 7 |
# 特徴量とターゲットにデータを切り分け X_train = data_train[:, 1:] y_train = data_train[:, 0] X_test = data_test[:, 1:] y_test = data_test[:, 0] |
これでデータの前処理が完了です。前処理を見ていただくと分かりますが、「正規化」の処理は一切行なっていません。
これは大きなメリットです。正規化がないというのは、つまり生のレートをAPIで取得して、そのままモデルに入力することで予測をすることが可能です。
STEP3 決定木モデルの訓練
では、いよいよ決定木モデルの訓練を行いましょう。機械学習ライブラリのScikit-learnを使います。
1 2 3 4 5 |
# 決定技モデルの訓練 clf_2 = DecisionTreeClassifier(max_depth=2) clf_2 = clf_2.fit(X_train, y_train) |
これで完了です。max_depth=2とありますが、これは決定木の深さを指定するハイパーパラメータです。では、訓練した決定木を可視化してみましょう。
1 2 3 4 5 6 7 8 9 10 11 |
# 決定木の可視化 dot_data = tree.export_graphviz(clf_2, out_file=None, feature_names=df.columns[1:6], class_names=df.columns[0], rounded = True, filled=True ) graph = graphviz.Source(dot_data) graph |
深さを2層と指定したのでちっちゃい決定木が可視化されています。それぞれ四角で囲まれていますが、これらは「ノード」と呼ばれます。一番上のノードを見てください。
value=[199, 200]とありますが、これは上昇したデータと下降したデータの数を表しています。一番上のノードの最上部に「low<=113.149」とあります。これは次の層への分岐条件を表しており「安値が113.149以下で分岐しろ」を意味してます。
2層目では左がTrue=つまり安値が113.149以下、左はFalseと分岐しています。このように決定木のモデルが訓練データをどのように学習したのかが確認できるのは非常に大きなメリットです。
では、テストデータを使って実際に予測してみましょう!ドキドキですね!下記のコードでは予測をして、その結果を混同行列として出力しています。
1 2 3 4 5 6 7 8 |
# テストデータで予測 pred_test_2 = clf_2.predict(X_test) # テストデータの予測結果(混同行列) matrix = confusion_matrix(y_test,pred_test_2) matrix |
1 2 3 4 5 |
-- 出力 array([[ 0, 46], [ 0, 53]]) |
・・・あれ?
混同行列を確認してみると・・決定木のモデルは全て上昇すると予測しています。うち実際に予測したのは53件、下降したのは46件という結果になりました。念のためこの決定木の正解率(Accuracy)を計算してみましょう。
1 2 3 4 5 6 7 |
# ランダムフォレスト テストデータ 正解率 accuracy_score(y_test,pred_test_2) -- 出力 0.5353535353535354 |
正解率53%と出ました。少しデータを見てみると分かりますが、テストデータに含まれる上昇したデータは全部で53件あります。つまり全て上昇すると予測するだけで53%の正解率が出てしまっただけです。。これはでは微妙ですね。
もう少し役に立つモデルを構築してみましょう。上記の決定木は2層と非常に単純な構造でしたので、次は20層の決定木を作ってみましょう。
1 2 3 4 5 |
# 20層の決定木モデルの訓練 clf_20 = DecisionTreeClassifier(max_depth=20) clf_20 = clf_20.fit(X_train, y_train) |
max_depth=20となっている部分に注目ください。では、同じ要領でテストデータを使って予測をして、混同行列を表示してみましょう。
1 2 3 4 5 6 7 8 |
# テストデータで予測 pred_test_20 = clf_20.predict(X_test) # テストデータの予測結果(混同行列) matrix = confusion_matrix(y_test,pred_test_20) matrix |
1 2 3 4 5 |
-- 出力 array([[30, 16], [32, 21]]) |
今度はしっかり上昇と下降の予測を行なっていますね。20層の決定木では37件が上昇したと予測をしており、そのうち21件が実際に上昇したデータと結果が出ています。
では、正解率も確認してみましょう。
1 2 3 4 5 6 7 |
# ランダムフォレスト テストデータ 正解率 accuracy_score(y_test,pred_test_20) -- 出力 0.5151515151515151 |
あっれーー・・・・下がっている。
ただ、思い出してください。最初の方で今回の500日全データの上昇と下降の割合を計算した際に、上昇50.701%とありました。つまりわずかではありますが、勘で予測をするより決定木で予測をした方が微妙に確率が高いわけです。(本当に微妙w)
まとめと次の課題
いかがでしたか?今回は決定木を使って為替レートが上がるか、下がるかを予測してみました。今回は非常に小さく、かつとてもシンプルなデータセットで予測を行いました。
より予測の精度を上げるため、特徴量を工夫したり、期間を工夫したりとまだまだ改善の余地は腐る程あります!是非、これを機会に決定木でFX予想に挑戦してみてください!
他の機械学習手法でのFX予想チュートリアルもまとめています。興味がある方は、ぜひこちらのチュートリアルも挑戦してみましょう!
・LSTMでFX予想する方法
・ディープラーニングでFX予想する方法
・ロジスティック回帰でFX予想する方法
ブログ読んでいただきありがとうございます!Twitterでも色々と発信しているので、是非フォローお願いします!
ディスカッション
コメント一覧
大変申し訳ないです!!写経させていただいてます。
こんな素晴らしいコンテンツありがとうございます。
控えめにいってネ申ですね。。。
data[np.arange(train_start, train_end), :]
===
こちらのdataというのは一体なんでしょうか?
コメント、ありがとうございます!
こちら・・大変失礼しました・・。dataではなくdfの間違いでした!本文も該当箇所を修正しました!ご指摘、ありがとうございます!