Finance-Quote-Grab
view release on metacpan or search on metacpan
lib/Finance/Quote/MLC.pm view on Meta::CPAN
# 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 <http://www.gnu.org/licenses/>.
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
( run in 1.305 second using v1.01-cache-2.11-cpan-f56aa216473 )