6
12

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-02-06

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

株式特徴量エンジニアリング

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

今回作った株式用特徴量
1)移動平均線(短期5日、長期25日)
2)ボリンジャーバンド(±3σまで、0.125刻みで)
3)ゴールデンクロス、デッドクロス

今後、このマネックスのHPにあるテクニカル指標一覧は順番に作っていきたいのですが、とりあえずここまでで、予測できるでしょうか?

ターゲットは株の未来予測

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

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

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

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

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

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

株式のデータはどこから。

これが問題です。スクレイピングでうまく取ってくるというのができればいいのですが、それはまだできません(苦笑)誰かスクレイピングを教えてください。
image.png

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

1306 NEXT FUNDS TOPIX連動型上場投信

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

みんな大好きランダムフォレスト

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

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

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

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

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

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

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

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


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×5日目
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"

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

#5日間移動平均線を計算
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)

#ターゲット今日から5日後の価格が高いか安いか
#移動量、今日から5日後の価格が高いか安いかを1日前にし知りたい。
#明日の終値で買って5日目にどうなっているか?

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を5日ずらして、五日後に上がったか、下がったかがターゲットになる
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')
6
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
12