FXトレードでロジスティック回帰を独自テクニカル指標として活用する方法(機械学習初心者向け)
- Pythonを使ってロジスティック回帰のモデルを構築します
- 過去為替レートのデータはCSVファイルでダウンロード出来ます
- データの処理からFXトレードへ活用するまでの一連の流れを解説
- 基本的なモデル構築を行います。ノウハウ公開の類ではありません。
- 所要時間は30分〜1時間程度
こんばんは、新米データサイエンティスト(@algon_fx)です。機械学習を使ってFXトレードへ生かす方法を日夜模索して生きています。
少し前まではLSTMというディープラーニングの一種を使ったFX予想ばっかりやっていたのですが、ここ最近はシンプルなニューラルネットワークでの予測により時間を割いています。
【参考記事】
・TensorFlowでニューラルネットワークを使ってFX予想をする方法
・LSTMでFX予測をやってみよう
ニューラルネットワークやLSTMは数学的にも理解が難しいですし、機械学習を始めたばかりの方だとしっかり理解して活用するには少しハードルが高いと思います。
そこで・・機械学習トレードを始めたばかりの方へおすすめなのが「ロジスティック回帰」です。機械学習には色々な種類の手法/アルゴリズムが存在します。ロジスティック回帰は線形回帰と並んで、一番最初に学ぶべきシンプルな機械学習手法です。
では、ロジスティク回帰の基礎を学びながら、実際にFXトレードでどのように使えばよいのか、モデル訓練を含めて一緒にやってみましょう。
ロジスティック回帰とは?
ロジスティック回帰とは機械学習の教師あり学習の手法の一つで分類を行う際に用いられます。非常にシンプルな構造ですが、計算コストも低く、簡単に実装が可能なことから根強い人気があります。
本記事では数式を用いた詳細の解説は行いません。ただし、どのような仕組みで動いているのかを理解するのは重要です。簡単な例題を使って、ロジスティック回帰の仕組みを紐解きましょう。
まずは下のデータをみてください。
成人の身長と性別を表した非常にシンプルなデータです。では、このデータを下記のように可視化してみましょう。
X軸に「身長」、Y軸には性別の値(1=男性、0=女性)を可視化したものです。男尊女卑ではありませんが、一般的に男性は身長が高く、女性は進捗が低い傾向にありますよね。それが顕著に現れたデータです。
では、ここで質問です。新たに2名のデータが下記のように追加されました。データAとデータBの性別を推測してみてください。
すでに与えられたデータ(赤マーカー)から推測すると、データAはおそらく男性、データBはおそらく女性と推測できるかと思います。
このように与えられたデータ(教師データ)をより厳密に数学を使って学習を行い、それぞれのデータが男性か女性かの確立を計算して分類予測するのがロジスティック回帰です。
ロジスティック下記では下記のような線をデータから導き出して分類推測を行います。(この線はシグモイド関数で導き出します。興味がある方は、詳細は調べてみてください)
こんな線を使ってどうやって予測するだよ(怒)って一見すると思いますが、実はこいつは非常に便利な形をしているのです。ではデータBとこの線を使って分類予測してみましょう。
データBですが身長が159cmですが、xが159の時の線のyを求めることで予測が可能です。下の図をみてください。データBと青い線が交わった部分のYの値を見ると「0.4」と読めます。
ここからが、重要です。このデータは1=男性で0=女性でした。ロジスティック回帰の線を使ったところデータBは0.4と出ました。
これは、データBは男性である確立が40%である。逆の言い方をすれば女性である確立が60%であることを示しています。
このようにロジスティック回帰では分類だけではなく、その分類に当てはまる確立までも確認することが可能なわけです。これはロジスティック回帰の大きなメリットの一つです。
例えば分類だけの精度で考えれば別の機械学習手法である「サポートベクターマシン(SVM)」などの方が一般的にうまく分類してくれます。ただし、SVMでは分類の確立を確認することは出来ません。
ロジスティック回帰をFXトレードで使う
ロジスティック回帰は解った、だが、どうやってFXトレードで使うだって話ですよね。結論から言うと使い方は様々です。
本記事では初歩の初歩として、為替レートが上がるか下がるかをロジスティック回帰で予測してみましょう。下のデータをみてください。
このように、当日の為替レートのデータを特徴量として使って、翌日の終値が上昇するか下降するかを予想することが可能です。
数字ばかりで見辛いデータですが、「終値」と「翌日終値」に注目してください。単純にこの2つのレートを差し引いてレートが翌日上がれば「上昇=1」、翌日下がっていれば「下降=0」をターゲットとして持っています。
では、実際にPythonを使ってやってみましょう!
STEP1 ライブラリとデータの読み込み
ロジスティック回帰の実装ですが、今回は機械学習ライブラリのScikit-learn(サイキット・ラーン)を使います。今回は便宜上、Scikit-learnを使いますが、機械学習初心者の方で、よりロジスティック回帰を深く理解したい方はスクラッチで組んで見て下さい。さほど難しいロジックではありません。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# データ処理のライブラリ import pandas as pd import numpy as np # データ可視化のライブラリ import matplotlib.pyplot as plt # 機械学習ライブラリ from sklearn.linear_model import LogisticRegression from sklearn.metrics import confusion_matrix from sklearn.metrics import accuracy_score |
次は為替レートを読み込みましょう。何度も登場しましたが、私がOANDA APIから取得したドル円の一日足のCSVファイルを使いましょう。下記からダウロードしてください。
1 2 3 4 5 6 7 |
# CSVファイルの読み込み df = pd.read_csv('../tensorFlow/usd_jpy_api.csv') # 最後の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 |
ご覧の通り、こちらのデータは最終日が2018年7月23日のドル円の1日足のデータです。終値(close)、始値(open)、高値(high)、安値(low)、取引量(volume)が含まれています。
STEP2 データの前処理
前述しましたが、ロジスティック回帰のモデルへ学習させる前にデータを少しいじってあげる必要があります。まずは終値を1日ずらして、その差分を計算してあげましょう。
1 2 3 4 5 6 7 8 9 |
# 終値を1日ずらして差分を計算 df['close+1'] = df.close.shift(-1) df['diff'] = df['close+1'] - df['close'] df = df[:-1] # 最初の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 0.079 1 2016/08/22 06:00:00 100.335 100.832 100.944 100.221 32920 100.253 -0.082 2 2016/08/23 06:00:00 100.253 100.339 100.405 99.950 26069 100.460 0.207 3 2016/08/24 06:00:00 100.460 100.270 100.619 100.104 22340 100.546 0.086 4 2016/08/25 06:00:00 100.546 100.464 100.627 100.314 17224 101.876 1.330 |
上記テーブルの最上部の8月19日の終値(close)と差分(diff)を見てください。8月19日の終値は100.256で翌日の終値は100.355です。その差分として0.079がdiffとして計算されているのが確認できます。
FXに限らずですが、データを処理する場合は細かくチェックをするとミスが無くなります。
では、このデータで当日の終値から翌日の終値が上昇したものと下降したものの割合をチェックしてみましょう。
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 |
若干ではありますが、上昇したレートの方が多いのが確認できます。参考までにですが、為替レートはランダムウォークと呼ばれており、つまり上昇も下降も「ランダム」だぜ!って意味です。このデータを見てみると、本当に綺麗に上昇と下降を繰り返しているのが解ります。
次はロジスティック回帰のモデルへ訓練するためターゲットを解りやすい形に変えましょう。具体的にはレートが上昇したら「1」、下降したら「0」とします。
1 2 3 4 5 6 7 8 9 |
# 上昇したら「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 df.tail() |
1 2 3 4 5 6 7 8 |
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 1.0 495 2018/07/17 06:00:00 112.908 112.320 112.933 112.231 19645 112.866 0.0 496 2018/07/18 06:00:00 112.866 112.914 113.144 112.723 17432 112.501 0.0 497 2018/07/19 06:00:00 112.501 112.887 113.187 112.072 26697 111.474 0.0 498 2018/07/20 06:00:00 111.474 112.504 112.631 111.401 33445 111.164 0.0 |
データを確認してみると当日と翌日の終値の変化量に応じて「1」「0」へ変換されています。
次は今回のモデルへ学習させない不要な列の削除やカラム名を解りやすいように変更して、順序も変更してあげましょう。
1 2 3 4 5 6 7 8 9 |
-- 出力 target close open high low volume 494 1.0 112.320 112.252 112.570 112.235 12518 495 0.0 112.908 112.320 112.933 112.231 19645 496 0.0 112.866 112.914 113.144 112.723 17432 497 0.0 112.501 112.887 113.187 112.072 26697 498 0.0 111.474 112.504 112.631 111.401 33445 |
大丈夫そうですね!
STEP3 訓練/テストデータへ分割
ターゲットとなる値の処理も完了しました。次は訓練データとテストデータへ分割を行います。
機械学習ではモデルへ学習を行わせる「訓練データ」、さらに学習済みのモデルを使って予測を行いモデルの精度の評価を行う「テストデータ」へ分割するのが一般的なプロセスです。
データ分割方法は様々な手法がありますが、FXのように時系列データの場合はランダムに分割するのは得策ではありません。(これも時と場合によりますが)
今回のデータは全部で500レコード(500日分)ですので、8割にあたる400件を訓練データとしてモデルの学習用に、残り100件をテストデータとして分割しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# 念のため元データからdf_2としてデータを分ける df_2 = df.copy() # データフレームの行数と列数を取得 n = df_2.shape[0] p = df_2.shape[1] # データフレームからNumpy配列へ変換 data = df_2.values # 訓練データ8割、テストデータ2割へ分割 train_start = 0 train_end = int(np.floor(0.8*n)) test_start = train_end + 1 test_end = n data_train = data[np.arange(train_start, train_end), :] data_test = data[np.arange(test_start, test_end), :] # テストデータの最後2行を確認 data_test[97:99] |
1 2 3 4 5 |
-- 出力 array([[ 0. , 112.501, 112.887, 113.187, 112.072, 26697. ], [ 0. , 111.474, 112.504, 112.631, 111.401, 33445. ]]) |
前のステップで表示させたデータの最後2行と上記の出力を確認して見てください。ターゲットや終値など、全ての値が合致しているのが確認できます。(つまり正常に分割が行えた)
データの最終処理として特徴量(X)とターゲット(y)へ切り分けを行います。
1 2 3 4 5 6 7 |
# 特徴量(X)とターゲット(y)へ切り分け X_train = data_train[:, 1:] y_train = data_train[:, 0] X_test = data_test[:, 1:] y_test = data_test[:, 0] |
これでデータ処理、完了です!
STEP4 モデル訓練
では、下準備した訓練データを使ってロジスティック回帰のモデル訓練を行いましょう。前述した通り今回は便宜上、Scikit-learnを使います。
1 2 3 4 5 |
# モデル訓練 clf = LogisticRegression() clf.fit(X_train, y_train) |
1 2 3 4 5 6 7 |
-- 出力 LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1, penalty='l2', random_state=None, solver='liblinear', tol=0.0001, verbose=0, warm_start=False) |
機械学習ライブラリを使うと、本当に簡単にモデル構築が行えます。しかし、機械学習を始めたばかりの方にはあまりお勧めできません。時間がある方は入門書などを購入して、自分でスクラッチで組んでみましょう。
STEP5 テストデータから予測と評価
モデル訓練には訓練データを使いました。つまりモデルは訓練データの内容を「学習」しており、そのまま訓練データで予測をしても本質的な意味での推測にはなりません。
ということで、テストデータを使って予測して見ましょう。これまた、Scikit-learnのpredict()を使うことで簡単に行えます。
1 2 3 4 |
# テストデータで訓練済みモデルを使って予測 pred_test = clf.predict(X_test) |
では、混同行列を使ってどれほど正しく予測できたか評価して見ましょう。混同行列(英:Confusion Matrix)は、今回のような分類モデルの評価を行う際に使われる最も初歩的な評価手法です。
1 2 3 4 5 |
# テストデータの予測結果(混同行列) matrix = confusion_matrix(y_test,pred_test, labels=[0,1]) matrix |
1 2 3 4 5 |
-- 出力 array([[33, 13], [40, 13]]) |
混同行列の数値ですが、下記のような意味です。ロジスティック回帰で当日終値が翌日に「上昇するか」「下降するか」を予測した結果が下記です。
結果として実際に下降した46日のうち、モデルが下降すると予測できたのは33日でした。また、実際に上昇したのは53日ですが、そのうちモデルが正しく上昇と予測できたのは13日です。
混同行列ですが、分類の評価では頻繁に使われます。慣れるまではややこしいですが、これを機会に読み方を覚えておくと便利です。
では、今回のモデルの正解率(英:Accuracy)を計算してみましょう。正解率とは、モデルが実際にデータに対してどれほど正しく予測できたかを表す指標です。
1 2 3 4 |
# 正解率 accuracy_score(y_test,pred_test) |
1 2 3 4 |
-- 出力 0.46464646464646464 |
び、び、微妙ですね!(笑)このロジスティック回帰のモデルを信用してトレードをしたら、確実に負けること必須ですね。
STEP6 確率を確認してみる
翌日の終値が今日よりも上がるのか、下がるのかを予測したところ、全く使い物にならないモデルが出来ました(笑)でも、安心してください。これでも使い道があります。
ロジスティック回帰の大きなメリットの一つとして「確率」が確認できると紹介しました。では、実際に確率を確認してみようではありません!Scikit-learnのpredict()では特徴量からターゲットの予測を行いますが、predict_proba()を使うことでそれぞれのクラスの確率を確認することが可能です。
1 2 3 4 5 6 7 |
# 確率を確認 proba_test = clf.predict_proba(X_test) # 最初の5レコードを表示 proba_test[0:5] |
1 2 3 4 5 6 7 8 |
-- 出力 array([[ 0.50825178, 0.49174822], [ 0.50019476, 0.49980524], [ 0.5483568 , 0.4516432 ], [ 0.54173923, 0.45826077], [ 0.47985292, 0.52014708]]) |
こちらですが、左側の数字がターゲット「0=下降」する確率で、右側が「1=上昇」する確率を表しています。確率ですので、当然1行を足せば「1=100%」となります。
このままでは扱いにくいのでPandasのデータフレーム形式へ変換してあげましょう。
1 2 3 4 5 6 7 8 9 10 |
# 正解ラベルと各クラスの確率をデータフレームへ格納 test_fin = pd.DataFrame({'Actual': y_test, 'down_proba': proba_test[:,0], 'up_proba' : proba_test[:,1] }, dtype='float64') # 最初の5行を表示 test_fin.head(5) |
1 2 3 4 5 6 7 8 |
Actual down_proba up_proba 0 0.0 0.508252 0.491748 1 1.0 0.500195 0.499805 2 1.0 0.548357 0.451643 3 0.0 0.541739 0.458261 4 1.0 0.479853 0.520147 |
これで見やすくなりましたね。読み方として、最上部の行を見てください。Actualとあるのが正解です。つまりこの日の翌日の終値は「0=下降した」のが実際のデータです。
では確率を見てください。down_probaはレートが下降する確率ですが、若干ではありますがup_proba(上昇する確率)よりも高いのがわかります。つまりロジスティック回帰の判断では、「自信はあまりないけど・・どっちかといえば・・上がるんじゃないっすか?」ってことですね。
では4行目(index = 3)を確認してみましょう。この日の正解は「0 = 下降した」ですが、確率を確認すると54%下降するとモデルは予測しています。つまり、ロジスティック回帰の判断としては「1行目よりも少しだけ下降する自信あるっす!」って訳ですね。
このように確率を確認できるのは非常に有益な情報となります。では、ロジスティック回帰が「下降する」と予測したデータのうち、さらに自信が高い=確率が高いレコードのみを確認して見ましょう。
1 2 3 4 |
# 56%以上の確率で翌日の終値は下がります! test_fin[test_fin['down_proba'] > 0.56] |
1 2 3 4 5 6 7 8 9 |
-- 出力 Actual down_proba up_proba 36 0.0 0.566478 0.433522 66 0.0 0.567425 0.432575 70 0.0 0.566812 0.433188 92 0.0 0.560888 0.439112 95 0.0 0.567498 0.432502 |
「Actual=正解ラベル」に注目してください。モデルが出力した確率56%で区切ってみると・・なんと100%の確率で実施にレートが下がっているではありませんか。
では、今度は上昇した確率が高いレコードも確認して見ましょう。
1 2 3 4 |
# 56%以上の確率で翌日の終値は上がります! test_fin[test_fin['up_proba'] > 0.56] |
1 2 3 4 5 6 7 8 9 10 11 12 |
-- 出力 Actual down_proba up_proba 12 0.0 0.428518 0.571482 42 0.0 0.413301 0.586699 56 0.0 0.370113 0.629887 57 1.0 0.385813 0.614187 60 1.0 0.399486 0.600514 75 1.0 0.436741 0.563259 97 0.0 0.436022 0.563978 98 0.0 0.367131 0.632869 |
上記の出力はモデルが上昇すると予測した確率が56%のレコードです。実際に見てみると・・全部で8レコードあり、実際に上昇した(Actual = 1)は3つしかありません。
おいっ(怒)
STEP7 トレンドを確認しながら予測結果を吟味する
ロジスティック回帰で確率が出力できるのは便利なのがわかりましたが・・それだけでは実戦トレードで活用するには厳しそうでした。
そこで、おすすめなのが全体のトレンドを確認しながら、ロジスティック回帰の確率を一つの参考として使うトレード手法です。
では、Matplotlibを使って終値、さらにロジスティック回帰が予測した「上昇する可能性」をチャートにして見ましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# 終値と確立をプロット fig, (ax1, ax2) = plt.subplots(2,1, gridspec_kw = {'height_ratios':[3, 1]}) ax1.plot(df['close'][400:500].index, df['close'][400:500]) ax1.axvline(x = 412, color='red', linewidth=0.8, alpha=0.5) ax1.axvline(x = 429, color='red', linewidth=0.8, alpha=0.5) ax1.axvline(x = 442, color='red', linewidth=0.8, alpha=0.5) ax1.axvline(x = 456, color='red', linewidth=0.8, alpha=0.5) ax1.axvline(x = 460, color='red', linewidth=0.8, alpha=0.5) ax2.plot(test_fin['up_proba'].index, test_fin['up_proba']) ax2.axhline(y = 0.5, color='green', linewidth=0.8, alpha=0.5) ax2.axvline(x = 12, color='red', linewidth=0.8, alpha=0.5) ax2.axvline(x = 29, color='red', linewidth=0.8, alpha=0.5) ax2.axvline(x = 42, color='red', linewidth=0.8, alpha=0.5) ax2.axvline(x = 56, color='red', linewidth=0.8, alpha=0.5) ax2.axvline(x = 60, color='red', linewidth=0.8, alpha=0.5) plt.legend() plt.show() |
上部のプロットはテストデータ期間(100日)の終値の推移です。対して下部のプロットは各日にちに対して翌日の終値が上昇する確率をプロットしたものです。便宜上、0.5=50%の部分に緑色の直線を引いてみました。
まずは①を確認してみてください。この日の予測は上昇する確率が高いとありますが、翌日の終値をみると下がってます。予測確率だけでトレードすると大損しますが、それ以前のレートの動きから考えると、ここでレートが反転するのか?逆にしないとなれば近日中か?と推測は可能です。
②のポイントも若干ではありますが、上昇する確率が高いとモデルは予測しています。それ以前のレートをみると、このまま上昇傾向になり得そうだな?ってことはひとまずは買い注文か?と推測が可能です。
ロジスティック回帰の予測のみでは、正直、役に全く立ちませんが、このように予測した確率を一つのテクニカル指標として活用は可能だと思います(少なくとも私は!w)
まとめと次の課題
少し長くなりましたが、如何でしたでしょうか?今回はFXトレードでロジスティック回帰を使って独自のテクニカル指標を作ってみるという流れをまとめてみました。
結果を見れば、全く使えなさそうな予測精度でしたが安心してください。ここからがむしろスタートです!
今回、一緒にコーディングしたロジスティック回帰は基本中の基本と考えてください。まだ、ここから精度を大幅に改善する方法は無数にあります。
ぜひ、色々なデータを触りながら、試行錯誤して自分のトレードスタイルに最もあったロジスティック回帰シグナル(勝手に命名)を作ってみては如何でしょうか?
ブログ読んでいただきありがとうございます!Twitterでも色々と発信しているので、是非フォローお願いします!
ディスカッション
コメント一覧
ロジスティック回帰はあまり使えないと思いこんでいたのですが、
確率を表示して、上昇するかどうかの判断材料にするのはとても良いアイデアだと思いました。
とても参考になりました。ありがとうございます。
ブログを読んで頂き、ありがとうございます!ロジスティック回帰も含めて確率を出力できる手法は様々な応用方法が可能ですよね!また続編として、少し踏み込んだ確率の使用方法もまとめたいと思います!