ツール作成

前回は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

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

まとめ

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

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

ツールのチュートリアルを兼ねてテストを行います。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を使って、取引が行われていたときのチャートを確認して行きたいと思います。

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

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

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

Recent Entries
Categories
    Tags
    Archives
    Search