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.
- data/.gitignore +32 -0
- data/HISTORY +68 -0
- data/README.rdoc +9 -6
- data/VERSION +1 -1
- data/bin/account_info +29 -0
- data/bin/contract_details +37 -0
- data/bin/depth_of_market +43 -0
- data/bin/historic_data +62 -0
- data/bin/{RequestHistoricData → historic_data_cli} +46 -91
- data/bin/market_data +49 -0
- data/bin/option_data +45 -0
- data/bin/template +21 -0
- data/bin/time_and_sales +63 -0
- data/lib/ib-ruby/connection.rb +166 -0
- data/lib/ib-ruby/constants.rb +91 -0
- data/lib/ib-ruby/messages/incoming.rb +807 -0
- data/lib/ib-ruby/messages/outgoing.rb +573 -0
- data/lib/ib-ruby/messages.rb +8 -1445
- data/lib/ib-ruby/models/bar.rb +26 -0
- data/lib/ib-ruby/models/contract.rb +335 -0
- data/lib/ib-ruby/models/execution.rb +55 -0
- data/lib/ib-ruby/models/model.rb +20 -0
- data/lib/ib-ruby/models/order.rb +262 -0
- data/lib/ib-ruby/models.rb +11 -0
- data/lib/ib-ruby/socket.rb +50 -0
- data/lib/ib-ruby/symbols/forex.rb +32 -72
- data/lib/ib-ruby/symbols/futures.rb +47 -68
- data/lib/ib-ruby/symbols/options.rb +30 -0
- data/lib/ib-ruby/symbols/stocks.rb +23 -0
- data/lib/ib-ruby/symbols.rb +9 -0
- data/lib/ib-ruby.rb +7 -8
- data/lib/legacy/bin/account_info_old +36 -0
- data/lib/legacy/bin/historic_data_old +81 -0
- data/lib/legacy/bin/market_data_old +68 -0
- data/lib/legacy/datatypes.rb +485 -0
- data/lib/legacy/ib-ruby.rb +10 -0
- data/lib/legacy/ib.rb +226 -0
- data/lib/legacy/messages.rb +1458 -0
- data/lib/version.rb +2 -3
- data/spec/ib-ruby/models/contract_spec.rb +261 -0
- data/spec/ib-ruby/models/order_spec.rb +64 -0
- data/spec/ib-ruby_spec.rb +0 -131
- metadata +106 -76
- data/bin/AccountInfo +0 -67
- data/bin/HistoricToCSV +0 -111
- data/bin/RequestMarketData +0 -78
- data/bin/SimpleTimeAndSales +0 -98
- data/bin/ib-ruby +0 -8
- data/lib/ib-ruby/datatypes.rb +0 -400
- data/lib/ib-ruby/ib.rb +0 -242
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
|