Googleアカウントさえ持っていれば環境構築の手間なく手軽に使える「Google Colaboratory」を用いて、複数の機械学習手法による株値動きの予測を簡単に行ってみました。

株価のデータセットは無料で手に入れられるものが少なく、各証券会社などから有料で利用できるものが多いですが、今回は「yahoo_finance_api2」から株価を取得して手軽に無料で行っていきます。


前提知識:
  • Python調べながら分かる
結論から申し上げると、今回の実験では欲しい精度が得られませんでした( 一一)



機械学習による株価予測概要

  • データセット
    • yahoo_finance_ap2から取得
    • 株価の情報と出来高を用いる
  • 超短期投資(デイトレ)を仮定
    • 実際に利益を見込めなければ作る面白みがない、そこで、前日の株情報から翌営業日の終値を予想して、翌日の始値によって取引の判断を行うことを仮定した予測を行う
今回は、以下2種類の機械学習の手法を試してみます。


回帰(値の予測)


重回帰分析(線形回帰)を用いて株価の予測を行います。端的に言うと、取得した情報から得られる複数の関連情報から、目的の株価を予測する方法です。
今回は、前日の以下複数の情報から翌営業日の「終値」を予測します。
  • 始値
  • 終値
  • 出来高
  • 高値と安値の差額
実用上のモデルの評価を行う方法としては、翌終値を予測し、「予測翌終値」が「翌営業日の始値」から見たときの相対位置として適切な位置に予測されている個数をカウントし、精度を判定します。


分類(上がる or 下がる)


上記に加えて、サポートベクターマシン(SVM)による分類予測も行います。
株価は最悪上がるか下がるかさえ予測できれば、厳密な値を予測できなくても良いので、翌営業日の始値と比較して、終値が上がるか、下がるかを予測します。

ただこの方法では、ほとんど値動きのない日への効果が薄いので、今回はあくまで機械学習の実装テストくらいの意味で行います。

主に用いるライブラリ:
  • pandas(データ加工)
  • seaborn(グラフ表示)
  • scikit-learn(機械学習)
最終的に取得したデータの一部を切り出した「テストデータ」から、実用精度を評価します。



yahoo_finance_api2からデータ取得


まず、日本株の株価データ取得のためのライブラリをインストール
!pip install yahoo_finance_api2
今回は、日本マクドナルドホールディングス(2702)を対象に設定。今回は20年分のデータを1日刻みで取得した。

その他、日本株を証券コードを変えることで好きに設定可能。データはならべく多く所得したいところだが、上場した年より前のデータを取得するとおかしなことになる。
import sys
from yahoo_finance_api2 import share
from yahoo_finance_api2.exceptions import YahooFinanceError

#「mykey」に日本株の銘柄コードを入力することで予測する株を変更
mykey = 2702
company_name = "日本マクドナルドホールディングス" # 特に名前を入れる意味はない

#20年間1日刻みデータの所得
company_code = str(mykey) + '.T'
my_share = share.Share(company_code)
symbol_data = None
try:
  symbol_data = my_share.get_historical(
      share.PERIOD_TYPE_YEAR, 20,
      share.FREQUENCY_TYPE_DAY, 1
  )
except YahooFinanceError as e:
  print(e.message)
  sys.exit(1)
取得データをデータフレームに格納して、さらに時間をミリ秒から日本標準時に変換
import pandas as pd

df = pd.DataFrame({'日付': symbol_data['timestamp'], '始値' : symbol_data['open'],
    '高値' : symbol_data['high'], '安値' : symbol_data['low'],
    '終値' : symbol_data['close'], '出来高' : symbol_data['volume']})
#時間表示変換(ミリ秒→日本標準時)
df['日付'] = pd.to_datetime(df['日付'], unit="ms")
df['日付'] = pd.DatetimeIndex(df['日付']).tz_localize("UTC").tz_convert("Asia/Tokyo")




データの編集・考察


上記概要でも述べた通り4つの項目(説明変数:始値,終値,出来高,値動き幅)と正解ラベル(目的変数:翌終値)をデータフレームに整えていく。
# 高値と安値の差額列作成
df['値動き幅'] = df['高値'] - df['安値']
# 正解ラベルの追加(翌高値)
df2 = df['終値']
df2 = df2.drop(df2.index[0])
df2 = df2.reset_index()
df2 = df2.drop('index', axis=1)
df2 = df2.set_axis(['翌終値'], axis=1)
# データ結合
df = pd.concat((df, df2), axis=1)
# 学習に不要な項目削除
df = df.drop(['高値', '安値'], axis=1)
df = df.drop(df.index[len(df.index)-1]) # 最終データには正解データがない
ちなみに、現在のデータフレームは以下のようになる。print()等で確認可能。
日付始値終値出来高値動き幅翌終値
02002-11-22 09:00:00+09:001740175046300201800
12002-11-25 09:00:00+09:001750180087100701810
22002-11-26 09:00:00+09:001790181052900301860
32002-11-27 09:00:00+09:001810186058500601980
42002-11-28 09:00:00+09:0018701980650001502060
.....................


欠損データの補完


欠損データの確認
df.isnull().sum()
欠損値への対処法としては、
  • 欠損値を含む行/列を取り除く
  • 欠損値を何らかの値で補完する
  • 欠損値を無視する
などがあるらしい。

今回はデータを補完。欠損データが存在しない場合も、とりあえずやっておけば間違いない。様々手法があるが、ここでは最頻値での補完を用いる。
from sklearn.impute import SimpleImputer
import numpy as np

#最頻値での補完
imp_mean = SimpleImputer(missing_values=np.nan, strategy='most_frequent', fill_value=None)
imp_mean.fit(df)
#補完
result_mean = imp_mean.transform(df) 
df = pd.DataFrame(result_mean, columns=df.columns)


データの可視化

import seaborn as sns
sns.pairplot(df, hue="翌終値")
h1117_2_com

データを可視化することで、傾向を見たりするらしいが今回はよくわからないのでスルー




訓練/テストデータの分割


データを機械学習の「モデルの訓練用」とモデルの性能を計る「モデルのテスト用」に分ける。
feature = df.loc[:, ["始値", "終値", "出来高", "値動き幅"]] # 説明変数
target = df.loc[:, ["翌終値"]] # 目的変数
#テスト用データを全体の10%とする
x_train, x_test, t_train, t_test = train_test_split(feature, target, test_size=0.1, random_state=1)
ここで各変数の対応は以下の通り。
変数名説明
x_train訓練用の説明変数
t_train訓練用の目的変数
x_test性能テスト用の説明変数(入力)
t_test性能テスト用の目的変数(出力)



手法1:回帰分析


機械学習自体のモデルの訓練自体は、機械学習用ライブラリである「scikit-learn」を使うことで以下のように簡単に終わる。
#重回帰分析(線形回帰)
from sklearn.linear_model import LinearRegression
LinearRegression = LinearRegression()
#モデルの訓練
LinearRegression.fit(x_train, t_train)
print(LinearRegression.coef_) #求まった重み w 
print(LinearRegression.intercept_) #求まったバイアス b 
精度の検証を行うために、求められた目的変数(翌終値)がどのくらい実際の目的変数と一致しているかを示す指標である決定係数を確認してみる。

最大値が1で、値が大きいほど与えたデータに対してフィットしているといえるらしい。
print("学習時性能:"+ str(LinearRegression.score(x_train, t_train)))
print("汎化性能  :"+ str(LinearRegression.score(x_test, t_test)))
汎化性能が大きいほど、どんな入力に対しても適切に値を出力できるモデルになっている。


実用的な精度評価


具体的な株価を1点予測しても、どの程度の誤差が許容可能で利益に繋がるのか考えることに意味がある。

そこで、翌日の始値(翌始値)から相対的に見て、予測した値(翌終値)が上昇するか、下落するかをどの程度予測できているかで評価を行う。

つまり「翌日の始値時点で終値が安くなることが分かっているなら売る、高くなるようなら買う」という判断材料として、どの程度優れているのかという指標で評価してみる。

まず、精度評価に必要なデータフレームを作る。以下はかなり冗長なコードなので最終的に下に示す表のようになっていれば、書き直すことをおすすめする。
df2 = pd.DataFrame(LinearRegression.predict(x_test), columns=t_test.columns) # データセット作成
df2 = df2.set_axis(['翌終値予測値'], axis=1)

df3 = df.drop(['翌終値', '日付', '始値', '終値', '出来高', '値動き幅'] ,axis=1)
df3 = df3.reset_index()

df4 = t_test.join(df3, how='inner')
df4 = df4.reset_index()
df4 = df4.drop(['index', 'level_0']  ,axis=1)

df3 = pd.concat([df4, df2], axis=1)

# 翌日の始値を追加
df4 = t_test
df4 = df4.reset_index()
# 翌日の始値にするためindexを振りなおす
for i in df4.columns:
  df4[i] = df4[i] + 1
df4 = df4.set_index('index')
df4 = df4.drop(['翌終値'] ,axis=1)
# 翌日の始値を取得
df2 = df['始値']
df2 = df4.join(df2, how='inner')
df2 = df2.reset_index()
df2 = df2.drop('index', axis=1)
df2 = df2.set_axis(['翌始値'], axis=1)

df2 = pd.concat([df2, df3], axis=1)
# 上昇するとき「1」、下落するとき「-1」、変化なし「0」
df2.loc[df2['翌始値'] == df2['翌終値'], '本結果'] = 0
df2.loc[df2['翌始値'] > df2['翌終値'], '本結果'] = -1
df2.loc[df2['翌始値'] < df2['翌終値'], '本結果'] = 1

df2.loc[df2['翌始値'] == df2['翌終値予測値'], '予測結果'] = 0
df2.loc[df2['翌始値'] > df2['翌終値予測値'], '予測結果'] = -1
df2.loc[df2['翌始値'] < df2['翌終値予測値'], '予測結果'] = 1
▼最終的にこのような表ができてればOK
翌始値翌終値翌終値予測値本結果予測結果
0200920002010.5748-11
1468047854726.80662311
2501049904989.681862-1-1
3173817361738.256288-11
4610060106027.109943-1-1
..................

何パーセントの正解率となっているか精度を確認
df_bool = (df2['本結果'] == df2['予測結果']).sum()
result = (df_bool / (len(df2))) * 100
print('予測精度 : ' + str(result) + '%')



余談:各入力変数の前処理


余談だがモデルの訓練を行う前に前処理を行うことも可能。これによって精度が向上する場合もあるらしい。

以下は、各入力変数ごとに平均が0、分散が1になるように値をスケーリングを行う「標準化」です。
from sklearn.preprocessing import StandardScaler

StandardScaler = StandardScaler()
#データセットの各入力変数ごとの平均と分散の値を計算
StandardScaler.fit(x_train)
print(StandardScaler.mean_) #平均
print(StandardScaler.var_) #分散
#標準化
x_train_scaled = StandardScaler.transform(x_train)
x_test_scaled = StandardScaler.transform(x_test)



手法2:SVMで分類


分類に関してもほとんど手順は同じなので、簡潔にまとめていく。


データの編集


まず、分類の正解データを追加
df2 = df['始値']
df2 = df2.drop(df2.index[0])
df2 = df2.reset_index()
df2 = df2.drop('index', axis=1)
df2 = df2.set_axis(['翌始値'], axis=1)
# データ結合
df = pd.concat((df, df2), axis=1)
df = df.drop(df.index[len(df.index)-1])
# 正解データ追加
df.loc[df['翌始値'] == df['翌終値'], '分類'] = 0
df.loc[df['翌始値'] > df['翌終値'], '分類'] = -1
df.loc[df['翌始値'] < df['翌終値'], '分類'] = 1
▼出力
日付始値終値出来高値動き幅翌終値翌始値分類
02002-11-22 09:00:00+09:00174017504630020180017501
12002-11-25 09:00:00+09:00175018008710070181017901
22002-11-26 09:00:00+09:00179018105290030186018101
32002-11-27 09:00:00+09:00181018605850060198018701
42002-11-28 09:00:00+09:001870198065000150206019801
...........................


モデルの訓練


データセットを「訓練データ」と「テストデータ」に分割。
from sklearn.model_selection import train_test_split

feature = df.loc[:, ['始値', '終値', '出来高', '値動き幅',]]
target = df.loc[:, ['分類']]
#テスト用データを全体の10%とする
x_train, x_test, t_train, t_test = train_test_split(feature, target, test_size=0.1, random_state=1)
サポートベクターマシンによるモデルの訓練
from sklearn import svm

svm = svm.SVC(decision_function_shape='ovo')
#モデルの訓練
svm.fit(x_train, t_train)


結果の精度検証


精度検証のためのデータセットを作成
df2 = pd.DataFrame(svm.predict(x_test), columns=t_test.columns)
df2 = df2.set_axis(['予測'], axis=1)

df3 = df.drop(['翌終値', '日付', '始値', '終値', '出来高', '値動き幅', '翌始値', '分類'] ,axis=1)
df3 = df3.reset_index()

df4 = t_test.join(df3, how='inner')
df4 = df4.reset_index()
df4 = df4.drop(['index', 'level_0']  ,axis=1)

df2 = pd.concat([df4, df2], axis=1)
分類予測
0-11
111
2-11
3-11
4-11
........

精度の検証
df_bool = (df2["分類"] == df2["予測"]).sum()
result = (df_bool / (len(df2))) * 100
print("分類予測精度:" + str(result) + "%")



まとめ


今回はチュートリアル的に株価予測を行ってみました。

私が行ったときはどちらも精度は50%程度で、人間が勘で当てる場合と変わりありませんでした。

説明変数や目的変数、学習モデル、対象銘柄など工夫できる箇所はたくさんあるので、試してみると面白いかもしれません。



参考:


お疲れさまでした。
このエントリーをはてなブックマークに追加
コメントを閉じる

コメント

コメントフォーム
記事の評価
  • リセット
  • リセット