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

Overview

end

Instance Method Summary collapse

Instance Method Details

#associate_ticdataObject



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

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



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

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
# 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.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)

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

	r  #  the collected result

end

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

return InTheMoneyOptions



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

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



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

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

  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  
    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



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

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

#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