File Coverage

blib/lib/App/Chart/Suffix/MLC.pm
Criterion Covered Total %
statement 21 23 91.3
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 29 31 93.5


line stmt bran cond sub pod time code
1             # MLC data downloading.
2              
3             # Copyright 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2017 Kevin Ryde
4              
5             # This file is part of Chart.
6             #
7             # Chart is free software; you can redistribute it and/or modify it under the
8             # terms of the GNU General Public License as published by the Free Software
9             # Foundation; either version 3, or (at your option) any later version.
10             #
11             # Chart is distributed in the hope that it will be useful, but WITHOUT ANY
12             # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13             # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14             # details.
15             #
16             # You should have received a copy of the GNU General Public License along
17             # with Chart. If not, see <http://www.gnu.org/licenses/>.
18              
19             package App::Chart::Suffix::MLC;
20 1     1   543 use 5.010;
  1         5  
21 1     1   6 use strict;
  1         3  
  1         27  
22 1     1   7 use warnings;
  1         3  
  1         36  
23 1     1   7 use Carp;
  1         3  
  1         65  
24 1     1   318 use Date::Calc;
  1         6489  
  1         51  
25 1     1   358 use URI::Escape;
  1         1888  
  1         86  
26 1     1   429 use Locale::TextDomain ('App-Chart');
  1         15420  
  1         6  
27              
28 1     1   5359 use App::Chart;
  0            
  0            
29             use App::Chart::Database;
30             use App::Chart::Download;
31             use App::Chart::DownloadHandler;
32             use App::Chart::DownloadHandler::IndivChunks;
33             use App::Chart::Latest;
34             use App::Chart::Sympred;
35             use App::Chart::TZ;
36              
37             # uncomment this to run the ### lines
38             # use Smart::Comments;
39              
40             my $pred = App::Chart::Sympred::Suffix->new ('.MLC');
41              
42             # not sure exactly where MLC operates out of, but Sydney is close enough
43             App::Chart::TZ->sydney->setup_for_symbol ($pred);
44              
45             App::Chart::setup_source_help
46             ($pred, __p('manual-node','MLC Funds'));
47              
48             # The home page has a link to fund descriptions, unfortunately it's in
49             # flash format.
50              
51              
52             #-----------------------------------------------------------------------------
53             # download
54             #
55             # This uses the unit prices under
56             #
57             # https://www.mlc.com.au/masterkeyWeb/execute/FramesetUnitPrices
58             #
59             # Filling in the boxes ends up with a url like the following, with full
60             # fund and product name, and a requested date range (dd/mm/yyyy).
61             #
62             # https://www.mlc.com.au/masterkeyWeb/execute/UnitPricesWQO?openAgent&reporttype=HistoricalDateRange&product=MasterKey%20Superannuation%20%28Gold%20Star%29&fund=Property%20Securities%20Fund&begindate=19/07/2003&enddate=19/07/2004&
63             #
64             # Old data has prices for weekends too, but the same as Friday.
65             #
66              
67             App::Chart::DownloadHandler::IndivChunks->new
68             (name => __('MLC'),
69             pred => $pred,
70             available_tdate => \&available_tdate,
71             url_func => \&url_func,
72             parse => \&parse,
73              
74             # If you fill in the web page boxes asking for more than 1 year it
75             # explains you can only get 1 year at a time (you get 1 year from the
76             # given start date).
77             chunk_size => 250);
78              
79             # Return the expected available tdate for data.
80             #
81             # This is only based on observation, in the morning it seems to be not the
82             # previous weekday but the one before that, and Thursday on a weekend.
83             # Don't know what time of day it ticks over, assume midnight for now.
84             #
85             sub available_tdate {
86             return App::Chart::Download::tdate_today_after
87             (23,59, App::Chart::TZ->sydney)
88             - 1;
89             }
90              
91             # Sample url:
92             # https://www.mlc.com.au/masterkeyWeb/execute/UnitPricesWQO?openAgent&reporttype=HistoricalDateRange&product=MasterKey%20Allocated%20Pension%20%28Five%20Star%29&fund=MLC%20MasterKey%20Horizon%201%20-%20Bond%20Portfolio&begindate=07/01/2007&enddate=07/01/2008&
93             #
94             # In the past it was plain http:
95             # http://www.mlc.com.au/masterkeyWeb/execute/UnitPricesWQO?openAgent&reporttype=HistoricalDateRange&product=MasterKey%20Superannuation%20%28Gold%20Star%29&fund=Property%20Securities%20Fund&begindate=19/07/2003&enddate=19/07/2004&
96             #
97             sub url_func {
98             my ($symbol, $lo, $hi) = @_;
99             my ($lo_year, $lo_month, $lo_day) = App::Chart::tdate_to_ymd ($lo);
100             my ($hi_year, $hi_month, $hi_day) = App::Chart::tdate_to_ymd ($hi);
101             my ($fund, $product) = split /,/, App::Chart::symbol_sans_suffix ($symbol);
102             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&',
103             URI::Escape::uri_escape ($product),
104             URI::Escape::uri_escape ($fund),
105             $lo_day, $lo_month, $lo_year,
106             $hi_day, $hi_month, $hi_year);
107             }
108              
109             # Lines like:
110             # historicalProduct1funds[1]="MLC Property Securities Fund,MasterKey Superannuation (Gold Star),29 March 2007,64.71567,0.00000";
111             #
112             sub parse {
113             my ($symbol, $resp) = @_;
114             my $content = $resp->decoded_content(raise_error=>1);
115              
116             my @data = ();
117             my $h = { source => __PACKAGE__,
118             resp => $resp,
119             currency => 'AUD',
120             data => \@data };
121              
122             while ($content =~ /^historicalProduct1funds.*=\"(.*)\"/mg) {
123             ### $&
124             my ($fund, $product, $date, $price) = split /,/, $1;
125              
126             # skip historicalProduct1funds[0]="All Funds" bit
127             if (! $product) { next; }
128              
129             my ($year, $month, $day) = Date::Calc::Decode_Date_EU ($date);
130             # skip weekends in some old data
131             next if (ymd_is_weekend ($year, $month, $day));
132             $date = App::Chart::ymd_to_iso ($year, $month, $day);
133              
134             push @data, { symbol => $fund . ',' . $product . '.MLC',
135             date => $date,
136             close => $price };
137             }
138             return $h;
139             }
140              
141             sub validate_symbol {
142             my ($symbol) = @_;
143             if ($symbol !~ /,/) {
144             print __x("MLC: invalid symbol, should be \"Fund,Product.MLC\": {symbol}\n",
145             symbol => $symbol);
146             return 0;
147             }
148             return 1;
149             }
150              
151              
152             #------------------------------------------------------------------------------
153             # latest
154             #
155             # The approach here is to download the latest price the same as for the
156             # database above, getting the latest and second latest, so as to calculate a
157             # "change", and back a few extra days to allow for public holidays.
158             #
159             # There's a single download of latest prices for all funds and products. If
160             # $symbol_list was big then it might be worth doing that instead of
161             # individual downloads, but not sure if a set of immediately preceding
162             # prices would be available to make the "change" amount. In any case for
163             # now it's probably unlikely there'll be many funds in the watchlist that
164             # are not in the database.
165             #
166              
167             App::Chart::LatestHandler->new
168             (pred => $pred,
169             proc => \&latest,
170             max_symbols => 1,
171             available_tdate => \&available_tdate);
172              
173             sub latest {
174             my ($symbol_list) = @_;
175             my $symbol = $symbol_list->[0];
176             if (! validate_symbol ($symbol)) { return; }
177             my $avail_tdate = available_tdate();
178             my $url = url_func ($symbol, $avail_tdate-3, $avail_tdate+1);
179             my $resp = App::Chart::Download->get ($url);
180             App::Chart::Download::write_latest_group (parse ($symbol, $resp));
181             }
182              
183              
184             #-----------------------------------------------------------------------------
185             # generic helpers
186              
187             sub ymd_is_weekend {
188             my ($year, $month, $day) = @_;
189             return (Date::Calc::Day_of_Week ($year, $month, $day) >= 6); # 6 or 7
190             }
191              
192              
193             1;
194             __END__