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



275
276
277
# File 'lib/ib/models/account.rb', line 275

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



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/ib/models/account.rb', line 242

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



306
307
308
309
# File 'lib/ib/models/account.rb', line 306

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



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

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/, contract: nil, 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
54
55
56
57
58
59
60
# File 'lib/ib/models/account.rb', line 36

def locate_order local_id: nil, perm_id: nil, order_ref: nil, status: /ubmitted/, contract: nil, 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
    if contract.present?
      if contract.con_id.nil? || contract.con_id =="" || contract.con_id.zero? 
        contract =  contract.verify.first unless contract.is_a? IB::Bag
      end
      matched_items = matched_items.find_all{|o| o.contract.essential == contract.essential } 
    elsif con_id.present?
      matched_items = matched_items.find_all{|o| o.contract.con_id == con_id } 
    end

	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



193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/ib/models/account.rb', line 193

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

	result = ->(l){ orders.detect{|x| x.local_id == l  && x. } }
	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] , … ] ]



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/ib/models/account.rb', line 283

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, enable_error: 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>

Raises:

  • (IB::TransmissionError)


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
162
163
164
165
166
167
168
169
# File 'lib/ib/models/account.rb', line 115

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

	order.contract ||=  if qualified_contract[ contract ]
                      contract
                       else
                         contract.verify.first
                       end

    if order.contract.nil?
     error "No valid contract given" if enable_error
     return 0
    end 
	## 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:  raise IB::Transmission Error
	sa = ib.subscribe( :Alert ) do | msg |
#      puts "local_id: #{the_local_id}"
		if msg.error_id == the_local_id
		 if [ 110, #  The price does not confirm to the minimum price variation for this contract
			   388,  # Order size x is smaller than the minimum required size of yy.
			  ].include? msg.code
         error msg.message if enable_error
         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
    self.orders.update_or_create order, :order_ref
    order.auto_adjust # if auto_adjust  /defined in lib/order_handling
	if convert_size
		order.action = order.total_quantity.to_i > 0  ?	:buy : :sell
      logger.info{ "Converted ordesize to #{order.total_quantity} and triggered a #{order.action}  order"} if  order.total_quantity.to_i < 0
		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
    i=0;	loop{i+=1; sleep(0.01); break if locate_order( local_id: the_local_id, status: nil ).present? || i> 1000  }

	ib.unsubscribe sa
    raise IB::TransmissionError," #{order.to_human} is not transmitted properly" if i >=1000
    the_local_id  # return_value
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)


218
219
220
221
222
223
224
225
226
227
228
# File 'lib/ib/models/account.rb', line 218

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.to_human} is not transmitted properly" if i >=1000
	order.what_if = false # reset what_if flag
	order.local_id = nil  # reset local_id to enable re-using the order-object for placing
	result[the_local_id].order_state.forcast  #  return_value
end