File Coverage

blib/lib/Finance/Quote/MLC.pm
Criterion Covered Total %
statement 14 77 18.1
branch 2 14 14.2
condition n/a
subroutine 4 10 40.0
pod 0 7 0.0
total 20 108 18.5


line stmt bran cond sub pod time code
1             # Copyright 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2014, 2015, 2016 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,
11             # but WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13             # Public License 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             package Finance::Quote::MLC;
19 1     1   169267 use strict;
  1         5  
  1         32  
20              
21 1     1   5 use vars qw($VERSION);
  1         2  
  1         98  
22             $VERSION = 15;
23              
24             # uncomment this to run the ### lines
25             #use Smart::Comments;
26              
27             sub methods {
28 0     0 0 0 return (mlc => \&mlc_quotes);
29             }
30             sub labels {
31 0     0 0 0 return (mlc => [ qw(date isodate name currency
32             last close
33             method source success errormsg
34              
35             copyright_url
36             ) ]);
37             }
38              
39 1         891 use constant COPYRIGHT_URL =>
40 1     1   6 'http://www.mlc.com.au/mlc/im_considering_mlc/personal/footer_tools/advice_warning_and_disclaimer';
  1         2  
41              
42              
43             sub mlc_quotes {
44 0     0 0 0 my ($fq, @symbol_list) = @_;
45 0         0 my $ua = $fq->user_agent;
46 0         0 my %quotes;
47              
48 0         0 foreach my $symbol (@symbol_list) {
49 0         0 my $url = make_url ($symbol);
50              
51 0         0 my $req = HTTP::Request->new ('GET', $url);
52 0         0 $ua->prepare_request ($req);
53 0         0 $req->accept_decodable; # we know decoded_content() below
54 0         0 $req->user_agent (__PACKAGE__."/$VERSION " . $req->user_agent);
55             ### Request: $req->as_string
56              
57 0         0 my $resp = $ua->request ($req);
58 0         0 resp_to_quotes ($fq, $resp, \%quotes, $symbol);
59             }
60 0 0       0 return wantarray() ? %quotes : \%quotes;
61             }
62              
63             # Sample url:
64             # https://www.mlc.com.au/masterkeyWeb/execute/UnitPricesWQO?openAgent&reporttype=HistoricalDateRange&product=MasterKey%20Allocated%20Pension%20%28Five%20Star%29&fund=MLC%20Horizon%201%20-%20Bond%20Portfolio&begindate=19/05/2010&enddate=28/05/2010&
65             #
66             # The end date is today Sydney time. Sydney timezone is +10, and +11 during
67             # daylight savings; but instead of figuring when daylight savings is in
68             # force just use +11 all the time.
69             #
70             # Obviously today's price won't be available just after midnight, so a time
71             # offset giving today after 9am or 4pm or some such could make more sense.
72             # Actually as of Feb 2009 price for a given day aren't available until the
73             # afternoon of the next weekday, so the end date used here is going to be
74             # anything from 1 to 4 days too much. It does no harm to ask beyond what's
75             # available.
76             #
77             # The start date requested takes account of the slackness in the end date
78             # and the possibility of public holidays. The worst case is on Tuesday
79             # morning. The available price is still only the previous Friday, and if
80             # Thu/Fri are Christmas day and boxing day holidays then only Wednesday is
81             # available, and then want also the preceding day to get the prev price,
82             # which means the Tuesday, which is -7 days. Go back 2 further days just in
83             # case too, for a total -9!
84             #
85             sub make_url {
86 0     0 0 0 my ($symbol) = @_;
87              
88 0         0 my $t = time();
89 0         0 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
90             = gmtime($t + 11 * 3600);
91 0         0 my $hi_day = $mday;
92 0         0 my $hi_month = $mon + 1;
93 0         0 my $hi_year = $year + 1900;
94              
95 0         0 ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
96             = gmtime($t + 10 * 3600 - 9 * 86400);
97 0         0 my $lo_day = $mday;
98 0         0 my $lo_month = $mon + 1;
99 0         0 my $lo_year = $year + 1900;
100              
101 0         0 my ($fund, $product) = symbol_to_fund_and_product ($symbol);
102              
103 0         0 require URI::Escape;
104 0         0 return sprintf ('https://www.mlc.com.au/masterkeyWeb/execute/UnitPricesWQO?openAgent&reporttype=HistoricalDateRange&product=%s&fund=%s&begindate=%02d/%02d/%04d&enddate=%02d/%02d/%04d&',
105             URI::Escape::uri_escape ($product),
106             URI::Escape::uri_escape ($fund),
107             $lo_day, $lo_month, $lo_year,
108             $hi_day, $hi_month, $hi_year);
109             }
110              
111             sub symbol_to_fund_and_product {
112 2     2 0 3186 my ($symbol) = @_;
113 2         5 my $pos = index ($symbol, ',');
114 2 100       8 if ($pos == -1) {
115 1         4 return ($symbol, '');
116             } else {
117 1         6 return (substr ($symbol, 0, $pos),
118             substr ($symbol, $pos+1));
119             }
120             }
121              
122             # store to hashref $quotes for $symbol based on HTTP::Response in $resp
123             #
124             # Initial line like:
125             #
126             # historicalProduct1funds[0]="All Funds"
127             #
128             # Then price lines like:
129             #
130             # historicalProduct1funds[1]="MLC Property Securities Fund,MasterKey Superannuation (Gold Star),29 March 2007,64.71567,0.00000";
131             #
132             sub resp_to_quotes {
133 0     0 0   my ($fq, $resp, $quotes, $symbol) = @_;
134              
135 0           $quotes->{$symbol,'method'} = 'mlc';
136 0           $quotes->{$symbol,'currency'} = 'AUD';
137 0           $quotes->{$symbol,'source'} = __PACKAGE__;
138 0           $quotes->{$symbol,'success'} = 1;
139              
140 0           my $content = $resp->decoded_content (raise_error => 1, charset => 'none');
141 0 0         if (! $resp->is_success) {
142 0           $quotes->{$symbol,'success'} = 0;
143 0           $quotes->{$symbol,'errormsg'} = $resp->status_line;
144 0           return;
145             }
146              
147 0 0         if ($content =~ /No unit prices available/i) {
148 0           $quotes->{$symbol,'success'} = 0;
149 0           $quotes->{$symbol,'errormsg'} = 'No unit prices available';
150 0           return;
151             }
152              
153 0           my @data; # elements are arrayrefs [ $isodate, $price ]
154              
155 0           while ($content =~ /^historicalProduct1funds.*=\"(.*)\"/mg) {
156 0           my ($got_fund, $got_product, $date, $price) = split /,/, $1;
157              
158             # skip historicalProduct1funds[0]="All Funds" bit
159 0 0         if (! $got_product) { next; }
  0            
160              
161 0           $date = dmy_to_iso ($fq, $date);
162 0           push @data, [ $date, $price ];
163             ### $date
164             ### $price
165             }
166 0 0         if (! @data) {
167 0           $quotes->{$symbol,'success'} = 0;
168 0           $quotes->{$symbol,'errormsg'}
169             = 'Oops, prices not matched in downloaded data';
170 0           return;
171             }
172              
173             # the lines come with newest date first, but don't assume that;
174             # sort to oldest date in $data[0], newest in endmost elem
175 0           @data = sort {$a->[0] cmp $b->[0]} @data;
  0            
176              
177 0           $fq->store_date($quotes, $symbol, {isodate => $data[-1]->[0]});
178 0           $quotes->{$symbol,'last'} = $data[-1]->[1];
179 0 0         if (@data > 1) {
180 0           $quotes->{$symbol,'close'} = $data[-2]->[1];
181             }
182 0           $quotes->{$symbol,'copyright_url'} = COPYRIGHT_URL;
183             }
184              
185             sub dmy_to_iso {
186 0     0 0   my ($fq, $dmy) = @_;
187 0           my %dummy_quotes;
188 0           $fq->store_date (\%dummy_quotes, '', {eurodate => $dmy});
189 0           return $dummy_quotes{'','isodate'};
190             }
191              
192             1;
193             __END__
194              
195             =head1 NAME
196              
197             Finance::Quote::MLC - MLC fund prices
198              
199             =head1 SYNOPSIS
200              
201             use Finance::Quote;
202             my $fq = Finance::Quote->new ('MLC');
203             my $fund = 'MLC MasterKey Horizon 1 - Bond Portfolio';
204             my $product = 'MasterKey Allocated Pension (Five Star)';
205             my %quotes = $fq->fetch('mlc', "$fund,$product");
206              
207             =head1 DESCRIPTION
208              
209             This module downloads MLC fund quotes from
210              
211             =over 4
212              
213             L<http://www.mlc.com.au>
214              
215             =back
216              
217             under
218              
219             =over 4
220              
221             https://www.mlc.com.au/masterkeyWeb/execute/FramesetUnitPrices
222              
223             =back
224              
225             As of Sept 2011 the web site terms of use,
226              
227             =over 4
228              
229             L<http://www.mlc.com.au/mlc/im_considering_mlc/personal/footer_tools/advice_warning_and_disclaimer>
230              
231             =back
232              
233             are for general information only, and only provided for residents of
234             Australia. It's your responsibility to ensure your use of this module
235             complies with current and future terms.
236              
237             =head2 Symbols
238              
239             The symbols used are the fund name and product name with a comma, for
240             example
241              
242             =for Finance_Quote_Grab symbols
243              
244             MLC Horizon 1 - Bond Portfolio,MasterKey Allocated Pension (Five Star)
245              
246             This is a lot to type, but you can usually cut and paste it from the web
247             pages. The page source in the link above has them in this form.
248              
249             The fund part is the actual investment, but there isn't a single price quote
250             for it, rather the price varies with the product due to different fees
251             subtracted.
252              
253             =head2 Fields
254              
255             The following standard C<Finance::Quote> fields are available
256              
257             =for Finance_Quote_Grab fields flowed standard
258              
259             date isodate name currency
260             last close
261             method source success errormsg
262              
263             Plus the following extras
264              
265             =for Finance_Quote_Grab fields flowed extra
266              
267             copyright_url
268              
269             As of June 2009, prices are published some time in the afternoon of the
270             following business day (Friday's prices some time Monday afternoon). So the
271             date field is always yesterday or the day before yesterday. The currency is
272             always "AUD" Australian dollars.
273              
274             =head1 SEE ALSO
275              
276             L<Finance::Quote>, L<LWP>
277              
278             MLC web site L<http://www.mlc.com.au>
279              
280             =head1 HOME PAGE
281              
282             L<http://user42.tuxfamily.org/finance-quote-grab/index.html>
283              
284             =head1 LICENCE
285              
286             Copyright 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2014, 2015, 2016 Kevin Ryde
287              
288             Finance-Quote-Grab is free software; you can redistribute it and/or modify
289             it under the terms of the GNU General Public License as published by the
290             Free Software Foundation; either version 3, or (at your option) any later
291             version.
292              
293             Finance-Quote-Grab is distributed in the hope that it will be useful, but
294             WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
295             or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
296             more details.
297              
298             You should have received a copy of the GNU General Public License along with
299             Finance-Quote-Grab; see the file F<COPYING>. If not, see
300             L<http://www.gnu.org/licenses/>.
301              
302             =cut