ib-api 10.33.1

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 (161) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +52 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/CLAUDE.md +131 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +17 -0
  8. data/Gemfile.lock +120 -0
  9. data/Guardfile +24 -0
  10. data/LICENSE +674 -0
  11. data/LLM_GUIDE.md +388 -0
  12. data/README.md +114 -0
  13. data/Rakefile +11 -0
  14. data/VERSION +1 -0
  15. data/api.gemspec +50 -0
  16. data/bin/console +96 -0
  17. data/bin/console.yml +3 -0
  18. data/bin/setup +8 -0
  19. data/bin/simple +91 -0
  20. data/changelog.md +32 -0
  21. data/conditions/ib/execution_condition.rb +31 -0
  22. data/conditions/ib/margin_condition.rb +28 -0
  23. data/conditions/ib/order_condition.rb +29 -0
  24. data/conditions/ib/percent_change_condition.rb +34 -0
  25. data/conditions/ib/price_condition.rb +44 -0
  26. data/conditions/ib/time_condition.rb +42 -0
  27. data/conditions/ib/volume_condition.rb +36 -0
  28. data/lib/class_extensions.rb +167 -0
  29. data/lib/ib/base.rb +109 -0
  30. data/lib/ib/base_properties.rb +178 -0
  31. data/lib/ib/connection.rb +573 -0
  32. data/lib/ib/constants.rb +402 -0
  33. data/lib/ib/contract.rb +30 -0
  34. data/lib/ib/errors.rb +52 -0
  35. data/lib/ib/messages/abstract_message.rb +68 -0
  36. data/lib/ib/messages/incoming/abstract_message.rb +116 -0
  37. data/lib/ib/messages/incoming/abstract_tick.rb +25 -0
  38. data/lib/ib/messages/incoming/account_message.rb +26 -0
  39. data/lib/ib/messages/incoming/alert.rb +34 -0
  40. data/lib/ib/messages/incoming/contract_data.rb +105 -0
  41. data/lib/ib/messages/incoming/contract_message.rb +13 -0
  42. data/lib/ib/messages/incoming/delta_neutral_validation.rb +23 -0
  43. data/lib/ib/messages/incoming/execution_data.rb +50 -0
  44. data/lib/ib/messages/incoming/histogram_data.rb +30 -0
  45. data/lib/ib/messages/incoming/historical_data.rb +65 -0
  46. data/lib/ib/messages/incoming/historical_data_update.rb +50 -0
  47. data/lib/ib/messages/incoming/managed_accounts.rb +21 -0
  48. data/lib/ib/messages/incoming/market_depth.rb +34 -0
  49. data/lib/ib/messages/incoming/market_depth_l2.rb +15 -0
  50. data/lib/ib/messages/incoming/next_valid_id.rb +19 -0
  51. data/lib/ib/messages/incoming/open_order.rb +290 -0
  52. data/lib/ib/messages/incoming/order_status.rb +85 -0
  53. data/lib/ib/messages/incoming/portfolio_value.rb +47 -0
  54. data/lib/ib/messages/incoming/position_data.rb +21 -0
  55. data/lib/ib/messages/incoming/positions_multi.rb +15 -0
  56. data/lib/ib/messages/incoming/real_time_bar.rb +32 -0
  57. data/lib/ib/messages/incoming/receive_fa.rb +30 -0
  58. data/lib/ib/messages/incoming/scanner_data.rb +54 -0
  59. data/lib/ib/messages/incoming/tick_by_tick.rb +77 -0
  60. data/lib/ib/messages/incoming/tick_efp.rb +18 -0
  61. data/lib/ib/messages/incoming/tick_generic.rb +12 -0
  62. data/lib/ib/messages/incoming/tick_option.rb +60 -0
  63. data/lib/ib/messages/incoming/tick_price.rb +60 -0
  64. data/lib/ib/messages/incoming/tick_size.rb +55 -0
  65. data/lib/ib/messages/incoming/tick_string.rb +13 -0
  66. data/lib/ib/messages/incoming.rb +292 -0
  67. data/lib/ib/messages/outgoing/abstract_message.rb +84 -0
  68. data/lib/ib/messages/outgoing/bar_request_message.rb +247 -0
  69. data/lib/ib/messages/outgoing/new-place-order.rb +193 -0
  70. data/lib/ib/messages/outgoing/old-place-order.rb +147 -0
  71. data/lib/ib/messages/outgoing/place_order.rb +149 -0
  72. data/lib/ib/messages/outgoing/request_account_summary.rb +79 -0
  73. data/lib/ib/messages/outgoing/request_historical_data.rb +182 -0
  74. data/lib/ib/messages/outgoing/request_market_data.rb +102 -0
  75. data/lib/ib/messages/outgoing/request_market_depth.rb +57 -0
  76. data/lib/ib/messages/outgoing/request_real_time_bars.rb +48 -0
  77. data/lib/ib/messages/outgoing/request_scanner_subscription.rb +73 -0
  78. data/lib/ib/messages/outgoing/request_tick_by_tick_data.rb +21 -0
  79. data/lib/ib/messages/outgoing.rb +410 -0
  80. data/lib/ib/messages.rb +139 -0
  81. data/lib/ib/order_condition.rb +26 -0
  82. data/lib/ib/plugins.rb +27 -0
  83. data/lib/ib/prepare_data.rb +61 -0
  84. data/lib/ib/raw_message_parser.rb +99 -0
  85. data/lib/ib/socket.rb +83 -0
  86. data/lib/ib/support.rb +236 -0
  87. data/lib/ib/version.rb +6 -0
  88. data/lib/ib-api.rb +44 -0
  89. data/lib/server_versions.rb +145 -0
  90. data/lib/support/array_function.rb +28 -0
  91. data/lib/support/logging.rb +45 -0
  92. data/models/ib/account.rb +72 -0
  93. data/models/ib/account_value.rb +33 -0
  94. data/models/ib/bag.rb +55 -0
  95. data/models/ib/bar.rb +31 -0
  96. data/models/ib/combo_leg.rb +127 -0
  97. data/models/ib/contract.rb +411 -0
  98. data/models/ib/contract_detail.rb +118 -0
  99. data/models/ib/execution.rb +67 -0
  100. data/models/ib/forex.rb +12 -0
  101. data/models/ib/future.rb +64 -0
  102. data/models/ib/index.rb +14 -0
  103. data/models/ib/option.rb +149 -0
  104. data/models/ib/option_detail.rb +84 -0
  105. data/models/ib/order.rb +720 -0
  106. data/models/ib/order_state.rb +155 -0
  107. data/models/ib/portfolio_value.rb +86 -0
  108. data/models/ib/spread.rb +176 -0
  109. data/models/ib/stock.rb +25 -0
  110. data/models/ib/underlying.rb +32 -0
  111. data/plugins/ib/advanced-account.rb +442 -0
  112. data/plugins/ib/alerts/base-alert.rb +125 -0
  113. data/plugins/ib/alerts/gateway-alerts.rb +15 -0
  114. data/plugins/ib/alerts/order-alerts.rb +73 -0
  115. data/plugins/ib/auto-adjust.rb +0 -0
  116. data/plugins/ib/connection-tools.rb +122 -0
  117. data/plugins/ib/eod.rb +326 -0
  118. data/plugins/ib/greeks.rb +102 -0
  119. data/plugins/ib/managed-accounts.rb +274 -0
  120. data/plugins/ib/market-price.rb +150 -0
  121. data/plugins/ib/option-chain.rb +167 -0
  122. data/plugins/ib/order-flow.rb +157 -0
  123. data/plugins/ib/order-prototypes/abstract.rb +67 -0
  124. data/plugins/ib/order-prototypes/adaptive.rb +40 -0
  125. data/plugins/ib/order-prototypes/all-in-one.rb +46 -0
  126. data/plugins/ib/order-prototypes/combo.rb +46 -0
  127. data/plugins/ib/order-prototypes/forex.rb +40 -0
  128. data/plugins/ib/order-prototypes/limit.rb +193 -0
  129. data/plugins/ib/order-prototypes/market.rb +116 -0
  130. data/plugins/ib/order-prototypes/pegged.rb +169 -0
  131. data/plugins/ib/order-prototypes/premarket.rb +31 -0
  132. data/plugins/ib/order-prototypes/stop.rb +202 -0
  133. data/plugins/ib/order-prototypes/volatility.rb +39 -0
  134. data/plugins/ib/order-prototypes.rb +118 -0
  135. data/plugins/ib/probability-of-expiring.rb +109 -0
  136. data/plugins/ib/process-orders.rb +155 -0
  137. data/plugins/ib/roll.rb +86 -0
  138. data/plugins/ib/spread-prototypes/butterfly.rb +77 -0
  139. data/plugins/ib/spread-prototypes/calendar.rb +97 -0
  140. data/plugins/ib/spread-prototypes/stock-spread.rb +56 -0
  141. data/plugins/ib/spread-prototypes/straddle.rb +70 -0
  142. data/plugins/ib/spread-prototypes/strangle.rb +93 -0
  143. data/plugins/ib/spread-prototypes/vertical.rb +83 -0
  144. data/plugins/ib/spread-prototypes.rb +70 -0
  145. data/plugins/ib/symbols/abstract.rb +136 -0
  146. data/plugins/ib/symbols/bonds.rb +28 -0
  147. data/plugins/ib/symbols/cfd.rb +19 -0
  148. data/plugins/ib/symbols/combo.rb +46 -0
  149. data/plugins/ib/symbols/commodity.rb +17 -0
  150. data/plugins/ib/symbols/forex.rb +41 -0
  151. data/plugins/ib/symbols/futures.rb +127 -0
  152. data/plugins/ib/symbols/index.rb +43 -0
  153. data/plugins/ib/symbols/options.rb +99 -0
  154. data/plugins/ib/symbols/stocks.rb +44 -0
  155. data/plugins/ib/symbols/version.rb +5 -0
  156. data/plugins/ib/symbols.rb +118 -0
  157. data/plugins/ib/verify.rb +226 -0
  158. data/symbols/w20.yml +210 -0
  159. data/t.txt +20 -0
  160. data/update.md +71 -0
  161. metadata +327 -0
@@ -0,0 +1,442 @@
1
+ module IB
2
+ =begin
3
+
4
+ Plugin that provides helper methods for orders
5
+
6
+
7
+ Public API
8
+ ==========
9
+
10
+ Extends IB::Account
11
+
12
+ =end
13
+
14
+ module Advanced
15
+
16
+
17
+ def account_data_scan search_key, search_currency=nil
18
+ if search_currency.present?
19
+ account_values.find_all{|x| x.key.match( search_key ) && x.currency == search_currency.upcase }
20
+ else
21
+ account_values.find_all{|x| x.key.match( search_key ) }
22
+ end
23
+ end
24
+
25
+
26
+
27
+ =begin rdoc
28
+ given any key of local_id, perm_id or order_ref
29
+ and an optional status, which can be a string or a
30
+ regexp ( status: /mitted/ matches Submitted and Presubmitted)
31
+ the last associated Order-record is returned.
32
+
33
+ Thus if several Orders are placed with the same order_ref, the active one is returned
34
+
35
+ (If multible keys are specified, local_id preceeds perm_id)
36
+
37
+ =end
38
+ def locate_order local_id: nil, perm_id: nil, order_ref: nil, status: /ubmitted/, contract: nil, con_id: nil
39
+ search_option = [ local_id.present? ? [:local_id , local_id] : nil ,
40
+ perm_id.present? ? [:perm_id, perm_id] : nil,
41
+ order_ref.present? ? [:order_ref , order_ref ] : nil ].compact.first
42
+ matched_items = if search_option.nil?
43
+ orders # select all orders of the current account
44
+ else
45
+ key,value = search_option
46
+ orders.find_all{|x| x[key].to_i == value.to_i }
47
+ end
48
+
49
+ if contract.present?
50
+ if contract.con_id.zero? && !contract.is_a?( IB::Bag )
51
+ contract = contract.verify.first
52
+ end
53
+ matched_items = matched_items.find_all{|o| o.contract.essential == contract.essential }
54
+ elsif con_id.present?
55
+ matched_items = matched_items.find_all{|o| o.contract.con_id == con_id }
56
+ end
57
+
58
+ if status.present?
59
+ status = Regexp.new(status) unless status.is_a? Regexp
60
+ matched_items.detect{|x| x.order_state.status =~ status }
61
+ else
62
+ matched_items.last # return the last item
63
+ end
64
+ end
65
+
66
+
67
+ =begin rdoc
68
+ requires an IB::Order as parameter.
69
+
70
+ If attached, the associated IB::Contract is used to specify the tws-command
71
+
72
+ The associated Contract overtakes the specified (as parameter)
73
+
74
+ auto_adjust: Limit- and Aux-Prices are adjusted to Min-Tick
75
+
76
+ convert_size: The action-attribute (:buy :sell) is associated according the content of :total_quantity.
77
+
78
+
79
+ The parameter «order» is modified!
80
+
81
+ It can further used to modify and eventually cancel
82
+
83
+
84
+ Example
85
+
86
+ j36 = IB::Stock.new symbol: 'J36', exchange: 'SGX'
87
+ order = IB::Limit.order size: 100, price: 65.5
88
+ g = IB::Connection.current.clients.last
89
+
90
+ g.preview contract: j36, order: order
91
+ => {:init_margin=>0.10864874e6,
92
+ :maint_margin=>0.9704137e5,
93
+ :equity_with_loan=>0.97877973e6,
94
+ :commission=>0.524e1,
95
+ :commission_currency=>"USD",
96
+ :warning=>""
97
+
98
+ g.place order: order
99
+ => 67 # returns local_id
100
+ order.contract # updated (and verifired) contract-record
101
+ => #<IB::Contract:0x00000000013c94b0 @attributes={:con_id=>9534669,
102
+ :exchange=>"SGX",
103
+ :right=>"",
104
+ :include_expired=>false}>
105
+
106
+ order.limit_price = 65 # set new price
107
+ g.modify order: order # and transmit
108
+ => 67 # returns local_id
109
+
110
+ g.locate_order( local_id: {a number} )
111
+ => returns the assigned order-record for inspection
112
+
113
+ g.cancel order: order
114
+ # logger output: 05:17:11 Cancelling 65 New #250/ from 3000/DU167349>
115
+ =end
116
+
117
+ def place_order order:, contract: nil, auto_adjust: true, convert_size: true
118
+ result = ->(l){ orders.detect{|x| x.local_id == l && x.submitted? } }
119
+ qualified_contract = ->(c) do
120
+ c.is_a?(IB::Contract) &&
121
+ #·IB::Symbols are always qualified. They carry a description-field
122
+ ( c.description.present? || !c.con_id.to_i.zero? ||
123
+ (c.con_id.to_i <0 && c.sec_type == :bag ) ) # bags that carry a negative con_id are qualified
124
+ end
125
+
126
+ # assign qualificated contract to the order object if not present
127
+ order.contract ||= if qualified_contract[ contract ]
128
+ contract
129
+ else
130
+ contract.verify.first
131
+ end
132
+ # disable auto-adjust if min_tick is not available
133
+ auto_adjust = false if order.contract.contract_detail.nil?
134
+
135
+ error "No valid contract given" unless order.contract.is_a?(IB::Contract)
136
+
137
+ ## sending of plain vanilla IB::Bags will fail using account.place, unless a (negative) con-id is provided!
138
+ error "place order: ContractVerification failed. No con_id assigned" unless qualified_contract[order.contract] or contract.nil?
139
+
140
+ # declare some variables
141
+ ib = IB::Connection.current
142
+ wrong_order = nil
143
+ the_local_id = nil
144
+ q = Queue.new
145
+
146
+ ### Handle Error messages
147
+ ### Default action: log message and raise IB::TransmissionError
148
+ sa = ib.subscribe( :Alert ) do | msg |
149
+ if msg.error_id == the_local_id
150
+ wrong_order = msg.message
151
+ if msg.code == 110 # The price does not confirm to the minimum price variation for this contract
152
+ if auto_adjust
153
+ wrong_order = nil
154
+ the_local_id = -1
155
+ ib.logger.info "adjusting order-price"
156
+ else
157
+ ib.logger.error "The price #{order.limit_price}/ #{order.aux_price} not confirm to the minimum price variation for #{order.contract.to_human}"
158
+ end
159
+ elsif [ 201, # Order rejected, No Trading permissions
160
+ 203, # Security is not allowed for trading
161
+ 325, # Discretionary Orders are not supported for this combination of order-type and exchange
162
+ 355, # Order size does not conform to market rule
163
+ 361, 362, 363, 364, # invalid trigger or stop-price
164
+ 388, # Order size x is smaller than the minimum required size of yy.
165
+ ].include? msg.code
166
+ ib.logger.error msg.message
167
+ end
168
+ q.close # closing the queue indicates that no order was transmitted
169
+ end
170
+ end
171
+ # transfer the received openOrder to the queue
172
+ sb = ib.subscribe( :OpenOrder ){|m| q << m.order if m.order.local_id.to_i == the_local_id.to_i }
173
+ # modify order (parameter)
174
+ order.account = account # assign the account_id to the account-field of IB::Order
175
+ self.orders.save_insert order, :order_ref
176
+ order.auto_adjust if ib.plugins.include?( "auto-adjust" ) && auto_adjust
177
+ if convert_size
178
+ order.action = order.total_quantity.to_d < 0 ? :sell : :buy unless order.action == :sell
179
+ logger.info{ "Converted ordersize to #{order.total_quantity} and triggered a #{order.action} order"} if order.total_quantity.to_d < 0
180
+ order.total_quantity = order.total_quantity.to_d.abs
181
+ end
182
+ # con_id and exchange fully qualify a contract, no need to transmit other data
183
+ # if no contract is passed to order.place, order.contract is used for placement
184
+ # ... delegated to order#modify...
185
+ # the_contract = order.contract.con_id.to_i > 0 ? Contract.new( con_id: order.contract.con_id, exchange: order.contract.exchange) : nil
186
+ contract = order.contract
187
+ order = order.then{|x| x.contract = nil; x }
188
+ loop do
189
+ the_local_id = ib.place_order order, contract # return the local_id
190
+ # if transmit is false, just include the local_id in the order-record
191
+ Thread.new{ if order.transmit || order.what_if then sleep 1 else sleep 0.001 end ; q.close }
192
+ tws_answer = q.pop
193
+
194
+ adjust_price = ->(p) do
195
+ if order.action == :sell
196
+ p + contract.contract_detail.min_tick
197
+ else
198
+ p - contract.contract_detail.min_tick
199
+ end
200
+ end
201
+
202
+ if q.closed?
203
+ if wrong_order.present?
204
+ raise IB::SymbolError, wrong_order
205
+ elsif the_local_id.present?
206
+ if the_local_id < 0 # auto-adjust condition
207
+ order.local_id = nil # reset order record
208
+ order.aux_price = adjust_price.call( order.aux_price ) unless order.aux_price.to_i.zero?
209
+ order.limit_price = adjust_price.call( order.limit_price ) unless order.limit_price.to_i.zero?
210
+ else
211
+ order.local_id = the_local_id
212
+ end
213
+ else
214
+ error " #{order.to_human} is not transmitted properly", :symbol
215
+ end
216
+ else
217
+ order=tws_answer # return order-record received from tws
218
+ end
219
+ break unless order.local_id.nil?
220
+ q = Queue.new # reset queue
221
+ end
222
+ order.contract = contract
223
+ ib.unsubscribe sa
224
+ ib.unsubscribe sb
225
+ order # return the order-record
226
+ end # place
227
+
228
+
229
+ # shortcut to enable
230
+ # account.place order: {} , contract: {}
231
+ # account.preview order: {} , contract: {}
232
+ # account.modify order: {}
233
+ alias place place_order
234
+
235
+ =begin #rdoc
236
+ Account#ModifyOrder operates in two modi:
237
+
238
+ First: The order is specified via local_id, perm_id or order_ref.
239
+ It is checked, whether the order is still modifyable.
240
+ Then the Order ist provided through the block. Any modification is done there.
241
+ Important: The Block has to return the modified IB::Order
242
+
243
+ Second: The order can be provided as parameter as well. This will be used
244
+ without further checking. The block is now optional.
245
+ Important: The OrderRecord must provide a valid Contract.
246
+
247
+ The simple version does not adjust the given prices to tick-limits.
248
+ This has to be done manually in the provided block
249
+ =end
250
+
251
+
252
+ def modify_order local_id: nil, order_ref: nil, order:nil, contract: nil
253
+
254
+ result = ->(l){ orders.detect{|x| x.local_id == l && x.submitted? } }
255
+ order ||= locate_order( local_id: local_id,
256
+ status: /ubmitted/ ,
257
+ order_ref: order_ref )
258
+ if order.is_a? IB::Order
259
+ order.modify
260
+ else
261
+ error "No suitable IB::Order provided/detected. Instead: #{order.inspect}"
262
+ end
263
+ end
264
+
265
+ alias modify modify_order
266
+
267
+ # Preview
268
+ #
269
+ # Submits a "WhatIf" Order
270
+ #
271
+ # Returns the presubmitted order record, where the local_id is erased and the what_if-flag is false
272
+ #
273
+ #
274
+ # output of the results:
275
+ # u = Connection.current.clients.last
276
+ # o = Limit.order ...
277
+ # c = Contract.new ...
278
+ # preview = u.preview contract: c, order: o
279
+ # puts preview.order_state.forcast
280
+ #
281
+ # The returned order can be used as argument for a subsequent order-placement
282
+ # i.e
283
+ # fits_margin_minium = ->(x) do
284
+ # buffer = x[equity_with_loan] - x[:init_margin]
285
+ # net_liquidation = account_data_scan( /NetLiq/ ).first.value.to_i
286
+ # buffer > net_liquidation * 0.1 # 90 Percent margin usage is aceptable
287
+ # end
288
+ # u.preview( contract: c, order: o )
289
+ # .check_margin(u){|y| fits_margin_minumum[ y.order_state.forcast ] } &.place
290
+ #
291
+ #
292
+ # The order received from the TWS is also kept in account.orders
293
+ #
294
+ # Raises IB::SymbolError if the Order could not be placed properly
295
+ #
296
+ def preview order:, contract: nil, **args_which_are_ignored
297
+ # to_do: use a copy of order instead of temporary setting order.what_if
298
+ q = Queue.new
299
+ ib = IB::Connection.current
300
+ contract = order.contract if contract.nil?
301
+ order = order.then{|x| x.contract = nil; x }
302
+ the_local_id = nil
303
+ # put the order into the queue (and exit) if the event is fired
304
+ req = ib.subscribe( :OpenOrder ) do |m|
305
+ q << m.order if m.order.local_id.to_i == the_local_id.to_i && !m.order.order_state.init_margin_after.nil?
306
+ end
307
+
308
+ order.what_if = true
309
+ order.account = account
310
+ the_local_id = ib.place_order order, contract
311
+ Thread.new{ sleep 2 ; q.close } # wait max 2 sec.
312
+ returned_order = q.pop
313
+ ib.unsubscribe req
314
+ # order.what_if = false # reset what_if flag
315
+ # order.local_id = nil # reset local_id to enable re-using the order-object for placing
316
+ raise IB::SymbolError,"(Preview-) #{order.to_human} is not transmitted properly" if q.closed?
317
+ #order.order_state.forcast # return_value
318
+ returned_order.local_id = nil
319
+ returned_order.what_if = false
320
+ returned_order.contract = contract
321
+ returned_order
322
+ end
323
+
324
+
325
+ # closes the contract by submitting an appropriate order
326
+ # the action- and total_amount attributes of the assigned order are overwritten.
327
+ #
328
+ # if a ratio-value (0 ..1) is specified in _order.total_quantity_ only a fraction of the position is closed.
329
+ # Other values are silently ignored
330
+ #
331
+ # if _reverse_ is specified, the opposite position is established.
332
+ # Any value in total_quantity is overwritten
333
+ #
334
+ # returns the order transmitted
335
+ #v # raises an IB::Error if no PortfolioValues have been loaded to the IB::Account
336
+ def close order:, contract: nil, reverse: false, **args_which_are_ignored
337
+ error "must only be called after initializing portfolio_values " if portfolio_values.blank?
338
+ contract_size = ->(c) do # note: portfolio_value.position is either positiv or negativ
339
+ if c.con_id <0 # Spread
340
+ p = portfolio_values.detect{|p| p.contract.con_id ==c.legs.first.con_id} &.position.to_i
341
+ p/ c.combo_legs.first.weight unless p.to_i.zero?
342
+ else
343
+ portfolio_values.detect{|x| x.contract.con_id == c.con_id} &.position.to_i # nil.to_i -->0
344
+ end
345
+ end
346
+
347
+ order.contract = contract.verify.first unless contract.nil? || contract.con_id.to_i <=0
348
+ error "Cannot transmit the order – No Contract given " unless order.contract.is_a?( IB::Contract )
349
+
350
+ the_quantity = if reverse
351
+ -contract_size[order.contract] * 2
352
+ elsif order.total_quantity.abs < 1 && !order.total_quantity.zero?
353
+ -contract_size[order.contract] * order.total_quantity.abs
354
+ else
355
+ -contract_size[order.contract]
356
+ end
357
+ if the_quantity.zero?
358
+ logger.info{ "Cannot close #{order.contract.to_human} - no position detected"}
359
+ else
360
+ order.total_quantity = the_quantity
361
+ order.action = nil
362
+ order.local_id = nil # in any case, close is a new order
363
+ logger.info { "Order modified to close, reduce or revese position: #{order.to_human}" }
364
+ place order: order, convert_size: true
365
+ end
366
+ end
367
+
368
+ # just a wrapper to the Gateway-cancel-order method
369
+ def cancel order:
370
+ Connection.current.cancel_order order.local_id
371
+ end
372
+
373
+ ## ToDo ... needs adaption !
374
+ #returns an hash where portfolio_positions are grouped into Watchlists.
375
+ #
376
+ # Watchlist => [ contract => [ portfoliopositon] , ... ] ]
377
+ #
378
+ def organize_portfolio_positions the_watchlistsi #= IB::Gateway.current.active_watchlists
379
+ the_watchlists = [ the_watchlists ] unless the_watchlists.is_a?(Array)
380
+ self.focuses = portfolio_values.map do | pw | # iterate over pw
381
+ ref_con_id = pw.contract.con_id
382
+ z = the_watchlists.map do | w | # iterate over w and assign to z
383
+ watchlist_contract = w.find do |c| # iterate over c
384
+ if c.is_a? IB::Bag
385
+ c.combo_legs.map( &:con_id ).include?( ref_con_id )
386
+ else
387
+ c.con_id == ref_con_id
388
+ end
389
+ end rescue nil
390
+ watchlist_contract.present? ? [w,watchlist_contract] : nil
391
+ end.compact
392
+
393
+ z.empty? ? [ IB::Symbols::Unspecified, pw.contract, pw ] : z.first + pw
394
+ end.group_by{|a,_,_| a }.map{|x,y|[x, y.map{|_,d,e|[d,e]}.group_by{|e,_| e}.map{|f,z| [f, z.map(&:last)]} ] }.to_h
395
+ # group:by --> [a,b,c] .group_by {|_g,_| g} --->{ a => [a,b,c] }
396
+ # group_by+map --> removes "a" from the resulting array
397
+ end
398
+
399
+
400
+ def locate_contract con_id
401
+ contracts.detect{|x| x.con_id.to_i == con_id.to_i }
402
+ end
403
+
404
+ ## returns the contract definition of an complex portfolio-position detected in the account
405
+ def complex_position con_id
406
+ con_id = con_id.con_id if con_id.is_a?(IB::Contract)
407
+ focuses.map{|x,y| y.detect{|x,y| x.con_id.to_i== con_id.to_i} }.compact.flatten.first
408
+ end
409
+ end # module Advanced
410
+ ##
411
+ # in the console (call gateway with watchlist: [:Spreads, :BuyAndHold])
412
+ #head :001 > .clients.first.focuses.to_a.to_human
413
+ #Unspecified
414
+ #<Stock: BLUE EUR SBF>
415
+ #<PortfolioValue: DU167348 Pos=720 @ 15.88;Value=11433.24;PNL=-4870.05 unrealized;<Stock: BLUE EUR SBF>
416
+ #<Stock: CSCO USD NASDAQ>
417
+ #<PortfolioValue: DU167348 Pos=44 @ 44.4;Value=1953.6;PNL=1009.8 unrealized;<Stock: CSCO USD NASDAQ>
418
+ #<Stock: DBB USD ARCA>
419
+ #<PortfolioValue: DU167348 Pos=-1 @ 16.575;Value=-16.58;PNL=1.05 unrealized;<Stock: DBB USD ARCA>
420
+ #<Stock: NEU USD NYSE>
421
+ #<PortfolioValue: DU167348 Pos=1 @ 375.617;Value=375.62;PNL=98.63 unrealized;<Stock: NEU USD NYSE>
422
+ #<Stock: WFC USD NYSE>
423
+ #<PortfolioValue: DU167348 Pos=100 @ 51.25;Value=5125.0;PNL=-171.0 unrealized;<Stock: WFC USD NYSE>
424
+ #BuyAndHold
425
+ #<Stock: CIEN USD NYSE>
426
+ #<PortfolioValue: DU167348 Pos=812 @ 29.637;Value=24065.57;PNL=4841.47 unrealized;<Stock: CIEN USD NYSE>
427
+ #<Stock: J36 USD SGX>
428
+ #<PortfolioValue: DU167348 Pos=100 @ 56.245;Value=5624.5;PNL=-830.66 unrealized;<Stock: J36 USD SGX>
429
+ #Spreads
430
+ #<Strangle Estx50(3200.0,3000.0)[Dec 2018]>
431
+ #<PortfolioValue: DU167348 Pos=-3 @ 168.933;Value=-5067.99;PNL=603.51 unrealized;<Option: ESTX50 20181221 call 3000.0 EUR>
432
+ #<PortfolioValue: DU167348 Pos=-3 @ 142.574;Value=-4277.22;PNL=-867.72 unrealized;<Option: ESTX50 20181221 put 3200.0 EUR>
433
+ # => nil
434
+ #
435
+ #
436
+ # load managed-accounts first and switch to gateway-mode
437
+ Connection.current.activate_plugin 'managed-accounts'
438
+ Connection.current.activate_plugin 'order-flow'
439
+ class Account
440
+ include Advanced
441
+ end
442
+ end ## module IB
@@ -0,0 +1,125 @@
1
+ module IB
2
+ class Alert
3
+ =begin
4
+ The Singleton IB::Alert handles any response to IB:Messages::Incomming:Alert
5
+
6
+ Individual methods can be defined as well as methods responding to a group of error-codes.
7
+ The default-behavior is defined in the method_missing-method. This just logs the object at the debug level.
8
+
9
+ Default-wrappers to completely ignore the error-message (ignore_alert)
10
+ and to log the object in a different log-level (log_alert_in [warn,info,error] ) are defined in base_alert.
11
+
12
+ Just add
13
+ ```
14
+ module IB
15
+ class Alert
16
+ log_alert_in_warn {list of codennumbers}
17
+ end
18
+ end
19
+ ```
20
+ to your code
21
+
22
+
23
+ IB::Gateway calls the methods in response of subscribing to the :Alert signal by calling
24
+ IB::Alert.send("alert_#{msg.code}", msg )
25
+
26
+ To define a response to the code 134 ( Modify order failed) a method like
27
+ module IB
28
+ class Alert
29
+ def self.alert_134 msg
30
+ (your code)
31
+ end
32
+ end
33
+ end
34
+ has to be written.
35
+
36
+ Important: The class is accessed asynchronically. Be careful while raising interrupts.
37
+
38
+ =end
39
+
40
+ # acts as prototype for any generated method
41
+ #require 'active_support'
42
+
43
+
44
+ def self.method_missing( method_id, msg , *args, &block )
45
+ if msg.is_a? IB::Messages::Incoming::Alert
46
+ # IB::Connection.logger.debug { msg.to_human }
47
+ else
48
+ IB::Connection.logger.error { "Argument to IB::Alert is not a IB::Messages::Incoming::Alert" }
49
+ IB::Connection.logger.error { "The object: #{msg.inspect} " }
50
+ end
51
+ rescue NoMethodError
52
+ unless IB::Connection.logger.nil?
53
+ IB::Connection.logger.error { "The Argument is not a valid IB::Messages:Incoming::Alert object"}
54
+ IB::Connection.logger.error { "The object: #{msg.inspect} " }
55
+ else
56
+ puts "No Logging-Device specified"
57
+ puts "The object: #{msg.inspect} "
58
+ end
59
+ end
60
+
61
+
62
+
63
+ class << self
64
+
65
+ def ignore_alert *codes
66
+ codes.each do |n|
67
+ class_eval <<-EOD
68
+ def self.alert_#{n} msg
69
+ # even the log_debug entry is suppressed
70
+ end
71
+ EOD
72
+ end
73
+ end
74
+ def log_alert_in_info *codes
75
+ codes.each do |n|
76
+ class_eval <<-EOD
77
+ def self.alert_#{n} msg
78
+ # IB::Connection.logger.info { msg.to_human }
79
+ end
80
+ EOD
81
+ end
82
+ end
83
+ def log_alert_in_warn *codes
84
+ codes.each do |n|
85
+ class_eval <<-EOD
86
+ def self.alert_#{n} msg
87
+ # IB::Connection.logger.warn { msg.to_human }
88
+ end
89
+ EOD
90
+ end
91
+ end
92
+
93
+ def log_alert_in_error *codes
94
+ codes.each do |n|
95
+ class_eval <<-EOD
96
+ def self.alert_#{n} msg
97
+ if msg.error_id.present? && msg.error_id > 0
98
+ # IB::Connection.logger.error { msg.message + ' id: ' + msg.error_id.to_s }
99
+ else
100
+ # IB::Connection.logger.error { msg.message }
101
+ end
102
+ end
103
+ EOD
104
+ end
105
+ end
106
+ end
107
+
108
+ ignore_alert 200 , # is handled by IB::Contract.update_contract
109
+ 2100, # API client has been unsubscribed from account data
110
+ 2105,
111
+ 399 # your order will not be placed at the exchange until
112
+
113
+ log_alert_in_info 1102 #Connectivity between IB and Trader Workstation has been restored
114
+
115
+
116
+ log_alert_in_error 320, 321, 323, 324, #ServerError
117
+ ## 110, # The price does not conform to the minimum price variation
118
+ # 103, #duplicate order ## order-alerts
119
+ # 201, #deleted objecta ## order-alerts
120
+ 326 #Unable connect as the client id is already in use
121
+
122
+ log_alert_in_warn 354 #Requested market data is not subscribed
123
+
124
+ end
125
+ end
@@ -0,0 +1,15 @@
1
+ # These Alerts are always active
2
+ module IB
3
+ class Alert
4
+
5
+ def self.alert_2102 msg
6
+ # Connectivity between IB and Trader Workstation has been restored - data maintained.
7
+ sleep 0.1 # no need to wait too long.
8
+ if IB::Gateway.current.check_connection
9
+ IB::Gateway.logger.debug { "Alert 2102: Connection stable" }
10
+ else
11
+ IB::Gateway.current.reconnect
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,73 @@
1
+ module IB
2
+ class Alert
3
+
4
+ def self.alert_388 msg
5
+ # Order size x is smaller than the minimum required size of yy.
6
+ IB::Gateway.logger.error msg.inspect
7
+ # error msg, :order, nil
8
+ end
9
+ def self.alert_202 msg
10
+ # do anything in a secure mutex-synchronized-environment
11
+ any_order = IB::Gateway.current.account_data do | account |
12
+ order= account.locate_order( local_id: msg.error_id )
13
+ if order.present? && ( order.order_state.status != 'Cancelled' )
14
+ order.order_states.update_or_create( IB::OrderState.new( status: 'Cancelled',
15
+ perm_id: order.perm_id,
16
+ local_id: order.local_id ) ,
17
+ :status )
18
+
19
+ end
20
+ order # return_value
21
+ end
22
+ if any_order.compact.empty?
23
+ IB::Gateway.logger.error{"Alert 202: The deleted order was not registered: local_id #{msg.error_id}"}
24
+ end
25
+
26
+ end
27
+
28
+
29
+ class << self
30
+ =begin
31
+ IB::Alert#AddOrderstateAlert
32
+
33
+ The OrderState-Record is used to record the history of the order.
34
+ If selected Alert-Messages appear, they are added to the Order.order_state-Array.
35
+ The last Status is available as Order.order_state, all states are accessible by Order.order_states
36
+
37
+ The TWS-Message-text is stored to the »warning-text«-field.
38
+ The Status is always »rejected«.
39
+ If the first OrderState-object of a Order is »rejected«, the order is not placed at all.
40
+ Otherwise only the last action is not applied and the order is unchanged.
41
+
42
+ =end
43
+ def add_orderstate_alert *codes
44
+ codes.each do |n|
45
+ class_eval <<-EOD
46
+ def self.alert_#{n} msg
47
+
48
+ if msg.error_id.present?
49
+ IB::Gateway.current.account_data do | account |
50
+ order= account.locate_order( local_id: msg.error_id )
51
+ if order.present? && ( order.order_state.status != 'Rejected' )
52
+ order.order_states.update_or_create( IB::OrderState.new( status: 'Rejected' ,
53
+ perm_id: order.perm_id,
54
+ warning_text: '#{n}: '+ msg.message,
55
+ local_id: msg.error_id ), :status )
56
+
57
+ IB::Gateway.logger.error{ msg.to_human }
58
+ end # order present?
59
+ end # mutex-environment
60
+ end # branch
61
+ end # def
62
+ EOD
63
+ end # loop
64
+ end # def
65
+ end
66
+ add_orderstate_alert 103, # duplicate order
67
+ 201, # deleted object
68
+ 105, # Order being modified does not match original order
69
+ 462, # Cannot change to the new Time in Force:GTD
70
+ 329, # Cannot change to the new order type:STP
71
+ 10147 # OrderId 0 that needs to be cancelled is not found.
72
+ end # class Alert
73
+ end # module IB
File without changes