ib-extensions 1.0

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 (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +6 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +9 -0
  7. data/Gemfile.lock +112 -0
  8. data/Guardfile +24 -0
  9. data/README.md +99 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +96 -0
  12. data/bin/console.yml +3 -0
  13. data/bin/gateway.rb +97 -0
  14. data/bin/setup +8 -0
  15. data/changelog.md +31 -0
  16. data/examples/cancel_orders +74 -0
  17. data/examples/eod +35 -0
  18. data/examples/input.rb +475 -0
  19. data/examples/market_price +57 -0
  20. data/examples/option_chain +67 -0
  21. data/examples/place_and_modify_order +162 -0
  22. data/examples/place_bracket_order +62 -0
  23. data/examples/place_butterfly_order +104 -0
  24. data/examples/place_combo_order +70 -0
  25. data/examples/place_limit_order +82 -0
  26. data/examples/place_the_limit_order +145 -0
  27. data/examples/volatility_research +139 -0
  28. data/examples/what_if_order +90 -0
  29. data/ib-extensions.gemspec +37 -0
  30. data/lib/ib-gateway.rb +5 -0
  31. data/lib/ib/alerts/base-alert.rb +128 -0
  32. data/lib/ib/alerts/gateway-alerts.rb +15 -0
  33. data/lib/ib/alerts/order-alerts.rb +68 -0
  34. data/lib/ib/eod.rb +152 -0
  35. data/lib/ib/extensions.rb +9 -0
  36. data/lib/ib/extensions/contract.rb +37 -0
  37. data/lib/ib/extensions/version.rb +5 -0
  38. data/lib/ib/flex.rb +150 -0
  39. data/lib/ib/gateway.rb +425 -0
  40. data/lib/ib/gateway/account-infos.rb +115 -0
  41. data/lib/ib/gateway/order-handling.rb +150 -0
  42. data/lib/ib/market-price.rb +134 -0
  43. data/lib/ib/models/account.rb +329 -0
  44. data/lib/ib/models/spread.rb +159 -0
  45. data/lib/ib/option-chain.rb +198 -0
  46. data/lib/ib/option-greeks.rb +88 -0
  47. data/lib/ib/order-prototypes.rb +110 -0
  48. data/lib/ib/order_prototypes/abstract.rb +67 -0
  49. data/lib/ib/order_prototypes/combo.rb +46 -0
  50. data/lib/ib/order_prototypes/forex.rb +40 -0
  51. data/lib/ib/order_prototypes/limit.rb +177 -0
  52. data/lib/ib/order_prototypes/market.rb +116 -0
  53. data/lib/ib/order_prototypes/pegged.rb +173 -0
  54. data/lib/ib/order_prototypes/premarket.rb +31 -0
  55. data/lib/ib/order_prototypes/stop.rb +202 -0
  56. data/lib/ib/order_prototypes/volatility.rb +39 -0
  57. data/lib/ib/spread-prototypes.rb +62 -0
  58. data/lib/ib/spread_prototypes/butterfly.rb +79 -0
  59. data/lib/ib/spread_prototypes/calendar.rb +85 -0
  60. data/lib/ib/spread_prototypes/stock-spread.rb +48 -0
  61. data/lib/ib/spread_prototypes/straddle.rb +75 -0
  62. data/lib/ib/spread_prototypes/strangle.rb +96 -0
  63. data/lib/ib/spread_prototypes/vertical.rb +84 -0
  64. data/lib/ib/verify.rb +226 -0
  65. metadata +206 -0
@@ -0,0 +1,5 @@
1
+ module IB
2
+ module Extensions
3
+ VERSION = "1.0"
4
+ end
5
+ end
@@ -0,0 +1,150 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ #require 'xmlsimple'
4
+ require 'ox'
5
+ module Ox
6
+ class Element
7
+ def ox
8
+ nodes.first
9
+ end
10
+ end
11
+ end
12
+
13
+ module IB
14
+
15
+ # FLEX is a web-based service from IB that helps you to retrieve your activity,
16
+ # trades and positions. It is working independently from TWS or Gateway, using your
17
+ # internet connection directly. See /misc/flex for extended FLEX documentation.
18
+ #
19
+ # In order to use this service, activate it and configure your token first.
20
+ # Your Token is located at Account Management->Reports->Delivery Settings->Flex Web Service.
21
+ # You need to activate Flex Web Service and generate new token(s) there.
22
+ # Your Flex Query Ids are in Account Management->Reports->Activity->Flex Queries.
23
+ # Create new Flex query and make sure to set its output format to XML.
24
+ #
25
+ # IB::Flex object incapsulates a single pre-defined Flex query.
26
+ #
27
+ #Details:
28
+ #
29
+ # https://www.interactivebrokers.com/en/software/am/am/reports/flex_web_service_version_3.htm
30
+ #
31
+ # Error Codes
32
+ # https://www.interactivebrokers.com/en/software/am/am/reports/version_3_error_codes.htm
33
+ class Flex
34
+
35
+ mattr_accessor :token, :uri
36
+ # Create new Flex query with options:
37
+ # :token => 1111111111111111111111111111111111 # CHANGE to your actual token!
38
+ # :query_id => 11111 # CHANGE to actual query id!
39
+ # :format => :xml (default) / :csv
40
+ # :verbose => true / false (default)
41
+ # :return_hash => true / false (default)
42
+ def initialize query_id: nil , # must be specified
43
+ token: nil , # must be specified once
44
+ format: :xml,# or :csv
45
+ verbose: false,
46
+ return_hash: false
47
+
48
+ # By default, uri is a well known FLEX Web Service URI
49
+ self.uri =
50
+ 'https://gdcdyn.interactivebrokers.com/Universal/servlet/FlexStatementService.SendRequest'
51
+
52
+ # convert parameters into instance-variables and assign them
53
+ method(__method__).parameters.each do |type, k|
54
+ next unless type == :key
55
+ case k
56
+ when :token
57
+ self.token = token unless token.nil?
58
+ else
59
+ v = eval(k.to_s)
60
+ instance_variable_set("@#{k}", v) unless v.nil?
61
+ end
62
+ end
63
+
64
+ error "Token not set", :flex if Flex.token.nil?
65
+ error "query_id not set", :flex if @query_id.nil?
66
+ yield self if block_given?
67
+ end
68
+
69
+ # Run a pre-defined Flex query against IB Flex Web Service
70
+ # Returns a (parsed) report or raises FlexError in case of problems
71
+ def run
72
+ # Initiate FLEX request at a known FLEX Web Service URI
73
+ # get_content returns an Ox-Element-Object
74
+ # Nodes > FlexStatementResponse and
75
+ # > if Status is other then 'suscess' ErrorCode and ErrorMessage
76
+ # > otherwise Uri and ReferenceCode
77
+
78
+ statement = get_content( Flex.uri, :t => Flex.token, :q => @query_id, :v => 3)
79
+ if @verbose
80
+ puts "the Statement returned by the request of the query"
81
+ puts Ox.load(Ox.dump( statement), mode: :hash)
82
+ end
83
+ error("Flex Query is invalid", :flex ) unless statement.value == 'FlexStatementResponse'
84
+ error("#{statement.ErrorCode.ox.to_i}: #{statement.ErrorMessage.ox}", :flex) if statement.Status.ox != 'Success'
85
+ #
86
+ # Retrieve the FLEX report
87
+ report = nil
88
+ until report do
89
+ sleep 5 # wait for the report to be prepared
90
+ report = get_content(statement.Url.ox,:t => Flex.token, :q => statement.ReferenceCode.ox, :v => 3,
91
+ :text_ok => @format != :xml)
92
+
93
+ # If Status is specified, returned xml contains only error message, not actual report
94
+ if report.nodes.include?('Status') && report.Status.ox =~ /Fail|Warn/
95
+ error_code = report.ErrorCode.ox.to_i
96
+ error_message = "#{error_code}: #{report.ErrorMessage.ox}"
97
+
98
+ case error_code
99
+ when 1001..1009, 1018, 1019, 1021
100
+ # Report is just not ready yet, wait and retry
101
+ puts error_message if @verbose
102
+ report = nil
103
+ else # Fatal error
104
+ error error_message, :flex
105
+ end
106
+ end
107
+ end
108
+ @return_hash ? Ox.load( Ox.dump(report), mode: :hash) : report # return hash or the Ox-Element
109
+ end
110
+
111
+ # Helper method to get (and parse XML) responses from IB Flex Web Service
112
+ def get_content address, fields
113
+ get = -> ( the_uri ) do
114
+ server = Net::HTTP.new(the_uri.host, the_uri.port)
115
+ server.use_ssl = true
116
+ server.verify_mode = OpenSSL::SSL::VERIFY_NONE if server.use_ssl? # Avoid OpenSSL failures
117
+ server.start{ |http| http.request( Net::HTTP::Get.new(the_uri.request_uri) ) }
118
+ end
119
+
120
+ text_ok = fields.delete(:text_ok)
121
+ the_uri = URI("#{address}?" + fields.map { |k, v| "#{k}=#{URI.encode(v.to_s)}" }.join('&'))
122
+ response = get[ the_uri ]
123
+
124
+ error("URI responded with #{response.code}", :flex) unless response.code.to_i == 200
125
+ if text_ok
126
+ response.body
127
+ else
128
+ Ox.parse response.body
129
+ end
130
+ end
131
+
132
+ # Helper method to get the body of response from IB Flex Web Service
133
+ # def get address, fields
134
+ # uri = URI("#{address}?" + fields.map { |k, v| "#{k}=#{URI.encode(v.to_s)}" }.join('&'))
135
+ # puts "URI #{uri}"
136
+ # server = Net::HTTP.new(uri.host, uri.port)
137
+ # server.use_ssl = (uri.scheme == 'https')
138
+ # server.verify_mode = OpenSSL::SSL::VERIFY_NONE if server.use_ssl? # Avoid OpenSSL failures
139
+
140
+ # resp = server.start do |http|
141
+ # req = Net::HTTP::Get.new(uri.request_uri)
142
+ # http.request(req)
143
+ # end
144
+ # error("URI responded with #{resp.code}", :flex) unless resp.code.to_i == 200
145
+ #
146
+ # resp.body
147
+ # end
148
+
149
+ end
150
+ end
@@ -0,0 +1,425 @@
1
+ #
2
+ require 'ib/gateway/account-infos'
3
+ require 'ib/gateway/order-handling'
4
+ require_relative 'alerts/gateway-alerts'
5
+ require_relative 'alerts/order-alerts'
6
+ require 'active_support/core_ext/module/attribute_accessors' # provides m_accessor
7
+ #module GWSupport
8
+ # provide AR4- ActiveRelation-like-methods to Array-Class
9
+ #refine Array do
10
+ class Array
11
+ # returns the item (in case of first) or the hole array (in case of create)
12
+ def first_or_create item, *condition, &b
13
+ int_array = if condition.empty?
14
+ [ find_all{ |x| x == item } ] if !block_given?
15
+ else
16
+ condition.map{ |c| find_all{|x| x[ c ] == item[ c ] }}
17
+ end || []
18
+ if block_given?
19
+ relation = yield
20
+ part_2 = find_all{ |x| x.send( relation ) == item.send( relation ) }
21
+ int_array << part_2 unless part_2.empty?
22
+ end
23
+ # reduce performs a logical "&" between the array-elements
24
+ # we are only interested in the first entry
25
+ r= int_array.reduce( :& )
26
+ r.present? ? r.first : self.push( item )
27
+ end
28
+ def update_or_create item, *condition, &b
29
+ member = first_or_create( item, *condition, &b)
30
+ self[ index( member ) ] = item unless member.is_a?(Array)
31
+ self # always returns the array
32
+ end
33
+
34
+ # performs [ [ array ] & [ array ] & [..] ].first
35
+ def intercept
36
+ a = self.dup
37
+ s = a.pop
38
+ while a.present?
39
+ s = s & a.pop
40
+ end
41
+ s.first unless s.nil? # return_value (or nil)
42
+ end
43
+ end # refine / class
44
+ #end # module
45
+
46
+ module IB
47
+
48
+ =begin
49
+ The Gateway-Class defines anything which has to be done before a connection can be established.
50
+ The Default Skeleton can easily be substituted by customized actions
51
+
52
+ The IB::Gateway can be used in three modes
53
+ (1) IB::Gateway.new( connect:true, --other arguments-- ) do | gateway |
54
+ ** subscribe to Messages and define the response **
55
+ # This block is executed before a connect-attempt is made
56
+ end
57
+ (2) gw = IB:Gateway.new
58
+ ** subscribe to Messages **
59
+ gw.connect
60
+ (3) IB::Gateway.new connect:true, host: 'localhost' ....
61
+
62
+ Independently IB::Alert.alert_#{nnn} should be defined for a proper response to warnings, error-
63
+ and system-messages.
64
+
65
+
66
+ The Connection to the TWS is realized throught IB::Connection. Additional to __IB::Connection.current__
67
+ IB::Gateway.tws points to the active Connection.
68
+
69
+ To support asynchronic access, the :recieved-Array of the Connection-Class is not active.
70
+ The Array is easily confused, if used in production mode with a FA-Account and has limits.
71
+ Thus IB::Conncetion.wait_for(message) is not available until the programm is called with
72
+ IB::Gateway.new serial_array: true (, ...)
73
+
74
+
75
+
76
+ =end
77
+
78
+ class Gateway
79
+
80
+ include LogDev # provides default_logger
81
+ include AccountInfos # provides Handling of Account-Data provided by the tws
82
+ include OrderHandling
83
+
84
+ # include GWSupport # introduces update_or_create, first_or_create and intercept to the Array-Class
85
+
86
+ # from active-support. Add Logging at Class + Instance-Level
87
+ mattr_accessor :logger
88
+ # similar to the Connection-Class: current represents the active instance of Gateway
89
+ mattr_accessor :current
90
+ mattr_accessor :tws
91
+
92
+
93
+
94
+ def initialize port: 4002, # 7497,
95
+ host: '127.0.0.1', # 'localhost:4001' is also accepted
96
+ client_id: random_id,
97
+ subscribe_managed_accounts: true,
98
+ subscribe_alerts: true,
99
+ subscribe_order_messages: true,
100
+ connect: true,
101
+ get_account_data: false,
102
+ serial_array: false,
103
+ logger: default_logger,
104
+ watchlists: [] , # array of watchlists (IB::Symbols::{watchlist}) containing descriptions for complex positions
105
+ **other_agruments_which_are_ignored,
106
+ &b
107
+
108
+ host, port = (host+':'+port.to_s).split(':')
109
+
110
+ self.logger = logger
111
+ logger.info { '-' * 20 +' initialize ' + '-' * 20 }
112
+ logger.tap{|l| l.progname = 'Gateway#Initialize' }
113
+
114
+ @connection_parameter = { received: serial_array, port: port, host: host, connect: false, logger: logger, client_id: client_id }
115
+
116
+ @account_lock = Mutex.new
117
+ @watchlists = watchlists
118
+ @gateway_parameter = { s_m_a: subscribe_managed_accounts,
119
+ s_a: subscribe_alerts,
120
+ s_o_m: subscribe_order_messages,
121
+ g_a_d: get_account_data }
122
+
123
+
124
+ Thread.report_on_exception = true
125
+ # https://blog.bigbinary.com/2018/04/18/ruby-2-5-enables-thread-report_on_exception-by-default.html
126
+ Gateway.current = self
127
+ # establish Alert-framework
128
+ IB::Alert.logger = logger
129
+ # initialise Connection without connecting
130
+ prepare_connection &b
131
+ # finally connect to the tws
132
+ connect = true if get_account_data
133
+ if connect
134
+ if connect(100) # tries to connect for about 2h
135
+ get_account_data(watchlists: watchlists.map{|b| IB::Symbols.allocate_collection b}) if get_account_data
136
+ # request_open_orders() if request_open_orders || get_account_data
137
+ else
138
+ @accounts = [] # definitivley reset @accounts
139
+ end
140
+ end
141
+
142
+ end
143
+
144
+ def active_watchlists
145
+ @watchlists
146
+ end
147
+
148
+ def get_host
149
+ "#{@connection_parameter[:host]}: #{@connection_parameter[:port] }"
150
+ end
151
+
152
+ def update_local_order order
153
+ # @local_orders is initialized by #PrepareConnection
154
+ @local_orders.update_or_create order, :local_id
155
+ end
156
+
157
+
158
+ ## ------------------------------------- connect ---------------------------------------------##
159
+ =begin
160
+ Zentrale Methode
161
+ Es wird ein Connection-Objekt (IB::Connection.current) angelegt.
162
+ Sollte keine TWS vorhanden sein, wird eine entsprechende Meldung ausgegeben und der Verbindungsversuch
163
+ wiederholt.
164
+ Weiterhin meldet sich die Anwendung zur Auswertung von Messages der TWS an.
165
+
166
+ =end
167
+ def connect maximal_count_of_retry=100
168
+
169
+ i= -1
170
+ logger.progname = 'Gateway#connect'
171
+ begin
172
+ tws.connect
173
+ rescue Errno::ECONNREFUSED => e
174
+ i+=1
175
+ if i < maximal_count_of_retry
176
+ if i.zero?
177
+ logger.info 'No TWS!'
178
+ else
179
+ logger.info {"No TWS Retry #{i}/ #{maximal_count_of_retry} " }
180
+ end
181
+ sleep i<50 ? 10 : 60 # Die ersten 50 Versuche im 10 Sekunden Abstand, danach 1 Min.
182
+ retry
183
+ else
184
+ logger.info { "Giving up!!" }
185
+ return false
186
+ end
187
+ rescue Errno::EHOSTUNREACH => e
188
+ logger.error 'Cannot connect to specified host'
189
+ logger.error e
190
+ return false
191
+ rescue SocketError => e
192
+ logger.error 'Wrong Adress, connection not possible'
193
+ return false
194
+ end
195
+
196
+ tws.start_reader
197
+ # let NextValidId-Event appear
198
+ (1..30).each do |r|
199
+ break if tws.next_local_id.present?
200
+ sleep 0.1
201
+ if r == 30
202
+ error "Connected, NextLocalId is not initialized. Repeat with another client_id"
203
+ end
204
+ end
205
+ # initialize @accounts (incl. aliases)
206
+ tws.send_message :RequestFA, fa_data_type: 3
207
+ logger.debug { "Communications successfully established" }
208
+ end # def
209
+
210
+
211
+
212
+
213
+
214
+ def reconnect
215
+ logger.progname = 'Gateway#reconnect'
216
+ if tws.present?
217
+ disconnect
218
+ sleep 1
219
+ end
220
+ logger.info "trying to reconnect ..."
221
+ connect
222
+ end
223
+
224
+ def disconnect
225
+ logger.progname = 'Gateway#disconnect'
226
+
227
+ tws.disconnect if tws.present?
228
+ @accounts = [] # each{|y| y.update_attribute :connected, false }
229
+ logger.info "Connection closed"
230
+ end
231
+
232
+
233
+ =begin
234
+ Proxy for Connection#SendMessage
235
+ allows reconnection if a socket_error occurs
236
+
237
+ checks the connection before sending a message.
238
+
239
+ =end
240
+
241
+ def send_message what, *args
242
+ logger.tap{|l| l.progname = 'Gateway#SendMessage' }
243
+ begin
244
+ if check_connection
245
+ tws.send_message what, *args
246
+ else
247
+ error( "Connection lost. Could not send message #{what}" )
248
+ end
249
+ end
250
+ end
251
+
252
+ =begin
253
+ Cancels one or multible orders
254
+
255
+ Argument is either an order-object or a local_id
256
+
257
+ =end
258
+
259
+ def cancel_order *orders
260
+
261
+ logger.tap{|l| l.progname = 'Gateway#CancelOrder' }
262
+
263
+ orders.compact.each do |o|
264
+ local_id = if o.is_a? (IB::Order)
265
+ logger.info{ "Cancelling #{o.to_human}" }
266
+ o.local_id
267
+ else
268
+ o
269
+ end
270
+ send_message :CancelOrder, :local_id => local_id.to_i
271
+ end
272
+
273
+ end
274
+
275
+ =begin
276
+ clients returns a list of Account-Objects
277
+
278
+ If only one Account is present, Client and Advisor are identical.
279
+
280
+ =end
281
+ def clients
282
+ @accounts.find_all &:user?
283
+ end
284
+ =begin
285
+ The Advisor is always the first account
286
+ =end
287
+ def advisor
288
+ @accounts.first
289
+ end
290
+
291
+ =begin
292
+ account_data provides a thread-safe access to linked content of accounts
293
+
294
+ (AccountValues, Portfolio-Values, Contracts and Orders)
295
+
296
+ It returns an Array of the return-values of the block
297
+
298
+ If called without a parameter, all clients are accessed
299
+ =end
300
+
301
+ def account_data account_or_id=nil
302
+
303
+ safe = ->(account) do
304
+ @account_lock.synchronize do
305
+ yield account
306
+ end
307
+ end
308
+
309
+ if block_given?
310
+ if account_or_id.present?
311
+ sa = account_or_id.is_a?(IB::Account) ? account_or_id : @accounts.detect{|x| x.account == account_or_id }
312
+ safe[sa] if sa.is_a? IB::Account
313
+ else
314
+ clients.map{|sa| safe[sa]}
315
+ end
316
+ end
317
+ end
318
+
319
+
320
+ private
321
+
322
+ def random_id
323
+ rand 99999
324
+ end
325
+
326
+
327
+
328
+ def prepare_connection &b
329
+ tws.disconnect if tws.is_a? IB::Connection
330
+ self.tws = IB::Connection.new @connection_parameter
331
+ @accounts = @local_orders = Array.new
332
+
333
+ # prepare Advisor-User hierachy
334
+ initialize_managed_accounts if @gateway_parameter[:s_m_a]
335
+ initialize_alerts if @gateway_parameter[:s_a]
336
+ initialize_order_handling if @gateway_parameter[:s_o_m] || @gateway_parameter[:g_a_d]
337
+ ## apply other initialisations which should apper before the connection as block
338
+ ## i.e. after connection order-state events are fired if an open-order is pending
339
+ ## a possible response is best defined before the connect-attempt is done
340
+ # ## Attention
341
+ # ## @accounts are not initialized yet (empty array)
342
+ if block_given?
343
+ yield self
344
+
345
+ end
346
+ end
347
+
348
+ =begin
349
+ InitializeManagedAccounts
350
+ defines the Message-Handler for :ManagedAccounts
351
+ Its always active.
352
+ =end
353
+
354
+ def initialize_managed_accounts
355
+ rec_id = tws.subscribe( :ReceiveFA ) do |msg|
356
+ msg.accounts.each do |a|
357
+ account_data( a.account ){| the_account | the_account.update_attribute :alias, a.alias } unless a.alias.blank?
358
+ end
359
+ logger.info { "Accounts initialized \n #{@accounts.map( &:to_human ).join " \n " }" }
360
+ end
361
+
362
+ man_id = tws.subscribe( :ManagedAccounts ) do |msg|
363
+ logger.progname = 'Gateway#InitializeManagedAccounts'
364
+ if @accounts.empty?
365
+ # just validate the message and put all together into an array
366
+ @accounts = msg.accounts_list.split(',').map do |a|
367
+ account = IB::Account.new( account: a.upcase , connected: true )
368
+ end
369
+ else
370
+ logger.info {"already #{@accounts.size} accounts initialized "}
371
+ @accounts.each{|x| x.update_attribute :connected , true }
372
+ end # if
373
+ end # subscribe do
374
+ end # def
375
+
376
+
377
+ def initialize_alerts
378
+
379
+ tws.subscribe( :AccountUpdateTime ){| msg | logger.debug{ msg.to_human }}
380
+ tws.subscribe(:Alert) do |msg|
381
+ logger.progname = 'Gateway#Alerts'
382
+ logger.debug " ----------------#{msg.code}-----"
383
+ # delegate anything to IB::Alert
384
+ IB::Alert.send("alert_#{msg.code}", msg )
385
+ end
386
+ end
387
+
388
+
389
+ # Handy method to ensure that a connection is established and active.
390
+ #
391
+ # The connection is reset on the IB-side at least once a day. Then the
392
+ # IB-Ruby-Connection has to be reestablished, too.
393
+ #
394
+ # check_connection reconnects if necessary and returns false if the connection is lost.
395
+ #
396
+ # It delays the process by 6 ms (150 MBit Cable connection)
397
+ #
398
+ # a = Time.now; G.check_connection; b= Time.now ;b-a
399
+ # => 0.00066005
400
+ #
401
+ def check_connection
402
+ answer = nil; count=0
403
+ z= tws.subscribe( :CurrentTime ) { answer = true }
404
+ while (answer.nil?)
405
+ begin
406
+ tws.send_message(:RequestCurrentTime) # 10 ms ##
407
+ i=0; loop{ break if answer || i > 40; i+=1; sleep 0.0001}
408
+ rescue IOError, Errno::ECONNREFUSED # connection lost
409
+ count = 6
410
+ rescue IB::Error # not connected
411
+ reconnect
412
+ count +=1
413
+ sleep 1
414
+ retry if count <= 5
415
+ end
416
+ count +=1
417
+ break if count > 5
418
+ end
419
+ tws.unsubscribe z
420
+ count < 5 && answer # return value
421
+ end
422
+ end # class
423
+
424
+ end # module
425
+