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.
Files changed (50) hide show
  1. data/HISTORY +4 -0
  2. data/TODO +0 -3
  3. data/VERSION +1 -1
  4. data/bin/contract_details +3 -3
  5. data/bin/depth_of_market +1 -1
  6. data/bin/historic_data +5 -8
  7. data/bin/market_data +2 -2
  8. data/bin/option_data +2 -2
  9. data/lib/ib-ruby/connection.rb +1 -9
  10. data/lib/ib-ruby/extensions.rb +8 -0
  11. data/lib/ib-ruby/messages/abstract_message.rb +89 -0
  12. data/lib/ib-ruby/messages/incoming.rb +415 -487
  13. data/lib/ib-ruby/messages/outgoing.rb +241 -305
  14. data/lib/ib-ruby/models/bar.rb +3 -3
  15. data/lib/ib-ruby/models/contract/bag.rb +1 -5
  16. data/lib/ib-ruby/models/contract.rb +50 -33
  17. data/lib/ib-ruby/models/execution.rb +6 -3
  18. data/lib/ib-ruby/models/order.rb +7 -5
  19. data/lib/ib-ruby/socket.rb +13 -0
  20. data/lib/ib-ruby/symbols/forex.rb +7 -14
  21. data/lib/ib-ruby/symbols/futures.rb +16 -20
  22. data/lib/ib-ruby/symbols/options.rb +6 -4
  23. data/lib/ib-ruby/symbols/stocks.rb +1 -1
  24. data/lib/ib-ruby.rb +1 -0
  25. data/spec/README.md +34 -0
  26. data/spec/ib-ruby/connection_spec.rb +4 -4
  27. data/spec/ib-ruby/messages/incoming_spec.rb +50 -0
  28. data/spec/ib-ruby/messages/outgoing_spec.rb +32 -0
  29. data/spec/ib-ruby/models/contract_spec.rb +27 -25
  30. data/spec/ib-ruby/models/order_spec.rb +56 -23
  31. data/spec/integration/account_info_spec.rb +85 -0
  32. data/spec/integration/contract_info_spec.rb +209 -0
  33. data/spec/integration/depth_data_spec.rb +46 -0
  34. data/spec/integration/historic_data_spec.rb +82 -0
  35. data/spec/integration/market_data_spec.rb +97 -0
  36. data/spec/integration/option_data_spec.rb +96 -0
  37. data/spec/integration/orders/execution_spec.rb +135 -0
  38. data/spec/{ib-ruby/messages → integration/orders}/open_order +9 -205
  39. data/spec/integration/orders/placement_spec.rb +150 -0
  40. data/spec/integration/orders/valid_ids_spec.rb +84 -0
  41. data/spec/integration_helper.rb +110 -0
  42. data/spec/message_helper.rb +13 -18
  43. data/spec/spec_helper.rb +35 -26
  44. metadata +33 -17
  45. data/spec/ib-ruby/messages/README.md +0 -16
  46. data/spec/ib-ruby/messages/account_info_spec.rb +0 -84
  47. data/spec/ib-ruby/messages/just_connect_spec.rb +0 -33
  48. data/spec/ib-ruby/messages/market_data_spec.rb +0 -92
  49. data/spec/ib-ruby/messages/orders_spec.rb +0 -219
  50. data/spec/ib-ruby_spec.rb +0 -0
data/HISTORY CHANGED
@@ -121,3 +121,7 @@
121
121
  == 0.5.19 / 2012-02-23
122
122
 
123
123
  * Contract
124
+
125
+ == 0.5.21 / 2012-03-06
126
+
127
+ * Fully tested with integration spec suite
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.19
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 = {123 => IB::Symbols::Stocks[:wfc],
17
- 125 => IB::Symbols::Options[:wfc20],
18
- 129 => IB::Symbols::Stocks[:wrong]}
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.data[:id]].description + ": " + msg.to_human
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.data[:id]].description + ": " +
35
- msg.data[:count].to_s + " items:"
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
- :id => id,
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
- sleep 5 # Wait for IB to respond to our request
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.data[:id]].description + ": " + msg.to_human
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, :id => id, :contract => contract
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.data[:id]].description + ": " + msg.to_human
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, :id => id, :contract => contract
39
+ ib.send_message :RequestMarketData, :ticker_id => id, :contract => contract
40
40
  end
41
41
 
42
42
  puts "\nSubscribed to market data"
@@ -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.data[:id]
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