Class: IB::Contract

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

Overview

end

Instance Method Summary collapse

Methods included from ProbabilityOfExpiring

#probability_of_assignment, #probability_of_expiring

Methods included from Eod

#eod, #from_csv, #get_bars, #to_csv

Instance Method Details

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

return a set of AtTheMoneyOptions



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

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


end

#included_in?(account) ⇒ Boolean

Returns:

  • (Boolean)


4
5
6
# File 'lib/ib/models/contract.rb', line 4

def included_in? 
  self if   .locate_contract(con_id)
end

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

return InTheMoneyOptions



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

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 TWS returns :TickSnapshotEnd if no ticks are transmitted
    # we don't have to implement out own 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.debug 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 && !(result.message =~ /market data is not available/)
            tws.logger.debug  "#{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

#necessary_attributesObject

returns a hash



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

def necessary_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 )



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

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

  ib = Connection.current

  # binary interthread communication
  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 

    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



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

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

#portfolio_value(account) ⇒ Object



8
9
10
11
12
13
14
# File 'lib/ib/models/contract.rb', line 8

def portfolio_value 
  if con_id.to_i > 0
    .portfolio_values.detect{|x| x.contract.con_id == con_id }
  else
    .portfolio_values.detect{|x| x.contract == self }
  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


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

def verify thread: nil, &b
  if thread
    Thread.new { _verify  &b }
  else
  _verify   &b
  end
end

#verify!Object

depreciated: Do not use anymore



75
76
77
78
79
80
81
82
# File 'lib/ib/verify.rb', line 75

def verify!
  c =  0
  IB::Connection.logger.warn "Contract.verify! is depreciated. Use \"contract =  contract.verify.first\" instead"
  c= verify.first
  self.attributes = c.invariant_attributes
  self.contract_detail = c.contract_detail
  self
end