ib-ruby 0.7.6 → 0.7.8

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