<基礎学習>機械学習(ランダムフォレスト)で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名に対して倍くらい人が来ているということもあり緊張しますが、がんばります!

<為替分析>11/29 ディープラーニングで為替自動トレード2

以下前回記事の続きです。

kurupical.hatenablog.com

今日は

  • 1.AI作成
  • 2.AIチューニング
  • 3.ポートフォリオの作成
  • 4.自動取引ツール作成 ←ここ

を書きます。

ちなみに、FXのシステムはバグがあり実際は全然勝てないシステムでした・・・。

tensorflowのグラフでif文使うときは

if flg
    a + b
else
    a * b

じゃなく

def add(a,b):
    return a + b
def mul(a,b):
    return a * b
tf.cond(flg, add(a+b), mul(a+b))

ってかかないと駄目でした。
上記を理解していなかったせいで、想定しない動きをしていました。

ということで自動売買は現在運用はしていません。
(実弾を使ってテストしたりで1000円くらいかかったのに・・・)
ですが自動売買システム自体は動くようになったので、その経緯を残しておきます。

もくじ

1.ブラウザ自動操作

seleniumを使いました。
webページを取り込んで、htmlを解析して特定の場所にある文章を取ってきたり、特定のボタンを押下したり、なんでもできます

インストールや基本的な文法は以下を見ました。
qiita.com

要素の名前の取得方法を、「Qiitaのログイン画面のユーザー名の要素を取得する」やり方を例に説明します。
とってきたい画面でF12キーを押下すると以下のような開発画面になります。
f:id:kurupical:20171218183406p:plain:w500

左下「インスペクター」の左にあるボタンをクリックして、名前を取得したい要素にカーソルを合わせます。
f:id:kurupical:20171218183820p:plain:w500

すると、該当するHTMLコードが黒塗りされます。
f:id:kurupical:20171218184027p:plain:w500

この場合、ユーザー名のidは"identity"となるので

find_element_by_id("inputName").send_keys("user@user.co.jp")

と入力すれば、ユーザー名のところに"user@user.co.jp"が勝手に入力されます。

また、id等がうまく取得できない場合はxpathが使えます。
調べたい箇所のHTMLコードをを右クリック→コピー→「xpath」とすれば取得できます。
以下のコードは、idで指定した場合と同じ動きをします。

find_element_by_xpath("//*[@id="identity"]").send_keys("user@user.co.jp")

以下のseleniumの逆引き辞書おすすめです。
www.seleniumqref.com

スクショが撮れたり、seleniumでいろいろできるみたいですね!

2.自動起動

Linuxではcrontabというwindowsでいうタスクマネージャーみたいなものがあるようです。
基本的な使い方は以下の記事を参考にして勉強しました。
qiita.com

注意事項は以下です。

pythonのパスが通ってない

python run.py ではなく、
/home/owner/anaconda3/bin/python run.pyとすると動きました。
パス自体は、コマンドでwhich pythonで調べられます。

カレントディレクトリはhome

ディレクトリが
test/hoge.py
test/data/hoge.csv

hoge.pyの中身が

pd.read_csv("data/hoge.csv")

となっているプログラムをcrontabで動かすためにcrontabに
00 15 * * * /home/owner/anaconda3/bin/python test/hoge.pyと登録しても
カレントディレクトリがhomeなので、data/hoge.csvを正しく参照できません。

どうしたらいいかわからなかったので、以下のようなrun.shファイルを作成し

cd test
/home/owner/anaconda3/bin/python test/hoge.py

00 15 * * * /home/owner/anaconda3/bin/python test/run.shと登録することで
カレントディレクトリを無理やり変えました。

雑記

HTMLとかCSSとかちゃんと知らないとSelenium使えないんだろーなーとか思っていたので、あまり知識がなくても使えてしまうのにびっくりしました。Seleniumの自動操作がちゃんと動いた時感動しました。。

肝心の自動取引AIのほうは、強化学習も試してみるなどもう少し粘りたいです。

AWSに乗せるのも少しやってみたのですが、よく考えたらソースコードに口座のIDとパスワードを埋め込んでいるので怖くてやめました。

今回はヒロセ通商のサイトで自動ログインから売買までを実装しましたので、
ヒロセ通商の自動ログイン〜売買までのソースが欲しいかたはご一報ください。
誤動作などによる責任は取れないため、ご利用は自己責任でお願いします。。

<メモ>Tensorflowのsessionをループ内で複数回open/closeするとメモリ不足になる

ずっと悩んでたのが解決したのでメモします。

Tensorflowのsessionをループ内で複数回open/closeするとメモリ不足になる

同一コード内で、Tensorflowのsessionを何度もopen/closeするプログラムを組んでいました。
するとちょっとずつ使用メモリが膨らんでいき、最後にはメモリー不足でプログラムが止まってしまう、という問題がずっとありました。

ガベージコレクションを試してみたのですが全く解決せず一旦放置していたのですが、
ついに見つかりました。
参考サイトは以下です。
stackoverflow.com

この質問に対する回答で、以下が記載されていました。

Explicitly create a new graph in each for loop. This might be necessary if the structure of the graph depends on the data available in the current iteration. You would do this as follows

for step in xrange(200):
    with tf.Graph().as_default(), tf.Session() as sess:
        # Remainder of loop body goes here.

計算グラフは、sessionをcloseしてもメモリ上に残るようです。
なので、繰り返し行う場合は、「どの計算グラフを使うか」を明示的に示す必要があるみたいです。

tensorflowの計算グラフが未だに十分理解できていません・・・。

<為替分析>11/29 ディープラーニングで為替自動トレード

ディープラーニングで為替の自動トレードツール作成を行いました。
まだ完了していないのですが、作成にいたる工程やその時苦労したことなどを書いていきます。

工程

工程ごとに順に書いていきます。

1.AI作成

めちゃくちゃ簡単ですが、以下に記載しています。
kurupical.hatenablog.com

2.AIチューニング

アンサンブル学習

株やFXをAIに予想させるときの結果は、学習回数よりも重みの初期値に大きく依存しているような気がします。
今回は1つのAIに学習させるのではなく、重み初期値の異なるAIを5つ作成して多数決を取る形を試してみました。
この方法は、複数の分類器を使って多数決を取るアンサンブル学習に近い考え方かなと思います。
なんとなく邪道な気もしますが・・・

以下は結果です。
f:id:kurupical:20171129140723p:plain:w400

各AIの正解率より、多数決AIのほうが正解率が高くなっています。
ただ、各AI間の相関が高いため、効果的な多数決となっているかどうかは不明です・・・。

このアンサンブル学習もどきを組み込みたかったのですが、うまく組み込めず。
Tensorflowのセッションを何回もOpen/Closeするとメモリが足りなくなって落ちてしまいます。

ガーベジコレクションについて調べたり教えてもらったりしたのですが、解決できませんでした。
Tensorflowのsession.close()、ちゃんとメモリ解放してるのかなぁ?

3.ポートフォリオの作成

AIは「売り」「買い」の判断しか行わないため、売買量や売買通貨は自分で決めることになります。(ここもAIにやらせればよかった)

リスク受容

ポートフォリオを組むにあたって一番大事なのは、「リスク許容量」だとよく言われます。
自分がどこまでのドローダウンに耐えられるのかをまずは考えました。
結果、20日で25%のドローダウンまでを受容することにしました。

売買通貨

買う通貨は、メジャーでスプレッドが安いUSDJPY/EURJPY/GBPJPYの3つとしました。
AIが得られる利益の相関は、3通貨間で高くない(0.09〜0.14)のでポートフォリオを組むには最適です。

ポートフォリオの構築

エクセルと10分くらい格闘し、以下のポートフォリオとしました。
ポートフォリオ構築の基準は、「リスク受容」の条件をギリギリ満たしていることです。

  • USD/JPY レバ1.79倍
  • EURJPY レバ1.68倍
  • GBPJPY レバ1.03倍

合計レバ5倍

リスク計算

過去10年でAIによる取引シミュレーションを行い、各20日間の累計利益を計算しました。
結果、以下となりました。

  • データ数: 2584(全データ2603を20日区切りに分けた)
  • 平均 : +8.17%
  • 標準偏差 : 9.88%
  • 最小 : -23.35%
  • 最大 : +57.33%

ここからリスクを計算します。
信頼区間は厳しめに99.9%としました。99.9%の確率で収益は以下区間に収まります
信頼区間99.9%のZは3.2905なので

平均 - 標準偏差 * 3.2905 < PL < 平均 + 標準偏差 * 3.2905
※PL=損益

具体的な値を代入すると、以下の通りです
-24.54% < PL < +40.68%

99.9%の確率で損益が-24.54%〜+40.68%の間に収束することになります。
(本読みながら計算したので、あっているかイマイチ自信はありません)

4.自動取引ツールの作成

書く体力がなくなったので、次回書きます。

参考サイト

以下サイトに記載されているアイデアをかなり取り入れさせていただきました。
システムトレードに限らず投資をする方にとってもかなり有益なブログだと思います。

we.love-profit.com

参考書籍

・アンサンブル学習とか
機械学習の勉強にもおすすめ。

システムトレード
ポートフォリオの組み方やリスクの考え方など、参考になりました。

<為替分析>11/21 ディープラーニングで為替分析(2)

今日書くこと

  • 結果
  • 結果検証
  • 考え方
  • システム化

結果

以下の条件で行いました

  • 取引期間: 2013/1/1〜2017/11/1

結果ですが、正解率が56.8%となりました。

結果検証

今回はじきだした正解率56.8%は優位性があるのか、単なるマグレなのか、について考察します。

デタラメに上がるか下がるかを予想した時の正解率を計算すると、99%の確率で45.9%(597回)〜55.1%(702回)に収まります。
つまり、56.8%という数字は優位性のある数字と見て間違いないと思います。
また、今回ハイパーパラメータのチューニングはほぼ全くしていないため、オーバーフィッティングにはなっていないと思います。

cf.計算根拠:区間推定
2.3 区間推定/信頼区間

考え方

  • 入力データは、予想したい日から過去20日分の為替データ。具体的には以下の通り。
  • 出力は、「翌日上がるか下がるか」。(上がれば買い、下がれば売り)

システム化

まあまあ良い数字が出たと思っています。
ということで、実用化に向けて以下を行いたいと思っています。

  • 取引判断の自動化
    • 毎日自動でデータ取得し、取引を判断する。(1日1回)
  • 取引判断通知の自動化
    • 自分のSlackに通知する。
  • (最終的には)取引の自動化

今回は、AWSとSlackの疎通をやりました。

AWSから自分のSlackに通知を送るまで

まず、AWSを開設しました。
学習はせず推論のみをさせるので、一番しょぼいt2.microにしました。

次に、以下を参考にAWS上にプログラムをのっけました。
qiita.com

で、テストしてみる・・・。
f:id:kurupical:20171121185023p:plain:w400

うまくいきました!

今後

ディープラーニング界隈では、多分ディープラーニングよりもその周り(環境の構築など)のスキルが必要になってくると思うので、この際頑張ってこのシステムを完成させてみようと思います。
インフラ周りは、素人同然ですが・・・。

<為替分析>11/14 ディープラーニングで為替分析(1)

久しぶりのブログ更新です。
半月ほどブログをほったらかしにしていたのですが、毎日20アクセスくらい入ってくるのは嬉しいですね。

cartpoleも完成して、目標がなくなってしまい手が止まって半月。
ニートをこじらせてました。
このままだと社会復帰できなさそうなので、手頃なチャレンジをしてみようと思います。

サマリ

  • チャレンジ内容
  • 背景
  • 具体的にやること

チャレンジ内容

ディープラーニングで為替分析をやってみます。
AIで株をやることと比較すると、以下のような特徴がありそうです。

  • FXが優位
    • データが取りやすい。株はデータが整備されていない。(株式分割など)
    • 場代の割合が株と比べて少ない。
  • 株が優位
    • ライバルの数。株は個別銘柄で勝負するので小規模な銘柄はライバルが少ないですが、FXはそうはいかなさそうです。ドル円とか無理ゲーそう。

背景

以下記事を読み、僕もやってみたくなりました。
qiita.com

本記事の最後のシリーズでは、機械学習で正解率58%を達成しています。
(200強のサンプルで正解率58%は統計的に優位性があると言えます。過学習の可能性は否定できませんが)

記事の中で特に印象深かったのは

  • 移動平均線などのテクニカル指標を入力データにしていたこと

でした。
テクニカル指標自体に意味があるというより、投資家がテクニカル指標を見て投資判断をする以上、テクニカル指標は相場形成に意味をなすという考えでした。

具体的にやること

チュートリアルとして、以下データを単純なニューラルネットワークに通すプログラムを書いてみます。

データを見てみる

過去10年のドル円データを取得し、1日の値動き(前日始値〜当日始値)のヒストグラムを作ってみました
pythonで作れよって話ですが、Excelが簡単だったので楽してそっちで・・・)

f:id:kurupical:20171114233243p:plain

1日値動きの平均は0.5(50pips)程度です。
手数料(0.3〜0.4pips)を考慮すると、手数料の割合は1%弱。
カジノのルーレットのハウスエッジより良い数字だと思います!

<メモ>DeepLearning ニューラルネットワークのチューニング観点

これまで株価予測(RNN)、強化学習(DQN)、画像認識(DNN)と3つやってみました。

これまでの経験で、どのハイパーパラメータをチューニングすると効果があるのかメモしました。

※ほとんど主観と断片的な知識で語っているので、間違いがあればご指摘ください。

ニューラルネットワークのノード数と層の厚み

重要度: ★★★★★

層が多ければ多いほどよいというものではなく、問題の複雑さを考慮に入れて決めるのがよさそうです。

例えば、単純な画像認識(mnist/手書き数字文字。28x28pxの画像)では、ノード数100×2層よりノード数30×2層のほうが学習スピードが高かったです。

一方、強化学習(cartpole)ではノード数16×3層では学習が収束せず、ノード数100×3層で学習が収束しました。ノード数200×3層だと、収束しませんでした。

ノード数や層が多ければ多いほどニューラルネットワークの表現力が強くなります。
その分、過学習してしまうみたいです。
なので、簡単な事象はニューラルネットワークの層やノード数を少なくするのがよいかもしれません。

学習率

重要度: ★★★

論文に書いてある学習率をそのまま使えば、概ね大丈夫そうです。
あまりここをチューニングする意味はなさそうです。

ニューラルネットワークの重み・バイアスの初期値

重要度: ★★★

分類問題の場合は、何も考えず「標準正規分布(平均=0,標準偏差=1)」でよさそうです。

回帰問題の場合は、「出力層で出力される値」に応じて重み付けを初期化する必要がありそうです。

cartpole(強化学習、回帰問題)の場合、出力層は-1〜1の間を想定していました。
この場合に重みの初期値が「標準正規分布(平均=0,標準偏差=1)」だと、出力層は-1〜1の値に収束しません。(ニューラルネットワークは掛け算なので、層を重ねるごとに数字が大きくなるためです)
この場合は、1以上の値と1以下の値がバランスよく散らばるような値に初期化してあげるとよいと思います。
cartpole問題は、平均=0,標準偏差=0.1で初期化してうまくいきました。

損失関数

重要度: ★

ほぼ、誤差二乗和法が最強です。 分類器の場合はCrossEntropyかな?

その他

  • Batch Normalization
  • Dropout

などニューラルネットワークの学習を効率化させる方法は、あまり組み込んでいないので語れないです。恥ずかしながら…