誰でもできるバックテスト
バックテストはシステムトレードにはなくてはならない道具です。バックテストをするからシステムトレードをする意味があるとも言えます。FREDから日経225をダウンロードします。(経済データのダウンロード参照) つぎにクロスオーバー戦略という移動平均を2本使ってシグナルを出すテクニカル手法の1つを使って、バックテストをしてみます。
初期化
%matplotlib inline
import matplotlib.pyplot as plt #描画ライブラリ
import pandas_datareader.data as web #データのダウンロードライブラリ
import pandas as pd
つぎに日経225のデータをFREDからダウンロードします。US Yahoo financeからダウンロードも可能です。(Yahoo Finance USから株価をダウンロードしてみた参照) その際にはデータの構造が変わります。
tsd = web.DataReader("NIKKEI225", 'fred','1949').dropna()
tsd.columns=['Close']
tsd.head(1),tsd.tail(1)
結果は
( Close
DATE
1949-05-16 176.21, Close
DATE
2019-09-20 22079.09)
となります。
トレンドフォロー戦略
データがダウンロードできたので、つぎはいよいよバックテストをする関数を作ります。
損切なしの単純タイプ
最初は損切しないタイプの戦略を行います。
def up_crossover_ma_two(tsd,window0,window9):
y=pd.DataFrame(tsd).copy()
y['pl']=0 #一日の損益を入れます。
y['ma0']=y.Close.rolling(window0).mean().shift(1) #短い移動平均の計算
y['ma9']=y.Close.rolling(window9).mean().shift(1) #長い移動平均の計算
y=y.dropna(how='any')#数値の無いものが1つでもあればその行を削除します。
#init----------------------------------
n=0
buy=0 #買った時の価格です。フラグの役割もしています。
buyF=0 #買いシグナルのフラグ。
sellF=0 #売りシグナルのフラグ
size=0 #トレードできる資産の最大の単位数を示します。
comm=0 #手数料を入れます。今回はゼロの設定です。
for i in range(1,len(y)):
c=y.Close.iloc[i] #その日の価格の取得
c0=y.Close.iloc[i-1] #前日の価格の取得
m0=y.ma0.iloc[i]
m9=y.ma9.iloc[i]
if buy!=0:
y.iloc[i,1]=(c-c0)# pl
if m0>m9:
buyF=1
sellF=0
if m0<m9:
sellF=1
buyF=0
if buyF==1 and buy==0:#entry long-position
buy=c+c*comm*2
y.iloc[i,1]=-c*comm*2
if sellF==1 and buy!=0 and c>buy:#c>buyで利益だ出たときだけ利食います。
buy=0
buyF=0
sellF=0
return y
このプログラムを実行してみましょう。短いほうの移動平均を100日、長いほうの移動平均を400日にしてみます。
y=up_crossover_ma_two(tsd,100,400)
y.pl.cumsum().plot()
結果はいいのですが、これは偶然です。過去を分析するのは簡単です。過去のデータに移動平均の日数をうまくいくように適合すればよいわけですから。将来これでうまくいくという保証はありません。いろいろやってみてください。それがシステムトレードの本質です。
さて、このバックテストの欠点は何でしょうか?リスク管理が甘いということです。いったん利食いを行って、利益を確定した後に、価格が下がってくれればいいですが、当然下がる場合もあります。下がるとどうなりますか?一単位買えなくなります。そのことがこのバックテストでは考慮されていません。しかし、この程度の長期のクロスオーバーではそれもOKとしましょう。
しかし損切をしたり、もっと短い期間でトレードするときはもっと慎重にバックテストをするべきです。
損切と短い期間での売買
多くの人はもっと短い期間で売買を繰り返す方法を好みます。また、損切を好みます。そうするといろいろな面で厄介なことが起きます。それは短期売買では利益を確定したり、損切をしたりした後に価格が下がってくれればいいですが、上がってしまうとⅠ単位買えなくなるという問題です。それを考慮したバックテストを紹介します。
def up_crossover_ma_two2(tsd,window0,window9):
y=pd.DataFrame(tsd).copy(9)
y['pl']=0
y['ma0']=tsd.Close.rolling(window0).mean().shift(1)
y['ma9']=tsd.Close.rolling(window9).mean().shift(1)
y['siz']=0
y=y.dropna(how='any')#数値の無いものが1つでもあればその行を削除します。
#init----------------------------------
n=0
buy=0
sell=0
buyF=0
sellF=0
size=0
comm=0
j=0
for i in range(1,len(y)):
c=y.Close.iloc[i]
c0=y.Close.iloc[i-1]
m0=y.ma0.iloc[i]
m9=y.ma9.iloc[i]
y.iloc[i,4]=size
if buy!=0:
y.iloc[i,1]=(c-c0)*size# pl
if m0>m9:
buyF=1
sellF=0
if m0<m9:
sellF=1
buyF=0
if buyF==1 and buy==0:#entry long-position
buy=c+c*comm*2
y.iloc[i,1]=-c*comm*2
if j==0:
pl0=c
j=1
cumpl=y.pl.cumsum().iloc[i]+pl0
if cumpl>c:
size=1
else:
size=cumpl/c
if sellF==1 and buy!=0 and c>buy:#c>buyで利益だ出たときだけ利食います。
buy=0
buyF=0
sellF=0
return y
このプログラムを実際に回してみましょう。
y=up_crossover_ma_two2(tsd,100,400)
y.pl.cumsum().plot()
y.siz.plot()
なるほどこの戦略では損切をしていないのに、sizが減ってしまっています。それは利食いの後に価格が上がってしまったからです。利食いの後に価格が上がってしまうと、元の枚数を買い戻せなくなってしまいます。損切をするとその影響はもっと大きくなるはずです。なぜなら損切をして価格が上がてしまうと、元の枚数を買い戻せなくなるからです。
バックテストの時期の影響
バックテストを行う期間によってそれぞれの戦略の結果が違ってきます。
tsd0=tsd.loc['1980':'1990']
for s in range(50,150,50):
for l in range(150,400,100):
y=up_crossover_ma_two2(tsd0,s,l)
y.pl.cumsum().plot(label=str(s)+'-'+str(l))
plt.legend()
tsd0=tsd.loc['1990':'2000']
for s in range(50,150,50):
for l in range(150,400,100):
y=up_crossover_ma_two2(tsd0,s,l)
y.pl.cumsum().plot(label=str(s)+'-'+str(l))
plt.legend()
tsd0=tsd.loc['2000':]
for s in range(50,150,50):
for l in range(150,400,100):
y=up_crossover_ma_two2(tsd0,s,l)
y.pl.cumsum().plot(label=str(s)+'-'+str(l))
plt.legend()
バックテストの時期と短期トレード
短期売買では取引費用を含まなければいけませんが、以下のバックでストでは含んでいません。短期売買では取引費用の影響は絶大です。
tsd0=tsd.loc['1980':'1990']
for s in range(10,50,10):
for l in range(50,100,25):
y=up_crossover_ma_two2(tsd0,s,l)
y.pl.cumsum().plot(label=str(s)+'-'+str(l))
plt.legend()
tsd0=tsd.loc['1990':'2000']
for s in range(10,50,10):
for l in range(50,100,25):
y=up_crossover_ma_two2(tsd0,s,l)
y.pl.cumsum().plot(label=str(s)+'-'+str(l))
plt.legend()
tsd0=tsd.loc['2000':]
for s in range(10,50,10):
for l in range(50,100,25):
y=up_crossover_ma_two2(tsd0,s,l)
y.pl.cumsum().plot(label=str(s)+'-'+str(l))
plt.legend()
いろいろ試してみて、システムトレードのセンスを養ってみてください。
注意
本資料は学習用に作成されたものであり投資等における判断はご自身で行うようお願いいたします。
関連サイト
- 特にシステムトレードに興味のある方
Jupyter notebookのインストール
Yahoo Finance USから株価をダウンロードしてみた
システムトレードってなに?
カルマンフィルター
システムトレードにおける対数の役割
経済データのダウンロード
グロスマン・ミラーモデル(翻訳)
- 人工知能関連
誰でもわかるニューラルネットワーク:アプリのように動かす人工知能ーテンソルフロープレイグラウンド
誰でもわかるニューラルネットワーク:正則化をテンソルフロープレイグラウンドで試してみた
参考
「Python3ではじめるシステムトレード」(パンローリング)「タートル流投資の魔術](徳間書店)
Pandas datareader (https://pandas-datareader.readthedocs.io/en/latest/)