Class: IB::Account

Inherits:
Object
  • Object
show all
Defined in:
lib/ib/models/account.rb

Instance Method Summary collapse

Instance Method Details

#account_data_scan(search_key, search_currency = nil) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/ib/models/account.rb', line 6

def  search_key, search_currency=nil
	if .is_a? Array
		if search_currency.present? 
			.find_all{|x| x.key.match( search_key )  && x.currency == search_currency.upcase }
		else
			.find_all{|x| x.key.match( search_key ) }
		end

	else  # not tested!!
		if search_currency.present?
			.where( ['key like %', search_key] ).where( currency: search_currency )
		else  # any currency
			.where( ['key like %', search_key] )
		end
	end
end

#cancel(order:) ⇒ Object

just a wrapper to the Gateway-cancel-order method



268
269
270
# File 'lib/ib/models/account.rb', line 268

def cancel order: 
	Gateway.current.cancel_order order 
end

#close(order:, contract: nil, reverse: false, **args_which_are_ignored) ⇒ Object

the action- and total_amount attributes of the assigned order are overwritten.

if a ratio-value (0 ..1) is specified in order.total_quantity only a fraction of the position is closed. Other values are silently ignored

if reverse is specified, the opposide position is established. Any value in total_quantity is overwritten

returns the order transmitted

raises an IB::Error if no PortfolioValues have been loaded to the IB::Acoount



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/ib/models/account.rb', line 235

def close order:, contract: nil, reverse: false,  **args_which_are_ignored
	error "must only be called after initializing portfolio_values "  if portfolio_values.blank?
	contract_size = ->(c) do			# note: portfolio_value.position is either positiv or negativ
		if c.con_id <0 # Spread
			p = portfolio_values.detect{|p| p.contract.con_id ==c.legs.first.con_id} &.position.to_i
			p/ c.combo_legs.first.weight  unless p.to_i.zero?
		else
			portfolio_values.detect{|x| x.contract.con_id == c.con_id} &.position.to_i   # nil.to_i -->0
		end
	end

	contract &.verify{|c| order.contract = c}   # if contract is specified: don't touch the parameter, get a new object . 
	error "Cannot transmit the order – No Contract given " unless order.contract.is_a?(IB::Contract)

	the_quantity = if reverse
													 -contract_size[order.contract] * 2 
												 elsif order.total_quantity.abs < 1 && !order.total_quantity.zero? 
													-contract_size[order.contract] *  order.total_quantity.abs 
												 else
													 -contract_size[order.contract] 
												 end
	if the_quantity.zero?
		logger.info{ "Cannot close #{order.contract.to_human} - no position detected"}
	else
		order.total_quantity = the_quantity
		order.action =  nil
		order.local_id =  nil  # in any case, close is a new order
		logger.info { "Order modified to close, reduce or revese position: #{order.to_human}" }
		place order: order, convert_size: true
	end
end

#complex_position(con_id) ⇒ Object

returns the contract definition of an complex portfolio-position detected in the account



299
300
301
302
# File 'lib/ib/models/account.rb', line 299

def complex_position con_id
	con_id = con_id.con_id	if con_id.is_a?(IB::Contract)
	focuses.map{|x,y| y.detect{|x,y| x.con_id.to_i==  con_id.to_i} }.compact.flatten.first
end

#locate_contract(con_id) ⇒ Object



294
295
296
# File 'lib/ib/models/account.rb', line 294

def locate_contract con_id
	contracts.detect{|x| x.con_id.to_i == con_id.to_i }
end

#locate_order(local_id: nil, perm_id: nil, order_ref: nil, status: /ubmitted/, con_id: nil) ⇒ Object

given any key of local_id, perm_id or order_ref and an optional status, which can be a string or a regexp ( status: /mitted/ matches Submitted and Presubmitted) the last associated Orderrecord is returned.

Thus if several Orders are placed with the same order_ref, the active one is returned

(If multible keys are specified, local_id preceeds perm_id)



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/ib/models/account.rb', line 36

def locate_order local_id: nil, perm_id: nil, order_ref: nil, status: /ubmitted/, con_id: nil
	search_option= [ local_id.present? ? [:local_id , local_id] : nil ,
								perm_id.present? ? [:perm_id, perm_id] : nil,
								order_ref.present? ? [:order_ref , order_ref ] : nil ].compact.first
	matched_items = if search_option.nil? 
										orders 
									else 
										orders.find_all{|x| x[search_option.first].to_i == search_option.last.to_i }
									end
	matched_items = matched_items.find_all{|x| x.contract.con_id == con_id } if con_id.present?

	if status.present?
		status = Regexp.new(status) unless status.is_a? Regexp
		matched_items.detect{|x| x.order_state.status =~ status }
	else
		matched_items.last  # return the last item
	end
end

#modify_order(local_id: nil, order_ref: nil, order: nil) ⇒ Object Also known as: modify

Account#ModifyOrder operates in two modi:

First: The order is specified via local_id, perm_id or order_ref. It is checked, whether the order is still modificable. Then the Order ist provided through the block. Any modification is done there. Important: The Block has to return the modified IB::Order

Second: The order can be provided as parameter as well. This will be used without further checking. The block is now optional. Important: The OrderRecord must provide a valid Contract.

The simple version does not adjust the given prices to tick-limits. This has to be done manualy in the provided block



185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/ib/models/account.rb', line 185

def modify_order  local_id: nil, order_ref: nil, order:nil

	result = ->(l){ orders.detect{|x| x.local_id == l  && x. } }
	logger.tap{ |l| l.progname = "Account #{}#modify_order"}
	order ||= locate_order( local_id: local_id, 
												 status: /ubmitted/ ,
												 order_ref: order_ref ) 
	if order.is_a? IB::Order
		order.modify
	else
		error "No suitable IB::Order provided/detected. Instead: #{order.inspect}" 
	end  
end

#organize_portfolio_positions(the_watchlists) ⇒ Object

returns an hash where portfolio_positions are grouped into Watchlists.

Watchlist => [ contract => [ portfoliopositon] , … ] ]



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/ib/models/account.rb', line 276

def organize_portfolio_positions   the_watchlists
  the_watchlists = [ the_watchlists ] unless the_watchlists.is_a?(Array)
	self.focuses = portfolio_values.map do | pw |
									z=	the_watchlists.map do | w |		
										ref_con_id = pw.contract.con_id
										watchlist_contract = w.find do |c| 
											c.is_a?(IB::Bag) ? c.combo_legs.map(&:con_id).include?(ref_con_id) : c.con_id == ref_con_id 
										end rescue nil	
										watchlist_contract.present? ? [w,watchlist_contract] : nil
									end.compact

									z.empty? ? [ IB::Symbols::Unspecified, pw.contract, pw ] : z.first << pw
	end.group_by{|a,_,_| a }.map{|x,y|[x, y.map{|_,d,e|[d,e]}.group_by{|e,_| e}.map{|f,z| [f, z.map(&:last)]} ] }.to_h
	# group:by --> [a,b,c] .group_by {|_g,_| g} --->{ a => [a,b,c] }
	# group_by+map --> removes "a" from the resulting array
end

#place_order(order:, contract: nil, auto_adjust: true, convert_size: false) ⇒ Object Also known as: place

requires an IB::Order as parameter.

If attached, the associated IB::Contract is used to specify the tws-command

The associated Contract overtakes the specified (as parameter)

auto_adjust: Limit- and Aux-Prices are adjusted to Min-Tick

convert_size: The action-attribute (:buy :sell) is associated according the content of :total_quantity.

The parameter «order» is modified!

It can be used to modify and eventually cancel

The method raises an IB::TransmissionError if the transmitted order ist not acknowledged by the tws after one second.

Example

j36 =  IB::Stock.new symbol: 'J36', exchange: 'SGX'
order =  IB::Limit.order size: 100, price: 65.5
g =  IB::Gateway.current.clients.last

g.preview contract: j36, order: order

=> {:init_margin=>0.10864874e6,

:maint_margin=>0.9704137e5, :equity_with_loan=>0.97877973e6, :commission=>0.524e1, :commission_currency=>“USD”, :warning=>“”}

the_local_id = g.place order: order

=> 67 # returns local_id order.contract # updated contract-record

=> #<IB::Contract:0x00000000013c94b0 @attributes={:con_id=>95346693,

:exchange=>“SGX”, :right=>“”, :include_expired=>false}>

order.limit_price = 65 # set new price g.modify order: order # and transmit => 67 # returns local_id

g.locate_order( local_id: the_local_id ) => returns the assigned order-record for inspection

g.cancel order: order # logger output: 05:17:11 Cancelling 65 New #250/ from 3000/DU167349>



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
155
156
157
158
159
160
161
# File 'lib/ib/models/account.rb', line 109

def place_order  order:, contract: nil, auto_adjust: true, convert_size:  false
	# adjust the orderprice to  min-tick
	logger.progname =  'Account#PlaceOrder' 
	result = ->(l){ orders.detect{|x| x.local_id == l  && x. } }
	#·IB::Symbols are always qualified. They carry a description-field
	qualified_contract = ->(c) { c.description.present? || (c.con_id.present?  &&  !c.con_id.to_i.zero?) || 
														c.con_id <0  && c.sec_type == :bag  }
	order.contract = contract.verify.first if contract.present? && order.contract.nil? 

	## sending of plain vanilla IB::Bags will fail using account.place, unless a (negative) con-id is provided!
#			error "place order: ContractVerification failed. No con_id assigned"  unless qualified_contract[order.contract]
	ib = IB::Connection.current
	wrong_order = nil
	the_local_id =  nil

	### Handle Error messages
	### Default action:  display error in log and return nil
	sa = ib.subscribe( :Alert ) do | msg |
		if msg.error_id == the_local_id
		 if [ 110, #  The price does not conform to the minimum price variation for this contract
			   388,  # Order size x is smaller than the minimum required size of yy.
			  ].include? msg.code
			 wrong_order =  msg.error_id.to_i  
			 ib.logger.error msg.message
		 end
		end
	end
	order. =    # assign the account_id to the account-field of IB::Order
	if qualified_contract[order.contract]
		self.orders.update_or_create order, :order_ref
		order.auto_adjust # if auto_adjust  /defined in lib/order_handling
	end
	if convert_size 
	 	order.action = order.total_quantity.to_i > 0  ? 	:buy : :sell 
		order.total_quantity  = order.total_quantity.to_i.abs
	end
		# apply non_guarenteed and other stuff bound to the contract to order.
	order.attributes.merge! order.contract.order_requirements unless order.contract.order_requirements.blank?
		#  con_id and exchange fully qualify a contract, no need to transmit other data
	the_contract = order.contract.con_id >0 ? Contract.new( con_id: order.contract.con_id, exchange: order.contract.exchange) : nil 
	the_local_id = order.place the_contract # return the local_id
	Timeout::timeout(1, IB::TransmissionError,"TimeOut ::Transmitted Order was not acknowledged") do 
		loop{ sleep(0.001); break if locate_order( local_id: the_local_id, status: nil ).present? }
	end

	ib.unsubscribe sa

	if wrong_order.nil?
		the_local_id  # return_value
	else
		nil
	end
end

#preview(order:, contract: nil, **args_which_are_ignored) ⇒ Object

Submits a “WhatIf” Order

Returns the order_state.forcast

The order received from the TWS is kept in account.orders

Raises IB::TransmissionError if the Order could not be placed properly

Raises:

  • (IB::TransmissionError)


211
212
213
214
215
216
217
218
219
220
221
# File 'lib/ib/models/account.rb', line 211

def preview order:, contract: nil, **args_which_are_ignored
	# to_do:  use a copy of order instead of temporary setting order.what_if
	result = ->(l){ orders.detect{|x| x.local_id == l  && x. } }
	order.what_if = true
	the_local_id = place_order order: order, contract: contract
	i=0; loop{ i= i+1;  break if result[the_local_id] || i > 1000; sleep 0.01 }
	raise IB::TransmissionError,"(Preview-)Order is not transmitted properly" if i >=1000
	order.what_if = false # reset what_if flag
	order.local_id = nil  # reset local_id to enable reusage of the order-object for placing
	result[the_local_id].order_state.forcast  #  return_value
end