ib-ruby 0.4.3 → 0.4.20

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.gitignore +32 -0
  2. data/HISTORY +68 -0
  3. data/README.rdoc +9 -6
  4. data/VERSION +1 -1
  5. data/bin/account_info +29 -0
  6. data/bin/contract_details +37 -0
  7. data/bin/depth_of_market +43 -0
  8. data/bin/historic_data +62 -0
  9. data/bin/{RequestHistoricData → historic_data_cli} +46 -91
  10. data/bin/market_data +49 -0
  11. data/bin/option_data +45 -0
  12. data/bin/template +21 -0
  13. data/bin/time_and_sales +63 -0
  14. data/lib/ib-ruby/connection.rb +166 -0
  15. data/lib/ib-ruby/constants.rb +91 -0
  16. data/lib/ib-ruby/messages/incoming.rb +807 -0
  17. data/lib/ib-ruby/messages/outgoing.rb +573 -0
  18. data/lib/ib-ruby/messages.rb +8 -1445
  19. data/lib/ib-ruby/models/bar.rb +26 -0
  20. data/lib/ib-ruby/models/contract.rb +335 -0
  21. data/lib/ib-ruby/models/execution.rb +55 -0
  22. data/lib/ib-ruby/models/model.rb +20 -0
  23. data/lib/ib-ruby/models/order.rb +262 -0
  24. data/lib/ib-ruby/models.rb +11 -0
  25. data/lib/ib-ruby/socket.rb +50 -0
  26. data/lib/ib-ruby/symbols/forex.rb +32 -72
  27. data/lib/ib-ruby/symbols/futures.rb +47 -68
  28. data/lib/ib-ruby/symbols/options.rb +30 -0
  29. data/lib/ib-ruby/symbols/stocks.rb +23 -0
  30. data/lib/ib-ruby/symbols.rb +9 -0
  31. data/lib/ib-ruby.rb +7 -8
  32. data/lib/legacy/bin/account_info_old +36 -0
  33. data/lib/legacy/bin/historic_data_old +81 -0
  34. data/lib/legacy/bin/market_data_old +68 -0
  35. data/lib/legacy/datatypes.rb +485 -0
  36. data/lib/legacy/ib-ruby.rb +10 -0
  37. data/lib/legacy/ib.rb +226 -0
  38. data/lib/legacy/messages.rb +1458 -0
  39. data/lib/version.rb +2 -3
  40. data/spec/ib-ruby/models/contract_spec.rb +261 -0
  41. data/spec/ib-ruby/models/order_spec.rb +64 -0
  42. data/spec/ib-ruby_spec.rb +0 -131
  43. metadata +106 -76
  44. data/bin/AccountInfo +0 -67
  45. data/bin/HistoricToCSV +0 -111
  46. data/bin/RequestMarketData +0 -78
  47. data/bin/SimpleTimeAndSales +0 -98
  48. data/bin/ib-ruby +0 -8
  49. data/lib/ib-ruby/datatypes.rb +0 -400
  50. data/lib/ib-ruby/ib.rb +0 -242
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This script connects to IB API and subscribes to market data for specific symbols.
4
+ # It then prints out all trades that exceed certain size.
5
+
6
+ require 'pathname'
7
+ LIB_DIR = (Pathname.new(__FILE__).dirname + '../lib/').realpath.to_s
8
+ $LOAD_PATH.unshift LIB_DIR unless $LOAD_PATH.include?(LIB_DIR)
9
+
10
+ require 'rubygems'
11
+ require 'bundler/setup'
12
+ require 'ib-ruby'
13
+
14
+ # Define the symbols we're interested in.
15
+ @market = {
16
+ 123 => IB::Symbols::Futures[:gbp],
17
+ 234 => IB::Symbols::Futures[:jpy]
18
+ }
19
+
20
+ # To determine when the timeout has passed.
21
+ @last_msg_time = Time.now.to_i + 2
22
+
23
+ # Connect to IB TWS.
24
+ ib = IB::Connection.new
25
+
26
+ # Subscribe to TWS alerts/errors
27
+ ib.subscribe(:Alert) { |msg| puts msg.to_human }
28
+
29
+ MIN_SIZE = 0
30
+
31
+ # This method filters out non-:last type events, and filters out any sale < MIN_SIZE.
32
+ # Note that we have to look the ticker id of each incoming message
33
+ # up in local memory to figure out what it's for.
34
+ # (N.B. The description field is not from IB TWS. It is defined
35
+ # locally in forex.rb, and is just arbitrary text.)
36
+ def show_sales_and_size(msg)
37
+ #return if msg.type != :last_price || msg.data[:size] < MIN_SIZE
38
+ puts @market[msg.data[:id]].description + ": " +
39
+ (msg.is_a?(IB::Messages::Incoming::TickPrice) ?
40
+ "#{msg.data[:size]} at #{msg.data[:price]}" : msg.to_human)
41
+ end
42
+
43
+ def show_size(msg)
44
+ return if msg.type != :last_size || msg.data[:size] < MIN_SIZE
45
+ end
46
+
47
+ # Now, subscribe to TickerPrice and TickerSize events. The code passed in the block
48
+ # will be executed when a message of that type is received, with the received message
49
+ # as its argument. In this case, we just print out the tick.
50
+ ib.subscribe(:TickPrice, :TickSize, :TickString) { |msg| show_sales_and_size(msg) }
51
+
52
+ # Now we actually request market data for the symbols we're interested in.
53
+
54
+ @market.each_pair do |id, contract|
55
+ ib.send :RequestMarketData, :id => id, :contract => contract
56
+ end
57
+
58
+ puts "\nSubscribed to TWS market data"
59
+ puts "\n******** Press <Enter> to cancel... *********\n\n"
60
+ gets
61
+ puts "Unsubscribing from TWS market data.."
62
+
63
+ @market.each_pair { |id, contract| ib.send :CancelMarketData, :id => id }
@@ -0,0 +1,166 @@
1
+ require 'ib-ruby/socket'
2
+ require 'logger'
3
+
4
+ if RUBY_VERSION < "1.9"
5
+ require 'sha1'
6
+ else
7
+ require 'digest/sha1'
8
+ include Digest
9
+ end
10
+
11
+ # Add method to_ib to render datetime in IB format (zero padded "yyyymmdd HH:mm:ss")
12
+ class Time
13
+ def to_ib
14
+ "#{self.year}#{sprintf("%02d", self.month)}#{sprintf("%02d", self.day)} " +
15
+ "#{sprintf("%02d", self.hour)}:#{sprintf("%02d", self.min)}:#{sprintf("%02d", self.sec)}"
16
+ end
17
+ end # Time
18
+
19
+ module IB
20
+ # Encapsulates API connection to TWS or Gateway
21
+ class Connection
22
+
23
+ # Please note, we are realizing only the most current TWS protocol versions,
24
+ # thus improving performance at the expense of backwards compatibility.
25
+ # Older protocol versions support can be found in older gem versions.
26
+
27
+ CLIENT_VERSION = 48 # Was 27 in original Ruby code
28
+ SERVER_VERSION = 53 # Minimal server version. Latest, was 38 in current Java code.
29
+ DEFAULT_OPTIONS = {:host =>"127.0.0.1",
30
+ :port => '4001', # Gateway, TWS: '7496'
31
+ :open => true
32
+ }
33
+
34
+ attr_reader :next_order_id
35
+
36
+ def initialize(opts = {})
37
+ @options = DEFAULT_OPTIONS.merge(opts)
38
+
39
+ @connected = false
40
+ @next_order_id = nil
41
+ @server = Hash.new # information about server and server connection state
42
+
43
+ # Message listeners. Key is the message class to listen for.
44
+ # Value is an Array of Procs. The proc will be called with the populated message
45
+ # instance as its argument when a message of that type is received.
46
+ # TODO: change Array of Procs into a Hash to allow unsubscribing
47
+ @listeners = Hash.new { |hash, key| hash[key] = Array.new }
48
+
49
+ self.open(@options) if @options[:open]
50
+ end
51
+
52
+ def server_version
53
+ @server[:version]
54
+ end
55
+
56
+ def open(opts = {})
57
+ raise Exception.new("Already connected!") if @connected
58
+
59
+ opts = @options.merge(opts)
60
+
61
+ # Subscribe to the NextValidID message from TWS that is always
62
+ # sent at connect, and save the id.
63
+ self.subscribe(Messages::Incoming::NextValidID) do |msg|
64
+ @next_order_id = msg.data[:id]
65
+ puts "Got next valid order id #{@next_order_id}."
66
+ end
67
+
68
+ @server[:socket] = IBSocket.open(opts[:host], opts[:port])
69
+
70
+ # Secret handshake.
71
+ @server[:socket].send(CLIENT_VERSION)
72
+ @server[:version] = @server[:socket].read_int
73
+ @server[:local_connect_time] = Time.now()
74
+ raise(Exception.new("TWS version >= #{SERVER_VERSION} required.")) if @server[:version] < SERVER_VERSION
75
+
76
+ puts "\tGot server version: #{@server[:version]}."
77
+ #logger.debug("\tGot server version: #{@server[:version]}.")
78
+
79
+ # Server version >= 20 sends the server time back. Our min server version is 38
80
+ @server[:remote_connect_time] = @server[:socket].read_string
81
+ #logger.debug("\tServer connect time: #{@server[:remote_connect_time]}.")
82
+
83
+ # Server wants an arbitrary client ID at this point. This can be used
84
+ # to identify subsequent communications.
85
+ @server[:client_id] = SHA1.digest(Time.now.to_s + $$.to_s).unpack("C*").join.to_i % 999999999
86
+ @server[:socket].send(@server[:client_id])
87
+ #logger.debug("\tSent client id # #{@server[:client_id]}.")
88
+
89
+ #logger.debug("Starting reader thread..")
90
+ Thread.abort_on_exception = true
91
+ @server[:reader_thread] = Thread.new { self.reader }
92
+
93
+ @connected = true
94
+ end
95
+
96
+ def close
97
+ @server[:reader_thread].kill # Thread uses blocking I/O, so join is useless.
98
+ @server[:socket].close()
99
+ @server = Hash.new
100
+ @@server_version = nil
101
+ @connected = false
102
+ #logger.debug("Disconnected.")
103
+ end
104
+
105
+ def to_s
106
+ "IB Connector: #{ @connected ? "connected." : "disconnected."}"
107
+ end
108
+
109
+ # Subscribe to incoming message events of type message_class.
110
+ # code is a Proc that will be called with the message instance as its argument.
111
+ def subscribe(*args, &block)
112
+ code = args.last.respond_to?(:call) ? args.pop : block
113
+
114
+ raise ArgumentError.new "Need listener proc or block" unless code.is_a? Proc
115
+
116
+ args.each do |message_class|
117
+ if message_class.is_a? Symbol
118
+ message_class = Messages::Incoming.const_get(message_class)
119
+ end
120
+
121
+ unless message_class < Messages::Incoming::AbstractMessage
122
+ raise ArgumentError.new "#{message_class} must be an IB message class"
123
+ end
124
+
125
+ @listeners[message_class].push(code)
126
+ end
127
+ end
128
+
129
+ # Send an outgoing message.
130
+ def send(message, *args)
131
+ if message.is_a? Symbol
132
+ message = Messages::Outgoing.const_get(message).new *args
133
+ end
134
+
135
+ raise Exception.new("only sending Messages::Outgoing") unless message.is_a? Messages::Outgoing::AbstractMessage
136
+
137
+ message.send(@server)
138
+ end
139
+
140
+ protected
141
+
142
+ def reader
143
+ loop do
144
+ # this blocks, so Thread#join is useless.
145
+ msg_id = @server[:socket].read_int
146
+
147
+ # Debug:
148
+ unless [1, 2, 4, 6, 7, 8, 9, 21, 53].include? msg_id
149
+ puts "Got message #{msg_id} (#{Messages::Incoming::Table[msg_id]})"
150
+ end
151
+
152
+ # Create a new instance of the appropriate message type, and have it read the message.
153
+ # NB: Failure here usually means unsupported message type received
154
+ msg = Messages::Incoming::Table[msg_id].new(@server[:socket], @server[:version])
155
+
156
+ if @listeners[msg.class].size > 0
157
+ @listeners[msg.class].each { |listener| listener.call(msg) }
158
+ else
159
+ # Warn if nobody listened to an incoming message.
160
+ puts " WARNING: Nobody listened to incoming message #{msg.class}"
161
+ end
162
+ end # loop
163
+ end # reader
164
+ end # class Connection
165
+ IB = Connection # Legacy alias
166
+ end # module IB
@@ -0,0 +1,91 @@
1
+ module IB
2
+ ### Widely used TWS constants:
3
+
4
+ EOL = "\0"
5
+
6
+ #FaMsgTypeName = {1 => "GROUPS",
7
+ # 2 => "PROFILES",
8
+ # 3 =>"ALIASES"}
9
+
10
+ # Enumeration of data types
11
+ DATA_TYPES = [:trades, :midpoint, :bid, :ask]
12
+
13
+ # Enumeration of bar size types for convenience.
14
+ # Bar sizes less than 30 seconds do not work for some securities.
15
+ BAR_SIZES = ['1 sec', '5 secs', '15 secs', '30 secs',
16
+ '1 min', '2 mins', '3 mins', '5 mins',
17
+ '15 mins', '30 mins', '1 hour', '1 day']
18
+
19
+ # Tick types as received in TickPrice and TickSize messages (enumeration)
20
+ TICK_TYPES = {
21
+ # int id => :Description # Corresponding API Event/Function/Method
22
+ 0 => :bid_size, # tickSize()
23
+ 1 => :bid_price, # tickPrice()
24
+ 2 => :ask_price, # tickPrice()
25
+ 3 => :ask_size, # tickSize()
26
+ 4 => :last_price, # tickPrice()
27
+ 5 => :last_size, # tickSize()
28
+ 6 => :high, # tickPrice()
29
+ 7 => :low, # tickPrice()
30
+ 8 => :volume, # tickSize()
31
+ 9 => :close_price, # tickPrice()
32
+ 10 => :bid_option, # tickOptionComputation() See Note 1 below
33
+ 11 => :ask_option, # tickOptionComputation() See => :Note 1 below
34
+ 12 => :last_option, # tickOptionComputation() See Note 1 below
35
+ 13 => :model_option, # tickOptionComputation() See Note 1 below
36
+ 14 => :open_tick, # tickPrice()
37
+ 15 => :low_13_week, # tickPrice()
38
+ 16 => :high_13_week, # tickPrice()
39
+ 17 => :low_26_week, # tickPrice()
40
+ 18 => :high_26_week, # tickPrice()
41
+ 19 => :low_52_week, # tickPrice()
42
+ 20 => :high_52_week, # tickPrice()
43
+ 21 => :avg_volume, # tickSize()
44
+ 22 => :open_interest, # tickSize()
45
+ 23 => :option_historical_vol, # tickGeneric()
46
+ 24 => :option_implied_vol, # tickGeneric()
47
+ 25 => :option_bid_exch, # not USED
48
+ 26 => :option_ask_exch, # not USED
49
+ 27 => :option_call_open_interest, # tickSize()
50
+ 28 => :option_put_open_interest, # tickSize()
51
+ 29 => :option_call_volume, # tickSize()
52
+ 30 => :option_put_volume, # tickSize()
53
+ 31 => :index_future_premium, # tickGeneric()
54
+ 32 => :bid_exch, # tickString()
55
+ 33 => :ask_exch, # tickString()
56
+ 34 => :auction_volume, # not USED
57
+ 35 => :auction_price, # not USED
58
+ 36 => :auction_imbalance, # not USED
59
+ 37 => :mark_price, # tickPrice()
60
+ 38 => :bid_efp_computation, # tickEFP()
61
+ 39 => :ask_efp_computation, # tickEFP()
62
+ 40 => :last_efp_computation, # tickEFP()
63
+ 41 => :open_efp_computation, # tickEFP()
64
+ 42 => :high_efp_computation, # tickEFP()
65
+ 43 => :low_efp_computation, # tickEFP()
66
+ 44 => :close_efp_computation, # tickEFP()
67
+ 45 => :last_timestamp, # tickString()
68
+ 46 => :shortable, # tickGeneric()
69
+ 47 => :fundamental_ratios, # tickString()
70
+ 48 => :rt_volume, # tickGeneric()
71
+ 49 => :halted, # see Note 2 below.
72
+ 50 => :bid_yield, # tickPrice() See Note 3 below
73
+ 51 => :asky_ield, # tickPrice() See Note 3 below
74
+ 52 => :last_yield, # tickPrice() See Note 3 below
75
+ 53 => :cust_option_computation, # tickOptionComputation()
76
+ 54 => :trade_count, # tickGeneric()
77
+ 55 => :trade_rate, # tickGeneric()
78
+ 56 => :volume_rate, # tickGeneric()
79
+ 57 => :last_rth_trade, # ?
80
+ # Note 1: Tick types BID_OPTION, ASK_OPTION, LAST_OPTION, and MODEL_OPTION return
81
+ # all Greeks (delta, gamma, vega, theta), the underlying price and the
82
+ # stock and option reference price when requested.
83
+ # MODEL_OPTION also returns model implied volatility.
84
+ # Note 2: When trading is halted for a contract, TWS receives a special tick:
85
+ # haltedLast=1. When trading is resumed, TWS receives haltedLast=0.
86
+ # A tick type, HALTED, tick ID= 49, is now available in regular market
87
+ # data via the API to indicate this halted state. Possible values for
88
+ # this new tick type are: 0 = Not halted, 1 = Halted.
89
+ # Note 3: Applies to bond contracts only.
90
+ }
91
+ end # module IB