ib-extensions 1.1 → 1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,24 +1,24 @@
1
1
  module IB
2
2
 
3
- class Account
4
-
5
-
6
- def account_data_scan search_key, search_currency=nil
7
- if account_values.is_a? Array
8
- if search_currency.present?
9
- account_values.find_all{|x| x.key.match( search_key ) && x.currency == search_currency.upcase }
10
- else
11
- account_values.find_all{|x| x.key.match( search_key ) }
12
- end
13
-
14
- else # not tested!!
15
- if search_currency.present?
16
- account_values.where( ['key like %', search_key] ).where( currency: search_currency )
17
- else # any currency
18
- account_values.where( ['key like %', search_key] )
19
- end
20
- end
21
- end
3
+ class Account
4
+
5
+
6
+ def account_data_scan search_key, search_currency=nil
7
+ if account_values.is_a? Array
8
+ if search_currency.present?
9
+ account_values.find_all{|x| x.key.match( search_key ) && x.currency == search_currency.upcase }
10
+ else
11
+ account_values.find_all{|x| x.key.match( search_key ) }
12
+ end
13
+
14
+ else # not tested!!
15
+ if search_currency.present?
16
+ account_values.where( ['key like %', search_key] ).where( currency: search_currency )
17
+ else # any currency
18
+ account_values.where( ['key like %', search_key] )
19
+ end
20
+ end
21
+ end
22
22
 
23
23
 
24
24
 
@@ -33,31 +33,32 @@ Thus if several Orders are placed with the same order_ref, the active one is ret
33
33
  (If multible keys are specified, local_id preceeds perm_id)
34
34
 
35
35
  =end
36
- def locate_order local_id: nil, perm_id: nil, order_ref: nil, status: /ubmitted/, contract: nil, con_id: nil
37
- search_option= [ local_id.present? ? [:local_id , local_id] : nil ,
38
- perm_id.present? ? [:perm_id, perm_id] : nil,
39
- order_ref.present? ? [:order_ref , order_ref ] : nil ].compact.first
40
- matched_items = if search_option.nil?
41
- orders
42
- else
43
- orders.find_all{|x| x[search_option.first].to_i == search_option.last.to_i }
44
- end
36
+ def locate_order local_id: nil, perm_id: nil, order_ref: nil, status: /ubmitted/, contract: nil, con_id: nil
37
+ search_option = [ local_id.present? ? [:local_id , local_id] : nil ,
38
+ perm_id.present? ? [:perm_id, perm_id] : nil,
39
+ order_ref.present? ? [:order_ref , order_ref ] : nil ].compact.first
40
+ matched_items = if search_option.nil?
41
+ orders
42
+ else
43
+ orders.find_all{|x| x[search_option.first].to_i == search_option.last.to_i }
44
+ end
45
+
45
46
  if contract.present?
46
- if contract.con_id.nil? || contract.con_id =="" || contract.con_id.zero?
47
- contract = contract.verify.first unless contract.is_a? IB::Bag
47
+ if contract.con_id.zero? && !contract.is_a?( IB::Bag )
48
+ contract = contract.verify.first
48
49
  end
49
50
  matched_items = matched_items.find_all{|o| o.contract.essential == contract.essential }
50
51
  elsif con_id.present?
51
52
  matched_items = matched_items.find_all{|o| o.contract.con_id == con_id }
52
53
  end
53
54
 
54
- if status.present?
55
- status = Regexp.new(status) unless status.is_a? Regexp
56
- matched_items.detect{|x| x.order_state.status =~ status }
57
- else
58
- matched_items.last # return the last item
59
- end
60
- end
55
+ if status.present?
56
+ status = Regexp.new(status) unless status.is_a? Regexp
57
+ matched_items.detect{|x| x.order_state.status =~ status }
58
+ else
59
+ matched_items.last # return the last item
60
+ end
61
+ end
61
62
 
62
63
 
63
64
  =begin rdoc
@@ -74,10 +75,8 @@ convert_size: The action-attribute (:buy :sell) is associated according the con
74
75
 
75
76
  The parameter «order» is modified!
76
77
 
77
- It can be used to modify and eventually cancel
78
+ It can further used to modify and eventually cancel
78
79
 
79
- The method raises an IB::TransmissionError if the transmitted order ist not acknowledged by the tws after
80
- one second.
81
80
 
82
81
  Example
83
82
 
@@ -85,111 +84,136 @@ Example
85
84
  order = IB::Limit.order size: 100, price: 65.5
86
85
  g = IB::Gateway.current.clients.last
87
86
 
88
- g.preview contract: j36, order: order
87
+ g.preview contract: j36, order: order
89
88
  => {:init_margin=>0.10864874e6,
90
- :maint_margin=>0.9704137e5,
91
- :equity_with_loan=>0.97877973e6,
92
- :commission=>0.524e1,
93
- :commission_currency=>"USD",
94
- :warning=>""}
89
+ :maint_margin=>0.9704137e5,
90
+ :equity_with_loan=>0.97877973e6,
91
+ :commission=>0.524e1,
92
+ :commission_currency=>"USD",
93
+ :warning=>""
95
94
 
96
95
  the_local_id = g.place order: order
97
- => 67 # returns local_id
98
- order.contract # updated contract-record
99
- => #<IB::Contract:0x00000000013c94b0 @attributes={:con_id=>95346693,
100
- :exchange=>"SGX",
101
- :right=>"",
102
- :include_expired=>false}>
103
-
104
- order.limit_price = 65 # set new price
105
- g.modify order: order # and transmit
106
- => 67 # returns local_id
107
-
108
- g.locate_order( local_id: the_local_id )
109
- => returns the assigned order-record for inspection
110
-
111
- g.cancel order: order
112
- # logger output: 05:17:11 Cancelling 65 New #250/ from 3000/DU167349>
96
+ => 67 # returns local_id
97
+ order.contract # updated contract-record
98
+
99
+ => #<IB::Contract:0x00000000013c94b0 @attributes={:con_id=>9534669,
100
+ :exchange=>"SGX",
101
+ :right=>"",
102
+ :include_expired=>false}>
103
+
104
+ order.limit_price = 65 # set new price
105
+ g.modify order: order # and transmit
106
+ => 67 # returns local_id
107
+
108
+ g.locate_order( local_id: the_local_id )
109
+ => returns the assigned order-record for inspection
110
+
111
+ g.cancel order: order
112
+ # logger output: 05:17:11 Cancelling 65 New #250/ from 3000/DU167349>
113
113
  =end
114
114
 
115
- def place_order order:, contract: nil, auto_adjust: true, convert_size: false, enable_error: false
116
- # adjust the orderprice to min-tick
117
- result = ->(l){ orders.detect{|x| x.local_id == l && x.submitted? } }
118
- #·IB::Symbols are always qualified. They carry a description-field
119
- 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 )) }
120
-
121
- order.contract ||= if qualified_contract[ contract ]
122
- contract
123
- else
124
- contract.verify.first
125
- end
126
-
127
- if order.contract.nil?
128
- error "No valid contract given" if enable_error
129
- return 0
130
- end
131
- ## sending of plain vanilla IB::Bags will fail using account.place, unless a (negative) con-id is provided!
132
- # error "place order: ContractVerification failed. No con_id assigned" unless qualified_contract[order.contract]
133
- ib = IB::Connection.current
134
- wrong_order = nil
135
- the_local_id = nil
136
-
137
- ### Handle Error messages
138
- ### Default action: raise IB::Transmission Error
139
- sa = ib.subscribe( :Alert ) do | msg |
140
- # puts "local_id: #{the_local_id}"
141
- if msg.error_id == the_local_id
142
- if [ 110, # The price does not confirm to the minimum price variation for this contract
143
- 388, # Order size x is smaller than the minimum required size of yy.
144
- ].include? msg.code
145
- error msg.message if enable_error
146
- wrong_order = msg.error_id.to_i
147
- ib.logger.error msg.message
148
- end
149
- end
150
- end
151
- order.account = account # assign the account_id to the account-field of IB::Order
152
- self.orders.update_or_create order, :order_ref
153
- order.auto_adjust # if auto_adjust /defined in lib/order_handling
154
- if convert_size
155
- order.action = order.total_quantity.to_i > 0 ? :buy : :sell
156
- logger.info{ "Converted ordesize to #{order.total_quantity} and triggered a #{order.action} order"} if order.total_quantity.to_i < 0
157
- order.total_quantity = order.total_quantity.to_i.abs
158
- end
159
- # apply non_guarenteed and other stuff bound to the contract to order.
160
- order.attributes.merge! order.contract.order_requirements unless order.contract.order_requirements.blank?
161
- # con_id and exchange fully qualify a contract, no need to transmit other data
162
- the_contract = order.contract.con_id >0 ? Contract.new( con_id: order.contract.con_id, exchange: order.contract.exchange) : nil
163
- the_local_id = order.place the_contract # return the local_id
164
- i=0; loop{i+=1; sleep(0.01); break if locate_order( local_id: the_local_id, status: nil ).present? || i> 1000 }
165
-
166
- ib.unsubscribe sa
167
- raise IB::TransmissionError," #{order.to_human} is not transmitted properly" if i >=1000
168
- the_local_id # return_value
169
- end # place
170
-
171
- # shortcut to enable
172
- # account.place order: {} , contract: {}
173
- # account.preview order: {} , contract: {}
174
- # account.modify order: {}
175
- alias place place_order
115
+ def place_order order:, contract: nil, auto_adjust: true, convert_size: true
116
+ # adjust the orderprice to min-tick
117
+ result = ->(l){ orders.detect{|x| x.local_id == l && x.submitted? } }
118
+ #·IB::Symbols are always qualified. They carry a description-field
119
+ 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 )) }
120
+
121
+ order.contract ||= if qualified_contract[ contract ]
122
+ contract
123
+ else
124
+ contract.verify.first
125
+ end
126
+
127
+ error "No valid contract given" unless order.contract.is_a?(IB::Contract)
128
+
129
+ ## sending of plain vanilla IB::Bags will fail using account.place, unless a (negative) con-id is provided!
130
+ error "place order: ContractVerification failed. No con_id assigned" unless qualified_contract[order.contract]
131
+
132
+ ib = IB::Connection.current
133
+ wrong_order = nil
134
+ the_local_id = nil
135
+ q = Queue.new
136
+
137
+ ### Handle Error messages
138
+ ### Default action: raise IB::Transmission Error
139
+ sa = ib.subscribe( :Alert ) do | msg |
140
+ # puts "local_id: #{the_local_id}"a
141
+ puts msg.inspect
142
+ if msg.error_id == the_local_id
143
+ if [ 110, # The price does not confirm to the minimum price variation for this contract
144
+ 201, # Order rejected, No Trading permissions
145
+ 203, # Security is not allowed for trading
146
+ 325, # Disretionary Orders are not supported for ths combination of oerder-type and exchange
147
+ 355, # Order size does not conform to market rule
148
+ 361, 362, 363, 364, # invalid trigger or stop-price
149
+ 388, # Order size x is smaller than the minimum required size of yy.
150
+ ].include? msg.code
151
+ wrong_order = msg.message
152
+ ib.logger.error msg.message
153
+ q.close # closing the queue indicates that no order was transmitted
154
+ end
155
+ end
156
+ end
157
+ sb = ib.subscribe( :OpenOrder ){|m| q << m.order if m.order.local_id.to_i == the_local_id.to_i }
158
+ # modify order (parameter)
159
+ order.account = account # assign the account_id to the account-field of IB::Order
160
+ self.orders.update_or_create order, :order_ref
161
+ order.auto_adjust if auto_adjust # /defined in file order_handling.rb
162
+ if convert_size
163
+ order.action = order.total_quantity.to_i < 0 ? :sell : :buy unless order.action == :sell
164
+ logger.info{ "Converted ordesize to #{order.total_quantity} and triggered a #{order.action} order"} if order.total_quantity.to_i < 0
165
+ order.total_quantity = order.total_quantity.to_i.abs
166
+ end
167
+ # apply non_guarenteed and other stuff bound to the contract to order.
168
+ order.attributes.merge! order.contract.order_requirements unless order.contract.order_requirements.blank?
169
+ # con_id and exchange fully qualify a contract, no need to transmit other data
170
+ # if no contract is passed to order.place, order.contract is used for placement
171
+ the_contract = order.contract.con_id.to_i >0 ? Contract.new( con_id: order.contract.con_id, exchange: order.contract.exchange) : nil
172
+ the_local_id = order.place the_contract # return the local_id
173
+ # if transmit is false, just include the local_id in the order-record
174
+ Thread.new{ if order.transmit || order.what_if then sleep 1 else sleep 0.001 end ; q.close }
175
+ tws_answer = q.pop
176
+
177
+ ib.unsubscribe sa
178
+ ib.unsubscribe sb
179
+ if q.closed?
180
+ if wrong_order.present?
181
+ raise IB::SymbolError, wrong_order
182
+ elsif the_local_id.present?
183
+ order.local_id = the_local_id
184
+ else
185
+ error " #{order.to_human} is not transmitted properly", :symbol
186
+ end
187
+ else
188
+ order=tws_answer # return order-record received from tws
189
+ end
190
+ the_local_id # return_value
191
+ end # place
192
+
193
+
194
+ # shortcut to enable
195
+ # account.place order: {} , contract: {}
196
+ # account.preview order: {} , contract: {}
197
+ # account.modify order: {}
198
+ alias place place_order
176
199
 
177
200
  =begin #rdoc
178
201
  Account#ModifyOrder operates in two modi:
179
202
 
180
203
  First: The order is specified via local_id, perm_id or order_ref.
181
- It is checked, whether the order is still modificable.
182
- Then the Order ist provided through the block. Any modification is done there.
183
- Important: The Block has to return the modified IB::Order
204
+ It is checked, whether the order is still modificable.
205
+ Then the Order ist provided through the block. Any modification is done there.
206
+ Important: The Block has to return the modified IB::Order
184
207
 
185
208
  Second: The order can be provided as parameter as well. This will be used
186
209
  without further checking. The block is now optional.
187
- Important: The OrderRecord must provide a valid Contract.
210
+ Important: The OrderRecord must provide a valid Contract.
188
211
 
189
212
  The simple version does not adjust the given prices to tick-limits.
190
213
  This has to be done manualy in the provided block
191
214
  =end
192
215
 
216
+
193
217
  def modify_order local_id: nil, order_ref: nil, order:nil
194
218
 
195
219
  result = ->(l){ orders.detect{|x| x.local_id == l && x.submitted? } }
@@ -213,19 +237,27 @@ This has to be done manualy in the provided block
213
237
  #
214
238
  # The order received from the TWS is kept in account.orders
215
239
  #
216
- # Raises IB::TransmissionError if the Order could not be placed properly
240
+ # Raises IB::SymbolError if the Order could not be placed properly
217
241
  #
218
242
  def preview order:, contract: nil, **args_which_are_ignored
219
243
  # to_do: use a copy of order instead of temporary setting order.what_if
220
- result = ->(l){ orders.detect{|x| x.local_id == l && x.submitted? } }
221
- order.what_if = true
222
- the_local_id = place_order order: order, contract: contract
223
- i=0; loop{ i= i+1; break if result[the_local_id] || i > 1000; sleep 0.01 }
224
- raise IB::TransmissionError,"(Preview-) #{order.to_human} is not transmitted properly" if i >=1000
225
- order.what_if = false # reset what_if flag
226
- order.local_id = nil # reset local_id to enable re-using the order-object for placing
227
- result[the_local_id].order_state.forcast # return_value
228
- end
244
+ q = Queue.new
245
+ ib = IB::Connection.current
246
+ the_local_id = nil
247
+ req = ib.subscribe( :OpenOrder ){|m| q << m.order if m.order.local_id.to_i == the_local_id.to_i }
248
+
249
+ result = ->(l){ orders.detect{|x| x.local_id == l && x.submitted? } }
250
+ order.what_if = true
251
+ order.account = account
252
+ the_local_id = order.place contract
253
+ Thread.new{ sleep 1 ; q.close }
254
+ returned_order = q.pop
255
+ ib.unsubscribe req
256
+ order.what_if = false # reset what_if flag
257
+ order.local_id = nil # reset local_id to enable re-using the order-object for placing
258
+ raise IB::SymbolError,"(Preview-) #{order.to_human} is not transmitted properly" if q.closed?
259
+ returned_order.order_state.forcast # return_value
260
+ end
229
261
 
230
262
  # closes the contract by submitting an appropiate order
231
263
  # the action- and total_amount attributes of the assigned order are overwritten.
@@ -254,12 +286,12 @@ This has to be done manualy in the provided block
254
286
  error "Cannot transmit the order – No Contract given " unless order.contract.is_a?(IB::Contract)
255
287
 
256
288
  the_quantity = if reverse
257
- -contract_size[order.contract] * 2
258
- elsif order.total_quantity.abs < 1 && !order.total_quantity.zero?
259
- -contract_size[order.contract] * order.total_quantity.abs
260
- else
261
- -contract_size[order.contract]
262
- end
289
+ -contract_size[order.contract] * 2
290
+ elsif order.total_quantity.abs < 1 && !order.total_quantity.zero?
291
+ -contract_size[order.contract] * order.total_quantity.abs
292
+ else
293
+ -contract_size[order.contract]
294
+ end
263
295
  if the_quantity.zero?
264
296
  logger.info{ "Cannot close #{order.contract.to_human} - no position detected"}
265
297
  else
@@ -280,7 +312,7 @@ This has to be done manualy in the provided block
280
312
  #
281
313
  # Watchlist => [ contract => [ portfoliopositon] , ... ] ]
282
314
  #
283
- def organize_portfolio_positions the_watchlists
315
+ def organize_portfolio_positions the_watchlists= IB::Gateway.current.active_watchlists
284
316
  the_watchlists = [ the_watchlists ] unless the_watchlists.is_a?(Array)
285
317
  self.focuses = portfolio_values.map do | pw |
286
318
  z= the_watchlists.map do | w |
@@ -310,7 +342,7 @@ This has to be done manualy in the provided block
310
342
  end # class
311
343
  ##
312
344
  # in the console (call gateway with watchlist: [:Spreads, :BuyAndHold])
313
- #head :001 > .active_accounts.first.focuses.to_a.to_human
345
+ #head :001 > .clients.first.focuses.to_a.to_human
314
346
  #Unspecified
315
347
  #<Stock: BLUE EUR SBF>
316
348
  #<PortfolioValue: DU167348 Pos=720 @ 15.88;Value=11433.24;PNL=-4870.05 unrealized;<Stock: BLUE EUR SBF>
@@ -333,4 +365,5 @@ This has to be done manualy in the provided block
333
365
  #<PortfolioValue: DU167348 Pos=-3 @ 142.574;Value=-4277.22;PNL=-867.72 unrealized;<Option: ESTX50 20181221 put 3200.0 EUR>
334
366
  # => nil
335
367
  # #
368
+
336
369
  end ## module
@@ -0,0 +1,19 @@
1
+ module IB
2
+ class Bag
3
+ def included_in? account
4
+ # iterate over combo-legs
5
+ # and return the bag if all con_id's are present in the account.contracts-map
6
+ self if combo_legs.map do |c_l|
7
+ account.locate_contract c_l.con_id
8
+ end.count == combo_legs.count
9
+ end
10
+
11
+ # returns an array of portfolio-values
12
+ #
13
+ def portfolio_value account
14
+ combo_legs.map do | c_l |
15
+ account.portfolio_values.detect{|x| x.contract.con_id == c_l.con_id}
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+
2
+ module IB
3
+ class Contract
4
+ def included_in? account
5
+ self if account.locate_contract(con_id)
6
+ end
7
+
8
+ def portfolio_value account
9
+ if con_id.to_i > 0
10
+ account.portfolio_values.detect{|x| x.contract.con_id == con_id }
11
+ else
12
+ account.portfolio_values.detect{|x| x.contract == self }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ module IB
2
+ class Future
3
+ # helper method to roll an existing future
4
+ #
5
+ # Argument is the expiry of the target-future.
6
+ #
7
+
8
+ def roll **args
9
+ error "specify expiry to roll a future" if args.empty?
10
+ args[:to] = args[:expiry] if args[:expiry].present? && args[:expiry] =~ /[mwMW]$/
11
+ args[:expiry]= IB::Spread.transform_distance( expiry, args.delete(:to )) if args[:to].present?
12
+
13
+ new_future = merge( **args ).verify.first
14
+ error "Cannot roll future; target is no IB::Contract" unless new_future.is_a? IB::Future
15
+ target = IB::Spread.new exchange: exchange, symbol: symbol, currency: currency
16
+ target.add_leg self, action: :buy
17
+ target.add_leg new_future, action: :sell
18
+ end
19
+ end
20
+ end
@@ -1,20 +1,27 @@
1
1
  module IB
2
2
  class Option
3
- def roll expiry, strike
4
- if strike.to_i > 2000
5
- expiry, strike = strike, expiry # exchange values if input occurs in wrong direction
6
- end
7
- new_option = Option.new( invariant_attributes.merge( con_id: nil, trading_class: '', last_trading_day: nil,
8
- local_symbol: "",
9
- expiry: expiry, strike: strike ))
10
- n_o = new_option.verify.first # get con_id
3
+ # helper method to roll an existing option
4
+ #
5
+ # Arguments are strike and expiry of the target-option.
6
+ #
7
+ # Example: ge= Symbols::Options.ge.verify.first.roll( strike: 13 )
8
+ # ge.to_human
9
+ # => " added <Option: GE 20210917 call 7.0 SMART USD> added <Option: GE 20210917 call 13.0 SMART USD>"
10
+ #
11
+ # rolls the Option to another strike
11
12
 
13
+ def roll **args
14
+ error "specify strike and expiry to roll option" if args.empty?
15
+ args[:to] = args[:expiry] if args[:expiry].present? && args[:expiry] =~ /[mwMW]$/
16
+ args[:expiry]= IB::Spread.transform_distance( expiry, args.delete(:to )) if args[:to].present?
17
+
18
+ new_option = merge( **args ).verify.first
19
+ myself = con_id.to_i.zero? ? self.verify.first : self
20
+ error "Cannot roll option; target is no IB::Contract" unless new_option.is_a? IB::Option
21
+ error "Cannot roll option; Option cannot be verified" unless myself.is_a? IB::Option
12
22
  target = IB::Spread.new exchange: exchange, symbol: symbol, currency: currency
13
- target.add_leg self, action: :buy
14
- target.add_leg n_o, action: :sell
15
- rescue NoMethodError
16
- Connection.logger.error "Rolling not possible. #{new_option.to_human} could not be verified"
17
- nil
23
+ target.add_leg myself, action: :buy
24
+ target.add_leg new_option, action: :sell
18
25
  end
19
26
  end
20
27
  end