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,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