medici 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ module Medici
2
+ class StockSymbolError < RuntimeError
3
+ attr_reader :symbol
4
+
5
+ def initialize(symbol = nil, message = nil)
6
+ super(message)
7
+ @symbol = symbol
8
+ end
9
+ end
10
+
11
+ class HistoricalDataError < RuntimeError
12
+ end
13
+ end
@@ -0,0 +1,52 @@
1
+ require 'date'
2
+
3
+ module Medici
4
+ class Historical
5
+ API_BASE_URL = 'http://www.google.com/finance/historical?'
6
+
7
+ attr_reader :date, :open, :high, :low, :close, :volume
8
+
9
+ def self.quote(symbol, start_date = nil, end_date = nil)
10
+ request = self.build_request(symbol, start_date, end_date)
11
+ response = Net::HTTP.get_response(URI.parse(request))
12
+ raise HistoricalDataError if response.code != '200'
13
+
14
+ self.parse(response.body)
15
+ end
16
+
17
+ private
18
+
19
+ def self.build_request(symbol, start_date, end_date)
20
+ params = {
21
+ q: symbol,
22
+ startdate: start_date,
23
+ enddate: end_date,
24
+ output: 'csv'
25
+ }
26
+
27
+ API_BASE_URL + params.each_with_object([]) do |(p, v), acc|
28
+ acc << "#{p}=#{v}" if not v.nil?
29
+ end.join('&')
30
+ end
31
+
32
+ def self.parse(response)
33
+ rows = response.split("\n")
34
+ rows.shift
35
+ rows.map do |row|
36
+ new(row.split(','))
37
+ end
38
+ end
39
+
40
+ private_class_method :new
41
+
42
+ def initialize(attributes)
43
+ @date = Date.parse(attributes[0])
44
+ @open = attributes[1].to_f
45
+ @high = attributes[2].to_f
46
+ @low = attributes[3].to_f
47
+ @close = attributes[4].to_f
48
+ @volume = attributes[5].to_i
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,92 @@
1
+ require 'net/http'
2
+ require 'xmlsimple'
3
+ require 'date'
4
+ require 'time'
5
+
6
+ module Medici
7
+ class Stock
8
+ API_BASE_URL = 'http://www.google.com/ig/api?'
9
+
10
+ PARSERS = {
11
+ identity: lambda { |_| _ },
12
+ to_int: lambda { |s| s.to_i },
13
+ to_float: lambda { |s| s.to_f },
14
+ to_date: lambda { |s| s.empty? ? nil : Date.parse(s) },
15
+ to_time: lambda { |s| s.empty? ? nil : Time.parse(s.scan(/.{2}/).join(':')) },
16
+ to_bool: lambda { |s| s.match(/(true|t|yes|y|1)$/i) != nil }
17
+ }
18
+
19
+ QUOTE_ATTRIBUTES = [
20
+ :symbol, :pretty_symbol, :symbol_lookup_url, :company, :exchange, :exchange_timezone,
21
+ :exchange_utc_offset, :exchange_closing, :divisor, :currency, :last, :high, :low,
22
+ :volume, :avg_volume, :market_cap, :open, :y_close, :change, :perc_change, :delay,
23
+ :trade_timestamp, :trade_date_utc, :trade_time_utc, :current_date_utc, :current_time_utc,
24
+ :symbol_url, :chart_url, :disclaimer_url, :ecn_url, :isld_last, :isld_trade_date_utc,
25
+ :isld_trade_time_utc, :brut_last, :brut_trade_date_utc, :brut_trade_time_utc, :daylight_savings
26
+ ]
27
+
28
+ PARSE_MAP = {
29
+ exchange_closing: PARSERS[:to_int],
30
+ divisor: PARSERS[:to_int],
31
+ last: PARSERS[:to_float],
32
+ high: PARSERS[:to_float],
33
+ low: PARSERS[:to_float],
34
+ volume: PARSERS[:to_int],
35
+ avg_volume: PARSERS[:to_int],
36
+ market_cap: PARSERS[:to_float],
37
+ open: PARSERS[:to_float],
38
+ y_close: PARSERS[:to_float],
39
+ change: PARSERS[:to_float],
40
+ perc_change: PARSERS[:to_float],
41
+ delay: PARSERS[:to_int],
42
+ isld_last: PARSERS[:to_float],
43
+ trade_date_utc: PARSERS[:to_date],
44
+ trade_time_utc: PARSERS[:to_time],
45
+ current_date_utc: PARSERS[:to_date],
46
+ current_time_utc: PARSERS[:to_time],
47
+ isld_trade_date_utc: PARSERS[:to_date],
48
+ isld_trade_time_utc: PARSERS[:to_time],
49
+ daylight_savings: PARSERS[:to_bool]
50
+ }
51
+ PARSE_MAP.default = PARSERS[:identity]
52
+
53
+ QUOTE_ATTRIBUTES.each do |attribute|
54
+ attr_reader attribute
55
+ end
56
+
57
+ def self.quotes(symbols)
58
+ query = symbols.map { |symbol| "stock=#{symbol}" }.join('&')
59
+ response = Net::HTTP.get_response(URI.parse(API_BASE_URL + query))
60
+
61
+ self.parse(response.body)
62
+ end
63
+
64
+ def self.quote(symbol)
65
+ self.quotes([symbol]).first
66
+ end
67
+
68
+ private
69
+
70
+ def self.parse(xml_data)
71
+ doc = XmlSimple.xml_in(xml_data)
72
+ doc['finance'].map do |stock|
73
+ attributes = stock.each_with_object({}) do |(attribute, value), hash|
74
+ hash[attribute] = value.first['data'] if QUOTE_ATTRIBUTES.include? attribute.to_sym
75
+ end
76
+ raise StockSymbolError.new(attributes['symbol']) if attributes['exchange'] == 'UNKNOWN EXCHANGE'
77
+
78
+ new(attributes)
79
+ end
80
+ end
81
+
82
+ private_class_method :new
83
+
84
+ def initialize(attributes)
85
+ attributes.each do |attribute, value|
86
+ parser = PARSE_MAP[attribute.to_sym]
87
+ instance_variable_set("@#{attribute}", parser.call(value))
88
+ end
89
+ end
90
+
91
+ end
92
+ end
data/lib/medici.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'medici/stock'
2
+ require 'medici/historical'
3
+
4
+ module Medici
5
+ extend self
6
+
7
+ def quotes(symbols)
8
+ Stock.quotes(symbols)
9
+ end
10
+
11
+ def quote(symbol)
12
+ Stock.quote(symbol)
13
+ end
14
+
15
+ def historical(symbol, start_date = nil, end_date = nil)
16
+ Historical.quote(symbol, start_date, end_date)
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: medici
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Timothy Spratt
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: xml-simple
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: Medici is a library for obtaining stock quotes and historical stock data
31
+ using the undocumented Google Finance API.
32
+ email: tim@onlysix.co.uk
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - lib/medici.rb
38
+ - lib/medici/errors.rb
39
+ - lib/medici/historical.rb
40
+ - lib/medici/stock.rb
41
+ homepage: http://github.com/timspratt/medici
42
+ licenses:
43
+ - MIT
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubyforge_project:
62
+ rubygems_version: 1.8.23
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: ! '["Simple real-time stock quotes from the Google Finance API.", "Medici
66
+ is a library for obtaining stock quotes and historical stock data using the undocumented
67
+ Google Finance API.", ["Timothy Spratt"]]'
68
+ test_files: []