ib-extensions 1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+