ib-ruby 0.7.4 → 0.7.6

Sign up to get free protection for your applications and to get access to all the features.
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