#!/usr/bin/env ruby # This script connects to IB API, and downloads historic data require 'rubygems' require 'time' require 'getopt/long' require 'bundler/setup' $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'ib-ruby' include Getopt opt = Getopt::Long.getopts( ["--help", BOOLEAN], ["--end", REQUIRED], ["--port", REQUIRED], ["--security", REQUIRED], ["--duration", REQUIRED], ["--barsize", REQUIRED], ["--csv", BOOLEAN], ["--dateformat", REQUIRED], ["--nonregularhours", BOOLEAN] ) if opt["help"] || opt["security"].nil? || opt["security"].empty? puts < 86400 STDERR.puts("\nTWS rejects --duration longer than 86400 seconds (1 day).\n") exit(1) end # This is the last time we want data for. END_DATE_TIME = (opt["end"] && eval(opt["end"]).to_ib) || Time.now.to_ib # This can be :trades, :midpoint, :bid, or :asked WHAT = (opt["what"] && opt["what"].to_sym) || :trades # Values less than 4 do not appear to actually work; they are rejected by the server. # BAR_SIZE = (opt["barsize"] && opt["barsize"].to_sym) || :min15 # If REGULAR_HOURS_ONLY is set to 0, all data available during the time # span requested is returned, even data bars covering time # intervals where the market in question was illiquid. If useRTH # has a non-zero value, only data within the "Regular Trading # Hours" of the product in question is returned, even if the time # span requested falls partially or completely outside of them. REGULAR_HOURS_ONLY = opt["nonregularhours"] ? 0 : 1 # Using a DATE_FORMAT of 1 will cause the dates in the returned # messages with the historic data to be in a text format, like # "20050307 11:32:16". If you set :format_date to 2 instead, you # will get an offset in seconds from the beginning of 1970, which # is the same format as the UNIX epoch time. DATE_FORMAT = (opt["dateformat"] && opt["dateformat"].to_i) || 1 PORT = (opt["port"] && opt["port"]) || '4001' # First, connect to IB TWS. ib = IB::Connection.new :client_id => 1112 #, :port => 7496 # TWS # Subscribe to TWS alerts/errors ib.subscribe(:Alert) { |msg| puts msg.to_human } lastMessageTime = Queue.new # for communicating with the reader thread. # Subscribe to incoming HistoricalData events. The code passed in the # block will be executed when a message of the subscribed type is # received, with the received message as its argument. In this case, # we just print out the data. # # Note that we have to look the ticker id of each incoming message # up in local memory to figure out what security it relates to. # The incoming message packet from TWS just identifies it by ticker id. # ib.subscribe(:HistoricalData) do |msg| if opt["csv"] puts "date,time,open,high,low,close,volume,wap,has_gaps" msg.results.each do |datum| puts "#{datum.time},#{datum.open},#{datum.high},#{datum.low}," + "#{datum.close},#{datum.volume},#{datum.wap},#{datum.has_gaps}" end else STDERR.puts "Received #{msg.count} items:" msg.results.each { |datum| puts datum.to_s } end lastMessageTime.push(Time.now) end # Now we actually request historical data for the symbols we're # interested in. TWS will respond with a HistoricalData message, # which will be received by the code above. ib.send_message :RequestHistoricalData, :request_id => 123, :contract => opt["security"], :end_date_time => END_DATE_TIME, :duration => DURATION, # seconds == 1 hour :bar_size => BAR_SIZE, # 1 minute bars :what_to_show => WHAT, :use_RTH => REGULAR_HOURS_ONLY, :format_date => DATE_FORMAT # A complication here is that IB does not send any indication when all historic data is done being delivered. # So we have to guess - when there is no more new data for some period, we interpret that as "end of data" and exit. while true lastTime = lastMessageTime.pop # blocks until a message is ready on the queue sleep 2 # .. wait .. exit if lastMessageTime.empty? # if still no more messages after 2 more seconds, exit. end