Ruby:Decorator模式大大减慢了简单的程序

时间:2015-09-26 17:58:21

标签: ruby

我最近编写了一个程序,用于从股票市场返回一堆不健康的股票。基本算法是:

  1. 查询交易所中每只股票的所有报价(纽约证券交易所或纳斯达克股票代码)

  2. 从第1步

  3. 找出少于5美元的那些
  4. 从第2步中找到那些已经停产3天并且体积很大的产品(价格昂贵,因为我必须为每个库存提出要求,这对于纳斯达克目前来说是~700)。

  5. 扫描新闻,查找步骤3返回的新闻。

  6. 我把这一切都放在一个文件中:

    原始实施(https://github.com/EdmundMai/minion/blob/aa14bc3234a4953e7273ec502276c6f0073b459d/lib/minion.rb):

    require 'bundler/setup'
    require "minion/version"
    require "yahoo-finance"
    require "business_time"
    require 'nokogiri'
    require 'open-uri'
    
    module Minion
      class << self
        def query(exchange)
          client = YahooFinance::Client.new
          all_companies = CSV.read("#{exchange}.csv")
    
          small_caps = []
    
          ticker_symbols = all_companies.map { |row| row[0] }
          ticker_symbols.each_slice(200) do |batch|
            data = client.quotes(batch, [:symbol, :last_trade_price, :average_daily_volume])
            small_caps << data.select { |stock| stock.last_trade_price.to_f < 5.0 }
          end
    
          attractive = []
    
          small_caps.flatten!.each_with_index do |small_cap, index|
            begin
              data = client.historical_quotes(small_cap.symbol, { start_date: 2.business_days.ago, end_date: Time.now })
              closing_prices = data.map(&:close).map(&:to_f)
              volumes = data.map(&:volume).map(&:to_i)
    
              negative_3_days_in_a_row = closing_prices == closing_prices.sort
              larger_than_average_volume = volumes.reduce(:+) / volumes.count > small_cap.average_daily_volume.to_i
    
              if negative_3_days_in_a_row && larger_than_average_volume
                attractive << small_cap.symbol
                puts "Qualified: #{small_cap.symbol}, finished with #{index} out of #{small_caps.count}"
              else
                puts "Not qualified: #{small_cap.symbol}, finished with #{index} out of #{small_caps.count}"
              end
            rescue => e
              puts e.inspect
            end
          end
    
          final_results = []
    
          attractive.each do |symbol|
            rss_feed = Nokogiri::HTML(open("http://feeds.finance.yahoo.com/rss/2.0/headline?s=#{symbol}&region=US&lang=en-US"))
            html_body = rss_feed.css('body')[0].text
            diluting = false
            ['warrant', 'cashless exercise'].each do |keyword|
              diluting = true if html_body.match(/#{keyword}/i)
            end
            final_results << symbol if diluting
          end
    
          final_results
        end
      end
    end
    

    这非常快,并且可以在一分钟或更短的时间内完成约700种库存的处理。

    然后,我尝试重构并将算法拆分成不同的类和文件而根本不改变算法。我决定使用装饰器模式,因为它似乎适合。但是,当我现在运行程序时,它会使每个请求都非常缓慢(15分钟以上)。我知道这一点,因为我的puts陈述打印得很慢。

    新的和较慢的实施(https://github.com/EdmundMai/minion/blob/master/lib/minion.rb

    require 'bundler/setup'
    require "minion/version"
    require "yahoo-finance"
    require "minion/dilution_finder"
    require "minion/negative_finder"
    require "minion/small_cap_finder"
    require "minion/market_fetcher"
    
    module Minion
      class << self
        def query(exchange)
          all_companies = CSV.read("#{exchange}.csv")
          all_tickers = all_companies.map { |row| row[0] }
    
          short_finder = DilutionFinder.new(NegativeFinder.new(SmallCapFinder.new(MarketFetcher.new(all_tickers))))
          short_finder.results
        end
      end
    end
    

    它根据我的puts

    落后的部分
    require "yahoo-finance"
    require "business_time"
    require_relative "stock_finder"
    
    class NegativeFinder < StockFinder
      def results
        client = YahooFinance::Client.new
        results = []
        finder.results.each_with_index do |stock, index|
          begin
            data = client.historical_quotes(stock.symbol, { start_date: 2.business_days.ago, end_date: Time.now })
            closing_prices = data.map(&:close).map(&:to_f)
            volumes = data.map(&:volume).map(&:to_i)
    
            negative_3_days_in_a_row = closing_prices == closing_prices.sort
            larger_than_average_volume = volumes.reduce(:+) / volumes.count > stock.average_daily_volume.to_i
    
            if negative_3_days_in_a_row && larger_than_average_volume
              results << stock
              puts "Qualified: #{stock.symbol}, finished with #{index} out of #{finder.results.count}"
            else
              puts "Not qualified: #{stock.symbol}, finished with #{index} out of #{finder.results.count}"
            end
          rescue => e
            puts e.inspect
          end
        end
        results
      end
    end
    

    它在第3步滞后(对每只股票提出一个请求)。不知道发生了什么,所以任何建议都会受到赞赏。如果要克隆程序并运行它,只需在lib / minion.rb的最后一行注释并输入ruby lib/minion.rb

2 个答案:

答案 0 :(得分:2)

经过调试后,我发现了它。这是因为我在循环内部调用finder.results(结果是装饰方法),如下所示:

require 'bundler/setup'
require "minion/version"
require "yahoo-finance"
require "minion/dilution_finder"
require "minion/negative_finder"
require "minion/small_cap_finder"
require "minion/market_fetcher"

module Minion
  class << self
    def query(exchange)
      all_companies = CSV.read("#{exchange}.csv")
      all_tickers = all_companies.map { |row| row[0] }

      short_finder = DilutionFinder.new(NegativeFinder.new(SmallCapFinder.new(MarketFetcher.new(all_tickers))))
      short_finder.results
    end
  end
end

根据我的puts

,这个部分滞后了
require "yahoo-finance"
require "business_time"
require_relative "stock_finder"

class NegativeFinder < StockFinder
  def results
    client = YahooFinance::Client.new
    results = []
    finder.results.each_with_index do |stock, index|
      begin
        data = client.historical_quotes(stock.symbol, { start_date: 2.business_days.ago, end_date: Time.now })
        closing_prices = data.map(&:close).map(&:to_f)
        volumes = data.map(&:volume).map(&:to_i)

        negative_3_days_in_a_row = closing_prices == closing_prices.sort
        larger_than_average_volume = volumes.reduce(:+) / volumes.count > stock.average_daily_volume.to_i

        if negative_3_days_in_a_row && larger_than_average_volume
          results << stock
          // HERE!!!!!!!!!!!!!!!!!!!!!!!!!
          puts "Qualified: #{stock.symbol}, finished with #{index} out of #{finder.results.count}" <------------------------------------
        else
          // AND HERE!!!!!!!!!!!!!!!!!!!!!!!!!
          puts "Not qualified: #{stock.symbol}, finished with #{index} out of #{finder.results.count}" <-----------------------------------------------------------
        end
      rescue => e
        puts e.inspect
      end
    end
    results
  end
end

每次迭代NegativeFinder中的循环时,都会导致一连串请求。删除该呼叫修复它。课程:使用装饰器模式时,要么只调用一次修饰方法,特别是当你在每个调用中做一些昂贵的事情时。或者将返回的变量保存在实例变量中,这样您就不必每次都计算它。

另外作为旁注,我决定不使用装饰器模式,因为我认为它不适用于此。像SmallCapFinder.new(SmallCapFinder.new(MarketFetcher.new(all_tickers)))这样的东西根本不添加功能(使用装饰器模式的主要功能),因此链接装饰器不会做任何事情。因此,我只是将它们作为方法而不是增加不必要的复杂性。

答案 1 :(得分:1)

你给我们的代码中缺少一些东西(Base class StockFinder,MarketFetcher)。但我认为你现在不止一个YahooFinance :: Client。输入/输出到其他系统通常是速度问题的原因。

我建议您首先封装财务客户端并访问财务数据。当您想要切换财务数据提供者或添加另一个时,这会更容易。而不是装饰模式,我只会使用普通的旧方法来寻找小型上限,找到负面的等等。