2017/5

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

さて、このサイトは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を使って、取引が行われていたときのチャートを確認して行きたいと思います。

皆さんは日経高値更新の波に乗れていますか?こういった時シストレしている人は二極化されるのではないでしょうか?私のシステムは腹立たしいタイプで、発狂しそうです。

本サイトはRubyでバックテストツールを作成し、それを使ってストラテジーの検証を行うことを目的としています。そのためには株価のデータがなければ始まりませんので、まずデータの取得と利用方法について説明して行きたいと思います。今回は動くものを用意しましたので、細かい話は行いません。

なお私の環境はUbuntu16.04、Ruby2.3となります。 他環境での動作は確認しておりませのでご容赦ください。

早速始めましょう。

目次

環境構築

まずは、Rubyのインストールから始めます。Ruby native extensionが必要になるためCコンパイラが必要になります。Ruby native extensionはRubyからC言語プログラムを呼び出すためのものです。Rubyで書かれたライブラリーをgemと呼びますが、nokogiriとsqliteというgemで必要になります。この2つのgemが利用するライブラリーもインストールする必要があります。nokogiriのインストールはすんなり行かない場合が有るかもしれません。そういった場合は多くのサイトで試行錯誤されているので調べて見てください。 ターミナルを開いて次のコマンドを叩いて行ってください。

sudo apt-get update
sudo apt-get install ruby
sudo apt-get install ruby-dev
sudo apt-get install build-essential
sudo apt-get install libxml2
sudo apt-get install libxml2-dev
sudo apt-get install zlib1g-dev
sudo apt-get install sqlite3
sudo apt-get install libsqlite3-dev
sudo apt-get install git
sudo gem install bundler

確認します。

$ruby -v
ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu]
$gem -v 
 2.5.1
$sqlite3 --version
3.11.0 2016-02-15 17:29:24 3d86....

株価取得のためのコードの取得、設定

Gitを利用してコードを取得し、セットアップを行います。

git clone git://github.com/Traver1/sok
cd sok
bundle install --path vendor/bundler
bundle exec rake db:migrate
bundle exec ruby exe/setup
bundle exec rake db:seed

必要なライブラリーが揃っていないと、bundle install …でnokogiriやsqlite3のインストールに失敗する場合があります。最後手前のexe/setupは株価データサイトk-db様のサイトへアクセスして、証券コード情報取得しています。日足データについても同サイトより取得していきます。利用にあたってはサーバーに負荷のかからぬよう適度な間をおいてご利用ください。もっとも、あまり頻度が高いと制限がかかると思いますが。

日足データの取得、保存

続いて日足データを取得してみましょう。ダイワ上場投信の2017年の株価を取得します。

bundle exec ruby exe/download 1305 2017

取得されたデータをSQLを叩いて確認してみます。

 sqlite3 db/development.sqlite3
 select * from soks;

確認できたでしょうか?sqlite3を終了します。

.exit

日足データの読み込み

日足データをcsv形式でコンソールにダンプしてみましょう。

bundle exec ruby exe/dump 1305 20170101 20170201

左から順に、証券コード-取引所、日付、初値、高値、安値、終値、出来高の情報が出力されたと思います。

まとめ

Rubyやその他もろもろをインストールした後に、sokを用いて株価の取得と読み書きを行いました。これで皆さんの環境でも日足データの読み書きが出来るようになったと思います。

しかし1日ごとに全銘柄の情報を更新したりだとか、チャートを表示したりだとか、そういった機能はありません。これからツールを作っていく過程で、中身の説明とともに実装して行こうと考えています。

日本郵政、野村不動産買収を検討って、ぱっと聞くと大丈夫なん?と思うのですがどうなりますかね。あまり関係のない私は引き続き環境構築を行っていきます。

さて、このサイトはRubyで簡単なバックテストツールを作成して、広く知られているストラテジーの検証を行っていくことを目的としています。前回、株価の取得方法について説明しました。このままバックテストツールの作成に入ってもよいのですが、先にチャート周りの環境を整えていこうと思います。

ただRubyはRailsと共にあるため、チャート作成ライブラリはどちらかというとWeb周りのものが多いようです。このサイトでは特にそういったものを必要としているわけではないので、しばし検討を行い、Gnuplotで吐き出す方針で行くことに決めました。実際重要になるのは収益曲線で株価チャートはおまけなのですが、今回は株価チャートを書くための準備を整えていきます。

インストールを行ったら終わりではありますが、取り掛かっていきましょう。

目次

インストール

gnuplotをインストールします。

sudo apt-get install gnuplot

前回のsokディレクトリにて次のコマンドを打ちます。

git pull
bundle install --path vendor/bunler

以上で終わりです。

株価のチャートをJPEG形式で出力するサンプルを幾つか上げておきます。このサンプルはgnuplot-numofinance.demoを参考に作ったものです。

サンプル1

sok/example/chartディレクトリにてbundle exec ruby sample1.rbを実行すると、sample1.jpegが出力されます。

sample1.rbの中はこのようになっています。必要に応じて、証券コードを変更してください。

Bundler.require

soks = Kabu::Company.find_by_code(1305).soks
dates, closes = Kabu::Soks.parse(soks, :date, :close)

Numo.gnuplot do
  set terminal: 'jpeg'
  set output:  'sample1.jpeg'
  set title: "Demo of plotting financial data"
  set yrange: closes.yrange
  set xtics: dates.xtics
  set lmargin: 9
  set rmargin: 2
  set grid: true
  plot dates.x, closes.y, with: :lines, notitle: true
end

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

このサイトはlokka+herokuを使って無料で運用しているのですが、lokkaは既に更新のとまっているCMSってこともあり、情報が少ない。画像を表示させるのにかなり手こずりました。ちなみに、gdriveからここの方法でいけました…そのうちlokkaで不親切だと感じたところを記事にできたらなと思います。

サンプル2

Bundler.require

soks = Kabu::Company.find_by_code(1305).soks
dates = Kabu::Soks.parse(soks,:date)
values = Kabu::Soks.parse(soks,:open,:high,:low,:close)

Numo.gnuplot do
  set terminal: 'jpeg'
  set output:  'sample2.jpeg'
  set title: "Demo of plotting financial data"
  set yrange: values.yrange
  set xtics: dates.xtics
  set lmargin: 9
  set rmargin: 2
  plot dates.x, *values.y, with: :financebars, lt: 8, notitle: true
end

sample2

ラインチャートからバーチャートに変えたものです。バーチャートには4本足が必要になります。100日分のデータを出力するとした場合valuesにはlengthが100の配列が4つ格納されています。それぞれ、初値高値安値終値となります。plotする際には配列を展開するために*が必要になります。

サンプル3

Bundler.require

soks = Kabu::Company.find_by_code(1305).soks
dates = Kabu::Soks.parse(soks,:date)
values = Kabu::Soks.parse(soks,:open,:high,:low,:close)
closes = values[3]
bols = closes.bol(25)
dates, values, bols = Kabu::Soks.adjust_length(dates, values, bols)

Numo.gnuplot do
  set terminal: 'jpeg'
  set output:  'sample3.jpeg'
  set title: "Demo of plotting financial data"
  set yrange: bols[1..2].yrange
  set xtics: dates.xtics
  set lmargin: 9
  set rmargin: 2
  set grid: true
  plot [dates.x, *values.y, with: :financebars, lt: 8, notitle: true],
    [dates.x, bols[0].y, with: :lines, notitle: true, lc: "'blue'"],
    [dates.x, bols[1].y, with: :lines, notitle: true, lc: "'blue'", lt: 0],
    [dates.x, bols[2].y, with: :lines, notitle: true, lc: "'blue'", lt: 0],
    [dates.x, bols[3].y, axes: :x1y2, with: :lines, notitle: true, lc: "'orange'"]
end

sample3

Sample2に対して、ボリンジャーバンドを追加しました。バンド幅は1です。ボリンジャーバンドの計算はKabu::Soksで行います。結果は平均、アッパーバンド、ボトムバンド、偏差がそれぞれ格納されている配列です。

サンプル4

Bundler.require

soks = Kabu::Company.find_by_code(1305).soks
dates = Kabu::Soks.parse(soks,:date)
values = Kabu::Soks.parse(soks,:open,:high,:low,:close)
volumes = Kabu::Soks.parse(soks,:volume)
v_aves = volumes.ave(25)
closes = values[3]
bols = closes.bol(25)
dates, values, bols, v_aves = Kabu::Soks.adjust_length(dates, values, bols, v_aves)

Numo.gnuplot do
  set terminal: 'jpeg'
  set output:  'sample4.jpeg'
  set multiplot: true
  set label: "Bolinger Band", at: [0.01, 0.03]
  set label: "http://gnuplot.sourceforge.net/demo/finance.html", at: [0.01, 0.07]
  set yrange: (values+bols[1..2]).yrange
  set lmargin: 8
  set rmargin: 2
  set bmargin: 0
  set xtics: dates.xtics(visible: false)
  set format_x: ""
  set grid: true
  set grid: true
  set origin: [0.0, 0.3]
  set size: [1.0, 0.7]
  plot [dates.x, *values.y, with: :financebars, lt: 8, notitle: true],
    [dates.x, bols[0].y, with: :lines, notitle: true, lc: "'blue'"],
    [dates.x, bols[1].y, with: :lines, notitle: true, lc: "'blue'", lt: 0],
    [dates.x, bols[2].y, with: :lines, notitle: true, lc: "'blue'", lt: 0],
    [dates.x, bols[3].y, axes: :x1y2, with: :lines, notitle: true, lc: "'orange'"]

  unset label: 1
  unset label: 2
  set :bmargin
  set tmargin: 0
  set format: :x
  set xtics: dates.xtics
  set ytics: volumes.ytics(count: 2)
  set grid: true
  set origin: [0.0, 0.0]
  set size: [1.0, 0.3]
  set yrange: volumes.yrange
  plot [dates.x, volumes.y, with: :impulses, notitle: true, lt: 3, lc: "'green'"],
    [dates.x, v_aves.y, with: :lines, notitle: true, lt: 3 ]
  unset multiplot: true
end

sample4

出来高を加えます。ようやくチャートらしくなりました。

サンプル5

最後にローソク足チャートを表示します。gnuplotのローソク足は色分けがかなりアナログな方法を用いているようで、上がったときと下がったときのplotを別々で描いています。

Bundler.require

soks = Kabu::Company.find_by_code(1305).soks
dates = Kabu::Soks.parse(soks,:date)
values = Kabu::Soks.parse(soks,:open,:high,:low,:close)
volumes = Kabu::Soks.parse(soks,:volume)
v_aves = volumes.ave(25)
closes = values[3]
bols = closes.bol(25)
dates, values, bols, v_aves = Kabu::Soks.adjust_length(dates, values, bols, v_aves)
up_stick, down_stick = values.split_up_and_down_sticks

Numo.gnuplot do
  set terminal: 'jpeg'
  set output:  'sample5.jpeg'
  set multiplot: true
  set label: "Bolinger Band", at: [0.01, 0.03]
  set label: "http://gnuplot.sourceforge.net/demo/finance.html", at: [0.01, 0.07]
  set yrange: (values+bols[1..2]).yrange
  set lmargin: 8
  set rmargin: 2
  set bmargin: 0
  set xtics: dates.xtics(visible: false)
  set format_x: ""
  set grid: true
  set grid: true
  set origin: [0.0, 0.3]
  set size: [1.0, 0.7]
  plot [dates.x, *up_stick.y, with: :candlesticks, lt: 6, notitle: true],
    [dates.x, *down_stick.y, with: :candlesticks, lt: 7, notitle: true],
    [dates.x, bols[0].y, with: :lines, notitle: true, lc: "'salmon'"],
    [dates.x, bols[1].y, with: :lines, notitle: true, lc: "'salmon'", lt: 0],
    [dates.x, bols[2].y, with: :lines, notitle: true, lc: "'salmon'", lt: 0],
    [dates.x, bols[3].y, axes: :x1y2, with: :lines, notitle: true, lc: "'orange'"]

  unset label: 1
  unset label: 2
  set :bmargin
  set tmargin: 0
  set format: :x
  set xtics: dates.xtics
  set ytics: volumes.ytics(count: 2)
  set grid: true
  set origin: [0.0, 0.0]
  set size: [1.0, 0.3]
  set yrange: volumes.yrange
  plot [dates.x, volumes.y, with: :impulses, notitle: true, lt: 3, lc: "'green'"], 
    [dates.x, v_aves.y, with: :lines, notitle: true, lt: 3 ]
  unset multiplot: true
end

sample5

必要なチャートは大体書けるようになると思います。numo-gnuplotを利用しない場合はRubyで一旦データファイルを作成し、それをgnuplotで読み込んでチャートを作成するという流れになると思います。Rなどの利用も考えると、こちらの方法が断然柔軟性は有ると思いますが、このサイトでは極力Rubyの中で完結させることを優先にすすめていきたいと思います。

まとめ

gnuplot を使ってチャートの出力を行いました。売買判定時にマーキングをする、だとか、出来高分布図を右に追加するだとか、ほしい機能はまだまだ有ると思いますが。そういった機能については必要時に随時追加していきます。チャートの作成に当たってはテンプレートを作ってそれを呼び出すと言うかたちになると思います。次回からバックテストツールの作成に入っていきます。

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

さて、このサイトは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を使って、取引が行われていたときのチャートを確認して行きたいと思います。

ツールのチュートリアルを兼ねてテストを行います。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

まとめ

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

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に改良を加えていこうと思います。

前回に引き続き、「売買システム入門」に従い、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にロスカットルールを加えました。すると、収益や平均収益が減少しました。悲しい限りです。

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

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

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

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システムについてはこれで終わりにして、シャンデ氏の著書に紹介されている次のシステムの検証に入りたいと思います。

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

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

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

今回は破産確率という点でシステムを評価しましょう。破産確率はバルサラの破産確率が有名ですが、ここではシミュレーションを行って、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回のトレードのうちに破産する可能性があることを示しました。