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::Account) ?  :  @accounts.detect{|x| x. ==  }
			safe[sa] if sa.is_a? IB::Account
		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::Account.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