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.
- checksums.yaml +4 -4
- data/Gemfile +2 -1
- data/Gemfile.lock +33 -34
- data/README.md +12 -7
- data/bin/console +3 -5
- data/bin/{gateway.rb → gateway} +13 -4
- data/changelog.md +6 -0
- data/ib-extensions.gemspec +1 -0
- data/lib/ib/alerts/base-alert.rb +3 -1
- data/lib/ib/alerts/order-alerts.rb +5 -0
- data/lib/ib/eod.rb +19 -23
- data/lib/ib/extensions.rb +1 -0
- data/lib/ib/extensions/version.rb +1 -1
- data/lib/ib/gateway.rb +77 -63
- data/lib/ib/gateway/account-infos.rb +1 -2
- data/lib/ib/gateway/order-handling.rb +121 -126
- data/lib/ib/market-price.rb +2 -2
- data/lib/ib/models/account.rb +63 -56
- data/lib/ib/models/option.rb +20 -0
- data/lib/ib/option-chain.rb +180 -182
- data/lib/ib/order-prototypes.rb +1 -1
- data/lib/ib/spread-prototypes.rb +2 -2
- data/lib/ib/spread_prototypes/stock-spread.rb +1 -1
- data/lib/ib/verify.rb +192 -222
- metadata +19 -18
- data/examples/cancel_orders +0 -74
- data/examples/eod +0 -35
- data/examples/input.rb +0 -475
- data/examples/market_price +0 -57
- data/examples/option_chain +0 -67
- data/examples/place_and_modify_order +0 -162
- data/examples/place_bracket_order +0 -62
- data/examples/place_butterfly_order +0 -104
- data/examples/place_combo_order +0 -70
- data/examples/place_limit_order +0 -82
- data/examples/place_the_limit_order +0 -145
- data/examples/volatility_research +0 -139
- data/examples/what_if_order +0 -90
- data/lib/ib/models/spread.rb +0 -159
@@ -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
|
data/lib/ib/option-chain.rb
CHANGED
@@ -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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
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
|
|
data/lib/ib/order-prototypes.rb
CHANGED
data/lib/ib/spread-prototypes.rb
CHANGED
@@ -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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
#
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
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
|