はじめに
前回はこんな記事を書きました。
今回はリターンがどれくらいになるのか求めてみたいと思います。
リターンの計算ですが、今まではしこしことトレーディングクラスなどを書いていたのですが、Backtestingという便利なライブラリを見つけたのでこれを使用することにします。
インストールは上のページにも書いてありますが下記のように行います。
$ pip install Backtesting
まずはリターンを求めてみる
Backtestingを使用するときには、Strategyクラスを実装する必要があります。
ここではゴールデンクロスで買う、デッドクロスで売る、という戦略でいくことにします。
- long_term, short_termをクラス変数で定義しています。クラス変数として定義することで、後で書きますがoptimizeメソッドを使用して最適な組み合わせを求めることができるようになります。
- initメソッドでSMAを求めています。Backtestingの中にもSMAを計算する処理はあるのですが、今後のことも考えて、TA-Libを使用することにしています。
- nextメソッドは時系列データごとに呼び出されます。ここではゴールデンクロスで買い、デッドクロスで売るという単純な戦略を取ってます。
class SmaCross(Strategy):
'''
SMS Cross Strategy
'''
long_term = 75
short_term = 25
def init(self):
close = self.data['Close']
self.long_sma = self.I(talib.SMA, close, self.long_term)
self.short_sma = self.I(talib.SMA, close, self.short_term)
def next(self):
if crossover(self.short_sma, self.long_sma):
self.buy()
elif crossover(self.long_sma, self.short_sma):
self.sell()
では、リターンを求めてみます。
- 株価の取得は前と同じget_stockメソッドを使っています。
- Backtestクラスのインスタンスを作成します。ここで先ほど作成したStrategyクラスを指定します。
- trade_on_closeで取引のタイミングを指定します。Trueにするとその日の終値で取引を行い、Falseにすると翌日の始値で取引を行います。その日の終値を確認して翌日に仕掛けるということでFalseにしています。
- exclusive_ordersは取引時にそれまでのポジションをクローズするかどうかを指定します。毎回ポジションをクローズしたいのでTrueとしています。
- runで計算を開始します。
df = get_stock(TICKER, START_DATE, END_DATE)
bt = Backtest(
df,
SmaCross,
cash=INIT_CASH,
trade_on_close=False,
exclusive_orders=True
)
output = bt.run()
print(output)
bt.plot()
print(output)の結果です。
Start 2011-01-04 00:00:00
End 2020-12-30 00:00:00
Duration 3648 days 00:00:00
Exposure Time [%] 94.9775
Equity Final [$] 650714
Equity Peak [$] 1.42691e+06
Return [%] -34.9286
Buy & Hold Return [%] 163.934
Return (Ann.) [%] -4.3251
Volatility (Ann.) [%] 18.8042
Sharpe Ratio 0
Sortino Ratio 0
Calmar Ratio 0
Max. Drawdown [%] -65.8071
Avg. Drawdown [%] -6.89812
Max. Drawdown Duration 2779 days 00:00:00
Avg. Drawdown Duration 192 days 00:00:00
# Trades 41
Win Rate [%] 36.5854
Best Trade [%] 40.7543
Worst Trade [%] -13.8896
Avg. Trade [%] -1.04275
Max. Trade Duration 288 days 00:00:00
Avg. Trade Duration 85 days 00:00:00
Profit Factor 0.806629
Expectancy [%] -0.650414
SQN -0.722688
_strategy SmaCross
_equity_curve ...
_trades Size EntryB...
dtype: object
"Return [%]"の行にリターンが出ています。結果は残念ながら**マイナス39.4%**でした。
bt.plot()でトレードの結果のグラフを出力できます。2013年半ばにピークを迎えた後、下降の一途を辿っているように見えます。
最適な組み合わせを探す(1)
Backtestのoptimizeメソッドを使用すると、短期と長期の期間の最適な組み合わせを求めることができます。
- SmaCrossクラスのクラス変数で定義したlong_term, short_termを範囲で指定します。するとこの範囲から最適な組み合わせを見つけてくれます。
- return_heatmapをTrueにすると、組み合わせのヒートマップを取得することができます。plot_heatmapsメソッドを使用すればグラフを表示できます。
stats, heatmap = bt.optimize(
long_term=range(3, MAX_LONG_TERM + 1, 101),
short_term=range(2, MAX_LONG_TERM, 100),
return_heatmap=True,
constraint=lambda p: p.short_term < p.long_term)
statsの内容は以下の通りです。
Start 2011-01-04 00:00:00
End 2020-12-30 00:00:00
Duration 3648 days 00:00:00
Exposure Time [%] 98.775
Equity Final [$] 1.97874e+06
Equity Peak [$] 1.97939e+06
Return [%] 97.8745
Buy & Hold Return [%] 163.934
Return (Ann.) [%] 7.27493
Volatility (Ann.) [%] 21.6981
Sharpe Ratio 0.33528
Sortino Ratio 0.52895
Calmar Ratio 0.152433
Max. Drawdown [%] -47.7255
Avg. Drawdown [%] -4.58205
Max. Drawdown Duration 2490 days 00:00:00
Avg. Drawdown Duration 108 days 00:00:00
# Trades 141
Win Rate [%] 48.227
Best Trade [%] 35.591
Worst Trade [%] -13.1921
Avg. Trade [%] 0.490535
Max. Trade Duration 192 days 00:00:00
Avg. Trade Duration 26 days 00:00:00
Profit Factor 1.44673
Expectancy [%] 0.629784
SQN 1.34157
_strategy SmaCross(long_te...
_equity_curve ...
_trades Size Entry...
dtype: object
リターンはlong_term=23, short_term=17のときに**プラス97.9%**となってます。資産が約2倍になったことになりますね。
トレードの結果をグラフにしてみます。
bt.plot()
下記のようにするとヒートマップを表示できます。
plot_heatmaps(heatmap, agg='mean', plot_width=2048, filename='heatmap')
long_term=23, short_term=17のところが、明るい色になってることがわかりますが、ここが最適な組み合わせの場所となります。
最適な組み合わせを探す(2)
上では2〜100日の5日刻みで最適な組み合わせを求めています。今度は250日までの期間を1日刻みで求めてみたいと思います。プログラム的にはほぼ同じで、期間と刻みが違うだけです。私のPCで1時間ちょっとかかりました。
statsの内容は下記の通りです。リターンは**プラス144.6%**で、資産が約2.4倍になったことになります。
Start 2011-01-04 00:00:00
End 2020-12-30 00:00:00
Duration 3648 days 00:00:00
Exposure Time [%] 86.2393
Equity Final [$] 2.44551e+06
Equity Peak [$] 2.44631e+06
Return [%] 144.551
Buy & Hold Return [%] 163.934
Return (Ann.) [%] 9.63845
Volatility (Ann.) [%] 21.03
Sharpe Ratio 0.458318
Sortino Ratio 0.731527
Calmar Ratio 0.309028
Max. Drawdown [%] -31.1895
Avg. Drawdown [%] -4.22459
Max. Drawdown Duration 452 days 00:00:00
Avg. Drawdown Duration 50 days 00:00:00
# Trades 13
Win Rate [%] 69.2308
Best Trade [%] 44.9915
Worst Trade [%] -17.4933
Avg. Trade [%] 7.17498
Max. Trade Duration 629 days 00:00:00
Avg. Trade Duration 243 days 00:00:00
Profit Factor 4.90971
Expectancy [%] 8.24431
SQN 2.07852
_strategy SmaCross(long_te...
_equity_curve ...
_trades Size EntryB...
続いてヒートマップをグラフにします。
右上の方が明るい色になってます。この辺の組み合わせが良いようです。
このヒートマップですが、軸のラベルがつぶれてしまって見えなくなってます。色なども調整したいので、Seabornのheatmapでグラフを作成してみます。
sns.set(font='IPAexGothic')
d = heatmap.reset_index().pivot('short_term', 'long_term', 'SQN')
fig, ax = plt.subplots(figsize=(12, 9))
sns.heatmap(d, square=True, cmap='seismic', center=0, ax=ax)
ax.set_title('移動平均期間の組み合わせによるSQN')
ax.invert_yaxis()
ax.grid()
plt.show()
赤の濃いところが良い組み合わせ、青の濃いところが悪い組み合わせになってます。
最後に
今回はSMAを使用して、長期と短期がどの組み合わせが最適化を求めました。結果、リターンは144%となりました。これは資産が2.4倍になったことになり年利にすると9%です(複利)。
しかしよく考えてみると、2011年当時10000円くらいだった日経平均株価は2020年末頃には26000円くらいになってます。2011年に買って何もしなければ資産が2.6倍(160%の増加)になった計算になります。
この結果だけを見るとSMAを使った戦略はもう一つなのかな、と思ってしまいます。ただし、今回はゴールデンクロスで買ってデッドクロスで売るという単純な戦略です。他の戦略を取れば結果も変わると思います。
今回のソースはGitHubに置いてます。