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

(緑色の42d lineが見にくいです...)

ここで2つの移動平均線(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の合図)

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

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)


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

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


さらにTrade Signalの発生しなくなった.(ほとんど何もしていない状態.)

(Mid, Long) = (84day, 252day)

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

Trade Signalのしきい値を変更してみる

文献記載のTrade Signalしきい値 SD = 50 であった.これは,S&P 500 のindexに対する値であることに注意が必要である.(S&P 500 = 1000 の時に 5%の差分をみていることになる.)

SD = 20


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

SD = 100


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

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

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

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

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