medici 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/medici/errors.rb +13 -0
- data/lib/medici/historical.rb +52 -0
- data/lib/medici/stock.rb +92 -0
- data/lib/medici.rb +18 -0
- metadata +68 -0
@@ -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
|
data/lib/medici/stock.rb
ADDED
@@ -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: []
|