2017/5

このサイトはRubyでバックテストツールを作成し様々なストラテジーを検証することを目的としています。前回から「トレードシステムはどう作ればよいのか①」ジョージ・プルートの検証を開始しています。DBSを適用して移動平均線の長さをボラに応じて変化させるシステムについて検証を行いました。

この著書には他にいくつかの移動平均線の長さを動的に変化させるシステムについて触れられていますので、同じように検証を行っていきたいと思います。

今回紹介するのはペリーカウフマンが1995年に「Smarter Trading」にて紹介したとされるKAMA(カウフマン・アダプティブ・ムービング・アベレージ)を用いたシステムです。

では見ていきましょう。

目次

実践

KAMAとは

KAMAにある背景はこのように説明されています。

KAMAは、ノイズの大きな市場はノイズの少ない市場よりもトレンドがゆっくりと形成されなければならないことをベースとするものである。つまり、ノイズの大きな(ボラティリティの高い)市場では遅延の大きな移動平均を使い、トレンド市場では遅延の小さな移動平均を使用すべきということである。

ジョージ・プルート『トレードシステムはどう作ればよいのか①』(PanRolling、2013年)

基本的にはボラが増えてるときは移動平均の長さを長くし、減ってるときは短くする。というルールですので思想は前回と変わっていませんね。

KAMAは私に馴染みのないインジケータなので、まずはそちらを説明します。 KAMAを計算するには3つの定数を用います。

  • ERを計算するための期間
  • FastEMA〃の平方根
  • SlowEMA〃の平方根

ここでは上記の定数を上から(10,2,30)として説明します。ERの計算はこのように行います。

Change = (終値 - 10日前終値).Abs
Vol = Sum((終値 - 前日終値).Abs,10日)
ER = Change / Vol

したがって、ERは0〜1の間の数値であり、10日間の間終値が上昇し続けた場合ERは1となり、10日前と今日の終値が一致する場合は0となります。

KAMAは指数移動平均の平滑化定数をこのERによって変化させるものです。したがって平滑化定数をαとするとKAMAは次のようにかけます。

KAMA = 前日KAMA + α (終値 - 前日KAMA)

そしてαは次のように計算します。

F = 2 / (FastEMA〃の平方根 + 1)
S = 2 / (SlowEMA〃の平方根 + 1)
α = (ER (F - S) + S) ** 2

式を眺めると、ERが0のときは遅行指数移動平均の平滑化定数の2乗になり、ERが1のときは先行移動平均の平滑化定数の2乗になります。ですからαはERの値によって、遅行指数移動平均の平滑化定数と先行指数移動平均の定数を変動する値となることがわかります。

となると、ボラの大きい場合はSlow、小さい場合はFastの指数移動平均を使っていく指標だということがわかりますね。(10,2,30)のKAMAの場合は4日から900日の指数移動平均の値を変動する指数となります。

売買ルールの説明

このKAMAを用いて移動平均のクロスによって売買をするシステムが紹介されているのですが、著書によると、先行移動平均としてKAMA(10,3,6)を遅行移動平均としてLAMA(10,3,6)を用いたとあります。このLAMAはKAMAと同じようにαを求め更に0.5をそれに乗じます。つまり

LAMA = 前日LAMA + 0.5 α (KAMA - 前日LAMA)

となります。KAMAの2倍の長さの遅行指数移動平均みたいなものですかね。あとは普段通りのルールです。

ストラテジーの構築

コードはこのようになります。

class KamaLama

    attr_accessor :length, :s_len, :l_len, :n

    def initialize
      @length = 37
      @l_len = 6
      @s_len = 3
      @kama = nil
      @lama = nil
      @m = 10
    end

    def set_env(soks, env)
      env[:closes] = Soks.parse(soks[0..-2],:close)
      env[:open] = soks[-1].open
    end

    def setup
      @kama = nil
      @lama = nil
      @pkama = nil
      @plama = nil
    end

    def decide(env)
      code = env[:code]
      open = env[:open]
      date = env[:date]
      position = env[:position]
      closes = env[:closes]

      @kama, @lama = calc_ave(closes)

      if position
        if @kama > @lama and position.sell?
          return Action::Buy.new(code, date, open, 2)
        elsif @kama < @lama and position.buy?
          return Action::Sell.new(code, date, open, 2)
        else
          return Action::None.new(code,open)
        end
      end

      if @kama > @lama
        return Action::Buy.new(code, date, open, 1)
      elsif @kama < @lama
        return Action::Sell.new(code, date, open, 1)
      else
        return Action::None.new(code,open)
      end
    end

    def calc_ave(closes)
      @pkama, @plama = @kama, @lama
      er = (closes[-1] - closes[-@m]).abs / closes[-@m-1..-1].diff.abs.sum
      alpha = (er*(2.0/(@s_len+1) - 2.0/(@l_len+1)) + 2.0/(@l_len+1)) ** 2
      if not @kama or not @lama
        @kama = closes[-@l_len**2..-1].ave(@l_len**2)[-1]
        @lama = @kama
      else
        @kama = @kama + alpha * (closes[-1] - @kama)
        @lama = @lama + 0.5 * alpha * (@kama - @lama)
      end
      [@kama, @lama]
    end
  end

チャートは6日と36日の移動平均を使っています。次のように騙しを消せてる局面もありましたが、大方、SMAと同じように騙しで損してるよに見えます。KAMAは2乗することで、移動平均の長さをダイナミックに変化させることに意味があって、36日ってのは短いのかもしれません。

profit_histgram

profit_histgram

N日検定

N日検定をかけます。

ここでお詫びです。クロスでの仕掛けを利用したシステムのN日検定のコードに不備がありました。検定以外の場合は今日だけを見て先行線が遅行線の上にあるか下にあるかを判定すれば十分ですが、N日で仕切った場合、再エントリーは前日と今日の指標を使ってクロスの判定を行う必要がありました。追って過去記事にも修正を加えたいと思います。

Code 5 10 15 20 30 50
I201 0.57 0.42 0.46 0.42 0.59 0.56
I202 0.46 0.49 0.40 0.36 0.43 0.47
I203 0.56 0.52 0.45 0.53 0.55 0.55
I204 0.39 0.47 0.50 0.37 0.40 0.41
I205 0.45 0.43 0.48 0.50 0.50 0.46
I206 0.54 0.55 0.52 0.48 0.51 0.53
I207 0.58 0.49 0.52 0.53 0.53 0.40
I208 0.56 0.54 0.46 0.48 0.50 0.34
I209 0.45 0.46 0.43 0.49 0.61 0.52
I210 0.42 0.45 0.47 0.44 0.50 0.52
I211 0.55 0.50 0.56 0.55 0.44 0.54
I212 0.40 0.46 0.48 0.45 0.44 0.52
I213 0.49 0.47 0.66 0.58 0.46 0.57
I214 0.54 0.39 0.46 0.51 0.50 0.50
I215 0.46 0.48 0.47 0.47 0.47 0.55
I216 0.50 0.45 0.46 0.56 0.46 0.44
I217 0.52 0.56 0.51 0.43 0.54 0.48
I218 0.55 0.45 0.45 0.48 0.55 0.63
I219 0.52 0.50 0.49 0.46 0.45 0.54
I220 0.49 0.48 0.51 0.43 0.47 0.52
I221 0.38 0.34 0.37 0.51 0.32 0.56
I222 0.55 0.46 0.51 0.43 0.47 0.46
I223 0.43 0.44 0.36 0.43 0.32 0.30
I224 0.51 0.52 0.42 0.42 0.44 0.50
I225 0.45 0.46 0.43 0.44 0.45 0.48
I226 0.37 0.44 0.49 0.46 0.50 0.52
I227 0.49 0.57 0.56 0.39 0.47 0.59
I228 0.57 0.57 0.54 0.58 0.50 0.55
I229 0.48 0.61 0.58 0.58 0.51 0.54
I230 0.52 0.47 0.54 0.58 0.41 0.52
I231 0.48 0.57 0.61 0.55 0.57 0.65
I232 0.43 0.54 0.50 0.54 0.54 0.48
I233 0.39 0.38 0.44 0.48 0.61 0.55
平均 0.49 0.48 0.49 0.48 0.48 0.51

修正を加えて見れば、勝率は軒並み5割を切る結果になりました。ちなみにランダムで仕掛けを行った場合はきれいに5割あたりに落ち着きます。

銘柄間の分散測定

銘柄間で分散を計算します。前回と同じようにSMAとの比較を行います。

  • KAMA
Code Income Trades Win Pf Average DD
I201 4.65 78 28.21 1.03 0.06 -48.37
I202 -179.41 92 27.17 0.54 -1.95 -75.64
I203 -20.29 75 29.33 0.9 -0.27 -47.49
I204 -107.83 86 19.77 0.54 -1.25 -44.28
I205 -18.81 72 33.33 0.91 -0.26 -38.99
I206 -13.95 78 35.9 0.93 -0.18 -50.21
I207 44.76 65 32.31 1.25 0.69 -34.88
I208 8.01 74 33.78 1.06 0.11 -25.89
I209 -16.94 69 33.33 0.93 -0.25 -76.23
I210 -142.09 94 19.15 0.61 -1.51 -120.48
I211 92.4 55 30.91 1.55 1.68 -27.09
I212 -6.16 78 29.49 0.98 -0.08 -48.84
I213 33.61 61 31.15 1.15 0.55 -46.54
I214 -14.93 74 28.38 0.94 -0.2 -45.42
I215 11.94 71 28.17 1.05 0.17 -45.08
I216 27.79 70 32.86 1.13 0.4 -32.07
I217 79.23 74 32.43 1.39 1.07 -37.49
I218 -9.55 73 28.77 0.96 -0.13 -30.36
I219 -22.96 73 30.14 0.91 -0.31 -44.44
I220 47.13 72 33.33 1.26 0.65 -21.04
I221 -102.07 88 18.18 0.58 -1.16 -100.09
I222 100.2 66 28.79 1.39 1.52 -52.77
I223 -14.11 67 35.82 0.93 -0.21 -47.04
I224 -74.97 83 24.1 0.67 -0.9 -53.52
I225 -107.15 82 21.95 0.56 -1.31 -66.1
I226 -113.88 83 22.89 0.63 -1.37 -63.51
I227 0.24 71 29.58 1.0 0.0 -29.39
I228 -28.49 73 28.77 0.89 -0.39 -65.64
I229 202.88 62 38.71 1.79 3.27 -58.19
I230 -7.13 63 33.33 0.97 -0.11 -65.32
I231 83.78 59 40.68 1.4 1.42 -36.79
I232 27.17 65 29.23 1.11 0.42 -41.87
I233 18.6 71 30.99 1.13 0.26 -26.99
平均 -6.6 73.0 29.7 1.0 0.0 -49.9
偏差 75.22 9.0 5.14 0.29 1.02 20.83
  • SMA 6×36
Code Income Trades Win Pf Average DD
平均 -15.3 97.0 32.9 1.0 -0.1 -41.8
偏差 80.04 8.94 4.31 0.26 0.82 13.54

対して優位があるようには思えないですね。フィルターを使って騙しを消したほうがマシです。

損益分析

一応最も収益の良かったI222の損益分析をしてみましょう。

======================================================
net income:               100.20070429413443
profit | loss:            357.80826860451316    | -257.6075643103787
average:                  1.5181924893050671
pf:                       1.4
max profit | max loss:    68.22095385508365     | -16.4881192106323
trades | wins | looses:   66     | 19     | 47
wins{%}                   28.8
max series of wins:       3
max series of looses:     9
average span{win}:        82
average span{loose}:      19
max drow down:            -52.768023978241665
bunkrupt:                 0.0
======================================================
  • 損益曲線

profit_histgram

  • 損益ヒストグラム

profit_histgram

  • 月次損益

profit_histgram

だーめですね。

まとめ

2007年〜2017年の期間において、33業種別株価指数を用いて、KAMAを用いたシステムを検証しましたが、あまり良い結果は得られませんでした。

使えそうにありません。

このサイトはRubyでバックテストツールを作成し様々なストラテジーを検証することを目的としています。前回までは「売買システム入門」を読んでいましたが、今回からは「トレードシステムはどう作ればよいのか①」ジョージ・プルートを読み進めていきたいと思います。この本には具体的なストラテジーが紹介されていますので、それぞれ検証を行っていきましょう。

はじめに検証していくのはDBS(ダイナミック・ブレイクアウト・システム)を移動平均線による交差法に適用したシステムです。基本的にはSMAですが、ボラリティによって移動平均線の長さを変えていくシステムです。検証方法はこれまで同様に「売買システム入門」にて紹介されていた方法にて行おうと思います。

では見ていきましょう。

目次

実践

売買ルールの説明

● ボラティリティを標準偏差で表す>

1.昨日の終値から始まる過去20 日の終値の標準編差を計算する。

2.今日の終値から始まる過去20 日の終値の標準偏差を計算する。

3.昨日の終値から始まる過去30 日の終値の標準偏差を計算する。

4.今日の終値から始まる過去30 日の終値の標準偏差を計算する。

5.昨日と今日のボラティリティのデルタ(変動)を計算する(デルタ=[今日のボラティリティ-昨日のボラティリティ]÷ 今日のボラティリティ)。長期のボラティリティは標本集団が大きいため、短期のボラティリティに比べるとそれほど頻繁には変化しない。

● 2つの移動平均線を計算する

1.短期移動平均線は12 日からスタートする。

2.長期移動平均線は41 日からスタートする。

● 2つの移動平均の交差でトレードする

1.短期移動平均線が長期移動平均線を下から上に交差したら、翌日の寄り付きで買う。

2.短期移動平均線が長期移動平均線を上から下に交差したら、翌日の寄り付きで売る。

● 移動平均線の長さを調整する

1.現在の短期移動平均線の長さに(1+デルタ)を掛ける。

2.現在の長期移動平均線の長さに(1+デルタ)を掛ける。

3.短期移動平均線が長期移動平均線を下回り、2つの移動平均線を利益を生む範囲内に維持するために、次の制約を設ける必要がある。

● 短期移動平均線の長さ 最小で12 日 最大で23 日

● 長期移動平均線の長さ 最小で41 日 最大で50 日

● 移動平均線を新しい長さで計算しなおす

ジョージ・プルート『トレードシステムはどう作ればよいのか①』(PanRolling、2013年)

ボラが増えてるときは移動平均の長さを長くし、減ってるときは短くする。というルールでです。

ストラテジーの構築

コードはこのようになります。

class SmaDbs

    attr_accessor :length

    def initialize
      @length = 51
      @l_len = 41
      @s_len = 12
    end

    def set_env(soks, env)
      env[:closes] = Soks.parse(soks[0..-2],:close)
      env[:open] = soks[-1].open
    end

    def setup
      @l_len = 41
      @s_len = 12
    end

    def decide(env)
      code = env[:code]
      open = env[:open]
      date = env[:date]
      position = env[:position]
      closes = env[:closes]

      s_ave, l_ave = calc_ave(closes)

      if position
        if (s_ave[-1] > l_ave[-1]) and position.sell?
          return Action::Buy.new(code, date, open, 1)
        elsif (s_ave[-1] < l_ave[-1]) and position.buy?
          return Action::Sell.new(code, date, open, 1)
        else
          return Action::None.new(code,open)
        end
      end

      if s_ave[-1] > l_ave[-1]
        return Action::Buy.new(code, date, open, 1)
      elsif s_ave[-1] < l_ave[-1]
        return Action::Sell.new(code, date, open, 1)
      else
        return Action::None.new(code,open)
      end
    end

    def calc_ave(closes)
      l_dev = closes[-31..-1].dev(30)
      s_dev = closes[-21..-1].dev(20)

      l_delta = (l_dev[-1] - l_dev[-2]) / l_dev[-1]
      s_delta = (s_dev[-1] - s_dev[-2]) / s_dev[-1]

      @l_len *= (1 + l_delta)
      @l_len = [@l_len, 41].max
      @l_len = [@l_len, 50].min

      @s_len *= (1 + s_delta)
      @s_len = [@s_len, 12].max
      @s_len = [@s_len, 23].min

      l_ave = closes[-@l_len.to_i..-1].ave(@l_len.to_i)
      s_ave = closes[-@s_len.to_i..-1].ave(@s_len.to_i)
      [s_ave, l_ave]
    end
  end

チャートは23日と51日の移動平均を使っています。クロスの前に注文が入ってることは確認できるので、おおよそコードはこれで良いでしょう。なぜに51日かと言いますと、間違えたからです。

profit_histgram

N日検定

N日検定をかけます。勝率平均55%なんてのはまだ見たことがありませんがどうなりますかね。データは33業種別株価指数の2007年〜2017年を用います。そもそもトレンドフォロー系は勝率は低いもので、こんなもんなのかもしれませんね。

Code 5 10 15 20 30 50
I201 0.52 0.48 0.50 0.50 0.48 0.48
I202 0.49 0.51 0.51 0.53 0.50 0.42
I203 0.49 0.55 0.53 0.50 0.50 0.48
I204 0.53 0.48 0.54 0.44 0.46 0.52
I205 0.50 0.52 0.54 0.54 0.46 0.48
I206 0.49 0.48 0.50 0.47 0.53 0.46
I207 0.51 0.54 0.55 0.54 0.55 0.46
I208 0.49 0.47 0.51 0.48 0.49 0.42
I209 0.52 0.50 0.51 0.59 0.49 0.33
I210 0.48 0.47 0.47 0.48 0.43 0.42
I211 0.53 0.49 0.53 0.52 0.43 0.50
I212 0.52 0.45 0.47 0.46 0.39 0.44
I213 0.51 0.48 0.50 0.48 0.54 0.44
I214 0.52 0.55 0.54 0.51 0.49 0.44
I215 0.51 0.51 0.51 0.50 0.53 0.38
I216 0.50 0.54 0.51 0.47 0.51 0.50
I217 0.48 0.46 0.48 0.48 0.54 0.44
I218 0.48 0.49 0.47 0.40 0.41 0.48
I219 0.49 0.49 0.50 0.55 0.58 0.52
I220 0.49 0.47 0.47 0.55 0.51 0.60
I221 0.53 0.54 0.57 0.47 0.41 0.52
I222 0.49 0.49 0.50 0.51 0.50 0.60
I223 0.49 0.52 0.53 0.55 0.53 0.60
I224 0.44 0.43 0.45 0.45 0.43 0.44
I225 0.49 0.50 0.43 0.42 0.55 0.46
I226 0.50 0.50 0.49 0.50 0.53 0.42
I227 0.53 0.49 0.50 0.51 0.50 0.44
I228 0.49 0.46 0.45 0.47 0.45 0.29
I229 0.49 0.47 0.49 0.48 0.45 0.42
I230 0.50 0.52 0.56 0.51 0.49 0.42
I231 0.48 0.51 0.48 0.46 0.51 0.40
I232 0.47 0.49 0.46 0.48 0.50 0.42
I233 0.53 0.53 0.48 0.53 0.49 0.48
平均 0.50 0.50 0.50 0.50 0.49 0.46

銘柄間の分散測定

銘柄間で分散を計算します。分散が小さければより堅牢なシステムだと言えるとのことですが、まぁどのあたりかって明確な基準は知らないので、他のシステムとの比較が必要になるのでしょうね。この場合だとSMAですか。

  • SMA DBS
Code Income Trades Win Pf Average DD
I201 25.87 77 38.96 1.15 0.34 -38.24
I202 40.87 72 44.44 1.16 0.57 -40.17
I203 -32.74 64 35.94 0.84 -0.51 -33.29
I204 -66.5 90 33.33 0.67 -0.74 -33.17
I205 -13.04 77 33.77 0.93 -0.17 -37.54
I206 -160.72 73 32.88 0.49 -2.2 -57.85
I207 4.35 74 31.08 1.02 0.06 -43.01
I208 -71.15 79 24.05 0.63 -0.9 -32.1
I209 51.36 62 41.94 1.25 0.83 -58.35
I210 -23.59 78 33.33 0.92 -0.3 -95.9
I211 23.07 66 33.33 1.1 0.35 -38.98
I212 5.27 73 43.84 1.02 0.07 -54.05
I213 40.23 63 38.1 1.17 0.64 -48.16
I214 63.09 58 37.93 1.4 1.09 -37.64
I215 35.82 62 29.03 1.15 0.58 -35.47
I216 -10.36 72 37.5 0.96 -0.14 -43.33
I217 4.75 73 31.51 1.02 0.07 -41.97
I218 -0.01 70 37.14 1.0 0.0 -29.44
I219 -36.41 71 42.25 0.86 -0.51 -47.68
I220 27.68 75 40.0 1.15 0.37 -41.04
I221 2.61 67 34.33 1.02 0.04 -31.63
I222 120.67 64 42.19 1.53 1.89 -42.13
I223 -17.78 67 35.82 0.91 -0.27 -35.74
I224 -128.11 87 27.59 0.54 -1.47 -65.44
I225 -33.1 75 32.0 0.81 -0.44 -26.09
I226 -69.11 66 33.33 0.73 -1.05 -45.58
I227 -11.65 73 39.73 0.93 -0.16 -33.85
I228 -30.5 76 36.84 0.89 -0.4 -51.67
I229 34.2 66 30.3 1.09 0.52 -69.08
I230 -8.02 62 35.48 0.97 -0.13 -48.11
I231 -50.11 66 37.88 0.83 -0.76 -53.68
I232 -28.32 71 32.39 0.91 -0.4 -65.12
I233 39.9 71 42.25 1.29 0.56 -31.1
平均 -8.2 70.0 35.8 1.0 -0.1 -45.0
偏差 53.35 7.0 4.77 0.22 0.75 14.07
  • SMA 23×51
Code Income Trades Win Pf Average DD
平均 2.1 54.0 35.5 1.0 0.1 -41.6
偏差 58.73 5.48 5.59 0.3 1.06 13.11

23日と51日のゴールデンクロス、デッドクロスでの売買を行ったほうが成績は微妙に良さそうですね。

損益分析

一応最も収益の良かったI214の損益分析をしてみましょう。

======================================================
net income:               63.089022560403215
profit | loss:            221.25246116662524    | -158.16343860622192
average:                  1.087741768282814
pf:                       1.4
max profit | max loss:    37.25461513706445     | -10.241653639739926
trades | wins | looses:   58     | 22     | 36
wins{%}                   37.9
max series of wins:       4
max series of looses:     8
average span{win}:        76
average span{loose}:      20
max drow down:            -37.636184966997675
bunkrupt:                 0.0
======================================================
  • 損益曲線

profit_histgram

  • 損益ヒストグラム

profit_histgram

  • 月次損益

profit_histgram

だめですね。

まとめ

2007年〜2017年の期間において、33業種別株価指数を用いて、DBSを適用したSMAシステムを検証しましたが、DBSを適用しないほうが僅かに収益は出るという結果に終わりました。

使えそうにはありません。

ただ、偏差の増減によって長さを変えてみようって考えは意味があるのかはさておき面白かったです。本当に僅かな可能性の中から試行錯誤してシステムを作っていく上で、なんか試してみようってのは必要ですよね。

「売買システム入門」を読み進めてきましたが、今回で最後としたいと思います。最後に取り上げるのはデータスクランブル、疑似データを用いた検証方法についてです。

疑似データは過去の日足をランダムピックしながら作るものです。まず作成方法について説明します。次に33業種別株価指数の幅広い銘柄において十分に有効だと思われる自作のシステムを紹介します。そして疑似データを用いてバックテストを行った場合にどうなるかってのを調べていきたいと思います。

早速参りましょう。

目次

実践

目次1

疑似データは下記の手順で作るとあります。

2本の日足を隣り合わせにする。そして1本目の日足の終値を基準として、2本目の日足の始値(0)、高値(H)、安値(L)、終値(C)との関係について調べる。この関係は以下のように示される。

delta O = O - C[1],

delta H = H - C[1],

delta L = L - C[1],

delta C = C - C[1]. 

ここで表れている[1]の記号は前日を意味する。

新たな日足は以下のように前日終値から導き出される(新たに作成した擬似データにはSynという接頭語を使っている)。

Syn - Close = Close[1] + delta C,

Syn - High = Close[1] + delta H,

Syn - Low = Close[1] + delta L,

Syn - Open = Close[1] + delta O.

トゥーシャー・シャンデ『売買システム入門』(PanRolling、2000年)

これだと高い時の株価と安い時の株価で価格差が異なることが考慮できてないですね。せめて前日比、または対数を取るべきでだと思うので、ここでは前日比を用いて疑似データの作成をしていきます。ただしデータは少数になるので、ティックを用いたストラテジーを検証することはできなくなります。

計算は次のようになります。初値のみ表示しますが、他についても同様です。

  • delta O = O / C[1]
  • Syn - O = Close[1] * delta O

コードは次のようになります。後半はチャートをプロットするためのコードです。

Bundler.require
include Kabu

n = 3000
com = Company.find_by_code 'I201'
file = File.expand_path "../../../data/strategy1-5/chart/#{com.code}.jpeg", File.dirname(__FILE__)

soks = com.soks
diffs = Soks.new
shuffled = Soks.new

soks.each_cons(2) do |prev,curnt|
  pc = prev.close
  diffs << [curnt.open/pc, curnt.high/pc, curnt.low/pc, curnt.close/pc]
end

shuffled << soks[-1]
(n-1).times do
  i = Random.rand(soks.length-1)
  pc = shuffled[-1].close
  s = Sok.new
  s.open = diffs[i][0] * pc
  s.high = diffs[i][1] * pc
  s.low = diffs[i][2] * pc
  s.close = diffs[i][3] * pc
  shuffled << s
end

values = Kabu::Soks.parse(shuffled,:open,:high,:low,:close)
up_stick, down_stick = values.split_up_and_down_sticks

Numo.gnuplot do
  reset
  set terminal: 'jpeg'
  set output:  file
  set grid: true
  plot [n.times.to_a, *up_stick.y, with: :candlesticks, lt: 6, notitle: true],
    [n.times.to_a, *down_stick.y, with: :candlesticks, lt: 7, notitle: true]
end

描画したチャートはこんな感じになりました。なかなかそれっぽいチャートが仕上がります。

profit_histgram

MMSシステムのパフォーマンス

著書のシステムはあまりパフォーマンスがよろしくないので、33業種別株価指数の多くの銘柄で有効だと思われるシステムを用意しました。ルールはこんな感じになります。

バグですな。。。Gainの取得が当日終値を使用しているため、未来のデータを用いています。2017/5/31追記

  • 仕掛け
    • 終値の前日比の65日偏差を超える前日比が発生した翌日の初値
  • ロスカット
    • 終値が前日比-1%となった翌日の初値
  • 仕切り
    1. 終値で利益が20%出た翌日の初値で仕切り
    2. 終値で利益が10%出ていてかつ仕掛けのルールを満たした場合翌日の初値でドテン
    3. 終値で利益が2%出ていてかつ25日ボリバン2.0を抜けていた翌日の初値で仕切り

この辺の数字は比較的適当に選んだ数字で最適化は言うほど行っておりません。これとは別のいくつかの仕掛けをN日検定にかけてみましたが、平均で勝率55%を達成するという優位な結果を得ることができなかったため、仕掛けをわやくちゃすることを諦めました。たまたま持ってしまったポジションが悪けりゃ即切って、良ければ出来る限り利益が出ているときに切る、そんな感じに動くシステムです。

コードはこのようになります。

module Kabu
  class TestStrategy

    attr_accessor :length, :n

    def initialize
      @length = 67
    end

    def set_env(soks, env)
      env[:soks] = soks
    end

    def setup
    end

    def decide(env)
      soks = env[:soks]
      code = env[:code]
      date = env[:date]
      position = env[:position]
      closes = Soks.parse(soks,:close)

      log = closes[-67..-2].log
      ave,btm,top,dev = log.bol(65,1)
      if not position.nil?

        gain = position.gain(soks[-1].close,1)

        if gain > 20 or gain < -1
          if  position.sell?
            return Action::Buy.new(code,date,soks[-1].open,1)
          elsif position.buy?
            return Action::Sell.new(code,date,soks[-1].open,1)
          end
        end

        if gain > 10
          if log[-1] < btm[-1] and position.sell?
            return Action::Buy.new(code,date,soks[-1].open,2)
          elsif log[-1] > top[-1] and position.buy?
            return Action::Sell.new(code,date,soks[-1].open,2)
          end
        end

        if gain > 2
          bol = closes[-26..-2].bol(25,2.0)

          if bol[1][-1] > closes[-2] and position.sell?
            return Action::Buy.new(code,date,soks[-1].open,1)
          elsif bol[2][-1] < closes[-2] and position.buy?
            return Action::Sell.new(code,date,soks[-1].open,1)
          end
        end

        return Action::None.new(code,soks[-1].open)
      end

      if log[-1] < btm[-1]
        return Action::Buy.new(code,date,soks[-1].open,1)
      elsif log[-1] > top[-1]
        return Action::Sell.new(code,date,soks[-1].open,1)
      else
        return Action::None.new(code,soks[-1].open)
      end
    end
  end
end

仕切りのルールを取っ払って、N日検定をした結果はこんな感じです。2007年から2017年の期間で33業種別株価指数を使い検定を行っています。仕切り日のN日には5、10、15、20、30、50を使いました。

Code 5 10 15 20 30 50
I201 0.54 0.53 0.50 0.53 0.61 0.53
I202 0.53 0.51 0.58 0.58 0.44 0.59
I203 0.51 0.49 0.55 0.53 0.56 0.49
I204 0.53 0.49 0.56 0.55 0.54 0.51
I205 0.48 0.49 0.49 0.56 0.51 0.58
I206 0.48 0.47 0.55 0.57 0.51 0.49
I207 0.54 0.54 0.54 0.50 0.54 0.53
I208 0.50 0.49 0.48 0.46 0.42 0.44
I209 0.53 0.51 0.42 0.49 0.59 0.54
I210 0.48 0.48 0.47 0.43 0.36 0.53
I211 0.49 0.53 0.55 0.56 0.54 0.51
I212 0.50 0.54 0.48 0.52 0.45 0.51
I213 0.52 0.48 0.56 0.54 0.53 0.56
I214 0.47 0.46 0.51 0.49 0.41 0.48
I215 0.53 0.46 0.53 0.50 0.48 0.47
I216 0.53 0.48 0.44 0.52 0.48 0.40
I217 0.53 0.55 0.50 0.53 0.58 0.51
I218 0.52 0.48 0.50 0.49 0.52 0.53
I219 0.48 0.54 0.47 0.51 0.52 0.44
I220 0.51 0.52 0.51 0.45 0.58 0.51
I221 0.53 0.49 0.60 0.58 0.56 0.52
I222 0.53 0.47 0.50 0.48 0.57 0.56
I223 0.50 0.52 0.54 0.54 0.50 0.42
I224 0.46 0.49 0.49 0.47 0.58 0.64
I225 0.51 0.56 0.53 0.53 0.50 0.57
I226 0.51 0.48 0.47 0.46 0.58 0.60
I227 0.49 0.43 0.52 0.47 0.46 0.47
I228 0.50 0.54 0.49 0.47 0.64 0.46
I229 0.49 0.49 0.52 0.55 0.49 0.41
I230 0.49 0.46 0.47 0.56 0.49 0.49
I231 0.50 0.51 0.53 0.41 0.46 0.28
I232 0.52 0.47 0.51 0.49 0.42 0.46
I233 0.52 0.58 0.58 0.56 0.54 0.36
平均 0.51 0.50 0.51 0.51 0.51 0.50

55%を超えることが仕掛けルールの優位を示唆する基準となっていますが、どのN日においてもせいぜい51%ですので、このシステムに仕掛けの優位はあまり期待できません。

続いて、同じ期間同じ銘柄を使って銘柄間の平均収益などを見ていきます。ここからは、先に提示したルール、コードを用います。

Code Income Trades Win Pf Average DD
I201 203.12 211 40.76 2.24 0.96 -10.12
I202 364.62 288 39.24 2.06 1.27 -27.58
I203 175.82 235 34.89 1.91 0.75 -14.0
I204 146.41 193 32.64 1.92 0.76 -17.82
I205 152.7 245 34.69 1.77 0.62 -11.16
I206 300.53 259 42.86 2.42 1.16 -14.48
I207 238.65 218 33.94 2.23 1.09 -19.91
I208 172.16 196 38.27 2.16 0.88 -13.72
I209 281.27 243 37.45 2.15 1.16 -13.69
I210 329.29 267 41.2 2.18 1.23 -18.8
I211 301.33 248 37.5 2.3 1.22 -12.26
I212 163.57 317 32.49 1.41 0.52 -23.01
I213 255.28 261 36.4 1.87 0.98 -18.08
I214 166.3 241 32.78 1.72 0.69 -20.83
I215 243.91 252 35.32 1.98 0.97 -30.73
I216 204.72 249 32.13 1.77 0.82 -25.28
I217 143.27 245 33.06 1.49 0.58 -21.18
I218 343.36 239 41.42 2.61 1.44 -13.7
I219 203.49 270 33.7 1.7 0.75 -23.97
I220 256.14 222 42.34 2.43 1.15 -13.14
I221 130.02 203 32.02 1.81 0.64 -17.0
I222 443.44 308 42.53 2.33 1.44 -19.29
I223 210.12 229 37.99 2.15 0.92 -13.18
I224 176.55 236 34.32 1.78 0.75 -18.64
I225 272.41 220 35.91 2.74 1.24 -9.62
I226 248.62 246 37.4 2.02 1.01 -16.97
I227 148.64 220 33.18 1.77 0.68 -11.66
I228 36.1 279 31.9 1.11 0.13 -33.02
I229 246.93 316 37.03 1.57 0.78 -21.25
I230 285.37 305 38.03 1.81 0.94 -24.58
I231 197.37 301 36.21 1.52 0.66 -23.47
I232 345.68 270 43.33 2.15 1.28 -21.66
I233 158.13 194 29.9 2.02 0.82 -21.83
平均 228.6 249.0 36.4 2.0 0.9 -18.7
偏差 81.74 34.47 3.65 0.35 0.29 5.77

業種間平均で200%を超える結果を残せています。年利20%!なかなかですね。最大ドローダウンは平均約20%。1%で損切りのシステムですので、負けがかさむのと、翌日仕切りのためのスリップによる影響が考えられます。

このバックテストは取引コストを勘定していません。成り行き売買で往復2ティック0.1%、SMBCなどの金利だけで取引できるところを選んで金利0.1%程度を見込んでも、0.9%の平均収益がありますので、十分プラスが出ると期待できます。

一番収益を上げたI202 について分析してみます。前回触れた破産確率も表示してみましょう。

======================================================
net income:               364.6153239672717
profit | loss:            707.2600967847432    | -342.6447728174714
average:                  1.2660254304419156
pf:                       2.1
max profit | max loss:    20.01936577099975     | -11.377274254716523
trades | wins | looses:   288     | 113     | 175
wins{%}                   39.2
max series of wins:       7
max series of looses:     10
average span{win}:        10
average span{loose}:      3
max drow down:            -27.58244976567554
bunkrupt:                 0.0
======================================================

破産確率は0%ですね。 素晴らしいじゃないですか。自画自賛。 ちなみにこの破産確率は、資金投下率100%つまり、レバレッジはかけずにトレードを1000回行った間に破産する確率をシミュレーションによって求めています。

  • 損益曲線

profit_histgram

  • 損益ヒストグラム

profit_histgram

  • 月次収益

profit_histgram

一見うなぎのぼりな損益曲線ですが、月次で見てみると4ヶ月もの間マイナスを漂う可能性があると事前に知っていることは有益だと思います。

一番成績の悪かったI228も見てみましょう。

======================================================
net income:               36.09507890151175
profit | loss:            377.1703053400553    | -341.0752264385436
average:                  0.1293730426577482
pf:                       1.1
max profit | max loss:    15.05757858699035     | -12.36803302860513
trades | wins | looses:   279     | 89     | 190
wins{%}                   31.9
max series of wins:       4
max series of looses:     11
average span{win}:        8
average span{loose}:      4
max drow down:            -33.016308846121134
bunkrupt:                 0.0
======================================================

これでも破産確率は0%です。

  • 損益曲線

profit_histgram

  • 損益ヒストグラム

profit_histgram

  • 月次収益

profit_histgram

プラスが出てるとはいえ、しんどいグラフですね。ヒストグラムを見ると、2%〜5%にかけて谷ができているので、ボリバンを使った仕切りルールに銘柄依存があると示唆されます。

さて、このシステムを擬似データを使ってバックテストした場合どうなるか?気になりませんか?

疑似データを用いた検証

著書には、疑似データはトレーダーの心理を反映していないため、あくまで平均がどうなるかが重要だとあります。

疑似データ約60年分(12000本)を作成しバックテストを行い、10回それを行った時の平均を求めてみましょう。オリジナルのデータにはI201の2007年から2017年のものを使用します。

結果は次のようになりました。

Code Income Trades Win Pf Average DD
0 910.27 1020 36.67 2.19 0.89 -16.96
1 952.62 1073 35.88 2.18 0.89 -14.27
2 1060.83 1064 38.63 2.38 1.0 -15.14
3 1125.88 1079 38.37 2.43 1.04 -13.1
4 1086.83 1038 35.55 2.4 1.05 -16.1
5 865.8 1079 36.14 2.06 0.8 -15.42
6 1179.46 1080 38.7 2.48 1.09 -16.7
7 1172.92 1032 39.24 2.59 1.14 -14.6
8 963.13 1084 37.18 2.14 0.89 -20.25
9 968.94 1016 37.5 2.26 0.95 -23.36
平均 1028.7 1056.0 37.4 2.3 1.0 -16.6
偏差 105.64 25.61 1.24 0.16 0.1 2.91

これは十分に期待できる結果じゃないでしょうか?コードに間違いがあるのじゃないかと不安になります。

まとめ

疑似データを作成して、バックテストを行う方法について説明しました。実際に期待できるシステムを用いてバックテストを行い、十分に期待が出来ることについて確認を行いました。実際にはシステムに穴があることを確認するための手順ですが、このシステムは十分に堅牢であると言えると思います。

私はかなり長い間こういったシストレ本に懐疑的でした。ですが、実際に丁寧に読んでみるとかなり収穫があることがわかりました。10年前に出会いたかったです。

売買システム入門を読み進めています。この本に挙げられている検証方法でシステムを検証していくと、一体システムトレードのどこに優位性があるのか訝しくなります。

では、この本が読む価値がないかと問われると、そうは思いません。なぜならばシステムの評価方法について、いくつかの方法を提示してくれるからです。タイトルは考え方→作成→評価となっていて、たしかに信条を説明しモデルを提示しているのですが、正直私はその辺は魅力を感じません。評価においてこの本は読む価値があると思います。実際に、この本を読み進めるだけで、私はシステムに対する評価方法をいくつか手に入れることができました。これは大きいと思います。

今回は破産確率という点でシステムを評価しましょう。破産確率はバルサラの破産確率が有名ですが、ここではシミュレーションを行って、1000回の試行で破産する確率を求め、破産確率に対するレバレッジの効果について調べていきます。

目次

実践

破産確率を求めるには

著書にはノーザー・バルサラ氏の著書を参考にし、シミュレーションを行ったとあります。このバルサラの破産確率というのは、至るブログで紹介されていて、その上ほとんどのブログの表は間違いだなどと指摘されていたりなど、何が正しいのか原著を読むしかない状況となっています。しかし、この原著が高いんですね。みんな読まないので好き放題出来るのでしょう。地雷はスルーします。

シャンデ氏の著書では1000回の試行で破産する確率をシミュレーションによって求めたとありますので、私もそうしたいと思います。用いる変数は著書と同じように下記になります。

  • ペイオフ比率(平均利益率 / 平均損失率)
  • 勝率
  • 許容リスク(投下資金比率 * 平均損失率)

シミュレーション方法

シミュレーションの流れはこんなものです。

  1. 乱数を用いて勝敗を決定
  2. 勝った場合: 許容リスク*ペイオフ比率を取得
  3. 負けた場合: 許容リスクを失う
  4. 1000回の試行までに全資金を失った場合破産

コードはこのようになります。

    def simulate
      bunkrupt_times = 0
      n.times do
        capital = 1
        bunkrupt = false
        span.times do
          gain = (Random.rand <= @win ? @pf * @risk  : - @risk)
          capital += gain
          if capital < 0
            bunkrupt = true
            break
          end
        end
        bunkrupt_times += 1 if bunkrupt
      end
      bunkrupt_times.to_f / n * 100
    end

破産確率

これを各々のパラメータについて1000回繰り返して破産した確率を求めます。 1%、1.5%、2%の許容リスクの場合、結果はこのようになります。

  • 1%
  1.0 1.5 2.0 2.5 3.0
25 100.0 100.0 100.0 76.0 5.1
30 100.0 100.0 61.5 0.3 0.0
35 100.0 78.7 0.2 0.0 0.0
40 100.0 0.7 0.0 0.0 0.0
45 55.8 0.0 0.0 0.0 0.0
50 0.2 0.0 0.0 0.0 0.0
  • 1.5%
  1.0 1.5 2.0 2.5 3.0
25 100.0 100.0 100.0 93.3 23.7
30 100.0 100.0 84.2 4.5 0.0
35 100.0 96.5 1.7 0.0 0.0
40 100.0 8.8 0.0 0.0 0.0
45 88.3 0.0 0.0 0.0 0.0
50 3.3 0.0 0.0 0.0 0.0
  • 2%
  1.0 1.5 2.0 2.5 3.0
25 100.0 100.0 100.0 96.4 35.3
30 100.0 100.0 92.7 9.0 0.1
35 100.0 99.4 4.9 0.0 0.0
40 100.0 18.7 0.0 0.0 0.0
45 96.4 0.0 0.0 0.0 0.0
50 10.7 0.0 0.0 0.0 0.0

シャンデ氏の著書と若干のブレはありますが、いい値を吐き出しているようです。

許容リスクを上げたということは、例えばあなたが平均損失率1%のシステムを使っていたとすると、レバレッジをかけて1.5倍、2倍の資金を投入したことに対応します。こうした時、よほど優秀なシステムでなければ多くの場合破産する可能性があることがわかると思います。

なお、ここで示している破産確率は、提示した方法で計算した場合の破産確率であり、また十分に収束していないため、周りに溢れている確率表とは一致しません。

まとめ

破産確率をシミュレーションによって求めました。レバレッジをかけた場合、多くのシステムが1000回のトレードのうちに破産する可能性があることを示しました。

このサイトはRubyでバックテストツールを作成し様々なストラテジーを検証することを目的としています。前回まではSMA65CC3についての検証を行っていました。その中でシステムを、N日検定とMFEを用いた方法で評価しました。

今回も引き続きこちらの本を読み進めていきます。

今回はSMA65CC3システムの次に紹介されているCB-PBシステムについて検証を行っていきます。特にこの章では仕切りルールに対して焦点が当てられているので、それに習って2パターンの仕切りルールを検証していきたいと思います。 それでは参りましょう。

目次

実践

CB-PBシステムとは

著書にはこのようにあります。

20日の新高値を付けた後の下落局面で買いポジションを建てる買いシグナルのみのシステムをチャネル・ブレイクアウト-プルバック(CB-PB)システムと呼ぶ。

トゥーシャー・シャンデ『売買システム入門』(PanRolling、2000年)

ブレイクアウトシステムに似ていますが、ブレイクアウトしてからの戻しで買おうってシステムのようですね。

戻しといっても色々な定義の仕方があります。ここでは、このようにすることにしています。

まず20日新高値を付け、その後7日以内に新たに5日安値を更新するところまで下落する。5日安値を更新した場合、翌日の寄り付きで買い仕掛ける。

トゥーシャー・シャンデ『売買システム入門』(PanRolling、2000年)

これで仕掛けのルールが決まりますので、この仕掛けに対し、ランダムな仕掛けより優位なのかを検証することができます。ストラテジーを書いて検証していきましょう。

ストラテジー構築

プログラムはこのようになります。

module Kabu
  class CbPbDays

    def initialize(n)
      @n = n
    end

    def decide(env)
      code = env[:code]
      date = env[:date]
      soks = env[:soks]
      position = env[:position]

      highs = soks[-27..-2].high(20)
      is_high = false
      highs.zip(soks[-8..-2]).each do |high,sok|
        next if high != sok.high
        is_high = true
        break
      end

      lows = soks[-6..-2].low(5)
      is_low = lows[-1] == soks[-2].low

      if not position.nil?
        if position.term >= @n
          return Action::Sell.new(code, date, soks[-1].open,1)
        else
          return Action::None.new(code, soks[-1].open)
        end
      end

      if is_high and is_low
        Action::Buy.new(code, date, soks[-1].open,1)
      else
        Action::None.new(code, soks[-1].open)
      end
    end
  end
end

仕切りルールを10日後の引けで仕切ることにして、理想的な取引がされているチャートを確認してみます。

image1

良さそうですね。これはうまくハマっているパターンですが、下記は天井を拾ったようなパターンです。

image2

N日検定

N日検定はその仕掛けがランダムな仕掛けより優位なのかを検証するためのものです。この検定では仕切りルールをN日後にした場合、適当なNであっても勝率が55%を超えるかを調べます。

ここではN=5,10,15,20,30,50について調べてみました。対象は33業種別株価指数の2007年〜2017年のデータです。

Code 5 10 15 20 30 50
I201 0.47 0.49 0.49 0.44 0.47 0.60
I202 0.50 0.56 0.49 0.53 0.56 0.49
I203 0.48 0.49 0.55 0.47 0.57 0.52
I204 0.58 0.53 0.53 0.52 0.52 0.53
I205 0.43 0.58 0.54 0.41 0.46 0.59
I206 0.49 0.46 0.49 0.44 0.47 0.56
I207 0.52 0.62 0.54 0.54 0.54 0.61
I208 0.48 0.47 0.47 0.47 0.38 0.43
I209 0.56 0.51 0.53 0.58 0.60 0.54
I210 0.49 0.56 0.56 0.48 0.56 0.62
I211 0.57 0.58 0.54 0.57 0.58 0.64
I212 0.44 0.37 0.42 0.38 0.41 0.61
I213 0.51 0.55 0.58 0.52 0.53 0.59
I214 0.52 0.53 0.56 0.48 0.54 0.60
I215 0.54 0.60 0.60 0.54 0.64 0.63
I216 0.55 0.47 0.51 0.50 0.56 0.64
I217 0.49 0.46 0.45 0.41 0.59 0.47
I218 0.58 0.63 0.56 0.52 0.50 0.63
I219 0.51 0.53 0.51 0.55 0.54 0.52
I220 0.45 0.54 0.48 0.43 0.47 0.42
I221 0.43 0.45 0.45 0.52 0.43 0.33
I222 0.47 0.47 0.47 0.38 0.50 0.55
I223 0.41 0.38 0.47 0.50 0.50 0.37
I224 0.49 0.49 0.46 0.41 0.45 0.40
I225 0.58 0.59 0.49 0.47 0.56 0.54
I226 0.50 0.52 0.55 0.52 0.58 0.60
I227 0.51 0.53 0.59 0.54 0.61 0.49
I228 0.48 0.44 0.44 0.31 0.37 0.52
I229 0.46 0.45 0.46 0.44 0.35 0.39
I230 0.52 0.46 0.54 0.53 0.42 0.48
I231 0.58 0.51 0.57 0.50 0.48 0.59
I232 0.58 0.53 0.51 0.47 0.44 0.52
I233 0.52 0.61 0.55 0.59 0.60 0.54
  0.51 0.51 0.51 0.48 0.51 0.53

55%を切っていますので、果たしてと言ったところです。銘柄によるところが大きそうです。

仕切りパターン1

仕切りパターンの1つ目として紹介されているのがN日後に仕切るというものです。個人的には投げやりではないかと思いますが。

N日検定で一番勝率のよかった50日を用いて検証してみましょう。

Code net income trades win(%) pf average dd
I201 81.34 35 60.0 1.96 2.32 -22.34
I202 16.25 35 48.57 1.08 0.46 -48.34
I203 6.47 33 51.52 1.06 0.2 -52.42
I204 49.31 36 52.78 1.67 1.37 -19.11
I205 35.69 34 58.82 1.28 1.05 -32.89
I206 7.74 32 56.25 1.07 0.24 -22.19
I207 61.0 33 60.61 1.77 1.85 -20.45
I208 -32.36 35 42.86 0.73 -0.92 -24.21
I209 -28.42 35 54.29 0.84 -0.81 -50.97
I210 78.42 34 61.76 1.7 2.31 -32.21
I211 80.6 33 63.64 1.71 2.44 -31.55
I212 0.85 33 60.61 1.0 0.03 -63.78
I213 51.14 34 58.82 1.41 1.5 -47.91
I214 48.12 35 60.0 1.41 1.37 -39.09
I215 79.06 35 62.86 1.69 2.26 -27.39
I216 54.63 33 63.64 1.49 1.66 -22.28
I217 64.74 34 47.06 1.6 1.9 -58.04
I218 65.65 35 62.86 1.57 1.88 -39.01
I219 38.39 33 51.52 1.36 1.16 -20.28
I220 -21.96 31 41.94 0.85 -0.71 -41.3
I221 8.78 33 33.33 1.08 0.27 -29.49
I222 77.17 33 54.55 1.45 2.34 -31.19
I223 -82.87 35 37.14 0.62 -2.37 -61.69
I224 -57.32 35 40.0 0.64 -1.64 -76.7
I225 28.34 35 54.29 1.27 0.81 -44.11
I226 43.91 35 60.0 1.34 1.25 -27.58
I227 27.71 35 48.57 1.22 0.79 -48.25
I228 20.35 31 51.61 1.17 0.66 -29.75
I229 31.49 33 39.39 1.14 0.95 -47.21
I230 -20.37 33 48.48 0.87 -0.62 -53.69
I231 16.26 34 58.82 1.09 0.48 -51.08
I232 -20.3 33 51.52 0.89 -0.62 -53.69
I233 39.38 35 54.29 1.52 1.13 -31.2
平均 25.7 33.0 53.1 1.3 0.8 -39.4
偏差 40.98 1.41 8.17 0.34 1.2 14.57

無事プラスにはなりました。PFは1.3、平均収益は0.8%となっています。取引コストは勘定していませんので、この数字ではしんどいですね。

仕切りパターン2

2つめのパターンとして紹介されているのが、新高値で仕切るというものです。同じ20日新高値を用いましょう。コードこのとおりです。

module Kabu
  class CbPbHigh

    def decide(env)
      code = env[:code]
      date = env[:date]
      soks = env[:soks]
      position = env[:position]

      highs = soks[-27..-2].high(20)
      is_high = false
      highs.zip(soks[-8..-2]).each do |high,sok|
        next if high != sok.high
        is_high = true
        break
      end

      lows = soks[-6..-2].low(5)
      is_low = lows[-1] == soks[-2].low

      if not position.nil?
        if highs[-1] <= soks[-1].high
          return Action::Sell.new(code, date, highs[-1],1)
        else
          return Action::None.new(code, soks[-1].open)
        end
      end

      if is_high and is_low
        Action::Buy.new(code, date, soks[-1].open,1)
      else
        Action::None.new(code, soks[-1].open)
      end
    end
  end
end

チャートも確認しておきます。

image3

問題なさそうですね。動かしてみます。

Code net income trades win(%) pf average dd
I201 9.55 89 71.91 1.07 0.11 -27.4
I202 32.34 85 72.94 1.13 0.38 -59.03
I203 -5.73 85 69.41 0.96 -0.07 -35.32
I204 40.4 91 79.12 1.4 0.44 -35.8
I205 -35.7 79 69.62 0.79 -0.45 -42.28
I206 -41.41 79 63.29 0.79 -0.52 -38.04
I207 -18.27 87 78.16 0.88 -0.21 -46.71
I208 -15.44 80 73.75 0.88 -0.19 -32.11
I209 -21.86 85 75.29 0.9 -0.26 -43.5
I210 47.93 87 73.56 1.27 0.55 -41.06
I211 -41.52 86 70.93 0.81 -0.48 -55.82
I212 -139.87 66 60.61 0.5 -2.12 -64.16
I213 -27.74 92 73.91 0.88 -0.3 -50.29
I214 -43.48 81 71.6 0.75 -0.54 -39.5
I215 -49.47 88 77.27 0.77 -0.56 -57.28
I216 -50.11 85 70.59 0.75 -0.59 -58.45
I217 -60.49 77 64.94 0.71 -0.79 -41.29
I218 -20.1 93 75.27 0.89 -0.22 -52.24
I219 -54.09 77 75.32 0.74 -0.7 -53.82
I220 -73.54 74 70.27 0.67 -0.99 -43.73
I221 -24.57 78 65.38 0.79 -0.31 -18.02
I222 -88.99 70 70.0 0.72 -1.27 -60.76
I223 -66.44 72 63.89 0.68 -0.92 -58.85
I224 -25.07 84 70.24 0.84 -0.3 -34.68
I225 70.9 96 79.17 1.7 0.74 -16.74
I226 -44.52 86 75.58 0.76 -0.52 -56.33
I227 -26.72 85 74.12 0.83 -0.31 -20.15
I228 -117.74 66 59.09 0.52 -1.78 -61.27
I229 -178.04 66 57.58 0.49 -2.7 -82.51
I230 -68.99 79 68.35 0.74 -0.87 -57.14
I231 2.9 82 69.51 1.01 0.04 -69.77
I232 -56.22 78 66.67 0.78 -0.72 -62.69
I233 -13.03 82 69.51 0.89 -0.16 -21.35
  -36.5 81.0 70.5 0.9 -0.5 -46.6
  49.66 7.55 5.43 0.24 0.7 15.56

勝率が平均80%を超えるなかなかのシステムですが、収益がマイナスです。コツコツドッカン系といったところですね。ロスカットのルールを検討することで改善出来るのかもしれません。MFEを調べてみましょう。

勝ちトレードが経験した最大の含み損のヒストグラムを累積したものです。

image4

-3.2%当たりをロスカットラインに設定した場合8割程度の勝ちトレードは救えそうです。

ストラテジーはこんな感じです。

class CbPbHighStopLoss

    attr_accessor :loss_line

    def initialize
      @loss_cutted = false
    end

    def setup
      @last_position = nil
    end

    def decide(env)
      code = env[:code]
      date = env[:date]
      soks = env[:soks]
      position = env[:position]

      highs = soks[-27..-2].high(20)
      is_high = false
      highs.zip(soks[-8..-2]).each do |high,sok|
        next if high != sok.high
        is_high = true
        break
      end

      lows = soks[-6..-2].low(5)
      is_low = lows[-1] == soks[-2].low

      if position.nil?
        if is_high and is_low
          return Action::Buy.new(code, date, soks[-1].open,1)
        else
          return Action::None.new(code, soks[-1].open)
        end
      end

      is_loss_cut = position.gain(soks[-2].close,1) < @loss_line

      if is_loss_cut
        @last_position = position
        return Action::Sell.new(code,date,soks[-1].open,1)
      end

      if highs[-1] <= soks[-1].high
        return Action::Sell.new(code, date, highs[-1],1)
      else
        return Action::None.new(code, soks[-1].open)
      end
    end
  end

仮に-3.4%をロスカットラインだとして、チャートを確認します。

image5

image6

ロスカット、通常仕切り共に問題なさそうです。

ロスカットを-10%〜-1%に置きながら、損益などを確認します。

image7

image8

image8

-1%のロスカットを設定すると、収益は改善し、偏差も下がっています。ただ思うほどの効果はありませんでした。ちなみに-11%のロスカットラインはロスカットがない場合の値になっています。-10%と滑らかに接続しないのは気になります。MFEの累積グラフには-16%あたりまで勝ちトレードが経験した含み損があるので、このまま-16%くらいまでロスカットラインを切り下げていくと徐々に収益が改善するのかもしれません。

まとめ

CB-PBシステムについて2パターンの仕切り方法で検証を行ってみました。どちらもいまいちでした。シャンデ氏の著書には、十分に成熟したマーケットを対象にするとあるため、長い期間に渡ってヨコヨコであったり、緩やかに上昇しているような銘柄に絞れば話は変わってくるのかもしれません。

AndroidのアプリにTermuxというLinuxターミナルエミュレータが有るのですが、それを使ってツールが動かないかなと試行錯誤してみたら、無事に動かせるってことがわかりました。./configure make make installとか久しぶりでした。

このツールが動くってことはnokogiriとsqlite3が動くってことなのでRails動くかなと思いましたが、インストールできても起動に失敗するようです。また機会があったら挑戦してみようと思います。

さて、このサイトはRubyでバックテストツールを作成し様々なストラテジーを検証することを目的としています。前回はSMA65CC3ストラテジーにロスカットルールを追加しました。そこでパフォーマンスの悪化は見られるものの、システムが堅牢となることを示しました。今回も引き続き「売買システム入門」を読み進め、SMA65CC3の改良を行っていきます。

今回はRAVI指標を用いてシグナルのフィルタリングを行います。今までマイナスを履き続けるシステムでしたが、このフィルターによってようやくプラスを吐き出すことに成功しました。最も使えるかは別ですが。

早速見ていきましょう。

目次

実践

RAVI指標とは

RAVI指標とは、レンジアクション・ベリフィケーション・インデックスの略で、レンジ内で推移する状態に焦点を絞った指標であり、当日の値動きが前日の値動き幅をどの程度上回っているかをみるものになります。

<RAVI指標の計算方法>

RAVI=(短期移動平均-長期移動平均)÷長期移動平均×100

RAVI指標が、基準値を下回った場合には、マーケットはレンジ内での推移と判断し、これ以上の場合はトレンドが現れている事を意味するという使い方をします。

「超早分かり!テクニカル分析」

実際にチャートを見てみましょう。下段がRAVIです。仮に1%を基準値とした場合 、トレンドがないと判断した日足の色を変えています。

ravi

前半はトレンドに勢いがあって、RAVIも吹っ切れていますが、後半はヨコヨコ展開になり、1%を下回る日がちらほらとあります。SMA65CC3によって発生する無駄なシグナルをいくつか消せそうなのが見て取れると思います。

ストラテジーの構築

フィルターの効果を検証するため、前回のロスカットルールは追加しません。またRAVIでシグナルをキャンセルした場合、一旦平均線を逆にクロスするまでは注文をかけないようにします。コードは下記のようになります。

module Kabu
  class Sma65Cc3Ravi

    def initialize(line = 1)
      @line = line
    end

    def decide(env)
      code = env[:code]
      date = env[:date]
      closes = env[:closes]
      open = env[:open]
      position = env[:position]

      ravi = closes.ravi(7,65)
      return Action::None.new(code,open) if ravi[-1] < @line

      aves = closes[-68..-1].ave(65)
      is_buy = 3.times.inject(true) do |ret, i|
        ret = (ret and (closes[-1-i] > aves[-i-1]))
      end
      is_buy = (is_buy and (closes[-4] <= aves[-4]))

      is_sell = 3.times.inject(true) do |ret, i|
        ret = (ret and (closes[-1-i] < aves[-i-1]))
      end
      is_sell = (is_sell and (closes[-4] >= aves[-4]))

      if position.nil?
        if is_buy
          return Action::Buy.new(code,date,open,1)
        elsif is_sell
          return Action::Sell.new(code,date,open,1)
        else
          return Action::None.new(code,open)
        end
      end

      if position.buy? and is_sell
        Action::Sell.new(code,date,open,2)
      elsif position.sell? and is_buy
        Action::Buy.new(code,date,open,2)
      else
        Action::None.new(code,open)
      end
    end
  end
end

検証

2007年〜2017年の33業種別株価指数を用いてバックテストを行ってみましょう。フィルターをかけない場合についてもバックテストして集計結果を比較してみます。

code net income trades win pf average dd
I201 45.29 23 47.83 1.61 1.97 -25.88
I202 12.68 45 42.22 1.07 0.28 -51.85
I203 -71.65 29 20.69 0.55 -2.47 -86.99
I204 -60.81 35 17.14 0.61 -1.74 -41.94
I205 85.88 19 36.84 2.33 4.52 -42.7
I206 15.59 44 31.82 1.11 0.35 -30.76
I207 102.95 31 35.48 2.12 3.32 -24.27
I208 49.18 26 38.46 1.68 1.89 -23.01
I209 120.83 31 51.61 2.44 3.9 -18.23
I210 9.81 49 26.53 1.06 0.2 -71.19
I211 -69.45 42 35.71 0.69 -1.65 -86.34
I212 -6.44 36 30.56 0.97 -0.18 -66.37
I213 -108.57 29 37.93 0.52 -3.74 -76.68
I214 53.21 27 44.44 1.5 1.97 -34.81
I215 68.02 36 33.33 1.5 1.89 -47.18
I216 88.26 25 32.0 1.84 3.53 -31.32
I217 161.7 25 48.0 3.22 6.47 -26.91
I218 45.03 25 32.0 1.38 1.8 -42.0
I219 -174.23 29 31.03 0.31 -6.01 -112.65
I220 101.79 20 50.0 2.45 5.09 -29.03
I221 50.24 21 38.1 1.65 2.39 -33.73
I222 85.85 30 46.67 1.55 2.86 -59.79
I223 107.13 26 46.15 2.26 4.12 -28.91
I224 -139.4 28 21.43 0.28 -4.98 -130.13
I225 20.9 26 34.62 1.19 0.8 -79.6
I226 -46.69 37 32.43 0.73 -1.26 -46.47
I227 -64.09 26 23.08 0.52 -2.46 -74.03
I228 -36.1 34 41.18 0.81 -1.06 -60.87
I229 173.81 38 26.32 1.83 4.57 -61.75
I230 41.08 33 36.36 1.27 1.24 -69.13
I231 83.14 34 41.18 1.57 2.45 -64.67
I232 107.23 38 34.21 1.77 2.82 -48.75
I233 63.45 17 47.06 2.06 3.73 -20.73
平均 27.7 30.0 36.1 1.4 1.1 -53.0
偏差 82.56 7.62 8.74 0.7 2.92 26.53
平均/無 -13.4 69.0 26.3 1.0 -0.1 -47.9
偏差/無 67.06 8.66 4.79 0.27 0.94 16.36

なんということでしょう。全く稼げなかったあのシステムが、わずかばかりか稼げるようになりました。PFの業種間平均は1.4。平均収益の平均は1.1%。そうはいっても、ここから取引コストを勘定することを考えれば、まだまだ使い物にはなりませんね。

著書には、フィルターについてはあまり多く触れられていません。RAVIのラインを切り下げた場合についての比較程度となっています。この辺はカーブフィッティングにつながりやすいからかもしれませんね。ですので、さっぱりとしていますが今回はこれで終わりです。

まとめ

RAVIを使ってシグナルにフィルターをかけました。すると収益が改善することがわかりました。

次回はSMA65CC3システムについてはこれで終わりにして、シャンデ氏の著書に紹介されている次のシステムの検証に入りたいと思います。

前回に引き続き、「売買システム入門」に従い、SMA65CC3を使ってストラテジーの検証を行っていきます。

ところで皆さんはロスカットをどうやって設定していますか?仕掛けの有効性はN日検定を用いることで調べることが出来ると記されていることを前回紹介しました。売買システム入門にはロスカットの評価方法についても詳細に説明されています。今回はその辺について紹介していきたいと思います。

目次

実践

ロスカットを追加する理由

バックテストを行っていると、最終収益はいいけども果たしてこのシステムは別のシステムに勝っているのか?と疑問になることが多々有ると思います。特にロスカットの設定は、追加することで利益が減ることが多いため、それなら追加しないほうが良いのか?などと考えることも有るかもしれません。

シャンデ氏はトレーディングシステムの基本原則なるものを6つほど上げていまして、その一つにこのようにあります。

3.トレーディング・システムのパラメータの数値は堅牢なものでなければならない。要するに様々な時間枠、多くの異なるマーケットで機能する必要がある。

トゥーシャー・シャンデ『売買システム入門』(PanRolling、2000年)

そして、ロスカットの追加がこの堅牢性を高めているってことが幾つかのデータを使って提示されています。著書に習って検証を行って行きたいと思います。

MFEの調査

MFEとは最高到達地点と呼ばれるもので、ポジションを持っているときに経験した最大の含み益や含み損のことを言うようです。ちなみに、なんの略かは記されていなのでわかりません。そこまで翻訳してほしいものです。

このMFEを調べることでロスカットのどこに置くべきか当たりをつけることが出来るようになります。早速調べてみましょう。

前回と同じように33業種別株価指数を用いて検証を行いますが、前回は円ベースで集計を行っていたため、銘柄間の比較を行うことができませんでした。そこで今回は収益をパーセントで集計するように回収を加え、銘柄間の比較がもうちょっとマシな形で出来るようにさせています。ストラテジーも同じものを使います。まだロスカットルールは追加しません。

勝ちトレードの最大含み損と負けトレードの最大含み益を集計してヒストグラムにすると、下記のようになりました。

  • 勝ちトレードのMFE

profit_histgram

  • 負けトレードのMFE

profit_histgram

Y軸が対数なんで、ぱっと見線形ですが気をつけてください。

これを見ると、勝ちトレードの大半は1% の含み損しか経験しておらず、負けトレードの中には20%もの含み益を持っていたものがある、などということが解ると思います。

勝ちトレードのMFEを累積させて表示させてみます。最大値は勝ちトレード数になりますので、そこを100とします。

profit_histgram

こうしてみると、例えば-4%当たりにロスカットラインを設定したとしても、残りの9割の勝ちトレードには影響を与えないことがわかります。これは有益な情報でですね。

ストラテジーの構築

ここで行いたいのは、ストップロスを使用しなかった最初のシミュレーションと全く同じトレードについて検証することである。この目的を達成するためには、同じシグナルが続くことを避けるためのルールを設定し、最初のシグナルで損切りを余儀なくされた後に再び同じ方向のトレードが続かないようにすることである。

トゥーシャー・シャンデ『売買システム入門』(PanRolling、2000年)

ということですので、これをコーディングしていきます。Ifが深くなるので、ネストを浅くしときます。

class Sma65Cc3StopLoss

  def initialize(loss_line)
    @loss_line = loss_line
    @loss_cut = false
  end

  def reset
    @loss_cutted = false
    @last_position = nil
  end

  def decide(env)
    code = env[:code]
    date = env[:date]
    closes = env[:closes]
    open = env[:open]
    position = env[:position]

    aves = closes[-67..-1].ave(65)
    is_buy = 3.times.inject(true) do |ret, i|
      ret = (ret and (closes[-1-i] > aves[-i-1]))
    end

    is_sell = 3.times.inject(true) do |ret, i|
      ret = (ret and (closes[-1-i] < aves[-i-1]))
    end

    if position.nil? and not @loss_cutted
      if is_buy
        return Action::Buy.new(code,date,open,1)
      elsif is_sell
        return Action::Sell.new(code,date,open,1)
      else
        return Action::None.new(code,open)
      end
    end

    if position.nil? and @loss_cutted
      if  @last_position.buy? and is_sell
        reset
        return Action::Sell.new(code,date,open,1)
      elsif @last_position.sell? and is_buy
        reset
        return Action::Buy.new(code,date,open,1)
      else
        return Action::None.new(code,open)
      end
    end

    is_loss_cut = position.gain(closes[-1],1) < @loss_line

    if is_loss_cut
      @loss_cutted = true
      @last_position = position
      if position.buy?
        return Action::Sell.new(code,date,open,1)
      elsif position.sell?
        return Action::Buy.new(code,date,open,1)
      end
    end

    if position.buy? and is_sell
      Action::Sell.new(code,date,open,2)
    elsif position.sell? and is_buy
      Action::Buy.new(code,date,open,2)
    else
      Action::None.new(code,open)
    end
  end
end

必要に応じてgit pullしてください。

ロスカットの効果

では、実際にロスカットラインを-10%〜-1%に設定させながら、バックテストを行い、結果にどのような影響を及ぼすのかを見てみましょう。2007年〜2017年の33業種別株価指数を用いて、バックテストを行い、銘柄間で平均した収益と、最大ドローダウンをロスカットラインに対して表示してみます。ただし-11%のデータはロスカットを設定しなかった場合の値です。

profit_histgram

ロスカットラインが狭くなるに連れて収益とドローダウンが減少しているのが見て取れます。この辺は予想通りでしょう。

続いて、銘柄間の偏差を見てみます。

profit_histgram

ロスカットラインが狭くなると、偏差も減少していることがわかります。つまり、銘柄間のバラ付きが少なくなったということです。この辺がシステムの堅牢性の話につながるのでしょう。

勝ちトレード数はもちろん減少しますが、平均値はどうでしょうか?

profit_histgram

悪化していますね。 ここまでの検証はexample/back_test/strategy1-2/にコードがあります。

まとめ

SMA65CC3にロスカットルールを加えました。すると、収益や平均収益が減少しました。悲しい限りです。

一方で最大ドローダウンが減少し、銘柄間の偏差も減少することがわかりました。つまり、システムが堅牢になったといえます。そもそもマイナス収益ですが。

システムの堅牢性に対する認識の差が、どのシステムを良しとするかの分かれ目になりそうですね。

次回も引き続き「売買システム入門」を読み進めて行きます。次回は騙しシグナルのフィルタリングの効果について検証していきます。

Intel Core-i9は12コアってPentiumを経験してきた世代としては信じられないです。AMDもRyzen8コアのCPUを出してたんですね。ここのところVPS環境での作業がメインなのでPCいじりとは疎遠なのですが、少しさわってみたい衝動に駆られました。

さて、このサイトはRubyでバックテストツールを作成し様々なストラテジーを検証することを目的としています。前回の記事で作成したツールのチュートリアルを兼ねてテストを行いましたので、いよいよストラテジーの検証に入っていきたいと思います。

最初に検証していくのは「売買システム入門〜金融工学の考え方→作り方→評価法」著トゥーシャー・シャンデに紹介されているストラテジーです。

この本では、シンプルなストラテジーの説明から始まって、騙しシグナルのためのフィルターに対する検証、ストップロスの効果についての検証、資金管理方法についての検証など、ストラテジー構築に必要な要素が網羅されています。私も追随しながら何回かに分けて検証を行っていきたいと思います。

今回検証を行うのは65SMA-3CCトレンドフォローシステムとして紹介されているシステムです。一番はじめに紹介されている最適化のされていないシステムとなります。

検証を行ってみると、このままのシステムでは使えんだろうなぁという結果が得られました。

それでは順次見ていきましょう。

目次

実践

65SMA-3CCとは

65SMA-3CCシステムでは、トレンドの転換を確認するために65日間単純移動平均線(65SMA)より上や下で3日連続して引ける(3CC)ことを条件とする。

トゥーシャー・シャンデ『売買システム入門』(PanRolling、2000年)

と説明されています。やけにシンプルですね。

ストラテジーの構築

きれいなコードでかけませんね。Noneをわざわざ定義しなけりゃいけないあたりが、微妙な感じで。

n日検定用と、本番用のコードです。それぞれexample/back_test/strategy1-1に有るコードからの抜粋です。

  • n日検定
class Sma65Cc3
  def initialize(n)
   @n = n    
  end  
  def decide(env)  
    code = env[:code]
    date = env[:date]  
    closes = env[:closes] 
    open = env[:open]  
    position = env[:position] 
    aves = closes[-67..-1].ave(65)
    is_buy = 3.times.inject(true) do |ret, i|
    ret = ret and (closes[-i-1] > aves[-i-1]) 
    end
    if not position.nil? and position.buy? 
      if position.term >= @n
        Action::Sell.new(code,date,open,1)   
      else
        Action::None.new(code)
      end
    else 
      if is_buy 
        Action::Buy.new(code,date,open,1) 
      else
        Action::None.new(code)  
      end 
    end 
  end 
end
  • SMA65CC3
class Sma65Cc3

  def decide(env)
    code = env[:code]
    date = env[:date]
    closes = env[:closes]
    open = env[:open]
    position = env[:position]

    aves = closes[-67..-1].ave(65)
    is_buy = 3.times.inject(true) do |ret, i|
      ret = (ret and (closes[-1-i] > aves[-i-1]))
    end

    is_sell = 3.times.inject(true) do |ret, i|
      ret = (ret and (closes[-1-i] < aves[-i-1]))
    end

    if not position.nil? and position.buy?
      if is_sell
        Action::Sell.new(code,date,open,2)
      else
        Action::None.new(code)
      end
    elsif not position.nil? and position.sell?
      if is_buy
        Action::Buy.new(code,date,open,2)
      else
        Action::None.new(code)
      end
    else
      if is_buy
        Action::Buy.new(code,date,open,1)
      elsif is_sell
        Action::Sell.new(code,date,open,1)
      else
        Action::None.new(code)
      end
    end
  end
end 

n日検定

解説を進める前に65SMA-3CCの仕掛ける方法がランダムな方法と比べて有効だと言い切れるかをチェックする重要な検証を行ってみよう。ルビューとルーカス(詳細は参考文献の欄参照)のアプローチ法に従い、この仕掛ける方法にn日後の大引けで仕切るという条件を加えて検証してみよう。ストップロスは使用せず、スリッページと手数料もないものと仮定する。

トゥーシャー・シャンデ『売買システム入門』(PanRolling、2000年)

シャンデ氏に習って私も同じことをしてみます。複数の市場を用いて行っていますが、私の手元には日本市場のものしかありません。加えてこのツールは、まだ株式の分割・統合の対応を行っていないので、闇雲に銘柄をピックアップすると結果が無意味となる場合があります。そこで、33業種別株価指数を用いて検証することにします。指数は、K-Db様のサイトよりダウンロードさせていただきます。下記コマンドにて皆様の環境にも準備することができます。ただし、1アクセスごとに10秒間のスリープを設けているので、時間はかかると思います。

git pull
bundle exec ruby exe/setup.rb
bundle exec rake db:seed
bundle exec ruby exe/index_setup.rb

シャンデ氏の著書では1975年〜1995年の間で入手可能な英ポンド、コーヒー、日本円、S&P500、国債など21のマーケットデータを使って検定が行われています。N日後手仕舞のNは5,10,15,20,30,50が用いられ、それぞれの勝率の業種間平均は55%を上回るものだと記されています。この55%を上回るってところがこの検定のキモのようです。

2007〜2017年の期間で、33業種別株価指数を用いて検定を行った結果は下記です。

bundle exec ruby example/back_test/strategy1-1/n_kentei.rb
code 5 10 15 20 30 50
I201 0.57 0.58 0.56 0.53 0.53 0.57
I202 0.51 0.47 0.43 0.40 0.54 0.50
I203 0.58 0.53 0.51 0.59 0.48 0.66
I204 0.56 0.52 0.47 0.54 0.50 0.53
I205 0.57 0.56 0.54 0.56 0.55 0.52
I206 0.52 0.50 0.55 0.49 0.52 0.50
I207 0.56 0.56 0.63 0.59 0.61 0.57
I208 0.56 0.52 0.56 0.53 0.56 0.50
I209 0.50 0.56 0.53 0.51 0.44 0.44
I210 0.49 0.48 0.47 0.44 0.43 0.49
I211 0.55 0.53 0.54 0.57 0.57 0.51
I212 0.52 0.50 0.48 0.53 0.55 0.47
I213 0.57 0.52 0.53 0.60 0.55 0.51
I214 0.59 0.51 0.57 0.53 0.52 0.51
I215 0.57 0.58 0.56 0.59 0.62 0.65
I216 0.55 0.57 0.54 0.56 0.58 0.64
I217 0.52 0.56 0.52 0.51 0.53 0.48
I218 0.53 0.53 0.51 0.51 0.56 0.46
I219 0.52 0.58 0.54 0.58 0.52 0.61
I220 0.48 0.48 0.45 0.45 0.42 0.52
I221 0.49 0.50 0.46 0.45 0.38 0.44
I222 0.55 0.55 0.54 0.50 0.57 0.47
I223 0.52 0.46 0.49 0.53 0.48 0.42
I224 0.49 0.50 0.47 0.45 0.46 0.47
I225 0.48 0.44 0.48 0.49 0.46 0.44
I226 0.53 0.56 0.55 0.54 0.54 0.50
I227 0.57 0.53 0.54 0.55 0.48 0.44
I228 0.49 0.48 0.48 0.52 0.51 0.42
I229 0.50 0.50 0.51 0.50 0.48 0.42
I230 0.52 0.50 0.52 0.53 0.49 0.50
I231 0.49 0.54 0.49 0.54 0.52 0.46
I232 0.51 0.48 0.51 0.49 0.46 0.45
I233 0.54 0.55 0.55 0.59 0.57 0.55
0.53 0.52 0.52 0.52 0.51 0.50  

微妙ですね。先に進めてみましょう。

検証

買いのみなのか売りもありなのか本文から汲めなかったので売りありで検証を行ってみました。著書の中では売買コストに100ドルを設定してありますが、私の検証ではコストは0です。著書の表と同じように出してみたつもりですが、集計で意味あるのってプロフィットファクターと勝率だけだと思います。

結果はさんさんたるものです。勝率は平均して30%。トレンドフォローであるのならばこの辺なのでしょう。プロフィットファクターは平均0.9なんで優位性は全くありません。ここに取引コストを加味されるのですから、とても使えるものではありません。

code net income trades win pf average dd
I201 159.96 63 0.32 1.38 2.54 -137.61
I202 -614.11 79 0.27 0.64 -7.77 -550.86
I203 -513.45 72 0.24 0.66 -7.13 -389.4
I204 -1294.94 90 0.13 0.49 -14.39 -848.49
I205 -41.89 66 0.27 0.96 -0.63 -157.73
I206 -213.75 73 0.27 0.75 -2.93 -210.31
I207 -236.23 72 0.19 0.89 -3.28 -558.33
I208 77.81 74 0.38 1.03 1.05 -872.83
I209 361.21 69 0.28 1.17 5.23 -594.46
I210 -2962.06 87 0.17 0.55 -34.05 -2786.72
I211 9.51 74 0.24 1.0 0.13 -410.62
I212 373.69 71 0.3 1.24 5.26 -413.2
I213 8.81 66 0.32 1.0 0.13 -250.29
I214 -195.77 66 0.26 0.9 -2.97 -319.01
I215 27.06 64 0.27 1.01 0.42 -488.34
I216 1283.03 60 0.32 1.53 21.38 -384.72
I217 887.11 60 0.28 1.23 14.79 -1075.7
I218 -2274.93 68 0.22 0.72 -33.45 -1933.22
I219 60.13 56 0.32 1.02 1.07 -1490.11
I220 183.9 59 0.31 1.21 3.12 -205.26
I221 -2117.07 84 0.19 0.43 -25.2 -1602.8
I222 465.29 59 0.31 1.3 7.89 -455.35
I223 41.74 61 0.28 1.08 0.68 -207.15
I224 -1060.79 80 0.23 0.65 -13.26 -1061.65
I225 -799.62 40 0.28 0.53 -19.99 -647.42
I226 -719.74 80 0.25 0.73 -9.0 -340.07
I227 -381.61 75 0.27 0.73 -5.09 -298.31
I228 5.14 75 0.27 1.01 0.07 -65.49
I229 148.02 66 0.29 1.11 2.24 -247.4
I230 -299.08 72 0.22 0.85 -4.15 -464.03
I231 226.39 63 0.29 1.21 3.59 -216.29
I232 75.7 59 0.27 1.02 1.28 -645.36
I233 -413.32 59 0.27 0.77 -7.01 -616.77
  -295.3 68.0 0.3 0.9 -3.6 -634.7

本来であれば、なぞって作ったシステムを同じデータを使って著書の結果が得られるのかを再現しなければならないのですが、データがないのでってことで確認はしません。

確かに理想的なトレードをしている局面もあります。

profit_histgram

profit_histgram

まとめ

2007年から2017年の業種別株価指数を用いて検証を行いました。この期間は序盤ダダッさげの相場で後半盛り返す、お椀型のチャートを形成しています。業種によってどの程度盛り返すのかってところで差異はありますが、基本的に変わりません。下げあり、上げありヨコヨコありと見ようによってはいいサンプルかもしれませんが、もう少し幅を広げてサンプルをえらべば結果は変わるのかもしれません。

あるいは、株式を取りまく環境の変化によってこの手法はすでに有効でなくなった可能性もあります。

2007〜2017の日本市場においてSMA65CC3という非常にシンプルな手法は有効とは言い難いという結果が得られました。次回も引き続きこの本になぞってすすめ、SMA65CC3に改良を加えていこうと思います。

ツールのチュートリアルを兼ねてテストを行います。StrategyをコーディングしてTraderにActionを渡しながらループさせるってのが基本的な流れになります。

サラッと眺めていきましょう。

目次

実践

サンプル1

ストラテジーは特定日付で売買をするようにコーディングします。1305を1株だけ3月17日に買い3月30日に売ります。再び4月25日に買い5月10日に売ります。それぞれの取引は終値で行います。株価は下記のとおりです。

  • 3月17日: 1412
  • 3月30日: 1423 (+11円)
  • 4月25日: 1471
  • 5月10日: 1402 (-69円)

したがって、1回目の取引が11円の勝ちで2回目の取引が69円の負けとなります。実際にコードを走らせてみます。

require File.expand_path '../../../lib/sok', File.dirname(__FILE__)
include Kabu

class Sample1Strategy

  def decide(env)
    close = env[:close]
    date = env[:date]
    code = env[:code]

    case date.strftime('%Y%m%d')
    when '20160317'
      Action::Buy.new(code, date, close, 1)
    when '20160330'
      Action::Sell.new(code, date, close, 1)
    when '20160425'
      Action::Buy.new(code,date, close, 1)
    when '20160510'
      Action::Sell.new(code ,date, close, 1)
    else
      Action::None.new(code)
    end
  end
end

trader = Trader.new
strategy = Sample1Strategy.new
code = 1305
soks = Sok.joins(:company).where('companies.code=?',code).order('date')

soks.each do |sok|
  action = strategy.decide(code: code, date: sok.date, close: sok.close)
  trader.receive [action]
end

trader.summary
dir = File.expand_path('../../../data/test/sample1', File.dirname(__FILE__))
trader.save dir
trader.plot_recorded_chart dir + '/chart'

出力結果は下記のとおりです。正しく計算できていますね。

======================================================
net income:               -58.0
profit | loss:            11.0    | -69.0
pf:                       0.2
max profit | max loss:    11.0     | -69.0
trades | wins | looses:   2     | 1     | 1
wins{%}                   50.0
max series of wins:       1
max series of looses:     1
average span{win}:        8
average span{loose}:      7
max drow down:            -69.0
======================================================

グラフも見てみます。

  • 損益曲線 profit_curve

  • 損益ヒストグラム profit_histgram

  • 月次損益 monthly_profit

取引回数が少ないため、もっさりとしたグラフですが、良さそうです。前回には書いていなかったグラフも追加しています。実際にトレードした時のチャートはどうなっていたのかを知るためのグラフです。三角マークが取引したところです。

  • 3/17~3/30 Trade1

  • 4/25~5/10 Trade1

Y軸の目盛りが微妙ですが、十分確認できるんじゃないでしょうか。

サンプル2

続いてドテンルールでのトレードを想定したストラテジーをコーディングします。反対注文を現在のポジションより多く発注することでドテンすることができます。コードはこのようになります。Action.newの引数が1から2に変わっています。

require File.expand_path '../../../lib/sok', File.dirname(__FILE__)
include Kabu

class Sample1Strategy

  def decide(env)
    close = env[:close]
    date = env[:date]
    code = env[:code]

    case date.strftime('%Y%m%d')
    when '20160317'
      Action::Buy.new(code, date, close, 1)
    when '20160330'
      Action::Sell.new(code, date, close, 2)
    when '20160425'
      Action::Buy.new(code,date, close, 2)
    when '20160510'
      Action::Sell.new(code ,date, close, 1)
    else
      Action::None.new(code)
    end
  end
end

trader = Trader.new
strategy = Sample1Strategy.new

code = 1305
soks = Sok.joins(:company).where('companies.code=?',code).order('date')

soks.each do |sok|
  action = strategy.decide(code: code, date: sok.date, close: sok.close)
trader.receive [action]
end                                                                                              
trader.summary
dir = File.expand_path('../../../data/test/sample2', File.dirname(__FILE__))
trader.save dir
trader.plot_recorded_chart dir + '/chart'

結果はこのようになります。

======================================================
net income:               -106.0
profit | loss:            11.0    | -117.0
pf:                       0.1
max profit | max loss:    11.0     | -69.0
trades | wins | looses:   3     | 1     | 2
wins{%}                   33.3
max series of wins:       1
max series of looses:     2
average span{win}:        8
average span{loose}:      12
max drow down:            -117.0
======================================================

負けトレード回数が一回増えています。3/30~4/25で売りから入った-48円が正しく計上されています。

サンプル3

最後に買いましを想定したコードです。

require File.expand_path '../../../lib/sok', File.dirname(__FILE__)
include Kabu

class Sample1Strategy

  def decide(env)
    close = env[:close]
    date = env[:date]
    code = env[:code]

    case date.strftime('%Y%m%d')
    when '20160317'
      Action::Buy.new(code, date, close, 1)
    when '20160330'
      Action::Buy.new(code, date, close, 1)
    when '20160425'
      Action::Sell.new(code,date, close, 1)
    when '20160510'
      Action::Sell.new(code ,date, close, 1)
    else
      Action::None.new(code)
    end
  end
end

trader = Trader.new
strategy = Sample1Strategy.new

code = 1305
soks = Sok.joins(:company).where('companies.code=?',code).order('date')

soks.each do |sok|
  action = strategy.decide(code: code, date: sok.date, close: sok.close)
trader.receive [action]
end
trader.summary
dir = File.expand_path('../../../data/test/sample3', File.dirname(__FILE__))
trader.save dir
trader.plot_recorded_chart dir + '/chart'

結果は下記のとおりで。あ。。。 バグ。なかったことにして修正後の結果を載せます。

======================================================
net income:               38.0
profit | loss:            59.0    | -21.0
pf:                       2.8
max profit | max loss:    59.0     | -21.0
trades | wins | looses:   2     | 1     | 1
wins{%}                   50.0
max series of wins:       1
max series of looses:     1
average span{win}:        26
average span{loose}:      25
max drow down:            -21.0
======================================================
  • 3/17~4/25 Trade4

  • 3/30~5/10 Trade5

まとめ

ストラテジーは日付固定で売買を行うものにして、バックテストツールのテストを行いました。細かいところが微妙な雰囲気を醸していますが、まぁ良い感じにはなっているんじゃないでしょうか?次回からいよいよストラテジーの検証に入っていきたいと思います。

週明け早々物騒ですね。ロフテッド軌道って迎撃出来るのでしょうか?迎撃してる訓練なんかが公開されると安心なのですが。

さて、このサイトはRubyでバックテストツールを作成し、様々なストラテジーを検証することを目的としています。前回はgnuplotを利用したチャートの出力について説明しました。

今回からバックテストツールの実装に入りたいと思います。ここで作成するのは単一銘柄を対象とした単利での売買システムとなります。

まず、バックテストの評価に当たってどのような指標を出力しなければならないかを一覧し、その後プログラムを動かしましょう。

目次

実践

バックテストの評価{#item1}

バックテストの結果として表示する指標は下記のものとします。

  • 純利益
  • 総利益
  • 総損失
  • 含み損益
  • 総利益÷総損失
  • 最大利益
  • 最大損失

  • 総トレード数
  • 勝ちトレード数
  • 負けトレード数
  • 勝率

  • 勝ちトレードの平均利益
  • 負けトレードの平均損失
  • トレードの平均損益

  • 最大連勝数
  • 最大連敗数
  • 勝ちトレードの平均保有期間
  • 負けトレードの平均保有期間
  • 最大ドローダウン

グラフで下記の表示も行えるようにします。

  • 損益曲線
  • 損益ヒストグラム
  • 月次損益

動作確認

特にインストールするものはありませんので、Sok配下でgit pullしてソースを更新してください。

コマンドを叩き、結果をターミナルに出力するプログラムが追加されています。プログラムの流れはざっとこんな感じです。

Strategyは株価やTraderからのPositionを材料に、Actionを決定します。ActionにはNone(何もしない)Buy、Sellがあります。このActionをTraderは受取って、取引の損益を記録し、Positionの変化を行います。Positionは各々の銘柄に対して、何時からいくらで何枚、BuyまたはSellで持っているという情報です。記録した結果を最後に上記の形式で出力します。

今回使うStrategyは、ほぼほぼ何もしないけども時々適当に売ったり買ったりするという適当なロジックです。

これを用いて実際にバックテストを行ってみます。Sok配下で次のコマンドを叩きます。

bundle exec ruby example/back_test/random_strategy.rb

次のような出力がされます。先にリストした指標が表示されているはずです。

======================================================
net income:               624.0
profit | loss:            2026.0    | -1402.0
pf:                       1.4
max profit | max loss:    173.0     | -220.0
trades | wins | looses:   74     | 38     | 36
wins{%}                   51.4
max series of wins:       6
max series of looses:     8
average span{win}:        25
average span{loose}:      16
max drow down:            -274.0
======================================================

また、Sok配下にdata/sample1というディレクトリが作成され、そこに3つのJPEGとrecordが作成されます。recordは後に各々の取引結果を調査したいときのためのダンプです。今回は使いません。

それぞれのJPEGを開きます。

  • 損益曲線 profit_curve

  • ヒストグラム profit_histgram

  • 月次損益 monthly_profit

ランダムで売買している割にはなかなかな結果ですが、月次で見ると結構忍耐が必要かもしれませんね。最も何回か動かせばマイナスの収益になったりもするのですが。

プログラムはここで確認できます。裏ではワヤクチャやってますが、表面的にはこんなコードです。

require File.expand_path '../../lib/sok', File.dirname(__FILE__)
include Kabu

trader = Trader.new
strategy = Kabu::Strategy::Random.new

code = 1305
soks = Sok.joins(:company).where('companies.code=?',code).order('date')
position = nil

soks.each do |sok|
  action = strategy.decide(code: code, date: sok.date, 
                    close: sok.close, position: position)
  trader.receive [action]
  position = trader.positions.any? ? trader.positions[0] : nil
end

trader.summary
trader.save File.expand_path('../../data/sample1', File.dirname(__FILE__))

まとめ

ツールを作成して実際にバックテストを行ってみました。

ただ、実際この結果がほんまもんかいな?ってところが有るので次回はダンプしていおいたrecordを使って、取引が行われていたときのチャートを確認して行きたいと思います。

Recent Entries
Categories
    Tags
    Archives
    Search