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,61 @@
1
+ require 'ib-ruby/messages/abstract_message'
2
+
3
+ module IB
4
+ module Messages
5
+ module Outgoing
6
+
7
+ # Container for specific message classes, keyed by their message_ids
8
+ Classes = {}
9
+
10
+ class AbstractMessage < IB::Messages::AbstractMessage
11
+
12
+ def initialize data={}
13
+ @data = data
14
+ @created_at = Time.now
15
+ end
16
+
17
+ # This causes the message to send itself over the server socket in server[:socket].
18
+ # "server" is the @server instance variable from the IB object.
19
+ # You can also use this to e.g. get the server version number.
20
+ #
21
+ # Subclasses can either override this method for precise control over how
22
+ # stuff gets sent to the server, or else define a method encode() that returns
23
+ # an Array of elements that ought to be sent to the server by calling to_s on
24
+ # each one and postpending a '\0'.
25
+ #
26
+ def send_to server
27
+ self.encode(server).flatten.each do |datum|
28
+ #p datum
29
+ server[:socket].write_data datum
30
+ end
31
+ end
32
+
33
+ # At minimum, Outgoing message contains message_id and version.
34
+ # Most messages also contain (ticker, request or order) :id.
35
+ # Then, content of @data Hash is encoded per instructions in data_map.
36
+ def encode server
37
+ [self.class.message_id,
38
+ self.class.version,
39
+ @data[:id] || @data[:ticker_id] || @data[:request_id]|| @data[:order_id] || [],
40
+ self.class.data_map.map do |(field, default_method, args)|
41
+ case
42
+ when default_method.nil?
43
+ @data[field]
44
+
45
+ when default_method.is_a?(Symbol) # method name with args
46
+ @data[field].send default_method, *args
47
+
48
+ when default_method.respond_to?(:call) # callable with args
49
+ default_method.call @data[field], *args
50
+
51
+ else # default
52
+ @data[field].nil? ? default_method : @data[field] # may be false still
53
+ end
54
+ end
55
+ ].flatten
56
+ end
57
+
58
+ end # AbstractMessage
59
+ end # module Outgoing
60
+ end # module Messages
61
+ end # module IB
@@ -0,0 +1,149 @@
1
+ module IB
2
+ module Messages
3
+ module Outgoing
4
+
5
+ # Messages that request bar data have special processing of @data
6
+
7
+ class BarRequestMessage < AbstractMessage
8
+ # Preprocessor for some data fields
9
+ def parse data
10
+ type = data[:data_type] || data[:what_to_show]
11
+ data_type = DATA_TYPES.invert[type] || type
12
+ unless DATA_TYPES.keys.include?(data_type)
13
+ error ":data_type must be one of #{DATA_TYPES.inspect}", :args
14
+ end
15
+
16
+ size = data[:bar_size] || data[:size]
17
+ bar_size = BAR_SIZES.invert[size] || size
18
+ unless BAR_SIZES.keys.include?(bar_size)
19
+ error ":bar_size must be one of #{BAR_SIZES.inspect}", :args
20
+ end
21
+
22
+ contract = data[:contract].is_a?(IB::Contract) ?
23
+ data[:contract] : IB::Contract.from_ib_ruby(data[:contract])
24
+
25
+ [data_type, bar_size, contract]
26
+ end
27
+ end
28
+
29
+ # data = { :id => ticker_id (int),
30
+ # :contract => Contract ,
31
+ # :bar_size => int/Symbol? Currently only 5 second bars are supported,
32
+ # if any other value is used, an exception will be thrown.,
33
+ # :data_type => Symbol: Determines the nature of data being extracted.
34
+ # :trades, :midpoint, :bid, :ask, :bid_ask,
35
+ # :historical_volatility, :option_implied_volatility,
36
+ # :option_volume, :option_open_interest
37
+ # - converts to "TRADES," "MIDPOINT," "BID," etc...
38
+ # :use_rth => int: 0 - all data available during the time span requested
39
+ # is returned, even data bars covering time intervals where the
40
+ # market in question was illiquid. 1 - only data within the
41
+ # "Regular Trading Hours" of the product in question is returned,
42
+ # even if the time span requested falls partially or completely
43
+ # outside of them.
44
+ RequestRealTimeBars = def_message 50, BarRequestMessage
45
+
46
+ class RequestRealTimeBars
47
+ def encode server
48
+ data_type, bar_size, contract = parse @data
49
+
50
+ [super,
51
+ contract.serialize_long,
52
+ bar_size,
53
+ data_type.to_s.upcase,
54
+ @data[:use_rth]].flatten
55
+ end
56
+ end # RequestRealTimeBars
57
+
58
+ # data = { :id => int: Ticker id, needs to be different than the reqMktData ticker
59
+ # id. If you use the same ticker ID you used for the symbol when
60
+ # you did ReqMktData, nothing comes back for the historical data call
61
+ # :contract => Contract: requested ticker description
62
+ # :end_date_time => String: "yyyymmdd HH:mm:ss", with optional time zone
63
+ # allowed after a space: "20050701 18:26:44 GMT"
64
+ # :duration => String, time span the request will cover, and is specified
65
+ # using the format: <integer> <unit>, eg: '1 D', valid units are:
66
+ # '1 S' (seconds, default if no unit is specified)
67
+ # '1 D' (days)
68
+ # '1 W' (weeks)
69
+ # '1 M' (months)
70
+ # '1 Y' (years, currently limited to one)
71
+ # :bar_size => String: Specifies the size of the bars that will be returned
72
+ # (within IB/TWS limits). Valid values include:
73
+ # '1 sec'
74
+ # '5 secs'
75
+ # '15 secs'
76
+ # '30 secs'
77
+ # '1 min'
78
+ # '2 mins'
79
+ # '3 mins'
80
+ # '5 mins'
81
+ # '15 mins'
82
+ # '30 min'
83
+ # '1 hour'
84
+ # '1 day'
85
+ # :what_to_show => Symbol: Determines the nature of data being extracted.
86
+ # Valid values:
87
+ # :trades, :midpoint, :bid, :ask, :bid_ask,
88
+ # :historical_volatility, :option_implied_volatility,
89
+ # :option_volume, :option_open_interest
90
+ # - converts to "TRADES," "MIDPOINT," "BID," etc...
91
+ # :use_rth => int: 0 - all data available during the time span requested
92
+ # is returned, even data bars covering time intervals where the
93
+ # market in question was illiquid. 1 - only data within the
94
+ # "Regular Trading Hours" of the product in question is returned,
95
+ # even if the time span requested falls partially or completely
96
+ # outside of them.
97
+ # :format_date => int: 1 - text format, like "20050307 11:32:16".
98
+ # 2 - offset in seconds from the beginning of 1970,
99
+ # which is the same format as the UNIX epoch time.
100
+ # }
101
+ #
102
+ # Note that as of 4/07 there is no historical data available for forex spot.
103
+ #
104
+ # data[:contract] may either be a Contract object or a String. A String should be
105
+ # in serialize_ib_ruby format; that is, it should be a colon-delimited string in
106
+ # the format (e.g. for Globex British pound futures contract expiring in Sep-2008):
107
+ #
108
+ # symbol:security_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
109
+ # GBP:FUT:200809:::62500:GLOBEX::USD:
110
+ #
111
+ # Fields not needed for a particular security should be left blank (e.g. strike
112
+ # and right are only relevant for options.)
113
+ #
114
+ # A Contract object will be automatically serialized into the required format.
115
+ #
116
+ # See also http://chuckcaplan.com/twsapi/index.php/void%20reqIntradayData%28%29
117
+ # for general information about how TWS handles historic data requests, whence
118
+ # the following has been adapted:
119
+ #
120
+ # The server providing historical prices appears to not always be
121
+ # available outside of market hours. If you call it outside of its
122
+ # supported time period, or if there is otherwise a problem with
123
+ # it, you will receive error #162 "Historical Market Data Service
124
+ # query failed.:HMDS query returned no data."
125
+ #
126
+ # For backfill on futures data, you may need to leave the Primary
127
+ # Exchange field of the Contract structure blank; see
128
+ # http://www.interactivebrokers.com/discus/messages/2/28477.html?1114646754
129
+ RequestHistoricalData = def_message [20, 4], BarRequestMessage
130
+
131
+ class RequestHistoricalData
132
+ def encode server
133
+ data_type, bar_size, contract = parse @data
134
+
135
+ [super,
136
+ contract.serialize_long(:include_expired),
137
+ @data[:end_date_time],
138
+ bar_size,
139
+ @data[:duration],
140
+ @data[:use_rth],
141
+ data_type.to_s.upcase,
142
+ @data[:format_date],
143
+ contract.serialize_legs].flatten
144
+ end
145
+ end # RequestHistoricalData
146
+
147
+ end # module Outgoing
148
+ end # module Messages
149
+ end # module IB
@@ -0,0 +1,24 @@
1
+ module IB
2
+ module Messages
3
+ module Outgoing
4
+
5
+ # Data format is { :id => int: order_id,
6
+ # :contract => Contract,
7
+ # :order => Order }
8
+ PlaceOrder = def_message [3, 31] # v.38 is NOT properly supported by API yet
9
+
10
+ class PlaceOrder
11
+
12
+ def encode server
13
+ # Old server version supports no enhancements
14
+ @version = 31 if server[:server_version] <= 60
15
+
16
+ [super,
17
+ @data[:order].serialize_with(server, @data[:contract])].flatten
18
+ end
19
+ end # PlaceOrder
20
+
21
+
22
+ end # module Outgoing
23
+ end # module Messages
24
+ end # module IB
@@ -1,9 +1,13 @@
1
1
  module IB
2
2
  module Models
3
+
4
+ require 'ib-ruby/models/model_properties'
5
+ require 'ib-ruby/models/model'
3
6
  require 'ib-ruby/models/contracts'
4
7
  # Flatten namespace (IB::Models::Option instead of IB::Models::Contracts::Option)
5
8
  include Contracts
6
9
 
10
+ require 'ib-ruby/models/order_state'
7
11
  require 'ib-ruby/models/order'
8
12
  require 'ib-ruby/models/combo_leg'
9
13
  require 'ib-ruby/models/execution'
@@ -1,26 +1,43 @@
1
- require 'ib-ruby/models/model'
2
-
3
1
  module IB
4
2
  module Models
5
- # This is a single data point delivered by HistoricData messages.
3
+ # This is a single data point delivered by HistoricData or RealTimeBar messages.
6
4
  # Instantiate with a Hash of attributes, to be auto-set via initialize in Model.
7
- class Bar < Model
8
- prop :time, # The date-time stamp of the start of the bar. The format is
9
- # determined by the reqHistoricalData() formatDate parameter.
10
- :open, # The bar opening price.
5
+ class Bar < Model.for(:bar)
6
+ include ModelProperties
7
+
8
+ prop :open, # The bar opening price.
11
9
  :high, # The high price during the time covered by the bar.
12
10
  :low, # The low price during the time covered by the bar.
13
11
  :close, # The bar closing price.
14
- :volume, # The bar opening price.
12
+ :volume, # Volume
15
13
  :wap, # Weighted average price during the time covered by the bar.
16
- :has_gaps, # Whether or not there are gaps in the data.
17
- :trades # int: When TRADES data history is returned, represents number
18
- # of trades that occurred during the time period the bar covers
14
+ :trades, # int: When TRADES data history is returned, represents number
15
+ # of trades that occurred during the time period the bar covers
16
+ :time, # TODO: convert into Time object?
17
+ # The date-time stamp of the start of the bar. The format is
18
+ # determined by the reqHistoricalData() formatDate parameter.
19
+ :has_gaps => :bool # Whether or not there are gaps in the data.
19
20
 
20
- def to_s
21
- "<Bar #{time}: wap: #{wap}, OHLC: #{open}, #{high}, #{low}, #{close}, " +
22
- (trades ? "trades: #{trades}," : "") + " vol: #{volume}, gaps? #{has_gaps}>"
21
+ validates_numericality_of :open, :high, :low, :close, :volume
22
+
23
+ # Order comparison
24
+ def == other
25
+ time == other.time &&
26
+ open == other.open &&
27
+ high == other.high &&
28
+ low == other.low &&
29
+ close == other.close &&
30
+ wap == other.wap &&
31
+ trades == other.trades &&
32
+ volume == other.volume
33
+ end
34
+
35
+ def to_human
36
+ "<Bar: #{time} wap #{wap} OHLC #{open} #{high} #{low} #{close} " +
37
+ (trades ? "trades #{trades}" : "") + " vol #{volume} gaps #{has_gaps}>"
23
38
  end
39
+
40
+ alias to_s to_human
24
41
  end # class Bar
25
42
  end # module Models
26
43
  end # module IB
@@ -4,53 +4,57 @@ module IB
4
4
  # ComboLeg objects represent individual securities in a "BAG" contract - which
5
5
  # is not really a contract, but a combination (combo) of securities. AKA basket
6
6
  # or bag of securities.
7
- class ComboLeg < Model
7
+ class ComboLeg < Model.for(:combo_leg)
8
+ include ModelProperties
9
+
8
10
  # General Notes:
9
11
  # 1. The exchange for the leg definition must match that of the combination order.
10
12
  # The exception is for a STK leg definition, which must specify the SMART exchange.
11
13
 
12
- # // open/close leg value is same as combo
13
- # Specifies whether the order is an open or close order. Valid values are:
14
- SAME = 0 # Same as the parent security. The only option for retail customers.
15
- OPEN = 1 # Open. This value is only valid for institutional customers.
16
- CLOSE = 2 # Close. This value is only valid for institutional customers.
17
- UNKNOWN = 3
18
-
19
14
  prop :con_id, # int: The unique contract identifier specifying the security.
20
15
  :ratio, # int: Select the relative number of contracts for the leg you
21
16
  # are constructing. To help determine the ratio for a
22
17
  # specific combination order, refer to the Interactive
23
18
  # Analytics section of the User's Guide.
24
19
 
25
- :action, # String: BUY/SELL/SSHORT/SSHORTX The side (buy or sell) for the leg.
26
20
  :exchange, # String: exchange to which the complete combo order will be routed.
27
- :open_close, # int: Specifies whether the order is an open or close order.
28
- # Valid values: ComboLeg::SAME/OPEN/CLOSE/UNKNOWN
29
-
30
21
  # For institutional customers only! For stock legs when doing short sale
31
- :short_sale_slot, # int: 0 - retail, 1 = clearing broker, 2 = third party
22
+ :short_sale_slot, # int: 0 - retail(default), 1 = clearing broker, 2 = third party
32
23
  :designated_location, # String: Only for shortSaleSlot == 2.
33
24
  # Otherwise leave blank or orders will be rejected.
34
- :exempt_code # int: ?
25
+ :exempt_code, # int: ?
26
+ [:side, :action] => PROPS[:side], # String: Action/side: BUY/SELL/SSHORT/SSHORTX
27
+ :open_close => PROPS[:open_close]
28
+ # int: Whether the order is an open or close order. Values:
29
+ # SAME = 0 Same as the parent security. The only option for retail customers.
30
+ # OPEN = 1 Open. This value is only valid for institutional customers.
31
+ # CLOSE = 2 Close. This value is only valid for institutional customers.
32
+ # UNKNOWN = 3
33
+
34
+ # Extra validations
35
+ validates_numericality_of :ratio, :con_id
36
+ validates_format_of :designated_location, :with => /^$/,
37
+ :message => "should be blank or orders will be rejected"
35
38
 
36
39
  DEFAULT_PROPS = {:con_id => 0,
37
- :open_close => SAME,
38
- :short_sale_slot => 0,
40
+ :open_close => :same, # The only option for retail customers.
41
+ :short_sale_slot => :default,
39
42
  :designated_location => '',
43
+ :exchange => 'SMART', # Unless SMART, Order modification fails
40
44
  :exempt_code => -1, }
41
45
 
42
46
  # Leg's weight is a combination of action and ratio
43
47
  def weight
44
- action == 'BUY' ? ratio : -ratio
48
+ side == :buy ? ratio : -ratio
45
49
  end
46
50
 
47
51
  def weight= value
48
52
  value = value.to_i
49
53
  if value > 0
50
- self.action = 'BUY'
54
+ self.side = :buy
51
55
  self.ratio = value
52
56
  else
53
- self.action = 'SELL'
57
+ self.side = :sell
54
58
  self.ratio = -value
55
59
  end
56
60
  end
@@ -59,13 +63,34 @@ module IB
59
63
  def serialize *fields
60
64
  [con_id,
61
65
  ratio,
62
- action,
66
+ side.to_sup,
63
67
  exchange,
64
- (fields.include?(:extended) ? [open_close, short_sale_slot,
65
- designated_location, exempt_code] : [])
68
+ (fields.include?(:extended) ?
69
+ [self[:open_close],
70
+ self[:short_sale_slot],
71
+ designated_location,
72
+ exempt_code] :
73
+ [])
66
74
  ].flatten
67
75
  end
68
- end # ComboLeg
69
76
 
77
+ def to_human
78
+ "<ComboLeg: #{side} #{ratio} con_id #{con_id} at #{exchange}>"
79
+ end
80
+
81
+ # Order comparison
82
+ def == other
83
+ other && other.is_a?(ComboLeg) &&
84
+ con_id == other.con_id &&
85
+ ratio == other.ratio &&
86
+ open_close == other.open_close &&
87
+ short_sale_slot == other.short_sale_slot&&
88
+ exempt_code == other.exempt_code &&
89
+ side == other.side &&
90
+ exchange == other.exchange &&
91
+ designated_location == other.designated_location
92
+ end
93
+
94
+ end # ComboLeg
70
95
  end # module Models
71
96
  end # module IB
@@ -15,8 +15,8 @@ module IB
15
15
  module Contracts
16
16
  # Specialized Contract subclasses representing different security types
17
17
  TYPES = Hash.new(Contract)
18
- TYPES[IB::SECURITY_TYPES[:bag]] = Bag
19
- TYPES[IB::SECURITY_TYPES[:option]] = Option
18
+ TYPES[:bag] = Bag
19
+ TYPES[:option] = Option
20
20
 
21
21
  # Returns concrete subclass for this sec_type, or default Contract
22
22
  def [] sec_type
@@ -11,11 +11,20 @@ module IB
11
11
  # General Notes:
12
12
  # 1. :exchange for the leg definition must match that of the combination order.
13
13
  # The exception is for a STK legs, which must specify the SMART exchange.
14
- # 2. :symbol => "USD" For combo Contract, this is an arbitrary value (like USD)
14
+ # 2. :symbol => "USD" For combo Contract, this is an arbitrary value (like "USD")
15
+
16
+ validates_format_of :sec_type, :with => /^bag$/, :message => "should be a bag"
17
+ validates_format_of :right, :with => /^none$/, :message => "should be none"
18
+ validates_format_of :expiry, :with => /^$/, :message => "should be blank"
19
+ validate :legs_cannot_be_empty
20
+
21
+ def legs_cannot_be_empty
22
+ errors.add(:legs, "legs cannot be empty") if legs.empty?
23
+ end
15
24
 
16
25
  def initialize opts = {}
17
26
  @legs = Array.new
18
- self[:sec_type] = IB::SECURITY_TYPES[:bag]
27
+ self.sec_type = :bag
19
28
  super opts
20
29
  end
21
30
 
@@ -36,11 +45,6 @@ module IB
36
45
  self[:legs_description] || legs.map { |leg| "#{leg.con_id}|#{leg.weight}" }.join(',')
37
46
  end
38
47
 
39
- def serialize_legs *fields
40
- return [0] if legs.empty?
41
- [legs.size, legs.map { |leg| leg.serialize *fields }]
42
- end
43
-
44
48
  # Check if two Contracts have same legs (maybe in different order)
45
49
  def same_legs? other
46
50
  legs == other.legs ||