累積リターンをfor文を使わずに算出する冴えたやり方

fin-pyもくもく会 #8 で for文を使って累積リターンを算出して遅いという話をしていたら、2casa さんより、対数収益率を計算した後に合計したらよいのではないかとアドバイスいただきました。

確かに「Pythonでfor文を使ったら負け」という格言があることですし、色々試してみました。

前提条件

下記のように最初の株価を100円とし、1000日分の騰落率をランダムに与えてみます。

In [1]:
import numpy as np


n = 1000  # データ数
s0 = 100  # 最初の株価
np.random.seed(10)
pct_change = np.random.randn(n - 1) * 0.01  # 騰落率

pct_change[:5]
Out[1]:
array([ 1.33158650e-02,  7.15278974e-03, -1.54540029e-02, -8.38384993e-05,
        6.21335974e-03])

上記の騰落率を使うと、2日目の株価は

100 + 100 * 1.33158650e-02 = 101.3315865

3日目の株価は

101.3315865 + 101.3315865 * 7.15278974e-03 = 102.05639003225512

となります。

for文を使う

まずは古典的なfor文を使ってみます。

In [2]:
%%time
stock_price1 = []
stock_price1.append(s0)

for x in pct_change:
    stock_price1.append(stock_price1[-1] * (1 + x))    
CPU times: user 0 ns, sys: 11.3 ms, total: 11.3 ms
Wall time: 1.8 ms

前述した計算とあっているか確認してみましょう。

In [3]:
stock_price1[:3]
Out[3]:
[100, 101.33158650412952, 102.05639003681789]

あってそうですね。

リストの append() を使う処理は重いので、numpy.ndarray を使ってみます。

In [4]:
%%time
stock_price2 = np.empty(n)
stock_price2[0] = s0

for i, x in enumerate(pct_change):
    stock_price2[i + 1] = stock_price2[i] * (1 + x)
CPU times: user 943 µs, sys: 857 µs, total: 1.8 ms
Wall time: 1.81 ms

速度に大きな改善はありませんでしたが、コードが少しスッキリしました。

累積積から算出する

cumprod() メソッドを使って累積積から算出します。
for文を使わないのでさらにスッキリしました。処理速度も改善しています。

In [5]:
%%time
stock_price3 = np.ones(1000)
stock_price3[1:] = (pct_change + 1).cumprod()
stock_price3 = stock_price3 * s0
CPU times: user 351 µs, sys: 315 µs, total: 666 µs
Wall time: 534 µs

対数収益率にした合計から算出する

対数収益率を使うと、累積リターンは合計(sum)から算出できます。
なんと、1行で書くことができました!

In [6]:
%%time
stock_price4 = np.exp(np.log(pct_change + 1).cumsum()) * s0
CPU times: user 488 µs, sys: 437 µs, total: 925 µs
Wall time: 448 µs

対数収益率にする利点としては、最後の収益を出したいだけなら合計するだけで算出できることです。

In [7]:
%%time
np.exp(np.log(pct_change + 1).sum()) * s0
CPU times: user 101 µs, sys: 90 µs, total: 191 µs
Wall time: 146 µs
Out[7]:
82.20214411270422

確認作業

それぞれの累積リターンの算出が合っているか確認してみます。

In [8]:
print(stock_price1[-5:])
print(stock_price2[-5:])
print(stock_price3[-5:])
print(stock_price4[-5:])
[82.45939577913853, 81.62297782334616, 81.35213013371089, 82.52416815892012, 82.20214411270433]
[82.45939578 81.62297782 81.35213013 82.52416816 82.20214411]
[82.45939578 81.62297782 81.35213013 82.52416816 82.20214411]
[82.45939578 81.62297782 81.35213013 82.52416816 82.20214411]

PythonユーザのためのJupyter[実践]入門 を参考に、算出した株価を可視化してみます。

In [9]:
import matplotlib.pyplot as plt
from matplotlib import rcParams


plt.plot(stock_price1, label='for(append)')
plt.plot(stock_price2, label='for(numpy.ndarray)')
plt.plot(stock_price3, label='cumprod')
plt.plot(stock_price4, label='cumsum')
plt.legend()
plt.show()

4つの方法で算出した結果が全て重なっているので、問題なさそうですね。