Class: IB::Contract

Inherits:
Object
  • Object
show all
Defined in:
lib/ib/eod.rb,
lib/ib/verify.rb,
lib/ib/market-price.rb,
lib/ib/option-chain.rb

Instance Method Summary collapse

Instance Method Details

#_verify(thread: nil, update:, &b) ⇒ Object

Base method to verify a contract

if :thread is given, the method subscribes to messages, fires the request and returns the thread, that receives the exit-condition-message

otherwise the method waits until the response form tws is processed

if :update is true, the attributes of the Contract itself are apdated

otherwise the Contract is untouched



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
# File 'lib/ib/verify.rb', line 112

def _verify thread: nil , update:,  &b # :nodoc:

  ib =  Connection.current
  # we generate a Request-Message-ID on the fly
  message_id = nil 
  # define local vars which are updated within the query-block
  exitcondition, count , queried_contract, r = false, 0, nil, []

  # currently the tws-request is suppressed for bags and if the contract_detail-record is present
  tws_request_not_nessesary = bag? || contract_detail.is_a?( ContractDetail )

  if tws_request_not_nessesary
    yield self if block_given?
    count = 1
  else # subscribe to ib-messages and describe what to do
    a = ib.subscribe(:Alert, :ContractData,  :ContractDataEnd) do |msg| 
      case msg
      when Messages::Incoming::Alert
        if msg.code == 200 && msg.error_id == message_id
          ib.logger.error { "Not a valid Contract :: #{self.to_human} " }
          exitcondition = true
        end
      when Messages::Incoming::ContractData
        if msg.request_id.to_i == message_id
          # if multible contracts are present, all of them are assigned
          # Only the last contract is saved in self;  'count' is incremented
          count +=1
          ## a specified block gets the contract_object on any uniq ContractData-Event
          r << if block_given?
                 yield msg.contract
          elsif count > 1 
            queried_contract = msg.contract  # used by the logger (below) in case of mulible contracts
          else
            msg.contract
          end
          if update
            self.attributes = msg.contract.attributes
            self.contract_detail = msg.contract_detail unless msg.contract_detail.nil?
          end
        end
      when Messages::Incoming::ContractDataEnd
        exitcondition = true if msg.request_id.to_i ==  message_id

      end  # case
    end # subscribe

    ### send the request !
  #  contract_to_be_queried =  con_id.present? ? self : query_contract  
    # if no con_id is present,  the given attributes are checked by query_contract
  #  if contract_to_be_queried.present?   # is nil if query_contract fails
      message_id = ib.send_message :RequestContractData, :contract => query_contract

      th =  Thread.new do
        begin
          Timeout::timeout(1) do
            loop{ break if exitcondition ; sleep 0.005 } 
          end
        rescue Timeout::Error
          Connection.logger.error{ "#{to_human} --> No ContractData recieved " }
        end
        ib.unsubscribe a
      end
      if thread.nil?
        th.join    # wait for the thread to finish
        r      # return array of contracts
      else
        th      # return active thread
      end
  #      else
  #        ib.logger.error { "Not a valid Contract-spezification, #{self.to_human}" }
    end
  #    end
end

#associate_ticdataObject



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
# File 'lib/ib/option-chain.rb', line 165

def associate_ticdata

  tws=  IB::Connection.current      # get the initialized ib-ruby instance
  the_id =  nil
  finalize= false
  #  switch to delayed data
  tws.send_message :RequestMarketDataType, :market_data_type => :delayed

  s_id = tws.subscribe(:TickSnapshotEnd) { |msg| finalize = true if msg.ticker_id == the_id }

  sub_id = tws.subscribe(:TickPrice, :TickSize,  :TickGeneric, :TickOption) do |msg|
    self.bars << msg.the_data if msg.ticker_id == the_id 
  end

  # initialize »the_id« that is used to identify the received tick messages
  # by firing the market data request
  the_id = tws.send_message :RequestMarketData,  contract: self , snapshot: true 

  #keep the method-call running until the request finished
  #and cancel subscriptions to the message handler.
  Thread.new do 
    i=0; loop{ i+=1; sleep 0.1; break if finalize || i > 1000 }
    tws.unsubscribe sub_id 
    tws.unsubscribe s_id
    #puts "#{symbol} data gathered" 
  end  # method returns the (running) thread

end

#atm_options(ref_price: :request, right: :put) ⇒ Object

return a set of AtTheMoneyOptions



133
134
135
136
137
138
139
# File 'lib/ib/option-chain.rb', line 133

def atm_options ref_price: :request, right: :put
option_chain(  right: right, ref_price: ref_price, sort: :expiry) do | chain |
          chain[0]
end

  
end

#eod(start: nil, to: Date.today, duration: nil, what: :trades) ⇒ Object

puts Symbols::Stocks.wfc.eod( to: Date.new(2019,10,9), duration: 3 ) <Bar: 2019-10-04 wap 48.964 OHLC 48.61 49.25 48.54 49.21 trades 9899 vol 50561> <Bar: 2019-10-07 wap 48.9445 OHLC 48.91 49.29 48.75 48.81 trades 10317 vol 50189> <Bar: 2019-10-08 wap 47.9165 OHLC 48.25 48.34 47.55 47.82 trades 12607 vol 53577>



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
# File 'lib/ib/eod.rb', line 93

def eod start:nil, to: Date.today, duration: nil , what: :trades 

  tws = IB::Connection.current
  recieved =  Queue.new
  r = nil
    # the hole response is transmitted at once! 
  a= tws.subscribe(IB::Messages::Incoming::HistoricalData) do |msg|
    if msg.request_id == con_id
      #          msg.results.each { |entry| puts "  #{entry}" }
      r = block_given? ?  msg.results.map{|y| yield y} : msg.results
    end
    recieved.push Time.now
  end
  b = tws.subscribe( IB::Messages::Incoming::Alert) do  |msg|
    if [321,162,200].include? msg.code
      tws.logger.info msg.message
      # TWS Error 200: No security definition has been found for the request
      # TWS Error 354: Requested market data is not subscribed.
      # TWS Error 162  # Historical Market Data Service error 
      recieved =[]
    end
  end
  

  duration =  if duration.present?
                duration.is_a?(String) ? duration : duration.to_s + " D"
              elsif start.present?
                BuisinesDays.business_days_between(start, to).to_s + " D"
              else
                "1 D"
              end

  tws.send_message IB::Messages::Outgoing::RequestHistoricalData.new(
    :request_id => con_id,
    :contract =>  self,
    :end_date_time => to.to_time.to_ib, #  Time.now.to_ib,
    :duration => duration, #    ?
    :bar_size => :day1, #  IB::BAR_SIZES.key(:hour)?
    :what_to_show => what,
    :use_rth => 0,
    :format_date => 2,
    :keep_up_todate => 0)

  Timeout::timeout(50) do   # max 5 sec.
    sleep 0.1 
    last_time =  recieved.pop # blocks until a message is ready on the queue
    loop do
      sleep 0.1
      break if recieved.empty?  # finish if no more data received
    end
  tws.unsubscribe a
  tws.unsubscribe b

  r  #  the collected result

  end
end

#itm_options(count: 5, right: :put, ref_price: :request, sort: :strike) ⇒ Object

return InTheMoneyOptions



142
143
144
145
146
147
148
149
150
# File 'lib/ib/option-chain.rb', line 142

def itm_options count:  5, right: :put, ref_price: :request, sort: :strike
  option_chain(  right: right,  ref_price: ref_price, sort: sort ) do | chain |
      if right == :put
        above_market_price_strikes = chain[1][0..count-1]
      else
        below_market_price_strikes = chain[-1][-count..-1].reverse
    end # branch
  end
end

#market_price(delayed: true, thread: false) ⇒ Object

if that fails use alternative exchanges (look to Contract.valid_exchanges)



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
# File 'lib/ib/market-price.rb', line 52

def market_price delayed:  true, thread: false

  tws=  Connection.current      # get the initialized ib-ruby instance
  the_id , the_price =  nil, nil
  tickdata =  Hash.new
  # define requested tick-attributes
  last, close, bid, ask   =   [ [ :delayed_last , :last_price ] , [:delayed_close , :close_price ],
                          [  :delayed_bid , :bid_price ], [  :delayed_ask , :ask_price ]] 
  request_data_type =  delayed ? :frozen_delayed :  :frozen

  tws.send_message :RequestMarketDataType, :market_data_type =>  IB::MARKET_DATA_TYPES.rassoc( request_data_type).first

  #keep the method-call running until the request finished
  #and cancel subscriptions to the message handler
  # method returns the (running) thread
  th = Thread.new do
    finalize, raise_delay_alert = false, false
    s_id = tws.subscribe(:TickSnapshotEnd){|x| finalize = true if x.ticker_id == the_id }

  e_id = tws.subscribe(:Alert){|x| raise_delay_alert = true if x.code == 354 && x.error_id == the_id } 
    # TWS Error 354: Requested market data is not subscribed.
#         r_id = tws.subscribe(:TickRequestParameters) {|x| } # raise_snapshot_alert =  true  if x.snapshot_permissions.to_i.zero?  && x.ticker_id == the_id  }
           
    # subscribe to TickPrices
    sub_id = tws.subscribe(:TickPrice ) do |msg| #, :TickSize,  :TickGeneric, :TickOption) do |msg|
      [last,close,bid,ask].each do |x| 
        tickdata[x] = msg.the_data[:price] if x.include?( IB::TICK_TYPES[ msg.the_data[:tick_type]]) 
        #  fast exit condition
        finalize = true if tickdata.size ==4  || ( tickdata[bid].present? && tickdata[ask].present? )  
      end if  msg.ticker_id == the_id 
    end
    # initialize »the_id« that is used to identify the received tick messages
    # by firing the market data request
    the_id = tws.send_message :RequestMarketData,  contract: self , snapshot: true 

    # todo implement config-feature to set timeout in configuration   (DRY-Feature)
    # Alternative zu Timeout
    # Thread.new do 
    #     i=0; loop{ i+=1; sleep 0.1; break if finalize || i > 1000 }

    i=0; 
    loop{ i+=1; break if i > 1000 || finalize || raise_delay_alert; sleep 0.05 } 
    tws.unsubscribe sub_id, s_id, e_id 
    # reduce :close_price delayed_close  to close a.s.o 
    if raise_delay_alert && !delayed
      error "No Marketdata Subscription, use delayed data <-- #{to_human}" 
#   elsif raise_snapshot_alert
#     error "No Snapshot Permissions, try alternative exchange  <-- #{to_human}"
    elsif i <= 1000
      tz = -> (z){ z.map{|y| y.to_s.split('_')}.flatten.count_duplicates.max_by{|k,v| v}.first.to_sym}
      data =  tickdata.map{|x,y| [tz[x],y]}.to_h
      valid_data = ->(d){ !(d.to_i.zero? || d.to_i == -1) }
      self.bars << data                      #  store raw data in bars
      the_price = if block_given? 
                    yield data 
                    # yields {:bid=>0.10142e3, :ask=>0.10144e3, :last=>0.10142e3, :close=>0.10172e3}
                  else # behavior if no block is provided
                    if valid_data[data[:last]]
                      data[:last] 
                    elsif valid_data[data[:bid]]
                      (data[:bid]+data[:ask])/2
                    elsif data[:close].present? 
                      data[:close]
                    else
                      nil
                    end
                  end
      self.misc =  the_price if thread  # store internally if in thread modus
    else   #  i > 1000
      tws.logger.info{ "#{to_human} --> No Marketdata received " }
    end

  end
  if thread
    th    # return thread
  else
    th.join
    the_price # return 
  end
end

#nessesary_attributesObject

returns a hash



61
62
63
64
65
66
67
68
69
70
# File 'lib/ib/verify.rb', line 61

def nessesary_attributes

v= { stock:  { currency: 'USD', exchange: 'SMART', symbol: nil} ,
 option: { currency: 'USD', exchange: 'SMART', right: 'P', expiry: nil, strike: nil, symbol:  nil} ,
 future: { currency: 'USD', exchange: nil, expiry: nil,  symbol: nil } , 
  forex:  { currency: 'USD', exchange: 'IDEALPRO', symbol: nil }
  }
sec_type.present? ? v[sec_type] : { con_id: nil, exchange: 'SMART' }  # enables to use only con_id for verifying
                                                                    # if the contract allows SMART routing
end

#option_chain(ref_price: :request, right: :put, sort: :strike, exchange: '') ⇒ Object

returns the Option Chain of the contract (if available)

parameters

right

:call, :put, :straddle

ref_price

:request or a numeric value

sort

:strike, :expiry

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
# File 'lib/ib/option-chain.rb', line 19

def option_chain ref_price: :request, right: :put, sort: :strike, exchange: ''

  ib =  Connection.current

  ## Enable Cashing of Definition-Matrix
  @option_chain_definition ||= [] 

  my_req = nil; finalize= false
  
  # -----------------------------------------------------------------------------------------------------
  # get OptionChainDefinition from IB ( instantiate cashed Hash )
  if @option_chain_definition.blank?
    sub_sdop = ib.subscribe( :SecurityDefinitionOptionParameterEnd ) { |msg| finalize = true if msg.request_id == my_req }
    sub_ocd =  ib.subscribe( :OptionChainDefinition ) do | msg |
      if msg.request_id == my_req
        message =  msg.data
        # transfer the first record to @option_chain_definition
        if @option_chain_definition.blank?
          @option_chain_definition =  msg.data

        end
        # override @option_chain_definition if a decent combination of attributes is met
        # us- options:  use the smart dataset
        # other options: prefer options of the default trading class 
        if message[:exchange] == 'SMART'  
          @option_chain_definition =  msg.data
          finalize = true
        end
        if @option_chain_definition.blank? &&  message[:trading_class] == symbol 
          @option_chain_definition =  msg.data
        end
      end
    end

    c = verify.first  #  ensure a complete set of attributes
    my_req = ib.send_message :RequestOptionChainDefinition, con_id: c.con_id,
      symbol: c.symbol,
      exchange: c.sec_type == :future ? c.exchange : "", # BOX,CBOE',
      sec_type: c[:sec_type]

    Thread.new do  

      Timeout::timeout(1, IB::TransmissionError,"OptionChainDefinition not received" ) do
        loop{ sleep 0.1; break if finalize } 
      end
      ib.unsubscribe sub_sdop , sub_ocd
    end.join
else
  Connection.logger.error { "#{to_human} : using cached data" }
end

# -----------------------------------------------------------------------------------------------------
# select values and assign to options
#
unless @option_chain_definition.blank? 
  requested_strikes =  if block_given?
                         ref_price = market_price if ref_price == :request
                         if ref_price.nil?
                           ref_price =  @option_chain_definition[:strikes].min  +
                             ( @option_chain_definition[:strikes].max -  
                              @option_chain_definition[:strikes].min ) / 2 
                             Connection.logger.error{  "#{to_human} :: market price not set – using midpoint of available strikes instead: #{ref_price.to_f}" }
                         end
                         atm_strike = @option_chain_definition[:strikes].min_by { |x| (x - ref_price).abs }
                         the_grouped_strikes = @option_chain_definition[:strikes].group_by{|e| e <=> atm_strike}  
                         begin
                           the_strikes =   yield the_grouped_strikes
                           the_strikes.unshift atm_strike unless the_strikes.first == atm_strike   # the first item is the atm-strike
                           the_strikes
                         rescue
                           Connection.logger.error "#{to_human} :: not enough strikes :#{@option_chain_definition[:strikes].map(&:to_f).join(',')} "
                           []
                         end
                       else
                         @option_chain_definition[:strikes]
                       end

    # third Friday of a month
    monthly_expirations =  @option_chain_definition[:expirations].find_all{|y| (15..21).include? y.day }
#       puts @option_chain_definition.inspect
    option_prototype = -> ( ltd, strike ) do 
        IB::Option.new( symbol: symbol, 
                       exchange: @option_chain_definition[:exchange],
                       trading_class: @option_chain_definition[:trading_class],
                       multiplier: @option_chain_definition[:multiplier],
                       currency: currency,  
                       last_trading_day: ltd, 
                       strike: strike, 
                       right: right )
    end
    options_by_expiry = -> ( schema ) do
      # Array: [ yymm -> Options] prepares for the correct conversion to a Hash
      Hash[  monthly_expirations.map do | l_t_d |
        [  l_t_d.strftime('%y%m').to_i , schema.map{ | strike | option_prototype[ l_t_d, strike ]}.compact ]
      end  ]                         # by Hash[ ]
    end
    options_by_strike = -> ( schema ) do
      Hash[ schema.map do | strike |
        [  strike ,   monthly_expirations.map{ | l_t_d | option_prototype[ l_t_d, strike ]}.compact ]
      end  ]                         # by Hash[ ]
    end

    if sort == :strike
      options_by_strike[ requested_strikes ] 
    else 
      options_by_expiry[ requested_strikes ] 
    end
  else
    Connection.logger.error "#{to_human} ::No Options available"
    nil # return_value
  end
end

#otm_options(count: 5, right: :put, ref_price: :request, sort: :strike) ⇒ Object

return OutOfTheMoneyOptions



153
154
155
156
157
158
159
160
161
162
# File 'lib/ib/option-chain.rb', line 153

def otm_options count:  5,  right: :put, ref_price: :request, sort: :strike
  option_chain( right: right, ref_price: ref_price, sort: sort ) do | chain |
      if right == :put
        #     puts "Chain: #{chain}"
        below_market_price_strikes = chain[-1][-count..-1].reverse
      else
        above_market_price_strikes = chain[1][0..count-1]
      end
  end
end

#query_contract(invalid_record: true) ⇒ Object

Generates an IB::Contract with the required attributes to retrieve a unique contract from the TWS

Background: If the tws is queried with a »complete« IB::Contract, it fails occacionally. So – even to update its contents, a defined subset of query-parameters has to be used.

The required data-fields are stored in a yaml-file and fetched by #YmlFile.

If ‘con_id` is present, only `con_id` and `exchange` are transmitted to the tws. Otherwise a IB::Stock, IB::Option, IB::Future or IB::Forex-Object with necessary attributes to query the tws is build (and returned)

If Attributes are missing, an IB::VerifyError is fired, This can be trapped with

rescue IB::VerifyError do ...


201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/ib/verify.rb', line 201

def  query_contract( invalid_record: true )  # :nodoc:
  # don't raise a verify error at this time. Contract.new con_id= xxxx, currency = 'xyz' is also valid
##  raise VerifyError, "Querying Contract faild: Invalid Security Type" unless SECURITY_TYPES.values.include? sec_type

  ## the yml contains symbol-entries
  ## these are converted to capitalized strings 
  items_as_string = ->(i){i.map{|x,y| x.to_s.capitalize}.join(', ')}
  ## here we read the corresponding attributes of the specified contract 
  item_values = ->(i){ i.map{|x,y| self.send(x).presence || y }}
  ## and finally we create a attribute-hash to instantiate a new Contract
  ## to_h is present only after ruby 2.1.0
  item_attributehash = ->(i){ i.keys.zip(item_values[i]).to_h }
  ## now lets proceed, but only if no con_id is present
  if con_id.blank? || con_id.zero?
#       if item_values[nessesary_attributes].any?( &:nil? ) 
#         raise VerifyError, "#{items_as_string[nessesary_attributes]} are needed to retrieve Contract,
#                                 got: #{item_values[nessesary_attributes].join(',')}"
#       end
  #      Contract.build  item_attributehash[nessesary_items].merge(:sec_type=> sec_type)  # return this
    Contract.build  self.attributes # return this
  else   # its always possible, to retrieve a Contract if con_id and exchange  or are present 
    Contract.new  con_id: con_id , :exchange => exchange.presence || item_attributehash[nessesary_attributes][:exchange].presence || 'SMART'        # return this
  end  # if 
end

#verify(thread: nil, &b) ⇒ Object

verifies the contract

returns the number of contracts retured by the TWS.

The method accepts a block. The queried contract-Object is assessible there. If multible contracts are specified, the block is executed with each of these contracts.

Parameter: thread: (true/false)

The verifiying-process ist time consuming. If multible contracts are to be verified, they can be queried simultaniously.

IB::Symbols::W500.map{|c|  c.verify(thread: true){ |vc| do_something }}.join

A simple verification works as follows:

s = IB::Stock.new symbol:"A"  
s --> <IB::Stock:0x007f3de81a4398 
    @attributes= {"symbol"=>"A", "sec_type"=>"STK", "currency"=>"USD", "exchange"=>"SMART"}> 
s.verify   --> 1
# s is unchanged !

s.verify{ |c| puts c.inspect }
 --> <IB::Stock:0x007f3de81a4398
     @attributes={"symbol"=>"A",  "updated_at"=>2015-04-17 19:20:00 +0200,

“sec_type”=>“STK”, “currency”=>“USD”, “exchange”=>“SMART”, “con_id”=>1715006, “expiry”=>“”, “strike”=>0.0, “local_symbol”=>“A”, “multiplier”=>0, “primary_exchange”=>“NYSE”},

@contract_detail=#<IB::ContractDetail:0x007f3de81ed7c8

@attributes={“market_name”=>“A”, “trading_class”=>“A”, “min_tick”=>0.01, “order_types”=>“ACTIVETIM, (…),WHATIF,”, “valid_exchanges”=>“SMART,NYSE,CBOE,ISE,CHX,(…)PSX”, “price_magnifier”=>1, “under_con_id”=>0, “long_name”=>“AGILENT TECHNOLOGIES INC”, “contract_month”=>“”, “industry”=>“Industrial”, “category”=>“Electronics”, “subcategory”=>“Electronic Measur Instr”, “time_zone”=>“EST5EDT”, “trading_hours”=>“20150417:0400-2000;20150420:0400-2000”, “liquid_hours”=>“20150417:0930-1600;20150420:0930-1600”, “ev_rule”=>0.0, “ev_multiplier”=>“”, “sec_id_list”=>{}, “updated_at”=>2015-04-17 19:20:00 +0200, “coupon”=>0.0, “callable”=>false, “puttable”=>false, “convertible”=>false, “next_option_partial”=>false}>>



55
56
57
58
# File 'lib/ib/verify.rb', line 55

def  verify  thread: nil,  &b 
  return [self] if contract_detail.present? || sec_type == :bag
  _verify update: false, thread: thread,  &b  # returns the allocated threads
end

#verify!Object

Verify that the contract is a valid IB::Contract, update the Contract-Object and return it.

Returns nil if the contract could not be verified.

> s =  Stock.new symbol: 'AA'
   => #<IB::Stock:0x0000000002626cc0 
      @attributes={:symbol=>"AA", :con_id=>0, :right=>"", :include_expired=>false, 
                   :sec_type=>"STK", :currency=>"USD", :exchange=>"SMART"}
> sp  = s.verify! &.essential
   => #<IB::Stock:0x00000000025a3cf8 
      @attributes={:symbol=>"AA", :con_id=>251962528, :exchange=>"SMART", :currency=>"USD",
                   :strike=>0.0, :local_symbol=>"AA", :multiplier=>0, :primary_exchange=>"NYSE", 
                   :trading_class=>"AA", :sec_type=>"STK", :right=>"", :include_expired=>false}

> s =  Stock.new symbol: 'invalid'
   =>  @attributes={:symbol=>"invalid", :sec_type=>"STK", :currency=>"USD", :exchange=>"SMART"}
>  sp  = s.verify! &.essential
   => nil


91
92
93
94
95
96
97
# File 'lib/ib/verify.rb', line 91

def verify!
  return self if contract_detail.present? || sec_type == :bag
  c =  0
  _verify( update: true){| response | c+=1 } # wait for the returned thread to finish
  IB::Connection.logger.error { "Multible Contracts detected during verify!."  } if c > 1
  con_id.to_i < 0 || contract_detail.is_a?(ContractDetail) ? self :  nil
end