Takehana Lab

System Trading : MultiCharts, TradingView, Python, R

tidyquantでバックテスト(複数パラメーター最適化)

前回の記事に引き続きバックテストを進めていきます。

takehana13.hateblo.jp

今回は複数のパラメータによる最適化をテストしてみたいと思います。2本のEMAを計算し、それぞれのパラメータを変化させてパフォーマンスを最大化します。

まずtq_mutate()で2本のEMAを計算します。

require(tidyquant)
require(tidyverse)
data(FANG)

term1 <- FANG %>% group_by(symbol) %>% 
  tq_mutate(select = adjusted,
            mutate_fun = EMA, n = 10,
            col_rename = "ema1") %>% 
  tq_mutate(select = adjusted,
            mutate_fun = EMA, n = 20,
            col_rename = "ema2")

計算結果を可視化して確認しておきます。

term1 %>% 
  ggplot(aes(date, adjusted)) +
  geom_line() +
  geom_line(aes(date, ema1), colour = "red") +
  geom_line(aes(date, ema2), colour = "blue") +
  theme_tq() +
  facet_wrap(.~symbol, scale = "free")

f:id:takehana13:20190928162928p:plain

次に前回のテストと同様にストラテジーを関数化します。

strategy_ema_cross <- function(param1, param2){
  term1 <- FANG %>% group_by(symbol) %>% 
    tq_mutate(select = adjusted,
              mutate_fun = EMA, n = param1,
              col_rename = "ema1") %>% 
    tq_mutate(select = adjusted,
              mutate_fun = EMA, n = param2,
              col_rename = "ema2")
  
  term2 <- term1 %>% tq_mutate(select = adjusted,
                               mutate_fun = ROC,
                               col_rename = "roc")
  
  term3 <- term2 %>% mutate(sig = lag(if_else(ema1 > ema2, 1, -1)),
                            ret = roc * sig)
  
  term4 <- term3 %>% drop_na %>% mutate(eq = cumsum(ret)) %>% 
    select(symbol, date, eq)
}

次に最適化用のパラメータセットを用意します。expand.grid()は与えられた変数について総当たりのテーブルを計算します。

param_set <- expand.grid(
  param1 = seq(10, 50, by = 10),
  param2 = seq(20, 60, by = 10)
  )

purrr::pmap()を使ってストラテジーにパラメータセットを適用します。

opt_ret <- param_set %>% pmap(strategy_ema_cross)
opt_ret <- opt_ret %>% set_names(str_c("param", seq_len(25)))
opt_ret <- opt_ret %>% bind_rows(.id = "param")

計算結果を可視化します。

opt_ret %>% filter(symbol == "FB") %>% 
  ggplot(aes(x = date, y = eq, colour = param)) +
  geom_line() +
  theme_tq() +
  geom_hline(yintercept = 0, colour = "darkgray") +
  scale_color_tq()

f:id:takehana13:20190928163117p:plain 結果はかなり見辛くなりますが、明確な傾向が表れました。この傾向が何を意味するのか探っていきます。

確認のためパラメータごとのパフォーマンスを可視化します。

opt_ret %>% filter(symbol == "FB") %>% 
  ggplot(aes(x = date, y = eq, fill = param)) +
  geom_area() +
  theme_tq() +
  scale_fill_tq() +
  facet_wrap(.~param)

f:id:takehana13:20190928163236p:plain 極端にパフォーマンスの悪いパラメータが複数あることが分かります。

分析のためパラメータとパフォーマンスのデータをsummariesで集約して単純化します。

perf_tbl <- opt_ret %>% filter(symbol == "FB") %>% 
  group_by(param) %>% 
  summarise(perf = sum(eq))

次に使用したパラメータセットの変数XとYの差を示すデータを作成します。

param_tbl <- param_set %>%
  mutate(
    diff = param2 - param1,
    param = as.factor(str_c("param", seq_len(25)))
    )

両者を比較検討してみましょう。評価しやすいように結果をランキング形式で並べ替えます。

まずパラメータごとのパフォーマンスを確認します。

perf_tbl %>% mutate(
  param = as.factor(param),
  param = fct_reorder(param, perf)
  ) %>% 
  ggplot(aes(param, perf, fill = param)) +
  geom_bar(stat = "identity") +
  coord_flip() +
  theme_tq() +
  scale_fill_tq()

f:id:takehana13:20190928215226p:plain

次にパラメータごとの2つの値の差を確認します。

param_tbl %>% mutate(lab = fct_reorder(lab, diff)) %>% 
  ggplot(aes(lab, diff, fill = lab)) +
  geom_bar(stat = "identity") +
  coord_flip() +
  theme_tq() +
  scale_fill_tq()

f:id:takehana13:20190928215336p:plain

param_tblの中には負の値を取るものがありますが、これはストラテジーのシグナルを反転させることを意味します。つまり「短期MAが長期MAを上回ったらショート」というシグナルです。

さらにパラメータ値の差とパフォーマンスの関係を可視化します。

left_join(perf_tbl, param_tbl, .by = param) %>% 
  select(perf, diff, param) %>% 
  ggplot(aes(diff, perf, fill = param)) + 
  geom_bar(colour = "black", stat = "identity", position = "dodge") +
  theme_tq() +
  scale_fill_tq()

f:id:takehana13:20190929121220p:plain

上記のプロットから次のことが読み取れます。

  1. 必ずしも2つのパラメータの差の大きさ=リターンとはならない。
  2. パフォーマンスの高いパラメータセットに差の値が負となるものはない
  3. パラメータの差が0となるセットは例外なくリターンが悪化

この結果から対象の銘柄におけるストラテジーの挙動について「2つのパラメータ値の差が正の値を取り、推定区間内で中間的な値を取るパラメータセットについてリターンが高い傾向にある」ことが分かります。

またパラメータ値の差が負の値を取る、または値が0のものについてはパフォーマンスが悪化しています。

グラフは明かに与えられたパラメータ外のファクターがパフォーマンスに影響を及ぼしていることを示唆しているため、さらに詳細な分析が必要といえます。


パラメータ間に差がないものについて極端にパフォーマンスが悪くなっているのは、このストラテジーのコードだと条件が偽となる場合常にショートしていることになるからです。

本来このようなパラメータについては除外して考えるべきですが、今回はテストの意味も兼ねてそのまま計算しました。