ib-extensions 1.2 → 1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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 +239 -127
- 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 +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
|
|