<基礎学習>機械学習(ランダムフォレスト)でFX - 為替データの特徴量抽出

もくじ

  • 近況報告
  • ランダムフォレストでFX分析してみた
  • 感想など

近況報告

あけましておめでとうございますm(_ _)m
今年初のブログです。

去年12月に新しい会社に入り、現在はAI関係ではない仕事をやっています。
4月から念願だった機械学習関係のプロジェクトに入ることになりました。

DeepLearningは勉強していたのですが機械学習やscikit-learnは全然勉強していなかったので
昔買った本を読んで勉強中です。。


現在は、これの第1版を読んでいます。

このなかで、「ランダムフォレスト」という手法が面白かったので、
チュートリアルがてらFXの分析を実装してみました。
今日は、それを記事にしてみました。

ランダムフォレストでFX分析してみた

目標

  • numpyのお作法をちゃんと守る(for文を使わず行列計算)
  • ランダムフォレストの理論を理解する

ランダムフォレストとは?

弱い決定木をたくさん学習させて多数決する、的なやつです
説明は省略します。。

何をするの?

<翌日の騰落を当てる>にあたり、FXでよくきく「移動平均線」「ボリンジャーバンド」といったテクニカル指標はどの程度重要な指標となるのか?を分析してみました。

具体的には、移動平均線(25日,75日,200日)とボリバン(25日,75日,200日それぞれの±2σ、3σ)と前日終値の合計16変数を調べました。
データは過去10年分のUSDJPYの日足です。

ソースコード

総作成時間:4時間
参考書籍:上で紹介した書籍

import pandas as pd
import numpy as np
from sklearn.preprocessing import OneHotEncoder
from copy import copy
from sklearn.ensemble import RandomForestClassifier
import matplotlib as plt

IDX_HAZIMENE = 1

feature_name = []

def diviveData(term, in_data):
    out_data = np.array([[]])
    for i in range(len(in_data) - term):
        if i == 0:
            out_data = [in_data[i:i+term]]
        else:
            out_data = np.append(out_data, [in_data[i:i+term]],axis=0)

    return out_data

def movingaverage(in_data, isAppend=False):
    '''
    移動平均線のデータを作成
    :param in_data: 作成に使うデータ
    :param isAppend: append対象ならtrue
    :return: MAのデータ np.array([len(in_data)])
    '''
    out_data = np.sum(in_data, axis=1) / len(in_data[0])
    if isAppend:
        feature_name.append("movingaverage{}".format(len(in_data[0])))
    return out_data

def bollingerband(in_data, sigma):
    '''
    ボリバンのデータを作成
    :param in_data: 作成に使うデータ
    :param sigma: ボリバンのシグマ
    :return: ボリバンのデータ np.array([len(in_data)]
    '''
    variance = np.sqrt(np.sum((in_data - movingaverage(in_data).reshape(-1,1)).astype(np.float64) ** 2, axis=1) / len(in_data[0]))
    # in_dataの最終行が最新の終値になっているので,in_data[:,-1]
    out_data = in_data[:, -1] + variance * sigma
    feature_name.append("bolingerband_date{}/{}sigma".format(len(in_data[0]),sigma))
    return out_data

def make_y_data(in_data):
    tomorrow_data = copy(in_data[1:])
    out_data = in_data[:-1] - tomorrow_data
    out_data = np.where(out_data<0, 0, 1)

    # one-hotにencodeするための準備
    out_data = out_data.reshape(-1,1)
    encoder = OneHotEncoder(n_values=2)

    out_data = encoder.fit_transform(out_data).toarray()
    return out_data

data = (pd.read_csv("data/USDJPY.csv")).values

data = data[:, 1]

div25 = diviveData(25, data)
div75 = diviveData(75, data)
div200 = diviveData(200, data)

pre_term = 0

#=================
# ラベル作成
#=================
y_data = make_y_data(data)

#=================
# データ作成
#=================
# 2次元にしないとnp.appendがとおらないので整形
x_data = data.reshape(1, -1)

for divdata, term in zip([div25, div75, div200], [25, 75, 200]):
    # データは少ない方にあわせる
    term_dif = term - pre_term
    x_data = x_data[:,term_dif:]
    pre_term = term

    # MA,BollingerBandをデータに加える
    x_data = np.append(x_data, np.array([bollingerband(divdata, sigma=2)]), axis=0)
    x_data = np.append(x_data, np.array([bollingerband(divdata, sigma=3)]), axis=0)
    x_data = np.append(x_data, np.array([bollingerband(divdata, sigma=-2)]), axis=0)
    x_data = np.append(x_data, np.array([bollingerband(divdata, sigma=-3)]), axis=0)
    x_data = np.append(x_data, np.array([movingaverage(divdata, isAppend=True)]), axis=0)

# 前日の価格
x_data = np.append(x_data, [data[200-1:-1]], axis=0)
feature_name.append("yesterday")

# 当日の価格を1とした相対的な値に変換
x_data = x_data / x_data[0,:]

# 当日の価格を教師データからのぞく
x_data = x_data[1:,]

#=================
# 機械学習にかける
#=================

# 前処理
x_data = x_data.transpose()

# x_dataは最新日のデータは除く(正解がわからないため)
x_data = x_data[:-1]
# y_dataをx_dataにあわせる
y_data = y_data[-len(x_data):]

clf = RandomForestClassifier(criterion='entropy', # 不純度
                             n_estimators=1000,  # 決定木の数
                             random_state=1,    # 乱数を固定
                             n_jobs=-1,         # 使うコア数(-1:ぜんぶ)
                             max_depth=10,      # 決定木の深さ
                             max_features='auto')   # 決定木で使う特徴量("auto":sqrt(特徴量数))

clf.fit(x_data, y_data)

importances = clf.feature_importances_
indices = np.argsort(importances)[::-1]

for i in range(x_data.shape[1]):
    print("{} : {:.6f}".format(feature_name[indices[i]], importances[indices[i]]))


print(clf.feature_importances_)

結果

movingaverage25 : 0.073916
bolingerband_date25/2sigma : 0.073495
yesterday : 0.070974
movingaverage200 : 0.069467
movingaverage75 : 0.069117
bolingerband_date25/3sigma : 0.065644
bolingerband_date25/-2sigma : 0.064432
bolingerband_date25/-3sigma : 0.062237
bolingerband_date75/3sigma : 0.059920
bolingerband_date75/2sigma : 0.059884
bolingerband_date200/3sigma : 0.056606
bolingerband_date75/-2sigma : 0.056600
bolingerband_date75/-3sigma : 0.056141
bolingerband_date200/-3sigma : 0.054896
bolingerband_date200/-2sigma : 0.053710
bolingerband_date200/2sigma : 0.052961

25日移動平均線、25日の2sigmaボリバンが相対的に大事な指標みたいです。

感想

ディープラーニングとの違いって?

「(ディープラーニング以外の)機械学習は特徴量を絞らないと次元の呪いが〜〜〜」ってずっと思っていたのですが、ランダムフォレストは特徴量を増やしても大丈夫なんでしょうか。
そうなると、ディープラーニングと比べてメリットデメリットは何なんでしょう。
感覚的には

  • ディープラーニング > ランダムフォレスト

    • 表現幅が広い(ディープラーニングだと、ボリバンや移動平均線ではなく普通に200日分の為替データを入力としそうです。ランダムフォレストでそれをすると学習うまくいかなさそうです。なんとなく。)
  • ランダムフォレスト > ディープラーニング

    • 「なぜこの結論に至ったか」の説明がしやすいです。ランダムフォレストは上のように特徴量の重要度が出せるので・・・
    • 学習に必要なデータが少なくてすむ?(一般論)

こんな感じでしょうか。。

特徴量の重要度は、1変数でしかできない?

説明変数の組み合わせで重要度を出したいです。たとえば、25日移動平均線と25日+2sigmaボリバンを組み合わせるとどれだけ重要な特徴量になるのか?など調べられると嬉しいです。

パラメータ

パラメータチューニング全くしてないので、してみるとどうなるのか。とくに、木の深さってどれくらいが推奨されるのだろう。。とか思ったり。。

numpy

for文使わず綺麗にかけた!ような気がします。笑
ただ、ゴリ押ししている部分も多いので汚いですね
3ヶ月後に読んだらなんのこっちゃってなりそうです・・・

その他

  • 久々なので気合入れて書こうと思ったのですが最後の方投げやりです。。すいません。
  • 今週土曜日3/17に某所でDeepLearningのハンズオンをやります。
    定員30名に対して倍くらい人が来ているということもあり緊張しますが、がんばります!