# Copyright 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2014, 2015, 2016 Kevin Ryde
# This file is part of Finance-Quote-Grab.
#
# Finance-Quote-Grab is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation; either version 3, or (at your option) any
# later version.
#
# Finance-Quote-Grab is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Finance-Quote-Grab. If not, see .
package Finance::Quote::MLC;
use strict;
use vars qw($VERSION);
$VERSION = 15;
# uncomment this to run the ### lines
#use Smart::Comments;
sub methods {
return (mlc => \&mlc_quotes);
}
sub labels {
return (mlc => [ qw(date isodate name currency
last close
method source success errormsg
copyright_url
) ]);
}
use constant COPYRIGHT_URL =>
'http://www.mlc.com.au/mlc/im_considering_mlc/personal/footer_tools/advice_warning_and_disclaimer';
sub mlc_quotes {
my ($fq, @symbol_list) = @_;
my $ua = $fq->user_agent;
my %quotes;
foreach my $symbol (@symbol_list) {
my $url = make_url ($symbol);
my $req = HTTP::Request->new ('GET', $url);
$ua->prepare_request ($req);
$req->accept_decodable; # we know decoded_content() below
$req->user_agent (__PACKAGE__."/$VERSION " . $req->user_agent);
### Request: $req->as_string
my $resp = $ua->request ($req);
resp_to_quotes ($fq, $resp, \%quotes, $symbol);
}
return wantarray() ? %quotes : \%quotes;
}
# Sample url:
# 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&
#
# The end date is today Sydney time. Sydney timezone is +10, and +11 during
# daylight savings; but instead of figuring when daylight savings is in
# force just use +11 all the time.
#
# Obviously today's price won't be available just after midnight, so a time
# offset giving today after 9am or 4pm or some such could make more sense.
# Actually as of Feb 2009 price for a given day aren't available until the
# afternoon of the next weekday, so the end date used here is going to be
# anything from 1 to 4 days too much. It does no harm to ask beyond what's
# available.
#
# The start date requested takes account of the slackness in the end date
# and the possibility of public holidays. The worst case is on Tuesday
# morning. The available price is still only the previous Friday, and if
# Thu/Fri are Christmas day and boxing day holidays then only Wednesday is
# available, and then want also the preceding day to get the prev price,
# which means the Tuesday, which is -7 days. Go back 2 further days just in
# case too, for a total -9!
#
sub make_url {
my ($symbol) = @_;
my $t = time();
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
= gmtime($t + 11 * 3600);
my $hi_day = $mday;
my $hi_month = $mon + 1;
my $hi_year = $year + 1900;
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
= gmtime($t + 10 * 3600 - 9 * 86400);
my $lo_day = $mday;
my $lo_month = $mon + 1;
my $lo_year = $year + 1900;
my ($fund, $product) = symbol_to_fund_and_product ($symbol);
require URI::Escape;
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&',
URI::Escape::uri_escape ($product),
URI::Escape::uri_escape ($fund),
$lo_day, $lo_month, $lo_year,
$hi_day, $hi_month, $hi_year);
}
sub symbol_to_fund_and_product {
my ($symbol) = @_;
my $pos = index ($symbol, ',');
if ($pos == -1) {
return ($symbol, '');
} else {
return (substr ($symbol, 0, $pos),
substr ($symbol, $pos+1));
}
}
# store to hashref $quotes for $symbol based on HTTP::Response in $resp
#
# Initial line like:
#
# historicalProduct1funds[0]="All Funds"
#
# Then price lines like:
#
# historicalProduct1funds[1]="MLC Property Securities Fund,MasterKey Superannuation (Gold Star),29 March 2007,64.71567,0.00000";
#
sub resp_to_quotes {
my ($fq, $resp, $quotes, $symbol) = @_;
$quotes->{$symbol,'method'} = 'mlc';
$quotes->{$symbol,'currency'} = 'AUD';
$quotes->{$symbol,'source'} = __PACKAGE__;
$quotes->{$symbol,'success'} = 1;
my $content = $resp->decoded_content (raise_error => 1, charset => 'none');
if (! $resp->is_success) {
$quotes->{$symbol,'success'} = 0;
$quotes->{$symbol,'errormsg'} = $resp->status_line;
return;
}
if ($content =~ /No unit prices available/i) {
$quotes->{$symbol,'success'} = 0;
$quotes->{$symbol,'errormsg'} = 'No unit prices available';
return;
}
my @data; # elements are arrayrefs [ $isodate, $price ]
while ($content =~ /^historicalProduct1funds.*=\"(.*)\"/mg) {
my ($got_fund, $got_product, $date, $price) = split /,/, $1;
# skip historicalProduct1funds[0]="All Funds" bit
if (! $got_product) { next; }
$date = dmy_to_iso ($fq, $date);
push @data, [ $date, $price ];
### $date
### $price
}
if (! @data) {
$quotes->{$symbol,'success'} = 0;
$quotes->{$symbol,'errormsg'}
= 'Oops, prices not matched in downloaded data';
return;
}
# the lines come with newest date first, but don't assume that;
# sort to oldest date in $data[0], newest in endmost elem
@data = sort {$a->[0] cmp $b->[0]} @data;
$fq->store_date($quotes, $symbol, {isodate => $data[-1]->[0]});
$quotes->{$symbol,'last'} = $data[-1]->[1];
if (@data > 1) {
$quotes->{$symbol,'close'} = $data[-2]->[1];
}
$quotes->{$symbol,'copyright_url'} = COPYRIGHT_URL;
}
sub dmy_to_iso {
my ($fq, $dmy) = @_;
my %dummy_quotes;
$fq->store_date (\%dummy_quotes, '', {eurodate => $dmy});
return $dummy_quotes{'','isodate'};
}
1;
__END__
=head1 NAME
Finance::Quote::MLC - MLC fund prices
=head1 SYNOPSIS
use Finance::Quote;
my $fq = Finance::Quote->new ('MLC');
my $fund = 'MLC MasterKey Horizon 1 - Bond Portfolio';
my $product = 'MasterKey Allocated Pension (Five Star)';
my %quotes = $fq->fetch('mlc', "$fund,$product");
=head1 DESCRIPTION
This module downloads MLC fund quotes from
=over 4
L
=back
under
=over 4
https://www.mlc.com.au/masterkeyWeb/execute/FramesetUnitPrices
=back
As of Sept 2011 the web site terms of use,
=over 4
L
=back
are for general information only, and only provided for residents of
Australia. It's your responsibility to ensure your use of this module
complies with current and future terms.
=head2 Symbols
The symbols used are the fund name and product name with a comma, for
example
=for Finance_Quote_Grab symbols
MLC Horizon 1 - Bond Portfolio,MasterKey Allocated Pension (Five Star)
This is a lot to type, but you can usually cut and paste it from the web
pages. The page source in the link above has them in this form.
The fund part is the actual investment, but there isn't a single price quote
for it, rather the price varies with the product due to different fees
subtracted.
=head2 Fields
The following standard C fields are available
=for Finance_Quote_Grab fields flowed standard
date isodate name currency
last close
method source success errormsg
Plus the following extras
=for Finance_Quote_Grab fields flowed extra
copyright_url
As of June 2009, prices are published some time in the afternoon of the
following business day (Friday's prices some time Monday afternoon). So the
date field is always yesterday or the day before yesterday. The currency is
always "AUD" Australian dollars.
=head1 SEE ALSO
L, L
MLC web site L
=head1 HOME PAGE
L
=head1 LICENCE
Copyright 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2014, 2015, 2016 Kevin Ryde
Finance-Quote-Grab is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3, or (at your option) any later
version.
Finance-Quote-Grab is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
You should have received a copy of the GNU General Public License along with
Finance-Quote-Grab; see the file F. If not, see
L.
=cut