ib-ruby 0.7.4 → 0.7.6

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 (93) hide show
  1. data/.gitignore +3 -0
  2. data/HISTORY +8 -0
  3. data/README.md +2 -2
  4. data/Rakefile +15 -0
  5. data/TODO +7 -2
  6. data/VERSION +1 -1
  7. data/bin/account_info +1 -1
  8. data/bin/cancel_orders +1 -1
  9. data/bin/contract_details +1 -1
  10. data/bin/depth_of_market +1 -1
  11. data/bin/fa_accounts +1 -1
  12. data/bin/fundamental_data +42 -0
  13. data/bin/historic_data +1 -1
  14. data/bin/historic_data_cli +1 -1
  15. data/bin/list_orders +1 -2
  16. data/bin/market_data +1 -1
  17. data/bin/option_data +1 -1
  18. data/bin/place_combo_order +1 -1
  19. data/bin/place_order +1 -1
  20. data/bin/template +1 -4
  21. data/bin/tick_data +2 -2
  22. data/bin/time_and_sales +1 -1
  23. data/lib/ib-ruby.rb +4 -0
  24. data/lib/ib-ruby/connection.rb +50 -34
  25. data/lib/ib-ruby/constants.rb +232 -37
  26. data/lib/ib-ruby/db.rb +25 -0
  27. data/lib/ib-ruby/extensions.rb +51 -1
  28. data/lib/ib-ruby/messages/abstract_message.rb +0 -8
  29. data/lib/ib-ruby/messages/incoming.rb +18 -493
  30. data/lib/ib-ruby/messages/incoming/abstract_message.rb +100 -0
  31. data/lib/ib-ruby/messages/incoming/alert.rb +34 -0
  32. data/lib/ib-ruby/messages/incoming/contract_data.rb +82 -0
  33. data/lib/ib-ruby/messages/incoming/delta_neutral_validation.rb +20 -0
  34. data/lib/ib-ruby/messages/incoming/execution_data.rb +59 -0
  35. data/lib/ib-ruby/messages/incoming/historical_data.rb +55 -0
  36. data/lib/ib-ruby/messages/incoming/market_depths.rb +44 -0
  37. data/lib/ib-ruby/messages/incoming/open_order.rb +32 -16
  38. data/lib/ib-ruby/messages/incoming/order_status.rb +67 -0
  39. data/lib/ib-ruby/messages/incoming/portfolio_value.rb +39 -0
  40. data/lib/ib-ruby/messages/incoming/real_time_bar.rb +32 -0
  41. data/lib/ib-ruby/messages/incoming/scanner_data.rb +49 -0
  42. data/lib/ib-ruby/messages/outgoing.rb +25 -223
  43. data/lib/ib-ruby/messages/outgoing/abstract_message.rb +61 -0
  44. data/lib/ib-ruby/messages/outgoing/bar_requests.rb +149 -0
  45. data/lib/ib-ruby/messages/outgoing/place_order.rb +24 -0
  46. data/lib/ib-ruby/models.rb +4 -0
  47. data/lib/ib-ruby/models/bar.rb +31 -14
  48. data/lib/ib-ruby/models/combo_leg.rb +48 -23
  49. data/lib/ib-ruby/models/contracts.rb +2 -2
  50. data/lib/ib-ruby/models/contracts/bag.rb +11 -7
  51. data/lib/ib-ruby/models/contracts/contract.rb +90 -66
  52. data/lib/ib-ruby/models/contracts/option.rb +16 -7
  53. data/lib/ib-ruby/models/execution.rb +34 -18
  54. data/lib/ib-ruby/models/model.rb +15 -7
  55. data/lib/ib-ruby/models/model_properties.rb +101 -44
  56. data/lib/ib-ruby/models/order.rb +176 -187
  57. data/lib/ib-ruby/models/order_state.rb +99 -0
  58. data/lib/ib-ruby/symbols/forex.rb +10 -10
  59. data/lib/ib-ruby/symbols/futures.rb +6 -6
  60. data/lib/ib-ruby/symbols/stocks.rb +3 -3
  61. data/spec/account_helper.rb +4 -5
  62. data/spec/combo_helper.rb +4 -4
  63. data/spec/db.rb +18 -0
  64. data/spec/ib-ruby/messages/{incoming_spec.rb → incoming/alert_spec.rb} +1 -0
  65. data/spec/ib-ruby/messages/incoming/open_order_spec.rb +100 -0
  66. data/spec/ib-ruby/messages/incoming/order_status_spec.rb +74 -0
  67. data/spec/ib-ruby/messages/{outgoing_spec.rb → outgoing/account_data_spec.rb} +0 -0
  68. data/spec/ib-ruby/messages/outgoing/market_data_type_spec.rb +44 -0
  69. data/spec/ib-ruby/models/bag_spec.rb +97 -0
  70. data/spec/ib-ruby/models/bar_spec.rb +45 -0
  71. data/spec/ib-ruby/models/combo_leg_spec.rb +56 -40
  72. data/spec/ib-ruby/models/contract_spec.rb +134 -170
  73. data/spec/ib-ruby/models/execution_spec.rb +35 -50
  74. data/spec/ib-ruby/models/option_spec.rb +127 -0
  75. data/spec/ib-ruby/models/order_spec.rb +89 -68
  76. data/spec/ib-ruby/models/order_state_spec.rb +55 -0
  77. data/spec/integration/contract_info_spec.rb +4 -6
  78. data/spec/integration/fundamental_data_spec.rb +41 -0
  79. data/spec/integration/historic_data_spec.rb +4 -4
  80. data/spec/integration/market_data_spec.rb +1 -3
  81. data/spec/integration/orders/attached_spec.rb +8 -10
  82. data/spec/integration/orders/combo_spec.rb +2 -2
  83. data/spec/integration/orders/execution_spec.rb +0 -1
  84. data/spec/integration/orders/placement_spec.rb +1 -3
  85. data/spec/integration/orders/valid_ids_spec.rb +1 -2
  86. data/spec/message_helper.rb +1 -1
  87. data/spec/model_helper.rb +211 -0
  88. data/spec/order_helper.rb +44 -37
  89. data/spec/spec_helper.rb +36 -23
  90. data/spec/v.rb +7 -0
  91. data/tasks/doc.rake +1 -1
  92. metadata +116 -12
  93. data/spec/integration/orders/open_order +0 -98
@@ -0,0 +1,100 @@
1
+ require 'ib-ruby/messages/abstract_message'
2
+
3
+ module IB
4
+ module Messages
5
+ module Incoming
6
+
7
+ # Container for specific message classes, keyed by their message_ids
8
+ Classes = {}
9
+
10
+ class AbstractMessage < IB::Messages::AbstractMessage
11
+
12
+ def version # Per message, received messages may have the different versions
13
+ @data[:version]
14
+ end
15
+
16
+ def check_version actual, expected
17
+ unless actual == expected || expected.is_a?(Array) && expected.include?(actual)
18
+ error "Unsupported version #{actual} received, expected #{expected}"
19
+ end
20
+ end
21
+
22
+ # Create incoming message from a given source (IB server or data Hash)
23
+ def initialize source
24
+ @created_at = Time.now
25
+ if source[:socket] # Source is a server
26
+ @server = source
27
+ @data = Hash.new
28
+ begin
29
+ self.load
30
+ rescue => e
31
+ error "Reading #{self.class}: #{e.class}: #{e.message}", :load, e.backtrace
32
+ ensure
33
+ @server = nil
34
+ end
35
+ else # Source is a @data Hash
36
+ @data = source
37
+ end
38
+ end
39
+
40
+ def socket
41
+ @server[:socket]
42
+ end
43
+
44
+ # Every message loads received message version first
45
+ # Override the load method in your subclass to do actual reading into @data.
46
+ def load
47
+ @data[:version] = socket.read_int
48
+
49
+ check_version @data[:version], self.class.version
50
+
51
+ load_map *self.class.data_map
52
+ end
53
+
54
+ # Load @data from the socket according to the given data map.
55
+ #
56
+ # map is a series of Arrays in the format of
57
+ # [ :name, :type ], [ :group, :name, :type]
58
+ # type identifiers must have a corresponding read_type method on socket (read_int, etc.).
59
+ # group is used to lump together aggregates, such as Contract or Order fields
60
+ def load_map(*map)
61
+ map.each do |instruction|
62
+ # We determine the function of the first element
63
+ head = instruction.first
64
+ case head
65
+ when Integer # >= Version condition: [ min_version, [map]]
66
+ load_map *instruction.drop(1) if version >= head
67
+
68
+ when Proc # Callable condition: [ condition, [map]]
69
+ load_map *instruction.drop(1) if head.call
70
+
71
+ when true # Pre-condition already succeeded!
72
+ load_map *instruction.drop(1)
73
+
74
+ when nil, false # Pre-condition already failed! Do nothing...
75
+
76
+ when Symbol # Normal map
77
+ group, name, type, block =
78
+ if instruction[2].nil? || instruction[2].is_a?(Proc)
79
+ [nil] + instruction # No group, [ :name, :type, (:block) ]
80
+ else
81
+ instruction # [ :group, :name, :type, (:block)]
82
+ end
83
+
84
+ data = socket.__send__("read_#{type}", &block)
85
+ if group
86
+ @data[group] ||= {}
87
+ @data[group][name] = data
88
+ else
89
+ @data[name] = data
90
+ end
91
+ else
92
+ error "Unrecognized instruction #{instruction}"
93
+ end
94
+ end
95
+ end
96
+
97
+ end # class AbstractMessage
98
+ end # module Incoming
99
+ end # module Messages
100
+ end # module IB
@@ -0,0 +1,34 @@
1
+ module IB
2
+ module Messages
3
+ module Incoming
4
+
5
+ # Called Error in Java code, but in fact this type of messages also
6
+ # deliver system alerts and additional (non-error) info from TWS.
7
+ ErrorMessage = Error = Alert = def_message([4, 2],
8
+ [:error_id, :int],
9
+ [:code, :int],
10
+ [:message, :string])
11
+ class Alert
12
+ # Is it an Error message?
13
+ def error?
14
+ code < 1000
15
+ end
16
+
17
+ # Is it a System message?
18
+ def system?
19
+ code > 1000 && code < 2000
20
+ end
21
+
22
+ # Is it a Warning message?
23
+ def warning?
24
+ code > 2000
25
+ end
26
+
27
+ def to_human
28
+ "TWS #{ error? ? 'Error' : system? ? 'System' : 'Warning'} #{code}: #{message}"
29
+ end
30
+ end # class Alert
31
+
32
+ end # module Incoming
33
+ end # module Messages
34
+ end # module IB
@@ -0,0 +1,82 @@
1
+ module IB
2
+ module Messages
3
+ module Incoming
4
+
5
+ ContractDetails = ContractData =
6
+ def_message([10, 6],
7
+ [:request_id, :int], # request id
8
+ [:contract, :symbol, :string],
9
+ [:contract, :sec_type, :string],
10
+ [:contract, :expiry, :string],
11
+ [:contract, :strike, :decimal],
12
+ [:contract, :right, :string],
13
+ [:contract, :exchange, :string],
14
+ [:contract, :currency, :string],
15
+ [:contract, :local_symbol, :string],
16
+
17
+ [:contract, :market_name, :string], # extended
18
+ [:contract, :trading_class, :string],
19
+ [:contract, :con_id, :int],
20
+ [:contract, :min_tick, :decimal],
21
+ [:contract, :multiplier, :string],
22
+ [:contract, :order_types, :string],
23
+ [:contract, :valid_exchanges, :string],
24
+ [:contract, :price_magnifier, :int],
25
+ [:contract, :under_con_id, :int],
26
+ [:contract, :long_name, :string],
27
+ [:contract, :primary_exchange, :string],
28
+ [:contract, :contract_month, :string],
29
+ [:contract, :industry, :string],
30
+ [:contract, :category, :string],
31
+ [:contract, :subcategory, :string],
32
+ [:contract, :time_zone, :string],
33
+ [:contract, :trading_hours, :string],
34
+ [:contract, :liquid_hours, :string])
35
+
36
+ class ContractData
37
+
38
+ def contract
39
+ @contract = IB::Contract.build @data[:contract]
40
+ end
41
+ end # ContractData
42
+
43
+ BondContractData =
44
+ def_message [18, 4],
45
+ [:request_id, :int],
46
+ [:contract, :symbol, :string],
47
+ [:contract, :sec_type, :string],
48
+ [:contract, :cusip, :string],
49
+ [:contract, :coupon, :decimal],
50
+ [:contract, :maturity, :string],
51
+ [:contract, :issue_date, :string],
52
+ [:contract, :ratings, :string],
53
+ [:contract, :bond_type, :string],
54
+ [:contract, :coupon_type, :string],
55
+ [:contract, :convertible, :boolean],
56
+ [:contract, :callable, :boolean],
57
+ [:contract, :puttable, :boolean],
58
+ [:contract, :desc_append, :string],
59
+ [:contract, :exchange, :string],
60
+ [:contract, :currency, :string],
61
+ [:contract, :market_name, :string], # extended
62
+ [:contract, :trading_class, :string],
63
+ [:contract, :con_id, :int],
64
+ [:contract, :min_tick, :decimal],
65
+ [:contract, :order_types, :string],
66
+ [:contract, :valid_exchanges, :string],
67
+ [:contract, :valid_next_option_date, :string],
68
+ [:contract, :valid_next_option_type, :string],
69
+ [:contract, :valid_next_option_partial, :string],
70
+ [:contract, :notes, :string],
71
+ [:contract, :long_name, :string]
72
+
73
+ class BondContractData
74
+
75
+ def contract
76
+ @contract = IB::Contract.build @data[:contract]
77
+ end
78
+ end # BondContractData
79
+
80
+ end # module Incoming
81
+ end # module Messages
82
+ end # module IB
@@ -0,0 +1,20 @@
1
+ module IB
2
+ module Messages
3
+ module Incoming
4
+
5
+ # The server sends this message upon accepting a Delta-Neutral DN RFQ
6
+ # - see API Reference p. 26
7
+ DeltaNeutralValidation = def_message 56,
8
+ [:request_id, :int],
9
+ [:contract, :under_con_id, :int],
10
+ [:contract, :under_delta, :decimal],
11
+ [:contract, :under_price, :decimal]
12
+ class DeltaNeutralValidation
13
+ def contract
14
+ @contract = IB::Contract.build @data[:contract].merge(:under_comp => true)
15
+ end
16
+ end # DeltaNeutralValidation
17
+
18
+ end # module Incoming
19
+ end # module Messages
20
+ end # module IB
@@ -0,0 +1,59 @@
1
+ module IB
2
+ module Messages
3
+ module Incoming
4
+
5
+ ExecutionData =
6
+ def_message [11, 8],
7
+ # The reqID that was specified previously in the call to reqExecution()
8
+ [:request_id, :int],
9
+ [:execution, :order_id, :int],
10
+ [:contract, :con_id, :int],
11
+ [:contract, :symbol, :string],
12
+ [:contract, :sec_type, :string],
13
+ [:contract, :expiry, :string],
14
+ [:contract, :strike, :decimal],
15
+ [:contract, :right, :string],
16
+ [:contract, :exchange, :string],
17
+ [:contract, :currency, :string],
18
+ [:contract, :local_symbol, :string],
19
+
20
+ [:execution, :exec_id, :string], # Weird format
21
+ [:execution, :time, :string],
22
+ [:execution, :account_name, :string],
23
+ [:execution, :exchange, :string],
24
+ [:execution, :side, :string],
25
+ [:execution, :shares, :int],
26
+ [:execution, :price, :decimal],
27
+ [:execution, :perm_id, :int],
28
+ [:execution, :client_id, :int],
29
+ [:execution, :liquidation, :int],
30
+ [:execution, :cumulative_quantity, :int],
31
+ [:execution, :average_price, :decimal]
32
+
33
+ class ExecutionData
34
+
35
+ def contract
36
+ @contract = IB::Contract.build @data[:contract]
37
+ end
38
+
39
+ def execution
40
+ @execution = IB::Execution.new @data[:execution]
41
+ end
42
+
43
+ def load
44
+ super
45
+
46
+ # As of client v.53, we can receive orderRef in ExecutionData
47
+ load_map [proc { | | @server[:client_version] >= 53 },
48
+ [:execution, :order_ref, :string]
49
+ ]
50
+ end
51
+
52
+ def to_human
53
+ "<ExecutionData #{request_id}: #{contract.to_human}, #{execution}>"
54
+ end
55
+
56
+ end # ExecutionData
57
+ end # module Incoming
58
+ end # module Messages
59
+ end # module IB
@@ -0,0 +1,55 @@
1
+ module IB
2
+ module Messages
3
+ module Incoming
4
+
5
+ # HistoricalData contains following @data:
6
+ # General:
7
+ # :request_id - The ID of the request to which this is responding
8
+ # :count - Number of Historical data points returned (size of :results).
9
+ # :results - an Array of Historical Data Bars
10
+ # :start_date - beginning of returned Historical data period
11
+ # :end_date - end of returned Historical data period
12
+ # Each returned Bar in @data[:results] Array contains this data:
13
+ # :date - The date-time stamp of the start of the bar. The format is
14
+ # determined by the RequestHistoricalData formatDate parameter.
15
+ # :open - The bar opening price.
16
+ # :high - The high price during the time covered by the bar.
17
+ # :low - The low price during the time covered by the bar.
18
+ # :close - The bar closing price.
19
+ # :volume - The volume during the time covered by the bar.
20
+ # :trades - When TRADES historical data is returned, represents number of trades
21
+ # that occurred during the time period the bar covers
22
+ # :wap - The weighted average price during the time covered by the bar.
23
+ # :has_gaps - Whether or not there are gaps in the data.
24
+
25
+ HistoricalData = def_message [17, 3],
26
+ [:request_id, :int],
27
+ [:start_date, :string],
28
+ [:end_date, :string],
29
+ [:count, :int]
30
+ class HistoricalData
31
+ attr_accessor :results
32
+
33
+ def load
34
+ super
35
+
36
+ @results = Array.new(@data[:count]) do |_|
37
+ IB::Bar.new :time => socket.read_string,
38
+ :open => socket.read_decimal,
39
+ :high => socket.read_decimal,
40
+ :low => socket.read_decimal,
41
+ :close => socket.read_decimal,
42
+ :volume => socket.read_int,
43
+ :wap => socket.read_decimal,
44
+ :has_gaps => socket.read_string,
45
+ :trades => socket.read_int
46
+ end
47
+ end
48
+
49
+ def to_human
50
+ "<HistoricalData: #{request_id}, #{count} items, #{start_date} to #{end_date}>"
51
+ end
52
+ end # HistoricalData
53
+ end # module Incoming
54
+ end # module Messages
55
+ end # module IB
@@ -0,0 +1,44 @@
1
+ module IB
2
+ module Messages
3
+ module Incoming
4
+
5
+ MarketDepth =
6
+ def_message 12, [:request_id, :int],
7
+ [:position, :int], # The row Id of this market depth entry.
8
+ [:operation, :int], # How it should be applied to the market depth:
9
+ # 0 = insert this new order into the row identified by :position
10
+ # 1 = update the existing order in the row identified by :position
11
+ # 2 = delete the existing order at the row identified by :position
12
+ [:side, :int], # side of the book: 0 = ask, 1 = bid
13
+ [:price, :decimal],
14
+ [:size, :int]
15
+
16
+ class MarketDepth
17
+ def side
18
+ @data[:side] == 0 ? :ask : :bid
19
+ end
20
+
21
+ def operation
22
+ @data[:operation] == 0 ? :insert : @data[:operation] == 1 ? :update : :delete
23
+ end
24
+
25
+ def to_human
26
+ "<#{self.message_type}: #{operation} #{side} @ "+
27
+ "#{position} = #{price} x #{size}>"
28
+ end
29
+ end
30
+
31
+ MarketDepthL2 =
32
+ def_message 13, MarketDepth, # Fields descriptions - see above
33
+ [:request_id, :int],
34
+ [:position, :int],
35
+ [:market_maker, :string], # The exchange hosting this order.
36
+ [:operation, :int],
37
+ [:side, :int],
38
+ [:price, :decimal],
39
+ [:size, :int]
40
+
41
+
42
+ end # module Incoming
43
+ end # module Messages
44
+ end # module IB
@@ -1,8 +1,8 @@
1
- # OpenOrder is the longest message with complex processing logics, it is isolated here
2
1
  module IB
3
2
  module Messages
4
3
  module Incoming
5
4
 
5
+ # OpenOrder is the longest message with complex processing logics
6
6
  OpenOrder =
7
7
  def_message [5, [23, 28]],
8
8
  [:order, :order_id, :int],
@@ -73,13 +73,32 @@ module IB
73
73
 
74
74
  class OpenOrder
75
75
 
76
+
76
77
  # Accessors to make OpenOrder API-compatible with OrderStatus message
78
+
79
+ def order
80
+ @order ||= IB::Order.new @data[:order].merge(:order_state => order_state)
81
+ end
82
+
83
+ def order_state
84
+ @order_state ||= IB::OrderState.new(
85
+ @data[:order_state].merge(
86
+ :order_id => @data[:order][:order_id],
87
+ :perm_id => @data[:order][:perm_id],
88
+ :parent_id => @data[:order][:parent_id],
89
+ :client_id => @data[:order][:client_id]))
90
+ end
91
+
92
+ def contract
93
+ @contract ||= IB::Contract.build @data[:contract]
94
+ end
95
+
77
96
  def order_id
78
- order && order.order_id
97
+ order.order_id
79
98
  end
80
99
 
81
100
  def status
82
- order && order.status
101
+ order.status
83
102
  end
84
103
 
85
104
  def load
@@ -166,20 +185,17 @@ module IB
166
185
  ],
167
186
 
168
187
  [:order, :what_if, :boolean],
169
- [:order, :status, :string],
170
188
 
189
+ [:order_state, :status, :string],
171
190
  # IB uses weird String with Java Double.MAX_VALUE to indicate no value here
172
- [:order, :init_margin, :decimal_max], # :string],
173
- [:order, :maint_margin, :decimal_max], # :string],
174
- [:order, :equity_with_loan, :decimal_max], # :string],
175
- [:order, :commission, :decimal_max], # May be nil!
176
- [:order, :min_commission, :decimal_max], # May be nil!
177
- [:order, :max_commission, :decimal_max], # May be nil!
178
- [:order, :commission_currency, :string],
179
- [:order, :warning_text, :string]
180
-
181
- @order = IB::Order.new @data[:order]
182
- @contract = IB::Contract.build @data[:contract]
191
+ [:order_state, :init_margin, :decimal_max], # :string],
192
+ [:order_state, :maint_margin, :decimal_max], # :string],
193
+ [:order_state, :equity_with_loan, :decimal_max], # :string],
194
+ [:order_state, :commission, :decimal_max], # May be nil!
195
+ [:order_state, :min_commission, :decimal_max], # May be nil!
196
+ [:order_state, :max_commission, :decimal_max], # May be nil!
197
+ [:order_state, :commission_currency, :string],
198
+ [:order_state, :warning_text, :string]
183
199
  end
184
200
 
185
201
  # Check if given value was set by TWS to something vaguely "positive"
@@ -195,7 +211,7 @@ module IB
195
211
  end
196
212
 
197
213
  def to_human
198
- "<OpenOrder: #{@contract.to_human} #{@order.to_human}>"
214
+ "<OpenOrder: #{contract.to_human} #{order.to_human}>"
199
215
  end
200
216
 
201
217
  end # class OpenOrder