ib-ruby 0.5.21 → 0.6.1
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.
- 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
|