medici 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []