
# yahoo ファイナンスの株価データページから株価を得る。
# 今日から過去 count_day 日分のデータを取得して、グラフをつくる。(省略した場合は 20日)
#  usage: ruby finance.rb [count_day]   
#
# 2009-09-12 katoy

require 'rubygems'
require 'open-uri'
require 'nokogiri'
require 'hpricot'
require 'google_chart'
require 'kconv'
require 'ya2yaml'
require 'date'
require 'pp'

$KCODE = "utf8"

TODAY = DateTime.now
YEAR0, MONTH0, DAY0 = 1990, 1, 1
YEAR1, MONTH1, DAY1 = TODAY.year, TODAY.month, TODAY.day
 
class Yahoo_finance
  VERSION = '2009-09-26'
  attr_reader :company, :data

  def initialize(company)
    @company = company
    @data = nil
  end

  def read_data(from_year = YEAR0, from_month = MONTH0, from_day = DAY0,
      to_year = YEAR1, to_month = MONTH1, to_day = DAY1)
    @data = get_data(from_year, from_month, from_day, to_year, to_month, to_day)
    @data.size
  end

  def update    
    read_data if @data == nil
    if @data.size > 0
      d = @data[@data.size - 1][0].scan(/(\d*)年(\d*)月(\d*)日/)
      year,month, day = d[0][0], d[0][1], d[0][2]
      @data = @data | get_data(year, month, day)
    end
  end

  def get_data(from_year = YEAR0, from_month = MONTH0, from_day = DAY0,
      to_year = YEAR1, to_month = MONTH1, to_day = DAY1)

    col = (@company.start_with?('998407'))? 5: 7  # 表の列数が 日経平均は少ない。

    data = []
    read_count = 0

    while(true) do
      path = sprintf("http://table.yahoo.co.jp/t?c=%d&a=%d&b=%d&f=%d&d=%d&e=%d&g=d&y=%d&s=%s&z=%s", 
        from_year, from_month, from_day,
        to_year, to_month, to_day,
        read_count, @company, @company)
      url = URI(path)
      doc = Nokogiri::XML(url.read.toutf8)
      xpath = "//small"

      vals = []
      day = ''
      row_count = 0
      doc.xpath(xpath).each_with_index do |entry, i|
        next if i < col  # 列名データ行は無視する

        vals << entry.text
        if i % col == col - 1 # 行の区切り
          data << vals
          vals = []
          row_count += 1
        end
      end

      break if row_count == 0 # データ無しのページ
      read_count += row_count        
    end

    data.reverse!
  end

  def get_market
    path = "http://stocks.finance.yahoo.co.jp/stocks/history/?code=#{@company}"
    doc = Hpricot(open(path).read)
    xpath = "//span.s170"

    market = []
    doc.search(xpath).each do |entry|
      text = entry.inner_text
      m = text.scan(/市場：(.*)/)
      if m[0][0] != ''
        id = market_id(m[0][0])
        market << id
        if id == ""
          pp "#{text}:#{id}:#{@company}"
        end
      else
        entry.next_sibling.search("//option").each do |opt|
          text = opt.inner_text
          id =  market_id(text)
          market << id
          if id == ""
            pp "#{text}:#{opt[:value]}:#{@company}"
          end
        end
      end
      break
    end
    market
  end

  # See: http://help.yahoo.co.jp/help/jp/finance/quote/quote-02.html
  # .t 	東京証券取引所（マザーズ、外国部を含む）
  # .o 	大阪証券取引所
  # .n 	名古屋証券取引所（セントレックスを含む）
  # .q 	ジャスダック証券取引所（NEOを含む）
  # .s 	札幌証券取引所（アンビシャスを含む）
  # .f 	福岡証券取引所（Q-Boardを含む）
  # .j 	ニッポン・ニュー・マーケット −「ヘラクレス」
  def market_id(text)
    if "東証1部" == text
      market = "t"
    elsif "東証2部" == text
      market = "t"
    elsif "マザーズ" == text
      market = "t"
    elsif "大証1部" == text
      market  = "o"
    elsif "大証2部" == text
      market = "o"
    elsif "名証1部" == text
      market = "n"
    elsif "名証2部" == text
      market = "n"
    elsif "名古屋セ" == text
      market = "n"
    elsif "JASDAQ" == text
      market = "q"
    elsif "札証" == text
      market = "s"
    elsif "札幌ア" == text
      market = "s"
    elsif "福証" == text
      market = "f"
    elsif "NEO" == text
      market = "j"
    elsif "HCG" == text
      market = "j"
    elsif "HCS" == text
      market = "j"
    else
      pp text
      market = ""
    end
    market
  end

  def make_graph(data, options = { })
    days = []
    vals =[]
    year = ''
    manth = ''
    
    data.each do |entry|
      vals << entry[4].gsub(",", "").to_f  # 終値

      # 年／月の変わり目で ラベルを変える
      d = entry[0].scan(/(\d*)年(\d*)月(\d*)日/)
      if year != d[0][0]
        year = d[0][0]
        manth = d[0][1]
        days << "#{d[0][0]}/#{d[0][1]}/#{d[0][2]}"
      elsif manth != d[0][1]
        manth = d[0][1]
        days << "#{d[0][1]}/#{d[0][2]}"
      else
        days << "#{d[0][2]}"
      end
    end

    lo = vals.min
    hi = vals.max
    min = lo - ((hi - lo) * 0.1).to_i
    max = hi
    #  max = (hi.to_i/ 10 * 10) + 10

    vals.map! { |v| v - min } # データ範囲を狭くする

    flc = GoogleChart::LineChart.new(options[:size], options[:title]) do |chart|
      chart.show_legend = false
      chart.axis(:x, :color => 'ff00ff', :labels => days) if options[:show_label]
      chart.axis(:y, :color => 'ff00ff', :range => [min, max])
      chart.fill(:background, :gradient, :angle => 0, :color => [['76A4FB', 1], ['ffffff', 0]])

      chart.data nil, vals , '0000ff'
    end
    flc
  end

  def save_yaml(options = {})
    s = self.ya2yaml
    open("charts/#{@company}.yaml", "w") {  |f|
      f.write s
    }

    if (options[:verify])
      ss = Yahoo_finance.load_yaml(@company).ya2yaml
      exit if ss != s
    end
  end

  def self.load_yaml(company)
    obj = nil
    File.open( "charts/#{ company}.yaml" ) {  |io|
      YAML.load_documents(io) do |y|
        obj = y
      end
    }
    obj
  end

end
