Last active
November 6, 2016 15:31
-
-
Save romuloceccon/15f5f9c35f14cb43a58de371a116a6bf to your computer and use it in GitHub Desktop.
Fetch financial statements from Google Finance
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #! /usr/bin/env ruby | |
| require 'bigdecimal' | |
| require 'csv' | |
| require 'net/http' | |
| require 'nokogiri' | |
| class CompanyStats | |
| attr_reader :name | |
| def initialize(name) | |
| @name = name | |
| @values = {} | |
| end | |
| def add_value(key, val) | |
| @values[key] = val | |
| end | |
| def dump | |
| @values.each_pair do |k, v| | |
| puts "#{k}: #{v && v.to_s('F')}" | |
| end | |
| end | |
| def value_of(item) | |
| if v = @values[item] | |
| v.to_s('F') | |
| end | |
| end | |
| end | |
| class Fetcher | |
| def initialize | |
| @http = Net::HTTP.new('www.google.com', 443) | |
| @http.use_ssl = true | |
| end | |
| def fetch(name, fstype) | |
| path = "/finance?q=BVMF%3A#{name}" | |
| path += "&fstype=#{fstype}" if fstype | |
| response = @http.get(path) | |
| unless response.is_a?(Net::HTTPSuccess) | |
| return [false, response.code] | |
| end | |
| [true, response.body] | |
| end | |
| end | |
| class Parser | |
| def initialize(html) | |
| @doc = Nokogiri::HTML(html) | |
| end | |
| protected | |
| def str_to_decimal(s) | |
| s = strip(s) | |
| s.nil? || s.empty? || s == '-' ? nil : BigDecimal(s.gsub(',', '')) | |
| end | |
| def strip(s) | |
| s.match(/^[\u00a0\s]*(.*?)[\u00a0\s]*$/)[1] | |
| end | |
| end | |
| class SummaryParser < Parser | |
| def parse(stats) | |
| price = @doc.xpath('//span[@class="pr"]') | |
| stats.add_value('Price', str_to_decimal(price.first.text)) | |
| snap = {} | |
| @doc.xpath('//div[@class="snap-panel"]//tr').each do |row| | |
| cols = row.xpath('./td').map { |x| x.text } | |
| snap[strip(cols[0])] = cols[1] | |
| end | |
| stats.add_value('P/E', str_to_decimal(snap['P/E'])) | |
| stats.add_value('Market capitalization', snap_to_decimal(snap['Mkt cap'])) | |
| end | |
| def valid? | |
| @doc.xpath('//div[@id="market-data-div"]').size > 0 | |
| end | |
| private | |
| def multiplier(s) | |
| { 'M' => 1e6, 'B' => 1e9 }[s] | |
| end | |
| def snap_to_decimal(s) | |
| if m = s.match(/^(.+)([MB])$/) | |
| str_to_decimal(m[1]) * multiplier(m[2]) | |
| end | |
| end | |
| end | |
| class FinancialsParser < Parser | |
| def parse(stats) | |
| ['inc', 'bal', 'cas'].each do |s| | |
| table = '//div[@id="%s"]//table[@id="%s"]' % ["#{s}annualdiv", "fs-table"] | |
| @doc.xpath(table + '/tbody/tr').each do |row| | |
| cols = row.xpath('./td').map { |x| x.text.strip } | |
| val = str_to_decimal(cols[1]) | |
| stats.add_value(cols[0], val && val * 1e6) | |
| end | |
| end | |
| end | |
| def valid? | |
| @doc.xpath('//div[@id="fs-type-tabs"]').size > 0 | |
| end | |
| end | |
| class TableOutput | |
| def initialize(io, columns) | |
| @io = io | |
| @columns = columns | |
| @csv = CSV.new(io) | |
| @csv << [''] + columns | |
| end | |
| def add(stats) | |
| @csv << [stats.name] + @columns.map { |x| stats.value_of(x) } | |
| end | |
| def close | |
| @csv.close | |
| end | |
| end | |
| if __FILE__ == $0 | |
| output = TableOutput.new(STDOUT, ['Total Revenue', 'P/E', 'Market capitalization']) | |
| begin | |
| fetcher = Fetcher.new | |
| while ARGV.size > 0 | |
| ticker = ARGV.shift | |
| stats = CompanyStats.new(ticker) | |
| [[nil, SummaryParser], ['ii', FinancialsParser]].each do |(fstype, klass)| | |
| success, data = fetcher.fetch(ticker, fstype) | |
| unless success | |
| STDERR.puts("HTTP error while fetching #{ticker}: #{data}") | |
| next | |
| end | |
| parser = klass.new(data) | |
| unless parser.valid? | |
| STDERR.puts("Invalid data for #{ticker}") | |
| next | |
| end | |
| parser.parse(stats) | |
| end | |
| output.add(stats) | |
| end | |
| ensure | |
| output.close | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment