ib-ruby 0.7.6 → 0.7.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/HISTORY +8 -0
  2. data/Rakefile +8 -0
  3. data/VERSION +1 -1
  4. data/bin/fundamental_data +6 -9
  5. data/lib/ib-ruby/connection.rb +16 -19
  6. data/lib/ib-ruby/constants.rb +3 -1
  7. data/lib/ib-ruby/extensions.rb +5 -0
  8. data/lib/ib-ruby/messages/incoming/contract_data.rb +46 -45
  9. data/lib/ib-ruby/messages/incoming/delta_neutral_validation.rb +8 -5
  10. data/lib/ib-ruby/messages/incoming/execution_data.rb +2 -2
  11. data/lib/ib-ruby/messages/incoming/next_valid_id.rb +18 -0
  12. data/lib/ib-ruby/messages/incoming/open_order.rb +23 -16
  13. data/lib/ib-ruby/messages/incoming/order_status.rb +5 -3
  14. data/lib/ib-ruby/messages/incoming/scanner_data.rb +15 -11
  15. data/lib/ib-ruby/messages/incoming.rb +1 -5
  16. data/lib/ib-ruby/messages/outgoing/abstract_message.rb +2 -1
  17. data/lib/ib-ruby/messages/outgoing/place_order.rb +1 -1
  18. data/lib/ib-ruby/messages/outgoing.rb +1 -1
  19. data/lib/ib-ruby/models/bag.rb +59 -0
  20. data/lib/ib-ruby/models/combo_leg.rb +10 -6
  21. data/lib/ib-ruby/models/contract.rb +278 -0
  22. data/lib/ib-ruby/models/contract_detail.rb +70 -0
  23. data/lib/ib-ruby/models/execution.rb +22 -16
  24. data/lib/ib-ruby/models/model.rb +75 -17
  25. data/lib/ib-ruby/models/model_properties.rb +40 -26
  26. data/lib/ib-ruby/models/option.rb +62 -0
  27. data/lib/ib-ruby/models/order.rb +122 -86
  28. data/lib/ib-ruby/models/order_state.rb +11 -12
  29. data/lib/ib-ruby/models/underlying.rb +36 -0
  30. data/lib/ib-ruby/models.rb +1 -4
  31. data/spec/account_helper.rb +2 -1
  32. data/spec/db.rb +1 -1
  33. data/spec/db_helper.rb +105 -0
  34. data/spec/ib-ruby/connection_spec.rb +3 -3
  35. data/spec/ib-ruby/messages/incoming/open_order_spec.rb +5 -5
  36. data/spec/ib-ruby/messages/incoming/order_status_spec.rb +3 -3
  37. data/spec/ib-ruby/models/bag_spec.rb +15 -23
  38. data/spec/ib-ruby/models/bar_spec.rb +0 -5
  39. data/spec/ib-ruby/models/combo_leg_spec.rb +18 -25
  40. data/spec/ib-ruby/models/contract_detail_spec.rb +54 -0
  41. data/spec/ib-ruby/models/contract_spec.rb +25 -37
  42. data/spec/ib-ruby/models/execution_spec.rb +64 -19
  43. data/spec/ib-ruby/models/option_spec.rb +12 -34
  44. data/spec/ib-ruby/models/order_spec.rb +107 -45
  45. data/spec/ib-ruby/models/order_state_spec.rb +12 -12
  46. data/spec/ib-ruby/models/underlying_spec.rb +36 -0
  47. data/spec/integration/contract_info_spec.rb +65 -55
  48. data/spec/integration/fundamental_data_spec.rb +2 -2
  49. data/spec/integration/orders/attached_spec.rb +3 -3
  50. data/spec/integration/orders/combo_spec.rb +3 -3
  51. data/spec/integration/orders/placement_spec.rb +8 -8
  52. data/spec/integration/orders/{execution_spec.rb → trades_spec.rb} +8 -12
  53. data/spec/integration/orders/valid_ids_spec.rb +3 -3
  54. data/spec/message_helper.rb +1 -1
  55. data/spec/model_helper.rb +150 -85
  56. data/spec/order_helper.rb +35 -18
  57. metadata +18 -10
  58. data/lib/ib-ruby/models/contracts/bag.rb +0 -62
  59. data/lib/ib-ruby/models/contracts/contract.rb +0 -320
  60. data/lib/ib-ruby/models/contracts/option.rb +0 -66
  61. data/lib/ib-ruby/models/contracts.rb +0 -27
data/HISTORY CHANGED
@@ -161,3 +161,11 @@
161
161
  == 0.7.6 / 2012-04-18
162
162
 
163
163
  * Bugfix for FundamentalData
164
+
165
+ == 0.7.7 / 2012-04-20
166
+
167
+ * Model validations and DB backend
168
+
169
+ == 0.7.8 / 2012-04-23
170
+
171
+ * Models are now fully DB-backed
data/Rakefile CHANGED
@@ -38,3 +38,11 @@ begin
38
38
  rescue LoadError => e
39
39
  puts "gem install standalone_migrations to get db:migrate:* tasks! (Error: #{e})"
40
40
  end
41
+
42
+ # rake db:redo DB=test"
43
+ namespace :db do
44
+ desc "Remake db from scratch: $ rake db:redo DB=test"
45
+ task :redo => [:drop, :create, :migrate] do
46
+ puts "Redo Finished!"
47
+ end
48
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.6
1
+ 0.7.8
data/bin/fundamental_data CHANGED
@@ -16,13 +16,8 @@ ib = IB::Connection.new :client_id => 1112 #, :port => 7496 # TWS
16
16
 
17
17
  ib.subscribe(:Alert) { |msg| puts msg.to_human }
18
18
 
19
- # Fundamental Data will arrive as XML. Need to parse it
20
- ib.subscribe(:FundamentalData) do |msg|
21
- puts 'Got fundamental data.'
22
- @xml = XmlSimple.xml_in(msg.data)
23
- pp @xml
24
- @parsing_finished = true
25
- end
19
+ # Fundamental Data will arrive as XML, parse it
20
+ ib.subscribe(:FundamentalData) { |msg| @xml = XmlSimple.xml_in(msg.data) }
26
21
 
27
22
  ibm = IB::Contract.new :symbol => 'IBM',
28
23
  :exchange => 'NYSE',
@@ -38,5 +33,7 @@ ib.send_message :RequestFundamentalData,
38
33
  :contract => ibm,
39
34
  :report_type => 'snapshot'
40
35
 
41
- # Needs some time to receive, parse and print XML. Standard timeout of 1 sec is too low.
42
- ib.wait_for(40) { @parsing_finished }
36
+ # Needs some time to receive and parse XML. Standard timeout of 1 sec is just too low.
37
+ ib.wait_for(30) { @xml}
38
+
39
+ pp @xml
@@ -28,7 +28,10 @@ module IB
28
28
 
29
29
  attr_accessor :server, # Info about IB server and server connection state
30
30
  :options, # Connection options
31
- :next_order_id # Next valid order id
31
+ :next_local_id # Next valid order id
32
+
33
+ alias next_order_id next_local_id
34
+ alias next_order_id= next_local_id=
32
35
 
33
36
  def initialize opts = {}
34
37
  @options = DEFAULT_OPTIONS.merge(opts)
@@ -39,7 +42,7 @@ module IB
39
42
 
40
43
  self.default_logger = options[:logger] if options[:logger]
41
44
  @connected = false
42
- @next_order_id = nil
45
+ self.next_local_id = nil
43
46
  @server = Hash.new
44
47
 
45
48
  connect if options[:connect]
@@ -53,8 +56,8 @@ module IB
53
56
 
54
57
  # TWS always sends NextValidId message at connect - save this id
55
58
  self.subscribe(:NextValidId) do |msg|
56
- @next_order_id = msg.order_id
57
- log.info "Got next valid order id: #{next_order_id}."
59
+ self.next_local_id = msg.local_id
60
+ log.info "Got next valid order id: #{next_local_id}."
58
61
  end
59
62
 
60
63
  server[:socket] = IBSocket.open(options[:host], options[:port])
@@ -181,7 +184,9 @@ module IB
181
184
 
182
185
  # Check if messages of given type were received at_least n times
183
186
  def received? message_type, times=1
184
- received[message_type].size >= times
187
+ @receive_lock.synchronize do
188
+ received[message_type].size >= times
189
+ end
185
190
  end
186
191
 
187
192
  # Check if all given conditions are satisfied
@@ -292,26 +297,18 @@ module IB
292
297
  # Place Order (convenience wrapper for send_message :PlaceOrder).
293
298
  # Assigns client_id and order_id fields to placed order. Returns assigned order_id.
294
299
  def place_order order, contract
295
- error "Unable to place order, next_order_id not known" unless @next_order_id
296
- order.client_id = server[:client_id]
297
- order.order_id = @next_order_id
298
- @next_order_id += 1
299
- modify_order order, contract
300
+ order.place contract, self
300
301
  end
301
302
 
302
303
  # Modify Order (convenience wrapper for send_message :PlaceOrder). Returns order_id.
303
304
  def modify_order order, contract
304
- send_message :PlaceOrder,
305
- :order => order,
306
- :contract => contract,
307
- :order_id => order.order_id
308
- order.order_id
305
+ order.modify contract, self
309
306
  end
310
307
 
311
- # Cancel Orders by their ids (convenience wrapper for send_message :CancelOrder).
312
- def cancel_order *order_ids
313
- order_ids.each do |order_id|
314
- send_message :CancelOrder, :order_id => order_id.to_i
308
+ # Cancel Orders by their local ids (convenience wrapper for send_message :CancelOrder).
309
+ def cancel_order *local_ids
310
+ local_ids.each do |local_id|
311
+ send_message :CancelOrder, :local_id => local_id.to_i
315
312
  end
316
313
  end
317
314
 
@@ -171,6 +171,8 @@ module IB
171
171
  'FOP' => :futures_option,
172
172
  'CASH' => :forex,
173
173
  'BOND' => :bond,
174
+ 'WAR' => :warrant,
175
+ 'FUND' => :fund, # ETF?
174
176
  'BAG' => :bag}.freeze
175
177
 
176
178
  # Obtain symbolic value from given property code:
@@ -285,7 +287,7 @@ module IB
285
287
 
286
288
  # Obtain property code from given symbolic value:
287
289
  # CODES[:side][:buy] -> 'B'
288
- CODES = Hash[VALUES.map { |property, hash| [property, hash.invert] }]
290
+ CODES = Hash[VALUES.map { |property, hash| [property, hash.invert] }].freeze
289
291
 
290
292
  # Most common property processors
291
293
  PROPS = {:side =>
@@ -48,6 +48,11 @@ class Symbol
48
48
  def to_f
49
49
  0
50
50
  end
51
+
52
+ # ActiveModel serialization depends on this method
53
+ def <=> other
54
+ to_s <=> other.to_s
55
+ end
51
56
  end
52
57
 
53
58
  class Object
@@ -14,68 +14,69 @@ module IB
14
14
  [:contract, :currency, :string],
15
15
  [:contract, :local_symbol, :string],
16
16
 
17
- [:contract, :market_name, :string], # extended
18
- [:contract, :trading_class, :string],
17
+ [:contract_detail, :market_name, :string], # extended
18
+ [:contract_detail, :trading_class, :string],
19
19
  [:contract, :con_id, :int],
20
- [:contract, :min_tick, :decimal],
20
+ [:contract_detail, :min_tick, :decimal],
21
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],
22
+ [:contract_detail, :order_types, :string],
23
+ [:contract_detail, :valid_exchanges, :string],
24
+ [:contract_detail, :price_magnifier, :int],
25
+ [:contract_detail, :under_con_id, :int],
26
+ [:contract_detail, :long_name, :string],
27
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])
28
+ [:contract_detail, :contract_month, :string],
29
+ [:contract_detail, :industry, :string],
30
+ [:contract_detail, :category, :string],
31
+ [:contract_detail, :subcategory, :string],
32
+ [:contract_detail, :time_zone, :string],
33
+ [:contract_detail, :trading_hours, :string],
34
+ [:contract_detail, :liquid_hours, :string])
35
35
 
36
36
  class ContractData
37
37
 
38
38
  def contract
39
- @contract = IB::Contract.build @data[:contract]
39
+ @contract = IB::Contract.build @data[:contract].
40
+ merge(:contract_detail => contract_detail)
40
41
  end
42
+
43
+ def contract_detail
44
+ @contract_detail = IB::ContractDetail.new @data[:contract_detail]
45
+ end
46
+
47
+ alias contract_details contract_detail
48
+
41
49
  end # ContractData
42
50
 
43
51
  BondContractData =
44
- def_message [18, 4],
52
+ def_message [18, 4], ContractData,
45
53
  [:request_id, :int],
46
54
  [:contract, :symbol, :string],
47
55
  [: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],
56
+ [:contract_detail, :cusip, :string],
57
+ [:contract_detail, :coupon, :decimal],
58
+ [:contract_detail, :maturity, :string],
59
+ [:contract_detail, :issue_date, :string],
60
+ [:contract_detail, :ratings, :string],
61
+ [:contract_detail, :bond_type, :string],
62
+ [:contract_detail, :coupon_type, :string],
63
+ [:contract_detail, :convertible, :boolean],
64
+ [:contract_detail, :callable, :boolean],
65
+ [:contract_detail, :puttable, :boolean],
66
+ [:contract_detail, :desc_append, :string],
59
67
  [:contract, :exchange, :string],
60
68
  [:contract, :currency, :string],
61
- [:contract, :market_name, :string], # extended
62
- [:contract, :trading_class, :string],
69
+ [:contract_detail, :market_name, :string], # extended
70
+ [:contract_detail, :trading_class, :string],
63
71
  [: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
72
+ [:contract_detail, :min_tick, :decimal],
73
+ [:contract_detail, :order_types, :string],
74
+ [:contract_detail, :valid_exchanges, :string],
75
+ [:contract_detail, :valid_next_option_date, :string],
76
+ [:contract_detail, :valid_next_option_type, :string],
77
+ [:contract_detail, :valid_next_option_partial, :string],
78
+ [:contract_detail, :notes, :string],
79
+ [:contract_detail, :long_name, :string]
79
80
 
80
81
  end # module Incoming
81
82
  end # module Messages
@@ -6,13 +6,16 @@ module IB
6
6
  # - see API Reference p. 26
7
7
  DeltaNeutralValidation = def_message 56,
8
8
  [:request_id, :int],
9
- [:contract, :under_con_id, :int],
10
- [:contract, :under_delta, :decimal],
11
- [:contract, :under_price, :decimal]
9
+ [:underlying, :con_id, :int],
10
+ [:underlying, :delta, :decimal],
11
+ [:underlying, :price, :decimal]
12
12
  class DeltaNeutralValidation
13
- def contract
14
- @contract = IB::Contract.build @data[:contract].merge(:under_comp => true)
13
+ def underlying
14
+ @underlying = IB::Underlying.new @data[:underlying]
15
15
  end
16
+
17
+ alias under_comp underlying
18
+
16
19
  end # DeltaNeutralValidation
17
20
 
18
21
  end # module Incoming
@@ -6,7 +6,7 @@ module IB
6
6
  def_message [11, 8],
7
7
  # The reqID that was specified previously in the call to reqExecution()
8
8
  [:request_id, :int],
9
- [:execution, :order_id, :int],
9
+ [:execution, :local_id, :int],
10
10
  [:contract, :con_id, :int],
11
11
  [:contract, :symbol, :string],
12
12
  [:contract, :sec_type, :string],
@@ -22,7 +22,7 @@ module IB
22
22
  [:execution, :account_name, :string],
23
23
  [:execution, :exchange, :string],
24
24
  [:execution, :side, :string],
25
- [:execution, :shares, :int],
25
+ [:execution, :quantity, :int],
26
26
  [:execution, :price, :decimal],
27
27
  [:execution, :perm_id, :int],
28
28
  [:execution, :client_id, :int],
@@ -0,0 +1,18 @@
1
+ module IB
2
+ module Messages
3
+ module Incoming
4
+
5
+ # This message is always sent by TWS automatically at connect.
6
+ # The IB::Connection class subscribes to it automatically and stores
7
+ # the order id in its @next_local_id attribute.
8
+ NextValidID = NextValidId = def_message(9, [:local_id, :int])
9
+
10
+ class NextValidId
11
+
12
+ # Legacy accessor
13
+ alias order_id local_id
14
+
15
+ end # class NextValidId
16
+ end # module Incoming
17
+ end # module Messages
18
+ end # module IB
@@ -5,7 +5,7 @@ module IB
5
5
  # OpenOrder is the longest message with complex processing logics
6
6
  OpenOrder =
7
7
  def_message [5, [23, 28]],
8
- [:order, :order_id, :int],
8
+ [:order, :local_id, :int],
9
9
 
10
10
  [:contract, :con_id, :int],
11
11
  [:contract, :symbol, :string],
@@ -70,12 +70,20 @@ module IB
70
70
  [:order, :delta_neutral_order_type, :string],
71
71
  [:order, :delta_neutral_aux_price, :decimal_max]
72
72
 
73
-
74
73
  class OpenOrder
75
74
 
76
-
77
75
  # Accessors to make OpenOrder API-compatible with OrderStatus message
78
76
 
77
+ def local_id
78
+ order.local_id
79
+ end
80
+
81
+ alias order_id local_id
82
+
83
+ def status
84
+ order.status
85
+ end
86
+
79
87
  def order
80
88
  @order ||= IB::Order.new @data[:order].merge(:order_state => order_state)
81
89
  end
@@ -83,23 +91,23 @@ module IB
83
91
  def order_state
84
92
  @order_state ||= IB::OrderState.new(
85
93
  @data[:order_state].merge(
86
- :order_id => @data[:order][:order_id],
94
+ :local_id => @data[:order][:local_id],
87
95
  :perm_id => @data[:order][:perm_id],
88
96
  :parent_id => @data[:order][:parent_id],
89
97
  :client_id => @data[:order][:client_id]))
90
98
  end
91
99
 
92
100
  def contract
93
- @contract ||= IB::Contract.build @data[:contract]
101
+ @contract ||= IB::Contract.build(
102
+ @data[:contract].merge(:underlying => underlying)
103
+ )
94
104
  end
95
105
 
96
- def order_id
97
- order.order_id
106
+ def underlying
107
+ @underlying = @data[:underlying_present] ? IB::Underlying.new(@data[:underlying]) : nil
98
108
  end
99
109
 
100
- def status
101
- order.status
102
- end
110
+ alias under_comp underlying
103
111
 
104
112
  def load
105
113
  super
@@ -152,7 +160,6 @@ module IB
152
160
  [:order, :scale_profit_offset, :decimal_max],
153
161
  [:order, :scale_auto_reset, :boolean],
154
162
  [:order, :scale_init_position, :int_max],
155
- [:order, :scale_init_position, :int_max],
156
163
  [:order, :scale_init_fill_qty, :decimal_max],
157
164
  [:order, :scale_random_percent, :boolean]]
158
165
  ],
@@ -169,12 +176,12 @@ module IB
169
176
  [:order, :clearing_account, :string],
170
177
  [:order, :clearing_intent, :string],
171
178
  [:order, :not_held, :boolean],
172
- [:contract, :under_comp, :boolean],
179
+ [:underlying_present, :boolean],
173
180
 
174
- [proc { | | filled?(@data[:contract][:under_comp]) },
175
- [:contract, :under_con_id, :int],
176
- [:contract, :under_delta, :decimal],
177
- [:contract, :under_price, :decimal]
181
+ [proc { | | filled?(@data[:underlying_present]) },
182
+ [:underlying, :con_id, :int],
183
+ [:underlying, :delta, :decimal],
184
+ [:underlying, :price, :decimal]
178
185
  ],
179
186
 
180
187
  [:order, :algo_strategy, :string],
@@ -32,7 +32,7 @@ module IB
32
32
  # order to be held. For example, when TWS is trying to locate shares for
33
33
  # a short sell, the value used to indicate this is 'locate'.
34
34
  OrderStatus = def_message [3, 6],
35
- [:order_state, :order_id, :int],
35
+ [:order_state, :local_id, :int],
36
36
  [:order_state, :status, :string],
37
37
  [:order_state, :filled, :int],
38
38
  [:order_state, :remaining, :int],
@@ -49,10 +49,12 @@ module IB
49
49
  end
50
50
 
51
51
  # Accessors to make OpenOrder and OrderStatus messages API-compatible
52
- def order_id
53
- order_state.order_id
52
+ def local_id
53
+ order_state.local_id
54
54
  end
55
55
 
56
+ alias order_id local_id
57
+
56
58
  def status
57
59
  order_state.status
58
60
  end
@@ -24,17 +24,21 @@ module IB
24
24
 
25
25
  @results = Array.new(@data[:count]) do |_|
26
26
  {:rank => socket.read_int,
27
- :contract => Contract.build(:con_id => socket.read_int,
28
- :symbol => socket.read_str,
29
- :sec_type => socket.read_str,
30
- :expiry => socket.read_str,
31
- :strike => socket.read_decimal,
32
- :right => socket.read_str,
33
- :exchange => socket.read_str,
34
- :currency => socket.read_str,
35
- :local_symbol => socket.read_str,
36
- :market_name => socket.read_str,
37
- :trading_class => socket.read_str),
27
+ :contract =>
28
+ Contract.build(
29
+ :con_id => socket.read_int,
30
+ :symbol => socket.read_str,
31
+ :sec_type => socket.read_str,
32
+ :expiry => socket.read_str,
33
+ :strike => socket.read_decimal,
34
+ :right => socket.read_str,
35
+ :exchange => socket.read_str,
36
+ :currency => socket.read_str,
37
+ :local_symbol => socket.read_str,
38
+ :contract_detail =>
39
+ IB::ContractDetail.new(
40
+ :market_name => socket.read_str,
41
+ :trading_class => socket.read_str)),
38
42
  :distance => socket.read_str,
39
43
  :benchmark => socket.read_str,
40
44
  :projection => socket.read_str,
@@ -26,11 +26,6 @@ module IB
26
26
 
27
27
  AccountUpdateTime = def_message 8, [:time_stamp, :string]
28
28
 
29
- # This message is always sent by TWS automatically at connect.
30
- # The IB::Connection class subscribes to it automatically and stores
31
- # the order id in its @next_order_id attribute.
32
- NextValidID = NextValidId = def_message(9, [:order_id, :int])
33
-
34
29
  NewsBulletins =
35
30
  def_message 14, [:request_id, :int], # unique incrementing bulletin ID.
36
31
  [:type, :int], # Type of bulletin. Valid values include:
@@ -87,6 +82,7 @@ module IB
87
82
  require 'ib-ruby/messages/incoming/execution_data'
88
83
  require 'ib-ruby/messages/incoming/historical_data'
89
84
  require 'ib-ruby/messages/incoming/market_depths'
85
+ require 'ib-ruby/messages/incoming/next_valid_id'
90
86
  require 'ib-ruby/messages/incoming/open_order'
91
87
  require 'ib-ruby/messages/incoming/order_status'
92
88
  require 'ib-ruby/messages/incoming/portfolio_value'
@@ -36,7 +36,8 @@ module IB
36
36
  def encode server
37
37
  [self.class.message_id,
38
38
  self.class.version,
39
- @data[:id] || @data[:ticker_id] || @data[:request_id]|| @data[:order_id] || [],
39
+ @data[:id] || @data[:ticker_id] || @data[:request_id] ||
40
+ @data[:local_id] || @data[:order_id] || [],
40
41
  self.class.data_map.map do |(field, default_method, args)|
41
42
  case
42
43
  when default_method.nil?
@@ -2,7 +2,7 @@ module IB
2
2
  module Messages
3
3
  module Outgoing
4
4
 
5
- # Data format is { :id => int: order_id,
5
+ # Data format is { :id => int: local_id,
6
6
  # :contract => Contract,
7
7
  # :order => Order }
8
8
  PlaceOrder = def_message [3, 31] # v.38 is NOT properly supported by API yet
@@ -45,7 +45,7 @@ module IB
45
45
  CancelCalculateImpliedVolatility = CancelImpliedVolatility = def_message(56)
46
46
  CancelCalculateOptionPrice = CancelOptionPrice = def_message(57)
47
47
 
48
- ## Data format is: @data ={ :id => order_id to cancel }
48
+ ## Data format is: @data ={ :id => local_id of order to cancel }
49
49
  CancelOrder = def_message 4
50
50
 
51
51
  ## These messages contain just one or two extra fields:
@@ -0,0 +1,59 @@
1
+ require 'ib-ruby/models/contract'
2
+
3
+ module IB
4
+ module Models
5
+
6
+ # "BAG" is not really a contract, but a combination (combo) of securities.
7
+ # AKA basket or bag of securities. Individual securities in combo are represented
8
+ # by ComboLeg objects.
9
+ class Bag < Contract
10
+ # General Notes:
11
+ # 1. :exchange for the leg definition must match that of the combination order.
12
+ # The exception is for a STK legs, which must specify the SMART exchange.
13
+ # 2. :symbol => "USD" For combo Contract, this is an arbitrary value (like "USD")
14
+
15
+ validates_format_of :sec_type, :with => /^bag$/, :message => "should be a bag"
16
+ validates_format_of :right, :with => /^none$/, :message => "should be none"
17
+ validates_format_of :expiry, :with => /^$/, :message => "should be blank"
18
+ validate :legs_cannot_be_empty
19
+
20
+ def legs_cannot_be_empty
21
+ errors.add(:legs, "legs cannot be empty") if legs.empty?
22
+ end
23
+
24
+ def default_attributes
25
+ {:legs => Array.new,
26
+ :sec_type => :bag}.merge super
27
+ end
28
+
29
+ def description
30
+ self[:description] || to_human
31
+ end
32
+
33
+ def to_human
34
+ "<Bag: #{[symbol, exchange, currency].join(' ')} legs: #{legs_description} >"
35
+ end
36
+
37
+ ### Leg-related methods
38
+
39
+ # TODO: Rewrite with legs and legs_description being strictly in sync...
40
+ # TODO: Find a way to serialize legs without references...
41
+ # IB-equivalent leg description.
42
+ def legs_description
43
+ self[:legs_description] || legs.map { |leg| "#{leg.con_id}|#{leg.weight}" }.join(',')
44
+ end
45
+
46
+ # Check if two Contracts have same legs (maybe in different order)
47
+ def same_legs? other
48
+ legs == other.legs ||
49
+ legs_description.split(',').sort == other.legs_description.split(',').sort
50
+ end
51
+
52
+ # Contract comparison
53
+ def == other
54
+ super && same_legs?(other)
55
+ end
56
+
57
+ end # class Bag
58
+ end # Models
59
+ end # IB
@@ -7,6 +7,8 @@ module IB
7
7
  class ComboLeg < Model.for(:combo_leg)
8
8
  include ModelProperties
9
9
 
10
+ belongs_to :contract
11
+
10
12
  # General Notes:
11
13
  # 1. The exchange for the leg definition must match that of the combination order.
12
14
  # The exception is for a STK leg definition, which must specify the SMART exchange.
@@ -36,12 +38,14 @@ module IB
36
38
  validates_format_of :designated_location, :with => /^$/,
37
39
  :message => "should be blank or orders will be rejected"
38
40
 
39
- DEFAULT_PROPS = {:con_id => 0,
40
- :open_close => :same, # The only option for retail customers.
41
- :short_sale_slot => :default,
42
- :designated_location => '',
43
- :exchange => 'SMART', # Unless SMART, Order modification fails
44
- :exempt_code => -1, }
41
+ def default_attributes
42
+ {:con_id => 0,
43
+ :open_close => :same, # The only option for retail customers.
44
+ :short_sale_slot => :default,
45
+ :designated_location => '',
46
+ :exchange => 'SMART', # Unless SMART, Order modification fails
47
+ :exempt_code => -1, }.merge super
48
+ end
45
49
 
46
50
  # Leg's weight is a combination of action and ratio
47
51
  def weight