ib-api 972.0 → 972.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +36 -37
  3. data/README.md +48 -3
  4. data/VERSION +1 -1
  5. data/api.gemspec +1 -1
  6. data/bin/console +4 -8
  7. data/changelog.md +12 -0
  8. data/lib/ib/base_properties.rb +3 -4
  9. data/lib/ib/connection.rb +18 -16
  10. data/lib/ib/logger.rb +1 -1
  11. data/lib/ib/messages/incoming.rb +1 -1
  12. data/lib/ib/messages/incoming/abstract_message.rb +7 -7
  13. data/lib/ib/messages/incoming/account_value.rb +5 -1
  14. data/lib/ib/messages/incoming/contract_data.rb +0 -2
  15. data/lib/ib/messages/incoming/historical_data.rb +25 -5
  16. data/lib/ib/messages/incoming/ticks.rb +21 -60
  17. data/lib/ib/messages/outgoing.rb +4 -2
  18. data/lib/ib/messages/outgoing/abstract_message.rb +3 -3
  19. data/lib/ib/messages/outgoing/bar_requests.rb +4 -5
  20. data/lib/ib/messages/outgoing/request_marketdata.rb +10 -7
  21. data/lib/ib/models.rb +1 -1
  22. data/lib/ib/support.rb +32 -15
  23. data/lib/logging.rb +45 -0
  24. data/lib/models/ib/account.rb +0 -13
  25. data/lib/models/ib/bag.rb +7 -1
  26. data/lib/models/ib/bar.rb +1 -1
  27. data/lib/models/ib/combo_leg.rb +22 -0
  28. data/lib/models/ib/contract.rb +44 -32
  29. data/lib/models/ib/contract_detail.rb +13 -7
  30. data/lib/models/ib/index.rb +1 -1
  31. data/lib/models/ib/option.rb +8 -2
  32. data/lib/models/ib/option_detail.rb +19 -3
  33. data/lib/models/ib/order.rb +5 -0
  34. data/lib/models/ib/spread.rb +168 -0
  35. data/lib/models/ib/stock.rb +9 -3
  36. data/lib/models/ib/underlying.rb +4 -1
  37. data/lib/requires.rb +10 -3
  38. metadata +9 -20
  39. data/example/README.md +0 -76
  40. data/example/account_info +0 -54
  41. data/example/account_positions +0 -30
  42. data/example/account_summary +0 -88
  43. data/example/cancel_orders +0 -74
  44. data/example/fa_accounts +0 -25
  45. data/example/fundamental_data +0 -40
  46. data/example/historic_data_cli +0 -186
  47. data/example/list_orders +0 -45
  48. data/example/portfolio_csv +0 -81
  49. data/example/scanner_data +0 -62
  50. data/example/template +0 -19
  51. data/example/tick_data +0 -28
@@ -83,6 +83,28 @@ module IB
83
83
  ].flatten
84
84
  end
85
85
 
86
+
87
+ # fields are generated by serialize(:extended)
88
+ # i.e.
89
+ # z= Strangle.build from: Symbols::Index.stoxx, p: 3700, c: 4000, expiry: 202106
90
+ # zc= z.combo_legs.serialize :extended
91
+ # => [[321584786, 1, "BUY", "DTB", 0, 0, "", -1], [321584637, 1, "BUY", "DTB", 0, 0, "", -1]]
92
+ # nz = zc.map{|o| ComboLeg.build *o }
93
+ # zc.map{|o| ComboLeg.build o } => # is equivalent
94
+ # => [#<IB::ComboLeg:0x0000000001c36bc0 @attributes={:con_id=>321584786, :ratio=>1, :side=>"B", :exchange=>"DTB", ...
95
+ # nz.first == z.combo_legs.first => true
96
+ #
97
+ def self.build *fields
98
+ self.new Hash[[:con_id,
99
+ :ratio,
100
+ :side, # reverse to_sup?
101
+ :exchange,
102
+ :open_close,
103
+ :short_sale_slot,
104
+ :designated_location,
105
+ :exempt_code].zip fields]
106
+ end
107
+
86
108
  def to_human
87
109
  "<ComboLeg: #{side} #{ratio} con_id #{con_id} at #{exchange}>"
88
110
  end
@@ -4,7 +4,14 @@ require 'models/ib/underlying'
4
4
 
5
5
 
6
6
  module IB
7
- class Contract < IB::Model
7
+
8
+ if defined?(Contract)
9
+ #Connection.current.logger.warn "Contract already a #{defined?(Contract)}"
10
+
11
+ # puts Contract.ancestors
12
+ # IB.send(:remove_const, 'Contract')
13
+ end
14
+ class Contract < IB::Model
8
15
  include BaseProperties
9
16
 
10
17
  # Fields are Strings unless noted otherwise
@@ -113,7 +120,7 @@ module IB
113
120
 
114
121
  def default_attributes # :nodoc:
115
122
  super.merge :con_id => 0,
116
- :strike => 0.0,
123
+ :strike => "",
117
124
  :right => :none, # Not an option
118
125
  # :exchange => 'SMART',
119
126
  :include_expired => false
@@ -130,13 +137,13 @@ module IB
130
137
 
131
138
  def serialize *fields # :nodoc:
132
139
  print_default = ->(field, default="") { field.blank? ? default : field }
133
- print_not_zero = ->(field, default="") { field.to_i.zero? ? default : field }
134
140
  [(con_id.present? && !con_id.is_a?(Symbol) && con_id.to_i > 0 ? con_id : ""),
135
141
  print_default[symbol],
136
142
  print_default[self[:sec_type]],
137
143
  ( fields.include?(:option) ?
138
144
  [ print_default[expiry],
139
- print_not_zero[strike],
145
+ ## a Zero-Strike-Option has to be defined with «strike: -1 »
146
+ strike.present? && ( strike.is_a?(Numeric) && !strike.zero? && strike > 0 ) ? strike : strike<0 ? 0 : "",
140
147
  print_default[self[:right]],
141
148
  print_default[multiplier]] : nil ),
142
149
  print_default[exchange],
@@ -211,17 +218,22 @@ module IB
211
218
  # the link to contract-details is __not__ maintained.
212
219
  def essential
213
220
 
214
- self_attributes = [ :right, :sec_type]
215
- the_attributes = [ :symbol , :con_id, :exchange,
221
+ the_attributes = [ :sec_type, :symbol , :con_id, :exchange, :right,
216
222
  :currency, :expiry, :strike, :local_symbol, :last_trading_day,
217
- :multiplier, :primary_exchange, :trading_class ]
218
- the_hash= the_attributes.map{|x| y= attributes[x]; [x,y] if y.present? }.compact.to_h
219
- the_hash[:description] = @description if @description.present?
220
- self.class.new the_hash.merge( self_attributes.map{|x| y = self.send(x); [x,y] unless y == :none}.compact.to_h )
223
+ :multiplier, :primary_exchange, :trading_class, :description ]
224
+ new_contract= self.class.new invariant_attributes.select{|k,_| the_attributes.include? k }.compact
225
+ new_contract[:description] = if @description.present?
226
+ @description
227
+ elsif contract_detail.present?
228
+ contract_detail.long_name
229
+ else
230
+ ""
231
+ end
232
+ new_contract # return contract
221
233
  end
222
234
 
223
235
 
224
- # creates a new Contract substituting attributes by the provied key-value pairs.
236
+ # creates a new Contract substituting attributes by the provided key-value pairs.
225
237
  #
226
238
  # con_id is resetted
227
239
  def merge **new_attributes
@@ -327,6 +339,10 @@ module IB
327
339
  self[:sec_type] == 'IND'
328
340
  end
329
341
 
342
+
343
+ def verify # :nodoc:
344
+ error "verify must be overloaded. Please require at least `ib/verify` from the `ib-extenstions` gem "
345
+ end
330
346
  =begin
331
347
  From the release notes of TWS 9.50
332
348
 
@@ -364,20 +380,33 @@ In places where these terms are used to indicate a concept, we have left them as
364
380
 
365
381
 
366
382
  ### Now let's deal with Contract subclasses
383
+ begin
367
384
 
368
- require 'models/ib/option'
385
+ require_relative 'option'
369
386
  require 'models/ib/bag'
370
387
  require 'models/ib/forex'
371
388
  require 'models/ib/future'
372
389
  require 'models/ib/stock'
373
390
  require 'models/ib/index'
391
+ ### walkaraound to enable spreads with orientdb
392
+ if IB::const_defined? :Spread
393
+ IB::send(:remove_const, :Spread)
394
+ #puts "Spread already defined"
395
+ #puts "erasing"
396
+ end
397
+ require 'models/ib/spread.rb'
398
+ end
399
+
400
+
374
401
 
375
402
  class Contract
376
403
  # Contract subclasses representing specialized security types.
404
+ using IBSupport
377
405
 
378
406
  Subclasses = Hash.new(Contract)
379
407
  Subclasses[:bag] = IB::Bag
380
408
  Subclasses[:option] = IB::Option
409
+ Subclasses[:futures_option] = IB::FutureOption
381
410
  Subclasses[:future] = IB::Future
382
411
  Subclasses[:stock] = IB::Stock
383
412
  Subclasses[:forex] = IB::Forex
@@ -385,31 +414,14 @@ In places where these terms are used to indicate a concept, we have left them as
385
414
 
386
415
 
387
416
  # This builds an appropriate Contract subclass based on its type
388
- #
389
- # the method is also used to copy Contract.values to new instances
417
+ #
418
+ # the method is also used to copy Contract.values to new instances
390
419
  def self.build opts = {}
391
420
  subclass =( VALUES[:sec_type][opts[:sec_type]] || opts['sec_type'] || opts[:sec_type]).to_sym
392
421
  Contract::Subclasses[subclass].new opts
393
422
  end
394
423
 
395
- # This returns a Contract initialized from the serialize_ib_ruby format string.
396
- def self.from_ib_ruby
397
- keys = [:con_id, :symbol, :sec_type, :expiry, :strike, :right, :multiplier,
398
- :exchange, :primary_exchange, :currency, :local_symbol]
399
- props = Hash[keys.zip(string.split(":"))]
400
- props.delete_if { |k, v| v.nil? || v.empty? }
401
- Contract.build props
402
- end
424
+
403
425
  end # class Contract
404
426
  end # module IB
405
427
 
406
- class String
407
- def to_contract
408
- keys = [:con_id, :symbol, :sec_type, :expiry, :strike, :right, :multiplier,
409
- :exchange, :primary_exchange, :currency, :local_symbol]
410
- props = Hash[keys.zip(split(":"))]
411
- props.delete_if { |k, v| v.nil? || v.empty? }
412
- IB::Contract.build props
413
-
414
- end
415
- end
@@ -95,13 +95,19 @@ module IB
95
95
  end
96
96
 
97
97
  def to_human
98
- ret = "<ContractDetails #{long_name}, market-name:#{market_name}, "
99
- ret << "category:#{category}, industry:#{industry} / #{subcategory}, " if category.present?
100
- ret << "underlying: con_id:#{under_con_id} , sec_type:#{under_sec_type}, symbol:#{under_symbol} " unless under_con_id.zero?
101
- ret << "ev_multiplier:#{ev_multiplier}, convertible:#{convertible}, cupon:#{coupon}, "
102
- ret << "md_size_multiplier:#{md_size_multiplier}, min_tick:#{min_tick}, next_option_partial:#{next_option_partial} "
103
- ret <<"price_magnifier:#{price_magnifier}, puttable:#{puttable}, sec_id-list:#{sec_id_list}, "
104
- ret <<"valid exchanges: #{ valid_exchanges}, order types: #{order_types} >"
98
+ ret = "<ContractDetails #{long_name}, "
99
+ ret << "--> #{market_name}, " if market_name.present?
100
+ ret << "/C/ #{category}, /I/ #{industry} /SC/ #{subcategory}, " if category.present?
101
+ ret << "Underlying:#{under_symbol}[#{under_sec_type}](#{under_con_id}), " unless under_con_id.zero?
102
+ ret << "ev_multiplier:#{ev_multiplier}, " if ev_multiplier.present?
103
+ ret << "convertible:#{convertible}, " if convertible
104
+ ret << "coupon:#{coupon}, " if coupon.present? && coupon > 0
105
+ ret << "md_size_multiplier:#{md_size_multiplier}, min_tick:#{min_tick}, "
106
+ ret << "next_option_partial:#{next_option_partial}, " if next_option_partial.present?
107
+ ret << "price_magnifier:#{price_magnifier}, "
108
+ ret << "puttable:#{puttable}, " if puttable.present?
109
+ ret << "sec_id-list:#{sec_id_list}, " unless sec_id_list.empty?
110
+ ret <<"valid exchanges: #{ valid_exchanges}; order types: #{order_types} >"
105
111
  end
106
112
 
107
113
  end # class ContractDetail
@@ -7,7 +7,7 @@ module IB
7
7
  super.merge :sec_type => :ind
8
8
  end
9
9
  def to_human
10
- "<Index: " + [symbol, currency].join(" ") + ">"
10
+ "<Index: " + [symbol, currency].join(" ") + " (#{description}) >"
11
11
  end
12
12
 
13
13
  end
@@ -1,5 +1,5 @@
1
- require 'models/ib/contract'
2
- require 'models/ib/option_detail'
1
+ #require_relative 'contract'
2
+ require_relative 'option_detail'
3
3
 
4
4
  module IB
5
5
  class Option < Contract
@@ -75,4 +75,10 @@ module IB
75
75
  end
76
76
 
77
77
  end # class Option
78
+
79
+ class FutureOption < Option
80
+ def default_attributes
81
+ super.merge :sec_type => :futures_option
82
+ end
83
+ end
78
84
  end # module IB
@@ -17,7 +17,8 @@ module IB
17
17
  :next_strike,
18
18
  :prev_expiry,
19
19
  :next_expiry,
20
- :option_price
20
+ :option_price,
21
+ :updated_at
21
22
  belongs_to :option
22
23
 
23
24
  # returns true if all datafields are filled with reasonal data
@@ -32,19 +33,34 @@ module IB
32
33
 
33
34
  def greeks?
34
35
  fields= [ :delta, :gamma, :vega, :theta,
35
- :implied_volatility, :pv_dividend]
36
+ :implied_volatility]
36
37
 
37
38
  !fields.detect{|y| self.send(y).nil?}
38
39
 
39
40
  end
40
41
 
42
+ def prices?
43
+ fields = [:implied_volatility, :under_price, :option_price]
44
+ !fields.detect{|y| self.send(y).nil?}
45
+ end
46
+
47
+ def iv
48
+ implied_volatility
49
+ end
50
+
51
+ def spread
52
+ bid_price - ask_price
53
+ end
54
+
41
55
  def to_human
42
56
  outstr= ->( item ) { if item.nil? then "--" else sprintf("%g" , item) end }
43
57
  att = " optionPrice: #{ outstr[ option_price ]}, UnderlyingPrice: #{ outstr[ under_price] } impl.Vola: #{ outstr[ implied_volatility ]} ; dividend: #{ outstr[ pv_dividend ]}; "
44
58
  greeks = "Greeks:: delta: #{ outstr[ delta ] }; gamma: #{ outstr[ gamma ]}, vega: #{ outstr[ vega ] }; theta: #{ outstr[ theta ]}"
45
59
  prices= " close: #{ outstr[ close_price ]}; bid: #{ outstr[ bid_price ]}; ask: #{ outstr[ ask_price ]} "
46
60
  if complete?
47
- "< "+ prices + "\n" + att + "\n" + greeks + " >"
61
+ "< "+ prices + "\n" + att + "\n" + greeks + " >"
62
+ elsif prices?
63
+ "< " + att + greeks + " >"
48
64
  else
49
65
  "< " + greeks + " >"
50
66
  end
@@ -514,6 +514,11 @@ Format of serialisation
514
514
  (account ? "/#{account}" : '') +
515
515
  (commission ? " fee #{commission}" : '') + ">"
516
516
  end
517
+ def serialize_rabbit
518
+ { 'Contract' => contract.present? ? contract.serialize( :option, :trading_class ): '' ,
519
+ 'Order' => self,
520
+ 'OrderState' => order_state}
521
+ end
517
522
 
518
523
  end # class Order
519
524
  end # module IB
@@ -0,0 +1,168 @@
1
+ # require 'ib/verify'
2
+ module IB
3
+ if defined?(Spread)
4
+ puts "Bag already a #{defined?(Spread)}"
5
+
6
+ # puts Spread.ancestors
7
+ IB.send(:remove_const, 'Spread')
8
+ end
9
+ class Spread < Bag
10
+ has_many :legs
11
+
12
+ using IBSupport
13
+
14
+ =begin
15
+ Parameters: front: YYYMM(DD)
16
+ back: {n}w, {n}d or YYYYMM(DD)
17
+
18
+ Adds (or substracts) relative (back) measures to the front month, just passes absolute YYYYMM(DD) value
19
+
20
+ front: 201809 back: 2m (-1m) --> 201811 (201808)
21
+ front: 20180908 back: 1w (-1w) --> 20180918 (20180902)
22
+ =end
23
+
24
+ def transform_distance front, back
25
+ # Check Format of back: 201809 --> > 200.000
26
+ # 20180989 ---> 20.000.000
27
+ start_date = front.to_i < 20000000 ? Date.strptime(front.to_s,"%Y%m") : Date.strptime(front.to_s,"%Y%m%d")
28
+ nb = if back.to_i > 200000
29
+ back.to_i
30
+ elsif back[-1] == "w" && front.to_i > 20000000
31
+ start_date + (back.to_i * 7)
32
+ elsif back[-1] == "m" && front.to_i > 200000
33
+ start_date >> back.to_i
34
+ else
35
+ error "Wrong date #{back} required format YYYMM, YYYYMMDD ord {n}w or {n}m"
36
+ end
37
+ if nb.is_a?(Date)
38
+ if back[-1]=='w'
39
+ nb.strftime("%Y%m%d")
40
+ else
41
+ nb.strftime("%Y%m")
42
+ end
43
+ else
44
+ nb
45
+ end
46
+ end # def
47
+
48
+ def to_human
49
+ self.description
50
+ end
51
+
52
+ def calculate_spread_value( array_of_portfolio_values )
53
+ array_of_portfolio_values.map{|x| x.send yield }.sum if block_given?
54
+ end
55
+
56
+ def fake_portfolio_position( array_of_portfolio_values )
57
+ calculate_spread_value= ->( a_o_p_v, attribute ) do
58
+ a_o_p_v.map{|x| x.send attribute }.sum
59
+ end
60
+ ar=array_of_portfolio_values
61
+ IB::PortfolioValue.new contract: self,
62
+ average_cost: calculate_spread_value[ar, :average_cost],
63
+ market_price: calculate_spread_value[ar, :market_price],
64
+ market_value: calculate_spread_value[ar, :market_value],
65
+ unrealized_pnl: calculate_spread_value[ar, :unrealized_pnl],
66
+ realized_pnl: calculate_spread_value[ar, :realized_pnl],
67
+ position: 0
68
+
69
+ end
70
+
71
+
72
+ # adds a leg to any spread
73
+ #
74
+ # Parameter:
75
+ # contract: Will be verified. Contract.essential is added to legs-array
76
+ # action: :buy or :sell
77
+ # weight:
78
+ # ratio:
79
+ #
80
+ # Default: action: :buy, weight: 1
81
+
82
+ def add_leg contract, **leg_params
83
+ evaluated_contracts = []
84
+ nc = contract.verify.first.essential
85
+ # weigth = 1 --> sets Combo.side to buy and overwrites the action statement
86
+ # leg_params[:weight] = 1 unless leg_params.key?(:weight) || leg_params.key?(:ratio)
87
+ if nc.is_a?( IB::Contract) && nc.con_id.present?
88
+ the_leg= ComboLeg.new( nc.attributes.slice( :con_id, :exchange )
89
+ .merge( leg_params ))
90
+ self.combo_legs << the_leg
91
+ self.description = "#{description.nil? ? "": description} added #{nc.to_human}" rescue "Spread: #{nc.to_human}"
92
+ self.legs << nc
93
+ end
94
+ self # return value to enable chaining
95
+
96
+
97
+ end
98
+
99
+ # removes the contract from the spread definition
100
+ #
101
+ def remove_leg contract
102
+ contract.verify do |c|
103
+ legs.delete_if { |x| x.con_id == c.con_id }
104
+ combo_legs.delete_if { |x| x.con_id == c.con_id }
105
+ self.description = description + " removed #{c.to_human}"
106
+ end
107
+ self
108
+ end
109
+
110
+ # essentail
111
+ # effectivley clones the object
112
+ #
113
+ def essential
114
+ the_es = self.class.new invariant_attributes
115
+ the_es.legs = legs.map{|y| IB::Contract.build y.invariant_attributes}
116
+ the_es.combo_legs = combo_legs.map{|y| IB::ComboLeg.new y.invariant_attributes }
117
+ the_es.description = description
118
+ the_es # return
119
+ end
120
+
121
+ def multiplier
122
+ (legs.map(&:multiplier).sum/legs.size).to_i
123
+ end
124
+
125
+ # provide a negative con_id
126
+ def con_id
127
+ if attributes[:con_id].present? && attributes[] < 0
128
+ attributes[:con_id]
129
+ else
130
+ -legs.map{ |x| x.is_a?(String) ? x.expand.con_id : x.con_id}.sum
131
+ end
132
+ end
133
+
134
+
135
+ def non_guaranteed= x
136
+ super.merge combo_params: [ ['NonGuaranteed', x] ]
137
+ end
138
+
139
+
140
+ def non_guaranteed
141
+ combo_params['NonGuaranteed']
142
+ end
143
+ # optional: specify default order prarmeters for all spreads
144
+ # def order_requirements
145
+ # super.merge symbol: symbol
146
+ # end
147
+
148
+
149
+ def self.build_from_json container
150
+ read_leg = ->(a) do
151
+ IB::ComboLeg.new :con_id => a.read_int,
152
+ :ratio => a.read_int,
153
+ :action => a.read_string,
154
+ :exchange => a.read_string
155
+
156
+ end
157
+ object= self.new container['Spread'].clone.read_contract
158
+ object.legs = container['legs'].map{|x| IB::Contract.build x.clone.read_contract}
159
+ object.combo_legs = container['combo_legs'].map{ |x| read_leg[ x.clone ] }
160
+ object.description = container['misc'].clone.read_string
161
+ object
162
+
163
+ end
164
+
165
+ end
166
+
167
+
168
+ end