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.
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