ib-extensions 1.0 → 1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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