ストックドッグ

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

第3回(最終回) matplotlibでローソク足を描いてみる 〜S◯I証券風のチャートを描いてみる〜

第3回では、matplotlibでローソク足を描いてみようと思います。

doz13189.hatenablog.com

doz13189.hatenablog.com


ちなみにこれで最終回なので、このエントリーを読み終われば、こんな感じのグラフが描けるはずです。

f:id:doz13189:20160822024916p:plain

今回のソースコードは200行くらいになってしまうので、まとめて最後に載せたいと思います。

では、今日載せたソースコードの内容の概要をざざっと説明すると。

  1. 5日移動平均線を計算し、その計算結果をデータフレームに追加する
  2. 始値終値もろもろ入ったデータフレームをmatplotlibでプロットする
  3. 出来高もmatplotlibで別のグラフにプロットする

5日移動平均線とは、5日間の調整後終値の平均をとった値です。
これを描くことで、簡単な株価の流れが読み取れます。
チャートを描くなら、必須ですね。必須。

他にも25日、60日などいろいろと種類があります。
株価の中期的な流れを見たいなら、25日。長期的に見たいなら60日などなど、自分のトレード方法に合わせて使ってください。

私は、5日線、25日線、60日線、100日線、300日線の5つを使います。
人によって、60日線ではなく75日線などあると思いますが、そこらへんは個人の自由です。
ソースコードの数字をぺっと変えてもらえば、変更できるので、それで対応してください。

最終的なコードには、5つ全ての移動平均線が載っていますが、長いのでここでは5日移動平均線のみを解説します。

5日移動平均線を計算し、その計算結果をデータフレームに追加する

f:id:doz13189:20160831032303p:plain

第2回でこんなデータを作ったと思います。
このAdj Closeを使います。

sma5 = pandas.Series.rolling(self.data['Adj Close'], window=5,center=False).mean()
sma5 = pandas.DataFrame(sma5)
sma5.columns = ["sma5"]
sma5 = sma5.dropna(subset=["sma5"])
kago = [go for go in range(len(sma5))]
sma5.index = kago
new_data = pandas.concat([self.data, sma5], axis=1)

Pythonには便利なもんで、5日移動平均線を計算してくれる関数があります。
それがpandas.Series.rollingです。
window = 5 の数字の部分を変えるだけで、それに応じた移動平均線を計算してくれます。

計算した結果をsma5に入れるのですが、この時に新しくデータフレームをこしらえます。
最終的には、5日移動平均線のデータフレームを作ったあとに、始値終値などが入っているデータフレームにくっつけます。

なぜこうするかというと、例えば、60日分の5日移動平均線を計算すると56個の計算結果が得られます。
5日移動平均線は、5日分のデータが必要なので、始めから4日間は計算することができません。
なので始めから4日間は欠損値になり、NaNとなります。
このNaNがすごくやっかいなのです。

もし、このままmatplotlibでプロットすると欠損値を詰めて計算してしまいます。
するとどうなるかというと、本来はグラフの始めから4日間がデータとして消えているはずなのですが、NaNを詰めて計算するので直近の4日間のデータが消えてしまいます。
直近の4日間がないと移動平均線としての存在が無です。

この解決策として、私はmatplotlibには欠損値を渡さないことにしました。
だから、データフレームに入れ、matplotolibは欠損値のない部分を抜き出して、プロットしています。

sma5 = sma5.dropna(subset=["sma5"])

ここらへんは今読んでも、若干実感がわかないと思うので、とりあえずそういうもんだと思ってくれてかまわないと思います。
とりあえず、欠損値を消すために上のコードを実行しています。

new_data = pandas.concat([self.data, sma5], axis=1)


そして、元のデータに今計算した5日移動平均線を追加しています。
先程も言ったとおり、5日以外の移動平均線は数字を変えてるだけで同じ処理を行います。

choice_data = new_data.ix[:, ["Date","Open","Close","High","Low",'Adj Close',"Volume","Hiduke","sma5","sma25","sma60","sma100","sma300"]]
self.choice_data = choice_data

すべての移動平均線が入ったら、それをchoice_dataとしてまとめます。
これでデータは完成です。
あとはmatplotlibに投げるのみ!


始値終値もろもろ入ったデータフレームをmatplotlibでプロットする

from matplotlib.finance import candlestick_ochl
ax1 = plt.subplot()
candlestick_ochl(ax1, choice_data.values, width=0.7, colorup='#ff1e1e', colordown='#1eff1e')

Pythonは本当に便利なもんで、ローソク足を描く関数も用意されています。
とてもありがたいです。
しかし、このcandlestick_ochlは少々やっかいで、こいつにかなりエラーを出されました。
第2回でデータを整えていたのもこいつのためです。

candlestick_ochlには2つの引数が必要です。
1つは、プロットするフィールド。
2つめは、日付や始値終値などが入ったデータ。

まず、プロットするフィールドとしてax1を与えます。
次にデータを渡すのですが、この時データの順番を守らないといけません。
データの中の順番が、日付→始値終値→高値→安値、でないといけません。
このあとにデータが何個入ってても構わないようです。
実際、私の場合は5日移動平均線などのデータがこの後ろに散在してます。

そして、日付の形式もわりとエラーの原因となります。
日付が計算できるかたちでないと行けないようです。
そのため、私はfloat型になおしています。

plt.plot(self.choice_data["Date"], self.choice_data["sma5"], color="#1e8eff", label = "sma5")
plt.legend(loc='best')

plt.xticks(self.choice_data["Date"][::40], self.choice_data["Hiduke"][::40])
plt.grid(color='#f5f5f5')

ax1.patch.set_facecolor('#333333')

try:
	x_left = self.choice_data["Date"][120]
except KeyError:
	left = len(self.choice_data["Date"])
	x_left = left - 1

x_right = self.choice_data["Date"][0]
plt.xlim(x_left - 5, x_right + 5)

filename_candle_day = self.code + "_candle_day.svg"
plt.savefig(filename_candle_day)

return plt.clf()

あとはプロットするのみです。

plt.legendで、ラベルの位置をベストな配置にしています。

plt.xticksで、日付を["Date"]から["Hiduke"]に置き換えて、表示しています。
元の["Date"]では、型を変えすぎて本当の日付が読み取れないので、こうしています。

try:で行っているのは、データが足りなかった時のためです。
120日分のデータが欲しいが、上場してから80日しか経っていない場合は、データが足りないためKeyErrorが発生します。
そのためエラーが出た場合は、exceptでデータがある分だけを計算するようにしています。

私の場合は、plt.show()ではなく、そのまま画像として保存したいので、plt.savefigしたあとにplt.clf()でpltを初期化しています。
画像の形式もpngやらあったのですが、ズームしても鮮度が変わらないsvgにしました。

ここでの、一番のこだわりは色ですね笑
S◯I風にするために色は精査しました。

www.colordic.org

このサイトからトライ&エラーで色をチョイスしました。
カッコよさという割とどうでもいいことに時間を使いましたね!

エラーなくプロットできれば、こんな感じになると思います。

f:id:doz13189:20160822033538p:plain


出来高もmatplotlibで別のグラフにプロットする
最後です。
チャートを見るときは、合わせて出来高もみたいですよね。
出来高は変わらないのに、株価だけ伸びていたり、その逆もあったりと、異変がシグナルとしてよくでてきます。

ax1 = plt.subplot()
ax1.bar(self.data["Date"], self.data["Volume"],  color='#1e8eff', edgecolor="#333333")
ax2 = ax1.twinx()

ax1.patch.set_facecolor('#333333')
plt.grid(color='#f5f5f5')
try:
	x_left = self.choice_data["Date"][120]
except KeyError:
	left = len(self.choice_data["Date"])
	x_left = left - 1
x_right = self.choice_data["Date"][0]
plt.xlim(x_left - 5, x_right + 5)
ax2.plot(self.data["Date"], self.data["Adj Close"], color="#ff1e8e")

filename_values_day = self.code + "_values_day.svg"
plt.savefig(filename_values_day)

return plt.clf()

コードの内容は変わらないので、解説はしませんね。
もし、エラーなくプロットできればこんな感じになると思います。
出来高と株価を比べやすいように、Adj Closeを同じフィールドにプロットしています。

f:id:doz13189:20160822031206p:plain

長かったですが、以上で終了です。
まとめてソースコードを貼っておきます。
ちなみに今回紹介した部分は、class Score:から下ですね。
上の部分は第1回と第2回で紹介しました。

import pandas
import datetime
import matplotlib.pyplot as plt

def scraping_yahoo(code, start, end, term):
	base = "http://info.finance.yahoo.co.jp/history/?code={0}.T&{1}&{2}&tm={3}&p={4}"

	start = str(start)
	start = start.split("-")
	start = "sy={0}&sm={1}&sd={2}".format(start[0], start[1], start[2])
	end = str(end)
	end = end.split("-")
	end = "ey={0}&em={1}&ed={2}".format(end[0], end[1], end[2])
	page = 1

	result = []
	while True:
		url = base.format(code, start, end, term, page)
		df = pandas.read_html(url, header=0)
		if len(df[1]) == 0:
			break

		result.append(df[1])
		page += 1
	result = pandas.concat(result)
	result.columns = ['Date', 'Open', 'High', 'Low', 'Close', 'Volume', 'Adj Close']

	return result


def generate(data):
	data_frame = data[["Date","Open","Close","High","Low",'Adj Close',"Volume",]]

	hiduke = pandas.DataFrame(data_frame["Date"])
	hiduke.columns = ["Hiduke"]

	dates = []
	for day in data_frame["Date"]:
		day = day.replace("年", "-")
		day = day.replace("月", "-")
		day = day.replace("日", "")
		time = datetime.datetime.strptime(day, '%Y-%m-%d')
		dates.append(time)

	data_frame["Date"] = dates
	tmp = data_frame["Date"].values.astype("datetime64[D]")
	d = tmp
	
	data_frame["Date"] = tmp.astype(float)

	index = len(data_frame["Date"])
	index_data = []
	for x in range(index):
		index_data.append(x)

	data_frame.index = index_data
	hiduke.index = index_data
	
	data_frame = pandas.concat([data_frame, hiduke], axis=1)

	return data_frame


class Score:
	def __init__(self, data, code):
		self.data = data
		self.code = code


	def sma_addition(self):

		sma5 = pandas.Series.rolling(self.data['Adj Close'], window=5,center=False).mean()
		sma5 = pandas.DataFrame(sma5)
		sma5.columns = ["sma5"]
		sma5 = sma5.dropna(subset=["sma5"])
		kago = [go for go in range(len(sma5))]
		sma5.index = kago
		new_data = pandas.concat([self.data, sma5], axis=1)

		
		sma25 = pandas.Series.rolling(self.data['Adj Close'], window=25,center=False).mean()
		sma25 = pandas.DataFrame(sma25)
		sma25.columns = ["sma25"]
		sma25 = sma25.dropna(subset=["sma25"])
		kago = [go for go in range(len(sma25))]
		sma25.index = kago
		new_data = pandas.concat([new_data, sma25], axis=1)

		sma60 = pandas.Series.rolling(self.data['Adj Close'], window=60,center=False).mean()
		sma60 = pandas.DataFrame(sma60)
		sma60.columns = ["sma60"]
		sma60 = sma60.dropna(subset=["sma60"])
		kago = [go for go in range(len(sma60))]
		sma60.index = kago
		new_data = pandas.concat([new_data, sma60], axis=1)

		sma100 = pandas.Series.rolling(self.data['Adj Close'], window=100,center=False).mean()
		sma100 = pandas.DataFrame(sma100)
		sma100.columns = ["sma100"]
		sma100 = sma100.dropna(subset=["sma100"])
		kago = [go for go in range(len(sma100))]
		sma100.index = kago
		new_data = pandas.concat([new_data, sma100], axis=1)

		sma300 = pandas.Series.rolling(self.data['Adj Close'], window=300,center=False).mean()
		sma300 = pandas.DataFrame(sma300)
		sma300.columns = ["sma300"]
		sma300 = sma300.dropna(subset=["sma300"])
		kago = [go for go in range(len(sma300))]
		sma300.index = kago
		new_data = pandas.concat([new_data, sma300], axis=1)

		choice_data = new_data.ix[:, ["Date","Open","Close","High","Low",'Adj Close',"Volume","Hiduke","sma5","sma25","sma60","sma100","sma300"]]
		self.choice_data = choice_data

		return self.choice_data



	def ochl(self):
		from matplotlib.finance import candlestick_ochl
		ax1 = plt.subplot()
		try:
			candlestick_ochl(ax1, self.choice_data.values, width=0.7, colorup='#ff1e1e', colordown='#1eff1e')
		except TypeError:
			pass


		plt.plot(self.choice_data["Date"], self.choice_data["sma5"], color="#1e8eff", label = "sma5")
		plt.plot(self.choice_data["Date"], self.choice_data["sma25"], color="#ff8e1e", label = "sma25")
		plt.plot(self.choice_data["Date"], self.choice_data["sma60"], color="#ff1e8e", label = "sma60")
		plt.plot(self.choice_data["Date"], self.choice_data["sma100"], color="#1effff", label = "sma100")
		plt.plot(self.choice_data["Date"], self.choice_data["sma300"], color="#ff7fbf", label = "sma300")
		plt.legend(loc='best')

		plt.xticks(self.choice_data["Date"][::40], self.choice_data["Hiduke"][::40])
		plt.grid(color='#f5f5f5')

		ax1.patch.set_facecolor('#333333')

		try:
			x_left = self.choice_data["Date"][120]
		except KeyError:
			left = len(self.choice_data["Date"])
			x_left = left - 1
		x_right = self.choice_data["Date"][0]
		plt.xlim(x_left - 5, x_right + 5)

		filename_candle_day = self.code + "_candle_day.svg"
		plt.savefig(filename_candle_day)

		return plt.clf()


	def values(self):
		ax1 = plt.subplot()
		ax1.bar(self.data["Date"], self.data["Volume"],  color='#1e8eff', edgecolor="#333333")
		ax2 = ax1.twinx()

		ax1.patch.set_facecolor('#333333')
		plt.grid(color='#f5f5f5')
		try:
			x_left = self.choice_data["Date"][120]
		except KeyError:
			left = len(self.choice_data["Date"])
			x_left = left - 1
		x_right = self.choice_data["Date"][0]
		plt.xlim(x_left - 5, x_right + 5)
		ax2.plot(self.data["Date"], self.data["Adj Close"], color="#ff1e8e")

		filename_values_day = self.code + "_values_day.svg"
		plt.savefig(filename_values_day)

		return plt.clf()






if __name__ == "__main__":
	company = 4312

	EndDate = datetime.date.today()
	StartDate = EndDate - datetime.timedelta(days=60)#640

	data = scraping_yahoo(company, StartDate, EndDate, "d")
	generate_data = generate(data)
	big_company = Score(generate_data, str(company))
	big_company.sma_addition()
	big_company.ochl()
	big_company.values()