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



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

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



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/ib/models/account.rb', line 274

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



338
339
340
341
# File 'lib/ib/models/account.rb', line 338

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



333
334
335
# File 'lib/ib/models/account.rb', line 333

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
61
# 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.zero?  && !contract.is_a?( IB::Bag )
      contract =  contract.verify.first
    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



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

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 = IB::Gateway.current.active_watchlists) ⇒ Object

returns an hash where portfolio_positions are grouped into Watchlists.

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



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/ib/models/account.rb', line 315

def organize_portfolio_positions   the_watchlists= IB::Gateway.current.active_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: true) ⇒ 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 further used to modify and eventually cancel

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=>9534669, 
                                                     :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>


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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/ib/models/account.rb', line 115

def place_order  order:, contract: nil, auto_adjust: true, convert_size: true 
  # 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.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

  error "No valid contract given" unless order.contract.is_a?(IB::Contract)

  ## 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
  q =  Queue.new

  ### Handle Error messages
  ### Default action:  raise IB::Transmission Error
  sa = ib.subscribe( :Alert ) do | msg |
    #      puts "local_id: #{the_local_id}"a
    puts msg.inspect
    if msg.error_id == the_local_id
      if [ 110, #  The price does not confirm to the minimum price variation for this contract
          201, # Order rejected, No Trading permissions
          203, # Security is not allowed for trading
          325, # Disretionary Orders are not supported for ths combination of oerder-type and exchange
          355, # Order size does not conform to market rule
          361, 362, 363, 364, # invalid trigger or stop-price
          388,  # Order size x is smaller than the minimum required size of yy.
      ].include? msg.code
        wrong_order =  msg.message
        ib.logger.error msg.message
        q.close   # closing the queue indicates that no order was transmitted
      end
    end
  end
  sb = ib.subscribe( :OpenOrder ){|m| q << m.order if m.order.local_id.to_i == the_local_id.to_i }
  #  modify order (parameter) 
  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  file order_handling.rb
  if convert_size
    order.action = order.total_quantity.to_i < 0 ? :sell : :buy unless order.action == :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
  #  if no contract is passed to order.place, order.contract is used for placement
  the_contract = order.contract.con_id.to_i >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
  # if transmit is false, just include the local_id in the order-record
  Thread.new{  if order.transmit  || order.what_if  then sleep 1 else sleep 0.001 end ;  q.close }
  tws_answer = q.pop

  ib.unsubscribe sa
  ib.unsubscribe sb
  if q.closed? 
    if wrong_order.present?
      raise IB::SymbolError,  wrong_order
    elsif the_local_id.present?
      order.local_id = the_local_id
    else
    error " #{order.to_human} is not transmitted properly", :symbol
    end
  else
    order=tws_answer #  return order-record received from tws
  end
  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::SymbolError if the Order could not be placed properly

Raises:

  • (IB::SymbolError)


242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/ib/models/account.rb', line 242

def preview order:, contract: nil, **args_which_are_ignored
# to_do:  use a copy of order instead of temporary setting order.what_if
  q =  Queue.new
  ib =  IB::Connection.current
  the_local_id = nil
  req =  ib.subscribe( :OpenOrder ){|m| q << m.order if m.order.local_id.to_i == the_local_id.to_i }

  result = ->(l){ orders.detect{|x| x.local_id == l  && x. } }
  order.what_if = true
  order. = 
  the_local_id = order.place  contract
  Thread.new{  sleep 1  ;  q.close }
  returned_order = q.pop
  ib.unsubscribe req
  order.what_if = false # reset what_if flag
  order.local_id = nil  # reset local_id to enable re-using the order-object for placing
  raise IB::SymbolError,"(Preview-) #{order.to_human} is not transmitted properly" if q.closed?
  returned_order.order_state.forcast  #  return_value
end