File Coverage

blib/lib/Finance/Quote/RBA.pm
Criterion Covered Total %
statement 80 101 79.2
branch 15 28 53.5
condition n/a
subroutine 11 12 91.6
pod 0 3 0.0
total 106 144 73.6


line stmt bran cond sub pod time code
1             # Copyright 2007, 2008, 2009, 2010, 2011, 2014, 2015, 2016, 2019 Kevin Ryde
2              
3             # This file is part of Finance-Quote-Grab.
4             #
5             # Finance-Quote-Grab is free software; you can redistribute it and/or
6             # modify it under the terms of the GNU General Public License as published
7             # by the Free Software Foundation; either version 3, or (at your option) any
8             # later version.
9             #
10             # Finance-Quote-Grab is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License
16             # along with Finance-Quote-Grab. If not, see <http://www.gnu.org/licenses/>.
17              
18              
19             package Finance::Quote::RBA;
20 1     1   659 use strict;
  1         2  
  1         29  
21 1     1   6 use Scalar::Util;
  1         1  
  1         44  
22 1     1   594 use Finance::Quote 1.15; # for isoTime()
  1         65173  
  1         64  
23              
24 1     1   10 use vars qw($VERSION %name_to_symbol);
  1         2  
  1         102  
25             $VERSION = 15;
26              
27             # uncomment this to run the ### lines
28             #use Smart::Comments;
29              
30              
31             sub methods {
32 1     1 0 2224 return (rba => \&rba_quotes);
33             }
34             sub labels {
35 2     2 0 1369 return (rba => [ qw(date isodate name currency
36             last close
37             method source success errormsg
38              
39             time copyright_url) ]);
40             }
41              
42 1         59 use constant EXCHANGE_RATES_URL =>
43 1     1   8 'https://www.rba.gov.au/statistics/frequency/exchange-rates.html';
  1         2  
44              
45 1         1011 use constant COPYRIGHT_URL =>
46 1     1   14 'https://www.rba.gov.au/copyright/index.html';
  1         5  
47              
48             sub rba_quotes {
49 0     0 0 0 my ($fq, @symbol_list) = @_;
50 0 0       0 if (! @symbol_list) { return; }
  0         0  
51              
52 0         0 my $ua = $fq->user_agent;
53 0         0 require HTTP::Request;
54 0         0 my $req = HTTP::Request->new ('GET', EXCHANGE_RATES_URL);
55 0         0 $ua->prepare_request ($req);
56 0         0 $req->accept_decodable; # using decoded_content() below
57 0         0 $req->user_agent (__PACKAGE__."/$VERSION " . $req->user_agent);
58              
59 0         0 my $resp = $ua->request ($req);
60 0         0 my %quotes;
61 0         0 _parse ($fq, $resp, \%quotes, \@symbol_list);
62 0 0       0 return wantarray() ? %quotes : \%quotes;
63             }
64              
65             sub _parse {
66 1     1   6117 my ($fq, $resp, $quotes, $symbol_list) = @_;
67              
68 1         3 foreach my $symbol (@$symbol_list) {
69 3         10 $quotes->{$symbol,'method'} = 'rba';
70 3         7 $quotes->{$symbol,'source'} = __PACKAGE__;
71 3         10 $quotes->{$symbol,'success'} = 0;
72             }
73              
74 1 50       5 if (! $resp->is_success) {
75 0         0 _errormsg ($quotes, $symbol_list, $resp->status_line);
76 0         0 return;
77             }
78 1         19 my $content = $resp->decoded_content (raise_error => 1, charset => 'none');
79              
80             # mung <tr id="USD"> to add <td>USD</td> so it appears in the TableExtract
81 1         209 $content =~ s{<tr>}{<tr><td></td>}ig;
82 1         21 $content =~ s{(<tr +id="([^"]*)">)}{$1<td>$2</td>}ig;
83              
84 1         701 require HTML::TableExtract;
85 1         15292 my $te = HTML::TableExtract->new
86             (
87             # now in a <caption> instead of a heading
88             # headers => [qr/Units of Foreign Currencies per/i],
89             slice_columns => 0);
90 1         231 $te->parse($content);
91 1         3725 my $ts = $te->first_table_found;
92 1 50       10 if (! $ts) {
93 0         0 _errormsg ($quotes, $symbol_list, 'rates table not found in HTML');
94 0         0 return;
95             }
96              
97             # column of letters "P" "U" "B" "L" "I" "C" "H" "O" "L" "I" "D" "A" "Y"
98             # on a bank holiday -- skip those
99 1         2 my ($col, $prevcol);
100 1         4 for (my $i = $ts->columns - 1; $i >= 2; $i--) {
101 1 50       340 if (Scalar::Util::looks_like_number ($ts->cell (1, $i))) {
102 1         86 $col = $i;
103 1         2 last;
104             }
105             }
106 1         3 for (my $i = $col - 1; $i >= 2; $i--) {
107 1 50       3 if (Scalar::Util::looks_like_number ($ts->cell (1, $i))) {
108 1         86 $prevcol = $i;
109 1         2 last;
110             }
111             }
112             ### $col
113             ### $prevcol
114 1 50       4 if (! defined $col) {
115 0         0 _errormsg ($quotes, $symbol_list, 'No numeric columns found');
116 0         0 return;
117             }
118              
119 1         3 my $date = $ts->cell (0, $col);
120              
121 1         79 my %want_symbol;
122 1         8 @want_symbol{@$symbol_list} = (); # hash slice
123 1         1 my %seen_symbol;
124              
125 1         2 foreach my $row (@{$ts->rows()}) {
  1         2  
126             ### $row
127              
128 4         238 my $symbol = $row->[0];
129 4 100       8 $symbol or next; # dates row, or no id="" in <tr>
130 3         7 $symbol =~ s/_.*//; # _4pm on TWI
131 3         7 $symbol = "AUD$symbol";
132 3 50       8 if (! exists $want_symbol{$symbol}) { next; } # unwanted row
  0         0  
133              
134 3         4 my $name = $row->[1];
135 3 50       7 defined $name or next; # dates row
136 3         6 ($name, my $time) = _name_extract_time ($fq, $name);
137              
138 3         42 my $rate = $row->[$col];
139 3         6 my $prev = $row->[$prevcol];
140              
141 3         15 $fq->store_date($quotes, $symbol, {eurodate => $date});
142 3 50       245 if (defined $time) {
143 3         9 $quotes->{$symbol,'time'} = $time;
144             }
145 3         8 $quotes->{$symbol,'name'} = $name;
146 3         7 $quotes->{$symbol,'last'} = $rate;
147 3         9 $quotes->{$symbol,'close'} = $prev;
148 3 50       11 if ($symbol ne 'TWI') {
149 3         10 $quotes->{$symbol,'currency'} = $symbol;
150             }
151 3         8 $quotes->{$symbol,'copyright_url'} = COPYRIGHT_URL;
152 3         6 $quotes->{$symbol,'success'} = 1;
153              
154             # don't delete AUDTWI from %want_symbol since want to get the last row
155             # which is 16:00 instead of the 9:00 one
156 3         8 $seen_symbol{$symbol} = 1;
157             }
158              
159              
160 1         7 delete @want_symbol{keys %seen_symbol}; # hash slice
161             # any not seen
162 1         7 _errormsg ($quotes, [keys %want_symbol], 'No such symbol');
163             }
164              
165             sub _errormsg {
166 1     1   2 my ($quotes, $symbol_list, $errormsg) = @_;
167 1         44 foreach my $symbol (@$symbol_list) {
168 0         0 $quotes->{$symbol,'errormsg'} = $errormsg;
169             }
170             }
171              
172             # pick out name and time from forms like
173             # Trade-weighted index (9am)
174             # Trade-weighted index (Noon)
175             # Trade-weighted index (4pm)
176             # or without a time is 4pm, like
177             # UK pound sterling
178             #
179             sub _name_extract_time {
180 11     11   8107 my ($fq, $name) = @_;
181              
182 11 100       89 if ($name =~ m/(.*?) +\(Noon\)$/i) { # Noon
    100          
183 2         10 return ($1, '12:00');
184             } elsif ($name =~ m/(.*?) +\(([0-9]+)([ap]m)\)$/i) { # 9am, 4pm
185 7         40 return ($1, $fq->isoTime("$2:00$3"));
186             } else {
187 2         6 return ($name, '16:00'); # default 4pm
188             }
189             }
190              
191             1;
192             __END__
193              
194             =head1 NAME
195              
196             Finance::Quote::RBA - download Reserve Bank of Australia currency rates
197              
198             =for test_synopsis my ($q, %rates);
199              
200             =for Finance_Quote_Grab symbols AUDGBP AUDUSD
201              
202             =head1 SYNOPSIS
203              
204             use Finance::Quote;
205             $q = Finance::Quote->new ('RBA');
206             %rates = $q->fetch ('rba', 'AUDGBP', 'AUDUSD');
207              
208             =head1 DESCRIPTION
209              
210             This module downloads currency rates for the Australian dollar from the
211             Reserve Bank of Australia,
212              
213             =over 4
214              
215             L<https://www.rba.gov.au/>
216              
217             =back
218              
219             using the page
220              
221             =over 4
222              
223             L<https://www.rba.gov.au/statistics/frequency/exchange-rates.html>
224              
225             =back
226              
227             As of June 2009 the web site terms of use,
228              
229             =over 4
230              
231             L<https://www.rba.gov.au/copyright/index.html>
232              
233             =back
234              
235             are for personal non-commercial use with proper attribution. (It will be
236             noted material is to be used in ``unaltered form'', but the bank advises
237             import into a charting program is permitted.) It's your responsibility to
238             ensure your use of this module complies with current and future terms.
239              
240             =head2 Symbols
241              
242             The symbols used are "AUDXXX" where XXX is the other currency. Each is the
243             value of 1 Australian dollar in the other currency. As of February 2019 the
244             following symbols are available
245              
246             AUDUSD US dollar
247             AUDCNY Chinese renminbi
248             AUDJPY Japanese yen
249             AUDEUR Euro
250             AUDKRW South Korean won
251             AUDSGD Singapore dollar
252             AUDNZD New Zealand dollar
253             AUDGBP British pound sterling
254             AUDMYR Malaysian ringgit
255             AUDTHB Thai baht
256             AUDIDR Indonesian rupiah
257             AUDINR Indian rupee
258             AUDTWD Taiwanese dollar
259             AUDVND Vietnamese dong
260             AUDHKD Hong Kong dollar
261             AUDPGK Papua New Guinea kina
262             AUDCHF Swiss franc
263             AUDAED United Arab Emirates dirham
264             AUDCAD Canadian dollar
265              
266             Plus the RBA's Trade Weighted Index for the Australian dollar, and the
267             Australian dollar valued in the IMF's Special Drawing Right basket of
268             currencies.
269              
270             AUDTWI Trade Weighted Index
271             AUDSDR Special Drawing Right
272              
273             The "AUD" in each is a bit redundant, but it's in the style of Yahoo Finance
274             currency crosses and makes it clear which way around the rate is expressed.
275              
276             The currency symbols are "id" attributes in the HTML page source so a new
277             currency should be accessible by that code.
278              
279             =head2 Fields
280              
281             The following standard C<Finance::Quote> fields are returned
282              
283             =for Finance_Quote_Grab fields flowed standard
284              
285             date isodate name currency
286             last close
287             method source success errormsg
288              
289             Plus the following extras
290              
291             =for Finance_Quote_Grab fields table extra
292              
293             time ISO string "HH:MM"
294             copyright_url
295              
296             C<time> is always "16:00", ie. 4pm, currently. The bank publishes TWI
297             (trade weighted index) values for 10am and Noon too, but not until the end
298             of the day when the 4pm value is the latest.
299              
300             C<currency> is the other currency. Prices are the value of an Australian
301             dollar in the respective currency. For example in "AUDUSD" the C<currency>
302             is "USD". C<currency> is omitted for "AUDTWI" since "TWI" is not a defined
303             international currency code. But it is returned for "AUDSDR", the IMF
304             special drawing right basket.
305              
306             =head1 OTHER NOTES
307              
308             Currency rates are downloaded just as "prices", there's no tie-in to the
309             C<Finance::Quote> currency conversion feature.
310              
311             The exchange rates page above includes an RSS feed in "cb" central bank
312             format, but it doesn't give previous day's rates for the Finance-Quote
313             "close" field.
314              
315             =head1 SEE ALSO
316              
317             L<Finance::Quote>, L<LWP>
318              
319             RBA website L<https://www.rba.gov.au/>
320              
321             =head1 HOME PAGE
322              
323             L<http://user42.tuxfamily.org/finance-quote-grab/index.html>
324              
325             =head1 LICENCE
326              
327             Copyright 2007, 2008, 2009, 2010, 2011, 2014, 2015, 2016, 2019 Kevin Ryde
328              
329             Finance-Quote-Grab is free software; you can redistribute it and/or modify
330             it under the terms of the GNU General Public License as published by the
331             Free Software Foundation; either version 3, or (at your option) any later
332             version.
333              
334             Finance-Quote-Grab is distributed in the hope that it will be useful, but
335             WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
336             or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
337             more details.
338              
339             You should have received a copy of the GNU General Public License along with
340             Finance-Quote-Grab; see the file F<COPYING>. If not, see
341             L<http://www.gnu.org/licenses/>.
342              
343             =cut