turtlechanのブログ

無知の私がLinuxをいじりながら書いていくブログ

【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形式 でエクスポートしたものを使うことにします。

ファイルの中身は以下のようになっている。

nikkei_225.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 を使って楽に計算します。

sma_ema.py
#! /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と等しくなるはずです。この件に関しては別記事にしようかと。。。今は無視してください。

 

turtlechan.hatenablog.com

 

視覚で分かりやすいようにグラフにする

数字を見てるだけだと全然実感が湧かないので、簡単なグラフにします。

pandas の DataFrame型 には グラフ描画のための plot()メソッド が用意されています。便利ですね。
必要なライブラリを先程のスクリプトに追加します。

import matplotlib as mpl
import matplotlib.pyplot as plt

main()関数 の最後に以下の一行を追記。

plt.show(df.plot())

以上で準備完了。実行すると次のグラフが表示された。

日経255のグラフ

簡単に確認できるので便利ですね。

おまけ

参考として単純移動平均(SMA)と指数平滑移動平均(EMA)の実行速度を図ってみた。

how_speed.py
#! /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 は遅いとよく見るが、思っていたより早い印象。

おわりに

参考になれば幸いです。