ib-extensions 1.1 → 1.3
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 -3
- data/Gemfile.lock +26 -26
- data/README.md +56 -10
- data/bin/console +10 -3
- data/bin/gateway +14 -9
- data/changelog.md +54 -0
- data/ib-extensions.gemspec +6 -4
- data/lib/ib/alerts/base-alert.rb +10 -13
- data/lib/ib/eod.rb +254 -125
- data/lib/ib/extensions/contract.rb +2 -30
- data/lib/ib/extensions/version.rb +1 -1
- data/lib/ib/extensions.rb +5 -0
- data/lib/ib/gateway/account-infos.rb +74 -47
- data/lib/ib/gateway/order-handling.rb +57 -25
- data/lib/ib/gateway.rb +45 -31
- data/lib/ib/market-price.rb +108 -97
- data/lib/ib/models/account.rb +178 -145
- data/lib/ib/models/bag.rb +19 -0
- data/lib/ib/models/contract.rb +16 -0
- data/lib/ib/models/future.rb +20 -0
- data/lib/ib/models/option.rb +20 -13
- data/lib/ib/option-chain.rb +36 -63
- data/lib/ib/option-greeks.rb +36 -33
- data/lib/ib/order_prototypes/all-in-one.rb +46 -0
- data/lib/ib/plot-poec.rb +60 -0
- data/lib/ib/probability_of_expiring.rb +109 -0
- data/lib/ib/spread-prototypes.rb +1 -0
- data/lib/ib/spread_prototypes/butterfly.rb +2 -4
- data/lib/ib/spread_prototypes/calendar.rb +25 -23
- data/lib/ib/spread_prototypes/straddle.rb +3 -3
- data/lib/ib/spread_prototypes/strangle.rb +8 -9
- data/lib/ib/spread_prototypes/vertical.rb +6 -7
- data/lib/ib/verify.rb +34 -39
- data/lib/ib-gateway.rb +12 -0
- metadata +53 -5
@@ -11,7 +11,7 @@ The order is identified by local_id and perm_id
|
|
11
11
|
Everything is carried out in a mutex-synchonized environment
|
12
12
|
=end
|
13
13
|
def update_order_dependent_object order_dependent_object # :nodoc:
|
14
|
-
|
14
|
+
account_data do | a |
|
15
15
|
order = if order_dependent_object.local_id.present?
|
16
16
|
a.locate_order( :local_id => order_dependent_object.local_id)
|
17
17
|
else
|
@@ -21,7 +21,7 @@ Everything is carried out in a mutex-synchonized environment
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
def initialize_order_handling
|
24
|
-
tws.subscribe( :CommissionReport, :ExecutionData, :OrderStatus, :OpenOrder, :OpenOrderEnd, :NextValidId ) do |msg|
|
24
|
+
tws.subscribe( :CommissionReport, :ExecutionData, :OrderStatus, :OpenOrder, :OpenOrderEnd, :NextValidId ) do |msg|
|
25
25
|
case msg
|
26
26
|
|
27
27
|
when IB::Messages::Incoming::CommissionReport
|
@@ -39,7 +39,6 @@ Everything is carried out in a mutex-synchonized environment
|
|
39
39
|
logger.info { "Order State not assigned-- #{msg.order_state.to_human} ----------" } if success.nil?
|
40
40
|
|
41
41
|
when IB::Messages::Incoming::OpenOrder
|
42
|
-
## todo --> handling of bags --> no con_id
|
43
42
|
account_data(msg.order.account) do | this_account |
|
44
43
|
# first update the contracts
|
45
44
|
# make open order equal to IB::Spreads (include negativ con_id)
|
@@ -63,9 +62,9 @@ Everything is carried out in a mutex-synchonized environment
|
|
63
62
|
o.executions << msg.execution
|
64
63
|
if msg.execution.cumulative_quantity.to_i == o.total_quantity.abs
|
65
64
|
logger.info{ "#{o.account} --> #{o.contract.symbol}: Execution completed" }
|
66
|
-
o.order_states.first_or_create( IB::OrderState.new( perm_id: o.perm_id,
|
67
|
-
|
68
|
-
|
65
|
+
o.order_states.first_or_create( IB::OrderState.new( perm_id: o.perm_id,
|
66
|
+
local_id: o.local_id,
|
67
|
+
status: 'Filled' ), :status )
|
69
68
|
# update portfoliovalue
|
70
69
|
a = @accounts.detect{ | x | x.account == o.account } # we are in a mutex controlled environment
|
71
70
|
pv = a.portfolio_values.detect{ | y | y.contract.con_id == o.contract.con_id}
|
@@ -86,23 +85,38 @@ Everything is carried out in a mutex-synchonized environment
|
|
86
85
|
end # do
|
87
86
|
end # def subscribe
|
88
87
|
|
89
|
-
# Resets the order-array for each account
|
88
|
+
# Resets the order-array for each account.
|
90
89
|
# Requests all open (eg. pending) orders from the tws
|
91
90
|
#
|
92
|
-
# Waits until the OpenOrderEnd-Message is
|
91
|
+
# Waits until the OpenOrderEnd-Message is received
|
93
92
|
|
94
93
|
|
95
94
|
def request_open_orders
|
96
95
|
|
97
|
-
|
98
|
-
subscription = tws.subscribe( :OpenOrderEnd ) {
|
99
|
-
account_data{| account | account.orders = [] }
|
96
|
+
q = Queue.new
|
97
|
+
subscription = tws.subscribe( :OpenOrderEnd ) { q.push(true) } # signal succsess
|
98
|
+
account_data {| account | account.orders = [] }
|
100
99
|
send_message :RequestAllOpenOrders
|
101
|
-
|
100
|
+
## the OpenOrderEnd-message usually appears after 0.1 sec.
|
101
|
+
## we wait for 1 sec.
|
102
|
+
th = Thread.new{ sleep 1 ; q.close }
|
103
|
+
|
104
|
+
q.pop # wait for OpenOrderEnd or finishing of thread
|
105
|
+
|
102
106
|
tws.unsubscribe subscription
|
107
|
+
if q.closed?
|
108
|
+
5.times do
|
109
|
+
logger.fatal { "Is the API in read-only modus? No Open Order Message received! "}
|
110
|
+
sleep 0.2
|
111
|
+
end
|
112
|
+
else
|
113
|
+
Thread.kill(th)
|
114
|
+
q.close
|
115
|
+
account_data {| account | account.orders } # reset order array
|
116
|
+
end
|
103
117
|
end
|
104
118
|
|
105
|
-
alias update_orders request_open_orders
|
119
|
+
alias update_orders request_open_orders
|
106
120
|
|
107
121
|
|
108
122
|
|
@@ -116,17 +130,35 @@ end # module
|
|
116
130
|
module IB
|
117
131
|
|
118
132
|
class Order
|
133
|
+
# Auto Adjust implements a simple algotithm to ensure that an order is accepted
|
134
|
+
#
|
135
|
+
# It reads `contract_detail.min_tick`.
|
136
|
+
#
|
137
|
+
# If min_tick < 0.01, the real tick-increments differ fron the min_tick_value
|
138
|
+
#
|
139
|
+
# For J36 (jardines) min tick is 0.001, but the minimal increment is 0.005
|
140
|
+
# For Tui1 its the samme, min_tick is 0.00001 , minimal increment ist 0.00005
|
141
|
+
#
|
142
|
+
# Thus, for min-tick smaller then 0.01, the value is rounded to the next higer digit.
|
143
|
+
#
|
144
|
+
# | min-tick | round |
|
145
|
+
# |--------------|------------|
|
146
|
+
# | 10 | 110 |
|
147
|
+
# | 1 | 111 |
|
148
|
+
# | 0.1 | 111.1 |
|
149
|
+
# | 0.001 | 111.11 |
|
150
|
+
# | 0.0001 | 111.11 |
|
151
|
+
# | 0.00001 | 111.111 |
|
152
|
+
# |--------------|------------|
|
153
|
+
#
|
119
154
|
def auto_adjust
|
120
155
|
# lambda to perform the calculation
|
121
156
|
adjust_price = ->(a,b) do
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
157
|
+
count = -Math.log10(b).round.to_i
|
158
|
+
count = count -1 if count > 2
|
159
|
+
a.round count
|
160
|
+
|
126
161
|
end
|
127
|
-
# adjust_price[2.6896, 0.1].to_f => 2.6
|
128
|
-
# adjust_price[2.0896, 0.05].to_f => 2.05
|
129
|
-
# adjust_price[2.0896, 0.002].to_f => 2.088
|
130
162
|
|
131
163
|
|
132
164
|
error "No Contract provided to Auto adjust" unless contract.is_a? IB::Contract
|
@@ -134,11 +166,11 @@ module IB
|
|
134
166
|
unless contract.is_a? IB::Bag
|
135
167
|
# ensure that contract_details are present
|
136
168
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
169
|
+
min_tick = contract.verify.first.contract_detail.min_tick
|
170
|
+
# there are two attributes to consider: limit_price and aux_price
|
171
|
+
# limit_price + aux_price may be nil or an empty string. Then ".to_f.zero?" becomes true
|
172
|
+
self.limit_price= adjust_price.call(limit_price.to_f, min_tick) unless limit_price.to_f.zero?
|
173
|
+
self.aux_price= adjust_price.call(aux_price.to_f, min_tick) unless aux_price.to_f.zero?
|
142
174
|
end
|
143
175
|
end
|
144
176
|
end # class Order
|
data/lib/ib/gateway.rb
CHANGED
@@ -113,7 +113,7 @@ IB::Gateway.new serial_array: true (, ...)
|
|
113
113
|
@connection_parameter = { received: serial_array, port: port, host: host, connect: false, logger: logger, client_id: client_id }
|
114
114
|
|
115
115
|
@account_lock = Mutex.new
|
116
|
-
|
116
|
+
@watchlists = watchlists.map{ |b| IB::Symbols.allocate_collection b }
|
117
117
|
@gateway_parameter = { s_m_a: subscribe_managed_accounts,
|
118
118
|
s_a: subscribe_alerts,
|
119
119
|
s_o_m: subscribe_order_messages,
|
@@ -133,7 +133,7 @@ IB::Gateway.new serial_array: true (, ...)
|
|
133
133
|
begin
|
134
134
|
i+=1
|
135
135
|
if connect(100) # tries to connect for about 2h
|
136
|
-
get_account_data(
|
136
|
+
get_account_data()
|
137
137
|
# request_open_orders() if request_open_orders || get_account_data
|
138
138
|
else
|
139
139
|
@accounts = [] # definitivley reset @accounts
|
@@ -156,6 +156,10 @@ IB::Gateway.new serial_array: true (, ...)
|
|
156
156
|
def active_watchlists
|
157
157
|
@watchlists
|
158
158
|
end
|
159
|
+
def add_watchlist watchlist
|
160
|
+
new_watchlist = IB::Symbols.allocate_collection( watchlist )
|
161
|
+
@watchlists << new_watchlist unless @watchlists.include?( new_watchlist )
|
162
|
+
end
|
159
163
|
|
160
164
|
def get_host
|
161
165
|
"#{@connection_parameter[:host]}: #{@connection_parameter[:port] }"
|
@@ -196,28 +200,21 @@ Weiterhin meldet sich die Anwendung zur Auswertung von Messages der TWS an.
|
|
196
200
|
return false
|
197
201
|
end
|
198
202
|
rescue Errno::EHOSTUNREACH => e
|
199
|
-
|
200
|
-
logger.error e
|
203
|
+
error "Cannot connect to specified host #{e}", :reader, true
|
201
204
|
return false
|
202
205
|
rescue SocketError => e
|
203
|
-
|
206
|
+
error 'Wrong Adress, connection not possible', :reader, true
|
204
207
|
return false
|
208
|
+
rescue IB::Error => e
|
209
|
+
logger.info e
|
205
210
|
end
|
206
211
|
|
207
|
-
tws.start_reader
|
208
|
-
# let NextValidId-Event appear
|
209
|
-
(1..30).each do |r|
|
210
|
-
break if tws.next_local_id.present?
|
211
|
-
sleep 0.1
|
212
|
-
if r == 30
|
213
|
-
error "Connected, NextLocalId is not initialized. Repeat with another client_id"
|
214
|
-
end
|
215
|
-
end
|
216
212
|
# initialize @accounts (incl. aliases)
|
217
213
|
tws.send_message( :RequestFA, fa_data_type: 3) if fa?
|
218
214
|
logger.debug { "Communications successfully established" }
|
219
215
|
# update open orders
|
220
216
|
request_open_orders if @gateway_parameter[:s_o_m] || @gateway_parameter[:g_a_d]
|
217
|
+
true # return gatway object
|
221
218
|
end # def
|
222
219
|
|
223
220
|
|
@@ -227,7 +224,7 @@ Weiterhin meldet sich die Anwendung zur Auswertung von Messages der TWS an.
|
|
227
224
|
def reconnect
|
228
225
|
if tws.present?
|
229
226
|
disconnect
|
230
|
-
|
227
|
+
sleep 0.1
|
231
228
|
end
|
232
229
|
logger.info "trying to reconnect ..."
|
233
230
|
connect
|
@@ -312,13 +309,28 @@ account_data provides a thread-safe access to linked content of accounts
|
|
312
309
|
It returns an Array of the return-values of the block
|
313
310
|
|
314
311
|
If called without a parameter, all clients are accessed
|
312
|
+
|
313
|
+
Example
|
314
|
+
|
315
|
+
```
|
316
|
+
g = IB::Gateway.current
|
317
|
+
# thread safe access
|
318
|
+
g.account_data &:portfolio_values
|
319
|
+
|
320
|
+
g.account_data &:account_values
|
321
|
+
|
322
|
+
# primitive access
|
323
|
+
g.clients.map &:portfolio_values
|
324
|
+
g.clients.map &:account_values
|
325
|
+
|
326
|
+
```
|
315
327
|
=end
|
316
328
|
|
317
329
|
def account_data account_or_id=nil
|
318
330
|
|
319
331
|
safe = ->(account) do
|
320
332
|
@account_lock.synchronize do
|
321
|
-
|
333
|
+
yield account
|
322
334
|
end
|
323
335
|
end
|
324
336
|
|
@@ -349,10 +361,9 @@ If called without a parameter, all clients are accessed
|
|
349
361
|
## a possible response is best defined before the connect-attempt is done
|
350
362
|
# ## Attention
|
351
363
|
# ## @accounts are not initialized yet (empty array)
|
352
|
-
|
353
|
-
|
364
|
+
yield self if block_given?
|
365
|
+
|
354
366
|
|
355
|
-
end
|
356
367
|
end
|
357
368
|
|
358
369
|
=begin
|
@@ -364,7 +375,7 @@ Its always active.
|
|
364
375
|
def initialize_managed_accounts
|
365
376
|
rec_id = tws.subscribe( :ReceiveFA ) do |msg|
|
366
377
|
msg.accounts.each do |a|
|
367
|
-
account_data( a.account
|
378
|
+
account_data( a.account ){| the_account | the_account.update_attribute :alias, a.alias } unless a.alias.blank?
|
368
379
|
end
|
369
380
|
logger.info { "Accounts initialized \n #{@accounts.map( &:to_human ).join " \n " }" }
|
370
381
|
end
|
@@ -407,25 +418,28 @@ Its always active.
|
|
407
418
|
# => 0.00066005
|
408
419
|
#
|
409
420
|
def check_connection
|
410
|
-
|
411
|
-
|
412
|
-
|
421
|
+
q = Queue.new
|
422
|
+
count = 0
|
423
|
+
result = nil
|
424
|
+
z= tws.subscribe( :CurrentTime ) { q.push true }
|
425
|
+
loop do
|
413
426
|
begin
|
414
427
|
tws.send_message(:RequestCurrentTime) # 10 ms ##
|
415
|
-
|
428
|
+
th = Thread.new{ sleep 1 ; q.push nil }
|
429
|
+
result = q.pop
|
430
|
+
count+=1
|
431
|
+
break if result || count > 10
|
416
432
|
rescue IOError, Errno::ECONNREFUSED # connection lost
|
417
|
-
count
|
433
|
+
count +=1
|
434
|
+
retry
|
418
435
|
rescue IB::Error # not connected
|
419
436
|
reconnect
|
420
|
-
count
|
421
|
-
|
422
|
-
retry if count <= 5
|
437
|
+
count = 0
|
438
|
+
retry
|
423
439
|
end
|
424
|
-
count +=1
|
425
|
-
break if count > 5
|
426
440
|
end
|
427
441
|
tws.unsubscribe z
|
428
|
-
|
442
|
+
result # return value
|
429
443
|
end
|
430
444
|
private
|
431
445
|
|
data/lib/ib/market-price.rb
CHANGED
@@ -21,114 +21,125 @@ module IB
|
|
21
21
|
#
|
22
22
|
# If last_price is received, its returned.
|
23
23
|
# If not, midpoint (bid+ask/2) is used. Else the closing price will be returned.
|
24
|
-
#
|
24
|
+
#
|
25
25
|
# Any value (even 0.0) which is stored in IB::Contract.misc indicates that the contract is
|
26
26
|
# accepted by `place_order`.
|
27
|
-
#
|
28
|
-
# The result can be customized by a provided block.
|
29
|
-
#
|
30
|
-
# IB::Symbols::Stocks.sie.market_price{ |x| x }
|
31
|
-
# -> {"bid"=>0.10142e3, "ask"=>0.10144e3, "last"=>0.10142e3, "close"=>0.10172e3}
|
32
|
-
#
|
33
|
-
#
|
34
|
-
# Raw-data are stored in the _bars_-attribute of IB::Contract
|
35
|
-
# (volatile, ie. data are not preserved when the Object is copied)
|
36
|
-
#
|
37
|
-
#Example: IB::Stock.new(symbol: :ge).market_price
|
38
|
-
# returns the current market-price
|
39
27
|
#
|
40
|
-
#
|
41
|
-
# assigns IB::Symbols.sie.misc with the value of the :last (or delayed_last) TickPrice-Message
|
42
|
-
# and returns this value, too
|
43
|
-
#
|
44
|
-
#Raises IB::Error
|
45
|
-
# if no Marketdata Subscription is present and delayed: false is specified
|
46
|
-
#
|
47
|
-
#
|
48
|
-
# Solutions: Catch the Error and retry with delayed: true
|
49
|
-
#
|
50
|
-
# if that fails use alternative exchanges (look to Contract.valid_exchanges)
|
28
|
+
# The result can be customized by a provided block.
|
51
29
|
#
|
52
|
-
|
30
|
+
# IB::Symbols::Stocks.sie.market_price{ |x| x }
|
31
|
+
# -> {"bid"=>0.10142e3, "ask"=>0.10144e3, "last"=>0.10142e3, "close"=>0.10172e3}
|
32
|
+
#
|
33
|
+
#
|
34
|
+
# Raw-data are stored in the _bars_-attribute of IB::Contract
|
35
|
+
# (volatile, ie. data are not preserved when the Object is copied)
|
36
|
+
#
|
37
|
+
#Example: IB::Stock.new(symbol: :ge).market_price
|
38
|
+
# returns the current market-price
|
39
|
+
#
|
40
|
+
#Example: IB::Stock.new(symbol: :ge).market_price(thread: true).join
|
41
|
+
# assigns IB::Symbols.sie.misc with the value of the :last (or delayed_last) TickPrice-Message
|
42
|
+
# and returns this value, too
|
43
|
+
#
|
44
|
+
|
45
|
+
def market_price delayed: true, thread: false, no_error: false
|
53
46
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
47
|
+
tws= Connection.current # get the initialized ib-ruby instance
|
48
|
+
the_id , the_price = nil, nil
|
49
|
+
tickdata = Hash.new
|
50
|
+
q = Queue.new
|
51
|
+
# define requested tick-attributes
|
52
|
+
last, close, bid, ask = [ [ :delayed_last , :last_price ] , [:delayed_close , :close_price ],
|
53
|
+
[ :delayed_bid , :bid_price ], [ :delayed_ask , :ask_price ]]
|
54
|
+
request_data_type = delayed ? :frozen_delayed : :frozen
|
61
55
|
|
62
|
-
|
56
|
+
# From the tws-documentation (https://interactivebrokers.github.io/tws-api/market_data_type.html)
|
57
|
+
# Beginning in TWS v970, a IBApi.EClient.reqMarketDataType callback of 1 will occur automatically
|
58
|
+
# after invoking reqMktData if the user has live data permissions for the instrument.
|
59
|
+
#
|
60
|
+
# so - even if "delayed" is specified, realtime-data are returned if RT-permissions are present
|
61
|
+
#
|
63
62
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
63
|
+
# method returns the (running) thread
|
64
|
+
th = Thread.new do
|
65
|
+
# about 11 sec after the request, the TWS returns :TickSnapshotEnd if no ticks are transmitted
|
66
|
+
# we don't have to implement out own timeout-criteria
|
67
|
+
s_id = tws.subscribe(:TickSnapshotEnd){|x| q.push(true) if x.ticker_id == the_id }
|
68
|
+
a_id = tws.subscribe(:Alert){|x| q.push(x) if [200, 354, 10167, 10168].include?( x.code ) && x.error_id == the_id }
|
69
|
+
# TWS Error 354: Requested market data is not subscribed.
|
70
|
+
# r_id = tws.subscribe(:TickRequestParameters) {|x| } # raise_snapshot_alert = true if x.snapshot_permissions.to_i.zero? && x.ticker_id == the_id }
|
70
71
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
end
|
83
|
-
# initialize »the_id« that is used to identify the received tick messages
|
84
|
-
# by firing the market data request
|
85
|
-
the_id = tws.send_message :RequestMarketData, contract: self , snapshot: true
|
72
|
+
# subscribe to TickPrices
|
73
|
+
sub_id = tws.subscribe(:TickPrice ) do |msg| #, :TickSize, :TickGeneric, :TickOption) do |msg|
|
74
|
+
[last,close,bid,ask].each do |x|
|
75
|
+
tickdata[x] = msg.the_data[:price] if x.include?( IB::TICK_TYPES[ msg.the_data[:tick_type]])
|
76
|
+
# fast exit condition
|
77
|
+
q.push(true) if tickdata.size >= 4
|
78
|
+
end if msg.ticker_id == the_id
|
79
|
+
end
|
80
|
+
# initialize »the_id« that is used to identify the received tick messages
|
81
|
+
# by firing the market data request
|
82
|
+
the_id = tws.send_message :RequestMarketData, contract: self , snapshot: true
|
86
83
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
84
|
+
while !q.closed? do
|
85
|
+
result = q.pop
|
86
|
+
if result.is_a? IB::Messages::Incoming::Alert
|
87
|
+
tws.logger.debug result.message
|
88
|
+
case result.code
|
89
|
+
when 200
|
90
|
+
q.close
|
91
|
+
error "#{to_human} --> #{result.message}" unless no_error
|
92
|
+
when 354, # not subscribed to market data
|
93
|
+
10167,
|
94
|
+
10168
|
95
|
+
if delayed && !(result.message =~ /market data is not available/)
|
96
|
+
tws.logger.debug "#{to_human} --> requesting delayed data"
|
97
|
+
tws.send_message :RequestMarketDataType, :market_data_type => 3
|
98
|
+
self.misc = :delayed
|
99
|
+
sleep 0.1
|
100
|
+
the_id = tws.send_message :RequestMarketData, contract: self , snapshot: true
|
101
|
+
else
|
102
|
+
q.close
|
103
|
+
tws.logger.error "#{to_human} --> No marketdata permissions" unless no_error
|
104
|
+
end
|
105
|
+
end
|
106
|
+
elsif result.present?
|
107
|
+
q.close
|
108
|
+
tz = -> (z){ z.map{|y| y.to_s.split('_')}.flatten.count_duplicates.max_by{|k,v| v}.first.to_sym}
|
109
|
+
data = tickdata.map{|x,y| [tz[x],y]}.to_h
|
110
|
+
valid_data = ->(d){ !(d.to_i.zero? || d.to_i == -1) }
|
111
|
+
self.bars << data # store raw data in bars
|
112
|
+
the_price = if block_given?
|
113
|
+
yield data
|
114
|
+
# yields {:bid=>0.10142e3, :ask=>0.10144e3, :last=>0.10142e3, :close=>0.10172e3}
|
115
|
+
else # behavior if no block is provided
|
116
|
+
if valid_data[data[:last]]
|
117
|
+
data[:last]
|
118
|
+
elsif valid_data[data[:bid]]
|
119
|
+
(data[:bid]+data[:ask])/2
|
120
|
+
elsif data[:close].present?
|
121
|
+
data[:close]
|
122
|
+
else
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
end
|
91
126
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
# elsif raise_snapshot_alert
|
99
|
-
# error "No Snapshot Permissions, try alternative exchange <-- #{to_human}"
|
100
|
-
elsif i <= 1000
|
101
|
-
tz = -> (z){ z.map{|y| y.to_s.split('_')}.flatten.count_duplicates.max_by{|k,v| v}.first.to_sym}
|
102
|
-
data = tickdata.map{|x,y| [tz[x],y]}.to_h
|
103
|
-
valid_data = ->(d){ !(d.to_i.zero? || d.to_i == -1) }
|
104
|
-
self.bars << data # store raw data in bars
|
105
|
-
the_price = if block_given?
|
106
|
-
yield data
|
107
|
-
# yields {:bid=>0.10142e3, :ask=>0.10144e3, :last=>0.10142e3, :close=>0.10172e3}
|
108
|
-
else # behavior if no block is provided
|
109
|
-
if valid_data[data[:last]]
|
110
|
-
data[:last]
|
111
|
-
elsif valid_data[data[:bid]]
|
112
|
-
(data[:bid]+data[:ask])/2
|
113
|
-
elsif data[:close].present?
|
114
|
-
data[:close]
|
115
|
-
else
|
116
|
-
nil
|
117
|
-
end
|
118
|
-
end
|
119
|
-
self.misc = the_price if thread # store internally if in thread modus
|
120
|
-
else # i > 1000
|
121
|
-
tws.logger.info{ "#{to_human} --> No Marketdata received " }
|
122
|
-
end
|
127
|
+
self.misc = misc == :delayed ? { :delayed => the_price } : { realtime: the_price }
|
128
|
+
else
|
129
|
+
q.close
|
130
|
+
error "#{to_human} --> No Marketdata received "
|
131
|
+
end
|
132
|
+
end
|
123
133
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
134
|
+
tws.unsubscribe sub_id, s_id, a_id
|
135
|
+
end
|
136
|
+
if thread
|
137
|
+
th # return thread
|
138
|
+
else
|
139
|
+
th.join
|
140
|
+
the_price # return
|
141
|
+
end
|
142
|
+
end #
|
132
143
|
|
133
144
|
end
|
134
145
|
end
|