Most Simple Algorithm Trade - パフォーマンス良好か？


"Python for Finance" (by Yves Hilpisch, O'reilly media) は全体が3部構成になっていて，Part-1は導入的な内容が書かれている．そのPart-1でPythonツールでできることの例として，米国株式インデックスS&P500の移動平均を用いた簡単なAlogorithm Tradeの説明があり，目が留まった．case studyの最終利益率が2.5倍を越えていて何ともポジティブな印象を持った反面，本当に話が合っているのか確認してみたくなった．

まず，本文通りにプログラムを実行し，さらに追加でパラメトリックなスタディをしてみる．

S&P500は，いわばアメリカ版Nikkei 225のようなもの．2000年以降，ネットバブルの影響と，リーマンショックの影響により，下図のようなチャートとなる．

**図．The S&P 500 index with 42d 252d trend lines**
![TL_42d-252d.png](https://qiita-image-store.s3.amazonaws.com/0/74152/22c8592c-e1c5-a925-0747-bc4d63cf60fd.png)

（緑色の42d lineが見にくいです...）

ここで２つの移動平均線(42day, 252day)に着目し，その動きから以下のTrade Action ルールを定める．

- Buy signal (go long) - the 42d trend is  SD points above the 252d trend
- Wait (park in cash) - the 42d trend is within a range of +/-SD points around the 252 trend
- Sell signal (go short) - the 42d trend is SD points below the 252d trend

SDは，(何の略であるか説明は無かったが）trade signalに対する一種のしきい値とのことである．ルールは以上で，これをpython codeにして計算する．

``` 
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# import pandas.io.data as web
#
# sp500 = web.DataReader('^GSPC', data_source='yahoo', start='1/1/2000', end='4/14/2014')

sp500 = pd.read_csv('./sp500.csv', index_col='Date', parse_dates=True)

mid_term = 42                        # parameter-A : moving average length (Mid.term)
long_term = 252                      # parameter-B : moving average length (Long term)
mid_label = str(mid_term) + 'd'
long_label = str(long_term) + 'd'
sp500[mid_label] = np.round(pd.rolling_mean(sp500['Close'], window=mid_term), 2)
sp500[long_label] = np.round(pd.rolling_mean(sp500['Close'], window=long_term), 2)

sp500[['Close', mid_label, long_label]].plot(grid=True, figsize=(8, 5))
sp500['mid-long'] = sp500[mid_label] - sp500[long_label]

SD = 50                             # parameter-C : threshold of trade action
# 
sp500['Regime'] = np.where(sp500['mid-long'] > SD, 1, 0)
sp500['Regime'] = np.where(sp500['mid-long'] < -SD, -1, sp500['Regime'])
sp500['Regime'].value_counts()
#
plt.figure()
sp500['Regime'].plot(lw=1.5, figsize=(8,2))
plt.ylim([-1.1, 1.1])

sp500['Market'] = np.log(sp500['Close'] / sp500['Close'].shift(1))
sp500['TradeReturn'] = sp500['Regime'].shift(1) * sp500['Market']
sp500[['Market', 'TradeReturn']].cumsum().apply(np.exp).plot(grid=True, figsize=(8,5))

``` 

上のリストはほぼ文献からの引用だが，一部，変更している．
- パラメトリック・スタディのため，移動平均の平均化区間を変更しやすくしている．
- SD も(変数の定義だけだが）パラメータとして明示的に変更するようにした．
- S&P500は，’yahoo.com'からデータを入手する，というのがオリジナル．（今回は，最初にwebサイトから取ってDisk保存し，それを繰り返し読むようにした．）

pandasで簡潔に書かれているが，

``` 
sp500[mid_label] = np.round(pd.rolling_mean(sp500['Close'], window=mid_term), 2)
```
ここでは，移動平均計算の **pd.rolling_mean() ** を用いている．

``` 
sp500['Market'] = np.log(sp500['Close'] / sp500['Close'].shift(1))
sp500['TradeReturn'] = sp500['Regime'].shift(1) * sp500['Market']
sp500[['Market', 'TradeReturn']].cumsum().apply(np.exp).plot(grid=True, figsize=(8,5))

```
ここでは，DataFrame オブジェクトに **cumsum(), apply(np.exp)** した後(累積値を求めてLogを元に戻して）**plot()**している．ここら辺の関数の使い方はとても参考になる．


結果は下図の通り．

** Fig. Signal regimes over time **（-1 : Shortの合図，+1：Longの合図)

![TSig_42d-252d.png](https://qiita-image-store.s3.amazonaws.com/0/74152/2d2ccbf4-379f-4a00-4138-b93bb9796bba.png)

** The S&P 500 index vs. investor's wealth **

![Return_42d-252d.png](https://qiita-image-store.s3.amazonaws.com/0/74152/183850b9-8c88-7244-79c1-96720636ad78.png)

S&P 500 index自体が増加率 1.3 (130%） であるのに対し，投資行動の結果の利益率が 2.7 (270%) となっている．この結果について著者は以下を述べている．

- 実際のTradeでは売買手数料が発生するので，利益率は落ちる結果となるだろう．
- 株価上昇trend時より，下落trendの状況下で（Short Positionを作ることで）大きく利益を挙げている．

2度の経済的危機（ネットバブル崩壊，リーマンショック）を経過した中で資産2.7倍はかなり魅力的に見える．(シンプル極まりないAlgorithm Tradeにしては良好である．）もしかすると移動平均の使い方や，Trade Signalのしきい値の設定が偶然よかったからなのでは? という疑問がわいた．以下，調べてみた．

### 移動平均区間(averaging period)を変更してみる
文献記載の移動平均計算区間は (Mid, Long) = **(42day, 252day)** であった．これを以下のように変えて計算した．

**(Mid, Long) = (42day, 126day)**

![TSig_42d-126d.png](https://qiita-image-store.s3.amazonaws.com/0/74152/4d3f1e6f-48bc-36ca-5fe7-9e3fe333f2c9.png)
![Return_42d-126d.png](https://qiita-image-store.s3.amazonaws.com/0/74152/50efe571-4a23-18ef-e5e3-f43c685fc475.png)

カーブの類似性が上がったため，Trade Signalの発生の頻度が下がった模様．

**(Mid, Long) = (84day, 126day)**

![TSig_84d-126d.png](https://qiita-image-store.s3.amazonaws.com/0/74152/5ad0bcf9-ba0a-ee5b-44a2-4f11b662ab38.png)
![Return_84d-126d.png](https://qiita-image-store.s3.amazonaws.com/0/74152/0461bcd6-024e-e3f6-55c5-bf21e26a8d50.png)

さらにTrade Signalの発生しなくなった．（ほとんど何もしていない状態．）

**(Mid, Long) = (84day, 252day)**
![TSig_84d-252d.png](https://qiita-image-store.s3.amazonaws.com/0/74152/d0e12c70-1fca-bd01-5455-8f25b8e90d29.png)
![Return_84d-252d.png](https://qiita-image-store.s3.amazonaws.com/0/74152/19a65118-bbc7-6953-26aa-234fd7b55456.png)

状況は最初のもの(42day, 252day)に近づいた．但し，最終利益率は約2.2 (220%)で最初の数字（約2.7）より劣っている．



### Trade Signalのしきい値を変更してみる
文献記載のTrade Signalしきい値 SD = 50 であった．これは，S&P 500 のindexに対する値であることに注意が必要である．(S&P 500 = 1000 の時に ５％の差分をみていることになる．）

**SD = 20**

![TSig_SD=20.png](https://qiita-image-store.s3.amazonaws.com/0/74152/eb34d8dd-09c7-515b-130f-851d3834fe5c.png)
![Return_SD=20.png](https://qiita-image-store.s3.amazonaws.com/0/74152/b5140111-ab6e-2cde-f528-26ccc0f03c96.png)

Signal Profileが -1.0 や +1.0 にへばりついているのは，休みなく売買していることを示している．最終利益率は，最初の値より若干upした．(約 2.8~2.9）

**SD = 100**

![TSig_SD=100.png](https://qiita-image-store.s3.amazonaws.com/0/74152/77f803ff-4725-dfc9-4b5b-6ed0ad3b9ca5.png)
![Return_SD=100.png](https://qiita-image-store.s3.amazonaws.com/0/74152/9ae97ee5-572c-864c-e624-4e1046fb807a.png)

Trade頻度は，かなり低下してる．結果は最悪で．なんと index にすら負けている．

以上のような結果でした．

- 予想した通り，文献記載のパラメータは（経験的に選択したのかも知れないが）相対的に「うまい」設定のように見える．
- 唯一，SD=20 （しきい値を下げた）条件で，若干パフォーマンスが改善した．（もちろんTrade手数料は無視するという前提はそのまま．）
- この２つの移動平均線を参考にする方法は，「ゴールデンクロス」「デッドクロス」と世の中で呼ばれているもので一般的らしい．

最初，利益率2.6倍にひかれましたが，よくよく考えると14年間でこれですから年率にすると8％前後の数字になってしまいます．実際の投資としては，それほど魅力ある内容ではない(?)のかも知れません．（但し，個人的に pandasの勉強にはなりました．）

**参考文献**
- "Python for Finance": [(O'reilly Media) http://shop.oreilly.com/product/0636920032441.do](http://shop.oreilly.com/product/0636920032441.do) 
- pandas 公式ドキュメント [http://pandas.pydata.org/pandas-docs/stable/index.html](http://pandas.pydata.org/pandas-docs/stable/index.html)