Pythonで株価の上下運動を機械学習で推定（ランダムフォレスト編）

#注意
 文中の「全く予測できない場合、50%なので効果はありそうだなという印象です。」が、かなり怪しい（恐らく間違っている）とご指摘を受けたました。皆さんもしっかり勉強しましょう。ページを消してもよいですが、自戒も込めてとりあえず残しておきます（消すかも）。

#はじめに

　前日までの株価データを用いて、翌日の株価が上がるか、下がるかを機械学習で判定します。ランダムフォレストを使っただけですが、株価をどう使うかだけでも結構面倒でした。上から順番に追って行けば、実際に分析するときの考慮事項が分かっていくと思います。（注意：プログラム及び分析結果の活用は自己責任でお願いします。）

- 分析する前のデータ準備（一番大事）
- ランダムフォレストとを用いた推定

#株価取得
プログラムの順番に説明をしていきます。最初の株価取得に関しては、[Pythonで株価取得とローソク足チャート作成](
http://qiita.com/kjybinp1105/items/be462b481b47b6f94b14 "Pythonで株価取得とローソク足チャート作成")を見てください。

```py3:株価取得
import datetime
import pandas_datareader.data as web
import matplotlib.pyplot as plt
import pandas as pd

#期間の設定
start = datetime.datetime(2011, 1, 1)
end = datetime.datetime(2017, 5, 30)

#株価取得
df = web.DataReader('TM', 'google', start, end)
df = df.loc[:, ['Close']]
```

#株価の加工
　機械学習では、入力$x$と出力$y$を決める必要があります。今回の分析では、連続する$period+1$日の株価を使って、最終日の翌日の株価が上がるかを判定します。単純に$x$を決定するならば、$x=(p_{1},...,p_{period},p_{period+1})$にしたいところですが、$x=(p_{1}/p_{period+1},...,p_{period}/p_{period+1})$にします。これは、年によって同じ値段でも意味合いが異なることが多いからです。特に具体的な根拠はありませんが、このような処理により、性能が上がることはよくあります。$y$は上がっていれば1、下がっていれば$-1$にしました。

```py3:株価の加工
#株価のある次の日を返す関数
def GetIndexNextDay(df,day):
    for p in range(1,10):
        if((day+datetime.timedelta(days=p)) in df.index):
            next_day = day+datetime.timedelta(days=p)
            return next_day

period = 5
transitions = []
sample =　1600
for i in range(sample):
　  day = df.index[i]
    day_transition = [df['Close'][day]/df['Close'][day]]
    next_day = day
    for term in range(1,(period+2)):
        next_day = GetIndexNextDay(df,next_day)
        day_transition.append(df['Close'][next_day]/df['Close'][day])
    transitions.append(day_transition)
    df2 = (pd.DataFrame(transitions)).T

    x = (df2.loc[:period-1,:].values/df2.loc[(period),:].values).T
    y = df2.loc[(period+1),:].values/df2.loc[(period),:].values
    x = pd.DataFrame(x)
    y = pd.DataFrame(y)

    f = lambda x :  1 if x > 1.0 else -1
    y = y[0].apply(f)
```

#ランダムフォレスト
　ランダムフォレストをざっくり説明すると、単純なルールと一部のデータで作った学習機（決定木）のアンケート結果で最終的な学習結果を出力する学習器です。学習器を特徴づけるパラメータとして、n_estimator(アンケート数)とmax_depth(単純なルールの複雑度)があるのですが、このパラメータの趣旨が分かれば十分です。逆に言うとこれらを調整しないと性能が出ないことが多いです。

```py3:ランダムフォレスト
#訓練用と評価用データに分類
x_train = x[:train]
x_test = x[train:sample]
y_train = y[:train]
y_test = y[train:sample]

model = RandomForestClassifier()
model.fit(x_train,y_train)

output_train = model.predict(x_train)
accurate_train = y_train-output_train
accurate_train = accurate_train.apply(lambda x : 1 if x == 0 else 0)
    
output_test = model.predict(x_test)
accurate_test = y_test-output_test
accurate_test = accurate_test.apply(lambda x : 1 if x == 0 else 0)
```

#評価
 作成したデータとランダムフォレストを用いて、株価の上下運動を推定したいと思います。今回最適な学習器を探すために、period,n_estimator,max_depthをグリッドリサーチしました。その結果、最適なパラメータはperiod=2,n_estimator=10,max_depth=100で正解率は58.6%でした。全く予測できない場合、50%なので効果はありそうだなという印象です。

```py3:評価
import datetime
import pandas_datareader.data as web
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

import sklearn.model_selection as ms
from sklearn.ensemble import RandomForestClassifier

#from sklearn.ensemble import RandomForestClassifier

def GetIndexNextDay(df,day):
    for p in range(1,10):
        if((day+datetime.timedelta(days=p)) in df.index):
            next_day = day+datetime.timedelta(days=p)
            return next_day
        
def GridForest(df,period,n_estimators,max_depth):
    transitions = []
    sample =1600
    train = 1500
    for i in range(sample):
        day = df.index[i]
        day_transition = [df['Close'][day]/df['Close'][day]]
        next_day = day
        for term in range(1,(period+2)):
            next_day = GetIndexNextDay(df,next_day)
            day_transition.append(df['Close'][next_day]/df['Close'][day])
        transitions.append(day_transition)

    #図示
    df2 = (pd.DataFrame(transitions)).T
    #df2 = df2.T
    #df2.plot(legend=False,color = 'black')

    x = (df2.loc[:period-1,:].values/df2.loc[(period),:].values).T
    y = df2.loc[(period+1),:].values/df2.loc[(period),:].values

    x = pd.DataFrame(x)
    y = pd.DataFrame(y)

    #print(y)

    th = 0.000
    f = lambda x : 0 if abs(x-1.0) < th else 1 if x > 1.0 else -1
    y = y[0].apply(f)

    x_train = x[:train]
    x_test = x[train:sample]
    y_train = y[:train]
    y_test = y[train:sample]

    model = RandomForestClassifier(n_estimators=n_estimators,max_depth=max_depth)
    model.fit(x_train,y_train)
    
    output_train = model.predict(x_train)
    accurate_train = y_train-output_train
    accurate_train = accurate_train.apply(lambda x : 1 if x == 0 else 0)
    
    output_test = model.predict(x_test)
    accurate_test = y_test-output_test
    accurate_test = accurate_test.apply(lambda x : 1 if x == 0 else 0)
    
    return accurate_train.sum()/accurate_train.shape[0],accurate_test.sum()/accurate_test.shape[0]

#株価取得
start = datetime.datetime(2011, 1, 1)#株価取得開始日
end = datetime.datetime(2017, 5, 30)#株価取得終了日
df = web.DataReader('TM', 'google', start, end)
df = df.loc[:, ['Close']]

periods = range(1,11)
n_estimators = [10,100]
max_depths = [10,50,100,500]

n = 5

max_accuracy = 0.0
max_para = []

for period in periods:
    for n_estimator in n_estimators:
        for max_depth in max_depths:
            acc_train = 0
            acc_test = 0
            for i in range(n):
                acc1,acc2 =  GridForest(df,period,n_estimator,max_depth)
                acc_train = acc_train + acc1/n
                acc_test = acc_test + acc2/n
            print('period',period,'n_estimator',n_estimator,'max_depth',max_depth,'acc_train',acc_train,'acc_test',acc_test)
            if(acc_test>max_accuracy):
                max_accuracy = acc_test
                max_para = [period,n_estimator,max_depth]

print('結果：',max_accuracy,max_para)
```


#参考
Pythonでデータ分析：ランダムフォレスト
http://tekenuko.hatenablog.com/entry/2016/09/20/222453