module IB require 'active_support/core_ext/date/calculations' module BuisinesDays # https://stackoverflow.com/questions/4027768/calculate-number-of-business-days-between-two-days # Calculates the number of business days in range (start_date, end_date] # # @param start_date [Date] # @param end_date [Date] # # @return [Fixnum] def self.business_days_between(start_date, end_date) days_between = (end_date - start_date).to_i return 0 unless days_between > 0 # Assuming we need to calculate days from 9th to 25th, 10-23 are covered # by whole weeks, and 24-25 are extra days. # # Su Mo Tu We Th Fr Sa # Su Mo Tu We Th Fr Sa # 1 2 3 4 5 # 1 2 3 4 5 # 6 7 8 9 10 11 12 # 6 7 8 9 ww ww ww # 13 14 15 16 17 18 19 # ww ww ww ww ww ww ww # 20 21 22 23 24 25 26 # ww ww ww ww ed ed 26 # 27 28 29 30 31 # 27 28 29 30 31 whole_weeks, extra_days = days_between.divmod(7) unless extra_days.zero? # Extra days start from the week day next to start_day, # and end on end_date's week date. The position of the # start date in a week can be either before (the left calendar) # or after (the right one) the end date. # # Su Mo Tu We Th Fr Sa # Su Mo Tu We Th Fr Sa # 1 2 3 4 5 # 1 2 3 4 5 # 6 7 8 9 10 11 12 # 6 7 8 9 10 11 12 # ## ## ## ## 17 18 19 # 13 14 15 16 ## ## ## # 20 21 22 23 24 25 26 # ## 21 22 23 24 25 26 # 27 28 29 30 31 # 27 28 29 30 31 # # If some of the extra_days fall on a weekend, they need to be subtracted. # In the first case only corner days can be days off, # and in the second case there are indeed two such days. extra_days -= if start_date.tomorrow.wday <= end_date.wday [start_date.tomorrow.sunday?, end_date.saturday?].count(true) else 2 end end (whole_weeks * 5) + extra_days end end class Contract # Receive EOD-Data # # The Enddate has to be specified (as Date Object), t # # The Duration can either be specified as Sting " yx D" or as Integer. # Altenative a start date can be specified with the :start parameter. # # The parameter :what specified the kind of received data: # Valid values: # :trades, :midpoint, :bid, :ask, :bid_ask, # :historical_volatility, :option_implied_volatility, # :option_volume, :option_open_interest # # The results can be preprocessed through a block, thus # # puts IB::Symbols::Index::stoxx.eod( duration: '10 d')){|r| r.to_human} # # # # # # # # # # # # «to_human« is not needed here because ist aliased with `to_s` # # puts Symbols::Stocks.wfc.eod( start: Date.new(2019,10,9), duration: 3 ) # # # # puts Symbols::Stocks.wfc.eod( to: Date.new(2019,10,9), duration: 3 ) # # # # 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 # def end # class end # module