投資quest:pyhtonでシステムトレードを作る(4)ランダムフォレストで予測

##投資quest:pyhtonでシステムトレードを作る(4)ランダムフォレスト

###株式特徴量エンジニアリング
今までの記事でとりあえず移動平均線、ボリンジャーバンド線、ゴールデンクロス等が計算できて、株式用の特徴量ができました。こういうところで、ＡＩ作るにはドメイン知識が必要だと理解できますよね。

今回作った株式用特徴量
１）移動平均線（短期５日、長期２５日）
２）ボリンジャーバンド（±３σまで、0.125刻みで）
３）ゴールデンクロス、デッドクロス

今後、このマネックスのＨＰにある[テクニカル指標一覧](https://info.monex.co.jp/technical-analysis/indicators/)は順番に作っていきたいのですが、とりあえずここまでで、予測できるでしょうか？

### ターゲットは株の未来予測
ターゲットを作ります。私は会社員なので、昼間はトレードできません。
夜に解析して、翌日買うという感じになります。
そういうわけで、翌日の終値で買って、それから５日後に上がっているか？下がっているか？の２値問題にしようと思います。
翌日の朝の初値は、夜のニューヨークダウと日経シカゴ先物の影響を強く受けますので、その影響のない終値にしました。
この未来予測ができるだろうか？という問題です。未来にあがるならば買えば、儲かります。。捕らぬ狸の皮算用です。（苦笑）

```python
#何日後に高くなったか？
diff_o = 5
#終値で、今日と５日前の価格を比べ、差額を出します。
df['diff'] = df['end_price'].diff(diff_o)

#価格を入れたtargetの列を作って、プラスの場合は１マイナスの場合は０に置換する。
df['target'] = df['diff']

df['target'].mask(df['target'] > 0 , 1 ,inplace = True)
df['target'].mask(df['target'] < 0 , 0 ,inplace = True)

#targetを前に６日ずらす。これで、翌日の終値で買うと５日後に上がったか、下がったかがターゲットになる
idou = -1*(diff_o+1)
df['target'] = df['target'].shift(idou)
df['diff'] = df['diff'].shift(idou)

```
これで、価格が上がったか下がったかの２値問題になりました。一応差額の値も入れておいて、うまくできたら、回帰問題にもしようと思います。
ここまでで、株式特徴量と目的targetができました。

##株式のデータはどこから。
これが問題です。スクレイピングでうまく取ってくるというのができればいいのですが、それはまだできません（苦笑）誰かスクレイピングを教えてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336017/103ed74d-82bf-eecd-1e06-858755c6c762.png)

https://kabuoji3.com/

こんな株式サイトがあります。一応トップページの片隅に「株価データ・株主優待情報・先物データ・ランキングデータ・CSVダウンロード無料」と書いてありますので、ここからCSVを２０年間分ダウンロードしました。つまり２０回クリックしました（苦笑）

##1306　NEXT FUNDS TOPIX連動型上場投信
ターゲットはTOPIXです。よくテレビで聞く日経平均は、銘柄の入れ替えとかあって価格の連続性が失われているのが問題になりそうだと思ったので、TOPIX東証株価指数（東京証券取引所市場第1部上場の全銘柄を対象として算出される株価指数）にしました。こちらの方が価格の連続性も問題ないでしょう。
天下の野村証券の1306  TOPIX連動型上場投信 をターゲットにしました。売買単位は１０口なので、２万円あれば投資できます。

## みんな大好きランダムフォレスト
なんとなくこれで予測できるか？不安にはなりますが、まずは一回動かしてみましょう！！
アルゴリズムはランダムフォレスト。

アルゴリズム；ランダムフォレスト
ハイパーパラメーター：Optunaによるチューニング
データ：２０年分　学習０．７、テスト０．３
特徴量：株価、売買量、５日移動平均線、２５日移動平均線、ボリンジャーバンド、ゴールデンクロス、デッドクロス
指標：ターゲットは５日後の株が上がるのRecall

結果は
学習　：0.889
テスト：0.737

まぁまぁの結果です。１０回投資すれば７回は勝てそうです。微妙ですが、これだけでも投資する価値はありそうです。

未来予測ができるなんて、なんか人工知能っぽいですよね。でもなんか簡単に予測できすぎだよねー？どっか間違えているか？と一瞬不安になりましたが、まぁ良しとしましょう。

後は、５日後から少し伸ばして１０日後を計算してみようと思います。

次回は、実際に予測した結果で何を見ているのか？確認していこうと思います。



以下、生のスクリプトです。
初心者のスクリプトなので冗長でいらない行(コメント文、確認でCSV吐き出しているところ等々)も多々ありますが、そのまま載せておきます。みんなでお金持ちになりましょう。
今後、改良を加えていきます。

```Pyhton

import pandas as pd
import matplotlib.pyplot as plt
import datetime
from sklearn.model_selection import train_test_split
import matplotlib.dates as mdates
import pickle

#read_csvですべてのデータを結合する。
#1306_2001から1306_2021
#https://kabuoji3.com/株価入手サイト

df = None
#n日目
n = 5
#n×５日目
m = n*5

#何日後に高くなったか？

diff_o = 5

for i in range(2001,2021):
    
    file = '1306_' + str(i) + '.csv'
    print(file)
    
    data = pd.read_csv(file,encoding='Shift-Jis',header = 1)
    
    
    df = pd.concat( [df,data],ignore_index=True)
    
df.rename(columns={'日付': 'day', '始値': 'open_price',
                   '高値':'high_price', '安値':'low_price',
                   '終値': 'end_price', '出来高':'volume',
                   '終値調整値':'end_price_ad_value'}, inplace=True)
  
#plt.plot(df['day'], df["end_price"], label="end_price")
#plt.show()
#'day'をdatetimeタイプに変更
df['day'] = pd.to_datetime(df['day'])
#'day'をindexにして置き換える。
df.set_index('day', inplace=True)

#平均線の名前
n_ave = str(n) + "day_ave"
n_std = str(n) + "day_std"
m_ave = str(m) + "day_ave"
m_std = str(m) + "day_std"

#５日間移動平均線を計算
df[n_ave]=df["end_price"].rolling(n).mean().round(1)
#標準偏差を求める
df[n_std]=df["end_price"].rolling(n).std().round(1)

#５日間移動平均線を計算
df[m_ave]=df["end_price"].rolling(m).mean().round(1)
#標準偏差を求める
df[m_std]=df["end_price"].rolling(m).std().round(1)

#ボリンジャーバンドを求める（±σ）
for i in range(24):
    j = (1 + i)/8
    
    str_plus_nj = str(n) + 'day_bol_+' + str(j)
    str_mainas_nj = str(n) + 'day_bol_-' + str(j)
    str_plus_mj = str(m) + 'day_bol_+' + str(j)
    str_mainas_mj = str(m) + 'day_bol_-' + str(j)

    
    df[str_plus_nj] = df[n_ave] + j*df[n_std]     
    df[str_mainas_nj] = df[n_ave] - j*df[n_std]
    
    df[str_plus_mj] = df[m_ave] + j*df[m_std]     
    df[str_mainas_mj] = df[m_ave] - j*df[m_std]
    
    width_nj = 'wide_' + str(n) +'day_' + str(j)
    width_mj = 'wide_' + str(m) +'day_' + str(j)
    df[width_nj] = 2*j*df[n_std] 
    df[width_mj] = 2*j*df[m_std] 

#ゴールデンクロスデットクロスを求める    
df['golden'] = 0
df['dead'] = 0
df['golden'] = df['golden'].where(df[n_ave] > df[m_ave],1)
df['dead'] = df['dead'].where(df[n_ave] < df[m_ave],1)
    
#ターゲット今日から５日後の価格が高いか安いか
#移動量、今日から５日後の価格が高いか安いかを１日前にし知りたい。
#明日の終値で買って５日目にどうなっているか？

df['diff'] = df['end_price'].diff(diff_o)
df['target'] = df['diff']
#print(df.dtypes)
df['target'].mask(df['target'] > 0 , 1 ,inplace = True)
df['target'].mask(df['target'] < 0 , 0 ,inplace = True)

#df.to_csv('210116_try.csv')

#targetを５日ずらして、五日後に上がったか、下がったかがターゲットになる
idou = -1*(diff_o+1)
df['target'] = df['target'].shift(idou)
df['diff'] = df['diff'].shift(idou)

#print(df.columns)
#print(df.isnull().sum())
df_temp = df.dropna(how='any',axis=0)
#print(df_temp.isnull().sum())

#df_temp.to_csv('210117_try.csv')

X_data = df_temp.drop(['diff','target'],axis=1)
y_data = df_temp['target']

X_train, X_test, y_train, y_test = train_test_split(X_data, y_data,test_size=0.3)

from sklearn.ensemble import RandomForestClassifier
import optuna
from sklearn.metrics import accuracy_score
from sklearn.model_selection import cross_val_score
from sklearn.metrics import recall_score

def objective(trial):
    n_estimators = trial.suggest_int("n_estimators", 4, 500)
    min_samples_split = trial.suggest_int("min_samples_split", 4, 100)
    max_leaf_nodes = int(trial.suggest_discrete_uniform("max_leaf_nodes", 4, 1000, 4))
    criterion = trial.suggest_categorical("criterion", ["gini", "entropy"])
    
    rf = RandomForestClassifier(n_estimators = n_estimators,
                                min_samples_split = min_samples_split, 
                                max_leaf_nodes = max_leaf_nodes,
                                criterion = criterion,n_jobs=-1)
    
    
    score = cross_val_score(rf, X_train, y_train, cv=5,scoring = 'recall')
    score_mean = score.mean()
    
    return score_mean

study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials = 250)

# チューニングしたハイパーパラメーターをフィット
optimised_rf = RandomForestClassifier(n_estimators = study.best_params['n_estimators'],
                                     min_samples_split = study.best_params['min_samples_split'],
                                     max_leaf_nodes = study.best_params['min_samples_split'],
                                     criterion = study.best_params['criterion'],  n_jobs=-1)

optimised_rf.fit(X_train ,y_train)
with open('optimised_rf_0206_2.pickle', mode='wb') as f:
    pickle.dump(optimised_rf, f)  
train_pred = optimised_rf.predict(X_train)
test_pred = optimised_rf.predict(X_test)

print('accuracy')
print(accuracy_score(y_train, train_pred))
print(accuracy_score(y_test, test_pred))

print('recall')
print(recall_score(y_train, train_pred))
print(recall_score(y_test, test_pred))

importance = pd.DataFrame(optimised_rf.feature_importances_,index=X_data.columns).T
importance.to_csv('feature_importance0131.csv')
```