ストックドッグ

KatoTakahiro。金融系の会社で働くSEが株やPython、その他諸々について書いています。サービスも運営してます→http://fmbrain.work

Pythonで機械学習を使った株価予測のコードを書こう

目次

はじめに

プログラミングを始めたばかりの人、機械学習を使って株価を分析してみたい人、このような人たちのために記事にしました。
少しでも助けとなれば私はうれしいです。

全てを読めば、株価を予測するためのコードを理解することができるよう心がけました。
理解することが大事なので、コードはシンプルにしてあります。

ひとつ注意点があります。
この記事を読むことで、株価を予測するためのコードは理解できるようになりますが、株価の予測はできません。
株価の予測は非常に難しいことであり、たくさんの工夫が必要です。
ただ、この記事は最初の一歩になると思います。
一歩さえ踏み出してしまえば、あとは各自で工夫していけるはずです。

ページの最後に本記事で使用したコードを掲載しています。

準備するもの

scikit-learn
pandas
ubuntu(OSは自由)
ネット回線

記事の流れ

  1. データ収集
  2. データの前処理
  3. モデルの学習
  4. 構築したモデルによる予測

予測手法

機械学習のランダムフォレストです。

ランダムフォレストは、調整が必要なパラメータが少なく過学習もしにくいため、機械学習の中でも扱いやすい部類に入ります。

かと言って、判別精度が悪いわけではないので非常に優秀な手法です。

ランダムフォレストについて詳しく知りたい方は以下の記事を参考にして下さい。

doz13189.hatenablog.com

データ収集

本記事で予測するのは、野村総合研究所(4307)の株価です。
以下のサイトで直近250日分のデータ(始値・高値・安値・終値出来高・売買代金)がダウンロードできます。
CSVと書かれている部分をクリックするだけです。

[4307 東証1部] 野村総合研究所 日足 時系列データ CSVダウンロード


次に説明変数となるデータを集めてきます。
野村総合研究所の株価を予測するには、野村総合研究所の株価データだけでは足りません。

では、何を集めてきたらいいでしょう?

まずは日経平均株価かな、次はTOPIX?あとドル円データも欲しいな…
あとは原油価格もあればうれしい、といろいろとあると思います。

あげだしたらきりがないし、正直それぞれ集めてくるの面倒くさいですね。
というか、こういうデータは自分で考えながら集めてくるものはありませんね。
集めれるだけデータを集めて、そこから説明変数として効果的なものを選びだすほうが効率が良いです。
少なくとも私はそうしています。

ということで、ここに載っているデータを適当に集めましょう。
ここにETF一覧が掲載されています。

stocks.finance.yahoo.co.jp

あれ?ETF??
私が探しているのは日経平均株価とか為替データで…

いいんです、ETFで。

ETFについて、とても軽く説明します。知っている方は読み飛ばして下さい。
ETFというのは、上場投資信託というもので、その名の通り投資信託が上場しているんです。
ヨーロッパ・アメリカでは有名な金融商品であり、最近は日本でも注目度が上がっている商品です。
投資信託と比べて色々とメリットはあるのですが、話が逸れそうなので詳しくは他のサイトに譲ります。

なぜ、ETFは説明変数候補として集めるかというと、指標と連動させている商品だからです。
例えば、日経225連動型上場投資信託
これは日経平均株価と連動するように運用されています。
そのため、日経平均株価を説明変数として組み込むのと、 日経225連動型上場投資信託を組み込むのは同じこと、になるんです。

ETFには様々な指標に連動させた商品があります。
上海株式、金、銀、原油、とうもろこしなどなど。
これらの指標は説明変数になりえます。

ということで、先ほどのETF一覧を見ながら、野村総合研究所の株価データをダウンロードしたサイトから集めてきましょう。

さすがに全部集めるのはしんどいので、適当に選びましょう。
ちなみに私は、

コード ETF
1309 上海株式指数・上証50連動型上場投資信託
1313 サムスンKODEX200証券上場指数投資信託
1314 上場インデックスファンドS&P日本新興株100
1322 上場インデックスファンド中国A株(パンダ)CSI300
1326 SPDRゴールド・シェア
1343 NEXT FUNDS 東証REIT指数連動型上場投信
1543 パラジウム上場信託(現物国内保管型)
1548 上場インデックスファンド中国H株(ハンセン中国企業株)
1549 上場インデックスファンドNifty50先物(インド株式)
1551 JASDAQ-TOP20上場投信
1633 NEXT FUNDS 不動産(TOPIX-17)上場投信
1673 ETFS 銀上場投資信託
1678 NEXT FUNDS インド株式指数・Nifty 50連動型上場投信
1681 上場インデックスファンド海外新興国株式(MSCIエマージング
1682 NEXT FUNDS 日経・東商取白金指数連動型上場投信
1698 上場インデックスファンド日本高配当(東証配当フォーカス100)

これらをダウンロードしました。

プラスアルファ
手動でデータを集めてくるのもいいですが、効率良く集めたいならスクレイピングがいいです。
現時点でスクレイピングがわからないという方は、「python スクレイピング]でググってみましょう。
思いの外スクレイピングが簡単にできることがわかると思います。
今回は、できるだけ記事をシンプルにしたいので余計な技術は使いません。

データが集まったらいよいよ前処理です。
前処理がおそらく一番難しくて、大変な作業です。
これさえ乗り切ってしまえばあとは楽勝です、がんばりましょう!

前処理

前処理ですること

  1. 野村総合研究所のデータと全てのETFのデータを統合して、ひとつの.csvにまとめる

まずは野村総合研究所の株価データを読み込んでみましょう。

import pandas as pd
df = pd.read_csv("code_4307.csv", header=0)
df.columns=["Date", "Open", "High", "Low", "Close", "Volume", "Trading Value"]
df["index"] = [i for i in range(len(df))]
print(df.head(10))

f:id:doz13189:20170209033100p:plain

人によっては、UnicodeDecodeErrorが出るかもしれません。
この原因は、ダウンロードしたcsvデータのcolumnsが日本語だからです。
csvデータの日付、始値、高値、安値、終値出来高の部分が文字化けしていると思うので、少し面倒ですがcsvデータからこれらを適当な英数字に修正しましょう。


さて、次のステップでETFのファイルを読み込み、それらを野村総合研究所の株価データと統合します。

そのために、まずETFのリストを作ります。
etf_listにダウンロードしたETFファイルのコードを入れましょう。

そして、ETFファイルの名前を「etf_1309.csv]にします。
ファイルの名前構成を「"etf_" + ETFコード + ".csv"」にすることで、ループ分を回した時にETFコードの部分を変えるだけで読み込むファイルを変えることができるようになります。

etf_list = [

1309,#上海株式指数・上証50連動型上場投資信託
1313,#サムスンKODEX200証券上場指数投資信託
1314,#上場インデックスファンドS&P日本新興株100
1322,#上場インデックスファンド中国A株(パンダ)CSI300
1326,#SPDRゴールド・シェア
1343,#NEXT FUNDS 東証REIT指数連動型上場投信
1543,#純パラジウム上場信託(現物国内保管型)
1548,#上場インデックスファンド中国H株(ハンセン中国企業株)
1551,#JASDAQ-TOP20上場投信
1633,#NEXT FUNDS 不動産(TOPIX-17)上場投信
1673,#ETFS 銀上場投資信託
1678,#NEXT FUNDS インド株式指数・Nifty 50連動型上場投信
1681,#上場インデックスファンド海外新興国株式(MSCIエマージング)
1682,#NEXT FUNDS 日経・東商取白金指数連動型上場投信
1698,#上場インデックスファンド日本高配当(東証配当フォーカス100)

]

さきほどの名前構成を変更したので、ループ分でetf_listに入っている全てのETFファイルを読み込むことができます。

for etf in etf_list:

	df_etf = pd.read_csv("etf_" + str(etf) + ".csv", header=0)
	df_etf.columns=["Date", "Open", "High", "Low", "Close", "Volume", "Trading Value"]


読み込むことができたら、次はETFファイルと野村総合研究所のデータを統合します。

統合する際に少し工夫が必要です。
ETFファイルは中身を見て頂ければわかると思いますが、穴ボコです。
NaNデータが多く、野村総合研究所のデータとそのまま統合すると、データの長さが異なるためズレて統合されてしまいます。

そのため、以下のコードでは野村総合研究所の日付をループ分で順番に取り出しながら、ETFファイルの日付に検索をかけます。
そして、日付が一致した日のETFのCloseデータを取り出します。
もし、そのCloseデータがNaNであった場合は前日のデータを代わりに入れています。

for etf in etf_list:

	df_etf = pd.read_csv("etf_" + str(etf) + ".csv", header=0)#データ読み込み
	df_etf.columns=["Date", "Open", "High", "Low", "Close", "Volume", "Trading Value"]#columns名を変更

	dates = []
	closeis = []
	for d in df["Date"]:
		date = df_etf.loc[(df_etf.Date == d), "Date"]#野村総合研究所の日付をETFファイルから検索
		yesterday_date = date.values[0]
		dates.append(date.values[0])#日付をデータセットに追加

		close = df_etf.loc[(df_etf.Date == d), "Close"]#日付が一致した日のETFのCloseのデータを取り出す
		if str(close.values[0]) != str("nan"):#取り出したCloseがnanでないかを判断
			yesterday_close = close.values[0]
			closeis.append(close.values[0])	

		else:
			closeis.append(yesterday_close)
		
	df_etf2 = pd.DataFrame({"Date_" + str(etf) : dates, "Close_" + str(etf) : closeis})#新しくデータフレームを作成
	df = pd.concat([df, df_etf2], axis=1)#野村総合研究所のデータとETFデータを統合

ETFデータは、Closeしか必要はありません。
しかし、プログラムでは日付データも一緒に追加しています。
これは、統合した後に目視で確認しやすいようにです。(出来上がったデータを見てもらえば、確認のしやすさがわかります)

よくデータ同士の統合で、データが一段ずれるなどのトラブルが起こります。
これはこういったトラブルを防ぐためのちょっとした工夫です。


さて、無事統合できました。
しかし、このETFデータにもうひと工夫する必要があります。

ETFのデータを階差系列に変更する必要があります。
このステップは非常に重要で、ETFの価格自体を説明変数として使っても、野村総合研究所の株価データは説明することはできません。
そのため、前日比などに変更する必要があります。

このコードをfor文の中に入れてやる必要があります。

df["diff_" + str(etf)] = (df["Close_" + str(etf)] / df["Close_" + str(etf)].shift(-1)) - 1

これで前処理は終了したので、新しくできたデータフレームをcode_4307_plus.csvとして保存します。

df.to_csv("code_4307_plus.csv")

モデルの学習

ここで新しくファイルを作成しましょう。

いよいよ機械学習で株価を予測します。

構築するモデルは、過去249日を学習して、250日目を予測するというものです。

import pandas as pd
from sklearn.ensemble import RandomForestClassifier


ETFデータを統合した野村総合研究所の株価データを表示しましょう。

df = pd.read_csv("code_4307_plus.csv")
df = df.sort_values(by=["index"], ascending=False)
print(df.tail(20))

ETFデータが全て統合されたデータが表示されたはずです。

このデータをテストデータとトレーニングデータにわけます。

df_train = df.iloc[1:len(df)-1]
#はじまりを[1:]としているのは、階差系列をとっているため[1]がNaNデータだからです。
#学習データにNaNデータがあると、エラーがでます。

df_test = df.iloc[len(df)-1:len(df)]
#テストデータには、一番最新のデータを入れます。

説明変数として使用するものをリストにしておきます。

xlist = [

"diff_1309",#上海株式指数・上証50連動型上場投資信託
"diff_1313",#サムスンKODEX200証券上場指数投資信託
"diff_1314",#上場インデックスファンドS&P日本新興株100
"diff_1322",#上場インデックスファンド中国A株(パンダ)CSI300
"diff_1326",#SPDRゴールド・シェア
"diff_1343",#NEXT FUNDS 東証REIT指数連動型上場投信
"diff_1543",#純パラジウム上場信託(現物国内保管型)
"diff_1548",#上場インデックスファンド中国H株(ハンセン中国企業株)
"diff_1551",#JASDAQ-TOP20上場投信
"diff_1633",#NEXT FUNDS 不動産(TOPIX-17)上場投信
"diff_1673",#ETFS 銀上場投資信託
"diff_1678",#NEXT FUNDS インド株式指数・Nifty 50連動型上場投信
"diff_1681",#上場インデックスファンド海外新興国株式(MSCIエマージング)
"diff_1682",#NEXT FUNDS 日経・東商取白金指数連動型上場投信
"diff_1698",#上場インデックスファンド日本高配当(東証配当フォーカス100)

]


ここで学習用のデータを作ります。
学習データの作り方は、x日のデータ(ETFの前日比)を説明変数に、x+1日の騰落を目的変数に入れます。
x+1日の株価が上昇していれば、+1を入れ、下落していれば-1を入れます。

x_train = []
y_train = []
for s in range(0, len(df_train) - 1):
	#print(df_train["Date"].iloc[s], df_train["Date"].iloc[s + 1])
	#print(df_train["Close"].iloc[s], df_train["Close"].iloc[s + 1])

	x_train.append(df_train[xlist].iloc[s])

	if df_train["Close"].iloc[s + 1] > df_train["Close"].iloc[s]:
		y_train.append(1)
	else:
		y_train.append(-1)

#print(x_train)
#print(y_train)

rf = RandomForestClassifier(n_estimators=len(x_train), random_state=0)
rf.fit(x_train, y_train)

n_estimatorsは、ランダムフォレストを構築するために生成する決定木の数です。
基本的には、データ数と同じにすれば大丈夫です。

コメントアウトしてあるprint文を表示してもらえば、一日づつズレて入っていることが解ると思います。

f:id:doz13189:20170209192840p:plain


では、最後に構築したモデルを使用して、株価を予測してみましょう。

データの中で一番最新のもの、つまり、2月7日のETFデータをtest_xとしていれます。

test_x = df_test[xlist].iloc[0]
test_y = rf.predict(test_x.reshape(1, -1))


f:id:doz13189:20170209204500p:plain

マイナス1が出たということは、2/8の株価は下落ということですね。

2月8日の株価を見てみましょう。

f:id:doz13189:20170209204350p:plain

下落しているので、予測は当たっていることになります。


以上で予測は終わりです。

もし、興味が湧いたという方がいればこのコードを少し修正して、他の日もシミュレーションをしてみると良い練習になると思います。

きっと散々な結果がでると思います。

もう一段ステップアップするには何をしたらいい?

ちょっと物足りないという人は他にも色々と試せることはあります。

  • 他の手法を試す
  • 数値を指数平滑化する
  • 過去249日で1日後を予測しているが、これが最適か考える
  • テクニカル指標など他の説明変数を試してみる

まとめ

とても簡単でしたね。

今は色々なパッケージがあるので、機械学習は簡単に扱うことができます。

案外簡単だったと思うので、この記事を機会に色々と勉強してみると面白いと思います。

今回使ったコード

ベタ打ちですみません。
ファイルは2つあります。

野村総合研究所のデータとETFデータを統合するためのファイル

import pandas as pd


df = pd.read_csv("code_4307.csv", header=0)
df.columns=["Date", "Open", "High", "Low", "Close", "Volume", "Trading Value"]
df["index"] = [i for i in range(len(df))]
print(df.head(20))

etf_list = [

		1309,#上海株式指数・上証50連動型上場投資信託

		1313,#サムスンKODEX200証券上場指数投資信託

		1314,#上場インデックスファンドS&P日本新興株100

		1322,#上場インデックスファンド中国A株(パンダ)CSI300

		1326,#SPDRゴールド・シェア

		1343,#NEXT FUNDS 東証REIT指数連動型上場投信

		1543,#純パラジウム上場信託(現物国内保管型)

		1548,#上場インデックスファンド中国H株(ハンセン中国企業株)

		#1549,#上場インデックスファンドNifty50先物(インド株式)

		1551,#JASDAQ-TOP20上場投信

		1633,#NEXT FUNDS 不動産(TOPIX-17)上場投信

		#1649,

		1673,#ETFS 銀上場投資信託

		1678,#NEXT FUNDS インド株式指数・Nifty 50連動型上場投信

		1681,#上場インデックスファンド海外新興国株式(MSCIエマージング)

		1682,#NEXT FUNDS 日経・東商取白金指数連動型上場投信

		1698,#上場インデックスファンド日本高配当(東証配当フォーカス100)

		]

for etf in etf_list:
	#print(etf)
	df_etf = pd.read_csv("etf_" + str(etf) + ".csv", header=0)
	df_etf.columns=["Date", "Open", "High", "Low", "Close", "Volume", "Trading Value"]

	dates = []
	closeis = []
	for d in df["Date"]:
		#try:
		date = df_etf.loc[(df_etf.Date == d), "Date"]
		yesterday_date = date.values[0]
		dates.append(date.values[0])

		close = df_etf.loc[(df_etf.Date == d), "Close"]
		if str(close.values[0]) != str("nan"):
			yesterday_close = close.values[0]
			closeis.append(close.values[0])	

		else:
			#print("nan")
			closeis.append(yesterday_close)
		
	df_etf2 = pd.DataFrame({"Date_" + str(etf) : dates,
							"Close_" + str(etf) : closeis})

	df = pd.concat([df, df_etf2], axis=1)
	df["diff_" + str(etf)] = (df["Close_" + str(etf)] / df["Close_" + str(etf)].shift(-1)) - 1
	#print(df)

df.to_csv("code_4307_plus.csv")


機械学習を行うためのファイル

import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score

df = pd.read_csv("code_4307_plus.csv")
df = df.sort_values(by=["index"], ascending=False)
print(df.tail(20))


df = df.iloc[0:len(df) - 1]
print(df.tail())

df_train = df.iloc[1:len(df)-1]
df_test = df.iloc[len(df)-1:len(df)]

#print("train", df_train)
#print("test", df_test)

xlist = [

		"diff_1309",#上海株式指数・上証50連動型上場投資信託

		"diff_1313",#サムスンKODEX200証券上場指数投資信託

		"diff_1314",#上場インデックスファンドS&P日本新興株100

		"diff_1322",#上場インデックスファンド中国A株(パンダ)CSI300

		"diff_1326",#SPDRゴールド・シェア

		"diff_1343",#NEXT FUNDS 東証REIT指数連動型上場投信

		"diff_1543",#純パラジウム上場信託(現物国内保管型)

		"diff_1548",#上場インデックスファンド中国H株(ハンセン中国企業株)

		"diff_1551",#JASDAQ-TOP20上場投信

		"diff_1633",#NEXT FUNDS 不動産(TOPIX-17)上場投信

		"diff_1673",#ETFS 銀上場投資信託

		"diff_1678",#NEXT FUNDS インド株式指数・Nifty 50連動型上場投信

		"diff_1681",#上場インデックスファンド海外新興国株式(MSCIエマージング)

		"diff_1682",#NEXT FUNDS 日経・東商取白金指数連動型上場投信

		"diff_1698",#上場インデックスファンド日本高配当(東証配当フォーカス100)

		]


x_train = []
y_train = []
for s in range(0, len(df_train) - 1):
	print("x_train : ", df_train["Date"].iloc[s])
	print("y_train : ", df_train["Date"].iloc[s + 1])
	print("")
	x_train.append(df_train[xlist].iloc[s])

	if df_train["Close"].iloc[s + 1] > df_train["Close"].iloc[s]:
		y_train.append(1)
	else:
		y_train.append(-1)

#print(x_train)
#print(y_train)

rf = RandomForestClassifier(n_estimators=len(x_train), random_state=0)
rf.fit(x_train, y_train)


test_x = df_test[xlist].iloc[0]
test_y = rf.predict(test_x.reshape(1, -1))

print("result : ", test_y[0])