ib-ruby 0.4.3 → 0.4.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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