为了满足自己炒股兴趣的需要,根据《神奇公式》,对A股市场做了一个排序系统,既完成了一个对自己有效的需求,也从中学习rails。

Source : https://coding.net/u/hwh008/p/mss/git

Site : http://www.magica.me

爬虫

股票的财务数据都是从sina抓的,所以Nokogiri是首选,有了他,做点基础的爬虫实在太容易了。

用whenever每天定点去爬一下收盘价,顺便检测一些需要更新财务数据的股票。

StockInfo.tick_sheet

缓存

我有一个很耗时的model函数,也就是对所有股票进行排序的公式实现:

StockSheet.calc_better_cheap

我希望这个公式的结果页面显示后,可以重用这个页面,不要重新计算排序,因此我尝试了action cache和fragment cache,然而并没有什么卵用,因为这两种cache都还是要执行controller#action的,费时的函数调用恰好放在action中。我可以把这个调用放到view里,这样就解决了问题,但是又涉及到请求参数变化的问题,而且这串调用放到view里也带来了限制,不方便在action里对数据做更多的修饰。

另外,我也搞不清楚fragment cache的实现机制和最佳实践。

经过搜索我找到了qor_cache,还是国人的优秀作品,有3点好处:

  1. 针对Model函数结果的cache,正好我就是model函数耗时多。
  2. qor_cache wrap了我指定的函数,使得我的代码对这个gem没有依赖,可以轻易的从config中删掉qor_cache。
  3. 以接口的方式告诉我一条cache的最佳实践:

    The cache key is the fluid part and the cache content is the fixed part. A given key should always return the same content. You never update the content after it’s been written and you never try to expire it either.

cache_key 'stock_update' do
    StockInfo.first.updated_at
end

scope :stock_sheet do
    cache_class_method :calc_better_cheap, 'stock_update'
end

函数的输入参数是一个hash obj,qor_cache将参数作为cache key的一部分,但是这个hash obj中,其实有些key并不影响cache,因此我根据qor_cache做了一个hack:

hash_obj.instance_eval do
        # 为了优化qor cache,不是每个选项都影响cache key
        def inspect
            %Q(inspect redefine:#{self["mktcap_min"]},#{self["mktcap_max"]})
        end
        self
    end

翻页

我没有使用will_paginate,因为will_paginate只包装relation对象,但我要做的是对数据进行排序计算,已经将数据全部都取出来了,因此找了一个支持page array的gem:kaminari,听这名字像日本人写的gem。

  def better_cheap
    stocks = Kaminari.paginate_array StockSheet.calc_better_cheap(calc_params)
    @stocks_page = stocks.page(params.fetch(:page, 1)).per 100
  end

函数式FP

函数式编程其实就是好看,可以把对集合的一连串操作都写在一行里,长长的。

神奇公式的排序算法就是,将集合所有元素根据指标A排序,取排序顺序为分数A,再根据指标B排序,取排序顺序为分数B,根据分数AB之和再排序。写到一行里是这样的:

def self.calc_better_cheap(opt)
    available(opt).sort { |a, b| a.better_v <=> b.better_v }.reverse.map.with_index(1) { |el, i| el.better_ord = i ; el}.sort { |a,b| a.cheap_v <=> b.cheap_v }.reverse.map.with_index(1) { |el, i| el.cheap_ord = i; el}.sort { |a,b| a.bc_value <=> b.bc_value }
  end

nginx + passenger

生产部署用的是passenger,很简单,安装就好了。就是环境变量遇上了一些问题,后来通过dotenv-rails解决。

Helper让View漂亮

有两种不漂亮的写法,一种是在view里写表达式,另一种是在action里把表达式算好的结果放到instance var。

不过erb模版对Helper的支持还是不够漂亮,我比较倾向的是那种管道式的,这样可以把一串helper像羊肉串一样串起来。