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