Class: IB::Gateway

Inherits:
Object
  • Object
show all
Includes:
AccountInfos, OrderHandling, Support::Logging
Defined in:
lib/ib/gateway.rb

Overview

The Gateway-Class defines anything which has to be done before a connection can be established. The Default Skeleton can easily be substituted by customized actions

The IB::Gateway can be used in three modes (1) IB::Gateway.new( connect:true, –other arguments– ) do | gateway | ** subscribe to Messages and define the response ** # This block is executed before a connect-attempt is made end (2) gw = IB:Gateway.new ** subscribe to Messages ** gw.connect (3) IB::Gateway.new connect:true, host: ‘localhost’ .…

Independently IB::Alert.alert_#nnn should be defined for a proper response to warnings, error- and system-messages.

The Connection to the TWS is realized throught IB::Connection. Additional to IB::Connection.current IB::Gateway.tws points to the active Connection.

To support asynchronic access, the :recieved-Array of the Connection-Class is not active. The Array is easily confused, if used in production mode with a FA-Account and has limits. Thus IB::Conncetion.wait_for(message) is not available until the programm is called with IB::Gateway.new serial_array: true (, …)

Instance Method Summary collapse

Methods included from OrderHandling

#initialize_order_handling, #request_open_orders, #update_order_dependent_object

Methods included from AccountInfos

#all_contracts, #get_account_data

Constructor Details

#initialize(port: 4002, host: '127.0.0.1', client_id: random_id, subscribe_managed_accounts: true, subscribe_alerts: true, subscribe_order_messages: true, connect: true, get_account_data: false, serial_array: false, logger: nil, watchlists: [], **other_agruments_which_are_ignored, &b) ⇒ Gateway

7497,



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/ib/gateway.rb', line 93

def initialize  port: 4002, # 7497,
  host: '127.0.0.1',   # 'localhost:4001' is also accepted
  client_id:  random_id,
  subscribe_managed_accounts: true,
  subscribe_alerts: true,
  subscribe_order_messages: true,
  connect: true,
  get_account_data: false,
  serial_array: false,
    logger: nil, 
  watchlists: [] ,  # array of watchlists (IB::Symbols::{watchlist}) containing descriptions for complex positions
  **other_agruments_which_are_ignored,
  &b

  host, port = (host+':'+port.to_s).split(':')

    self.class.configure_logger logger

    self.logger.info { '-' * 20 +' initialize ' + '-' * 20 }

  @connection_parameter = { received: serial_array, port: port, host: host, connect: false, logger: logger, client_id: client_id }

  @account_lock = Mutex.new
    @watchlists = watchlists.map{ |b| IB::Symbols.allocate_collection b }
  @gateway_parameter = { s_m_a: subscribe_managed_accounts,
                      s_a: subscribe_alerts,
                      s_o_m: subscribe_order_messages,
                      g_a_d:  }


  Thread.report_on_exception = true
  # https://blog.bigbinary.com/2018/04/18/ruby-2-5-enables-thread-report_on_exception-by-default.html
  Gateway.current = self
  # initialise Connection without connecting
  prepare_connection &b
  # finally connect to the tws
  connect =  true if 

  if connect
    i = 0
    begin
      i+=1
      if connect(100)  # tries to connect for about 2h
        ()
        #    request_open_orders() if request_open_orders || get_account_data
      else
        @accounts = []   # definitivley reset @accounts
      end
    rescue IB::Error => e
      disconnect
      logger.fatal e.message
      if e.message =~ /NextLocalId is not initialized/
        Kernel.exit
      elsif i < 5
        retry
      else
        raise "could not get account data"
      end
    end
  end

end

Instance Method Details

#account_data(account_or_id = nil) ⇒ Object

account_data provides a thread-safe access to linked content of accounts

(AccountValues, Portfolio-Values, Contracts and Orders)

It returns an Array of the return-values of the block

If called without a parameter, all clients are accessed

Example

“‘ g = IB::Gateway.current # thread safe access g.account_data &:portfolio_values

g.account_data &:account_values

# primitive access g.clients.map &:portfolio_values g.clients.map &:account_values

“‘



329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/ib/gateway.rb', line 329

def  =nil

  safe = ->() do
    @account_lock.synchronize do
        yield 
    end
  end

  if block_given?
    if .present?
      sa = .is_a?(IB::) ?  :  @accounts.detect{|x| x. ==  }
      safe[sa] if sa.is_a? IB::
    else
      clients.map{|s| safe[s]}
    end
  end
end

#active_watchlistsObject



156
157
158
# File 'lib/ib/gateway.rb', line 156

def active_watchlists
  @watchlists
end

#add_watchlist(watchlist) ⇒ Object



159
160
161
162
# File 'lib/ib/gateway.rb', line 159

def add_watchlist watchlist
 new_watchlist = IB::Symbols.allocate_collection( watchlist ) 
 @watchlists <<  new_watchlist unless @watchlists.include?( new_watchlist )
end

#advisorObject

The Advisor is always the first account



300
301
302
# File 'lib/ib/gateway.rb', line 300

def advisor
  @accounts.first
end

#cancel_order(*orders) ⇒ Object

Cancels one or multible orders

Argument is either an order-object or a local_id



266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/ib/gateway.rb', line 266

def cancel_order *orders


  orders.compact.each do |o|
    local_id = if o.is_a? (IB::Order)
                 logger.info{ "Cancelling #{o.to_human}" }
                 o.local_id
               else
                 o
               end
    send_message :CancelOrder, :local_id => local_id.to_i
  end

end

#check_connectionObject

Handy method to ensure that a connection is established and active.

The connection is reset on the IB-side at least once a day. Then the IB-Ruby-Connection has to be reestablished, too.

check_connection reconnects if necessary and returns false if the connection is lost.

It delays the process by 6 ms (150 MBit Cable connection)

a =  Time.now; G.check_connection; b= Time.now ;b-a
 => 0.00066005


420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/ib/gateway.rb', line 420

def check_connection
    q =  Queue.new 
    count = 0
    result = nil
    z= tws.subscribe( :CurrentTime ) { q.push true }
  loop do
    begin
      tws.send_message(:RequestCurrentTime)                        # 10 ms  ##
        th = Thread.new{ sleep 1 ; q.push nil }
        result =  q.pop 
        count+=1
        break if result || count > 10
    rescue IOError, Errno::ECONNREFUSED   # connection lost
      count +=1 
        retry
    rescue IB::Error # not connected
      reconnect 
      count = 0
      retry
    end
  end
  tws.unsubscribe z
  result #  return value
end

#clientsObject

clients returns a list of Account-Objects

If only one Account is present, Client and Advisor are identical.



287
288
289
# File 'lib/ib/gateway.rb', line 287

def  clients
  @accounts.find_all &:user?
end

#connect(maximal_count_of_retry = 100) ⇒ Object

Zentrale Methode Es wird ein Connection-Objekt (IB::Connection.current) angelegt. Sollte keine TWS vorhanden sein, wird ein entsprechende Meldung ausgegeben und der Verbindungsversuch wiederholt. Weiterhin meldet sich die Anwendung zur Auswertung von Messages der TWS an.



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/ib/gateway.rb', line 183

def connect maximal_count_of_retry=100

  i= -1
  begin
    tws.connect
  rescue  Errno::ECONNREFUSED => e
    i+=1
    if i < maximal_count_of_retry
      if i.zero?
        logger.info 'No TWS!'
      else
        logger.info {"No TWS        Retry #{i}/ #{maximal_count_of_retry} " }
      end
      sleep i<50 ? 10 : 60   # Die ersten 50 Versuche im 10 Sekunden Abstand, danach 1 Min.
      retry
    else
      logger.info { "Giving up!!" }
      return false
    end
  rescue Errno::EHOSTUNREACH => e
    error "Cannot connect to specified host  #{e}", :reader, true
    return false
  rescue SocketError => e
    error 'Wrong Adress, connection not possible', :reader, true
    return false
    rescue IB::Error => e
      logger.info e
  end

  # initialize @accounts (incl. aliases)
  tws.send_message( :RequestFA, fa_data_type: 3) if fa?
  logger.debug { "Communications successfully established" }
    # update open orders
    request_open_orders if @gateway_parameter[:s_o_m] || @gateway_parameter[:g_a_d]
    true #  return gatway object
end

#disconnectObject



233
234
235
236
237
238
# File 'lib/ib/gateway.rb', line 233

def disconnect

  tws.disconnect if tws.present?
  @accounts = [] # each{|y| y.update_attribute :connected,  false }
  logger.info "Connection closed"
end

#fa?Boolean

is the account a financial advisor

Returns:

  • (Boolean)


292
293
294
# File 'lib/ib/gateway.rb', line 292

def fa?
   !(advisor == clients.first)
end

#get_hostObject



164
165
166
# File 'lib/ib/gateway.rb', line 164

def get_host
  "#{@connection_parameter[:host]}: #{@connection_parameter[:port] }"
end

#initialize_alertsObject



397
398
399
400
401
402
403
404
405
# File 'lib/ib/gateway.rb', line 397

def initialize_alerts

  tws.subscribe(  :AccountUpdateTime  ){| msg | logger.debug{ msg.to_human }}
  tws.subscribe(:Alert) do |msg|
    logger.debug " ----------------#{msg.code}-----"
    # delegate anything to IB::Alert
    IB::Alert.send("alert_#{msg.code}", msg )
  end
end

#initialize_managed_accountsObject

InitializeManagedAccounts defines the Message-Handler for :ManagedAccounts Its always active.



375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/ib/gateway.rb', line 375

def initialize_managed_accounts
  rec_id = tws.subscribe( :ReceiveFA )  do |msg|
    msg.accounts.each do |a|
      ( a. ){|  | .update_attribute :alias, a.alias } unless a.alias.blank?
    end
    logger.info { "Accounts initialized \n #{@accounts.map( &:to_human  ).join " \n " }" }
  end

  man_id = tws.subscribe( :ManagedAccounts ) do |msg| 
    if @accounts.empty?
      # just validate the message and put all together into an array
      @accounts =  msg.accounts_list.split(',').map do |a| 
         = IB::.new( account: a.upcase ,  connected: true )
      end
    else
      logger.info {"already #{@accounts.size} accounts initialized "}
      @accounts.each{|x| x.update_attribute :connected ,  true }
    end # if
  end # subscribe do
end

#prepare_connection {|_self| ... } ⇒ Object

Yields:

  • (_self)

Yield Parameters:

  • _self (IB::Gateway)

    the object that the method was called on



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# File 'lib/ib/gateway.rb', line 350

def prepare_connection &b
  tws.disconnect if tws.is_a? IB::Connection
    self.tws = IB::Connection.new  **@connection_parameter.merge( logger: self.logger )
  @accounts = @local_orders = Array.new

  # prepare Advisor-User hierachy
  initialize_managed_accounts if @gateway_parameter[:s_m_a]
  initialize_alerts if @gateway_parameter[:s_a]
    initialize_order_handling if @gateway_parameter[:s_o_m] || @gateway_parameter[:g_a_d]
  ## apply other initialisations which should apper before the connection as block
  ## i.e. after connection order-state events are fired if an open-order is pending
  ## a possible response is best defined before the connect-attempt is done
  # ##  Attention
  # ##  @accounts are not initialized yet (empty array)
    yield  self if block_given?


end

#reconnectObject



224
225
226
227
228
229
230
231
# File 'lib/ib/gateway.rb', line 224

def reconnect
  if tws.present?
    disconnect
      sleep 0.1
  end
  logger.info "trying to reconnect ..."
  connect
end

#send_message(what, *args) ⇒ Object

Proxy for Connection#SendMessage allows reconnection if a socket_error occurs

checks the connection before sending a message.



249
250
251
252
253
254
255
256
257
# File 'lib/ib/gateway.rb', line 249

def send_message what, *args
  begin
    if  check_connection
      tws.send_message what, *args
    else
      error( "Connection lost. Could not send message  #{what}" )
    end
  end
end

#update_local_order(order) ⇒ Object



168
169
170
171
# File 'lib/ib/gateway.rb', line 168

def update_local_order order
  # @local_orders is initialized by #PrepareConnection
  @local_orders.update_or_create order, :local_id
end