それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

SBI証券の発注スクリプト

システムトレードが流行りだ。一瞬で動く値動きを追いかけられるのは、GSとか大手の社員だけだ。素人にはシステムトレードAPIが公開されない。

ミリ秒単位のトレードとかすごいね

東京証券取引所は、ミリ秒単位で注文を捌けるらしいが、一般素人はできない。

なので、大手のヘッジファンドの狩り場になっている気がする。

素人でもできるだけ早く発注できないか。と考えてスクリプト組んでみた。

しかし、株価をチェックできないとミリ秒単位で発注できないので発注スクリプトだけでは大雨できないね。どうしたものか。できるだけ遅延なくリアルタイム株価出す方法ないかな。

SBI証券の注文用のクライアント

SBIのバックアップサイトをベースに作っているのでそれなりに、長期間動くと思います。
gems に登録しようと思ったけど、既に同じようなものがあったんで遠慮しておく。
あと、バックアップサイトは4/3秒つまり、1.333秒/回 を超えるリクエストは弾くので、どうしたものか。どれだけ頑張っても1.5秒以上の発注はできない模様。ふーむ

#coding: utf-8

require 'mechanize'


class Mechanize::HTTP::Agent
  def post_connect uri, response, body_io # :yields: agent, uri, response, body
     @post_connect_hooks.each do |hook|
       begin
         hook.call self, uri, response, body_io
       ensure
         body_io.rewind
       end
     end
   end
end



class Mechanize::HTTP::Agent
    @@interval = 0.75
    alias_method :original_fetch, :fetch
    def fetch( uri, method = :get, headers = {}, params = [], referer = current_page, redirects = 0)
        page = nil

        cnt = 0
        page = self.original_fetch( uri, method, headers, params, referer , redirects)                    
        until (page.title =~ /error/).nil? do
          # SBIバックアップサイトは秒間4/3回のリクエスト以上を受け付けてくれない。
          puts "接続数エラー..再取得します(#{cnt+1}回目)"
          page = self.original_fetch( uri, method, headers, params, referer , redirects)                    
          sleep 0.8 + 0.1 * cnt
          cnt = cnt + 1 
        end

        return page
    end
end



# SBIのバックアップサイトをscraperして、注文を簡単に出す
class SBISec
  attr_accessor :m
  def initialize( login_name, login_pass,order_pass )
    @@login_name, @@login_pass,@@order_pass = login_name, login_pass,order_pass
    @m = Mechanize.new 
    m.ssl_version = 'SSLv3'
    @cookie = "saved.sbi_cookie.jar.yml"

    m.post_connect_hooks << lambda{|ua,uri,res,body_io|
          body = body_io.read.toutf8
          body.gsub! /Shift_JIS/,"utf-8"
          body_io.truncate body_io.pos
          body_io.rewind
          body_io.puts body
    }

    self.login
  end
  def login
    m.get "https://k.sbisec.co.jp/"
    m.page.forms[0].field_with( :name => "password").value= @@login_pass
    m.page.forms[0].field_with( :name => "username").value= @@login_name
    m.page.forms[0].submit
  end 
  def go_to_top

    begin 
      m.page.link_with( :text => /トップ/).click
    rescue => e
        m.get "https://k.sbisec.co.jp/bsite/member/menu.do"
    end

    cnt = 0
    until (m.page.title =~ /error/).nil? do

      puts "エラー..再取得#{cnt+1}回目"
      m.get "https://k.sbisec.co.jp/bsite/member/menu.do"
      puts m.page.title 
      sleep 1 + 0.1 * cnt
      cnt = cnt + 1 
    end
  end
  def price_now(stock_id)
    m.page.link_with( :text => /株価/).click
    m.page.forms[0].field_with( :name=>"ipm_product_code").value=stock_id
    m.page.forms[0].click_button(m.page.forms[0].button_with :name => /id/)
    price = m.page.at("td[text()^='現在']").parent.at("font").text # 現在値
    return price
  end
  def keep_session
    self.go_to_top
  end
  def orders
    m.page.link_with( :text =>/注文取消/訂正/).click
    m.page.search("div//tr//table[cellspacing='0'][cellpadding='0'][width='100%'][border='0']")
    list = m.page.search('div//table[border="0"][cellspacing="0"][cellpadding="0"][width="100%"][style="margin-top:2px;"]/tr/td/table/tr')
    list = list.map{|e| e.to_html.gsub(/\r|\n|\t/, "") }
    orders = list.each_slice(4).map
    orders = orders.map{|e| e.join }
    orders = orders.map{|e| "<table>#{e}</table>" }
    orders = orders.map{|e| Nokogiri::HTML(e) }
    orders = orders.map{|e|
      {
        "銘柄"           => e.at("//tr[1]/td/table/tr/td[1]").text,
        "注文単価"       =>  e.at("//tr[5]/td[4]").text,
        "株数"           =>  e.at("//tr[5]/td[2]").text,
        "種類"           =>  e.at("//tr[4]/td[2]").text,
        "詳細リンク"     => e.at("//tr[1]/td/table/tr/td[1]/a").attr("href"),
        "取消リンク"     => e.at("//tr[1]/td/table/tr/td[2]/a[2]").attr("href"),
        "訂正リンク"     => e.at("//tr[1]/td/table/tr/td[2]/a[1]").attr("href"),
      }
    }
  end
  def cancel_all_order(stock=nil)
    puts stock.nil?
    self.orders.select{|e|
      if stock.nil? then 
        true
      elsif e["銘柄"] =~ /#{stock}/ then
        true
      else
        false
      end
    }.each{|e|
      puts e["取消リンク"]
      m.get e["取消リンク"]
      m.page.forms[0].field_with( :name=>"password").value=@@order_pass
      m.page.forms[0].submit
    }
    self.go_to_top

  end

  def force_buy(stock_id,quantity)
    #成行で買う。
    self.buy(stock_id,quantity,nil,"成行:無条件")
  end
  def wait_buy(stock_id,quantity,price)
    #指値で買う。
    self.buy(stock_id,quantity,price )
  end
  alias_method :at_market_order, :force_buy
  alias_method :limit_order    , :wait_buy

  def buy( stock_id, quantity, price=nil, order_type="指値:無条件")
    #株価ページ
    m.page.link_with( :text => /株価/).click
    m.page.forms[0].field_with( :name=>"ipm_product_code").value=  stock_id
    m.page.forms[0].click_button(m.page.forms[0].button_with :name => /id/)
    m.page.link_with( :text =>/信用新規買/).click
    self.submit_order( stock_id, quantity, price, order_type)
    self.go_to_top
  end
  def force_sell(stock_id,quantity)
    #成行で買う。
    self.sell(stock_id,quantity,nil,"成行:無条件")
  end
  def wait_sell(stock_id,quantity,price)
    #指値で買う。
    self.sell(stock_id,quantity,price )
  end
  alias_method :at_market_order, :force_buy
  alias_method :limit_order    , :wait_buy
  def sell( stock_id, quantity, price=nil, order_type="指値:無条件")
    #株価ページ
    m.page.link_with( :text => /株価/).click
    m.page.forms[0].field_with( :name=>"ipm_product_code").value=  stock_id
    m.page.forms[0].click_button(m.page.forms[0].button_with :name => /id/)
    m.page.link_with( :text =>/信用新規売/).click
    self.submit_order( stock_id, quantity, price, order_type)
    self.go_to_top
  end
  def submit_order( stock_id, quantity, price=nil, order_type)
    #発注ページ
      # <select name="sasinari_kbn">
      #   <option value="DEFAULT_SELECT_VALUE" selected="">選択して下さい</option>
      #   <option value=" ">指値:無条件</option>
      #   <option value="Z">指値:寄指</option>
      #   <option value="I">指値:引指</option>
      #   <option value="F">指値:不成</option>
      #   <option value="P">指値:IOC</option>
      #   <option value="N">成行:無条件</option>
      #   <option value="Y">成行:寄成</option>
      #   <option value="H">成行:引成</option>
      #   <option value="O">成行:IOC</option>
      # </select>

    m.page.forms[0].field_with( :name=> "price").value =  price
    m.page.forms[0].field_with( :name=> /quantity/).value = quantity  #返済注文のときは sum_quantity
    m.page.forms[0].field_with( :name=>"sasinari_kbn").option_with( :text=>/#{order_type}/).select
    m.page.forms[0].field_with( :name=>"password").value=@@order_pass
    m.page.forms[0].submit
    m.page.forms[0].submit

  end
  def return_sell( stock_id, quantity, price, order_type="指値:無条件")
    #返済売り
    m.page.link_with( :text => /株価/).click
    m.page.forms[0].field_with( :name=>"ipm_product_code").value=stock_id
    m.page.forms[0].click_button(m.page.forms[0].button_with :name => /id/)
    m.page.link_with( :text =>/信用返済/現引現渡/).click
    m.page.link_with( :text =>/返済売/).click

    self.submit_order( stock_id, quantity, price, order_type)

    self.go_to_top

  end
end