1#!/usr/bin/perl -w
2#
3#    Copyright (C) 2019, Jalon Avens
4#
5#    This program is free software; you can redistribute it and/or modify
6#    it under the terms of the GNU General Public License as published by
7#    the Free Software Foundation; either version 2 of the License, or
8#    (at your option) any later version.
9#
10#    This program 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
13#    GNU General Public License for more details.
14#
15#    You should have received a copy of the GNU General Public License
16#    along with this program; if not, write to the Free Software
17#    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18#    02110-1301, USA
19
20package Finance::Quote::MorningstarAU;
21
22use strict;
23use warnings;
24
25use constant DEBUG => $ENV{DEBUG};
26use if DEBUG, 'Smart::Comments';
27
28use JSON;
29use Web::Scraper;
30
31our $VERSION = '1.51'; # VERSION
32
33sub methods {
34  return (aufunds => \&morningstarau, morningstarau => \&morningstarau,);
35}
36
37sub labels {
38  my @labels = qw/currency date isodate method name price symbol/;
39  return (aufund => \@labels, morningstarau => \@labels);
40}
41
42sub morningstarau {
43  my $quoter  = shift;
44  my @symbols = @_;
45  my $ua      = $quoter->user_agent();
46
47  return unless @symbols;
48
49  my %info;
50
51  foreach my $symbol (@symbols) {
52    eval {
53      my $lookup = "https://www.morningstar.com.au/Ausearch/SecurityCodeAutoLookup?q=$symbol";
54      my $reply  = $ua->get($lookup);
55
56      die "Failed to find APIR $symbol" unless $reply->code == 200;
57
58      my $json_data = JSON::decode_json $reply->content;
59
60      ### MorningstarAU lookup: $json_data
61
62      die "Failed to find unique APIR $symbol" unless $json_data and $json_data->{hits}->{total} == 1;
63
64      my $id = $json_data->{hits}->{hits}[0]->{_source}->{Symbol};
65
66      ### MorningstarAU input: $symbol
67      ### MorningstarAU id   : $id
68
69      my $url = "https://www.morningstar.com.au/Funds/FundReport/$id";
70      $reply  = $ua->get($url);
71
72      die "Failed to fetch quote for $symbol using id $id" unless $reply->code == 200;
73
74      my $processor = scraper {
75        process 'div#maincontent h1.RecentHeading', 'name' => ['TEXT',  sub {s/^\s*|\s*$//g}];
76        process 'h3 + p.fundreportsubheading', 'date[]' => ['TEXT', qr/^as at ([0-9]{1,2} [A-Za-z]{3} [0-9]{4})/];
77        process 'table.tablefundreport td', 'table[]' => ['TEXT', sub {s/\s//g}];
78      };
79
80      my $data = $processor->scrape($reply);
81
82      ### data: $data
83
84      my %table = @{$data->{table}};
85
86      die "Mismatch symbol $symbol to APIR Code $table{APIRCode}" unless $symbol eq $table{APIRCode};
87
88      $info{$symbol, 'success'}  = 1;
89      $info{$symbol, 'currency'} = $table{BaseCurrency} eq '$A' ? 'AUD' : $table{BaseCurrency};
90
91      my @dates = grep defined, @{$data->{date}};
92      $quoter->store_date(\%info, $symbol, {'eurodate' => $dates[-1]});
93
94      $info{$symbol, 'method'}   = 'morningstarau';
95      $info{$symbol, 'name'}     = $data->{name};
96      $info{$symbol, 'price'}    = $table{'ExitPrice$'};
97      $info{$symbol, 'symbol'}   = $table{APIRCode};
98    };
99
100    if ($@) {
101      chomp($@);
102      ### error: $@
103
104      $info{$symbol, 'success'}  = 0;
105      $info{$symbol, 'errormsg'} = $@;
106    }
107  }
108
109  return wantarray() ? %info : \%info;
110}
111
1121;
113
114=head1 NAME
115
116Finance::Quote::MorningstarAU - Obtain Australian managed fund quotes from morningstar.com.au
117
118=head1 SYNOPSIS
119
120    $q = Finance::Quote->new;
121
122    %info = Finance::Quote->fetch("morningstarau","<APIR> ...");  # Only query morningstar.com.au using APIRs
123    %info = Finance::Quote->fetch("aufunds","<APIR> ...");  # Failover to other sources
124
125=head1 DESCRIPTION
126
127This module fetches information from the MorningStar Funds service
128https://morningstar.com.au to provide quotes on Australian managed funds in
129AUD.
130
131Funds are identified by their APIR code.
132
133This module is loaded by default on a Finance::Quote object. It's also possible
134to load it explicitly by placing "morningstarau" in the argument list to
135Finance::Quote->new().
136
137=head2 Managed Funds
138
139This module provides both the "morningstarau" and "aufunds" fetch methods for
140fetching Australian funds prices from morningstar.com.au. Please use the
141"aufunds" fetch method if you wish to have failover with future sources for of
142Ausralian fund quotations which might be provided by other Finance::Quote
143modules. Using the "morningstarau" method will guarantee that your information
144only comes from the morningstar.com.au website.
145
146=head1 LABELS RETURNED
147
148The following labels may be returned by
149Finance::Quote::MorningstarAU::morningstarau:
150
151    currency, date, isodate, method, name, price, symbol
152
153=head1 SEE ALSO
154
155Morningstart Australia website https://morningstar.com.au
156
157=head1 AUTHOR
158
159Jalon Avens & others
160
161=head1 COPYRIGHT AND LICENSE
162
163Copyright (C) 2019 by Jalon Avens
164
165This program is free software; you can redistribute it and/or modify
166it under the terms of the GNU General Public License as published by
167the Free Software Foundation; either version 2 of the License, or (at
168your option) any later version.
169
170=cut
171