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
	@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
				(watchlists: watchlists.map{|b| IB::Symbols.allocate_collection b})  if 
				#    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



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/ib/gateway.rb', line 317

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

#advisorObject

The Advisor is always the first account



303
304
305
# File 'lib/ib/gateway.rb', line 303

def advisor
	@accounts.first
end

#cancel_order(*orders) ⇒ Object

Cancels one or multible orders

Argument is either an order-object or a local_id



269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/ib/gateway.rb', line 269

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


409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/ib/gateway.rb', line 409

def check_connection
	answer = nil; count=0
	z= tws.subscribe( :CurrentTime ) { answer = true }
	while (answer.nil?)
		begin
			tws.send_message(:RequestCurrentTime)												# 10 ms  ##
			i=0; loop{ break if answer || i > 40; i+=1; sleep 0.0001}
		rescue IOError, Errno::ECONNREFUSED   # connection lost
			count = 6
		rescue IB::Error # not connected
			reconnect 
			count +=1
			sleep 1
			retry if count <= 5
		end
		count +=1
		break if count > 5
	end
	tws.unsubscribe z
	count < 5  && answer #  return value
end

#clientsObject

clients returns a list of Account-Objects

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



290
291
292
# File 'lib/ib/gateway.rb', line 290

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.



179
180
181
182
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
219
220
221
# File 'lib/ib/gateway.rb', line 179

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
		logger.error 'Cannot connect to specified host'
		logger.error  e
		return false
	rescue SocketError => e
		logger.error 'Wrong Adress, connection not possible'
		return false
	end

	tws.start_reader
	# let NextValidId-Event appear
	(1..30).each do |r|
		break if tws.next_local_id.present?
		sleep 0.1
		if r == 30
			error "Connected, NextLocalId is not initialized. Repeat with another client_id"
		end
	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]
end

#disconnectObject



236
237
238
239
240
241
# File 'lib/ib/gateway.rb', line 236

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)


295
296
297
# File 'lib/ib/gateway.rb', line 295

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

#get_hostObject



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

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

#initialize_alertsObject



386
387
388
389
390
391
392
393
394
# File 'lib/ib/gateway.rb', line 386

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.



364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'lib/ib/gateway.rb', line 364

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(&b) ⇒ Object



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/ib/gateway.rb', line 338

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)
	if block_given?
		yield  self

	end
end

#reconnectObject



227
228
229
230
231
232
233
234
# File 'lib/ib/gateway.rb', line 227

def reconnect
	if tws.present?
		disconnect
		sleep 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.



252
253
254
255
256
257
258
259
260
# File 'lib/ib/gateway.rb', line 252

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



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

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