2017/6

前回は、指数を使わずに個別株価でシステムを評価すればパフォーマンスはマシになるのではないか?という仮定に対する検証を行い、確かにその通りのようだという結果を示しました。 今回も引き続き「高勝率システムの考え方と作り方と検証」の検証を行っていきます。

目次

実践

ルール

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

1.トレード対象の銘柄は1株当たり5ドル以上で、200 日単純移動平均線を上回っていること。これによって、長期的な上昇トレンドであることが示される。

2.過去21 日(1カ月)での1日の平均出来高が少なくとも25 万株あること。これで、流動性がある銘柄であることが保証される。

3.100 日HV(ヒストリカルボラティリティ)の値が30 を超える(ヒストリカルボラティリティの定義については付録を参照のこと)。

4.10 日ADX(アベレージ・ディレクショナル・インデックス)の値が30 を超える(ADX の定義については付録を参照のこと)。

5.その銘柄が2日以上、続けて下げて引けていること。

6.今日、その銘柄はY期間移動平均線(Y=4、5、6)よりも少なくともX%(X=4%、5%、6%)下で引けること。この点については、具体例を見れば明らかになるだろう。

7.上のルールが満たされていれば、翌日に今日の終値よりもさらにZ%(Z=4~10%)下に指値を入れて買う。

8.3期間単純移動平均線を上回って引ける日に、終値で手仕舞う。

ローレンス・A・コナーズ他『高勝率システムの考え方と作り方と検証』(PanRolling、2014年)

前回もそうでしたが、場中や引けでのトレードが必要になるため、完全自動化を目指すのであればトレードシステムや、ブラウザの自動化などといったことが必要となります。今のところは手動でそういったことが出来るってことを前提にしておきます。

ストラテジーの構築

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

class HvAdxPb < Strategy

    attr_accessor :closes, :z, :y, :x

    def initialize
      super
      @length = 201
      @t_price = nil
      @z = 0.04
      @y = 4
      @x = 0.04
    end

    def set_up
      super
    end

    def pass?
      super
    end

    def set_env
      super
      @t_price = nil
      if soks[-201..-1]
        @closes = Soks.parse(soks[-201..-2],:close)
        @open = soks[-1].open
        if soks[-1].low < soks[-2].close * (1-@z) and soks[-1].volume > @q / 3
          @t_price = (soks[-2].close * (1-@z)).to_i
        end
      end
    end

    def decide(env)
      if position.nil?
        return none if @t_price.nil?
      end
      if position
        ave = Soks.parse(soks[-3..-1], :close).ave(3)[-1]
        return none if ave > soks[-1].close
        return sell(soks[-1].close,@position.volume)
      else
        ave = closes.ave(200)[-1]
        return none if closes[-1] < ave

        hv = closes[-101..-1].log.dev(100)[-1] * Math.sqrt(365) * 100
        return none if hv < 30

        adx = soks[-25..-2].adx(10,14)[-1]
        return none if adx < 30

        diffs = closes[-3..-1].diff
        return none if not (diffs[-1] < 0 and diffs[-2] < 0)

        uave = closes[-@y..-1].ave(@y)[-1] * (1-@x)
        return none if closes[-1] > uave

        volume = calc_volume(@t_price,0.3)
        return buy(@t_price,volume)
      end
    end
  end
  • 終値 > 200日移動平均
  • ヒストリカルボラティリティ > 30
  • ADX > 30
  • 2日連続陰線
  • 4日移動平均からの乖離率 < -4%

だった場合に当日に前日比 -4%の位置で売買可能ならば買いを仕掛けます。つまり当日に取引があり、安値が前日比 -4%以下を満たしている場合となります。

チャートを書いて、確認をしておきます。

profit_histgram

問題なさそうですね。

ADXをRubyで実装したものが見当たらないので、ついでに紹介しておきます。このメソッドはArrayを継承しているクラスにあることを想定してます。

  def dx(length)
      result = []
      self.each_cons(length+1) do |values|
        pdm, mdm, tr = [], [], []
        values.each_cons(2) do |vs|
          pdm << vs[-1].high - vs[-2].high
          mdm << vs[-2].low - vs[-1].low
          if (pdm[-1] < 0 and mdm[-1] < 0) or pdm == mdm
            pdm[-1], mdm[-1] = 0, 0
          elsif pdm[-1] > mdm[-1]
            mdm[-1] = 0
          elsif pdm[-1] < mdm[-1]
            pdm[-1] = 0
          end
          tr << [vs[-1].high - vs[-1].low, vs[-1].high - vs[-2].close, vs[-2].close - vs[-1].low].max
        end
        ts, ps, ms = tr.sum, pdm.sum, mdm.sum
        pdi = ps / ts * 100
        mdi = ms / ts * 100
        result << (pdi - mdi).abs / (pdi + mdi) * 100
      end
      Soks[*result]
    end

    def adx(n,m)
      result = []
      self.dx(n).each_cons(m) do |dxs|
        result << dxs.sum / length
      end
      Soks[*result]
    end

検証

33業種別株価指数の構成銘柄66個を適当に選んでそれぞれのパフォーマンスを調べてみます。ほとんど取引は行われないため、取引がなかった銘柄については表示していません。期間は2007年から2017年とし、単利での結果です。

Code Income Trades Win PF Average DD
1720 20.92 4.0 100.0 0.0 5.23 0.0
2768 2.39 1.0 100.0 0.0 2.39 0.0
3048 -2.35 1.0 0.0 0.0 -2.35 -2.35
3103 6.56 1.0 100.0 0.0 6.56 0.0
3105 1.85 1.0 100.0 0.0 1.85 0.0
3110 -0.56 1.0 0.0 0.0 -0.56 -0.56
3436 2.96 1.0 100.0 0.0 2.96 0.0
3632 9.14 3.0 66.67 7.54 3.05 -1.4
3656 32.24 7.0 57.14 3.68 4.61 -10.34
4506 6.9 1.0 100.0 0.0 6.9 0.0
4902 -6.08 1.0 0.0 0.0 -6.08 -6.08
5411 1.19 1.0 100.0 0.0 1.19 0.0
5631 9.52 1.0 100.0 0.0 9.52 0.0
5901 -1.52 1.0 0.0 0.0 -1.52 -1.52
6101 29.6 2.0 100.0 0.0 14.8 0.0
7211 9.51 2.0 100.0 0.0 4.76 0.0
7832 8.58 2.0 100.0 0.0 4.29 0.0
8303 4.36 1.0 100.0 0.0 4.36 0.0
8306 3.82 1.0 100.0 0.0 3.82 0.0
8473 24.12 2.0 100.0 0.0 12.06 0.0
8515 -9.29 6.0 83.33 0.7 -1.55 -31.43
平均 2.3 0.6 76.5 0.2 1.2 -0.8
偏差 6.95 1.31 38.83 1.02 3.15 4.08

合計して40回程度しか取引をしていないので、これだけで判断はしきれませんが、勝率76%、平均1.2%とかなりパフォーマンスが高いことがわかります。

次に複利で対象銘柄を広げ、より実運用に近い形でバックテストを行ってみます。

対象銘柄はコードの若い順に並べて10個飛ばしに1つづピックアップし、初期資産は100万円とします。著書のルールにあった出来高も考慮することとします。

期間は2000年から2017年をもちいますが、200日の日足が必要になることから実際は2001年辺りからの取引となります。

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

======================================================
net income:               2912364.5999999973
profit | loss:            9023931.799999999    | -6111567.2
average:                  11511.322529644258
pf:                       1.5
max profit | max loss:    822393.0     | -526104.0
trades | wins | looses:   253     | 182     | 71
wins{%}                   71.9
max series of wins:       18
max series of looses:     6
average span{win}:        2
average span{loose}:      5
max drow down:            -1454140.8
======================================================

200回を超えるトレード回数であっても7割を超える勝率があるので、このシステムに対する信頼度は上がるのではないでしょうか?

次に損益曲線を見てみます。

profit_histgram

今年に入るまでかなり安定した成績であることが分かりますが、今年のドローダウンはちときついですね。

パフォーマンス悪化は、単に資金が増えたからってのでは説明しにくい減り方な気がします。

この本の発売は2014年ですので、手法が広まったことによるものなのかもしれません。

あるいは、このシステムは恐怖心からトレード機会が生まれるはずだという信念がもとになっているため、恐怖心に対する麻痺から、かなり下押してからでないとトレード機会が生まれにくくなっている、というような理由も考えられます。

まとめ

押し目を利用したシステムについて検証を行いました。かなり安定したシステムであることがわかりました。しかし、ここ最近パフォーマンスが悪化しているため、利用するにあたってはひとまずその原因究明が必要かもしれません。

ともあれ、何かと併用しながら運用するのに十分魅力的なシステムだと思います。

前回から「高勝率システムの考え方と作り方と検証」を読み始めています。

前回は窓を利用したシステムに対して33業種別株価指数を用いて検証を行い、優位はないことを確認しました。

しかし、個別の株価で確認を行うと案外優位はあるのでは?という可能性が見えてきました。そこで今回は個別株価を使ってギャップシステムを評価していこうと思います。

利用する銘柄は33業種別株価指数の構成銘柄より2つづつ選んだ66銘柄です。条件は出来高程度で、それなりに出来高があるものを適当にピックアップしました。

33業種別株価指数を用いていたのは、株式の分割併合に対する処理が未実装だったためです。まだ問題は残っていますが、まぁ良い感じになってきました。今まで作ってきたシステムも、あわせて検証してみたいと思います。

目次

実践

検証方法は33業種別株価指数を用いていた時の方法と変わりません。2007年から2017年の株価データを用いて、それぞれの銘柄でバックテストを行い、最後に結果を集計します。

長いですが、結果はこのようになります。

Code Income Trades Win PF Average DD
1322 64.22 193.0 61.14 1.31 0.33 -34.25
1379 29.69 163.0 60.74 1.22 0.18 -30.8
1515 66.98 196.0 59.69 1.23 0.34 -36.27
1605 -25.64 205.0 52.68 0.92 -0.13 -35.65
1720 106.82 188.0 63.3 1.4 0.57 -23.19
1721 71.76 187.0 62.03 1.34 0.38 -20.32
2264 83.51 171.0 63.16 1.5 0.49 -37.48
2269 74.07 120.0 67.5 1.81 0.62 -12.08
2331 53.13 184.0 66.85 1.28 0.29 -22.06
2379 26.77 186.0 60.22 1.06 0.14 -48.75
2768 15.89 167.0 58.08 1.06 0.1 -21.52
3048 -70.83 127.0 57.48 0.71 -0.56 -37.81
3092 145.0 169.0 60.36 1.57 0.86 -27.15
3101 29.67 129.0 55.04 1.18 0.23 -24.33
3103 66.83 68.0 57.35 2.0 0.98 -23.38
3105 1.28 207.0 61.84 1.0 0.01 -44.4
3110 -26.77 178.0 56.74 0.92 -0.15 -36.22
3231 19.3 205.0 59.02 1.06 0.09 -45.83
3407 50.82 192.0 58.85 1.26 0.26 -22.08
3436 -2.29 227.0 56.39 1.0 -0.01 -37.61
3632 28.95 164.0 58.54 1.11 0.18 -18.62
3656 -34.58 104.0 53.85 0.88 -0.33 -33.73
3861 31.41 161.0 62.73 1.16 0.2 -18.6
3863 -6.07 72.0 58.33 0.93 -0.08 -14.96
4021 76.13 200.0 59.0 1.33 0.38 -39.42
4503 72.45 186.0 66.67 1.47 0.39 -16.92
4506 48.7 168.0 64.29 1.25 0.29 -23.27
4543 93.06 203.0 67.49 1.43 0.46 -37.82
4902 40.4 221.0 65.16 1.12 0.18 -49.85
5002 1.36 199.0 58.29 1.0 0.01 -33.17
5017 -141.13 189.0 53.44 0.7 -0.75 -67.14
5105 -67.97 190.0 52.11 0.83 -0.36 -35.08
5110 103.96 207.0 62.8 1.41 0.5 -24.82
5202 -17.02 162.0 58.02 0.95 -0.11 -31.28
5411 -146.63 207.0 53.62 0.66 -0.71 -33.16
5413 -2.97 85.0 57.65 0.98 -0.03 -25.12
5631 -101.53 223.0 56.5 0.74 -0.46 -77.6
5802 19.43 225.0 59.11 1.07 0.09 -39.4
5803 39.09 202.0 60.4 1.13 0.19 -20.34
5901 115.97 209.0 67.94 1.5 0.55 -21.92
6101 -63.94 205.0 53.66 0.85 -0.31 -45.11
7181 6.22 24.0 62.5 1.19 0.26 -14.68
7203 0.55 229.0 59.83 1.0 0.0 -23.53
7211 -37.94 158.0 55.7 0.85 -0.24 -30.39
7701 113.89 223.0 67.26 1.52 0.51 -18.02
7832 75.75 188.0 63.83 1.38 0.4 -32.48
7867 -50.22 160.0 53.13 0.79 -0.31 -30.39
8001 -55.04 195.0 54.87 0.81 -0.28 -16.11
8253 11.05 206.0 57.28 1.03 0.05 -41.52
8303 31.03 154.0 58.44 1.11 0.2 -35.72
8306 -9.37 218.0 56.88 0.97 -0.04 -22.66
8473 -6.54 235.0 60.43 0.99 -0.03 -43.7
8515 -156.8 200.0 53.5 0.77 -0.78 -65.4
8601 5.92 217.0 56.22 1.02 0.03 -26.17
8750 6.1 153.0 58.17 1.03 0.04 -19.02
8801 92.39 224.0 60.71 1.3 0.41 -25.56
9001 5.28 150.0 53.33 1.04 0.04 -26.97
9006 63.97 161.0 66.46 1.56 0.4 -19.66
9101 -45.31 178.0 56.18 0.86 -0.25 -35.35
9107 -156.87 185.0 49.73 0.64 -0.85 -37.85
9201 64.43 78.0 69.23 2.18 0.83 -10.22
9202 -20.72 141.0 52.48 0.86 -0.15 -27.16
9302 -33.94 173.0 50.87 0.87 -0.2 -18.43
9303 47.71 200.0 55.5 1.21 0.24 -21.39
9504 -31.69 165.0 60.61 0.87 -0.19 -34.28
9531 84.41 174.0 64.37 1.57 0.49 -18.78
平均 13.2 176.3 59.2 1.1 0.1 -30.7
偏差 65.97 42.31 4.62 0.31 0.38 12.73

33業種別株価指数を用いてたときとは比べ物にならないですね。平均利益あと4倍は欲しいところですが。

ギャップを利用したシステムは、局所的なエッジに対して仕掛けるため、指数に平滑化してしまうと優位がなくなってしまっているのかもしれません。

SMA65CC3

SMA65CC3は65日の移動平均線を3日連続で上回っていたら仕掛けるシステムです。仕切りは仕掛けと一致するドテンシステムです。

長いとブログシステムに怒られたので、詳細をカットして平均と偏差のみ表示します。

Code Income Trades Win PF Average DD
平均 27.0 60.1 29.6 1.1 0.6 -59.4
偏差 140.76 14.63 5.89 0.47 2.65 22.77

こちらもバラつきはありますが、33業種別株価指数をもちいたときよりマシになっています。中には800%なんて利益を上げてる銘柄もありました。2379ディップ。定位からの上昇をつかむのはクロスシステムの醍醐味と行ったところでしょうか。

CB-PB

CB-PBは過去7日以内に20日高値をつけ、かつ3日以内に5日安値をつけていれば仕掛けるシステムです。仕切りのパターんがいくつかある中で、20日高値に戻ったら仕切るパターンをもちいています。

Code Income Trades Win PF Average DD
平均 -32.8 74.5 66.1 0.9 -0.4 -41.3
偏差 59.53 15.24 6.99 0.27 0.84 17.63

勝率の高さは相変わらずですが、これは使えませんね。

KAMA-PB

最後にKAMAをもちいたプルバックシステムです。これは自作のシステムで、33業種別株価指数をもちいた検証で十分に満足の行く結果が得られているものです。

また他にあれこれと検証を行った結果十分に実践投入が可能だろうと踏んでいるシステムです。

KAMAは指数平滑化平均の一種で、効率レシオによって平滑化の度合いが変動します。そのため上昇、下降局面で階段状のチャートを描く特徴があります。

このシステムは10日KAMA-STCが100または0かつ、KAMAの偏差0.5バンド以内で売買を仕掛け、仕切りはトレーリングストップを置きます。

Code Income Trades Win PF Average DD
平均 88.8 78.1 59.7 1.7 1.2 -30.4
偏差 79.26 16.61 6.33 0.63 1.11 11.96

個別銘柄でも申し分ない出来だと思います。バグが怖いですね。。。

まとめ

個別銘柄で検証を行うと、幾つかのシステムは33業種別株価指数による検証に対してパフォーマンスが上がっていることがわかりました。指数にすると銘柄ごとに発生したエッジが平滑化されて薄れている可能性があるのでしょう。

特にギャップを利用したシステムはこの傾向が強いと思われます。逆に、指数で良い結果の得られていたシステムは個別株価における検証でも十分な結果を示すことが確認できました。

著書にはこのシステムに対して、ギャップの下に指値で注文した時の効果や、RSI、平均などの効果について詳細な検証結果が示されていますが、ギャップを利用したシステム検証は一旦これで終わりにしたいと思います。

今回から「高勝率システムの考え方と作り方と検証」を読み進めていこうと思います。

先ずはじめに検証していくシステムはギャップ、窓開けを利用したものです。検証方法は今までと同様に33業種別株価指数を用いて行います。

目次

実践

ルール

著書のルールはこのようなものです。

1.私たちは2001 ~2012 年に取引されていた銘柄のうちで、1株5ドル以上で、過去21 取引日(1カ月)に少なくとも平均100 万株の出来高があるものをすべて調べた。こうするのは流動性がある銘柄だけを調べるためだ。

2.当日の2期間RSI の値が5以下で引ける。これで、その銘柄が売られ過ぎというシグナルになる(この水準を極端な売られ過ぎと見る人もいる)。RSI についてもっと知りたい人は、付録を参照してほしい。

3.翌日に、下にギャップを空けて寄り付けば(つまり、翌日の始値が今日の安値よりも安ければ)、寄り付きでその銘柄を買う。

4.あとの章ではいくつかの手仕舞いポイントを見ていくつもりだが、ここでの検証では、その銘柄が3期間移動平均線を上回って引けたら、大引けで手仕舞うことに

ローレンス・A・コナーズ他『高勝率システムの考え方と作り方と検証』(PanRolling、2014年)

ルールは基本的に上記の通りに作ります。しかし、1.については扱うデータが指数であるため、基本的に流動性はあるものだとして、無視します。

システムの構築

リファクタリングを行いましたので、今までと記法が若干変わります。幾分マシになったと思います。コードは下記のとおりです。

class Gap < Strategy

    attr_accessor :closes, :open

    def initialize
      @length = 4
    end

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

    def decide(env)
      if position
        ave = closes[-3..-1].ave(3)[-1]
        if ave < closes[-1]
          return Action::Sell.new(code,date,closes[-1],1)
        end
      else
        gap_down = soks[-2].low > soks[-1].open
        rsi = closes[-3..-2].rsi(2)[-1]
        if rsi < 5 and gap_down
          return Action::Buy.new(code,date,open,1)
        end
      end
      return Action::None.new(code,open)
    end
  end

チャートを書いてみます。

profit_histgram

良さそうですね。ストップロスをおいてないので、下記のようなトレードもいくつかあります。

profit_histgram

検証

2007年から2017年の33業種別株価指数を用いて個別にバックテストを行います。結果は下記のとおりです。

Code Income Trades Win PF Average DD
I201 5.61 207 62.32 1.03 0.03 -20.69
I202 -63.44 232 55.6 0.8 -0.27 -38.21
I203 -33.49 222 58.56 0.86 -0.15 -17.84
I204 19.73 215 61.4 1.13 0.09 -26.23
I205 -51.1 216 57.87 0.77 -0.24 -24.43
I206 -71.82 196 58.16 0.72 -0.37 -20.02
I207 -18.97 232 60.78 0.91 -0.08 -23.05
I208 -19.21 213 56.81 0.89 -0.09 -22.66
I209 -91.36 210 59.05 0.69 -0.44 -34.54
I210 -80.11 229 53.28 0.74 -0.35 -34.69
I211 -55.63 234 58.55 0.81 -0.24 -39.97
I212 -168.23 225 51.11 0.58 -0.75 -21.94
I213 -76.3 237 54.01 0.76 -0.32 -37.16
I214 0.31 228 64.47 1.0 0.0 -23.05
I215 -72.11 243 57.61 0.77 -0.3 -29.59
I216 -22.5 251 62.15 0.91 -0.09 -35.14
I217 -131.29 242 57.02 0.6 -0.54 -50.09
I218 -39.33 228 64.91 0.83 -0.17 -31.54
I219 -57.36 235 58.3 0.78 -0.24 -35.18
I220 -56.78 197 58.38 0.76 -0.29 -57.3
I221 -11.47 202 59.41 0.93 -0.06 -17.52
I222 -220.58 226 49.12 0.52 -0.98 -46.51
I223 15.35 207 59.42 1.09 0.07 -23.22
I224 -38.71 233 58.37 0.85 -0.17 -24.71
I225 -24.13 199 55.78 0.86 -0.12 -13.91
I226 -124.51 231 56.71 0.61 -0.54 -40.16
I227 -41.08 212 56.13 0.79 -0.19 -15.82
I228 -120.33 225 52.89 0.66 -0.53 -30.22
I229 -211.01 238 46.64 0.53 -0.89 -71.6
I230 -86.99 219 55.25 0.72 -0.4 -38.89
I231 -145.66 228 54.39 0.64 -0.64 -34.43
I232 -87.75 232 55.17 0.75 -0.38 -48.73
I233 -0.48 224 62.95 1.0 -0.0 -25.24
平均 -66.1 223.0 57.3 0.8 -0.3 -31.9
偏差 59.47 13.71 4.0 0.15 0.26 12.56

勝率は57%と高めですが、とても使えるものではないですね。著書の結果では勝率は75%、平均損益も0.7%とかなり良いシステムなのですけども。個別銘柄を対象にした場合は結果が変わるのかもしれません。

養命酒2540を見てみます。2000年から2017年のデータを使っています。

======================================================
2540
net income:               170.8798905150817
profit | loss:            328.618207829292    | -157.73831731421015
average:                  0.5974821346681178
pf:                       2.1
max profit | max loss:    11.243243243243244     | -10.964083175803403
trades | wins | looses:   286     | 204     | 82
wins{%}                   71.3
max series of wins:       14
max series of looses:     5
average span{win}:        1
average span{loose}:      3
max drow down:            -13.085295297015524
bunkrupt:                 0.0
======================================================

かなりいい成績ですね。損益曲線を調べてみます。

profit_histgram

途中スランプはあったとしても、長期に渡って有効であるように感じます。

一方で、ソフトバンク9984を見てみますと、次のようになります。

======================================================
9984
net income:               -127.40634943983551
profit | loss:            726.6759200390887    | -854.0822694789244
average:                  -0.33006826279750134
pf:                       0.9
max profit | max loss:    17.22488038277512     | -29.369627507163326
trades | wins | looses:   386     | 232     | 154
wins{%}                   60.1
max series of wins:       13
max series of looses:     8
average span{win}:        1
average span{loose}:      4
max drow down:            -118.06053901093593
bunkrupt:                 0.0
======================================================

損益曲線を見てみます。

profit_histgram

なんだこれは。。。 序盤の暴落を抜けたあとは、比較的良いトレードを行っているようですが、序盤何に一体何があった?

チャートを見てみると、エグい暴落局面を拾っているようですね。

profit_histgram

profit_histgram

連日のストップ安なんかの局面はルールの1.を加えることで回避できた可能性もあります。

まとめ

ギャップを利用したシステムについて検証を行いました。33業種別株価指数ではあまり優位な結果を得ることができませんでしたが、個別に見ていくと良い成績が得られる可能性があることがわかりました。

やっぱシストレ本は読んでて本当に意味があるんかいなと懐疑的になってきますね。確かに評価方法を知れたのは収穫だったのですけども。

人が関わる以上過去のチャートの中に痕跡が残る、あるいはその痕跡をもとにトレードを行っているはずで、その中に優位な痕跡を見つけることができれば数打つことで優位を保証できるというのがシストレの大前提です。

問題なのは、そのパターンを移動平均やら、いくつかの指標を使って上手く抜けているかかどうか?ってところになると思います。

何とかしてパターンを特定しようとするわけですが、複数市場を対象にするだけでその特定が一気に困難になりました。

そこで一旦指標から離れて、もっと正確にパターンを抜くことを目標としていこうと思います。

パターン分類をしようとするとなると、クラスタ分析なんてのが良さげですが、今回はもっと単純に始めてみたいと思います。

テンプレートを作成し、それと規格化された株価との2乗距離を測定することでテンプレートに近いチャートの抜き出しを行いたいと思います。

では、さっそく参りましょう。

目次

実践

パターン抽出方法の説明

パターンの抽出は以下の方法で行います。

  1. テンプレートの作成
  2. 株価の規格化
  3. テンプレートと規格化された株価の二乗距離の測定
  4. 距離が規定値以下かどうかを判定

テンプレートの作成

テンプレートは0から1の間の50日分の点で作成します。例えば急落局面のテンプレートを作成しようとした場合は、次のようなグラフで書ける50個の点を用意することになります。

profit_histgram

株価の規格化

テンプレートは0〜1の値ですので、それに合わせて株価も0〜1の値を取るように弄くります。単純に50日間の株価の中で最小となる値を0、最大となる値を1として規格化を行います。式で書けば以下のようになります。

(株価t - 株価.MIN) / (株価.MAX - 株価.MIN)

テンプレートと規格化された株価の二乗距離の測定

あとはテンプレートと、株価との距離を測定します。各日において求めた二乗誤差の集計が二乗距離となります。つまり、

Σt50(テンプレートt - 規格化株価t)2

最後にこの距離が規定値以下の場合はテンプレートと同じ株価だと判定して終わりです。

コードにすると下記のようになります。

class Pattern
    attr_accessor :pattern, :thr

    def initialize(pattern)
      @thr = 1.5
      @pattern = Soks[*pattern]
    end

    def correspond?(target)
      size = @pattern.length
      min = target[-size..-1].min
      reg = target[-size..-1].max - min
      ts = target[-size..-1].map {|t| (t-min).to_f / reg}
      di = 0
      ts.zip(pattern).each do |t,p|
        di += (t - p) ** 2
      end
      di = Math.sqrt(di)
      @thr > di
    end
  end

実際にどういった株価が抽出されるのかを確認してみます。☓までがパターン一致部分でそれから20日ぶん表示してあります。

profit_histgram

N日検定

これで抜き出された株価は、指標を用いたシグナル判定に比べ、より似た値動きをしているはずです。

N日検定を用いて、その優位性を確認してみましょう。データは2007年から2017年の33業種別株価指数を用います。

ルールは次のようになります。

  • パターン判定されるかつ10日安値をつける。
  • 翌日の初値で売り。
  • N日経過で仕切り。

用いたパターンは上記のものです。コードにすれば下記のようになります。

      Pattern.new(40.times.map {|i| i.to_f/39} +
                  10.times.map {|i| 1.0 - i.to_f/9})
Code 5 10 15 20 30 50
I201 0.47 0.50 0.58 0.33 0.45 0.50
I202 0.58 0.45 0.45 0.36 0.18 0.36
I203 0.60 0.67 0.58 0.50 0.25 0.58
I204 0.33 0.44 0.33 0.33 0.29 0.33
I205 0.73 0.60 0.70 0.70 0.70 0.78
I206 0.43 0.44 0.53 0.47 0.35 0.43
I207 0.64 0.58 0.58 0.75 0.58 0.50
I208 0.57 0.67 0.50 0.58 0.50 0.50
I209 0.44 0.50 0.62 0.46 0.62 0.64
I210 0.38 0.33 0.43 0.43 0.43 0.50
I211 0.53 0.54 0.62 0.54 0.46 0.55
I212 0.72 0.64 0.69 0.62 0.54 0.58
I213 0.50 0.59 0.59 0.59 0.47 0.56
I214 0.50 0.50 0.54 0.54 0.54 0.77
I215 0.63 0.67 0.67 0.53 0.67 0.67
I216 0.54 0.80 0.70 0.50 0.60 0.67
I217 0.53 0.62 0.69 0.62 0.54 0.54
I218 0.38 0.43 0.38 0.46 0.42 0.50
I219 0.40 0.33 0.33 0.25 0.18 0.27
I220 0.73 0.43 0.36 0.43 0.29 0.55
I221 0.38 0.42 0.50 0.67 0.42 0.58
I222 0.56 0.50 0.50 0.50 0.43 0.55
I223 0.50 0.50 0.58 0.42 0.36 0.78
I224 0.46 0.58 0.42 0.42 0.50 0.70
I225 0.64 0.60 0.60 0.40 0.40 0.56
I226 0.39 0.63 0.56 0.56 0.44 0.54
I227 0.63 0.67 0.67 0.64 0.57 0.62
I228 0.43 0.60 0.50 0.50 0.50 0.60
I229 0.73 0.55 0.64 0.55 0.45 0.60
I230 0.61 0.57 0.43 0.50 0.57 0.54
I231 0.90 0.75 0.63 0.75 0.50 0.75
I232 0.58 0.36 0.45 0.64 0.64 0.36
I233 0.43 0.33 0.33 0.25 0.36 0.36
平均 0.53 0.53 0.53 0.50 0.45 0.54

N日検定はN日によらず勝率55%を超えることで、その仕掛けの優位を確認する検定です。この結果はそれに到底及びませんが、私が行ってきた今までの結果からすればましな結果になっています。

少しは期待できるかもしれません。

検証

仕切りを10日安値をつけたら買い戻しとして、バックテストを行ってみます。データは同じものを使います。

Code Income Trades Win PF Average DD
I201 10.13 12 50.0 1.51 0.84 -10.63
I202 5.17 11 27.27 1.14 0.47 -23.81
I203 1.58 12 41.67 1.09 0.13 -5.87
I204 -15.23 17 29.41 0.53 -0.9 -11.25
I205 14.61 10 60.0 2.54 1.46 -8.33
I206 -12.98 17 35.29 0.73 -0.76 -32.12
I207 24.36 12 50.0 2.63 2.03 -12.35
I208 -0.73 12 41.67 0.96 -0.06 -12.62
I209 -4.36 13 38.46 0.89 -0.34 -16.95
I210 -45.29 15 13.33 0.26 -3.02 -28.76
I211 -9.7 13 38.46 0.69 -0.75 -13.08
I212 51.82 13 61.54 3.18 3.99 -9.09
I213 -4.72 17 52.94 0.89 -0.28 -12.28
I214 6.66 14 50.0 1.27 0.48 -9.3
I215 19.62 16 43.75 1.76 1.23 -14.1
I216 30.7 10 60.0 4.22 3.07 -7.03
I217 7.52 13 46.15 1.37 0.58 -15.51
I218 -5.7 13 23.08 0.78 -0.44 -14.66
I219 -14.37 12 25.0 0.61 -1.2 -19.33
I220 -19.41 14 42.86 0.41 -1.39 -20.21
I221 -5.18 12 33.33 0.72 -0.43 -15.04
I222 -1.14 14 28.57 0.97 -0.08 -35.81
I223 -13.51 12 33.33 0.44 -1.13 -19.11
I224 -11.08 12 25.0 0.61 -0.92 -12.59
I225 4.46 10 60.0 1.41 0.45 -4.56
I226 23.56 16 50.0 1.73 1.47 -18.2
I227 20.65 15 53.33 2.24 1.38 -4.71
I228 27.83 10 40.0 2.09 2.78 -10.95
I229 34.16 11 63.64 2.38 3.11 -9.03
I230 1.31 14 50.0 1.05 0.09 -9.38
I231 24.53 8 75.0 4.97 3.07 -4.31
I232 -7.56 11 45.45 0.78 -0.69 -20.24
I233 -6.93 12 33.33 0.61 -0.58 -6.78
平均 4.0 12.0 43.1 1.4 0.4 -14.2
分散 18.85 2.24 13.58 1.08 1.54 7.55

トレード数が少ないのでなんとも言い難いですが、期待していたほどではありませんね。

まとめ

二乗距離を用いて、テンプレートに一致するかの判定を行い、仕掛けのルールに組み込んでみました。ものは試しに程度のことしか行っていませんが、いじり方によっては期待ができそうな気はします。

前回はVIDYAという指標を使ってシステムの検証を行っていました。この指標は次のようにかけました。

VIDYAt = VIDYAt-1 + a (CLOSE - VIDYAt-1)

このaには短期分散と長期分散の比率が入っていて、比率が高いほど誤差が反映される仕組みになっています。ですので、終値がVIDYAを頻繁に交差するような場合は、そのたびにVIDYAが方向を変えます。それは少し困るので、もうちょっと賢いフィルタを導入することで、どうにかできないかと思いまして、カルマンフィルタを実装していくことにしました。カルマンフィルタを導入すると上記の式は、

予測値t = 予測値t-1 + a (観測値 - 予測値t-1)

みたいな感じでかけて、aは予測値と観測値のそれぞれの誤差の内分比で評価されることになります。例えば株価の場合は観測値に終値を用い、予測値には移動平均を用いて見ることなんてことが出来るかもしれません。これまでの指標と同様の式の形をしていますので、予測値の誤差が大きい場合は観測値の値が反映され、観測値の誤差が大きい場合は予測値の値が反映されることになります。

詳細な説明は他の多くのサイトをご覧ください。なんでみんなあんなに上手に説明できるんだろう。。。

目次

実践

実装

愚直にWikipediaをコーディングしていきます。カルマンフィルターの項に予測と更新の節があり、そこに更新式が説明されています。いかつい式ですが、コードにしてしまえばさっぱりしていますね。行列の計算はNumo::NArrayを利用させてもらいましたが、逆行列のためのLinalgが導入できない悲しい環境でしたので、そこだけMatrixを使っています。

class Kalman
  include Numo

  attr_accessor :ft0, :pt1, :qt0, :gt0, :xht1, :ht0, :rt0

  def predict(ut0)
    ut0 = 0 if not ut0
    @xht0 = @ft0.dot(@xht1) + ut0
    @pt0 = @ft0.dot(@pt1).dot(@ft0.transpose)+@gt0.dot(@qt0).dot(@gt0.transpose)
    [@xht0, @pt0]
  end

  def update(zt0)
    @xht1 = @xht0 + kt0.dot(et0(zt0))
    ktht = kt0.dot(@ht0)
    @pt1 = (Int32.eye(*ktht.shape) - ktht).dot(@pt0)
    [@xht1, @pt1]
  end

  def observe(zt0, ut0=nil)
    predict(ut0)
    update(zt0)
  end

  def et0(zt0)
    zt0 - @ht0.dot(@xht0)
  end

  def st0
    @rt0 + @ht0.dot(@pt0).dot(@ht0.transpose)
  end

  def kt0
    @pt0.dot(@ht0.transpose).dot(Matrix[*st0.to_a].inv.to_a)
  end
end

サンプル1

まずはWikipediaにある、設定例を参照して1次元のトロッコ問題の設定を行っていきます。設定っていうのは行列F、G、Q、H、R、Pを決めて行くことです。これらの行列は式が決まらない限り設定できませんので、Wikipediaを見ながらご確認ください。 コードにするとこのようになります。

Bundler.require
include Numo

sa = 0.1
sz = 0.1
dt = 0.01

dir = File.expand_path '../../data/kalman/', File.dirname(__FILE__)
FileUtils.mkdir_p dir
file = dir + '/sample1.jpeg'

f = DFloat.new(2,2).fill 0
f[0,true] = [1, dt]
f[1,true] = [0, 1]

g = DFloat.new(2,1).fill 0
g[0,0] = dt**2 / 2
g[1,0] = dt

q = DFloat.new(2,1).fill 0
q[0,0] = sa
q[1,0] = sa

h = DFloat.new(1,2).fill 0
h[0,true] = [1,0]

r = DFloat.new(1,1).fill 0
r[0,0] = sz


kalman = Kalman.new
kalman.ft0 = f
kalman.gt0 = g
kalman.qt0 = q
kalman.ht0 = h
kalman.rt0 = r

これで設定が完了です。続いて実際に乱数を生成してどのようにフィルタリングされるかを確認します。

obs = []
trs = []
inp = DFloat.new(1,1).fill 0
x = []
1000.times do |i|
  inp[0,0] = Random.rand
  out, p = kalman.observe inp
  obs << inp[0,0]
  trs << out[0,0]
  x << i
  puts "#{i}\t#{inp[0,0].round(2)}\t#{out[0,0].round(2)}"
end

Numo.gnuplot do
  reset
  set terminal: 'jpeg'
  set output:  file
  set grid: true
  plot [x, obs, with: :lines, title: 'observe', lc: "'salmon'"],
    [x, trs, with: :lines, title: 'true', lc: "'blue'", lt: 0]
end

obsが観測値でtrsが真の値などと呼ばれる予測値になります。Inpは0〜1のランダム値でそれを位置情報として観測しています。観測は誤差のため、かなりギザ付いた感じなんですね。その観測誤差を考慮して実際の位置を予測した結果が真の値として帰ってきます。実際にグラフを確認してみます。

profit_histgram

なんとなーくそれっぽい結果が帰ってきてますね。はじめは予測値の誤差がでかいため、大きく振れてますが次第にそれっぽい位置に落ち着いています。

サンプル2

株価の場合はどうするのかと言うことになるので、適当にモデルを作って確認してみましょう。

Averaget = Averaget-1 + ut

Close`t = Averaget + vt

ut = a(Closet-1 - Averaget-1)

Averageは予測でCloseは観測、制御ベクトルutは1期前のAverageとCloseの差です。vtはノイズです。これでF,Hは要素が1のみの1行1列の行列になることがわかります。G,R,Qは適当に設定して、下のコード のようになりますので、それを走らせて確認してみます。

Bundler.require
include Numo
include Kabu

sa = 1
sz = 50
alpha = 0.1
count = 200

dir = File.expand_path '../../data/kalman/', File.dirname(__FILE__)
FileUtils.mkdir_p dir
file = dir + '/sample2.jpeg'

f = DFloat.new(1,1).fill 0
f[0,0] = 1

g = DFloat.new(1,1).fill 0
g[0,0] = 1

q = DFloat.new(1,1).fill 0
q[0,0] = sa

h = DFloat.new(1,1).fill 0
h[0,0] = 1

r = DFloat.new(1,1).fill 0
r[0,0] = sz


com = Company.find_by_code 'I201'
closes = Soks.parse(com.soks.order(:date).limit(count+10), :close)

kalman = Kalman.new
kalman.ft0 = f
kalman.gt0 = g
kalman.qt0 = q
kalman.ht0 = h
kalman.rt0 = r

kalman.pt1 = DFloat.new(1,1).fill closes[0..10].dev(11)[-1]
kalman.xht1 = DFloat.new(1,1).fill closes[10]

obs = []
trs = []
inp = DFloat.new(1,1).fill 0
ut = DFloat.new(1,1).fill 0
x = []
closes[11..-1].each_with_index do |c,i|
  inp[0,0] = c
  ut[0,0] = alpha * (c - closes[i-1])
  out, p = kalman.observe inp, ut
  obs << inp[0,0]
  trs << out[0,0]
  x << i
  puts "#{i}\t#{inp[0,0].round(2)}\t#{out[0,0].round(2)}"
end

Numo.gnuplot do
  reset
  set terminal: 'jpeg'
  set output:  file
  set grid: true
  plot [x, obs, with: :lines, title: 'observe', lc: "'salmon'"],
    [x, trs, with: :lines, title: 'true', lc: "'blue'", lt: 0]
end

profit_histgram

移動平均とのクロスに対して、鈍感になっていることは確認できますね。

サンプル3

最後に重みを予測値にして、AR(1)の重みを推定させてみます。AR(1)は

Xt = w1 Xt-1 + w0 + u

と書け、wiが重み、uはノイズです。この場合は行列Hに過去の株価を設定し、重みwを予測します。株価の前日比をrtとして

xt = xt-1

rt = w0 + w1 rt-1 + ut

xt = [w0, w1]T

こんな感じに書けるので、Fが2行2列の単位行列で、Hは1行2列の、要素に1と前日の前日比が入った行列であれば良いことがわかります。Q,R,Gは式を満たすように適当に設定します。 コードはこのようになります。

Bundler.require
include Numo
include Kabu

sz = 0.01
sa = 1
p_ = 1
count = 200

dir = File.expand_path '../../data/kalman/', File.dirname(__FILE__)
FileUtils.mkdir_p dir
file = dir + '/sample3.jpeg'

f = DFloat.new(p_+1,p_+1).eye

g = DFloat.new(1,1).fill 0

q = DFloat.new(1,1).fill 0

h = DFloat.new(1,p_+1).fill 0

p = DFloat.new(p_+1,p_+1).eye

r = DFloat.new(1,1).fill 0
r[0,0] = sz

x = DFloat.new(p_+1,1).fill(0)

com = Company.find_by_code 'I201'
closes = Soks.parse(com.soks.order(:date).limit(count+10), :close)
logs = closes.log

kalman = Kalman.new
kalman.ft0 = f
kalman.gt0 = g
kalman.ht0 = h
kalman.qt0 = q
kalman.rt0 = r
kalman.pt1 = p
kalman.xht1 = x

obs = Soks.new
trs = Soks.new
inp = DFloat.new(1,1).fill 0
ut = DFloat.new(1,1).fill 0
x = []
logs.each_cons(p_+1).to_a.each_with_index do |c,i|

  kalman.ht0[0,true] = [1] + c[0..-2]
  inp[0,0] = c[-1]

  out, pt = kalman.observe inp
  obs << inp[0,0]
  trs << out[0]
  x << i
  puts "#{i}\t#{inp[0,0].round(2)}\t#{out[true,0].to_a.join("\t")}"
end

Numo.gnuplot do
  reset
  set terminal: 'jpeg'
  set output:  file
  set grid: true
  set y2tics: true
  set ytics: :nomirror
  plot [x, obs[40..-1].cumu, with: :lines, title: 'observe', lc: "'salmon'"],
    [x, trs[40..-1], with: :lines, axes: :x1y2, title: 'true', lc: "'blue'", lt: 0]
end

私が興味があるのは、w0のドリフト項ですので、それと前日比の累積値をプロットしてみます。

profit_histgram

うーん。これはなんか微妙ですねー。

まとめ

とりあえず作って動かしてみましたよ。ただ、ぼんやりとした評価しかできないあたりに、作っては見たもののってところの限界を感じます。バックテストに落としこんで結果が出れば早いのですが、今のところ、で?って感じにしかなりません。

この辺をかじりだすと時系列統計学なんてあたりに手を出さなきゃならんのですかね。でも、それはシステムにどう落とし込めばいいのかが分からないので結局行き詰まりになりそうな気もするところです。

みなさんはふるさと納税してますか?私は去年か一昨年だかに初めてやってみまして、山形県寒河江市の米、はえぬきですかね、を返礼品として頂きました。一人暮らしの我が家にドカドカと米が届きまして、当面米に困ることはありませんでした。お米は美味しいのでおすすめです。まだやったことのない方は調べてみてはいかがでしょうか?

さて、このサイトはRubyでバックテストツールを作成し様々なストラテジーを検証することを目的としています。前回はKAMAを用いたシステムを著書とは別の方法で作成し検証を行いました。検証したシステムはまだまだ改善の余地がありましたが、その方向性は悪くなかったと思います。

KAMAは指数平滑移動平均の平滑化定数をボラティリティによって指定の範囲で増減させる指標でした。今回はVIDYAという似たような別の指標を使ってシステムを構築し検証していきたいと思います。

では参りましょう。

目次

実践

VIDYAとは

ぱっとVIDYAを検索かけてみると、なかなか情報が錯綜しているように見えますね。この辺はあまり馴染みがなさそうに見受けられます。VIDYA(Variable Index Dinamic Average)は1992年にシャンデ氏が発表した指標とされています。株価のボラティリティによって平滑化定数が変動する指数平滑化移動平均の一つです。ボラティリティをVI、平滑化定数をαとして、次のようにかけます。

VIDYA = 前日VYDIA + α VI (終値 - 前日VIDYA)

このVIには色々な指標を用いることが出来るのですが、ここに標準偏差の比率を選択すると一般的にVIDYAと呼ばれるようです。すなわち

VI = STDEV(終値,短期期間) / STDEV(終値,長期期間)

となります。またαは平滑化定数ですので、平滑化の期間をNとして下記のようにかけます。

α = 2 / ( 1 + N)

後になってシャンデ氏がVIにCMO(Chande Momentum Ocillator)を用いるようになります。この場合はVMA(Variable Moving Average)などと呼ぶようです。今回はVIDYAを利用するのでCMOについては説明しませんが(知ってもいませんが)、VIは0〜1の値である必要があるため、CMOを利用する場合は絶対を取って100でわる必要があります。あるいは標準偏差の比率を用いた場合は1を超える場合があるので、1にクリップする必要があります。またVIに前回のKAMAで利用したER(Effeciency Racio)を用いることなどもできます。

式を眺めますと、VIが1に近づいた場合はN日の指数平滑移動平均に近づき、VIが0に近づいた場合は∞日の指数平滑化移動平均に近づくことがわかります。つまり、前日VIDYAと一致します。

KAMAには平滑化の期間に上限、下限があったのに対して、VIDYAには下限のみが存在することになります。

名前なんかどうでもいいんだよ計算できりゃー。てなわけで次に進みましょう。

売買ルール

著書には明確にルールが説明されていませんが、チャートを見ていると終値とVIDYAのクロスで売買しているようです。著書に従ってストラテジーを構築します。と言いたいところですが、パラメータが明示されてないですね。。。Nには39日を設定したとありますので、それを利用しますが、標準偏差の期間については明示されていません。適当に7と39くらいにしときますか。 一応ルールを書けば下記のようになります。

  • 終値 > VIDYA ならば どてん買い
  • 終値 < VIDYA ならば どてん売り
  • 翌日初値で注文
  • ロスカットなし

ちなみにこの著者はNを39にした理由を条件を一致させるためとしていますが、KAMAの場合は上限を36日(≒39)に設定しVIDYAの場合は下限を39日に設定していることになるため、条件は一致していません。ですが著書にはこれで良い結果が出たと書いてあるので同じように39日を使います。

ストラテジーの構築

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

class Vidya

    attr_accessor :length, :l_len, :s_len

    def initialize
      @length = 40
      @l_len = 39
      @s_len = 7
      @alpha = 2.0 / (1 + 39)
      @vidya = nil
    end

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

    def setup
      @vidya = nil
    end

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

      @vidya = calc_ave(closes)
      is_buy = closes[-1] > @vidya
      is_sell = closes[-1] < @vidya

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

      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

    def calc_ave(closes)
      if @vidya.nil?
        @vidya = closes[-1]
      else
        sdev = closes[-@s_len..-1].dev(@s_len)
        ldev = closes[-@l_len..-1].dev(@l_len)
        vi = sdev / ldev
        vi = 1 if vi > 1
        @vidya = @vidya + @alpha * vi * (closes[-1] - @vidya)
      end
      @vidya
    end
  end

チャートを書いていきます。終値とのクロスですので非常に騙しが多いですが、豪快に取れるときは素晴らしいですね。

profit_histgram

profit_histgram

あとは、期待するわけでもなく同じ手順を繰り返していきます。

N日検定

N日検定を行った結果は下記です。2007年から2017年の期間で33業種別株価指数を用いて検定を行っています。Nは5,10,15,20,30,50です。

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

ですよね。

銘柄間の分散測定

バックテストをN日検定と同一の条件で行って、銘柄間の分散を調べていきます。SMA1×39のバックテストの結果と比較してみます。

  • VIDYA
Code Income Trades Win PF Average DD
I201 52.29 155 27.1 1.33 0.34 -17.86
I202 -116.35 179 22.91 0.68 -0.65 -33.06
I203 -92.67 158 24.05 0.64 -0.59 -36.0
I204 -81.32 197 20.3 0.63 -0.41 -41.67
I205 -23.63 170 27.65 0.89 -0.14 -54.34
I206 -39.2 163 26.38 0.84 -0.24 -38.4
I207 36.47 169 29.59 1.2 0.22 -13.93
I208 -47.31 189 22.75 0.76 -0.25 -49.54
I209 -74.49 199 21.11 0.75 -0.37 -32.5
I210 -178.81 243 23.46 0.55 -0.74 -48.12
I211 48.26 152 26.32 1.22 0.32 -26.38
I212 128.24 111 33.33 1.71 1.16 -28.52
I213 -7.4 145 23.45 0.97 -0.05 -26.72
I214 23.18 153 25.49 1.12 0.15 -27.21
I215 80.27 138 29.71 1.43 0.58 -16.64
I216 140.68 121 35.54 1.99 1.16 -18.9
I217 100.23 132 29.55 1.56 0.76 -22.7
I218 -40.13 165 23.64 0.83 -0.24 -26.78
I219 26.6 133 24.06 1.12 0.2 -29.71
I220 70.43 143 29.37 1.43 0.49 -29.17
I221 -5.34 183 28.96 0.97 -0.03 -19.92
I222 72.5 132 28.03 1.25 0.55 -36.31
I223 41.22 149 30.87 1.22 0.28 -43.22
I224 -62.69 177 22.6 0.73 -0.35 -25.31
I225 -74.42 213 22.54 0.69 -0.35 -27.22
I226 -16.07 144 28.47 0.93 -0.11 -28.71
I227 -7.53 164 26.83 0.96 -0.05 -23.91
I228 91.61 134 26.87 1.49 0.68 -33.52
I229 248.12 108 30.56 2.28 2.3 -21.16
I230 14.84 154 26.62 1.06 0.1 -40.86
I231 0.85 180 25.56 1.0 0.0 -46.85
I232 20.19 126 27.78 1.08 0.16 -53.59
I233 36.14 143 21.68 1.25 0.25 -19.1
平均 11.1 158.0 26.5 1.1 0.2 -31.4
偏差 82.06 28.98 3.51 0.39 0.59 10.8
  • SMA1×65
Code Income Trades Win PF Average DD
平均 2.6 165.0 28.5 1.0 0.1 -33.7
偏差 74.03 17.35 4.25 0.26 0.46 10.71

以外に悪くないですね。取引コストを勘定していないので、それを入れるととんとんと言ったところでしょうか。騙しのフィルターを加えると大分成績が上がるような気がします。

損益分析

収益の良いI229の損益分析を行ってみましょう。

======================================================
net income:               248.12273587116596
profit | loss:            442.4213703271667    | -194.29863445600074
average:                  2.297432739547833
pf:                       2.3
max profit | max loss:    145.5657136202178     | -10.358867244850071
trades | wins | looses:   108     | 33     | 75
wins{%}                   30.6
max series of wins:       2
max series of looses:     10
average span{win}:        61
average span{loose}:      6
max drow down:            -21.155972535376836
bunkrupt:                 0.0
======================================================
  • 損益曲線

profit_histgram

  • 損益ヒストグラム

profit_histgram

  • 月次損益

profit_histgram

おや?これはなかなかいい感じじゃないでしょうか?クロスで仕掛けるシステムとして結構理想的な収益曲線を描けているように感じます。いじっくっていくと期待できるのではないでしょうか?

まとめ

KAMAに続き、VIDYAを使ってシステムを作成し検証を行いました。VIDYAとKAMAで条件は異なるため、比較は行なえませんがVIDYAは簡単なシステムながらポテンシャルを秘めていることがわかりました。

前回は「トレードシステムはどう作ればよいか①」よりKAMAとLAMAを用いて、そのクロスで売買を行うシステムを検証しました。KAMAはペリーカウフマンによって考案された、ボラティリティによって時間軸が変動する適応型の指数平滑移動平均です。LAMAはそれに遅延して追随する指標でした。

用いるパラメータは著書のとおりに設定し、2007年から2017年の33業種別株価指数に対してバックテストを行いました。しかし、たいして優位な結果を得ることができませんでした。

KAMAは広いレンジでダイナミックに時間軸が変動することに意味があると私は考えています。したがって、9日〜36日と比較的短いレンジではKAMAを使う意味がないのではないかと。

そこで、今回はまずレンジを16日〜900日に設定してLAMAとのクロスにより売買を行った場合、どのような結果になるのかを検証してみたいと思います。

その後、このKAMAを用いて別のシステムを作ってみたので、それを検証して行きたいと思います。

では、見ていきましょう。

目次

実践

KAMAのパラメータ変更による効果

KAMA、LAMAのパラメータを変更して検証を行います。その他の売買ルールは前回と変わりません。パラメータは共に(10,4,30)を用います。10は時間軸を変動させるためのボラティリティを求めるための日数で、4,30はそれぞれ時間軸の下限と上限の平方根です。したがってこのパラメータのKAMAは16日〜900日の間で変動する平滑化定数を用いた指数平滑移動平均となります。

バックテストを行って銘柄間の分散を調べます。

  • (10,4,30)
Code Income Trades Win PF Average DD
I201 9.24 19 31.58 1.1 0.49 -46.4
I202 -80.04 20 25.0 0.47 -4.0 -56.89
I203 12.95 19 10.53 1.1 0.68 -76.2
I204 25.44 16 18.75 1.29 1.59 -35.05
I205 23.73 18 27.78 1.29 1.32 -63.7
I206 -82.96 28 25.0 0.57 -2.96 -83.48
I207 31.19 20 20.0 1.28 1.56 -44.59
I208 -25.28 26 19.23 0.79 -0.97 -43.42
I209 -42.24 20 25.0 0.64 -2.11 -43.58
I210 -122.29 38 13.16 0.51 -3.22 -159.23
I211 58.18 16 31.25 1.72 3.64 -43.65
I212 3.43 22 31.82 1.02 0.16 -95.1
I213 -62.48 20 15.0 0.6 -3.12 -82.43
I214 -40.5 22 18.18 0.69 -1.84 -46.61
I215 42.93 16 31.25 1.5 2.68 -54.31
I216 90.9 16 37.5 2.17 5.68 -31.87
I217 20.83 17 29.41 1.21 1.23 -58.53
I218 -12.87 17 11.76 0.9 -0.76 -86.97
I219 100.19 16 37.5 2.6 6.26 -24.07
I220 41.73 24 41.67 1.47 1.74 -22.48
I221 32.89 16 37.5 1.46 2.06 -27.31
I222 79.0 21 38.1 1.72 3.76 -39.74
I223 87.17 16 43.75 2.55 5.45 -23.13
I224 1.66 20 20.0 1.02 0.08 -69.77
I225 0.72 20 15.0 1.01 0.04 -62.68
I226 -48.96 22 22.73 0.59 -2.23 -52.52
I227 3.57 20 20.0 1.03 0.18 -68.68
I228 59.18 18 33.33 1.63 3.29 -45.5
I229 97.72 18 33.33 1.61 5.43 -86.0
I230 -13.13 24 29.17 0.92 -0.55 -54.27
I231 -45.27 24 16.67 0.78 -1.89 -125.36
I232 34.7 20 15.0 1.25 1.73 -96.88
I233 35.73 18 16.67 1.5 1.98 -45.95
平均 9.6 20.0 25.5 1.2 0.8 -60.5
偏差 54.1 4.36 9.17 0.53 2.69 29.4
  • (10,3,9)
平均 -6.6 73.0 29.7 1.0 0.0 -49.9
偏差 75.22 9.0 5.14 0.29 1.02 20.83

トレード回数が激減しましたね。。。収益は改善しましたが、DDが微妙です。うーん、そもそも使えませんし、どっともどっちと言ったところでしょうか?

KAMA-STCを使ったCB-PBのルール

クロスをトリガーとするシステムには向かないのでは?ということで、別のアプローチでシステムを作っていきます。KAMAはN日前と当日の終値の差が少なくなると、一気に変動が少なくなる指標です。したがって、押し目を作りながら上昇していくようなチャートに対して、KAMAは階段状のチャートを形成していきます。

そこでKAMAに対してストキャスティックを適用し、100と0をそれぞれ上昇局面と下降局面であると定義します。そして、それぞれの押し目で仕掛けていきます。

  • 仕掛け
    • KAMA-STC = 100
      • 終値 < KAMA + DEV * 0.3
    • KAMA-STC = 0
      • 終値 > KAMA - DEV * 0.3
    • KAMA-STC ≡ (KAMA-KAMA.MIN) / (KAMA.MAX-KAMA.MIN)
    • DEV ≡ √(Σ(終値-KAMA)**2/N)
    • 翌日初値
  • 仕切り
    • 利益 > 0
      • トレイリングストップ 5日で利確
    • それ以外
      • 仕掛けの条件でドテン
    • トレインリングストップ ≡ 過去N日間の安値 = 今日の安値の場合売り (またはその逆)
    • 翌日初値
  • ロスカットなし

KAMA-STCを計算するための期間は10日とします。

バックテストは2007年から2017年の33業種別株価指数を用いて、単利で行います。

ストラテジーの構築

コードはこんな感じです。KAMAの計算については前回の記事を参照願います。

class KamaEmb < KamaLama

    attr_accessor :kmasa_l, :t_stop_l, :length

    def initialize
      @kamas = Soks.new
      @t_stop_l = 5
      @m_stop = 0
      @kamas_l = 10
      @dev_r = 0.3
      @length = [@kamas_l,@t_stop_l].max + 2
    end

    def m=(m)
      @m = m
      @length = [@m,@length].max
    end

    def set_env(soks, env)
      super
      env[:soks] = soks[0..-2]
    end

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

      @kama, @lama = calc_ave(closes)
      @kamas << @kama
      return Action::None.new(code,open) if @kamas.length < @kamas_l
      dev = closes[-@kamas_l..-1].vol(@kamas_l,@kamas)[-1]
      stc = (@kamas.last - @kamas.min ) / (@kamas.max - @kamas.min)  * 100
      is_buy_p = @is_buy
      is_sell_p = @is_sell
      @is_buy = (stc == 100 and closes[-1] < @kama+dev*@dev_r)
      @is_sell = (stc == 0 and closes[-1] > @kama-dev*@dev_r)

      @kamas.shift

      if position
        gain = position.gain(closes[-1], position.volume)

        if gain > @m_stop
          high = soks[-@t_stop_l..-1].high(@t_stop_l)[-1]
          low = soks[-@t_stop_l..-1].low(@t_stop_l)[-1]
          if position.sell? and high == soks[-1].high
            return Action::Buy.new(code, date, open, 1)
          elsif  position.buy? and low == soks[-1].low
            return Action::Sell.new(code, date, open, 1)
          else
            return Action::None.new(code,open)
          end
        else
          if @is_buy and not is_buy_p and position.sell?
            return Action::Buy.new(code, date, open, 2)
          elsif @is_sell and not is_sell_p and position.buy?
            return Action::Sell.new(code, date, open, 2)
          else
            return Action::None.new(code,open)
          end
        end
      end

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

大分救いようがなくなってきました。チャートを書いてみます。

profit_histgram

profit_histgram

比較的短い周期で勝ちを重ねていきますが、負けるときは盛大に負けています。

検証

順々に見ていきます。N日検定を行います。N には5,10,15,20,30,50を使います。

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

この検定は仕掛けに対しN日後で仕切った場合、N日によらず55%を超えることで、仕掛けの優位を確認するものです。しかし今のところ55%なんて数字は出た試しがありません。この数字は今までで一番良いのじゃないでしょうか。

続いて銘柄ごとにバックテストを行い結果の偏差を見ていきます。

Code Income Trades Win PF Average DD
I201 -2.9 66 53.03 0.98 -0.04 -32.97
I202 49.36 84 65.48 1.32 0.59 -40.87
I203 -15.01 84 55.95 0.87 -0.18 -44.73
I204 -51.03 81 66.67 0.67 -0.63 -55.29
I205 25.53 93 62.37 1.21 0.27 -21.13
I206 13.6 98 55.1 1.09 0.14 -28.28
I207 103.62 80 62.5 2.19 1.3 -13.83
I208 43.7 81 62.96 1.58 0.54 -10.39
I209 76.65 78 66.67 1.6 0.98 -19.45
I210 91.77 82 59.76 1.8 1.12 -15.37
I211 113.52 70 67.14 2.24 1.62 -18.74
I212 135.82 83 66.27 2.03 1.64 -22.87
I213 64.33 68 67.65 1.51 0.95 -27.24
I214 -25.59 76 52.63 0.82 -0.34 -21.63
I215 -7.53 83 54.22 0.96 -0.09 -23.17
I216 65.92 75 69.33 1.69 0.88 -25.25
I217 64.74 80 56.25 1.46 0.81 -28.74
I218 -5.67 80 57.5 0.97 -0.07 -35.37
I219 73.05 74 68.92 1.6 0.99 -33.17
I220 -3.79 72 58.33 0.96 -0.05 -23.8
I221 17.36 82 65.85 1.18 0.21 -25.02
I222 88.65 71 70.42 1.62 1.25 -31.46
I223 58.25 84 61.9 1.45 0.69 -26.68
I224 39.81 83 56.63 1.32 0.48 -21.73
I225 14.85 91 54.95 1.15 0.16 -27.03
I226 -24.02 75 53.33 0.85 -0.32 -29.97
I227 50.22 82 62.2 1.48 0.61 -16.7
I228 101.16 88 67.05 1.81 1.15 -19.3
I229 139.36 74 68.92 2.25 1.88 -20.08
I230 148.42 80 65.0 2.21 1.86 -29.8
I231 118.27 80 61.25 1.81 1.48 -22.23
I232 119.99 73 69.86 2.05 1.64 -24.13
I233 4.49 76 60.53 1.06 0.06 -22.47
平均 51.1 79.0 62.0 1.4 0.7 -26.0
偏差 53.22 6.86 5.52 0.45 0.69 8.83

パラメータは調整しましたが、まずまずの結果じゃないでしょうか?取引コストを考慮してませんが、平均収益が0.7%あるので十分プラスになると思われます。ただ、偏差が大きいので銘柄を絞りこめる必要がありますね。勝率は多くの銘柄で60%を超えてるので、チャートにあった大負けが発生してもなんとかなるのでしょう。

パラメータを弄っても、大負け大勝ちする銘柄は決まっています。何が影響しているのかを調べる必要があります。

I230の損益分析を行います。

======================================================
net income:               149.94356260678595
profit | loss:            270.73463561464536    | -120.79107300785935
average:                  1.8980197798327334
pf:                       2.2
max profit | max loss:    26.391543119229144     | -29.795831211708073
trades | wins | looses:   79     | 52     | 27
wins{%}                   65.8
max series of wins:       6
max series of looses:     4
average span{win}:        13
average span{loose}:      19
max drow down:            -29.795831211708073
bunkrupt:                 0.0
======================================================
  • 損益曲線

profit_histgram

  • 損益ヒストグラム

profit_histgram

  • 月次損益

profit_histgram

細かい勝ちを積み重ねるシステムの割には大勝ちに依存していますね。うーん。成績は良いですが、ちょっと期待していた損益曲線にはならなかったようです。

まとめ

まずKAMAとLAMAを使ったクロスによるシステムを検証しました。劇的な変化はありません。続いてKAMA-STCを使ってCB-PBシステムを検証しました。今までの中では一番まともなシステムだと思います。システムとしては課題が残っていますが、弄っていく価値はあるかもしれません。

Recent Entries
Categories
    Tags
    Archives
    Search