Class: IB::Contract
- Inherits:
-
Object
- Object
- IB::Contract
- Defined in:
- lib/ib/eod.rb,
lib/ib/verify.rb,
lib/ib/market-price.rb,
lib/ib/option-chain.rb
Instance Method Summary collapse
-
#_verify(thread: nil, update:, &b) ⇒ Object
Base method to verify a contract.
- #associate_ticdata ⇒ Object
-
#atm_options(ref_price: :request, right: :put) ⇒ Object
return a set of AtTheMoneyOptions.
-
#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>.
-
#itm_options(count: 5, right: :put, ref_price: :request, sort: :strike) ⇒ Object
return InTheMoneyOptions.
-
#market_price(delayed: true, thread: false) ⇒ Object
if that fails use alternative exchanges (look to Contract.valid_exchanges).
-
#nessesary_attributes ⇒ Object
returns a hash.
-
#option_chain(ref_price: :request, right: :put, sort: :strike, exchange: '') ⇒ Object
returns the Option Chain of the contract (if available).
-
#otm_options(count: 5, right: :put, ref_price: :request, sort: :strike) ⇒ Object
return OutOfTheMoneyOptions.
-
#query_contract(invalid_record: true) ⇒ Object
Generates an IB::Contract with the required attributes to retrieve a unique contract from the TWS.
-
#verify(thread: nil, &b) ⇒ Object
verifies the contract .
-
#verify! ⇒ Object
Verify that the contract is a valid IB::Contract, update the Contract-Object and return it.
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 = 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 == ib.logger.error { "Not a valid Contract :: #{self.to_human} " } exitcondition = true end when Messages::Incoming::ContractData if msg.request_id.to_i == # 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 == 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 = ib. :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_ticdata ⇒ Object
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. :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. << 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. :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 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. # 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. 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 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. :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. :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. << 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_attributes ⇒ Object
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 = 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 [:exchange] == 'SMART' @option_chain_definition = msg.data finalize = true end if @option_chain_definition.blank? && [:trading_class] == symbol @option_chain_definition = msg.data end end end c = verify.first # ensure a complete set of attributes my_req = ib. :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 = -> ( 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 = -> ( 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 [ requested_strikes ] else [ 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 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 |