ib-ruby 0.5.21 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY +8 -0
- data/README.md +46 -27
- data/TODO +13 -2
- data/VERSION +1 -1
- data/bin/generic_data.rb +26 -0
- data/bin/place_order +1 -1
- data/lib/ib-ruby/connection.rb +126 -65
- data/lib/ib-ruby/messages/incoming.rb +3 -3
- data/lib/ib-ruby/models/bar.rb +11 -11
- data/lib/ib-ruby/models/combo_leg.rb +23 -29
- data/lib/ib-ruby/models/contract/bag.rb +34 -2
- data/lib/ib-ruby/models/contract/option.rb +2 -2
- data/lib/ib-ruby/models/contract.rb +151 -197
- data/lib/ib-ruby/models/execution.rb +27 -45
- data/lib/ib-ruby/models/model.rb +10 -4
- data/lib/ib-ruby/models/model_properties.rb +63 -0
- data/lib/ib-ruby/models/order.rb +274 -320
- data/lib/ib-ruby/symbols/stocks.rb +11 -5
- data/spec/account_helper.rb +80 -0
- data/spec/ib-ruby/connection_spec.rb +195 -52
- data/spec/ib-ruby/messages/incoming_spec.rb +4 -4
- data/spec/ib-ruby/models/combo_leg_spec.rb +1 -0
- data/spec/ib-ruby/models/contract_spec.rb +1 -1
- data/spec/ib-ruby/models/execution_spec.rb +73 -0
- data/spec/integration/account_info_spec.rb +12 -59
- data/spec/integration/contract_info_spec.rb +23 -37
- data/spec/integration/depth_data_spec.rb +4 -4
- data/spec/integration/historic_data_spec.rb +15 -27
- data/spec/integration/market_data_spec.rb +74 -61
- data/spec/integration/option_data_spec.rb +5 -48
- data/spec/integration/orders/execution_spec.rb +26 -31
- data/spec/integration/orders/open_order +2 -0
- data/spec/integration/orders/placement_spec.rb +28 -28
- data/spec/integration/orders/valid_ids_spec.rb +11 -11
- data/spec/integration_helper.rb +46 -32
- data/spec/message_helper.rb +4 -38
- data/spec/spec_helper.rb +2 -3
- metadata +9 -2
data/HISTORY
CHANGED
data/README.md
CHANGED
@@ -11,12 +11,9 @@ implied. Your use of this software is at your own risk. It may contain
|
|
11
11
|
any number of bugs, known or unknown, which might cause you to lose
|
12
12
|
money if you use it. You've been warned.
|
13
13
|
|
14
|
-
__It is specifically NOT RECOMMENDED that this code be used for live trading.__
|
15
|
-
|
16
14
|
This code is not sanctioned or supported by Interactive Brokers
|
17
15
|
This software is available under the LGPL. See the file LICENSE for full licensing details.
|
18
16
|
|
19
|
-
|
20
17
|
## REQUIREMENTS:
|
21
18
|
|
22
19
|
Either the Interactive Brokers
|
@@ -42,30 +39,52 @@ localhost if you're running ib-ruby on the same machine as TWS.
|
|
42
39
|
First, start up Interactive Broker's Trader Work Station or Gateway.
|
43
40
|
Make sure it is configured to allow API connections on localhost.
|
44
41
|
Note that TWS and Gateway listen to different ports, this library assumes
|
45
|
-
connection to Gateway (localhost:4001) by default, this can changed via :host
|
46
|
-
options given to IB::Connection.new.
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
42
|
+
connection to Gateway (localhost:4001) by default, this can changed via :host
|
43
|
+
and :port options given to IB::Connection.new.
|
44
|
+
|
45
|
+
require 'ib-ruby'
|
46
|
+
|
47
|
+
ib = IB::Connection.new
|
48
|
+
ib.subscribe(:Alert, :AccountValue) { |msg| puts msg.to_human }
|
49
|
+
ib.send_message :RequestAccountData
|
50
|
+
ib.wait_for :AccountDownloadEnd
|
51
|
+
|
52
|
+
ib.subscribe(:OpenOrder) { |msg| puts "Placed: #{msg.order}!" }
|
53
|
+
ib.subscribe(:ExecutionData) { |msg| puts "Filled: #{msg.execution}!" }
|
54
|
+
contract = IB::Models::Contract.new :symbol => 'WFC',
|
55
|
+
:exchange => 'NYSE'
|
56
|
+
:currency => 'USD',
|
57
|
+
:sec_type => IB::SECURITY_TYPES[:stock]
|
58
|
+
buy_order = IB::Models::Order.new :total_quantity => 100,
|
59
|
+
:limit_price => 21.00,
|
60
|
+
:action => 'BUY',
|
61
|
+
:order_type => 'LMT'
|
62
|
+
ib.place_order buy_order, contract
|
63
|
+
ib.wait_for :ExecutionData
|
64
|
+
|
65
|
+
Your code interacts with TWS via exchange of messages. Messages that you send to
|
66
|
+
TWS are called 'Outgoing', messages your code receives from TWS - 'Incoming'.
|
67
|
+
|
68
|
+
First, you need to subscribe to incoming message types you're interested in
|
69
|
+
using `Connection#subscribe`. The code block (or proc) given to `#subscribe`
|
70
|
+
will be executed when an incoming message of the requested type is received
|
71
|
+
from TWS, with the received message as its argument.
|
72
|
+
|
73
|
+
Then, you request specific data from TWS using `Connection#send_message` or place
|
74
|
+
your order using `Connection#place_order`. TWS will respond with messages that you
|
75
|
+
should have subscribed for, and these messages are be processed in a code block
|
76
|
+
given to `#subscribe`.
|
77
|
+
|
78
|
+
In order to give TWS time to respond, you either run a message processing loop or
|
79
|
+
just wait until Connection receives the messages type you requested.
|
80
|
+
|
81
|
+
See `lib/ib-ruby/messages` for a full list of supported incoming/outgoing messages
|
82
|
+
and their attributes. The original TWS docs and code samples can be found
|
83
|
+
in `misc` directory.
|
84
|
+
|
85
|
+
The sample scripts in `bin` directory provide examples of how common tasks
|
86
|
+
can be achieved using ib-ruby. You may also want to look into `spec/integration`
|
87
|
+
directory for more scenarios and examples of handling IB messages.
|
69
88
|
|
70
89
|
## LICENSE:
|
71
90
|
|
data/TODO
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
1. Create integration tests for basic use cases
|
2
|
-
|
3
1
|
Plan:
|
4
2
|
|
3
|
+
1. Detailed message documentation
|
4
|
+
|
5
5
|
2. Make ActiveModel-like attributes Hash for easy attributes updating
|
6
6
|
|
7
7
|
3. IB#send_message method should accept block, thus compressing subscribe/send_message
|
@@ -14,8 +14,19 @@ http://finance.groups.yahoo.com/group/TWSAPI/message/25413
|
|
14
14
|
|
15
15
|
6. Compatibility check for new TWS v.966
|
16
16
|
|
17
|
+
7. @received_at timestamp in messages
|
18
|
+
|
19
|
+
8. Collect all messages in Connection#received_messages
|
20
|
+
|
21
|
+
9. Flow handlers: Connection#wait_for / Connection#received?
|
22
|
+
|
23
|
+
10. Create integration tests for more use cases (spec/README)
|
24
|
+
|
25
|
+
|
17
26
|
Done:
|
18
27
|
|
28
|
+
1. Create integration tests for basic use cases
|
29
|
+
|
19
30
|
2. IB#subscribe should accept regexes.
|
20
31
|
|
21
32
|
Ideas for future:
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.6.1
|
data/bin/generic_data.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This script reproduces https://github.com/ib-ruby/ib-ruby/issues/12
|
4
|
+
|
5
|
+
require 'pathname'
|
6
|
+
LIB_DIR = (Pathname.new(__FILE__).dirname + '../lib/').realpath.to_s
|
7
|
+
$LOAD_PATH.unshift LIB_DIR unless $LOAD_PATH.include?(LIB_DIR)
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'bundler/setup'
|
11
|
+
require 'ib-ruby'
|
12
|
+
|
13
|
+
contract = IB::Models::Contract.new :symbol=> 'AAPL',
|
14
|
+
:exchange=> "Smart",
|
15
|
+
:currency=> "USD",
|
16
|
+
:sec_type=> IB::SECURITY_TYPES[:stock],
|
17
|
+
:description=> "Some stock"
|
18
|
+
ib = IB::Connection.new
|
19
|
+
ib.subscribe(:Alert) { |msg| puts msg.to_human }
|
20
|
+
ib.subscribe(:TickGeneric, :TickString, :TickPrice, :TickSize) { |msg| puts msg.inspect }
|
21
|
+
ib.send_message :RequestMarketData, :id => 123, :contract => contract
|
22
|
+
|
23
|
+
puts "\nSubscribed to market data"
|
24
|
+
puts "\n******** Press <Enter> to cancel... *********\n\n"
|
25
|
+
gets
|
26
|
+
puts "Cancelling market data subscription.."
|
data/bin/place_order
CHANGED
data/lib/ib-ruby/connection.rb
CHANGED
@@ -15,9 +15,10 @@ module IB
|
|
15
15
|
:port => '4001', # IB Gateway connection (default)
|
16
16
|
#:port => '7496', # TWS connection, with annoying pop-ups
|
17
17
|
:client_id => nil, # Will be randomly assigned
|
18
|
-
:connect => true,
|
19
|
-
:reader => true,
|
20
|
-
:
|
18
|
+
:connect => true, # Connect at initialization
|
19
|
+
:reader => true, # Start a separate reader Thread
|
20
|
+
:received => true, # Keep all received messages in a Hash
|
21
|
+
:logger => nil,
|
21
22
|
}
|
22
23
|
|
23
24
|
# Singleton to make active Connection universally accessible as IB::Connection.current
|
@@ -28,7 +29,7 @@ module IB
|
|
28
29
|
attr_reader :server # Info about IB server and server connection state
|
29
30
|
attr_accessor :next_order_id # Next valid order id
|
30
31
|
|
31
|
-
def initialize
|
32
|
+
def initialize opts = {}
|
32
33
|
@options = DEFAULT_OPTIONS.merge(opts)
|
33
34
|
|
34
35
|
self.default_logger = @options[:logger] if @options[:logger]
|
@@ -40,19 +41,13 @@ module IB
|
|
40
41
|
Connection.current = self
|
41
42
|
end
|
42
43
|
|
43
|
-
|
44
|
-
# Value is a Hash of subscriber Procs, keyed by their subscription id.
|
45
|
-
# All subscriber Procs will be called with the message instance
|
46
|
-
# as an argument when a message of that type is received.
|
47
|
-
def subscribers
|
48
|
-
@subscribers ||= Hash.new { |hash, key| hash[key] = Hash.new }
|
49
|
-
end
|
44
|
+
### Working with connection
|
50
45
|
|
51
46
|
def connect
|
52
47
|
raise "Already connected!" if connected?
|
53
48
|
|
54
|
-
# TWS always sends
|
55
|
-
self.subscribe(:
|
49
|
+
# TWS always sends NextValidId message at connect - save this id
|
50
|
+
self.subscribe(:NextValidId) do |msg|
|
56
51
|
@next_order_id = msg.order_id
|
57
52
|
log.info "Got next valid order id: #{@next_order_id}."
|
58
53
|
end
|
@@ -101,16 +96,14 @@ module IB
|
|
101
96
|
@connected
|
102
97
|
end
|
103
98
|
|
104
|
-
|
105
|
-
@reader_running && @server[:reader] && @server[:reader].alive?
|
106
|
-
end
|
99
|
+
### Working with message subscribers
|
107
100
|
|
108
101
|
# Subscribe Proc or block to specific type(s) of incoming message events.
|
109
102
|
# Listener will be called later with received message instance as its argument.
|
110
103
|
# Returns subscriber id to allow unsubscribing
|
111
|
-
def subscribe
|
104
|
+
def subscribe *args, &block
|
112
105
|
subscriber = args.last.respond_to?(:call) ? args.pop : block
|
113
|
-
|
106
|
+
id = random_id
|
114
107
|
|
115
108
|
raise ArgumentError.new "Need subscriber proc or block" unless subscriber.is_a? Proc
|
116
109
|
|
@@ -118,50 +111,110 @@ module IB
|
|
118
111
|
message_classes =
|
119
112
|
case
|
120
113
|
when what.is_a?(Class) && what < Messages::Incoming::AbstractMessage
|
121
|
-
what
|
114
|
+
[what]
|
122
115
|
when what.is_a?(Symbol)
|
123
|
-
Messages::Incoming.const_get(what)
|
116
|
+
[Messages::Incoming.const_get(what)]
|
124
117
|
when what.is_a?(Regexp)
|
125
118
|
Messages::Incoming::Table.values.find_all { |klass| klass.to_s =~ what }
|
126
119
|
else
|
127
120
|
raise ArgumentError.new "#{what} must represent incoming IB message class"
|
128
121
|
end
|
129
|
-
|
122
|
+
message_classes.flatten.each do |message_class|
|
130
123
|
# TODO: Fix: RuntimeError: can't add a new key into hash during iteration
|
131
|
-
subscribers[message_class][
|
124
|
+
subscribers[message_class][id] = subscriber
|
132
125
|
end
|
133
126
|
end
|
134
|
-
|
127
|
+
id
|
135
128
|
end
|
136
129
|
|
137
130
|
# Remove all subscribers with specific subscriber id (TODO: multiple ids)
|
138
|
-
def unsubscribe
|
131
|
+
def unsubscribe *ids
|
132
|
+
removed = []
|
133
|
+
ids.each do |id|
|
134
|
+
removed_at_id = subscribers.map { |_, subscribers| subscribers.delete id }.compact
|
135
|
+
raise "No subscribers with id #{id}" if removed_at_id.empty?
|
136
|
+
removed << removed_at_id
|
137
|
+
end
|
138
|
+
removed.flatten
|
139
|
+
end
|
140
|
+
|
141
|
+
# Message subscribers. Key is the message class to listen for.
|
142
|
+
# Value is a Hash of subscriber Procs, keyed by their subscription id.
|
143
|
+
# All subscriber Procs will be called with the message instance
|
144
|
+
# as an argument when a message of that type is received.
|
145
|
+
def subscribers
|
146
|
+
@subscribers ||= Hash.new { |hash, subs| hash[subs] = Hash.new }
|
147
|
+
end
|
148
|
+
|
149
|
+
## Check if subscribers for given type exists
|
150
|
+
#def subscribed? message_type
|
151
|
+
# message_type
|
152
|
+
# (subscribers[message_type.class] ||
|
153
|
+
# subscribers[message_type.class] ||
|
154
|
+
# subscribers[message_type]).empty?
|
155
|
+
#end
|
156
|
+
|
157
|
+
### Working with received messages Hash
|
158
|
+
|
159
|
+
# Hash of received messages, keyed by message type
|
160
|
+
def received
|
161
|
+
@received ||= Hash.new { |hash, message_type| hash[message_type] = Array.new }
|
162
|
+
end
|
139
163
|
|
140
|
-
|
141
|
-
|
164
|
+
# Check if messages of given type were received at_least n times
|
165
|
+
def received? message_type, times=1
|
166
|
+
received[message_type].size >= times
|
167
|
+
end
|
168
|
+
|
169
|
+
# Clear received messages Hash
|
170
|
+
def clear_received message_type=nil
|
171
|
+
if message_type
|
172
|
+
received[message_type].clear
|
173
|
+
else
|
174
|
+
received.each { |_, message_type| message_type.clear }
|
142
175
|
end
|
143
176
|
end
|
144
177
|
|
145
|
-
#
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
178
|
+
# Wait for specific condition(s) - given as callable/block, or
|
179
|
+
# message type(s) - given as Symbol or [Symbol, times] pair.
|
180
|
+
# Timeout after given time or 2 seconds.
|
181
|
+
def wait_for *args, &block
|
182
|
+
time = args.find { |arg| arg.is_a? Numeric } || 2
|
183
|
+
timeout = Time.now + time
|
184
|
+
args.push(block) if block
|
185
|
+
|
186
|
+
sleep 0.1 until timeout < Time.now ||
|
187
|
+
args.inject(true) do |result, arg|
|
188
|
+
result && if arg.is_a?(Symbol)
|
189
|
+
received?(arg)
|
190
|
+
elsif arg.is_a?(Array)
|
191
|
+
received?(*arg)
|
192
|
+
elsif arg.respond_to?(:call)
|
193
|
+
arg.call
|
194
|
+
else
|
195
|
+
true
|
196
|
+
end
|
157
197
|
end
|
158
|
-
raise "Not able to send messages, IB not connected!" unless connected?
|
159
|
-
message.send_to(@server)
|
160
198
|
end
|
161
199
|
|
162
|
-
|
200
|
+
### Working with Incoming messages from IB
|
201
|
+
|
202
|
+
# Start reader thread that continuously reads messages from server in background.
|
203
|
+
# If you don't start reader, you should manually poll @server[:socket] for messages
|
204
|
+
# or use #process_messages(msec) API.
|
205
|
+
def start_reader
|
206
|
+
Thread.abort_on_exception = true
|
207
|
+
@reader_running = true
|
208
|
+
@server[:reader] = Thread.new do
|
209
|
+
process_messages while @reader_running
|
210
|
+
end
|
211
|
+
end
|
163
212
|
|
164
|
-
|
213
|
+
def reader_running?
|
214
|
+
@reader_running && @server[:reader] && @server[:reader].alive?
|
215
|
+
end
|
216
|
+
|
217
|
+
# Process incoming messages during *poll_time* (200) msecs, nonblocking
|
165
218
|
def process_messages poll_time = 200 # in msec
|
166
219
|
time_out = Time.now + poll_time/1000.0
|
167
220
|
while (time_left = time_out - Time.now) > 0
|
@@ -170,27 +223,48 @@ module IB
|
|
170
223
|
end
|
171
224
|
end
|
172
225
|
|
173
|
-
# Process single incoming message (blocking)
|
226
|
+
# Process single incoming message (blocking!)
|
174
227
|
def process_message
|
175
|
-
# This read blocks!
|
176
|
-
msg_id = @server[:socket].read_int
|
228
|
+
msg_id = @server[:socket].read_int # This read blocks!
|
177
229
|
|
178
230
|
# Debug:
|
179
|
-
|
180
|
-
log.debug "Got message #{msg_id} (#{Messages::Incoming::Table[msg_id]})"
|
181
|
-
end
|
231
|
+
log.debug "Got message #{msg_id} (#{Messages::Incoming::Table[msg_id]})"
|
182
232
|
|
183
233
|
# Create new instance of the appropriate message type, and have it read the message.
|
184
234
|
# NB: Failure here usually means unsupported message type received
|
185
235
|
msg = Messages::Incoming::Table[msg_id].new(@server[:socket])
|
186
236
|
|
237
|
+
# Deliver message to all registered subscribers, alert if no subscribers
|
187
238
|
subscribers[msg.class].each { |_, subscriber| subscriber.call(msg) }
|
188
239
|
log.warn "No subscribers for message #{msg.class}!" if subscribers[msg.class].empty?
|
240
|
+
|
241
|
+
# Collect all received messages into a @received Hash
|
242
|
+
received[msg.message_type] << msg if @options[:received]
|
189
243
|
end
|
190
244
|
|
191
|
-
|
192
|
-
|
193
|
-
#
|
245
|
+
### Sending Outgoing messages to IB
|
246
|
+
|
247
|
+
# Send an outgoing message.
|
248
|
+
def send_message what, *args
|
249
|
+
message =
|
250
|
+
case
|
251
|
+
when what.is_a?(Messages::Outgoing::AbstractMessage)
|
252
|
+
what
|
253
|
+
when what.is_a?(Class) && what < Messages::Outgoing::AbstractMessage
|
254
|
+
what.new *args
|
255
|
+
when what.is_a?(Symbol)
|
256
|
+
Messages::Outgoing.const_get(what).new *args
|
257
|
+
else
|
258
|
+
raise ArgumentError.new "Only able to send outgoing IB messages"
|
259
|
+
end
|
260
|
+
raise "Not able to send messages, IB not connected!" unless connected?
|
261
|
+
message.send_to(@server)
|
262
|
+
end
|
263
|
+
|
264
|
+
alias dispatch send_message # Legacy alias
|
265
|
+
|
266
|
+
# Place Order (convenience wrapper for send_message :PlaceOrder).
|
267
|
+
# Assigns client_id and order_id fields to placed order. Returns assigned order_id.
|
194
268
|
def place_order order, contract
|
195
269
|
send_message :PlaceOrder,
|
196
270
|
:order => order,
|
@@ -202,24 +276,13 @@ module IB
|
|
202
276
|
order.order_id
|
203
277
|
end
|
204
278
|
|
205
|
-
# Cancel Orders by their
|
279
|
+
# Cancel Orders by their ids (convenience wrapper for send_message :CancelOrder).
|
206
280
|
def cancel_order *order_ids
|
207
281
|
order_ids.each do |order_id|
|
208
282
|
send_message :CancelOrder, :id => order_id.to_i
|
209
283
|
end
|
210
284
|
end
|
211
285
|
|
212
|
-
# Start reader thread that continuously reads messages from server in background.
|
213
|
-
# If you don't start reader, you should manually poll @server[:socket] for messages
|
214
|
-
# or use #process_messages(msec) API.
|
215
|
-
def start_reader
|
216
|
-
Thread.abort_on_exception = true
|
217
|
-
@reader_running = true
|
218
|
-
@server[:reader] = Thread.new do
|
219
|
-
process_messages while @reader_running
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
286
|
protected
|
224
287
|
|
225
288
|
def random_id
|
@@ -227,6 +290,4 @@ module IB
|
|
227
290
|
end
|
228
291
|
|
229
292
|
end # class Connection
|
230
|
-
#IB = Connection # Legacy alias
|
231
|
-
|
232
293
|
end # module IB
|
@@ -146,7 +146,7 @@ module IB
|
|
146
146
|
# This message is always sent by TWS automatically at connect.
|
147
147
|
# The IB::Connection class subscribes to it automatically and stores
|
148
148
|
# the order id in its @next_order_id attribute.
|
149
|
-
NextValidID = def_message
|
149
|
+
NextValidID = NextValidId = def_message(9, [:order_id, :int])
|
150
150
|
|
151
151
|
NewsBulletins =
|
152
152
|
def_message 14, [:request_id, :int], # unique incrementing bulletin ID.
|
@@ -246,7 +246,7 @@ module IB
|
|
246
246
|
[:tick_type, :int],
|
247
247
|
[:size, :int]
|
248
248
|
|
249
|
-
TickGeneric = def_message 45, AbstractTick,
|
249
|
+
TickGeneric = def_message [45, 6], AbstractTick,
|
250
250
|
[:ticker_id, :int],
|
251
251
|
[:tick_type, :int],
|
252
252
|
[:value, :decimal]
|
@@ -256,7 +256,7 @@ module IB
|
|
256
256
|
[:tick_type, :int],
|
257
257
|
[:value, :string]
|
258
258
|
|
259
|
-
TickEFP = def_message 47, AbstractTick,
|
259
|
+
TickEFP = def_message [47, 6], AbstractTick,
|
260
260
|
[:ticker_id, :int],
|
261
261
|
[:tick_type, :int],
|
262
262
|
[:basis_points, :decimal],
|
data/lib/ib-ruby/models/bar.rb
CHANGED
@@ -5,17 +5,17 @@ module IB
|
|
5
5
|
# This is a single data point delivered by HistoricData messages.
|
6
6
|
# Instantiate with a Hash of attributes, to be auto-set via initialize in Model.
|
7
7
|
class Bar < Model
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
8
|
+
prop :time, # The date-time stamp of the start of the bar. The format is
|
9
|
+
# determined by the reqHistoricalData() formatDate parameter.
|
10
|
+
:open, # The bar opening price.
|
11
|
+
:high, # The high price during the time covered by the bar.
|
12
|
+
:low, # The low price during the time covered by the bar.
|
13
|
+
:close, # The bar closing price.
|
14
|
+
:volume, # The bar opening price.
|
15
|
+
:wap, # Weighted average price during the time covered by the bar.
|
16
|
+
:has_gaps, # Whether or not there are gaps in the data.
|
17
|
+
:trades # int: When TRADES data history is returned, represents number
|
18
|
+
# of trades that occurred during the time period the bar covers
|
19
19
|
|
20
20
|
def to_s
|
21
21
|
"<Bar #{time}: wap: #{wap}, OHLC: #{open}, #{high}, #{low}, #{close}, " +
|
@@ -16,35 +16,29 @@ module IB
|
|
16
16
|
CLOSE = 2 # Close. This value is only valid for institutional customers.
|
17
17
|
UNKNOWN = 3
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
@short_sale_slot = 0
|
43
|
-
@designated_location = ''
|
44
|
-
@exempt_code = -1
|
45
|
-
|
46
|
-
super opts
|
47
|
-
end
|
19
|
+
prop :con_id, # int: The unique contract identifier specifying the security.
|
20
|
+
:ratio, # int: Select the relative number of contracts for the leg you
|
21
|
+
# are constructing. To help determine the ratio for a
|
22
|
+
# specific combination order, refer to the Interactive
|
23
|
+
# Analytics section of the User's Guide.
|
24
|
+
|
25
|
+
:action, # String: BUY/SELL/SSHORT/SSHORTX The side (buy or sell) for the leg.
|
26
|
+
:exchange, # String: exchange to which the complete combo order will be routed.
|
27
|
+
:open_close, # int: Specifies whether the order is an open or close order.
|
28
|
+
# Valid values: ComboLeg::SAME/OPEN/CLOSE/UNKNOWN
|
29
|
+
|
30
|
+
# For institutional customers only! For stock legs when doing short sale
|
31
|
+
:short_sale_slot, # int: 0 - retail, 1 = clearing broker, 2 = third party
|
32
|
+
:designated_location, # String: Only for shortSaleSlot == 2.
|
33
|
+
# Otherwise leave blank or orders will be rejected.
|
34
|
+
:exempt_code # int: ?
|
35
|
+
|
36
|
+
DEFAULT_PROPS = {:con_id => 0,
|
37
|
+
:ratio => 0,
|
38
|
+
:open_close => SAME,
|
39
|
+
:short_sale_slot => 0,
|
40
|
+
:designated_location => '',
|
41
|
+
:exempt_code => -1, }
|
48
42
|
|
49
43
|
# Some messages include open_close, some don't. wtf.
|
50
44
|
def serialize *fields
|
@@ -13,19 +13,51 @@ module IB
|
|
13
13
|
# The exception is for a STK legs, which must specify the SMART exchange.
|
14
14
|
# 2. :symbol => "USD" For combo Contract, this is an arbitrary value (like �USD�)
|
15
15
|
|
16
|
+
attr_reader :legs # leg definitions for this contract.
|
17
|
+
|
18
|
+
alias combo_legs legs
|
19
|
+
alias combo_legs_description legs_description
|
20
|
+
alias combo_legs_description= legs_description=
|
21
|
+
|
16
22
|
def initialize opts = {}
|
17
23
|
super opts
|
18
|
-
@
|
24
|
+
@legs = Array.new
|
25
|
+
self[:sec_type] = IB::SECURITY_TYPES[:bag]
|
19
26
|
end
|
20
27
|
|
21
28
|
def description
|
22
|
-
|
29
|
+
self[:description] || to_human
|
23
30
|
end
|
24
31
|
|
25
32
|
def to_human
|
26
33
|
"<Bag: #{[symbol, exchange, currency].join(' ')} legs: #{legs_description} >"
|
27
34
|
end
|
28
35
|
|
36
|
+
### Leg-related methods
|
37
|
+
# TODO: Rewrite with legs and legs_description being strictly in sync...
|
38
|
+
|
39
|
+
# TODO: Find a way to serialize legs without references...
|
40
|
+
# IB-equivalent leg description.
|
41
|
+
def legs_description
|
42
|
+
self[:legs_description] || legs.map { |leg| "#{leg.con_id}|#{leg.weight}" }.join(',')
|
43
|
+
end
|
44
|
+
|
45
|
+
def serialize_legs *fields
|
46
|
+
return [0] if legs.empty?
|
47
|
+
[legs.size, legs.map { |leg| leg.serialize *fields }]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Check if two Contracts have same legs (maybe in different order)
|
51
|
+
def same_legs? other
|
52
|
+
legs == other.legs ||
|
53
|
+
legs_description.split(',').sort == other.legs_description.split(',').sort
|
54
|
+
end
|
55
|
+
|
56
|
+
# Contract comparison
|
57
|
+
def == other
|
58
|
+
super && same_legs?(other)
|
59
|
+
end
|
60
|
+
|
29
61
|
end # class Bag
|
30
62
|
|
31
63
|
TYPES[IB::SECURITY_TYPES[:bag]] = Bag
|
@@ -44,8 +44,8 @@ module IB
|
|
44
44
|
|
45
45
|
def initialize opts = {}
|
46
46
|
super opts
|
47
|
-
|
48
|
-
|
47
|
+
self[:sec_type] = IB::SECURITY_TYPES[:option]
|
48
|
+
self[:description] ||= osi ? osi : "#{symbol} #{strike} #{right} #{expiry}"
|
49
49
|
end
|
50
50
|
|
51
51
|
def to_human
|