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,10 @@
1
+ require 'version'
2
+
3
+ module IbRuby
4
+ end # module IbRuby
5
+
6
+ require 'ib-ruby/datatypes'
7
+ require 'ib-ruby/ib'
8
+ require 'ib-ruby/messages'
9
+ require 'ib-ruby/symbols/forex'
10
+ require 'ib-ruby/symbols/futures'
data/lib/legacy/ib.rb ADDED
@@ -0,0 +1,226 @@
1
+ require 'socket'
2
+ require 'logger'
3
+ require 'bigdecimal'
4
+ require 'bigdecimal/util'
5
+
6
+ if RUBY_VERSION < "1.9"
7
+ require 'sha1'
8
+ else
9
+ require 'digest/sha1'
10
+ include Digest
11
+ end
12
+
13
+ # Add method to_ib to render datetime in IB format (zero padded "yyyymmdd HH:mm:ss")
14
+ class Time
15
+ def to_ib
16
+ "#{self.year}#{sprintf("%02d", self.month)}#{sprintf("%02d", self.day)} " +
17
+ "#{sprintf("%02d", self.hour)}:#{sprintf("%02d", self.min)}:#{sprintf("%02d", self.sec)}"
18
+ end
19
+ end # Time
20
+
21
+
22
+ module IB
23
+
24
+ #logger = Logger.new(STDERR)
25
+
26
+ class IBSocket < TCPSocket
27
+
28
+ # send nice null terminated binary data
29
+ def send(data)
30
+ self.syswrite(data.to_s + "\0")
31
+ end
32
+
33
+ def read_string
34
+ str = self.gets("\0").chop
35
+ #if str.nil?
36
+ # p 'NIL! FReaking NILLLLLLLLLLLLLLLLLLLLLLLL!'
37
+ # ''
38
+ #else
39
+ # str.chop
40
+ #end
41
+ end
42
+
43
+ def read_int
44
+ self.read_string.to_i
45
+ end
46
+
47
+ def read_int_max
48
+ str = self.read_string
49
+ str.nil? || str.empty? ? nil : str.to_i
50
+ end
51
+
52
+ def read_boolean
53
+ self.read_string.to_i != 0
54
+ end
55
+
56
+ def read_decimal
57
+ # Floating-point numbers shouldn't be used to store money...
58
+ self.read_string.to_d
59
+ end
60
+
61
+ def read_decimal_max
62
+ str = self.read_string
63
+ # Floating-point numbers shouldn't be used to store money...
64
+ str.nil? || str.empty? ? nil : str.to_d
65
+ end
66
+ end
67
+
68
+ # class IBSocket
69
+
70
+ class IB
71
+
72
+ # Please note, we are realizing only the most current TWS protocol versions,
73
+ # thus improving performance at the expense of backwards compatibility.
74
+ # Older protocol versions can be found in older gem versions.
75
+
76
+ CLIENT_VERSION = 27 # 48 drops dead # Was 27 in original Ruby code
77
+ SERVER_VERSION = 53 # Minimal server version. Latest, was 38 in current Java code.
78
+ TWS_IP_ADDRESS = "127.0.0.1"
79
+ TWS_PORT = "7496"
80
+
81
+ attr_reader :next_order_id
82
+
83
+ def initialize(options_in = {})
84
+ @options = {:ip => TWS_IP_ADDRESS, :port => TWS_PORT, }.merge(options_in)
85
+
86
+ @connected = false
87
+ @next_order_id = nil
88
+ @server = Hash.new # information about server and server connection state
89
+
90
+ # Message listeners. Key is the message class to listen for.
91
+ # Value is an Array of Procs. The proc will be called with the populated message
92
+ # instance as its argument when a message of that type is received.
93
+ @listeners = Hash.new { |hash, key| hash[key] = Array.new }
94
+
95
+ #logger.debug("IB#init: Initializing...")
96
+
97
+ self.open(@options)
98
+ end
99
+
100
+ def server_version
101
+ @server[:version]
102
+ end
103
+
104
+
105
+ def open(options_in = {})
106
+ raise Exception.new("Already connected!") if @connected
107
+
108
+ opts = @options.merge(options_in)
109
+
110
+ # Subscribe to the NextValidID message from TWS that is always
111
+ # sent at connect, and save the id.
112
+ self.subscribe(IncomingMessages::NextValidID) do |msg|
113
+ @next_order_id = msg.data[:id]
114
+ p "Got next valid order id #{@next_order_id}."
115
+ end
116
+
117
+ @server[:socket] = IBSocket.open(opts[:ip], opts[:port])
118
+ #logger.info("* TWS socket connected to #{@options[:ip]}:#{@options[:port]}.")
119
+
120
+ # Secret handshake.
121
+ @server[:socket].send(CLIENT_VERSION)
122
+ @server[:version] = @server[:socket].read_int
123
+ @server[:local_connect_time] = Time.now()
124
+ @@server_version = @server[:version]
125
+ raise(Exception.new("TWS version >= #{SERVER_VERSION} required.")) if @@server_version < SERVER_VERSION
126
+
127
+ puts "\tGot server version: #{@server[:version]}."
128
+ #logger.debug("\tGot server version: #{@server[:version]}.")
129
+
130
+ # Server version >= 20 sends the server time back. Our min server version is 38
131
+ @server[:remote_connect_time] = @server[:socket].read_string
132
+ #logger.debug("\tServer connect time: #{@server[:remote_connect_time]}.")
133
+
134
+ # Server wants an arbitrary client ID at this point. This can be used
135
+ # to identify subsequent communications.
136
+ @server[:client_id] = SHA1.digest(Time.now.to_s + $$.to_s).unpack("C*").join.to_i % 999999999
137
+ @server[:socket].send(@server[:client_id])
138
+ #logger.debug("\tSent client id # #{@server[:client_id]}.")
139
+
140
+ #logger.debug("Starting reader thread..")
141
+ Thread.abort_on_exception = true
142
+ @server[:reader_thread] = Thread.new { self.reader }
143
+
144
+ @connected = true
145
+ end
146
+
147
+ def close
148
+ @server[:reader_thread].kill # Thread uses blocking I/O, so join is useless.
149
+ @server[:socket].close()
150
+ @server = Hash.new
151
+ @@server_version = nil
152
+ @connected = false
153
+ #logger.debug("Disconnected.")
154
+ end
155
+
156
+ def to_s
157
+ "IB Connector: #{ @connected ? "connected." : "disconnected."}"
158
+ end
159
+
160
+ # Subscribe to incoming message events of type message_class.
161
+ # code is a Proc that will be called with the message instance as its argument.
162
+ def subscribe(message_class, code = nil, &block)
163
+ code ||= block
164
+
165
+ raise ArgumentError.new "Need listener proc or block" unless code.is_a? Proc
166
+ unless message_class < IncomingMessages::AbstractMessage
167
+ raise ArgumentError.new "#{message_class} must be an IB message class"
168
+ end
169
+
170
+ @listeners[message_class].push(code)
171
+ end
172
+
173
+ # Send an outgoing message.
174
+ def dispatch(message)
175
+ raise Exception.new("dispatch() must be given an OutgoingMessages::AbstractMessage subclass") unless message.is_a?(OutgoingMessages::AbstractMessage)
176
+
177
+ #logger.info("Sending message " + message.inspect)
178
+ message.send(@server)
179
+ end
180
+
181
+ protected
182
+
183
+ def reader
184
+ #logger.debug("Reader started.")
185
+
186
+ while true
187
+ # this blocks, so Thread#join is useless.
188
+ msg_id = @server[:socket].read_int
189
+
190
+ p "Reader: got message id #{msg_id}." unless [4, 6, 7, 8, 53].include? msg_id
191
+
192
+ if msg_id == 0
193
+ p "Zero msg id! Must be a nil passed in... Ignoring..."
194
+ else
195
+ # Create a new instance of the appropriate message type, and have it read the message.
196
+ # NB: Failure here usually means unsupported message type received
197
+ msg = IncomingMessages::Table[msg_id].new(@server[:socket], @server[:version])
198
+
199
+ @listeners[msg.class].each { |listener|
200
+ listener.call(msg)
201
+ }
202
+
203
+ #logger.debug { " Listeners: #{@listeners.inspect} inclusion: #{ @listeners.include?(msg.class)}" }
204
+
205
+ # Log the error messages. Make an exception for the "successfully connected"
206
+ # messages, which, for some reason, come back from IB as errors.
207
+ if msg.is_a?(IncomingMessages::Error)
208
+ # connect strings
209
+ if msg.code == 2104 || msg.code == 2106
210
+ #logger.info(msg.to_human)
211
+ else
212
+ #logger.error(msg.to_human)
213
+ end
214
+ else
215
+ # Warn if nobody listened to a non-error incoming message.
216
+ unless @listeners[msg.class].size > 0
217
+ #logger.warn { " WARNING: Nobody listened to incoming message #{msg.class}" }
218
+ end
219
+ end
220
+ end
221
+ # #logger.debug("Reader done with message id #{msg_id}.")
222
+ end # while
223
+ end # reader
224
+
225
+ end # class IB
226
+ end # module IB