ib-ruby 0.7.4 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. data/.gitignore +3 -0
  2. data/HISTORY +8 -0
  3. data/README.md +2 -2
  4. data/Rakefile +15 -0
  5. data/TODO +7 -2
  6. data/VERSION +1 -1
  7. data/bin/account_info +1 -1
  8. data/bin/cancel_orders +1 -1
  9. data/bin/contract_details +1 -1
  10. data/bin/depth_of_market +1 -1
  11. data/bin/fa_accounts +1 -1
  12. data/bin/fundamental_data +42 -0
  13. data/bin/historic_data +1 -1
  14. data/bin/historic_data_cli +1 -1
  15. data/bin/list_orders +1 -2
  16. data/bin/market_data +1 -1
  17. data/bin/option_data +1 -1
  18. data/bin/place_combo_order +1 -1
  19. data/bin/place_order +1 -1
  20. data/bin/template +1 -4
  21. data/bin/tick_data +2 -2
  22. data/bin/time_and_sales +1 -1
  23. data/lib/ib-ruby.rb +4 -0
  24. data/lib/ib-ruby/connection.rb +50 -34
  25. data/lib/ib-ruby/constants.rb +232 -37
  26. data/lib/ib-ruby/db.rb +25 -0
  27. data/lib/ib-ruby/extensions.rb +51 -1
  28. data/lib/ib-ruby/messages/abstract_message.rb +0 -8
  29. data/lib/ib-ruby/messages/incoming.rb +18 -493
  30. data/lib/ib-ruby/messages/incoming/abstract_message.rb +100 -0
  31. data/lib/ib-ruby/messages/incoming/alert.rb +34 -0
  32. data/lib/ib-ruby/messages/incoming/contract_data.rb +82 -0
  33. data/lib/ib-ruby/messages/incoming/delta_neutral_validation.rb +20 -0
  34. data/lib/ib-ruby/messages/incoming/execution_data.rb +59 -0
  35. data/lib/ib-ruby/messages/incoming/historical_data.rb +55 -0
  36. data/lib/ib-ruby/messages/incoming/market_depths.rb +44 -0
  37. data/lib/ib-ruby/messages/incoming/open_order.rb +32 -16
  38. data/lib/ib-ruby/messages/incoming/order_status.rb +67 -0
  39. data/lib/ib-ruby/messages/incoming/portfolio_value.rb +39 -0
  40. data/lib/ib-ruby/messages/incoming/real_time_bar.rb +32 -0
  41. data/lib/ib-ruby/messages/incoming/scanner_data.rb +49 -0
  42. data/lib/ib-ruby/messages/outgoing.rb +25 -223
  43. data/lib/ib-ruby/messages/outgoing/abstract_message.rb +61 -0
  44. data/lib/ib-ruby/messages/outgoing/bar_requests.rb +149 -0
  45. data/lib/ib-ruby/messages/outgoing/place_order.rb +24 -0
  46. data/lib/ib-ruby/models.rb +4 -0
  47. data/lib/ib-ruby/models/bar.rb +31 -14
  48. data/lib/ib-ruby/models/combo_leg.rb +48 -23
  49. data/lib/ib-ruby/models/contracts.rb +2 -2
  50. data/lib/ib-ruby/models/contracts/bag.rb +11 -7
  51. data/lib/ib-ruby/models/contracts/contract.rb +90 -66
  52. data/lib/ib-ruby/models/contracts/option.rb +16 -7
  53. data/lib/ib-ruby/models/execution.rb +34 -18
  54. data/lib/ib-ruby/models/model.rb +15 -7
  55. data/lib/ib-ruby/models/model_properties.rb +101 -44
  56. data/lib/ib-ruby/models/order.rb +176 -187
  57. data/lib/ib-ruby/models/order_state.rb +99 -0
  58. data/lib/ib-ruby/symbols/forex.rb +10 -10
  59. data/lib/ib-ruby/symbols/futures.rb +6 -6
  60. data/lib/ib-ruby/symbols/stocks.rb +3 -3
  61. data/spec/account_helper.rb +4 -5
  62. data/spec/combo_helper.rb +4 -4
  63. data/spec/db.rb +18 -0
  64. data/spec/ib-ruby/messages/{incoming_spec.rb → incoming/alert_spec.rb} +1 -0
  65. data/spec/ib-ruby/messages/incoming/open_order_spec.rb +100 -0
  66. data/spec/ib-ruby/messages/incoming/order_status_spec.rb +74 -0
  67. data/spec/ib-ruby/messages/{outgoing_spec.rb → outgoing/account_data_spec.rb} +0 -0
  68. data/spec/ib-ruby/messages/outgoing/market_data_type_spec.rb +44 -0
  69. data/spec/ib-ruby/models/bag_spec.rb +97 -0
  70. data/spec/ib-ruby/models/bar_spec.rb +45 -0
  71. data/spec/ib-ruby/models/combo_leg_spec.rb +56 -40
  72. data/spec/ib-ruby/models/contract_spec.rb +134 -170
  73. data/spec/ib-ruby/models/execution_spec.rb +35 -50
  74. data/spec/ib-ruby/models/option_spec.rb +127 -0
  75. data/spec/ib-ruby/models/order_spec.rb +89 -68
  76. data/spec/ib-ruby/models/order_state_spec.rb +55 -0
  77. data/spec/integration/contract_info_spec.rb +4 -6
  78. data/spec/integration/fundamental_data_spec.rb +41 -0
  79. data/spec/integration/historic_data_spec.rb +4 -4
  80. data/spec/integration/market_data_spec.rb +1 -3
  81. data/spec/integration/orders/attached_spec.rb +8 -10
  82. data/spec/integration/orders/combo_spec.rb +2 -2
  83. data/spec/integration/orders/execution_spec.rb +0 -1
  84. data/spec/integration/orders/placement_spec.rb +1 -3
  85. data/spec/integration/orders/valid_ids_spec.rb +1 -2
  86. data/spec/message_helper.rb +1 -1
  87. data/spec/model_helper.rb +211 -0
  88. data/spec/order_helper.rb +44 -37
  89. data/spec/spec_helper.rb +36 -23
  90. data/spec/v.rb +7 -0
  91. data/tasks/doc.rake +1 -1
  92. metadata +116 -12
  93. data/spec/integration/orders/open_order +0 -98
@@ -0,0 +1,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 ||