見出し画像

【Python】データ分析で株価を予測できるのか!?

目次

1.背景
 
1.1 経緯
 1.2 条件
2.利用するデータとデータ分析方法
 
2.1 利用する環境
 2.2 利用するデータ
 2.3 データ分析方法
3.結果
 
3.1 分析データ
 3.2 分析結果
 3.3 組み合わせ分析結果
 3.4 結論
4.分析方法の詳細
 
4.1 データの扱い
 4.2 教師あり学習(回帰)
 4.3 教師あり学習(分類)
 4.3 ディープラーニング(回帰)
 4.4 ディープラーニング(分類)
5. あとがき

1.背景

1.1 経緯

4か月ほど前から、データ分析講座を受講している。講座の最後のコースは、学習成果の実践としてブログを作成すること。テーマは自由。
私は、30年ほど株取引を行っているが、なかなか儲からない。そこで、データ分析で株価を予想できないかと考えた。

1.2 条件

私は、昼間は働いているサラリーマンなので、株取引を行うとしても、せいぜい1日1回。これから株が上がると思えば、株を買うし、下がると思えば、株を売る(信用取引)。その日の終値近くで、買うか売るかを決めて、株取引を行い、翌日の終値近くの株取引(買っていた株を売る、売っていた株を買う)で清算する。この方法で、なるべく稼げる分析アルゴリズムを調べることにした。
なお、一つの分析方法の正解率が高いとは思えない(そんな方法があれば、みんなが儲かっている)ので、いくつもの分析方法を組み合わせた予測も考える。

2.利用するデータとデータ分析方法

2.1 利用する環境

実行環境は、データ分析講座でも利用している「Google Colaboratory」を使用した。

2.2 利用するデータ

「日経株価 ダウンロード」で検索したサイトからダウンロードした日経平均株価の日次データを利用した。分析しやすいように、CSVファイルを成形して使っている。なお、日経平均株価で実際に株取引できるわけではないが、ここでは、株価の上がり・下がりが予測できるかどうかを調査するために使っている。

2.3 データ分析方法

データ分析講座で習った分析方法を一通り試すことにする。

  • 教師あり学習(回帰)

    • 線形回帰(LinearRegression):説明変数の関係性を元に、数値を予測する手法で、直線で予測する回帰モデル

    • ラッソ回帰(Lasso):「予測に影響を及ぼしにくいデータ」にかかる係数をゼロに近づける手法を用いて線形回帰の適切なパラメータを設定する回帰モデル

    • リッジ回帰(Ridge):係数が大きくなりすぎないように制限する手法を用いて線形回帰の適切なパラメータを設定する回帰モデル

    • ElasticNet回帰(ElasticNet):ラッソ回帰とリッジ回帰を組み合わたモデル

  • 教師あり学習(分類)

    • ロジスティック回帰(LogisticRegression):線形分離可能なデータの境界線を学習によって見つけて、データの分類を行う手法

    • 線形SVM(LinearSVC):データの境界線(直線)を見つけ、データの分類を行う手法

    • 非線形SVM(SVC):データの境界線(曲線を含む)を見つけ、データの分類を行う手法

    • 決定木(DecisionTreeClassifier):データの要素内でのある値を境にデータを分割し、データの属するクラスを決定する手法

    • ランダムフォレスト(RandomForestClassifier):決定木のモデルを複数作り、分類の結果をモデルの多数決で決める手法

    • k-NN(KNeighborsClassifier):予測をするデータと類似したデータをいくつか見つけ、多数決により分類結果を決める手法

  • ディープラーニング(Keras+Tensorflow)

    • ディープラーニング(回帰):生物の神経細胞の仕組みを模したアルゴリズム「ニューラルネットワーク」を利用した手法。回帰では、株価を予測する。

    • ディープラーニング(分類):生物の神経細胞の仕組みを模したアルゴリズム「ニューラルネットワーク」を利用した手法。分類では、株価が上がるか下がるかの確率を予測する。

3.結果

3.1 分析データ

2020/7/2~2020/12/30の120日分のデータを学習データとし、2021/1/4の株価のup/down(up=1/down=0)を予測。次に、2020/7/3~2021/1/4の120日分のデータから、2021/1/5の株価のup/downを予測。これを繰り返し、2021/12/30までの245日分(1年分)の株価を予測した。
回帰で株価を予測した場合は、その予測株価が前日の株価より高いか安いかで、株価のup/down(up=1/down=0)に変換して、正解率を率を出した。

3.2 分析結果

結果は、下の表の通りです。正解率が高かったのは、以下の分析である。
 1位 ディープラーニング(株価予測)  :55.1%
 2位 決定木(DecisionTreeClassifier)    :53.5%
 3位 ディープラーニング(up/down予測):53.1%
実は、全てdownと予測したら49%(upなら51%)の正解率なので、それ以下であれば、データ分析した価値はない(ヤマ感より悪い)。

教師あ学習(回帰)による予測結果
教師あり学習(分類)による予測結果(1)
教師あり学習(分類)による予測結果(2)
教師あり学習(分類)による予測結果(3)
ディープラーニングによる予測結果

3.3 組み合わせ分析結果

各分析の予測結果を見ると、連続して正解の時もあれば、連続不正解の時もあります。そこで、正解率が高い3つの分析の多数決でその日の予測を決める「多数決」と、過去5日間の正解率が一番高い分析方法を、その日の予測として採用する「5日間単純加算」、過去5日間の正解数を合計した数を、また5日間合計した「5日間加重加算」で予測してみた。
その結果、正解率は、最高で57.1%。ディープラーニング(株価予測)単独と比べて2%上がった。

分析結果の組み合わせによる予測結果

3.4 結論

もし、60~70%以上の確率で株価のup/downが予測できるなら、大金持ちになれると思ったが、残念ながらそんなに良い結果は得られなかった。
架空の話にまるが、多数決(up/down正解率:57.1%)の結果に従い、日経平均(27,013円~30,670円)の売買を毎日繰り返した場合、6,907円(30,670円の約22%)のプラスになる。本当に22%の利益が出るなら、多数決を信じて、株価取引を行う価値はあるかもしれない。ただ、その時期の株価変動によって正解率の高い分析方法も正解率も変わるので、常に一定の利益を出すことは難しいでしょう。(株取引は、自己責任でお願いします。)

4.分析方法の詳細

データ分析セミナーで学んだといっても、初心者なので、いろんなところで悩みました。その悩んだ部分を中心に補足します。

4.1 データの扱い

4.1.1 CSVファイルの読み込み

CSVファイルは、googleドライブに格納したので、まず、googleドライブをマウントする処理を実行する。

from google.colab import drive
drive.mount('/content/drive')

次に、CSVファイルを読み込む処理を実施する。このファイルを何も考えずにpd.read_csv()で読み込むと、文字化けした。文字化けの原因は、文字コードだと思い、”encoding="shift_jis”を追加して解決。なお、ファイルの文字コードは、テキストエディタを使って確認した。

# データの読み込み
# 1行に、日付、updown、終値、前日~6か月前までの終値が格納されている
all_data = pd.read_csv("/content/drive/MyDrive/Colab Notebooks/data/101_daily_220620-2120_all.csv", encoding="shift_jis")
all_data

ここで出力されたファイルのデータは以下の通り。

前述したように、日経平均株価の日次データを使ったが、分析しやすいように、上記のような形にExcelで成形した(ファイルには、2022/6/20までのデータを含んでいるが、使ったのは、2021/12/30までのデータ)。各項目の意味は、以下の通り。

日付:この日の日付。2020/7/2の場合として以下説明。
updown:前日の終値に比べて、この日の終値が上がった(1)、下がった(0)
終値:この日の終値。2020/7/2の終値。
1day before:1日前(1営業日前)の終値。2020/7/1の終値。
2days before:2日前(2営業日前)の終値。2020/6/30の終値。
・・・
120days before:120日前(120営業日前)の終値。2020/1/6の終値。

4.1.2 学習データと予測データの作成

CSVファイルから読み込んだデータ(all_data)から、最初の123日分のデータを最初の学習データ(day=0、0~122行目)とする。そのデータから、「日付」と「updown」を削除して、「1day before」から「120days before」のデータを説明変数、「終値」を目的変数とした。
予測するデータは、124日目のデータ(123行目)とした。

  # 変数の定義・初期化
  train_index = day                                         # 学習データの最初のindex
  train_num   = 123                                         # 学習データの数
  pred_index  = train_index + train_num                     # 予測するデータのindex

  # 学習データ決定
  train_data = all_data[train_index:pred_index]             # 123日分のデータ
  train_data = train_data.drop(["日付", "updown"], axis=1)  # 共通で必要のないデータを削除 
  stock_train_X = train_data.drop("終値", axis=1)           # 説明変数(予測するデータ以外)を代入
  stock_train_y = train_data["終値"]                        # 目的変数(予測するデータのみ)を代入

  # 予測するデータ決定
  test_data = all_data[pred_index:pred_index+1]             # 学習データの次の1日(1日だけどスライス指定)
  test_data = test_data.drop(['日付', 'updown'], axis=1)    # 共通で必要のないデータを削除
  stock_test_X = test_data.drop("終値", axis=1)             # 説明変数(当日の終値以外)を代入
  stock_test_y = test_data["終値"]                          # 目的変数(当日の終値のみ)を代入

4.1.3 結果格納用DataFrameの作成

結果を格納するDataFrameは、カラムを定義し、格納する予定の行数分を0で埋めた。当初、カラムだけ定義し、データが空のDataFrameを作れば良いと思ったが、locやat代入できなかった。

# 変数の定義・初期化
pred_columns = ["LinearReg", "LinearReg-updown", "Lasso", "Lasso-updown", 
                "Ridge", "Ridge-updown", "ElasticNet", "ElasticNet-updown"] 

# 予測結果を代入するDataFrameを作成
pred_data = pd.DataFrame( index=[], columns=pred_columns)   # 予測を代入するDataFrameの枠を作成
record = pd.Series([0,0,0,0,0,0,0,0], index=pred_data.columns) # pred_dataを初期化するデータ作成
for _ in range(pred_days):                                  # 予測する日数分
    pred_data = pred_data.append(record, ignore_index=True) # pred_dataのデータ追加・データ初期化

4.1.4 CSVファイルへの書き出し

結果を格納するDataFrameに、分析結果を格納した後、CSVファイルに書き出す。

# CSVファイルに書き出し
pred_data.to_csv("/content/drive/MyDrive/Colab Notebooks/data/101_daily_pred_Reg.csv")

4.2 教師あり学習(回帰)

4.2.1 ソースコード

教師あり学習(回帰)の分析アルゴリズムとして、線形回帰(LinearRegression)、ラッソ回帰(Lasso)、リッジ回帰(Ridge)、ElasticNet回帰(ElasticNet)を使った。前述したように、120日分のデータで学習し、121日目の株価を上記アルゴリズムで予測、これを245日分繰り返した。また、株価を予測した後、その株価が前日の株価より大きければ、株価が上がる(up)予想とし、小さければ、株価が下がる(down)予想として、結果を出した。
ここで、最初に悩んだのは、DataFrameへのデータコピー。最初は、iatを使っていたが、カラム名を入れた方が間違えないと思い、atを使うように変更した。

##### 教師あり学習(回帰)#####
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Lasso
from sklearn.linear_model import Ridge
from sklearn.linear_model import ElasticNet

# データの読み込み
# 1行に、日付、updown、終値、前日~6か月前までの終値が格納されている
# updownは、その日の終値が前日より上がったら1,下がったら0の値
all_data = pd.read_csv("/content/drive/MyDrive/Colab Notebooks/data/101_daily_220620-2120_all.csv", encoding="shift_jis")

# 最初の半年分(123個)のデータを学習し、翌日のデータを予測する。
# 予測するのは、2021年1月4日~2021年12月30日までの245日分
# 変数の定義・初期化
pred_columns = ["LinearReg", "LinearReg-updown", "Lasso", "Lasso-updown", 
                "Ridge", "Ridge-updown", "ElasticNet", "ElasticNet-updown"] 
pred_days = 245                                             # 予測するのは245日分

# 予測結果を代入するDataFrameを作成
pred_data = pd.DataFrame( index=[], columns=pred_columns)   # 予測を代入するDataFrameの枠を作成
record = pd.Series([0,0,0,0,0,0,0,0], index=pred_data.columns) # pred_dataを初期化するデータ作成
for _ in range(pred_days):                                  # 予測する日数分
    pred_data = pred_data.append(record, ignore_index=True) # pred_dataのデータ追加・データ初期化

for day in range(pred_days):
  # 変数の定義・初期化
  train_index = day                                         # 学習データの最初のindex
  train_num   = 123                                         # 学習データの数
  pred_index  = train_index + train_num                     # 予測するデータのindex

  # 学習データ決定
  train_data = all_data[train_index:pred_index]             # 123日分のデータ
  train_data = train_data.drop(["日付", "updown"], axis=1)  # 共通で必要のないデータを削除 
  stock_train_X = train_data.drop("終値", axis=1)           # 説明変数(予測するデータ以外)を代入
  stock_train_y = train_data["終値"]                        # 目的変数(予測するデータのみ)を代入

  # 予測するデータ決定
  test_data = all_data[pred_index:pred_index+1]             # 学習データの次の1日(1日だけどスライス指定)
  test_data = test_data.drop(['日付', 'updown'], axis=1)    # 共通で必要のないデータを削除
  stock_test_X = test_data.drop("終値", axis=1)             # 説明変数(当日の終値以外)を代入
  stock_test_y = test_data["終値"]                          # 目的変数(当日の終値のみ)を代入
  
  # 教師あり学習(回帰)を実行
  # 線形回帰
  model = LinearRegression()                                # モデルは、LinearRegression
  model.fit(stock_train_X, stock_train_y)                   # 学習実行
  stock_pred = model.predict(stock_test_X)                  # 予測実行
  pred_data.at[day, "LinearReg"] = int(stock_pred)          # 予測した株価をDataFrameに格納
  if(int(stock_test_X["1day before"]) < int(stock_pred)):   # 前日より高い予測だったら
    pred_data.at[day, "LinearReg-updown"] = 1               # 株価が上がる予想(1)を代入
  else:
    pred_data.at[day, "LinearReg-updown"] = 0               # 株価が下がる予想(0)を代入
  print("day={}, 学習モデル:{}, stock_pred:{}, updown={}".format(day, model.__class__.__name__, stock_pred, pred_data.at[day, "LinearReg-updown"]))

  # ラッソ回帰
  model = Lasso()                                           # モデルは、Lasso
  model.fit(stock_train_X, stock_train_y)                   # 学習実行
  stock_pred = model.predict(stock_test_X)                  # 予測実行
  pred_data.at[day, "Lasso"] = int(stock_pred)              # 予測した株価をDataFrameに格納
  if(int(stock_test_X["1day before"]) < int(stock_pred)):   # 前日より高い予測だったら
    pred_data.at[day, "Lasso-updown"] = 1                   # 株価が上がる予想(1)を代入
  else:
    pred_data.at[day, "Lasso-updown"] = 0                   # 株価が下がる予想(0)を代入
  print("day={}, 学習モデル:{}, stock_pred:{}, updown={}".format(day, model.__class__.__name__, stock_pred, pred_data.at[day, "Lasso-updown"]))

  # リッジ回帰
  model = Ridge()                                           # モデルは、Ridge
  model.fit(stock_train_X, stock_train_y)                   # 学習実行
  stock_pred = model.predict(stock_test_X)                  # 予測実行
  pred_data.at[day, "Ridge"] = int(stock_pred)              # 予測した株価をDataFrameに格納
  if(int(stock_test_X["1day before"]) < int(stock_pred)):   # 前日より高い予測だったら
    pred_data.at[day, "Ridge-updown"] = 1                   # 株価が上がる予想(1)を代入
  else:
    pred_data.at[day, "Ridge-updown"] = 0                   # 株価が下がる予想(0)を代入
  print("day={}, 学習モデル:{}, stock_pred:{}, updown={}".format(day, model.__class__.__name__, stock_pred, pred_data.at[day, "Ridge-updown"]))

  # ElasticNet回帰
  model = ElasticNet()                                      # モデルは、ElasticNet
  model.fit(stock_train_X, stock_train_y)                   # 学習実行
  stock_pred = model.predict(stock_test_X)                  # 予測実行
  pred_data.at[day, "ElasticNet"] = int(stock_pred)         # 予測した株価をDataFrameに格納
  if(int(stock_test_X["1day before"]) < int(stock_pred)):   # 前日より高い予測だったら
    pred_data.at[day, "ElasticNet-updown"] = 1              # 株価が上がる予想(1)を代入
  else:
    pred_data.at[day, "ElasticNet-updown"] = 0              # 株価が下がる予想(0)を代入
  print("day={}, 学習モデル:{}, stock_pred:{}, updown={}".format(day, model.__class__.__name__, stock_pred, pred_data.at[day, "ElasticNet-updown"]))

# CSVファイルに書き出し
pred_data.to_csv("/content/drive/MyDrive/Colab Notebooks/data/101_daily_pred_Reg.csv")

4.2.2 結果

結果のCSVファイル(最初の11日分)は、以下の通り。

結果CSVファイル(101_daily_pred_Reg.csv)

このupdownの正解率を計算した結果は、以下の通り。LassoとElasticNetが、52.2%だが、全てupと予想しても51%であるため、予測できているとは言い難い。株価予想に、教師あり学習(回帰)は使えなかった。

教師あ学習(回帰)による予測結果

4.3 教師あり学習(分類)

4.3.1 ソースコード

教師あり学習(分類)の分析アルゴリズムとして、ロジスティック回帰(LogisticRegression)、線形SVM(LinearSVC)、非線形SVM(SVC)、決定木(DecisionTreeClassifier)、ランダムフォレスト(RandomForestClassifier)、k-NN(KNeighborsClassifier)を使った。
なお、パラメータ調整するときは、120日分を学習した後、30日分をまとめて予測して、一番良いパラメータを選んだ。下のソースコードでは、パラメータ調整の部分は、コメントアウトしている。もし、このコメントを外して予測させると、1日単位で、パラメータの良し悪しを判定する(予測データの結果と比較する)ので、ほとんどその日の結果と同じ値となる。(最初、97%という正解率が出て、何を間違ったのか、悩みました。)

##### 教師あり学習(分類)#####
import pandas as pd
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import RandomizedSearchCV
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import f1_score

# データの読み込み
all_data = pd.read_csv("/content/drive/MyDrive/Colab Notebooks/data/101_daily_220620-2120_all.csv", encoding="shift_jis")

### 最初の半年分(123個)のデータを学習し、翌日のデータを予測する。###
# 予測するのは、2021年1月4日~2021年12月30日までの245日分
# 変数の定義・初期化
pred_columns = ["LogisticReg", "Log_C", "Log_Multi", "LinearSVC", 
                "SVC", "SVC_Grid", "SVC_Random", 
                "DecisionTree", "DT_max_depth", "DT_random_state", 
                "RandomForest", "RF_n_estimators", "RF_max_depth", "RF_random_state", "KNeighbors"] 
pred_days = 245       # 予測する日数を定義

# 予測結果を代入するDataFrameを作成
pred_data = pd.DataFrame( index=[], columns=pred_columns)   # 予測を代入するDataFrameの枠を作成
record = pd.Series([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], index=pred_data.columns)  # pred_dataを初期化するデータ作成
for _ in range(pred_days):                                  # 予測する日数分
    pred_data = pred_data.append(record, ignore_index=True) # pred_dataのデータ追加・データ初期化

for day in range(pred_days):
  # 変数の定義・初期化
  train_index = day                                         # 学習データの最初のindex
  train_num   = 123                                         # 学習データの数
  pred_index  = train_index + train_num                     # 予測するデータのindex

  # 学習データ格納
  train_data = all_data[train_index:pred_index]             # 123日分のデータ
  train_data = train_data.drop(["日付", "終値"], axis=1)    # 共通で必要のないデータを削除 
  stock_train_X = train_data.drop("updown", axis=1)         # 目的変数(予測するデータ)を削除
  stock_train_y = train_data["updown"]                      # 目的変数(予測するデータ)のみ代入
  
  # 予測するデータ格納
  test_data = all_data[pred_index:pred_index+1]             # 学習データの次の1日(1日だけどスライス指定)
  test_data = test_data.drop(['日付', '終値'], axis=1)      # 共通で必要のないデータを削除
  stock_test_X = test_data.drop("updown", axis=1)           # 目的変数(予測するデータ)を削除
  stock_test_y = test_data["updown"]                        # 目的変数(予測するデータ)のみ代入

  ### ロジスティック回帰
  model = LogisticRegression()                              # モデルはLogisticRegression
  model.fit(stock_train_X, stock_train_y)                   # 学習
  stock_pred = model.predict(stock_test_X)                  # 予測
  pred_data.at[day, "LogisticReg"] = int(stock_pred)        # 結果用DataFrameへ代入
  print("day={}, 学習モデル:{}, パラメーター:{}, stock_pred:{}".format(day, model.__class__.__name__, None, stock_pred))

  ### ロジスティック回帰のC設定
  """
  max_score = 0
  max_param = []
  max_stock_pred = 0
  # Cの値の範囲を設定(今回は1e-5,1e-4,1e-3,0.01,0.1,1,10,100,1000,10000)
  C_list = [10 ** i for i in range(-5, 5)]
  j = 1
  for C in C_list:
      model = LogisticRegression(C=C, random_state=42)
      model.fit(stock_train_X, stock_train_y)
      stock_pred = model.predict(stock_test_X)
      score = f1_score(stock_test_y, stock_pred, average="micro")
      j += 1
      if score > max_score:
        max_score = score
        max_param = C
        max_stock_pred = stock_pred
  """
  # Cは、1e-5が一番よかったので、この値で予測する
  model = LogisticRegression(C=10**-5, random_state=42)
  model.fit(stock_train_X, stock_train_y)
  stock_pred = model.predict(stock_test_X)
  pred_data.at[day, "Log_C"] = int(stock_pred)              # 結果用DataFrameへ代入
  print("day={}, 学習モデル:{}, パラメーターC:{}, stock_pred:{}".format(day, model.__class__.__name__, 10**-5, stock_pred))

  ### ロジスティック回帰のmulti_class設定
  model = LogisticRegression(C=0.1, multi_class = "ovr", random_state=42)
  model.fit(stock_train_X, stock_train_y)
  stock_pred = model.predict(stock_test_X)
  pred_data.at[day, "Log_Multi"] = int(stock_pred)
  print("day={}, 学習モデル:{}, パラメーター:{}, stock_pred:{}".format(day, model.__class__.__name__, None, stock_pred))

  ### 線形SVC
  model = LinearSVC()
  model.fit(stock_train_X, stock_train_y)
  stock_pred = model.predict(stock_test_X)
  pred_data.at[day, "LinearSVC"] = int(stock_pred)
  print("day={}, 学習モデル:{}, パラメーター:{}, stock_pred:{}".format(day, model.__class__.__name__, None, stock_pred))

  ### 非線形SVC
  model = SVC()
  model.fit(stock_train_X, stock_train_y)
  stock_pred = model.predict(stock_test_X)
  pred_data.at[day, "SVC"] = int(stock_pred)
  print("day={}, 学習モデル:{}, パラメーター:{}, stock_pred:{}".format(day, model.__class__.__name__, None, stock_pred))

  ### 決定木
  model = DecisionTreeClassifier()
  model.fit(stock_train_X, stock_train_y)
  stock_pred = model.predict(stock_test_X)
  pred_data.at[day, "DecisionTree"] = int(stock_pred)
  print("day={}, 学習モデル:{}, パラメーター:{}, stock_pred:{}".format(day, model.__class__.__name__, None, stock_pred))

  ### 決定木のmax_depth設定
  """
  max_score = 0
  max_depth = 0
  max_stock_pred = 0
  # max_depthの値の範囲(1から10)
  depth_list = [i for i in range(1, 11)]
  # max_depthを変えながらモデルを学習
  for depth in depth_list:
      model = DecisionTreeClassifier(max_depth=depth)
      model.fit(stock_train_X, stock_train_y)
      stock_pred = model.predict(stock_test_X)
      score = f1_score(stock_test_y, stock_pred, average="micro")
      if max_score < score:
        max_score = score
        max_depth = depth
        max_stock_pred = stock_pred
  """
  # max_depth=3が一番良かったので固定
  model = DecisionTreeClassifier(max_depth=3)
  model.fit(stock_train_X, stock_train_y)
  stock_pred = model.predict(stock_test_X)
  pred_data.at[day, "DT_max_depth"] = int(stock_pred)
  print("day={}, 学習モデル:{}, パラメーターmax_depth:{}, stock_pred:{}".format(day, model.__class__.__name__, 3, stock_pred))

  ### 決定木のrandom_state設定
  """
  max_score         = 0
  max_random_state  = 0
  max_stock_pred    = 0
  # random_stateの値の範囲(1から10)
  random_state_list = [i for i in range(1, 11)]
  # random_stateを変えながらモデルを学習
  for random_state in random_state_list:
      model = DecisionTreeClassifier(random_state=random_state)
      model.fit(stock_train_X, stock_train_y)
      stock_pred = model.predict(stock_test_X)
      score = f1_score(stock_test_y, stock_pred, average="micro")
      if max_score < score:
        max_score         = score
        max_random_state  = random_state
        max_stock_pred    = stock_pred
  """
  # random_state=9が一番良かったので固定
  model = DecisionTreeClassifier(random_state=9)
  model.fit(stock_train_X, stock_train_y)
  stock_pred = model.predict(stock_test_X)
  pred_data.at[day, "DT_random_state"] = int(stock_pred)
  print("day={}, 学習モデル:{}, パラメーターrandom_state:{}, stock_pred:{}".format(day, model.__class__.__name__, 9, stock_pred))

  ### ランダムフォレスト
  model = RandomForestClassifier()
  model.fit(stock_train_X, stock_train_y)
  stock_pred = model.predict(stock_test_X)
  pred_data.at[day, "RandomForest"] = int(stock_pred)
  print("day={}, 学習モデル:{}, パラメーター:{}, stock_pred:{}".format(day, model.__class__.__name__, None, stock_pred))

  ### ランダムフォレストのn_estimators設定
  """
  max_score       = 0
  max_estimattors = 0
  max_stock_pred  = 0
  # n_estimatorsの値の範囲(1から20)
  n_estimators_list = [i for i in range(1, 21)]
  # n_estimatorsを変えながらモデルを学習・予測
  for n_estimators in n_estimators_list:
      model = RandomForestClassifier(n_estimators = n_estimators)
      model.fit(stock_train_X, stock_train_y)
      stock_pred = model.predict(stock_test_X)
      score = f1_score(stock_test_y, stock_pred, average="micro")
      if max_score < score:
        max_score       = score
        max_estimattors = n_estimators
        max_stock_pred  = stock_pred
  """
  # n_estimators = 3が一番良かったので、この値で固定
  model = RandomForestClassifier(n_estimators = 3)
  model.fit(stock_train_X, stock_train_y)
  stock_pred = model.predict(stock_test_X)
  pred_data.at[day, "RF_n_estimators"] = int(stock_pred)
  print("day={}, 学習モデル:{}, パラメーターn_estimators:{}, stock_pred:{}".format(day, model.__class__.__name__, 3, stock_pred))

  ### ランダムフォレストのmax_depth設定
  """
  max_score       = 0
  max_depth       = 0
  max_stock_pred  = 0

  # max_depthの値の範囲(1から10)
  depth_list = [i for i in range(1, 11)]

  # max_depthを変えながらモデルを学習・予測
  for depth in depth_list:
      model = RandomForestClassifier(max_depth = depth)
      model.fit(stock_train_X, stock_train_y)
      stock_pred = model.predict(stock_test_X)
      score = f1_score(stock_test_y, stock_pred, average="micro")
      if max_score < score:
        max_score       = score
        max_depth       = depth
        max_stock_pred  = stock_pred
  """
  # max_depth=1が一番良かったので固定
  model = RandomForestClassifier(max_depth = 1)
  model.fit(stock_train_X, stock_train_y)
  stock_pred = model.predict(stock_test_X)
  pred_data.at[day, "RF_max_depth"] = int(stock_pred)
  print("day={}, 学習モデル:{}, パラメーターmax_depth:{}, stock_pred:{}".format(day, model.__class__.__name__, 1, stock_pred))

  ### ランダムフォレストのrandom_state設定
  """
  max_score         = 0
  max_random_state  = 0
  max_stock_pred    = 0
  # random_stateの値の範囲(1から101、10毎)
  random_state_list = [i for i in range(1, 101, 10)]
  # random_stateを変えながらモデルを学習・予測
  for random_state in random_state_list:
      model = RandomForestClassifier(random_state = random_state)
      model.fit(stock_train_X, stock_train_y)
      stock_pred = model.predict(stock_test_X)
      score = f1_score(stock_test_y, stock_pred, average="micro")
      if max_score < score:
        max_score         = score
        max_random_state  = random_state
        max_stock_pred    = stock_pred
  """
  # random_state=91が一番良かったので固定
  model = RandomForestClassifier(random_state = 91)
  model.fit(stock_train_X, stock_train_y)
  stock_pred = model.predict(stock_test_X)
  pred_data.at[day, "RF_random_state"] = int(stock_pred)
  print("day={}, 学習モデル:{}, パラメーターrandom_state:{}, stock_pred:{}".format(day, model.__class__.__name__, 91, stock_pred))

  ### k近傍法
  """
  max_score       = 0
  max_k           = 0
  max_stock_pred  = 0
  # n_neighborsの値の範囲(1から10)
  k_list = [i for i in range(1, 11)]
  # n_neighborsを変えながらモデルを学習・予測
  for k in k_list:
      model = KNeighborsClassifier(n_neighbors = k)
      model.fit(stock_train_X, stock_train_y)
      stock_pred = model.predict(stock_test_X)
      score = f1_score(stock_test_y, stock_pred, average="micro")
      if max_score < score:
        max_score       = score
        max_k           = k
        max_stock_pred  = stock_pred
  """
  # n_neighbors=3が一番良かったので、固定
  model = KNeighborsClassifier(n_neighbors = 3)
  model.fit(stock_train_X, stock_train_y)
  stock_pred = model.predict(stock_test_X)
  pred_data.at[day, "KNeighbors"] = int(stock_pred)
  print("day={}, 学習モデル:{}, パラメーターk:{}, stock_pred:{}".format(day, model.__class__.__name__, 3, stock_pred))

print("pred_data")
print(pred_data)

# CSVファイルへ出力
pred_data.to_csv("/content/drive/MyDrive/Colab Notebooks/data/101_daily_pred_CL.csv")

4.3.2 結果

結果のCSVファイル(最初の11日分)は、以下の通り。

結果のCSVファイル(101_daily_pred_CL.csv)

そして、このupdownの正解率を計算した結果は、以下の通り。DecisionTreeが、一番良くて53.5%。全てupと予想しても51%であるため、予測できているとは言い難いが、DecisionTreeの予測に従って、毎日株取引を行った場合、7568円のプラスであり、株価(最大株価30,670円)の24.7%となる。
正解率は高くないものの、株価予想に、教師あり学習(分類)が使える可能性はある。

教師あり学習(分類)による予測結果(1)
教師あり学習(分類)による予測結果(2)
教師あり学習(分類)による予測結果(3)

4.3 ディープラーニング(回帰)

4.3.1 ソースコード

ディープラーニングを使った株価予想のソースコードは、以下の通り。
なお、パラメータ調整するときは、120日分を学習した後、30日分をまとめて予測して、一番良いパラメータの組み合わせを選んだ。調整したパラメータは、Dense()の数、Dense()のパラメータ、Activationのパラメータ(sigmoid, relu)Dropoutの有無、compile()のパラメータ(optimizer)、fit()のパラメータ(epoch, batch_size)。最初は、まったく予測ができていなかったが、パラメータを調整することで、予測ができるようになった。また、30日分をまとめて予測したパラメータでは、1日毎の予測では、良い結果が出なかったため、パラメータ調整を繰り返した。

##### ディープラーニング(回帰)#####
import pandas as pd
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Activation, Dense, Dropout, Input, BatchNormalization
from tensorflow.keras import optimizers
from sklearn.metrics import mean_squared_error
from sklearn.datasets import load_boston
import matplotlib.pyplot as plt

# データの読み込み
# データには、日付、updown、終値、前日~6か月前までの終値が格納されている
# updownは、その日の終値が前日より上がったら1,下がったら0の値
all_data = pd.read_csv("/content/drive/MyDrive/Colab Notebooks/data/101_daily_220620-2120_all_org.csv", encoding="shift_jis")

### 最初の半年分(123個)のデータを学習し、翌日のデータを予測する。###
# 予測するのは、2021年1月4日~2021年12月30日までの245日分
# 変数の定義・初期化
pred_columns = ["dnnReg", "error", "updown"] 
pred_days = 245                                             # 予測する日数を定義

# 予測結果を代入するDataFrameを作成
pred_data = pd.DataFrame( index=[], columns=pred_columns)   # 予測を代入するDataFrameの枠を作成
record = pd.Series([0, 0, 0], index=pred_data.columns)      # pred_dataを初期化するデータ作成
for _ in range(pred_days):                                  # 予測する日数分
    pred_data = pred_data.append(record, ignore_index=True) # pred_dataのデータ追加・データ初期化

# 出力結果の固定
tf.random.set_seed(0)

# モデル定義
model = Sequential()

# ここにコードを記入してください(model.add)
model.add(Dense(128, input_dim=120))
model.add(Activation("relu"))
model.add(Dense(512))
model.add(Activation("relu"))
model.add(Dense(512))
model.add(Activation("relu"))
model.add(Dropout(rate=0.6))
model.add(Dense(1))

# 損失関数にmse、最適化関数にadamを採用
model.compile(loss='mse', optimizer='adam')

for day in range(pred_days):
  # 変数の定義・初期化
  train_index = day                                         # 学習データの最初のindex
  train_num   = 123                                         # 学習データの数
  pred_index  = train_index + train_num                     # 予測するデータのindex

  # 学習データ決定
  train_data = all_data[train_index:pred_index]             # 123日分のデータ
  train_data = train_data.drop(["日付", "updown"], axis=1)  # 共通で必要のないデータを削除 
  stock_train_X = train_data.drop("終値", axis=1)           # 説明変数(予測するデータ以外)を代入
  stock_train_y = train_data["終値"]                        # 目的変数(予測するデータのみ)を代入

  # 予測するデータ決定
  test_data = all_data[pred_index:pred_index+1]             # 学習データの次の1日(1日だけどスライス指定)
  test_data = test_data.drop(['日付', 'updown'], axis=1)    # 共通で必要のないデータを削除
  stock_test_X = test_data.drop("終値", axis=1)             # 説明変数(当日の終値以外)を代入
  stock_test_y = test_data["終値"]                          # 目的変数(当日の終値のみ)を代入

  # モデルを学習させます
  history = model.fit(stock_train_X, stock_train_y, epochs=60 , batch_size=64 , verbose=0, validation_data=(stock_test_X, stock_test_y) )

  # 予測値を出力します
  stock_pred = model.predict(stock_test_X)                  # 株価を予測
  pred_data.at[day, "dnnReg"] = int(stock_pred)                # 結果出力用DataFrameへ保存

  # 二乗誤差を出力します
  mse= mean_squared_error(stock_test_y, stock_pred)         # 誤差を計算
  pred_data.at[day, "error"] = int(mse**0.5)                # 結果出力用DataFrameへ保存

  # 上がったか下がったか(updown)を出力します
  if(int(stock_test_X["1day before"]) < int(stock_pred)):   # 前日より高い予測だったら
    pred_data.at[day, "updown"] = 1                         # 株価が上がる予想(1)を代入
    print("day={}, stock_pred={}, REG RMSE={:.2f}, updown={}".format(day, stock_pred, mse**0.5, 1))
  else:
    pred_data.at[day, "updown"] = 0                         # 株価が下がる予想(0)を代入
    print("day={}, stock_pred={}, REG RMSE={:.2f}, updown={}".format(day, stock_pred, mse**0.5, 0))

# CSVファイルへ出力
pred_data.to_csv("/content/drive/MyDrive/Colab Notebooks/data/101_daily_pred_dnnReg.csv")

4.3.2 結果

結果のCSVファイル(最初の11日分)は、以下の通り。

結果のCSVファイル(101_daily_pred_dnnReg.csv)

このupdownの正解率を計算した結果は、55.1%。この予測に従って、毎日株取引を行った場合、4776円のプラスであり、株価(最大株価30,670円)の15.6%となる。
正解率は高くないものの、株価予想に、ディープラーニング(回帰)が使える可能性はある。

ディープラーニング(回帰)による予測結果

4.4 ディープラーニング(分類)

4.4.1 ソースコード

ディープラーニングを使った株価のupdown予測(分類)のソースコードは、以下の通り。
パラメータ調整方法は、ディープラーニング(回帰)と同じように行った。
最初は、教師あり学習(分類)と同じように、「updown」というカラムに1 or 0の値を入れて、予測させるが、上手くいかないため、「up」と「down」の二つのカラムに分けたデータを使用した。それでも、全てupと予測することが多かったが、パラメータを調整することで、それなりの予測結果が得られた。

##### ディープラーニング(分類)#####
import pandas as pd
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Activation, Dense, Dropout, Input, BatchNormalization
from tensorflow.keras import optimizers
import matplotlib.pyplot as plt

# データの読み込み
# データには、日付、updown、終値、前日~6か月前までの終値が格納されている
# updownは、その日の終値が前日より上がったら1,下がったら0の値
all_data = pd.read_csv("/content/drive/MyDrive/Colab Notebooks/data/101_daily_220620-2120_all-01.csv", encoding="shift_jis")

### 最初の半年分(123個)のデータを学習し、翌日のデータを予測する。###
# 予測するのは、2021年1月4日~2021年12月30日までの245日分
# 変数の定義・初期化
pred_columns = ["dnn-up", "dnn-down", "score", "updown"] 
pred_days = 245       # 予測する日数を定義

# 予測結果を代入するDataFrameを作成
pred_data = pd.DataFrame( index=[], columns=pred_columns)   # 予測を代入するDataFrameの枠を作成
record = pd.Series([0, 0, 0, 0], index=pred_data.columns)  # pred_dataを初期化するデータ作成
for _ in range(pred_days):                                  # 予測する日数分
    pred_data = pred_data.append(record, ignore_index=True) # pred_dataのデータ追加・データ初期化

# 出力結果の固定
tf.random.set_seed(0)

# モデル定義
model = Sequential()

# ここにコードを記入してください(model.add)
model.add(Dense(512, input_dim=120))
model.add(Activation("sigmoid"))
model.add(Dense(512))
model.add(Activation("relu"))
model.add(Dense(512))
model.add(Activation("relu"))
model.add(Dropout(rate=0.5))
model.add(Dense(2))
model.add(Activation("softmax"))

# 損失関数にmse、最適化関数にadamを採用
sgd = optimizers.SGD(lr=0.1)
model.compile(loss='categorical_crossentropy', optimizer='sgd')

for day in range(pred_days):
  # 変数の定義・初期化
  train_index = day                                         # 学習データの最初のindex
  train_num   = 123                                         # 学習データの数
  pred_index  = train_index + train_num                     # 予測するデータのindex

  # 学習データ決定
  train_data = all_data[train_index:pred_index]             # 123日分のデータ
  train_data = train_data.drop(["日付", "終値"], axis=1)    # 共通で必要のないデータを削除 
  stock_train_X = train_data.drop(["up", "down"], axis=1)     # 説明変数(予測するデータ以外)を代入
  stock_train_y = train_data[["up", "down"]]                # 目的変数(予測するデータのみ)を代入

  # 予測するデータ決定
  test_data = all_data[pred_index:pred_index+1]             # 学習データの次の1日(1日だけどスライス指定)
  test_data = test_data.drop(['日付', '終値'], axis=1)      # 共通で必要のないデータを削除
  stock_test_X = test_data.drop(["up", "down"], axis=1)       # 説明変数(当日の終値以外)を代入
  stock_test_y = test_data[["up", "down"]]                  # 目的変数(当日の終値のみ)を代入

  # モデルを学習させます
  history = model.fit(stock_train_X, stock_train_y, epochs=100, batch_size=32 , verbose=0, validation_data=(stock_test_X, stock_test_y) )

  # 予測値を出力します
  stock_pred = model.predict(stock_test_X)
  pred_data.loc[day, ["dnn-up", "dnn-down"]] = stock_pred

  # 予測値を評価します
  score = model.evaluate(stock_test_X, stock_test_y, verbose=0)
  pred_data.at[day, "score"] = score

  # 上がったか下がったか(updown)を出力します
  if(stock_pred[0][0] > stock_pred[0][1]):   # 前日より高い予測だったら
    pred_data.at[day, "updown"] = 1                   # 株価が上がる予想(1)を代入
    print("index={}, stock_pred={}, updown={}, score={}".format(day, stock_pred, 1, score))
  else:
    pred_data.at[day, "updown"] = 0                   # 株価が下がる予想(0)を代入
    print("index={}, stock_pred={}, updown={}, score={}".format(day, stock_pred, 0, score))

pred_data.to_csv("/content/drive/MyDrive/Colab Notebooks/data/101_daily_pred_dnnCL.csv")

4.4.2 結果

結果のCSVファイル(最初の11日分)は、以下の通り。

結果のCSVファイル(101_daily_pred_dnnCL.csv)

このupdownの正解率を計算した結果は、53.1%。この予測に従って、毎日株取引を行った場合、3378円のプラスであり、株価(最大株価30,670円)の11.0%となる。
正解率は高くないものの、株価予想に、ディープラーニング(分類)が使える可能性はある。

5. あとがき

最後まで、見ていただきありがとうございました。
20年前までは、C言語やC++のプログラミングを行っていましたが、その後は開発プロジェクトマネージャや企画の仕事で、プログラミングを行う機会がありませんでした。20年振りのコーディングで、C言語の書き方に慣れているため、Pythonだったら、もっと簡単に記述できるところ、恥ずかしいコードがたくさんありそうな気がします。データ分析初心者が、初めて自分で分析したソースコードなので、間違いや改善点などが多くあると思いますが、お許しください。
ただ、データ分析講座を受けただけでは、身につかなかったことが、今回苦労したことで、多く理解できてきました。これを機会に、データ分析の仕事ができるようになれば嬉しいです。

#python #データ分析 #ソースコード #LinearRegression #Lasso #Ridge #ElasticNet #LogisticRegression #LinearSVC #SVC #DecisionTreeClassifier #RandomForestClassifier #KNeighborsClassifier #tensorflow #keras #株価

この記事が気に入ったらサポートをしてみませんか?