【Python】移動平均を計算してみる
株の分析で必ずと言っていいほど出てくるのが、移動平均線。
今回はPythonで単純移動平均(SMA)と指数平滑移動平均(EMA)を求めたいと思います。
※python2で書いています。
計算式の確認
検索すればすぐに出てくるけれど、一応確認のため書きます。
単純移動平均(SMA)
期間3日の移動平均の場合
SMA[0] = (1日目終値 + 2日目終値 + 3日目終値) / 3
SMA[1] = (2日目終値 + 3日目終値 + 4日目終値) / 3
︙
SMA[n] = (n+1日目終値 + n+2日目終値 + n+3日目終値) / 3
指数平滑移動平均(EMA)
EMAの場合α(平滑定数)というものがある。
α = 2 / (n + 1)
期間3日の指数移動平均の場合
α = 2 / (3 + 1)
EMA[0] = SMA[0]
EMA[1] = EMA[0] + α * (4日目終値 - EMA[0])
EMA[2] = EMA[1] + α * (5日目終値 - EMA[1])
︙
EMA[n] = EMA[n-1] + α * (n+3日目終値 - EMA[n-1])
計算していく
ここからPythonで書いて計算してみたいと思います。
元データの準備
計算するために元のデータを準備しないといけませんね。。。
今回は 汲めども尽きない 無尽蔵さん から過去データ、2015年度の日経225年間データを使わせて頂きました。ありがとうございます。
Officeソフトで開いて日足のタブを CSV形式 でエクスポートしたものを使うことにします。
ファイルの中身は以下のようになっている。
無尽蔵,日経225先物,,〔日足〕,, 日付,始値,高値,安値,終値,出来高 15/01/05,17350,17550,17040,17070,99582 ~省略~ 15/12/30,19070,19110,18960,19010,27240
Pythonスクリプト内では、次のようにして終値だけを抜き出して使用していきます。
import codecs
# 終値の用意
with codecs.open('./nikkei_225.csv', 'r', encoding='utf-8') as f:
# header = [next(f).encode('utf-8') for _ in np.arange(2)]
[next(f).encode('utf-8') for _ in range(2)]
closeList = [float(row.split(',')[4]) for row in f]
単純移動平均(SMA) と 指数平滑移動平均(EMA) を計算する
サードパーティライブラリの pandas を使って計算するのが楽らしい。今回は pandas を使って楽に計算します。
#! /usr/bin/env python # coding: utf-8 import codecs import pandas as pd def sma(closeList=[], term=5): '''単純移動平均の計算''' return list(pd.Series(closeList).rolling(term).mean()) def ema(closeList=[], term=5): '''指数平滑移動平均の計算''' return list(pd.Series(closeList).ewm(span=term).mean()) def main(): # 終値の用意 with codecs.open('./nikkei_225.csv', 'r', encoding='utf-8') as f: [next(f).encode('utf-8') for _ in range(2)] closeList = [float(row.split(',')[4]) for row in f] # pandas df = pd.DataFrame(dict(close=closeList, sma=sma(closeList), ema=ema(closeList))) print(df) if __name__ == '__main__': main()
pandas を使うと一行で 単純移動平均、指数平滑移動平均 を書くことができる。
SMAは、pandas の rolling()メソッド に期間を指定して mean()メソッド で平均。
EMAは、pandas の ewm()メソッド の span に期間を指定して mean()メソッド で平均。
今回は実行時間も測定したかったので、わざわざ関数にしてある。なお、期間は5日にした。
出力結果が見やすくなるように pandas.DataFrame に突っ込んで print している。
実行結果は以下。
close ema sma 0 17070.0 17070.000000 NaN 1 16700.0 16848.000000 NaN 2 17140.0 16986.315789 NaN 3 17350.0 17137.384615 NaN 4 16940.0 17061.611374 17040.0 5 17040.0 17053.714286 17034.0 ~省略~ 239 18820.0 18875.635525 18924.0 240 18750.0 18833.757017 18800.0 241 18820.0 18829.171345 18798.0 242 19110.0 18922.780896 18862.0 243 19010.0 18951.853931 18902.0
SMAの計算結果は問題なさそう。
EMAの方は 0 ~ 3 に数値があることに違和感を感じる。さらに上記で確認した計算式では、EMAの一日目(インデックスでは4)はSMAと等しくなるはずです。この件に関しては別記事にしようかと。。。今は無視してください。
視覚で分かりやすいようにグラフにする
数字を見てるだけだと全然実感が湧かないので、簡単なグラフにします。
pandas の DataFrame型 には グラフ描画のための plot()メソッド が用意されています。便利ですね。
必要なライブラリを先程のスクリプトに追加します。
import matplotlib as mpl
import matplotlib.pyplot as plt
main()関数 の最後に以下の一行を追記。
plt.show(df.plot())
以上で準備完了。実行すると次のグラフが表示された。
簡単に確認できるので便利ですね。
おまけ
参考として単純移動平均(SMA)と指数平滑移動平均(EMA)の実行速度を図ってみた。
#! /usr/bin/env python # coding: utf-8 import codecs import timeit import pandas as pd def sma(closeList=[], term=5): return list(pd.Series(closeList).rolling(term).mean()) def ema(closeList=[], term=5): return list(pd.Series(closeList).ewm(span=term).mean()) def main(): # 終値の用意 with codecs.open('./nikkei_225.csv', 'r', encoding='utf-8') as f: [next(f).encode('utf-8') for _ in range(2)] closeList = [float(row.split(',')[4]) for row in f] loop = 1000 result = timeit.timeit(lambda: sma(closeList), number=loop) print('SMA: {0}'.format(result / loop)) result = timeit.timeit(lambda: ema(closeList), number=loop) print('EMA: {0}'.format(result / loop)) if __name__ == '__main__': main()
SMA: 0.000712622880936 EMA: 0.000755685806274
pandas は遅いとよく見るが、思っていたより早い印象。
おわりに
参考になれば幸いです。