Class: IB::Contract

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

Overview

end

Instance Method Summary collapse

Instance Method Details

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

return a set of AtTheMoneyOptions



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

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>



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

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}" }
        self.bars = 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.close
    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)

    recieved.pop # blocks until a message is ready on the queue or the queue is closed

  tws.unsubscribe a
  tws.unsubscribe b

    block_given? ?  bars.map{|y| yield y} : bars  # return bars or result of block

end

#from_csv(file: nil) ⇒ Object

read csv-data into bars



156
157
158
159
160
161
162
# File 'lib/ib/eod.rb', line 156

def from_csv file: nil
  file ||=  "#{symbol}.csv"
  self.bars = []
  CSV.foreach( file,  headers: true, header_converters: :symbol) do |row|
    self.bars << IB::Bar.new( **row.to_h )
  end
end

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

return InTheMoneyOptions



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

def itm_options count:  5, right: :put, ref_price: :request, sort: :strike, exchange: ''
  option_chain(  right: right,  ref_price: ref_price, sort: sort, exchange: exchange ) 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, no_error: false) ⇒ Object

Raw-data are stored in the bars-attribute of IB::Contract

(volatile, ie. data are not preserved when the Object is copied)

Example: IB::Stock.new(symbol: :ge).market_price returns the current market-price

Example: IB::Stock.new(symbol: :ge).market_price(thread: true).join assigns IB::Symbols.sie.misc with the value of the :last (or delayed_last) TickPrice-Message and returns this value, too



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
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/ib/market-price.rb', line 45

def market_price delayed:  true, thread: false, no_error: false

  tws=  Connection.current     # get the initialized ib-ruby instance
  the_id , the_price =  nil, nil
  tickdata =  Hash.new
  q =  Queue.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

  # From the tws-documentation (https://interactivebrokers.github.io/tws-api/market_data_type.html)
  # Beginning in TWS v970, a IBApi.EClient.reqMarketDataType callback of 1 will occur automatically
  # after invoking reqMktData if the user has live data permissions for the instrument.
  #
  # so - even if "delayed" is specified, realtime-data are returned if RT-permissions are present
  #

  # method returns the (running) thread
  th = Thread.new do
    # about 11 sec after the request, the TES returns :TickSnapshotEnd if no ticks are transmitted
    # we don't have to implement out ow timeout-criteria
    s_id = tws.subscribe(:TickSnapshotEnd){|x| q.push(true) if x.ticker_id == the_id }
    a_id = tws.subscribe(:Alert){|x| q.push(x) if [200, 354, 10167, 10168].include?( x.code )  && 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
        q.push(true) if tickdata.size >= 4 
      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 

    while !q.closed? do
      result = q.pop
      if result.is_a? IB::Messages::Incoming::Alert
        tws.logger.info result.message
        case result.code
        when 200 
          q.close
          error "#{to_human} --> #{result.message}"   unless no_error
        when 354, #   not subscribed to market data
          10167,
          10168
          if delayed
            tws.logger.info  "#{to_human} --> requesting delayed data" 
            tws.send_message :RequestMarketDataType, :market_data_type => 3 
            self.misc = :delayed
            sleep 0.1
            the_id = tws.send_message :RequestMarketData,  contract: self , snapshot: true 
          else
            q.close
            tws.logger.error "#{to_human} --> No marketdata permissions"  unless no_error
          end
        end
      elsif result.present?
        q.close
        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 = misc == :delayed ? { :delayed =>  the_price }  : { realtime: the_price }   
      else
        q.close
        error "#{to_human} --> No Marketdata received " 
      end
    end

    tws.unsubscribe sub_id, s_id, a_id
  end
  if thread
    th  # return thread
  else
    th.join
    the_price # return 
  end
end

#nessesary_attributesObject

returns a hash



57
58
59
60
61
62
63
64
65
66
# File 'lib/ib/verify.rb', line 57

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: '', trading_class: nil) ⇒ Object

returns the Option Chain (monthly options, expiry: third friday) of the contract (if available)

parameters

right

:call, :put, :straddle ( default: :put )

ref_price

:request or a numeric value ( default: :request )

sort

:strike, :expiry

exchange

List of Exchanges to be queried (Blank for all available Exchanges)

trading_class ( optional )



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

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

  ib = Connection.current
  finalize = Queue.new

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

  my_req = nil

  # -----------------------------------------------------------------------------------------------------
  # get OptionChainDefinition from IB ( instantiate cashed Hash )
  if @option_chain_definition.blank?
    sub_sdop = ib.subscribe( :SecurityDefinitionOptionParameterEnd ) { |msg| finalize.push(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.push(true)
        end
        if message[:trading_class] == symbol
          @option_chain_definition = msg.data
          finalize.push(true)
        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]

    finalize.pop #  wait until data appeared 
    #i=0; loop { sleep 0.1; break if i> 1000 || finalize; i+=1 } 

    ib.unsubscribe sub_sdop, sub_ocd
  else
    Connection.logger.info { "#{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.warn { "#{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).verify &.first
    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, exchange: '') ⇒ Object

return OutOfTheMoneyOptions



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

def otm_options count:  5,  right: :put, ref_price: :request, sort: :strike, exchange: ''
  option_chain( right: right, ref_price: ref_price, sort: sort, exchange: exchange ) 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

#to_csv(file: nil) ⇒ Object

creates (or overwrites) the specified file (or symbol.csv) and saves bar-data



146
147
148
149
150
151
152
153
# File 'lib/ib/eod.rb', line 146

def to_csv file:nil
  file ||=  "#{symbol}.csv"

  if bars.present?
    headers = bars.first.invariant_attributes.keys
    CSV.open( file, 'w' ) {|f| f << headers ; bars.each {|y| f << y.invariant_attributes.values } }
  end
end

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

verifies the contract

returns the number of contracts returned by the TWS.

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

Verify returns an Array of contracts. The operation leaves the contract untouched.

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.first.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
   => []

Takes a Block to modify the queried contracts

f = Future.new symbol: ‘M2K’

con_ids = f.verify{ |c| c.con_id }

[412889018, 428519982, 446091466, 461318872, 477836981]

Parameter: thread: (true/false)

If multiple contracts are to be verified, they can be queried simultaneously.

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


51
52
53
54
# File 'lib/ib/verify.rb', line 51

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

depreciated: Do not use anymore



70
71
72
73
74
75
76
# File 'lib/ib/verify.rb', line 70

def verify!
  c =  0
  IB::Connection.logger.warn "Contract.verify! is depreciated. Use \"contract =  contract.verify.first\" instead"
  _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
  self
end