ib-api 972.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +50 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +16 -0
  7. data/Gemfile.lock +105 -0
  8. data/Guardfile +24 -0
  9. data/LICENSE +674 -0
  10. data/README.md +65 -0
  11. data/Rakefile +11 -0
  12. data/VERSION +1 -0
  13. data/api.gemspec +43 -0
  14. data/bin/console +95 -0
  15. data/bin/console.yml +3 -0
  16. data/bin/setup +8 -0
  17. data/changelog.md +7 -0
  18. data/example/README.md +76 -0
  19. data/example/account_info +54 -0
  20. data/example/account_positions +30 -0
  21. data/example/account_summary +88 -0
  22. data/example/cancel_orders +74 -0
  23. data/example/fa_accounts +25 -0
  24. data/example/fundamental_data +40 -0
  25. data/example/historic_data_cli +186 -0
  26. data/example/list_orders +45 -0
  27. data/example/portfolio_csv +81 -0
  28. data/example/scanner_data +62 -0
  29. data/example/template +19 -0
  30. data/example/tick_data +28 -0
  31. data/lib/extensions/class-extensions.rb +87 -0
  32. data/lib/ib-api.rb +7 -0
  33. data/lib/ib/base.rb +103 -0
  34. data/lib/ib/base_properties.rb +160 -0
  35. data/lib/ib/connection.rb +450 -0
  36. data/lib/ib/constants.rb +393 -0
  37. data/lib/ib/errors.rb +44 -0
  38. data/lib/ib/logger.rb +26 -0
  39. data/lib/ib/messages.rb +99 -0
  40. data/lib/ib/messages/abstract_message.rb +101 -0
  41. data/lib/ib/messages/incoming.rb +251 -0
  42. data/lib/ib/messages/incoming/abstract_message.rb +116 -0
  43. data/lib/ib/messages/incoming/account_value.rb +78 -0
  44. data/lib/ib/messages/incoming/alert.rb +34 -0
  45. data/lib/ib/messages/incoming/contract_data.rb +102 -0
  46. data/lib/ib/messages/incoming/delta_neutral_validation.rb +23 -0
  47. data/lib/ib/messages/incoming/execution_data.rb +50 -0
  48. data/lib/ib/messages/incoming/historical_data.rb +84 -0
  49. data/lib/ib/messages/incoming/market_depths.rb +44 -0
  50. data/lib/ib/messages/incoming/next_valid_id.rb +18 -0
  51. data/lib/ib/messages/incoming/open_order.rb +277 -0
  52. data/lib/ib/messages/incoming/order_status.rb +85 -0
  53. data/lib/ib/messages/incoming/portfolio_value.rb +78 -0
  54. data/lib/ib/messages/incoming/real_time_bar.rb +32 -0
  55. data/lib/ib/messages/incoming/scanner_data.rb +54 -0
  56. data/lib/ib/messages/incoming/ticks.rb +268 -0
  57. data/lib/ib/messages/outgoing.rb +437 -0
  58. data/lib/ib/messages/outgoing/abstract_message.rb +88 -0
  59. data/lib/ib/messages/outgoing/account_requests.rb +112 -0
  60. data/lib/ib/messages/outgoing/bar_requests.rb +250 -0
  61. data/lib/ib/messages/outgoing/place_order.rb +209 -0
  62. data/lib/ib/messages/outgoing/request_marketdata.rb +99 -0
  63. data/lib/ib/messages/outgoing/request_tick_data.rb +21 -0
  64. data/lib/ib/model.rb +4 -0
  65. data/lib/ib/models.rb +14 -0
  66. data/lib/ib/server_versions.rb +114 -0
  67. data/lib/ib/socket.rb +185 -0
  68. data/lib/ib/support.rb +160 -0
  69. data/lib/ib/version.rb +6 -0
  70. data/lib/models/ib/account.rb +85 -0
  71. data/lib/models/ib/account_value.rb +33 -0
  72. data/lib/models/ib/bag.rb +55 -0
  73. data/lib/models/ib/bar.rb +31 -0
  74. data/lib/models/ib/combo_leg.rb +105 -0
  75. data/lib/models/ib/condition.rb +245 -0
  76. data/lib/models/ib/contract.rb +415 -0
  77. data/lib/models/ib/contract_detail.rb +108 -0
  78. data/lib/models/ib/execution.rb +67 -0
  79. data/lib/models/ib/forex.rb +13 -0
  80. data/lib/models/ib/future.rb +15 -0
  81. data/lib/models/ib/index.rb +15 -0
  82. data/lib/models/ib/option.rb +78 -0
  83. data/lib/models/ib/option_detail.rb +55 -0
  84. data/lib/models/ib/order.rb +519 -0
  85. data/lib/models/ib/order_state.rb +152 -0
  86. data/lib/models/ib/portfolio_value.rb +64 -0
  87. data/lib/models/ib/stock.rb +16 -0
  88. data/lib/models/ib/underlying.rb +34 -0
  89. data/lib/models/ib/vertical.rb +96 -0
  90. data/lib/requires.rb +12 -0
  91. metadata +203 -0
@@ -0,0 +1,55 @@
1
+ require 'models/ib/contract'
2
+
3
+ module IB
4
+
5
+ # "BAG" is not really a contract, but a combination (combo) of securities.
6
+ # AKA basket or bag of securities. Individual securities in combo are represented
7
+ # by ComboLeg objects.
8
+ class Bag < Contract
9
+ # General Notes:
10
+ # 1. :exchange for the leg definition must match that of the combination order.
11
+ # The exception is for a STK legs, which must specify the SMART exchange.
12
+ # 2. :symbol => "USD" For combo Contract, this is an arbitrary value (like "USD")
13
+
14
+ validates_format_of :sec_type, :with => /\Abag\z/, :message => "should be a bag"
15
+ validates_format_of :right, :with => /\Anone\z/, :message => "should be none"
16
+ validates_format_of :expiry, :with => /\A\z/, :message => "should be blank"
17
+
18
+ def default_attributes
19
+ super.merge :sec_type => :bag #,:legs => Array.new,
20
+ end
21
+
22
+ # def description
23
+ # self[:description] || to_human
24
+ # end
25
+
26
+ def to_human
27
+ "<Bag: #{[symbol, exchange, currency].join(' ')} legs: #{legs_description} >"
28
+ end
29
+
30
+ def con_id= arg
31
+ # dont' update con_id
32
+ end
33
+
34
+ ### Leg-related methods
35
+
36
+ # TODO: Rewrite with legs and legs_description being strictly in sync...
37
+ # TODO: Find a way to serialize legs without references...
38
+ # IB-equivalent leg description.
39
+ def legs_description
40
+ self[:legs_description] || combo_legs.map { |the_leg| "#{the_leg.con_id}|#{the_leg.weight}" }.join(',')
41
+ end
42
+
43
+ # Check if two Contracts have same legs (maybe in different order)
44
+ def same_legs? other
45
+ combo_legs == other.combo_legs ||
46
+ legs_description.split(',').sort == other.legs_description.split(',').sort
47
+ end
48
+
49
+ # Contract comparison
50
+ def == other
51
+ super && same_legs?(other)
52
+ end
53
+
54
+ end # class Bag
55
+ end # IB
@@ -0,0 +1,31 @@
1
+ module IB
2
+ # This is a single data point delivered by HistoricData or RealTimeBar messages.
3
+ # Instantiate with a Hash of attributes, to be auto-set via initialize in Model.
4
+ class Bar < IB::Model
5
+ include BaseProperties
6
+
7
+ has_one :contract # The bar represents timeseries info for this Contract
8
+
9
+ prop :open, # The bar opening price.
10
+ :high, # The high price during the time covered by the bar.
11
+ :low, # The low price during the time covered by the bar.
12
+ :close, # The bar closing price.
13
+ :volume, # Volume
14
+ :wap, # Weighted average price during the time covered by the bar.
15
+ :trades, # int: When TRADES data history is returned, represents number
16
+ # of trades that occurred during the time period the bar covers
17
+ :time #DateTime
18
+ # The date-time stamp of the start of the bar. The format is
19
+ # determined by the reqHistoricalData() formatDate parameter.
20
+ # :has_gaps => :bool # Whether or not there are gaps in the data. ## omitted since ServerVersion 124
21
+
22
+ validates_numericality_of :open, :high, :low, :close, :volume
23
+
24
+ def to_human
25
+ "<Bar: #{time} wap #{wap} OHLC #{open} #{high} #{low} #{close} " +
26
+ (trades ? "trades #{trades}" : "") + " vol #{volume}>"
27
+ end
28
+
29
+ alias to_s to_human
30
+ end # class Bar
31
+ end # module IB
@@ -0,0 +1,105 @@
1
+ module IB
2
+
3
+ # ComboLeg is essentially a join Model between Combo (BAG) Contract and
4
+ # individual Contracts (securities) that this BAG contains.
5
+ class ComboLeg < IB::Model
6
+ include BaseProperties
7
+
8
+ # BAG Combo Contract that contains this Leg
9
+ # belongs_to :combo, :class_name => 'Contract'
10
+ # Contract that constitutes this Leg
11
+ # belongs_to :leg_contract, :class_name => 'Contract', :foreign_key => :leg_contract_id
12
+
13
+ # General Notes:
14
+ # 1. The exchange for the leg definition must match that of the combination order.
15
+ # The exception is for a STK leg definition, which must specify the SMART exchange.
16
+
17
+ prop :con_id, # int: The unique contract identifier specifying the security.
18
+ :ratio, # int: Select the relative number of contracts for the leg you
19
+ # are constructing. To help determine the ratio for a
20
+ # specific combination order, refer to the Interactive
21
+ # Analytics section of the User's Guide.
22
+ :exchange, # String: exchange to which the complete combo order will be routed.
23
+ #
24
+ # For institutional customers only! For stock legs when doing short sale
25
+ :short_sale_slot, # int: 0 - retail(default),
26
+ # 1 = clearing broker, 2 = third party
27
+ :designated_location, # String: Only for shortSaleSlot == 2.
28
+ # Otherwise leave blank or orders will be rejected.
29
+ :exempt_code, # int: (-1)
30
+ [:side, :action] => PROPS[:side], # String: Action/side: BUY/SELL/SSHORT/SSHORTX
31
+ :open_close => PROPS[:open_close]
32
+ # int: Whether the order is an open or close order. Values:
33
+ # SAME = 0 Same as the parent security. The only option for retail customers.
34
+ # OPEN = 1 Open. This value is only valid for institutional customers.
35
+ # CLOSE = 2 Close. This value is only valid for institutional customers.
36
+ # UNKNOWN = 3
37
+ :price # support for pet leg prices
38
+
39
+ # Extra validations
40
+ validates_numericality_of :ratio, :con_id
41
+ validates_format_of :designated_location, :with => /\A\z/,
42
+ :message => "should be blank or orders will be rejected"
43
+
44
+ def default_attributes
45
+ super.merge :con_id => 0,
46
+ :ratio => 1,
47
+ :side => :buy,
48
+ :open_close => :same, # The only option for retail customers.
49
+ :short_sale_slot => :default,
50
+ :designated_location => '',
51
+ :exchange => 'SMART', # Unless SMART, Order modification fails
52
+ :exempt_code => -1
53
+ end
54
+
55
+ # Leg's weight is a combination of action and ratio
56
+ def weight
57
+ side == :buy ? ratio : -ratio
58
+ end
59
+
60
+ def weight= value
61
+ value = value.to_i
62
+ if value > 0
63
+ self.side = :buy
64
+ self.ratio = value
65
+ else
66
+ self.side = :sell
67
+ self.ratio = -value
68
+ end
69
+ end
70
+
71
+ # Some messages include open_close, some don't. wtf.
72
+ def serialize *fields
73
+ [con_id,
74
+ ratio,
75
+ side.to_sup,
76
+ exchange,
77
+ (fields.include?(:extended) ?
78
+ [self[:open_close],
79
+ self[:short_sale_slot],
80
+ designated_location,
81
+ exempt_code] :
82
+ [])
83
+ ].flatten
84
+ end
85
+
86
+ def to_human
87
+ "<ComboLeg: #{side} #{ratio} con_id #{con_id} at #{exchange}>"
88
+ end
89
+
90
+ # Order comparison
91
+ def == other
92
+ super(other) ||
93
+ other.is_a?(self.class) &&
94
+ con_id == other.con_id &&
95
+ ratio == other.ratio &&
96
+ open_close == other.open_close &&
97
+ short_sale_slot == other.short_sale_slot &&
98
+ exempt_code == other.exempt_code &&
99
+ side == other.side &&
100
+ exchange == other.exchange &&
101
+ designated_location == other.designated_location
102
+ end
103
+
104
+ end # ComboLeg
105
+ end # module IB
@@ -0,0 +1,245 @@
1
+ require 'ib/support'
2
+ module IB
3
+ class OrderCondition < IB::Model
4
+ include BaseProperties
5
+
6
+
7
+ prop :operator, # 1 -> " >= " , 0 -> " <= " see /lib/ib/constants # 338f
8
+ :conjunction_connection, # "o" -> or "a"
9
+ :contract
10
+ def self.verify_contract_if_necessary c
11
+ c.con_id.to_i.zero? ||( c.primary_exchange.blank? && c.exchange.blank?) ? c.verify! : c
12
+ end
13
+ def condition_type
14
+ error "condition_type method is abstract"
15
+ end
16
+ def default_attributes
17
+ super.merge( operator: ">=" , conjunction_connection: :and )
18
+ end
19
+
20
+ def serialize_contract_by_con_id
21
+ [ contract.con_id , contract.primary_exchange.presence || contract.exchange ]
22
+ end
23
+
24
+ def serialize
25
+ [ condition_type, self[:conjunction_connection] ]
26
+ end
27
+ end
28
+
29
+
30
+
31
+ class PriceCondition < OrderCondition
32
+ using IBSupport # refine Array-method for decoding of IB-Messages
33
+ prop :price,
34
+ :trigger_method # see /models/ib/order.rb# 51 ff and /lib/ib/constants # 210 ff
35
+
36
+ def default_attributes
37
+ super.merge( :trigger_method => :default )
38
+ end
39
+
40
+ def condition_type
41
+ 1
42
+ end
43
+
44
+ def self.make buffer
45
+ m= self.new conjunction_connection: buffer.read_string,
46
+ operator: buffer.read_int,
47
+ price: buffer.read_decimal
48
+
49
+ the_contract = IB::Contract.new con_id: buffer.read_int, exchange: buffer.read_string
50
+ m.contract = the_contract
51
+ m.trigger_method = buffer.read_int
52
+ m
53
+
54
+ end
55
+
56
+ def serialize
57
+ super << self[:operator] << price << serialize_contract_by_con_id << self[:trigger_method]
58
+ end
59
+
60
+ # dsl: PriceCondition.fabricate some_contract, ">=", 500
61
+ def self.fabricate contract, operator, price
62
+ error "Condition Operator has to be \">=\" or \"<=\" " unless ["<=", ">="].include? operator
63
+ self.new operator: operator,
64
+ price: price.to_i,
65
+ contract: verify_contract_if_necessary( contract )
66
+ end
67
+
68
+ end
69
+
70
+ class TimeCondition < OrderCondition
71
+ using IBSupport # refine Array-method for decoding of IB-Messages
72
+ prop :time
73
+
74
+ def condition_type
75
+ 3
76
+ end
77
+
78
+ def self.make buffer
79
+ self.new conjunction_connection: buffer.read_string,
80
+ operator: buffer.read_int,
81
+ time: buffer.read_parse_date
82
+ end
83
+
84
+ def serialize
85
+ t = self[:time]
86
+ if t.is_a?(String) && t =~ /^\d{8}\z/ # expiry-format yyymmmdd
87
+ self.time = DateTime.new t[0..3],t[4..5],t[-2..-1]
88
+ end
89
+ serialized_time = case self[:time] # explicity formatting of time-object
90
+ when String
91
+ self[:time]
92
+ when DateTime
93
+ self[:time].gmtime.strftime("%Y%m%d %H:%M:%S %Z")
94
+ when Date, Time
95
+ self[:time].strftime("%Y%m%d %H:%M:%S")
96
+ end
97
+
98
+ super << self[:operator] << serialized_time
99
+ end
100
+
101
+ def self.fabricate operator, time
102
+ self.new operator: operator,
103
+ time: time
104
+ end
105
+ end
106
+
107
+ class ExecutionCondition < OrderCondition
108
+ using IBSupport # refine Array-method for decoding of IB-Messages
109
+
110
+ def condition_type
111
+ 5
112
+ end
113
+
114
+ def self.make buffer
115
+ m =self.new conjunction_connection: buffer.read_string,
116
+ operator: buffer.read_int
117
+
118
+ the_contract = IB::Contract.new sec_type: buffer.read_string,
119
+ exchange: buffer.read_string,
120
+ symbol: buffer.read_string
121
+ m.contract = the_contract
122
+ m
123
+ end
124
+
125
+ def serialize
126
+ super << contract[:sec_type] <<(contract.primary_exchange.presence || contract.exchange) << contract.symbol
127
+ end
128
+
129
+ def self.fabricate contract
130
+ self.new contract: verify_contract_if_necessary( contract )
131
+ end
132
+
133
+ end
134
+
135
+ class MarginCondition < OrderCondition
136
+ using IBSupport # refine Array-method for decoding of IB-Messages
137
+
138
+ prop :percent
139
+
140
+ def condition_type
141
+ 4
142
+ end
143
+
144
+ def self.make buffer
145
+ self.new conjunction_connection: buffer.read_string,
146
+ operator: buffer.read_int,
147
+ percent: buffer.read_int
148
+
149
+ end
150
+
151
+ def serialize
152
+ super << self[:operator] << percent
153
+ end
154
+ def self.fabricate operator, percent
155
+ error "Condition Operator has to be \">=\" or \"<=\" " unless ["<=", ">="].include? operator
156
+ self.new operator: operator,
157
+ percent: percent
158
+ end
159
+ end
160
+
161
+
162
+ class VolumeCondition < OrderCondition
163
+ using IBSupport # refine Array-method for decoding of IB-Messages
164
+
165
+ prop :volume
166
+
167
+ def condition_type
168
+ 6
169
+ end
170
+
171
+ def self.make buffer
172
+ m = self.new conjunction_connection: buffer.read_string,
173
+ operator: buffer.read_int,
174
+ volumne: buffer.read_int
175
+
176
+ the_contract = IB::Contract.new con_id: buffer.read_int, exchange: buffer.read_string
177
+ m.contract = the_contract
178
+ m
179
+ end
180
+
181
+ def serialize
182
+
183
+ super << self[:operator] << volume << serialize_contract_by.con_id
184
+ end
185
+
186
+ # dsl: VolumeCondition.fabricate some_contract, ">=", 50000
187
+ def self.fabricate contract, operator, volume
188
+ error "Condition Operator has to be \">=\" or \"<=\" " unless ["<=", ">="].include? operator
189
+ self.new operator: operator,
190
+ volume: volume,
191
+ contract: verify_contract_if_necessary( contract )
192
+ end
193
+ end
194
+
195
+ class PercentChangeCondition < OrderCondition
196
+ using IBSupport # refine Array-method for decoding of IB-Messages
197
+ prop :percent_change
198
+
199
+ def condition_type
200
+ 7
201
+ end
202
+
203
+ def self.make buffer
204
+ m = self.new conjunction_connection: buffer.read_string,
205
+ operator: buffer.read_int,
206
+ percent_change: buffer.read_decimal
207
+
208
+ the_contract = IB::Contract.new con_id: buffer.read_int, exchange: buffer.read_string
209
+ m.contract = the_contract
210
+ m
211
+ end
212
+
213
+ def serialize
214
+ super << self[:operator] << percent_change << serialize_contract_by_con_id
215
+
216
+ end
217
+ # dsl: PercentChangeCondition.fabricate some_contract, ">=", "5%"
218
+ def self.fabricate contract, operator, change
219
+ error "Condition Operator has to be \">=\" or \"<=\" " unless ["<=", ">="].include? operator
220
+ self.new operator: operator,
221
+ percent_change: change.to_i,
222
+ contract: verify_contract_if_necessary( contract )
223
+ end
224
+ end
225
+ class OrderCondition
226
+ using IBSupport # refine Array-method for decoding of IB-Messages
227
+ # subclasses representing specialized condition types.
228
+
229
+ Subclasses = Hash.new(OrderCondition)
230
+ Subclasses[1] = IB::PriceCondition
231
+ Subclasses[3] = IB::TimeCondition
232
+ Subclasses[5] = IB::ExecutionCondition
233
+ Subclasses[4] = IB::MarginCondition
234
+ Subclasses[6] = IB::VolumeCondition
235
+ Subclasses[7] = IB::PercentChangeCondition
236
+
237
+
238
+ # This builds an appropriate subclass based on its type
239
+ #
240
+ def self.make_from buffer
241
+ condition_type = buffer.read_int
242
+ OrderCondition::Subclasses[condition_type].make( buffer )
243
+ end
244
+ end # class
245
+ end # module
@@ -0,0 +1,415 @@
1
+ require 'models/ib/contract_detail'
2
+ require 'models/ib/underlying'
3
+
4
+
5
+
6
+ module IB
7
+ class Contract < IB::Model
8
+ include BaseProperties
9
+
10
+ # Fields are Strings unless noted otherwise
11
+ prop :con_id, # int: The unique contract identifier.
12
+ :currency, # Only needed if there is an ambiguity, e.g. when SMART exchange
13
+ # and IBM is being requested (IBM can trade in GBP or USD).
14
+
15
+ :legs_description, # received in OpenOrder for all combos
16
+
17
+ :sec_type, # Security type. Valid values are: SECURITY_TYPES
18
+
19
+ :sec_id => :sup, # Unique identifier of the given secIdType.
20
+
21
+ :sec_id_type => :sup, # Security identifier, when querying contract details or
22
+ # when placing orders. Supported identifiers are:
23
+ # - ISIN (Example: Apple: US0378331005)
24
+ # - CUSIP (Example: Apple: 037833100)
25
+ # - SEDOL (6-AN + check digit. Example: BAE: 0263494)
26
+ # - RIC (exchange-independent RIC Root and exchange-
27
+ # identifying suffix. Ex: AAPL.O for Apple on NASDAQ.)
28
+
29
+ :symbol => :s, # This is the symbol of the underlying asset.
30
+
31
+ :local_symbol => :s, # Local exchange symbol of the underlying asset
32
+ :trading_class => :s,
33
+ # Future/option contract multiplier (only needed when multiple possibilities exist)
34
+ :multiplier => {:set => :i},
35
+
36
+ :strike => :f, # double: The strike price.
37
+ :expiry => :s, # The expiration date. Use the format YYYYMM or YYYYMMDD
38
+ :last_trading_day => :s, # the tws returns the last trading day in Format YYYYMMMDD hh:mm
39
+ # which may differ from the expiry
40
+ :exchange => :sup, # The order destination, such as Smart.
41
+ :primary_exchange => :sup, # Non-SMART exchange where the contract trades.
42
+ :include_expired => :bool, # When true, contract details requests and historical
43
+ # data queries can be performed pertaining to expired contracts.
44
+ # Note: Historical data queries on expired contracts are
45
+ # limited to the last year of the contracts life, and are
46
+ # only supported for expired futures contracts.
47
+ # This field can NOT be set to true for orders.
48
+
49
+
50
+ # Specifies a Put or Call. Valid input values are: P, PUT, C, CALL
51
+ :right => {
52
+ :set => proc { |val|
53
+ self[:right] =
54
+ case val.to_s.upcase
55
+ when 'NONE', '', '0', '?'
56
+ ''
57
+ when 'PUT', 'P'
58
+ 'P'
59
+ when 'CALL', 'C'
60
+ 'C'
61
+ else
62
+ val
63
+ end
64
+ },
65
+ :validate => {:format => {:with => /\Aput$|^call$|^none\z/,
66
+ :message => "should be put, call or none"}}
67
+ }
68
+
69
+ attr_accessor :description # NB: local to ib, not part of TWS.
70
+
71
+ ### Associations
72
+ has_many :misc # multi purpose association
73
+ has_many :orders # Placed for this Contract
74
+ has_many :portfolio_values
75
+
76
+ has_many :bars # Possibly representing trading history for this Contract
77
+
78
+ has_one :contract_detail # Volatile info about this Contract
79
+
80
+ # For Contracts that are part of BAa ## leg is now a method of contract
81
+ # has_one :leg #, :class_name => 'ComboLeg', :foreign_key => :leg_contract_id
82
+ # has_one :combo, :class_name => 'Contract', :through => :leg
83
+
84
+ # for Combo/BAG Contracts that contain ComboLegs
85
+ has_many :combo_legs#, :foreign_key => :combo_id
86
+ # has_many :leg_contracts, :class_name => 'Contract', :through => :combo_legs
87
+ # alias legs combo_legs
88
+ # alias legs= combo_legs=
89
+
90
+ # alias combo_legs_description legs_description
91
+ # alias combo_legs_description= legs_description=
92
+
93
+ # for Delta-Neutral Combo Contracts
94
+ has_one :underlying
95
+ alias under_comp underlying
96
+ alias under_comp= underlying=
97
+
98
+
99
+ ### Extra validations
100
+ validates_inclusion_of :sec_type, :in => CODES[:sec_type].keys,
101
+ :message => "should be valid security type"
102
+
103
+ validates_format_of :expiry, :with => /\A\d{6}$|^\d{8}$|\A\z/,
104
+ :message => "should be YYYYMM or YYYYMMDD"
105
+
106
+ validates_format_of :primary_exchange, :without => /SMART/,
107
+ :message => "should not be SMART"
108
+
109
+ validates_format_of :sec_id_type, :with => /ISIN|SEDOL|CUSIP|RIC|\A\z/,
110
+ :message => "should be valid security identifier"
111
+
112
+ validates_numericality_of :multiplier, :strike, :allow_nil => true
113
+
114
+ def default_attributes # :nodoc:
115
+ super.merge :con_id => 0,
116
+ :strike => 0.0,
117
+ :right => :none, # Not an option
118
+ # :exchange => 'SMART',
119
+ :include_expired => false
120
+ end
121
+ # This returns an Array of data from the given contract and is used to represent
122
+ # contracts in outgoing messages.
123
+ #
124
+ # Different messages serialize contracts differently. Go figure.
125
+ #
126
+ # Note that it does NOT include the combo legs.
127
+ # serialize :option, :con_id, :include_expired, :sec_id
128
+ #
129
+ # 18/1/18: serialise always includes conid
130
+
131
+ def serialize *fields # :nodoc:
132
+ print_default = ->(field, default="") { field.blank? ? default : field }
133
+ print_not_zero = ->(field, default="") { field.to_i.zero? ? default : field }
134
+ [(con_id.present? && !con_id.is_a?(Symbol) && con_id.to_i > 0 ? con_id : ""),
135
+ print_default[symbol],
136
+ print_default[self[:sec_type]],
137
+ ( fields.include?(:option) ?
138
+ [ print_default[expiry],
139
+ print_not_zero[strike],
140
+ print_default[self[:right]],
141
+ print_default[multiplier]] : nil ),
142
+ print_default[exchange],
143
+ ( fields.include?(:primary_exchange) ? print_default[primary_exchange] : nil ) ,
144
+ print_default[currency],
145
+ print_default[local_symbol],
146
+ ( fields.include?(:trading_class) ? print_default[trading_class] : nil ),
147
+ ( fields.include?(:include_expired) ? print_default[include_expired,0] : nil ),
148
+ ( fields.include?(:sec_id_type) ? [print_default[sec_id_type], print_default[sec_id]] : nil )
149
+ ].flatten.compact
150
+ end
151
+
152
+ # serialize contract
153
+ # con_id. sec_type, expiry, strike, right, multiplier exchange, primary_exchange, currency, local_symbol, include_expired
154
+ # other fields on demand
155
+ def serialize_long *fields # :nodoc:
156
+ serialize :option, :include_expired, :primary_exchange, :trading_class, *fields
157
+ end
158
+
159
+ # serialize contract
160
+ # con_id. sec_type, expiry, strike, right, multiplier, exchange, primary_exchange, currency, local_symbol
161
+ # other fields on demand
162
+ # acutal used by place_order, request_marketdata, request_market_depth, exercise_options
163
+ def serialize_short *fields # :nodoc:
164
+ serialize :option, :trading_class, :primary_exchange, *fields
165
+ end
166
+
167
+ # same as :serialize_short, omitting primary_exchange
168
+ # used by RequestMarketDepth
169
+ def serialize_supershort *fields # :nodoc:
170
+ serialize :option, :trading_class, *fields
171
+ end
172
+
173
+ # Serialize under_comp parameters: EClientSocket.java, line 471
174
+ def serialize_under_comp *args # :nodoc:
175
+ under_comp ? under_comp.serialize : [false]
176
+ end
177
+
178
+ # Defined in Contract, not BAG subclass to keep code DRY
179
+ def serialize_legs *fields # :nodoc:
180
+ case
181
+ when !bag?
182
+ []
183
+ when combo_legs.empty?
184
+ [0]
185
+ else
186
+ [combo_legs.size, combo_legs.map { |the_leg| the_leg.serialize *fields }].flatten
187
+ end
188
+ end
189
+
190
+
191
+
192
+ # This produces a string uniquely identifying this contract, in the format used
193
+ # for command line arguments in the IB-Ruby examples. The format is:
194
+ #
195
+ # symbol:sec_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
196
+ #
197
+ # Fields not needed for a particular security should be left blank
198
+ # (e.g. strike and right are only relevant for options.)
199
+ #
200
+ # For example, to query the British pound futures contract trading on Globex
201
+ # expiring in September, 2008, the string is:
202
+ #
203
+ # GBP:FUT:200809:::62500:GLOBEX::USD:
204
+ def serialize_ib_ruby
205
+ serialize_long.join(":")
206
+ end
207
+
208
+ # extracts essential attributes of the contract,
209
+ # and returns a new contract.
210
+ #
211
+ # the link to contract-details is __not__ maintained.
212
+ def essential
213
+
214
+ self_attributes = [ :right, :sec_type]
215
+ the_attributes = [ :symbol , :con_id, :exchange,
216
+ :currency, :expiry, :strike, :local_symbol, :last_trading_day,
217
+ :multiplier, :primary_exchange, :trading_class ]
218
+ the_hash= the_attributes.map{|x| y= attributes[x]; [x,y] if y.present? }.compact.to_h
219
+ the_hash[:description] = @description if @description.present?
220
+ self.class.new the_hash.merge( self_attributes.map{|x| y = self.send(x); [x,y] unless y == :none}.compact.to_h )
221
+ end
222
+
223
+
224
+ # creates a new Contract substituting attributes by the provied key-value pairs.
225
+ #
226
+ # con_id is resetted
227
+ def merge **new_attributes
228
+ self.con_id = 0
229
+ self.class.new attributes.merge new_attributes
230
+ end
231
+
232
+ # Contract comparison
233
+
234
+ def == other # :nodoc:
235
+ return false if !other.is_a?(Contract)
236
+ return true if super(other)
237
+ return true if !con_id.to_i.zero? && con_id == other.con_id
238
+
239
+ return false unless other.is_a?(self.class)
240
+
241
+ # Different sec_id_type
242
+ return false if sec_id_type && other.sec_id_type && sec_id_type != other.sec_id_type
243
+
244
+ # Different sec_id
245
+ return false if sec_id && other.sec_id && sec_id != other.sec_id
246
+
247
+ # Different symbols
248
+ return false if symbol && other.symbol && symbol != other.symbol
249
+
250
+ # Different currency
251
+ return false if currency && other.currency && currency != other.currency
252
+
253
+ # Same con_id for all Bags, but unknown for new Contracts...
254
+ # 0 or nil con_id matches any
255
+ return false if con_id != 0 && other.con_id != 0 &&
256
+ con_id && other.con_id && con_id != other.con_id
257
+
258
+ # SMART or nil exchange matches any
259
+ return false if exchange != 'SMART' && other.exchange != 'SMART' &&
260
+ exchange && other.exchange && exchange != other.exchange
261
+
262
+ # Comparison for Bonds and Options
263
+ if bond? || option?
264
+ return false if right != other.right || strike != other.strike
265
+ return false if multiplier && other.multiplier &&
266
+ multiplier != other.multiplier
267
+ return false if expiry && expiry[0..5] != other.expiry[0..5]
268
+ return false unless expiry && (expiry[6..7] == other.expiry[6..7] ||
269
+ expiry[6..7].empty? || other.expiry[6..7].empty?)
270
+ end
271
+
272
+ # All else being equal...
273
+ sec_type == other.sec_type
274
+ end
275
+
276
+ def to_s
277
+ "<Contract: " + instance_variables.map do |key|
278
+ value = send(key[1..-1])
279
+ " #{key}=#{value} (#{value.class}) " unless value.blank?
280
+ end.compact.join(',') + " >"
281
+ end
282
+
283
+ def to_human
284
+ "<Contract: " +
285
+ [symbol,
286
+ sec_type,
287
+ (expiry == '' ? nil : expiry),
288
+ (right == :none ? nil : right),
289
+ (strike == 0 ? nil : strike),
290
+ exchange,
291
+ currency
292
+ ].compact.join(" ") + ">"
293
+ end
294
+
295
+ def to_short
296
+ if expiry.blank? && last_trading_day.blank?
297
+ "#{symbol}# {exchange}# {currency}"
298
+ elsif expiry.present?
299
+ "#{symbol}(#{strike}) #{right} #{expiry} /#{exchange}/#{currency}"
300
+ else
301
+ "#{symbol}(#{strike}) #{right} #{last_trading_day} /#{exchange}/#{currency}"
302
+ end
303
+ end
304
+ # Testing for type of contract:
305
+ # depreciated : use is_a?(IB::Stock, IB::Bond, IB::Bag etc) instead
306
+ def bag? # :nodoc:
307
+ self[:sec_type] == 'BAG'
308
+ end
309
+
310
+ def bond? # :nodoc:
311
+
312
+ self[:sec_type] == 'BOND'
313
+ end
314
+
315
+ def stock? # :nodoc:
316
+
317
+ self[:sec_type] == 'STK'
318
+ end
319
+
320
+ def option? # :nodoc:
321
+
322
+ self[:sec_type] == 'OPT'
323
+ end
324
+
325
+ def index? # :nodoc:
326
+
327
+ self[:sec_type] == 'IND'
328
+ end
329
+
330
+ =begin
331
+ From the release notes of TWS 9.50
332
+
333
+ Within TWS and Mosaic, we use the last trading day and not the actual expiration date for futures, options and futures options contracts. To be more accurate, all fields and selectors throughout TWS that were labeled Expiry or Expiration have been changed to Last Trading Day. Note that the last trading day and the expiration date may be the same or different dates.
334
+
335
+ In many places, such as the OptionTrader, Probability Lab and other options/futures tools, this is a simple case of changing the name of a field to Last Trading Day. In other cases the change is wider-reaching. For example, basket files that include derivatives were previously saved using the Expiry header. When you try to import these legacy .csv files, you will now receive a message requiring that you change this column title to LastTradingDayorContractMonth before the import will be accepted. New basket files that include derivatives will use this correct header. Additionally, this new field serves two functions. If you use the format YYYYMMDD, we understand you are identifying the last trading day for a contract. If you use the format YYYYMM, we understand you are identifying the contract month.
336
+
337
+ In places where these terms are used to indicate a concept, we have left them as Expiry or Expiration. For example in the Option Chain settings where we allow you to "Load the nearest N expiries" we have left the word expiries. Additionally, the Contract Description window will show both the Last Trading Date and the Expiration Date. Also in cases where it's appropriate, we have replaced Expiry or Expiration with Contract Month.
338
+
339
+ =end
340
+
341
+
342
+ # IB-ruby uses expiry to query Contracts.
343
+ #
344
+ # The response from the TWS is stored in 'last_trading_day' (Contract) and 'real_expiration_data' (ContractDetails)
345
+ #
346
+ # However, after querying a contract, 'expiry' ist overwritten by 'last_trading_day'. The original 'expiry'
347
+ # is still available through 'attributes[:expiry]'
348
+
349
+ def expiry
350
+ if self.last_trading_day.present?
351
+ last_trading_day.gsub(/-/,'')
352
+ else
353
+ @attributes[:expiry]
354
+ end
355
+ end
356
+
357
+
358
+ # is read by Account#PlaceOrder to set requirements for contract-types, as NonGuaranteed for stock-spreads
359
+ def order_requirements
360
+ Hash.new
361
+ end
362
+
363
+ end # class Contract
364
+
365
+
366
+ ### Now let's deal with Contract subclasses
367
+
368
+ require 'models/ib/option'
369
+ require 'models/ib/bag'
370
+ require 'models/ib/forex'
371
+ require 'models/ib/future'
372
+ require 'models/ib/stock'
373
+ require 'models/ib/index'
374
+
375
+ class Contract
376
+ # Contract subclasses representing specialized security types.
377
+
378
+ Subclasses = Hash.new(Contract)
379
+ Subclasses[:bag] = IB::Bag
380
+ Subclasses[:option] = IB::Option
381
+ Subclasses[:future] = IB::Future
382
+ Subclasses[:stock] = IB::Stock
383
+ Subclasses[:forex] = IB::Forex
384
+ Subclasses[:index] = IB::Index
385
+
386
+
387
+ # This builds an appropriate Contract subclass based on its type
388
+ #
389
+ # the method is also used to copy Contract.values to new instances
390
+ def self.build opts = {}
391
+ subclass =( VALUES[:sec_type][opts[:sec_type]] || opts['sec_type'] || opts[:sec_type]).to_sym
392
+ Contract::Subclasses[subclass].new opts
393
+ end
394
+
395
+ # This returns a Contract initialized from the serialize_ib_ruby format string.
396
+ def self.from_ib_ruby
397
+ keys = [:con_id, :symbol, :sec_type, :expiry, :strike, :right, :multiplier,
398
+ :exchange, :primary_exchange, :currency, :local_symbol]
399
+ props = Hash[keys.zip(string.split(":"))]
400
+ props.delete_if { |k, v| v.nil? || v.empty? }
401
+ Contract.build props
402
+ end
403
+ end # class Contract
404
+ end # module IB
405
+
406
+ class String
407
+ def to_contract
408
+ keys = [:con_id, :symbol, :sec_type, :expiry, :strike, :right, :multiplier,
409
+ :exchange, :primary_exchange, :currency, :local_symbol]
410
+ props = Hash[keys.zip(split(":"))]
411
+ props.delete_if { |k, v| v.nil? || v.empty? }
412
+ IB::Contract.build props
413
+
414
+ end
415
+ end