ib-ruby 0.5.19 → 0.5.21
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY +4 -0
- data/TODO +0 -3
- data/VERSION +1 -1
- data/bin/contract_details +3 -3
- data/bin/depth_of_market +1 -1
- data/bin/historic_data +5 -8
- data/bin/market_data +2 -2
- data/bin/option_data +2 -2
- data/lib/ib-ruby/connection.rb +1 -9
- data/lib/ib-ruby/extensions.rb +8 -0
- data/lib/ib-ruby/messages/abstract_message.rb +89 -0
- data/lib/ib-ruby/messages/incoming.rb +415 -487
- data/lib/ib-ruby/messages/outgoing.rb +241 -305
- data/lib/ib-ruby/models/bar.rb +3 -3
- data/lib/ib-ruby/models/contract/bag.rb +1 -5
- data/lib/ib-ruby/models/contract.rb +50 -33
- data/lib/ib-ruby/models/execution.rb +6 -3
- data/lib/ib-ruby/models/order.rb +7 -5
- data/lib/ib-ruby/socket.rb +13 -0
- data/lib/ib-ruby/symbols/forex.rb +7 -14
- data/lib/ib-ruby/symbols/futures.rb +16 -20
- data/lib/ib-ruby/symbols/options.rb +6 -4
- data/lib/ib-ruby/symbols/stocks.rb +1 -1
- data/lib/ib-ruby.rb +1 -0
- data/spec/README.md +34 -0
- data/spec/ib-ruby/connection_spec.rb +4 -4
- data/spec/ib-ruby/messages/incoming_spec.rb +50 -0
- data/spec/ib-ruby/messages/outgoing_spec.rb +32 -0
- data/spec/ib-ruby/models/contract_spec.rb +27 -25
- data/spec/ib-ruby/models/order_spec.rb +56 -23
- data/spec/integration/account_info_spec.rb +85 -0
- data/spec/integration/contract_info_spec.rb +209 -0
- data/spec/integration/depth_data_spec.rb +46 -0
- data/spec/integration/historic_data_spec.rb +82 -0
- data/spec/integration/market_data_spec.rb +97 -0
- data/spec/integration/option_data_spec.rb +96 -0
- data/spec/integration/orders/execution_spec.rb +135 -0
- data/spec/{ib-ruby/messages → integration/orders}/open_order +9 -205
- data/spec/integration/orders/placement_spec.rb +150 -0
- data/spec/integration/orders/valid_ids_spec.rb +84 -0
- data/spec/integration_helper.rb +110 -0
- data/spec/message_helper.rb +13 -18
- data/spec/spec_helper.rb +35 -26
- metadata +33 -17
- data/spec/ib-ruby/messages/README.md +0 -16
- data/spec/ib-ruby/messages/account_info_spec.rb +0 -84
- data/spec/ib-ruby/messages/just_connect_spec.rb +0 -33
- data/spec/ib-ruby/messages/market_data_spec.rb +0 -92
- data/spec/ib-ruby/messages/orders_spec.rb +0 -219
- data/spec/ib-ruby_spec.rb +0 -0
data/HISTORY
CHANGED
data/TODO
CHANGED
@@ -2,8 +2,6 @@
|
|
2
2
|
|
3
3
|
Plan:
|
4
4
|
|
5
|
-
1. Create integration tests (Brokerton?)
|
6
|
-
|
7
5
|
2. Make ActiveModel-like attributes Hash for easy attributes updating
|
8
6
|
|
9
7
|
3. IB#send_message method should accept block, thus compressing subscribe/send_message
|
@@ -16,7 +14,6 @@ http://finance.groups.yahoo.com/group/TWSAPI/message/25413
|
|
16
14
|
|
17
15
|
6. Compatibility check for new TWS v.966
|
18
16
|
|
19
|
-
|
20
17
|
Done:
|
21
18
|
|
22
19
|
2. IB#subscribe should accept regexes.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.5.
|
1
|
+
0.5.21
|
data/bin/contract_details
CHANGED
@@ -13,9 +13,9 @@ require 'ib-ruby'
|
|
13
13
|
# Definition of what we want market data for. We have to keep track of what ticker id
|
14
14
|
# corresponds to what symbol ourselves, because the ticks don't include any other
|
15
15
|
# identifying information. The choice of ticker ids is, as far as I can tell, arbitrary.
|
16
|
-
@market = {
|
17
|
-
|
18
|
-
|
16
|
+
@market = {122 => IB::Symbols::Stocks[:wfc],
|
17
|
+
133 => IB::Symbols::Options[:wfc20],
|
18
|
+
144 => IB::Symbols::Stocks[:wrong]}
|
19
19
|
|
20
20
|
# Connect to IB TWS.
|
21
21
|
ib = IB::Connection.new
|
data/bin/depth_of_market
CHANGED
@@ -27,7 +27,7 @@ ib.subscribe(:Alert) { |msg| puts msg.to_human }
|
|
27
27
|
|
28
28
|
# Subscribe to MarketDepth events.
|
29
29
|
ib.subscribe(:MarketDepth) do |msg|
|
30
|
-
puts @market[msg.
|
30
|
+
puts @market[msg.request_id].description + ": " + msg.to_human
|
31
31
|
end
|
32
32
|
|
33
33
|
# Now we actually request L2 market data for the symbols we're interested in.
|
data/bin/historic_data
CHANGED
@@ -31,12 +31,8 @@ ib.subscribe(:Alert) { |msg| puts msg.to_human }
|
|
31
31
|
# Note that we have to look the ticker id of each incoming message
|
32
32
|
# up in local memory to figure out what it's for.
|
33
33
|
ib.subscribe(IB::Messages::Incoming::HistoricalData) do |msg|
|
34
|
-
puts @market[msg.
|
35
|
-
|
36
|
-
|
37
|
-
msg.data[:results].each do |datum|
|
38
|
-
puts " " + datum.to_s
|
39
|
-
end
|
34
|
+
puts @market[msg.request_id].description + ": #{msg.count} items:"
|
35
|
+
msg.results.each { |entry| puts " #{entry}" }
|
40
36
|
@last_msg_time = Time.now.to_i
|
41
37
|
end
|
42
38
|
|
@@ -44,7 +40,7 @@ end
|
|
44
40
|
# respond with a HistoricalData message, which will be processed by the code above.
|
45
41
|
@market.each_pair do |id, contract|
|
46
42
|
ib.send_message IB::Messages::Outgoing::RequestHistoricalData.new(
|
47
|
-
:
|
43
|
+
:request_id => id,
|
48
44
|
:contract => contract,
|
49
45
|
:end_date_time => Time.now.to_ib,
|
50
46
|
:duration => '2 D', # ?
|
@@ -54,4 +50,5 @@ end
|
|
54
50
|
:format_date => 1)
|
55
51
|
end
|
56
52
|
|
57
|
-
|
53
|
+
# Wait for IB to respond to our request
|
54
|
+
sleep 0.1 until @last_msg_time && @last_msg_time < Time.now.to_i + 0.5
|
data/bin/market_data
CHANGED
@@ -28,12 +28,12 @@ ib.subscribe(:Alert) { |msg| puts msg.to_human }
|
|
28
28
|
# argument. In this case, we just print out the tick. NB: The description field is not
|
29
29
|
# from IB TWS. It is defined locally in forex.rb, and is just arbitrary text.
|
30
30
|
ib.subscribe(:TickPrice, :TickSize) do |msg|
|
31
|
-
puts @market[msg.
|
31
|
+
puts @market[msg.ticker_id].description + ": " + msg.to_human
|
32
32
|
end
|
33
33
|
|
34
34
|
# Now we actually request market data for the symbols we're interested in.
|
35
35
|
@market.each_pair do |id, contract|
|
36
|
-
ib.send_message :RequestMarketData, :
|
36
|
+
ib.send_message :RequestMarketData, :ticker_id => id, :contract => contract
|
37
37
|
end
|
38
38
|
|
39
39
|
puts "\nSubscribed to market data"
|
data/bin/option_data
CHANGED
@@ -31,12 +31,12 @@ ib.subscribe(:Alert) { |msg| puts msg.to_human }
|
|
31
31
|
# (N.B. The description field is not from IB TWS. It is defined
|
32
32
|
# locally in forex.rb, and is just arbitrary text.)
|
33
33
|
ib.subscribe(:TickPrice, :TickSize, :TickOption, :TickString) do |msg|
|
34
|
-
puts @market[msg.
|
34
|
+
puts @market[msg.ticker_id].description + ": " + msg.to_human
|
35
35
|
end
|
36
36
|
|
37
37
|
# Now we actually request market data for the symbols we're interested in.
|
38
38
|
@market.each_pair do |id, contract|
|
39
|
-
ib.send_message :RequestMarketData, :
|
39
|
+
ib.send_message :RequestMarketData, :ticker_id => id, :contract => contract
|
40
40
|
end
|
41
41
|
|
42
42
|
puts "\nSubscribed to market data"
|
data/lib/ib-ruby/connection.rb
CHANGED
@@ -1,14 +1,6 @@
|
|
1
1
|
require 'ib-ruby/socket'
|
2
2
|
require 'ib-ruby/logger'
|
3
3
|
|
4
|
-
# Add method to_ib to render datetime in IB format (zero padded "yyyymmdd HH:mm:ss")
|
5
|
-
class Time
|
6
|
-
def to_ib
|
7
|
-
"#{self.year}#{sprintf("%02d", self.month)}#{sprintf("%02d", self.day)} " +
|
8
|
-
"#{sprintf("%02d", self.hour)}:#{sprintf("%02d", self.min)}:#{sprintf("%02d", self.sec)}"
|
9
|
-
end
|
10
|
-
end # Time
|
11
|
-
|
12
4
|
module IB
|
13
5
|
# Encapsulates API connection to TWS or Gateway
|
14
6
|
class Connection
|
@@ -61,7 +53,7 @@ module IB
|
|
61
53
|
|
62
54
|
# TWS always sends NextValidID message at connect - save this id
|
63
55
|
self.subscribe(:NextValidID) do |msg|
|
64
|
-
@next_order_id = msg.
|
56
|
+
@next_order_id = msg.order_id
|
65
57
|
log.info "Got next valid order id: #{@next_order_id}."
|
66
58
|
end
|
67
59
|
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# Add method to_ib to render datetime in IB format (zero padded "yyyymmdd HH:mm:ss")
|
2
|
+
class Time
|
3
|
+
def to_ib
|
4
|
+
"#{year}#{sprintf("%02d", month)}#{sprintf("%02d", day)} " +
|
5
|
+
"#{sprintf("%02d", hour)}:#{sprintf("%02d", min)}:#{sprintf("%02d", sec)}"
|
6
|
+
end
|
7
|
+
end # Time
|
8
|
+
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# EClientSocket.java uses sendMax() rather than send() for a number of these.
|
2
|
+
# It sends an EOL rather than a number if the value == Integer.MAX_VALUE (or Double.MAX_VALUE).
|
3
|
+
# These fields are initialized to this MAX_VALUE.
|
4
|
+
# This has been implemented with nils in Ruby to represent the case where an EOL should be sent.
|
5
|
+
|
6
|
+
# TODO: Don't instantiate messages, use their classes as just namespace for .encode/decode
|
7
|
+
# TODO: realize Message#fire method that raises EWrapper events
|
8
|
+
|
9
|
+
module IB
|
10
|
+
module Messages
|
11
|
+
|
12
|
+
# This is just a basic generic message from the server.
|
13
|
+
#
|
14
|
+
# Class variables:
|
15
|
+
# @message_id - int: message id.
|
16
|
+
# @message_type - Symbol: message type (e.g. :OpenOrderEnd)
|
17
|
+
#
|
18
|
+
# Instance attributes (at least):
|
19
|
+
# version - int: current version of message format.
|
20
|
+
# @data - Hash of actual data read from a stream.
|
21
|
+
#
|
22
|
+
# Override the load(socket) method in your subclass to do actual reading into @data.
|
23
|
+
class AbstractMessage
|
24
|
+
|
25
|
+
# Class methods
|
26
|
+
def self.data_map # Data keys (with types?)
|
27
|
+
@data_map ||= []
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.version # Per class, minimum message version supported
|
31
|
+
@version || 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.message_id
|
35
|
+
@message_id
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns message type Symbol (e.g. :OpenOrderEnd)
|
39
|
+
def self.message_type
|
40
|
+
to_s.split(/::/).last.to_sym
|
41
|
+
end
|
42
|
+
|
43
|
+
def message_id
|
44
|
+
self.class.message_id
|
45
|
+
end
|
46
|
+
|
47
|
+
def message_type
|
48
|
+
self.class.message_type
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_accessor :created_at, :data
|
52
|
+
|
53
|
+
def to_human
|
54
|
+
"<#{self.message_type}:" +
|
55
|
+
@data.map do |key, value|
|
56
|
+
unless [:version].include?(key)
|
57
|
+
" #{key} #{ value.is_a?(Hash) ? value.inspect : value}"
|
58
|
+
end
|
59
|
+
end.compact.join(',') + " >"
|
60
|
+
end
|
61
|
+
|
62
|
+
end # class AbstractMessage
|
63
|
+
|
64
|
+
# Macro that defines short message classes using a one-liner
|
65
|
+
# id_version is either a [message_id, version] pair or just message_id (version 1)
|
66
|
+
# data_map contains instructions for processing @data Hash. Format:
|
67
|
+
# Incoming messages: [field, type] or [group, field, type]
|
68
|
+
# Outgoing messages: [field, default] or [field, method, [args]]
|
69
|
+
def def_message id_version, *data_map, &to_human
|
70
|
+
base = data_map.first.is_a?(Class) ? data_map.shift : self::AbstractMessage
|
71
|
+
Class.new(base) do
|
72
|
+
@message_id, @version = id_version
|
73
|
+
@version ||= 1
|
74
|
+
@data_map = data_map
|
75
|
+
|
76
|
+
@data_map.each do |(name, _, type_args)|
|
77
|
+
if type_args.is_a?(Symbol) # This is Incoming with [group, field, type]
|
78
|
+
attr_reader name
|
79
|
+
else
|
80
|
+
define_method(name) { @data[name] }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
define_method(:to_human, &to_human) if to_human
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end # module Messages
|
89
|
+
end # module IB
|