mumboe-currency 0.5
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.
- data/bin/currency_historical_rate_load +105 -0
- data/examples/ex1.rb +13 -0
- data/examples/xe1.rb +20 -0
- data/lib/currency/active_record.rb +265 -0
- data/lib/currency/config.rb +91 -0
- data/lib/currency/core_extensions.rb +41 -0
- data/lib/currency/currency/factory.rb +228 -0
- data/lib/currency/currency.rb +175 -0
- data/lib/currency/currency_version.rb +6 -0
- data/lib/currency/exception.rb +119 -0
- data/lib/currency/exchange/rate/deriver.rb +157 -0
- data/lib/currency/exchange/rate/source/base.rb +166 -0
- data/lib/currency/exchange/rate/source/failover.rb +63 -0
- data/lib/currency/exchange/rate/source/federal_reserve.rb +160 -0
- data/lib/currency/exchange/rate/source/historical/rate.rb +184 -0
- data/lib/currency/exchange/rate/source/historical/rate_loader.rb +186 -0
- data/lib/currency/exchange/rate/source/historical/writer.rb +220 -0
- data/lib/currency/exchange/rate/source/historical.rb +79 -0
- data/lib/currency/exchange/rate/source/new_york_fed.rb +127 -0
- data/lib/currency/exchange/rate/source/provider.rb +120 -0
- data/lib/currency/exchange/rate/source/test.rb +50 -0
- data/lib/currency/exchange/rate/source/the_financials.rb +191 -0
- data/lib/currency/exchange/rate/source/timed_cache.rb +198 -0
- data/lib/currency/exchange/rate/source/xe.rb +165 -0
- data/lib/currency/exchange/rate/source.rb +89 -0
- data/lib/currency/exchange/rate.rb +214 -0
- data/lib/currency/exchange/time_quantitizer.rb +111 -0
- data/lib/currency/exchange.rb +50 -0
- data/lib/currency/formatter.rb +290 -0
- data/lib/currency/macro.rb +321 -0
- data/lib/currency/money.rb +295 -0
- data/lib/currency/money_helper.rb +13 -0
- data/lib/currency/parser.rb +151 -0
- data/lib/currency.rb +143 -0
- data/test/string_test.rb +54 -0
- data/test/test_base.rb +44 -0
- metadata +90 -0
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'currency/exchange/rate/source/historical'
|
2
|
+
require 'currency/exchange/rate/source/historical/rate'
|
3
|
+
require 'currency/exchange/rate/source/historical/writer'
|
4
|
+
|
5
|
+
|
6
|
+
# Currency::Config.current.historical_table_name = 'currency_rates'
|
7
|
+
# opts['uri_path'] ||= 'syndicated/cnusa/fxrates.xml'
|
8
|
+
|
9
|
+
# Loads rates from multiple sources and will store them
|
10
|
+
# as historical rates in a database.
|
11
|
+
|
12
|
+
class ::Currency::Exchange::Rate::Source::Historical::RateLoader
|
13
|
+
attr_accessor :options
|
14
|
+
attr_accessor :source_options
|
15
|
+
attr_accessor :required_currencies
|
16
|
+
attr_accessor :rate_sources
|
17
|
+
attr_accessor :rate_source_options
|
18
|
+
attr_accessor :verbose
|
19
|
+
attr_accessor :preferred_summary_source
|
20
|
+
attr_accessor :base_currencies
|
21
|
+
attr_accessor :summary_rate_src
|
22
|
+
attr_reader :writer
|
23
|
+
|
24
|
+
def initialize(opts = { })
|
25
|
+
self.summary_rate_src = 'summary'
|
26
|
+
self.source_options = { }
|
27
|
+
self.options = opts.dup.freeze
|
28
|
+
self.base_currencies = [ :USD ]
|
29
|
+
self.required_currencies =
|
30
|
+
[
|
31
|
+
:USD,
|
32
|
+
:GBP,
|
33
|
+
:CAD,
|
34
|
+
:EUR,
|
35
|
+
# :MXP,
|
36
|
+
]
|
37
|
+
self.verbose = ! ! ENV['CURRENCY_VERBOSE']
|
38
|
+
opts.each do | k, v |
|
39
|
+
setter = "#{k}="
|
40
|
+
send(setter, v) if respond_to?(setter)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def initialize_writer(writer = Currency::Exchange::Rate::Source::Historical::Writer.new)
|
46
|
+
@writer = writer
|
47
|
+
|
48
|
+
writer.time_quantitizer = :current
|
49
|
+
writer.required_currencies = required_currencies
|
50
|
+
writer.base_currencies = base_currencies
|
51
|
+
writer.preferred_currencies = writer.required_currencies
|
52
|
+
writer.reciprocal_rates = true
|
53
|
+
writer.all_rates = true
|
54
|
+
writer.identity_rates = false
|
55
|
+
|
56
|
+
options.each do | k, v |
|
57
|
+
setter = "#{k}="
|
58
|
+
writer.send(setter, v) if writer.respond_to?(setter)
|
59
|
+
end
|
60
|
+
|
61
|
+
writer
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def run
|
66
|
+
rate_sources.each do | src |
|
67
|
+
# Create a historical rate writer.
|
68
|
+
initialize_writer
|
69
|
+
|
70
|
+
# Handle creating a summary rates called 'summary'.
|
71
|
+
if src == summary_rate_src
|
72
|
+
summary_rates(src)
|
73
|
+
else
|
74
|
+
require "currency/exchange/rate/source/#{src}"
|
75
|
+
src_cls_name = src.gsub(/(^|_)([a-z])/) { | m | $2.upcase }
|
76
|
+
src_cls = Currency::Exchange::Rate::Source.const_get(src_cls_name)
|
77
|
+
src = src_cls.new(source_options)
|
78
|
+
|
79
|
+
writer.source = src
|
80
|
+
|
81
|
+
writer.write_rates
|
82
|
+
end
|
83
|
+
end
|
84
|
+
ensure
|
85
|
+
@writer = nil
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def summary_rates(src)
|
90
|
+
# A list of summary rates.
|
91
|
+
summary_rates = [ ]
|
92
|
+
|
93
|
+
# Get a list of all rate time ranges before today,
|
94
|
+
# that do not have a 'cnu' summary rate.
|
95
|
+
h_rate_cls = Currency::Exchange::Rate::Source::Historical::Rate
|
96
|
+
conn = h_rate_cls.connection
|
97
|
+
|
98
|
+
# Select only rates from yesterday or before back till 30 days.
|
99
|
+
date_1 = Time.now - (0 * 24 * 60 * 60)
|
100
|
+
date_0 = date_1 - (30 * 24 * 60 * 60)
|
101
|
+
|
102
|
+
date_0 = conn.quote(date_0)
|
103
|
+
date_1 = conn.quote(date_1)
|
104
|
+
|
105
|
+
query =
|
106
|
+
"SELECT
|
107
|
+
DISTINCT a.date_0, a.date_1
|
108
|
+
FROM
|
109
|
+
#{h_rate_cls.table_name} AS a
|
110
|
+
WHERE
|
111
|
+
a.source <> '#{src}'
|
112
|
+
AND a.date_1 >= #{date_0} AND a.date_1 < #{date_1}
|
113
|
+
AND (SELECT COUNT(b.id) FROM #{h_rate_cls.table_name} AS b
|
114
|
+
WHERE
|
115
|
+
b.c1 = a.c1 AND b.c2 = a.c2
|
116
|
+
AND b.date_0 = a.date_0 AND b.date_1 = a.date_1
|
117
|
+
AND b.source = '#{src}') = 0
|
118
|
+
ORDER BY
|
119
|
+
date_0"
|
120
|
+
STDERR.puts "query = \n#{query.split("\n").join(' ')}" if verbose
|
121
|
+
|
122
|
+
dates = conn.query(query)
|
123
|
+
|
124
|
+
dates.each do | date_range |
|
125
|
+
STDERR.puts "\n=============================================\n" if verbose
|
126
|
+
STDERR.puts "date_range = #{date_range.inspect}" if verbose
|
127
|
+
|
128
|
+
# Query for all rates that have the same date range.
|
129
|
+
q_rate = h_rate_cls.new(:date_0 => date_range[0], :date_1 => date_range[1])
|
130
|
+
available_rates = q_rate.find_matching_this(:all)
|
131
|
+
|
132
|
+
# Collect all the currency pairs and rates.
|
133
|
+
currency_pair = { }
|
134
|
+
available_rates.each do | h_rate |
|
135
|
+
rate = h_rate.to_rate
|
136
|
+
(currency_pair[ [ rate.c1, rate.c2 ] ] ||= [ ]) << [ h_rate, rate ]
|
137
|
+
# STDERR.puts "rate = #{rate} #{h_rate.date_0} #{h_rate.date_1}" if verbose
|
138
|
+
end
|
139
|
+
|
140
|
+
currency_pair.each_pair do | currency_pair, rates |
|
141
|
+
STDERR.puts "\n =============================================\n" if verbose
|
142
|
+
STDERR.puts " currency_pair = #{currency_pair}" if verbose
|
143
|
+
|
144
|
+
# Create a summary rate for the currency pair.
|
145
|
+
selected_rates = [ ]
|
146
|
+
|
147
|
+
rates.each do | h_rates |
|
148
|
+
h_rate, rate = *h_rates
|
149
|
+
|
150
|
+
# Sanity check!
|
151
|
+
next if h_rate.source == src
|
152
|
+
|
153
|
+
# Found perferred source?
|
154
|
+
if h_rate.source == preferred_summary_source
|
155
|
+
selected_rates = [ h_rates ]
|
156
|
+
break
|
157
|
+
end
|
158
|
+
|
159
|
+
selected_rates << h_rates
|
160
|
+
end
|
161
|
+
|
162
|
+
unless selected_rates.empty?
|
163
|
+
summary_rate = Currency::Exchange::Rate::Writable.new(currency_pair[0], currency_pair[1], 0.0)
|
164
|
+
selected_rates.each do | h_rates |
|
165
|
+
h_rate, rate = *h_rates
|
166
|
+
STDERR.puts " rate = #{rate.inspect}" if verbose
|
167
|
+
summary_rate.collect_rate(rate)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Save the rate.
|
171
|
+
summary_rate.rate = summary_rate.rate_avg
|
172
|
+
summary_rate.source = src
|
173
|
+
summary_rate.derived = 'summary(' + selected_rates.collect{|r| r[0].id}.sort.join(',') + ')'
|
174
|
+
STDERR.puts " summary_rate = #{summary_rate} #{summary_rate.rate_samples}" if verbose
|
175
|
+
|
176
|
+
summary_rates << summary_rate
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
writer.write_rates(summary_rates)
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
|
@@ -0,0 +1,220 @@
|
|
1
|
+
|
2
|
+
require 'currency/exchange/rate/source/historical'
|
3
|
+
|
4
|
+
# Responsible for writing historical rates from a rate source.
|
5
|
+
class Currency::Exchange::Rate::Source::Historical::Writer
|
6
|
+
|
7
|
+
# Error during handling of historical rates.
|
8
|
+
class Error < ::Currency::Exception::Base; end
|
9
|
+
|
10
|
+
# The source of rates.
|
11
|
+
attr_accessor :source
|
12
|
+
|
13
|
+
# If true, compute all Rates between rates.
|
14
|
+
# This can be used to aid complex join reports that may assume
|
15
|
+
# c1 as the from currency and c2 as the to currency.
|
16
|
+
attr_accessor :all_rates
|
17
|
+
|
18
|
+
# If true, store identity rates.
|
19
|
+
# This can be used to aid complex join reports.
|
20
|
+
attr_accessor :identity_rates
|
21
|
+
|
22
|
+
# If true, compute and store all reciprocal rates.
|
23
|
+
attr_accessor :reciprocal_rates
|
24
|
+
|
25
|
+
# If set, a set of preferred currencies.
|
26
|
+
attr_accessor :preferred_currencies
|
27
|
+
|
28
|
+
# If set, a list of required currencies.
|
29
|
+
attr_accessor :required_currencies
|
30
|
+
|
31
|
+
# If set, a list of required base currencies.
|
32
|
+
# base currencies must have rates as c1.
|
33
|
+
attr_accessor :base_currencies
|
34
|
+
|
35
|
+
# If set, use this time quantitizer to
|
36
|
+
# manipulate the Rate date_0 date_1 time ranges.
|
37
|
+
# If :default, use the TimeQuantitizer.default.
|
38
|
+
attr_accessor :time_quantitizer
|
39
|
+
|
40
|
+
|
41
|
+
def initialize(opt = { })
|
42
|
+
@all_rates = true
|
43
|
+
@identity_rates = false
|
44
|
+
@reciprocal_rates = true
|
45
|
+
@preferred_currencies = nil
|
46
|
+
@required_currencies = nil
|
47
|
+
@base_currencies = nil
|
48
|
+
@time_quantitizer = nil
|
49
|
+
opt.each_pair{| k, v | self.send("#{k}=", v) }
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
# Returns a list of selected rates from source.
|
54
|
+
def selected_rates
|
55
|
+
# Produce a list of all currencies.
|
56
|
+
currencies = source.currencies
|
57
|
+
|
58
|
+
# $stderr.puts "currencies = #{currencies.join(', ')}"
|
59
|
+
|
60
|
+
selected_rates = [ ]
|
61
|
+
|
62
|
+
# Get list of preferred_currencies.
|
63
|
+
if self.preferred_currencies
|
64
|
+
self.preferred_currencies = self.preferred_currencies.collect do | c |
|
65
|
+
::Currency::Currency.get(c)
|
66
|
+
end
|
67
|
+
currencies = currencies.select do | c |
|
68
|
+
self.preferred_currencies.include?(c)
|
69
|
+
end.uniq
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
# Check for required currencies.
|
74
|
+
if self.required_currencies
|
75
|
+
self.required_currencies = self.required_currencies.collect do | c |
|
76
|
+
::Currency::Currency.get(c)
|
77
|
+
end
|
78
|
+
|
79
|
+
self.required_currencies.each do | c |
|
80
|
+
unless currencies.include?(c)
|
81
|
+
raise ::Currency::Exception::MissingCurrency,
|
82
|
+
[
|
83
|
+
"Required currency #{c.inspect} not in #{currencies.inspect}",
|
84
|
+
:currency, c,
|
85
|
+
:required_currency, currencies,
|
86
|
+
]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
# $stderr.puts "currencies = #{currencies.inspect}"
|
93
|
+
|
94
|
+
deriver = ::Currency::Exchange::Rate::Deriver.new(:source => source)
|
95
|
+
|
96
|
+
# Produce Rates for all pairs of currencies.
|
97
|
+
if all_rates
|
98
|
+
currencies.each do | c1 |
|
99
|
+
currencies.each do | c2 |
|
100
|
+
next if c1 == c2 && ! identity_rates
|
101
|
+
rate = deriver.rate(c1, c2, nil)
|
102
|
+
selected_rates << rate unless selected_rates.include?(rate)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
elsif base_currencies
|
106
|
+
base_currencies.each do | c1 |
|
107
|
+
c1 = ::Currency::Currency.get(c1)
|
108
|
+
currencies.each do | c2 |
|
109
|
+
next if c1 == c2 && ! identity_rates
|
110
|
+
rate = deriver.rate(c1, c2, nil)
|
111
|
+
selected_rates << rate unless selected_rates.include?(rate)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
else
|
115
|
+
selected_rates = source.rates.select do | r |
|
116
|
+
next if r.c1 == r.c2 && ! identity_rates
|
117
|
+
currencies.include?(r.c1) && currencies.include?(r.c2)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
if identity_rates
|
122
|
+
currencies.each do | c1 |
|
123
|
+
c1 = ::Currency::Currency.get(c1)
|
124
|
+
c2 = c1
|
125
|
+
rate = deriver.rate(c1, c2, nil)
|
126
|
+
selected_rates << rate unless selected_rates.include?(rate)
|
127
|
+
end
|
128
|
+
else
|
129
|
+
selected_rates = selected_rates.select do | r |
|
130
|
+
r.c1 != r.c2
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
if reciprocal_rates
|
135
|
+
selected_rates.clone.each do | r |
|
136
|
+
c1 = r.c2
|
137
|
+
c2 = r.c1
|
138
|
+
rate = deriver.rate(c1, c2, nil)
|
139
|
+
selected_rates << rate unless selected_rates.include?(rate)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# $stderr.puts "selected_rates = #{selected_rates.inspect}\n [#{selected_rates.size}]"
|
144
|
+
|
145
|
+
selected_rates
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
# Returns an Array of Historical::Rate objects that were written.
|
150
|
+
# Avoids writing Rates that already have been written.
|
151
|
+
def write_rates(rates = selected_rates)
|
152
|
+
|
153
|
+
# Create Historical::Rate objects.
|
154
|
+
h_rate_class = ::Currency::Exchange::Rate::Source::Historical::Rate
|
155
|
+
|
156
|
+
# Most Rates from the same Source will probably have the same time,
|
157
|
+
# so cache the computed date_range.
|
158
|
+
date_range_cache = { }
|
159
|
+
rate_0 = nil
|
160
|
+
if time_quantitizer = self.time_quantitizer
|
161
|
+
time_quantitizer = ::Currency::Exchange::TimeQuantitizer.current if time_quantitizer == :current
|
162
|
+
end
|
163
|
+
|
164
|
+
h_rates = rates.collect do | r |
|
165
|
+
rr = h_rate_class.new.from_rate(r)
|
166
|
+
rr.dates_to_localtime!
|
167
|
+
|
168
|
+
if rr.date && time_quantitizer
|
169
|
+
date_range = date_range_cache[rr.date] ||= time_quantitizer.quantitize_time_range(rr.date)
|
170
|
+
rr.date_0 = date_range.begin
|
171
|
+
rr.date_1 = date_range.end
|
172
|
+
end
|
173
|
+
|
174
|
+
rate_0 ||= rr if rr.date_0 && rr.date_1
|
175
|
+
|
176
|
+
rr
|
177
|
+
end
|
178
|
+
|
179
|
+
# Fix any dateless Rates.
|
180
|
+
if rate_0
|
181
|
+
h_rates.each do | rr |
|
182
|
+
rr.date_0 = rate_0.date_0 unless rr.date_0
|
183
|
+
rr.date_1 = rate_0.date_1 unless rr.date_1
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Save them all or none.
|
188
|
+
stored_h_rates = [ ]
|
189
|
+
h_rate_class.transaction do
|
190
|
+
h_rates.each do | rr |
|
191
|
+
# Skip identity rates.
|
192
|
+
next if rr.c1 == rr.c2 && ! identity_rates
|
193
|
+
|
194
|
+
# Skip if already exists.
|
195
|
+
existing_rate = rr.find_matching_this(:first)
|
196
|
+
if existing_rate
|
197
|
+
stored_h_rates << existing_rate # Already existed.
|
198
|
+
else
|
199
|
+
begin
|
200
|
+
rr.save!
|
201
|
+
rescue Object => err
|
202
|
+
raise ::Currency::Exception::Generic,
|
203
|
+
[
|
204
|
+
"During save of #{rr.inspect}",
|
205
|
+
:error, err,
|
206
|
+
]
|
207
|
+
end
|
208
|
+
stored_h_rates << rr # Written.
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Return written Historical::Rates.
|
214
|
+
stored_h_rates
|
215
|
+
end
|
216
|
+
|
217
|
+
end # class
|
218
|
+
|
219
|
+
|
220
|
+
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
|
2
|
+
# See LICENSE.txt for details.
|
3
|
+
|
4
|
+
require 'currency/exchange/rate/source/base'
|
5
|
+
|
6
|
+
# Gets historical rates from database using Active::Record.
|
7
|
+
# Rates are retrieved using Currency::Exchange::Rate::Source::Historical::Rate as
|
8
|
+
# a database record proxy.
|
9
|
+
#
|
10
|
+
# See Currency::Exchange::Rate::Source::Historical::Writer for a rate archiver.
|
11
|
+
#
|
12
|
+
class Currency::Exchange::Rate::Source::Historical < Currency::Exchange::Rate::Source::Base
|
13
|
+
|
14
|
+
# Select specific rate source.
|
15
|
+
# Defaults to nil
|
16
|
+
attr_accessor :source
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@source = nil # any
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def source_key
|
25
|
+
@source ? @source.join(',') : ''
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
# This Exchange's name is the same as its #uri.
|
30
|
+
def name
|
31
|
+
"historical #{source_key}"
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def initialize(*opt)
|
36
|
+
super
|
37
|
+
@rates_cache = { }
|
38
|
+
@raw_rates_cache = { }
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def clear_rates
|
43
|
+
@rates_cache.clear
|
44
|
+
@raw_rates_cache.clear
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
# Returns a Rate.
|
50
|
+
def get_rate(c1, c2, time)
|
51
|
+
# rate =
|
52
|
+
get_rates(time).select{ | r | r.c1 == c1 && r.c2 == c2 }[0]
|
53
|
+
# $stderr.puts "#{self}.get_rate(#{c1}, #{c2}, #{time.inspect}) => #{rate.inspect}"
|
54
|
+
# rate
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
# Return a list of base Rates.
|
59
|
+
def get_rates(time = nil)
|
60
|
+
@rates_cache["#{source_key}:#{time}"] ||=
|
61
|
+
get_raw_rates(time).collect do | rr |
|
62
|
+
rr.to_rate
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
# Return a list of raw rates.
|
68
|
+
def get_raw_rates(time = nil)
|
69
|
+
@raw_rates_cache["#{source_key}:#{time}"] ||=
|
70
|
+
::Currency::Exchange::Rate::Source::Historical::Rate.new(:c1 => nil, :c2 => nil, :date => time, :source => source).
|
71
|
+
find_matching_this(:all)
|
72
|
+
end
|
73
|
+
|
74
|
+
end # class
|
75
|
+
|
76
|
+
|
77
|
+
require 'currency/exchange/rate/source/historical/rate'
|
78
|
+
|
79
|
+
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
|
2
|
+
# See LICENSE.txt for details.
|
3
|
+
|
4
|
+
require 'currency/exchange/rate/source/base'
|
5
|
+
|
6
|
+
require 'net/http'
|
7
|
+
require 'open-uri'
|
8
|
+
require 'rexml/document'
|
9
|
+
|
10
|
+
|
11
|
+
# Connects to http://www.newyorkfed.org/markets/fxrates/FXtoXML.cfm
|
12
|
+
# ?FEXdate=2007%2D02%2D14%2000%3A00%3A00%2E0&FEXtime=1200 and parses XML.
|
13
|
+
#
|
14
|
+
# No rates are available on Saturday and Sunday.
|
15
|
+
#
|
16
|
+
class Currency::Exchange::Rate::Source::NewYorkFed < ::Currency::Exchange::Rate::Source::Provider
|
17
|
+
# Defines the pivot currency for http://xe.com/.
|
18
|
+
PIVOT_CURRENCY = :USD
|
19
|
+
|
20
|
+
def initialize(*opt)
|
21
|
+
self.uri = 'http://www.newyorkfed.org/markets/fxrates/FXtoXML.cfm?FEXdate=#{date_YYYY}%2D#{date_MM}%2D#{date_DD}%2000%3A00%3A00%2E0&FEXTIME=1200'
|
22
|
+
@raw_rates = nil
|
23
|
+
super(*opt)
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
# Returns 'newyorkfed.org'.
|
28
|
+
def name
|
29
|
+
'newyorkfed.org'
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
# New York Fed rates are not available on Saturday and Sunday.
|
34
|
+
def available?(time = nil)
|
35
|
+
time ||= Time.now
|
36
|
+
! [0, 6].include?(time.wday) ? true : false
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def clear_rates
|
41
|
+
@raw_rates = nil
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def raw_rates
|
47
|
+
rates
|
48
|
+
@raw_rates
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
# The fed swaps rates on some currency pairs!
|
53
|
+
# See http://www.newyorkfed.org/markets/fxrates/noon.cfm (LISTS AUD!)
|
54
|
+
# http://www.newyorkfed.org/xml/fx.html (DOES NOT LIST AUD!)
|
55
|
+
@@swap_units = {
|
56
|
+
:AUD => true,
|
57
|
+
:EUR => true,
|
58
|
+
:NZD => true,
|
59
|
+
:GBP => true,
|
60
|
+
}
|
61
|
+
|
62
|
+
|
63
|
+
# Parses XML for rates.
|
64
|
+
def parse_rates(data = nil)
|
65
|
+
data = get_page_content unless data
|
66
|
+
|
67
|
+
rates = [ ]
|
68
|
+
|
69
|
+
@raw_rates = { }
|
70
|
+
|
71
|
+
$stderr.puts "#{self}: parse_rates: data =\n#{data}" if @verbose
|
72
|
+
|
73
|
+
doc = REXML::Document.new(data).root
|
74
|
+
x_series = doc.elements.to_a('//frbny:Series')
|
75
|
+
raise ParserError, "no UNIT attribute" unless x_series
|
76
|
+
x_series.each do | series |
|
77
|
+
c1 = series.attributes['UNIT'] # WHAT TO DO WITH @UNIT_MULT?
|
78
|
+
raise ParserError, "no UNIT attribute" unless c1
|
79
|
+
c1 = c1.upcase.intern
|
80
|
+
|
81
|
+
c2 = series.elements.to_a('frbny:Key/frbny:CURR')[0].text
|
82
|
+
raise ParserError, "no frbny:CURR element" unless c2
|
83
|
+
c2 = c2.upcase.intern
|
84
|
+
|
85
|
+
rate = series.elements.to_a('frbny:Obs/frbny:OBS_VALUE')[0]
|
86
|
+
raise ParserError, 'no frbny:OBS_VALUE' unless rate
|
87
|
+
rate = rate.text.to_f
|
88
|
+
|
89
|
+
date = series.elements.to_a('frbny:Obs/frbny:TIME_PERIOD')[0]
|
90
|
+
raise ParserError, 'no frbny:TIME_PERIOD' unless date
|
91
|
+
date = date.text
|
92
|
+
date = Time.parse("#{date} 12:00:00 -05:00") # USA NY => EST
|
93
|
+
|
94
|
+
# Handle arbitrary rate reciprocals!
|
95
|
+
if @@swap_units[c1] || @@swap_units[c2]
|
96
|
+
c1, c2 = c2, c1
|
97
|
+
end
|
98
|
+
|
99
|
+
rates << new_rate(c1, c2, rate, date)
|
100
|
+
|
101
|
+
(@raw_rates[c1] ||= { })[c2] ||= rate
|
102
|
+
(@raw_rates[c2] ||= { })[c1] ||= 1.0 / rate
|
103
|
+
end
|
104
|
+
|
105
|
+
# $stderr.puts "rates = #{rates.inspect}"
|
106
|
+
raise ::Currency::Exception::UnavailableRates,
|
107
|
+
[
|
108
|
+
"No rates found in #{get_uri.inspect}",
|
109
|
+
:uri, get_uri,
|
110
|
+
] if rates.empty?
|
111
|
+
|
112
|
+
rates
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
# Return a list of known base rates.
|
117
|
+
def load_rates(time = nil)
|
118
|
+
# $stderr.puts "#{self}: load_rates(#{time})" if @verbose
|
119
|
+
self.date = time
|
120
|
+
parse_rates
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
end # class
|
125
|
+
|
126
|
+
|
127
|
+
|