ib-extensions 1.0 → 1.1

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.
@@ -0,0 +1,20 @@
1
+ module IB
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
11
+
12
+ 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
18
+ end
19
+ end
20
+ end
@@ -8,188 +8,186 @@ end
8
8
  class Contract
9
9
 
10
10
 
11
-
12
- # returns the Option Chain of the contract (if available)
13
- #
14
- ## parameters
15
- ### right:: :call, :put, :straddle
16
- ### ref_price:: :request or a numeric value
17
- ### sort:: :strike, :expiry
18
- ### exchange:: List of Exchanges to be queried (Blank for all available Exchanges)
19
- def option_chain ref_price: :request, right: :put, sort: :strike, exchange: ''
20
-
21
- ib = Connection.current
22
-
23
- ## Enable Cashing of Definition-Matrix
24
- @option_chain_definition ||= []
25
-
26
- my_req = nil; finalize= false
27
-
28
- # -----------------------------------------------------------------------------------------------------
29
- # get OptionChainDefinition from IB ( instantiate cashed Hash )
30
- if @option_chain_definition.blank?
31
- sub_sdop = ib.subscribe( :SecurityDefinitionOptionParameterEnd ) { |msg| finalize = true if msg.request_id == my_req }
32
- sub_ocd = ib.subscribe( :OptionChainDefinition ) do | msg |
33
- if msg.request_id == my_req
34
- message = msg.data
35
- # transfer the first record to @option_chain_definition
36
- if @option_chain_definition.blank?
37
- @option_chain_definition = msg.data
38
-
39
- end
40
- # override @option_chain_definition if a decent combination of attributes is met
41
- # us- options: use the smart dataset
42
- # other options: prefer options of the default trading class
43
- if message[:exchange] == 'SMART'
44
- @option_chain_definition = msg.data
45
- finalize = true
46
- end
47
- if @option_chain_definition.blank? && message[:trading_class] == symbol
48
- @option_chain_definition = msg.data
49
- end
50
- end
51
- end
52
-
53
- c = verify.first # ensure a complete set of attributes
54
- my_req = ib.send_message :RequestOptionChainDefinition, con_id: c.con_id,
55
- symbol: c.symbol,
56
- exchange: c.sec_type == :future ? c.exchange : "", # BOX,CBOE',
57
- sec_type: c[:sec_type]
58
-
59
- Thread.new do
60
-
61
- Timeout::timeout(1, IB::TransmissionError,"OptionChainDefinition not received" ) do
62
- loop{ sleep 0.1; break if finalize }
63
- end
64
- ib.unsubscribe sub_sdop , sub_ocd
65
- end.join
66
- else
67
- Connection.logger.error { "#{to_human} : using cached data" }
68
- end
69
-
70
- # -----------------------------------------------------------------------------------------------------
71
- # select values and assign to options
72
- #
73
- unless @option_chain_definition.blank?
74
- requested_strikes = if block_given?
75
- ref_price = market_price if ref_price == :request
76
- if ref_price.nil?
77
- ref_price = @option_chain_definition[:strikes].min +
78
- ( @option_chain_definition[:strikes].max -
79
- @option_chain_definition[:strikes].min ) / 2
80
- Connection.logger.error{ "#{to_human} :: market price not set using midpoint of available strikes instead: #{ref_price.to_f}" }
81
- end
82
- atm_strike = @option_chain_definition[:strikes].min_by { |x| (x - ref_price).abs }
83
- the_grouped_strikes = @option_chain_definition[:strikes].group_by{|e| e <=> atm_strike}
84
- begin
85
- the_strikes = yield the_grouped_strikes
86
- the_strikes.unshift atm_strike unless the_strikes.first == atm_strike # the first item is the atm-strike
87
- the_strikes
88
- rescue
89
- Connection.logger.error "#{to_human} :: not enough strikes :#{@option_chain_definition[:strikes].map(&:to_f).join(',')} "
90
- []
91
- end
92
- else
93
- @option_chain_definition[:strikes]
94
- end
95
-
96
- # third Friday of a month
97
- monthly_expirations = @option_chain_definition[:expirations].find_all{|y| (15..21).include? y.day }
98
- # puts @option_chain_definition.inspect
99
- option_prototype = -> ( ltd, strike ) do
100
- IB::Option.new( symbol: symbol,
101
- exchange: @option_chain_definition[:exchange],
102
- trading_class: @option_chain_definition[:trading_class],
103
- multiplier: @option_chain_definition[:multiplier],
104
- currency: currency,
105
- last_trading_day: ltd,
106
- strike: strike,
107
- right: right )
108
- end
109
- options_by_expiry = -> ( schema ) do
110
- # Array: [ yymm -> Options] prepares for the correct conversion to a Hash
111
- Hash[ monthly_expirations.map do | l_t_d |
112
- [ l_t_d.strftime('%y%m').to_i , schema.map{ | strike | option_prototype[ l_t_d, strike ]}.compact ]
113
- end ] # by Hash[ ]
114
- end
115
- options_by_strike = -> ( schema ) do
116
- Hash[ schema.map do | strike |
117
- [ strike , monthly_expirations.map{ | l_t_d | option_prototype[ l_t_d, strike ]}.compact ]
118
- end ] # by Hash[ ]
119
- end
120
-
121
- if sort == :strike
122
- options_by_strike[ requested_strikes ]
123
- else
124
- options_by_expiry[ requested_strikes ]
125
- end
126
- else
127
- Connection.logger.error "#{to_human} ::No Options available"
128
- nil # return_value
129
- end
130
- end # def
131
-
132
- # return a set of AtTheMoneyOptions
133
- def atm_options ref_price: :request, right: :put
134
- option_chain( right: right, ref_price: ref_price, sort: :expiry) do | chain |
135
- chain[0]
136
- end
137
-
138
-
139
- end
140
-
141
- # return InTheMoneyOptions
142
- def itm_options count: 5, right: :put, ref_price: :request, sort: :strike
143
- option_chain( right: right, ref_price: ref_price, sort: sort ) do | chain |
144
- if right == :put
145
- above_market_price_strikes = chain[1][0..count-1]
146
- else
147
- below_market_price_strikes = chain[-1][-count..-1].reverse
148
- end # branch
149
- end
150
- end # def
151
-
152
- # return OutOfTheMoneyOptions
153
- def otm_options count: 5, right: :put, ref_price: :request, sort: :strike
154
- option_chain( right: right, ref_price: ref_price, sort: sort ) do | chain |
155
- if right == :put
156
- # puts "Chain: #{chain}"
157
- below_market_price_strikes = chain[-1][-count..-1].reverse
158
- else
159
- above_market_price_strikes = chain[1][0..count-1]
160
- end
161
- end
162
- end
163
-
164
-
165
- def associate_ticdata
166
-
167
- tws= IB::Connection.current # get the initialized ib-ruby instance
168
- the_id = nil
169
- finalize= false
170
- # switch to delayed data
171
- tws.send_message :RequestMarketDataType, :market_data_type => :delayed
172
-
173
- s_id = tws.subscribe(:TickSnapshotEnd) { |msg| finalize = true if msg.ticker_id == the_id }
174
-
175
- sub_id = tws.subscribe(:TickPrice, :TickSize, :TickGeneric, :TickOption) do |msg|
176
- self.bars << msg.the_data if msg.ticker_id == the_id
177
- end
178
-
179
- # initialize »the_id« that is used to identify the received tick messages
180
- # by firing the market data request
181
- the_id = tws.send_message :RequestMarketData, contract: self , snapshot: true
182
-
183
- #keep the method-call running until the request finished
184
- #and cancel subscriptions to the message handler.
185
- Thread.new do
186
- i=0; loop{ i+=1; sleep 0.1; break if finalize || i > 1000 }
187
- tws.unsubscribe sub_id
188
- tws.unsubscribe s_id
189
- #puts "#{symbol} data gathered"
190
- end # method returns the (running) thread
191
-
192
- end # def
11
+
12
+ # returns the Option Chain of the contract (if available)
13
+ #
14
+ ## parameters
15
+ ### right:: :call, :put, :straddle
16
+ ### ref_price:: :request or a numeric value
17
+ ### sort:: :strike, :expiry
18
+ ### exchange:: List of Exchanges to be queried (Blank for all available Exchanges)
19
+ def option_chain ref_price: :request, right: :put, sort: :strike, exchange: ''
20
+
21
+ ib = Connection.current
22
+ finalize = Queue.new
23
+
24
+ ## Enable Cashing of Definition-Matrix
25
+ @option_chain_definition ||= []
26
+
27
+ my_req = nil
28
+
29
+ # -----------------------------------------------------------------------------------------------------
30
+ # get OptionChainDefinition from IB ( instantiate cashed Hash )
31
+ if @option_chain_definition.blank?
32
+ sub_sdop = ib.subscribe( :SecurityDefinitionOptionParameterEnd ) { |msg| finalize.push(true) if msg.request_id == my_req }
33
+ sub_ocd = ib.subscribe( :OptionChainDefinition ) do | msg |
34
+ if msg.request_id == my_req
35
+ message = msg.data
36
+ # transfer the first record to @option_chain_definition
37
+ if @option_chain_definition.blank?
38
+ @option_chain_definition = msg.data
39
+ end
40
+ # override @option_chain_definition if a decent combination of attributes is met
41
+ # us- options: use the smart dataset
42
+ # other options: prefer options of the default trading class
43
+ if message[:exchange] == 'SMART'
44
+ @option_chain_definition = msg.data
45
+ finalize.push(true)
46
+ end
47
+ if message[:trading_class] == symbol
48
+ @option_chain_definition = msg.data
49
+ finalize.push(true)
50
+ end
51
+ end
52
+ end
53
+
54
+ c = verify.first # ensure a complete set of attributes
55
+ my_req = ib.send_message :RequestOptionChainDefinition, con_id: c.con_id,
56
+ symbol: c.symbol,
57
+ exchange: c.sec_type == :future ? c.exchange : "", # BOX,CBOE',
58
+ sec_type: c[:sec_type]
59
+
60
+ finalize.pop # wait until data appeared
61
+ #i=0; loop { sleep 0.1; break if i> 1000 || finalize; i+=1 }
62
+
63
+ ib.unsubscribe sub_sdop , sub_ocd
64
+ else
65
+ Connection.logger.info { "#{to_human} : using cached data" }
66
+ end
67
+
68
+ # -----------------------------------------------------------------------------------------------------
69
+ # select values and assign to options
70
+ #
71
+ unless @option_chain_definition.blank?
72
+ requested_strikes = if block_given?
73
+ ref_price = market_price if ref_price == :request
74
+ if ref_price.nil?
75
+ ref_price = @option_chain_definition[:strikes].min +
76
+ ( @option_chain_definition[:strikes].max -
77
+ @option_chain_definition[:strikes].min ) / 2
78
+ Connection.logger.warn { "#{to_human} :: market price not set – using midpoint of available strikes instead: #{ref_price.to_f}" }
79
+ end
80
+ atm_strike = @option_chain_definition[:strikes].min_by { |x| (x - ref_price).abs }
81
+ the_grouped_strikes = @option_chain_definition[:strikes].group_by{|e| e <=> atm_strike}
82
+ begin
83
+ the_strikes = yield the_grouped_strikes
84
+ the_strikes.unshift atm_strike unless the_strikes.first == atm_strike # the first item is the atm-strike
85
+ the_strikes
86
+ rescue
87
+ Connection.logger.error "#{to_human} :: not enough strikes :#{@option_chain_definition[:strikes].map(&:to_f).join(',')} "
88
+ []
89
+ end
90
+ else
91
+ @option_chain_definition[:strikes]
92
+ end
93
+
94
+ # third Friday of a month
95
+ monthly_expirations = @option_chain_definition[:expirations].find_all{|y| (15..21).include? y.day }
96
+ # puts @option_chain_definition.inspect
97
+ option_prototype = -> ( ltd, strike ) do
98
+ IB::Option.new symbol: symbol,
99
+ exchange: @option_chain_definition[:exchange],
100
+ trading_class: @option_chain_definition[:trading_class],
101
+ multiplier: @option_chain_definition[:multiplier],
102
+ currency: currency,
103
+ last_trading_day: ltd,
104
+ strike: strike,
105
+ right: right
106
+ end
107
+ options_by_expiry = -> ( schema ) do
108
+ # Array: [ yymm -> Options] prepares for the correct conversion to a Hash
109
+ Hash[ monthly_expirations.map do | l_t_d |
110
+ [ l_t_d.strftime('%y%m').to_i , schema.map{ | strike | option_prototype[ l_t_d, strike ]}.compact ]
111
+ end ] # by Hash[ ]
112
+ end
113
+ options_by_strike = -> ( schema ) do
114
+ Hash[ schema.map do | strike |
115
+ [ strike , monthly_expirations.map{ | l_t_d | option_prototype[ l_t_d, strike ]}.compact ]
116
+ end ] # by Hash[ ]
117
+ end
118
+
119
+ if sort == :strike
120
+ options_by_strike[ requested_strikes ]
121
+ else
122
+ options_by_expiry[ requested_strikes ]
123
+ end
124
+ else
125
+ Connection.logger.error "#{to_human} ::No Options available"
126
+ nil # return_value
127
+ end
128
+ end # def
129
+
130
+ # return a set of AtTheMoneyOptions
131
+ def atm_options ref_price: :request, right: :put
132
+ option_chain( right: right, ref_price: ref_price, sort: :expiry) do | chain |
133
+ chain[0]
134
+ end
135
+
136
+
137
+ end
138
+
139
+ # return InTheMoneyOptions
140
+ def itm_options count: 5, right: :put, ref_price: :request, sort: :strike
141
+ option_chain( right: right, ref_price: ref_price, sort: sort ) do | chain |
142
+ if right == :put
143
+ above_market_price_strikes = chain[1][0..count-1]
144
+ else
145
+ below_market_price_strikes = chain[-1][-count..-1].reverse
146
+ end # branch
147
+ end
148
+ end # def
149
+
150
+ # return OutOfTheMoneyOptions
151
+ def otm_options count: 5, right: :put, ref_price: :request, sort: :strike
152
+ option_chain( right: right, ref_price: ref_price, sort: sort ) do | chain |
153
+ if right == :put
154
+ # puts "Chain: #{chain}"
155
+ below_market_price_strikes = chain[-1][-count..-1].reverse
156
+ else
157
+ above_market_price_strikes = chain[1][0..count-1]
158
+ end
159
+ end
160
+ end
161
+
162
+
163
+ def associate_ticdata
164
+
165
+ tws= IB::Connection.current # get the initialized ib-ruby instance
166
+ the_id = nil
167
+ finalize= false
168
+ # switch to delayed data
169
+ tws.send_message :RequestMarketDataType, :market_data_type => :delayed
170
+
171
+ s_id = tws.subscribe(:TickSnapshotEnd) { |msg| finalize = true if msg.ticker_id == the_id }
172
+
173
+ sub_id = tws.subscribe(:TickPrice, :TickSize, :TickGeneric, :TickOption) do |msg|
174
+ self.bars << msg.the_data if msg.ticker_id == the_id
175
+ end
176
+
177
+ # initialize »the_id« that is used to identify the received tick messages
178
+ # by firing the market data request
179
+ the_id = tws.send_message :RequestMarketData, contract: self , snapshot: true
180
+
181
+ #keep the method-call running until the request finished
182
+ #and cancel subscriptions to the message handler.
183
+ Thread.new do
184
+ i=0; loop{ i+=1; sleep 0.1; break if finalize || i > 1000 }
185
+ tws.unsubscribe sub_id
186
+ tws.unsubscribe s_id
187
+ #puts "#{symbol} data gathered"
188
+ end # method returns the (running) thread
189
+
190
+ end # def
193
191
  end # class
194
192
 
195
193
 
@@ -67,7 +67,7 @@ module IB
67
67
  end
68
68
 
69
69
  # initialise order with given attributes
70
- IB::Order.new the_arguments
70
+ IB::Order.new **the_arguments
71
71
  end
72
72
 
73
73
  def alternative_parameters
@@ -1,4 +1,4 @@
1
- require 'ib/models/spread'
1
+ #require 'ib/models/spread'
2
2
  require 'ib/verify'
3
3
  # These modules are used to facilitate referencing of most common Spreads
4
4
 
@@ -24,7 +24,7 @@ module IB
24
24
 
25
25
  def initialize_spread ref_contract = nil, **attributes
26
26
  error "Initializing of Spread failed – contract is missing" unless ref_contract.is_a?(IB::Contract)
27
- the_contract = ref_contract.merge( attributes ).verify.first
27
+ the_contract = ref_contract.merge( **attributes ).verify.first
28
28
  error "Underlying for Spread is not valid: #{ref_contract.to_human}" if the_contract.nil?
29
29
  the_spread= IB::Spread.new the_contract.attributes.slice( :exchange, :symbol, :currency )
30
30
  error "Initializing of Spread failed – Underling is no Contract" if the_spread.nil?
@@ -19,7 +19,7 @@ module IB
19
19
  def fabricate *underlying, ratio: [1,-1], **args
20
20
  #
21
21
  are_stocks = ->(l){ l.all?{|y| y.is_a? IB::Stock} }
22
- legs = underlying.map{|y| y.is_a?( IB::Stock ) ? y.merge(args) : IB::Stock.new( symbol: y ).merge(args)}
22
+ legs = underlying.map{|y| y.is_a?( IB::Stock ) ? y.merge(**args) : IB::Stock.new( symbol: y ).merge(**args)}
23
23
  error "only spreads with two underyings of type »IB::Stock« are supported" unless legs.size==2 && are_stocks[legs]
24
24
  initialize_spread( legs.first ) do | the_spread |
25
25
  c_l = legs.zip(ratio).map do |l,r|
data/lib/ib/verify.rb CHANGED
@@ -1,226 +1,196 @@
1
1
  module IB
2
2
  # define a custom ErrorClass which can be fired if a verification fails
3
- class VerifyError < StandardError
4
- end
5
-
6
- class Contract
7
-
8
-
9
- # IB::Contract#Verify
10
-
11
- # verifies the contract
12
- #
13
- # returns the number of contracts retured by the TWS.
14
- #
15
- #
16
- # The method accepts a block. The queried contract-Object is assessible there.
17
- # If multible contracts are specified, the block is executed with each of these contracts.
18
- #
19
- # Parameter: thread: (true/false)
20
- #
21
- # The verifiying-process ist time consuming. If multible contracts are to be verified,
22
- # they can be queried simultaniously.
23
- # IB::Symbols::W500.map{|c| c.verify(thread: true){ |vc| do_something }}.join
24
- #
25
- # A simple verification works as follows:
26
- #
27
- # s = IB::Stock.new symbol:"A"
28
- # s --> <IB::Stock:0x007f3de81a4398
29
- # @attributes= {"symbol"=>"A", "sec_type"=>"STK", "currency"=>"USD", "exchange"=>"SMART"}>
30
- # s.verify --> 1
31
- # # s is unchanged !
32
- #
33
- # s.verify{ |c| puts c.inspect }
34
- # --> <IB::Stock:0x007f3de81a4398
35
- # @attributes={"symbol"=>"A", "updated_at"=>2015-04-17 19:20:00 +0200,
36
- # "sec_type"=>"STK", "currency"=>"USD", "exchange"=>"SMART",
37
- # "con_id"=>1715006, "expiry"=>"", "strike"=>0.0, "local_symbol"=>"A",
38
- # "multiplier"=>0, "primary_exchange"=>"NYSE"},
39
- # @contract_detail=#<IB::ContractDetail:0x007f3de81ed7c8
40
- # @attributes={"market_name"=>"A", "trading_class"=>"A", "min_tick"=>0.01,
41
- # "order_types"=>"ACTIVETIM, (...),WHATIF,",
42
- # "valid_exchanges"=>"SMART,NYSE,CBOE,ISE,CHX,(...)PSX",
43
- # "price_magnifier"=>1, "under_con_id"=>0,
44
- # "long_name"=>"AGILENT TECHNOLOGIES INC", "contract_month"=>"",
45
- # "industry"=>"Industrial", "category"=>"Electronics",
46
- # "subcategory"=>"Electronic Measur Instr", "time_zone"=>"EST5EDT",
47
- # "trading_hours"=>"20150417:0400-2000;20150420:0400-2000",
48
- # "liquid_hours"=>"20150417:0930-1600;20150420:0930-1600",
49
- # "ev_rule"=>0.0, "ev_multiplier"=>"", "sec_id_list"=>{},
50
- # "updated_at"=>2015-04-17 19:20:00 +0200, "coupon"=>0.0,
51
- # "callable"=>false, "puttable"=>false, "convertible"=>false,
52
- # "next_option_partial"=>false}>>
53
- #
54
- #
55
- def verify thread: nil, &b
56
- return [self] if contract_detail.present? || sec_type == :bag
57
- _verify update: false, thread: thread, &b # returns the allocated threads
58
- end # def
59
-
60
- # returns a hash
61
- def nessesary_attributes
62
-
63
- v= { stock: { currency: 'USD', exchange: 'SMART', symbol: nil} ,
64
- option: { currency: 'USD', exchange: 'SMART', right: 'P', expiry: nil, strike: nil, symbol: nil} ,
65
- future: { currency: 'USD', exchange: nil, expiry: nil, symbol: nil } ,
66
- forex: { currency: 'USD', exchange: 'IDEALPRO', symbol: nil }
67
- }
68
- sec_type.present? ? v[sec_type] : { con_id: nil, exchange: 'SMART' } # enables to use only con_id for verifying
3
+ # class VerifyError < StandardError
4
+ # end
5
+
6
+ class Contract
7
+
8
+ # IB::Contract#Verify
9
+
10
+ # verifies the contract
11
+ #
12
+ # returns the number of contracts returned by the TWS.
13
+ #
14
+ #
15
+ # The method accepts a block. The queried contract-Object is accessible there.
16
+ # If multiple contracts are specified, the block is executed with each of these contracts.
17
+ #
18
+ #
19
+ # Verify returns an _Array_ of contracts. The operation leaves the contract untouched.
20
+ #
21
+ #
22
+ # Returns nil if the contract could not be verified.
23
+ #
24
+ # > s = Stock.new symbol: 'AA'
25
+ # => #<IB::Stock:0x0000000002626cc0
26
+ # @attributes={:symbol=>"AA", :con_id=>0, :right=>"", :include_expired=>false,
27
+ # :sec_type=>"STK", :currency=>"USD", :exchange=>"SMART"}
28
+ # > sp = s.verify.first.essential
29
+ # => #<IB::Stock:0x00000000025a3cf8
30
+ # @attributes={:symbol=>"AA", :con_id=>251962528, :exchange=>"SMART", :currency=>"USD",
31
+ # :strike=>0.0, :local_symbol=>"AA", :multiplier=>0, :primary_exchange=>"NYSE",
32
+ # :trading_class=>"AA", :sec_type=>"STK", :right=>"", :include_expired=>false}
33
+ #
34
+ # > s = Stock.new symbol: 'invalid'
35
+ # => @attributes={:symbol=>"invalid", :sec_type=>"STK", :currency=>"USD", :exchange=>"SMART"}
36
+ # > sp = s.verify
37
+ # => []
38
+ #
39
+ # Takes a Block to modify the queried contracts
40
+ #
41
+ # f = Future.new symbol: 'M2K'
42
+ # con_ids = f.verify{ |c| c.con_id }
43
+ # [412889018, 428519982, 446091466, 461318872, 477836981]
44
+ #
45
+ #
46
+ # Parameter: thread: (true/false)
47
+ #
48
+ # If multiple contracts are to be verified, they can be queried simultaneously.
49
+ # IB::Symbols::W500.map{|c| c.verify(thread: true){ |vc| do_something }}.join
50
+
51
+ def verify thread: nil, &b
52
+ return [self] if contract_detail.present? || sec_type == :bag
53
+ _verify update: false, thread: thread, &b # returns the allocated threads
54
+ end # def
55
+
56
+ # returns a hash
57
+ def nessesary_attributes
58
+
59
+ v= { stock: { currency: 'USD', exchange: 'SMART', symbol: nil},
60
+ option: { currency: 'USD', exchange: 'SMART', right: 'P', expiry: nil, strike: nil, symbol: nil},
61
+ future: { currency: 'USD', exchange: nil, expiry: nil, symbol: nil },
62
+ forex: { currency: 'USD', exchange: 'IDEALPRO', symbol: nil }
63
+ }
64
+ sec_type.present? ? v[sec_type] : { con_id: nil, exchange: 'SMART' } # enables to use only con_id for verifying
69
65
  # if the contract allows SMART routing
70
- end
71
-
72
- # Verify that the contract is a valid IB::Contract, update the Contract-Object and return it.
73
- #
74
- # Returns nil if the contract could not be verified.
75
- #
76
- # > s = Stock.new symbol: 'AA'
77
- # => #<IB::Stock:0x0000000002626cc0
78
- # @attributes={:symbol=>"AA", :con_id=>0, :right=>"", :include_expired=>false,
79
- # :sec_type=>"STK", :currency=>"USD", :exchange=>"SMART"}
80
- # > sp = s.verify! &.essential
81
- # => #<IB::Stock:0x00000000025a3cf8
82
- # @attributes={:symbol=>"AA", :con_id=>251962528, :exchange=>"SMART", :currency=>"USD",
83
- # :strike=>0.0, :local_symbol=>"AA", :multiplier=>0, :primary_exchange=>"NYSE",
84
- # :trading_class=>"AA", :sec_type=>"STK", :right=>"", :include_expired=>false}
85
- #
86
- # > s = Stock.new symbol: 'invalid'
87
- # => @attributes={:symbol=>"invalid", :sec_type=>"STK", :currency=>"USD", :exchange=>"SMART"}
88
- # > sp = s.verify! &.essential
89
- # => nil
90
-
91
- def verify!
92
- return self if contract_detail.present? || sec_type == :bag
93
- c = 0
94
- _verify( update: true){| response | c+=1 } # wait for the returned thread to finish
95
- IB::Connection.logger.error { "Multible Contracts detected during verify!." } if c > 1
96
- con_id.to_i < 0 || contract_detail.is_a?(ContractDetail) ? self : nil
97
- end
98
-
99
- # private
100
-
101
- # Base method to verify a contract
102
- #
103
- # if :thread is given, the method subscribes to messages, fires the request and returns the thread, that
104
- # receives the exit-condition-message
105
- #
106
- # otherwise the method waits until the response form tws is processed
107
- #
108
- #
109
- # if :update is true, the attributes of the Contract itself are apdated
110
- #
111
- # otherwise the Contract is untouched
112
- def _verify thread: nil , update:, &b # :nodoc:
113
-
114
- ib = Connection.current
115
- # we generate a Request-Message-ID on the fly
116
- message_id = nil
117
- # define local vars which are updated within the query-block
118
- exitcondition, count , queried_contract, r = false, 0, nil, []
119
-
120
- # currently the tws-request is suppressed for bags and if the contract_detail-record is present
121
- tws_request_not_nessesary = bag? || contract_detail.is_a?( ContractDetail )
122
-
123
- if tws_request_not_nessesary
124
- yield self if block_given?
125
- count = 1
126
- else # subscribe to ib-messages and describe what to do
127
- a = ib.subscribe(:Alert, :ContractData, :ContractDataEnd) do |msg|
128
- case msg
129
- when Messages::Incoming::Alert
130
- if msg.code == 200 && msg.error_id == message_id
131
- ib.logger.error { "Not a valid Contract :: #{self.to_human} " }
132
- exitcondition = true
133
- end
134
- when Messages::Incoming::ContractData
135
- if msg.request_id.to_i == message_id
136
- # if multible contracts are present, all of them are assigned
137
- # Only the last contract is saved in self; 'count' is incremented
138
- count +=1
139
- ## a specified block gets the contract_object on any uniq ContractData-Event
140
- r << if block_given?
141
- yield msg.contract
142
- elsif count > 1
143
- queried_contract = msg.contract # used by the logger (below) in case of mulible contracts
144
- else
145
- msg.contract
146
- end
147
- if update
148
- self.attributes = msg.contract.attributes
149
- self.contract_detail = msg.contract_detail unless msg.contract_detail.nil?
150
- end
151
- end
152
- when Messages::Incoming::ContractDataEnd
153
- exitcondition = true if msg.request_id.to_i == message_id
154
-
155
- end # case
156
- end # subscribe
157
-
158
- ### send the request !
159
- # contract_to_be_queried = con_id.present? ? self : query_contract
160
- # if no con_id is present, the given attributes are checked by query_contract
161
- # if contract_to_be_queried.present? # is nil if query_contract fails
162
- message_id = ib.send_message :RequestContractData, :contract => query_contract
163
-
164
- th = Thread.new do
165
- begin
166
- Timeout::timeout(1) do
167
- loop{ break if exitcondition ; sleep 0.005 }
168
- end
169
- rescue Timeout::Error
170
- Connection.logger.error{ "#{to_human} --> No ContractData recieved " }
171
- end
172
- ib.unsubscribe a
173
- end
174
- if thread.nil?
175
- th.join # wait for the thread to finish
176
- r # return array of contracts
177
- else
178
- th # return active thread
179
- end
180
- # else
181
- # ib.logger.error { "Not a valid Contract-spezification, #{self.to_human}" }
182
- end
183
- # end
184
- end
185
-
186
- # Generates an IB::Contract with the required attributes to retrieve a unique contract from the TWS
187
- #
188
- # Background: If the tws is queried with a »complete« IB::Contract, it fails occacionally.
189
- # So even to update its contents, a defined subset of query-parameters has to be used.
190
- #
191
- # The required data-fields are stored in a yaml-file and fetched by #YmlFile.
192
- #
193
- # If `con_id` is present, only `con_id` and `exchange` are transmitted to the tws.
194
- # Otherwise a IB::Stock, IB::Option, IB::Future or IB::Forex-Object with necessary attributes
195
- # to query the tws is build (and returned)
196
- #
197
- # If Attributes are missing, an IB::VerifyError is fired,
198
- # This can be trapped with
199
- # rescue IB::VerifyError do ...
200
-
201
- def query_contract( invalid_record: true ) # :nodoc:
202
- # don't raise a verify error at this time. Contract.new con_id= xxxx, currency = 'xyz' is also valid
203
- ## raise VerifyError, "Querying Contract faild: Invalid Security Type" unless SECURITY_TYPES.values.include? sec_type
204
-
205
- ## the yml contains symbol-entries
206
- ## these are converted to capitalized strings
207
- items_as_string = ->(i){i.map{|x,y| x.to_s.capitalize}.join(', ')}
208
- ## here we read the corresponding attributes of the specified contract
209
- item_values = ->(i){ i.map{|x,y| self.send(x).presence || y }}
210
- ## and finally we create a attribute-hash to instantiate a new Contract
211
- ## to_h is present only after ruby 2.1.0
212
- item_attributehash = ->(i){ i.keys.zip(item_values[i]).to_h }
213
- ## now lets proceed, but only if no con_id is present
214
- if con_id.blank? || con_id.zero?
215
- # if item_values[nessesary_attributes].any?( &:nil? )
216
- # raise VerifyError, "#{items_as_string[nessesary_attributes]} are needed to retrieve Contract,
217
- # got: #{item_values[nessesary_attributes].join(',')}"
218
- # end
219
- # Contract.build item_attributehash[nessesary_items].merge(:sec_type=> sec_type) # return this
220
- Contract.build self.attributes # return this
221
- else # its always possible, to retrieve a Contract if con_id and exchange or are present
222
- Contract.new con_id: con_id , :exchange => exchange.presence || item_attributehash[nessesary_attributes][:exchange].presence || 'SMART' # return this
223
- end # if
224
- end # def
225
- end # class
66
+ end
67
+
68
+ #
69
+ # depreciated: Do not use anymore
70
+ def verify!
71
+ c = 0
72
+ IB::Connection.logger.warn "Contract.verify! is depreciated. Use \"contract = contract.verify.first\" instead"
73
+ _verify( update: true){| response | c+=1 } # wait for the returned thread to finish
74
+ IB::Connection.logger.error { "Multible Contracts detected during verify!." } if c > 1
75
+ self
76
+ end
77
+
78
+ private
79
+
80
+ # Base method to verify a contract
81
+ #
82
+ # if :thread is given, the method subscribes to messages, fires the request and returns the thread, that
83
+ # receives the exit-condition-message
84
+ #
85
+ # otherwise the method waits until the response form tws is processed
86
+ #
87
+ #
88
+ # if :update is true, the attributes of the Contract itself are adapted
89
+ #
90
+ # otherwise the Contract is untouched
91
+ def _verify thread: nil , update:, &b # :nodoc:
92
+ ib = Connection.current
93
+ # we generate a Request-Message-ID on the fly
94
+ message_id = nil
95
+ # define local vars which are updated within the query-block
96
+ recieved_contracts = []
97
+ queue = Queue.new
98
+ a = nil
99
+
100
+ # a tws-request is suppressed for bags and if the contract_detail-record is present
101
+ tws_request_not_nessesary = bag? || contract_detail.is_a?( ContractDetail )
102
+
103
+ if tws_request_not_nessesary
104
+ yield self if block_given?
105
+ return self
106
+ else # subscribe to ib-messages and describe what to do
107
+ a = ib.subscribe(:Alert, :ContractData, :ContractDataEnd) do |msg|
108
+ case msg
109
+ when Messages::Incoming::Alert
110
+ if msg.code == 200 && msg.error_id == message_id
111
+ ib.logger.error { "Not a valid Contract :: #{self.to_human} " }
112
+ queue.close
113
+ end
114
+ when Messages::Incoming::ContractData
115
+ if msg.request_id.to_i == message_id
116
+ # if multiple contracts are present, all of them are assigned
117
+ # Only the last contract is saved in self; 'count' is incremented
118
+ ## a specified block gets the contract_object on any unique ContractData-Event
119
+ recieved_contracts << if block_given?
120
+ yield msg.contract
121
+ else
122
+ msg.contract
123
+ end
124
+ if update
125
+ self.attributes = msg.contract.attributes
126
+ self.contract_detail = msg.contract_detail unless msg.contract_detail.nil?
127
+ end
128
+ end
129
+ when Messages::Incoming::ContractDataEnd
130
+ queue.push(1) if msg.request_id.to_i == message_id
131
+
132
+ end # case
133
+ end # subscribe
134
+
135
+ ### send the request !
136
+ # contract_to_be_queried = con_id.present? ? self : query_contract
137
+ # if no con_id is present, the given attributes are checked by query_contract
138
+ # if contract_to_be_queried.present? # is nil if query_contract fails
139
+ message_id = ib.send_message :RequestContractData, :contract => query_contract
140
+
141
+ th = Thread.new do
142
+ queue.pop
143
+ # j=0; loop{ sleep(0.01); j+=1; break if queue.closed? || queue.pop || j> 100; }
144
+ # ib.logger.error{ "#{to_human} --> No ContractData recieved " } if j >= 100 && !queue.closed?
145
+ ib.unsubscribe a
146
+ end
147
+ if thread.nil?
148
+ th.join # wait for the thread to finish
149
+ recieved_contracts # return queue of contracts
150
+ else
151
+ th # return active thread
152
+ end
153
+ end
154
+ end
155
+
156
+ # Generates an IB::Contract with the required attributes to retrieve a unique contract from the TWS
157
+ #
158
+ # Background: If the tws is queried with a »complete« IB::Contract, it fails occasionally.
159
+ # So – even to update its contents, a defined subset of query-parameters has to be used.
160
+ #
161
+ # The required data-fields are stored in a yaml-file and fetched by #YmlFile.
162
+ #
163
+ # If `con_id` is present, only `con_id` and `exchange` are transmitted to the tws.
164
+ # Otherwise a IB::Stock, IB::Option, IB::Future or IB::Forex-Object with necessary attributes
165
+ # to query the tws is build (and returned)
166
+ #
167
+ # If Attributes are missing, an IB::VerifyError is fired,
168
+ # This can be trapped with
169
+ # rescue IB::VerifyError do ...
170
+
171
+ def query_contract( invalid_record: true ) # :nodoc:
172
+ # don't raise a verify error at this time. Contract.new con_id= xxxx, currency = 'xyz' is also valid
173
+ ## raise VerifyError, "Querying Contract failed: Invalid Security Type" unless SECURITY_TYPES.values.include? sec_type
174
+
175
+ ## the yml contains symbol-entries
176
+ ## these are converted to capitalized strings
177
+ items_as_string = ->(i){i.map{|x,y| x.to_s.capitalize}.join(', ')}
178
+ ## here we read the corresponding attributes of the specified contract
179
+ item_values = ->(i){ i.map{|x,y| self.send(x).presence || y }}
180
+ ## and finally we create a attribute-hash to instantiate a new Contract
181
+ ## to_h is present only after ruby 2.1.0
182
+ item_attributehash = ->(i){ i.keys.zip(item_values[i]).to_h }
183
+ ## now lets proceed, but only if no con_id is present
184
+ if con_id.blank? || con_id.zero?
185
+ # if item_values[necessary_attributes].any?( &:nil? )
186
+ # raise VerifyError, "#{items_as_string[necessary_attributes]} are needed to retrieve Contract,
187
+ # got: #{item_values[necessary_attributes].join(',')}"
188
+ # end
189
+ # Contract.build item_attributehash[necessary_items].merge(:sec_type=> sec_type) # return this
190
+ Contract.build self.invariant_attributes # return this
191
+ else # its always possible, to retrieve a Contract if con_id and exchange or are present
192
+ Contract.new con_id: con_id , :exchange => exchange.presence || item_attributehash[nessesary_attributes][:exchange].presence || 'SMART' # return this
193
+ end # if
194
+ end # def
195
+ end
226
196
  end #module