ib-extensions 1.2 → 1.3.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 +3 -4
- data/Gemfile.lock +52 -42
- data/README.md +56 -10
- data/bin/console +10 -3
- data/bin/gateway +14 -9
- data/changelog.md +54 -0
- data/ib-extensions.gemspec +7 -5
- data/lib/ib/alerts/base-alert.rb +10 -13
- data/lib/ib/eod.rb +243 -127
- data/lib/ib/extensions/version.rb +14 -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 +34 -34
- data/lib/ib/models/account.rb +177 -144
- 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 +4 -6
- data/lib/ib/option-greeks.rb +27 -21
- 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,36 +21,36 @@ 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
|
-
#
|
27
|
+
#
|
28
28
|
# The result can be customized by a provided block.
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
# Raw-data are stored in the _bars_-attribute of IB::Contract
|
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
35
|
# (volatile, ie. data are not preserved when the Object is copied)
|
36
|
-
#
|
36
|
+
#
|
37
37
|
#Example: IB::Stock.new(symbol: :ge).market_price
|
38
38
|
# returns the current market-price
|
39
39
|
#
|
40
|
-
#Example: IB::Stock.new(symbol: :ge).market_price(thread: true).join
|
40
|
+
#Example: IB::Stock.new(symbol: :ge).market_price(thread: true).join
|
41
41
|
# assigns IB::Symbols.sie.misc with the value of the :last (or delayed_last) TickPrice-Message
|
42
42
|
# and returns this value, too
|
43
43
|
#
|
44
44
|
|
45
45
|
def market_price delayed: true, thread: false, no_error: false
|
46
46
|
|
47
|
-
tws= Connection.current
|
47
|
+
tws= Connection.current # get the initialized ib-ruby instance
|
48
48
|
the_id , the_price = nil, nil
|
49
49
|
tickdata = Hash.new
|
50
50
|
q = Queue.new
|
51
51
|
# define requested tick-attributes
|
52
|
-
last, close, bid, ask =
|
53
|
-
[ :delayed_bid , :bid_price ], [ :delayed_ask , :ask_price ]]
|
52
|
+
last, close, bid, ask = [ [ :delayed_last , :last_price ] , [:delayed_close , :close_price ],
|
53
|
+
[ :delayed_bid , :bid_price ], [ :delayed_ask , :ask_price ]]
|
54
54
|
request_data_type = delayed ? :frozen_delayed : :frozen
|
55
55
|
|
56
56
|
# From the tws-documentation (https://interactivebrokers.github.io/tws-api/market_data_type.html)
|
@@ -62,42 +62,42 @@ module IB
|
|
62
62
|
|
63
63
|
# method returns the (running) thread
|
64
64
|
th = Thread.new do
|
65
|
-
# about 11 sec after the request, the
|
66
|
-
# we don't have to implement out
|
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
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 }
|
68
|
+
a_id = tws.subscribe(:Alert){|x| q.push(x) if [200, 354, 10167, 10168].include?( x.code ) && x.error_id == the_id }
|
69
69
|
# TWS Error 354: Requested market data is not subscribed.
|
70
70
|
# r_id = tws.subscribe(:TickRequestParameters) {|x| } # raise_snapshot_alert = true if x.snapshot_permissions.to_i.zero? && x.ticker_id == the_id }
|
71
71
|
|
72
72
|
# subscribe to TickPrices
|
73
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]])
|
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
76
|
# fast exit condition
|
77
|
-
q.push(true) if tickdata.size >= 4
|
78
|
-
end if msg.ticker_id == the_id
|
77
|
+
q.push(true) if tickdata.size >= 4
|
78
|
+
end if msg.ticker_id == the_id
|
79
79
|
end
|
80
80
|
# initialize »the_id« that is used to identify the received tick messages
|
81
81
|
# by firing the market data request
|
82
|
-
the_id = tws.send_message :RequestMarketData, contract: self , snapshot: true
|
82
|
+
the_id = tws.send_message :RequestMarketData, contract: self , snapshot: true
|
83
83
|
|
84
84
|
while !q.closed? do
|
85
85
|
result = q.pop
|
86
86
|
if result.is_a? IB::Messages::Incoming::Alert
|
87
|
-
tws.logger.
|
87
|
+
tws.logger.debug result.message
|
88
88
|
case result.code
|
89
|
-
when 200
|
89
|
+
when 200
|
90
90
|
q.close
|
91
91
|
error "#{to_human} --> #{result.message}" unless no_error
|
92
92
|
when 354, # not subscribed to market data
|
93
93
|
10167,
|
94
94
|
10168
|
95
|
-
if delayed
|
96
|
-
tws.logger.
|
97
|
-
tws.send_message :RequestMarketDataType, :market_data_type => 3
|
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
98
|
self.misc = :delayed
|
99
99
|
sleep 0.1
|
100
|
-
the_id = tws.send_message :RequestMarketData, contract: self , snapshot: true
|
100
|
+
the_id = tws.send_message :RequestMarketData, contract: self , snapshot: true
|
101
101
|
else
|
102
102
|
q.close
|
103
103
|
tws.logger.error "#{to_human} --> No marketdata permissions" unless no_error
|
@@ -109,25 +109,25 @@ module IB
|
|
109
109
|
data = tickdata.map{|x,y| [tz[x],y]}.to_h
|
110
110
|
valid_data = ->(d){ !(d.to_i.zero? || d.to_i == -1) }
|
111
111
|
self.bars << data # store raw data in bars
|
112
|
-
the_price = if block_given?
|
113
|
-
yield data
|
112
|
+
the_price = if block_given?
|
113
|
+
yield data
|
114
114
|
# yields {:bid=>0.10142e3, :ask=>0.10144e3, :last=>0.10142e3, :close=>0.10172e3}
|
115
115
|
else # behavior if no block is provided
|
116
116
|
if valid_data[data[:last]]
|
117
|
-
data[:last]
|
117
|
+
data[:last]
|
118
118
|
elsif valid_data[data[:bid]]
|
119
119
|
(data[:bid]+data[:ask])/2
|
120
|
-
elsif data[:close].present?
|
120
|
+
elsif data[:close].present?
|
121
121
|
data[:close]
|
122
122
|
else
|
123
123
|
nil
|
124
124
|
end
|
125
125
|
end
|
126
126
|
|
127
|
-
self.misc = misc == :delayed ? { :delayed => the_price } : { realtime: the_price }
|
127
|
+
self.misc = misc == :delayed ? { :delayed => the_price } : { realtime: the_price }
|
128
128
|
else
|
129
129
|
q.close
|
130
|
-
error "#{to_human} --> No Marketdata received "
|
130
|
+
error "#{to_human} --> No Marketdata received "
|
131
131
|
end
|
132
132
|
end
|
133
133
|
|
@@ -137,7 +137,7 @@ module IB
|
|
137
137
|
th # return thread
|
138
138
|
else
|
139
139
|
th.join
|
140
|
-
the_price # return
|
140
|
+
the_price # return
|
141
141
|
end
|
142
142
|
end #
|
143
143
|
|