ib-ruby 0.5.19 → 0.5.21
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 +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
|