File Coverage

blib/lib/Finance/Quote/FTfunds.pm
Criterion Covered Total %
statement 26 130 20.0
branch 0 52 0.0
condition 0 9 0.0
subroutine 10 11 90.9
pod 0 3 0.0
total 36 205 17.5


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2              
3             # ftfunds.pm
4             #
5             # Obtains quotes for UK Unit Trusts from http://funds.ft.com/ - please
6             # refer to the end of this file for further information.
7             #
8             # author: Martin Sadler (martinsadler@users.sourceforge.net)
9             #
10             # Version 0.1 Initial version - 06 Sep 2010
11             #
12             # Version 0.2 Better look-up - 19 Sep 2010
13             #
14             # Version 0.3 name changed to "ftfunds"
15             # (all lower case) and tidy-up - 31 Jan 2011
16             #
17             # Version 0.4 Allows alphanumerics MEXIDs
18             # and back to "FTfunds" - 28 Feb 2011
19             #
20             # Version 1.0 Changed to work with the new
21             # format of funds.ft.com - 14 Sep 2011
22             #
23             # Version 2.0 Changed to work with the latest
24             # format of funds.ft.com - 31 Mar 2013
25             #
26             # This program is free software; you can redistribute it and/or modify
27             # it under the terms of the GNU General Public License as published by
28             # the Free Software Foundation; either version 2 of the License, or
29             # (at your option) any later version.
30             #
31             # This program is distributed in the hope that it will be useful,
32             # but WITHOUT ANY WARRANTY; without even the implied warranty of
33             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34             # GNU General Public License for more details.
35             #
36             # You should have received a copy of the GNU General Public License
37             # along with this program; if not, write to the Free Software
38             # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
39             # 02110-1301, USA
40             #
41              
42              
43             package Finance::Quote::FTfunds;
44             require 5.005;
45              
46 5     5   2797 use strict;
  5         16  
  5         151  
47 5     5   25 use warnings;
  5         13  
  5         140  
48              
49             # Set DEBUG => 0 for no debug messages, => 1 for first level, => 2 for 2nd level, etc.
50              
51 5     5   24 use constant DEBUG => 0;
  5         10  
  5         297  
52              
53             # URLs
54 5     5   38 use vars qw($VERSION $FTFUNDS_LOOK_UD $FTFUNDS_LOOK_LD $FTFUNDS_MAIN_URL);
  5         10  
  5         311  
55              
56 5     5   35 use LWP::UserAgent;
  5         10  
  5         28  
57 5     5   138 use HTTP::Request::Common;
  5         12  
  5         364  
58 5     5   2874 use HTTP::Cookies;
  5         35279  
  5         55  
59 5     5   2703 use HTML::TokeParser;
  5         11469  
  5         73  
60             # use Data::Dumper;
61              
62             our $VERSION = '1.58'; # VERSION
63              
64             $FTFUNDS_MAIN_URL = "https://markets.ft.com";
65             $FTFUNDS_LOOK_LD = "https://markets.ft.com/data/funds/tearsheet/summary?s=";
66             $FTFUNDS_LOOK_UD = "http://funds.ft.com/UnlistedTearsheet/Summary?s=";
67              
68             # this will work with ISIN codes only.
69              
70             # FIXME -
71              
72 5     5 0 27 sub methods { return (ftfunds => \&ftfunds_fund,
73             ukfunds => \&ftfunds_fund); }
74              
75             {
76             my @labels = qw/name currency last date time price nav source iso_date method net p_change success errormsg/;
77              
78 5     5 0 17 sub labels { return (ftfunds => \@labels,
79             ukfunds => \@labels); }
80             }
81              
82             #
83             # =======================================================================
84              
85             sub ftfunds_fund {
86 0     0 0   my $quoter = shift;
87 0           my @symbols = @_;
88              
89 0 0         return unless @symbols;
90              
91 0           my %fundquote;
92              
93 0           my $ua = $quoter->user_agent;
94 0           my $cj = HTTP::Cookies->new();
95 0           $ua->cookie_jar( $cj );
96              
97 0           foreach (@symbols)
98             {
99 0           my $code = $_;
100              
101 0           my $code_type = "** Invalid **";
102 0 0 0       if ($code =~ m/^[a-zA-Z]{2}[a-zA-Z0-9]{9}\d(.*)/ && !$1) { $code_type = "ISIN"; }
  0 0 0        
    0 0        
103 0           elsif ($code =~ m/^[a-zA-Z0-9]{6}\d(.*)/ && !$1 ) { $code_type = "SEDOL"; }
104 0           elsif ($code =~ m/^[a-zA-Z0-9]{4,6}(.*)/ && !$1) { $code_type = "MEXID"; }
105              
106             # current version can only use ISIN - report an error and exit if any other type
107              
108 0 0         if ($code_type ne "ISIN")
109             {
110 0           $fundquote {$code,"success"} = 0;
111 0           $fundquote {$code,"errormsg"} = "Error - invalid symbol";
112 0           next;
113             }
114              
115 0           $fundquote {$code,"success"} = 1; # ever the optimist....
116 0           $fundquote {$code,"errormsg"} = "Success";
117              
118             # perform the look-up - if not found, return with error
119              
120             # try listed funds first...
121              
122 0           my $webdoc = $ua->get($FTFUNDS_LOOK_LD.$code);
123 0 0         if (!$webdoc->is_success)
124             {
125             # serious error, report it and give up
126 0           $fundquote {$code,"success"} = 0;
127 0           $fundquote {$code,"errormsg"} =
128             "Error - failed to retrieve fund data : HTTP status = ".$webdoc->status_line;
129 0           next;
130             }
131              
132 0           DEBUG and print "\nTitle = ",$webdoc->title,"\n";
133 0           DEBUG and print "\nStatus = ",$webdoc->status_line, "\n";
134 0           DEBUG > 1 and print "\nCookie Jar = : \n",Dumper($cj),"\n\n";
135              
136 0           $fundquote {$code, "source"} = $FTFUNDS_LOOK_LD.$code;
137              
138             # if page contains "<h2>0 results</h2>" it's not found...
139             # ... try unlisted funds...
140              
141 0 0         if ($webdoc->content =~
142             m[<h2>(0 results)</h2>] )
143             {
144 0           DEBUG and print "\nTrying unlisted funds for ",$code," : ",$1,"\n";
145 0           $webdoc = $ua->get($FTFUNDS_LOOK_UD.$code);
146 0 0         if (!$webdoc->is_success)
147             {
148             # serious error, report it and give up
149 0           $fundquote {$code,"success"} = 0;
150 0           $fundquote {$code,"errormsg"} =
151             "Error - failed to retrieve fund data : HTTP status = ".$webdoc->status_line;
152 0           next;
153             }
154              
155 0           DEBUG and print "\nTitle = ",$webdoc->title,"\n";
156 0           DEBUG and print "\nStatus = ",$webdoc->status_line, "\n";
157 0           DEBUG > 1 and print "\nCookie Jar = : \n",Dumper($cj),"\n\n";
158              
159 0           $fundquote {$code, "source"} = $FTFUNDS_LOOK_UD.$code;
160             }
161              
162 0           $fundquote {$code, "symbol"} = $code;
163              
164             # Find name by simple regexp
165              
166 0           my $name;
167 0 0         if ($webdoc->content =~
168             m[<title>(.*) [Ss]ummary - FT.com] )
169             {
170 0           $name = $1 ;
171             }
172 0 0         if (!defined($name)) {
173             # not a serious error - don't report it ....
174             # ... but set a useful message ....
175 0           $fundquote {$code,"errormsg"} = "Warning - failed to find fund name";
176 0           $name = "*** UNKNOWN ***";
177             # ... and continue
178             }
179 0           $fundquote {$code, "name"} = $name; # set name
180              
181             # Find price and currency
182 0           my $currency;
183             my $price;
184 0 0         if ($webdoc->content =~
185             m[<span class="mod-ui-data-list__label"[^>]*>Price [(]([A-Z]{3})[)]</span><span class="mod-ui-data-list__value">([\.\,0-9]*)</span>] )
186             {
187 0           $currency = $1;
188 0           $price = $2;
189             }
190 0 0         if (!defined($currency)) {
191             # serious error, report it and give up
192 0           $fundquote {$code,"success"} = 0;
193 0           $fundquote {$code,"errormsg"} = "Error - failed to find a currency";
194 0           next;
195             }
196 0 0         if (!defined($price)) {
197             # serious error, report it and give up
198 0           $fundquote {$code,"success"} = 0;
199 0           $fundquote {$code,"errormsg"} = "Error - failed to find a price";
200 0           next;
201             }
202 0 0         if ($price =~ m[([0-9]*),([\.0-9]*)])
203             {
204 0           $price = $1 * 1000 + $2;
205             }
206              
207             # Find net and percent-age change
208 0           my $net;
209             my $pchange;
210 0 0         if ($webdoc->content =~
211             m[<span class="mod-ui-data-list__label">Today's Change</span><span class="mod-ui-data-list__value"><span [^>]*><i [^>]*></i>(-?[\.0-9]*) / (-?[\.0-9]*)%</span>] )
212             {
213 0           $net = $1 ;
214 0           $pchange = $2;
215             }
216 0 0         if (!defined($net)) {
217             # not a serious error - don't report it ....
218             # $fundquote {$code,"success"} = 0;
219             # ... but set a useful message ....
220 0           $fundquote {$code,"errormsg"} = "Warning - failed to find a net change.";
221 0           $net = "-0.00"; # ???? is this OK ????
222             # ... and continue
223             }
224 0 0         if (!defined($pchange)) {
225             # not a serious error - don't report it ....
226             # $fundquote {$code,"success"} = 0;
227             # ... but set a useful message ....
228 0           $fundquote {$code,"errormsg"} = "Warning - failed to find a %-change";
229 0           $pchange = "-0.00"; # ???? is this OK ????
230             # ... and continue
231             }
232 0 0         if ($net =~ m[([0-9]*),([\.0-9]*)])
233             {
234 0           $net = $1 * 1000 + $2;
235             }
236 0 0         if ($pchange =~ m[([0-9]*),([\.0-9]*)])
237             {
238 0           $pchange = $1 * 1000 + $2;
239             }
240              
241             # deal with GBX pricing of UK unit trusts
242 0 0         if ($currency eq "GBX")
243             {
244 0           $currency = "GBP" ;
245 0           $price = $price / 100 ;
246 0           $net = $net / 100 ;
247             }
248              
249             # now set prices, net change and currency
250              
251 0           $fundquote {$code, "price"} = $price;
252 0           $fundquote {$code, "last"} = $price;
253 0           $fundquote {$code, "nav"} = $price;
254 0           $fundquote {$code, "net"} = $net;
255 0           $fundquote {$code, "currency"} = $currency;
256 0           $fundquote {$code, "p_change"} = $pchange; # set %-change
257              
258             # Find time
259              
260             # NB. version 2.0 - there is no time quoted on the current (31/3/2013) factsheet page for unit trusts
261             # ... this code left in in case it is available in later revisions of the page
262              
263 0           my $time;
264 0 0         if ($webdoc->content =~ m[......... some string that will identify the time ............] )
265             {
266 0 0         if ($1 =~ m[(\d\d:\d\d)] ) # strip any trailing text (Timezone, etc.)
267             {
268 0           $time = $1;
269             }
270             }
271 0 0         if (!defined($time)) {
272             # not a serious error - don't report it ....
273             # $fundquote {$code,"success"} = 0;
274             # ... but set a useful message ....
275 0           $fundquote {$code,"errormsg"} = "Warning - failed to find a time";
276 0           $time = "17:00"; # set to 17:00 if no time supplied ???
277             # gnucash insists on having a valid-format
278             # ... and continue
279             }
280              
281 0           $fundquote {$code, "time"} = $time; # set time
282              
283             # Find date
284              
285 0           my $date;
286 0 0         if ($webdoc->content =~
287             m[([A-Za-z]{3}) ([0-9]{2}) ([0-9]{4})] )
288             {
289 0           $date = "$2/$1/$3" ;
290             }
291 0 0         if (!defined($date)) {
292             # not a serious error - don't report it ....
293             # $fundquote {$code,"success"} = 0;
294             # ... but set a useful message ....
295 0           $fundquote {$code,"errormsg"} = "Warning - failed to find a date";
296             # use today's date
297 0           $quoter->store_date(\%fundquote, $code, {today => 1});
298             # ... and continue
299             }
300             else
301             {
302 0           $quoter->store_date(\%fundquote, $code, {eurodate => $date});
303             }
304              
305 0           $fundquote {$code, "method"} = "ftfunds"; # set method
306 0           sleep 1; # go to sleep for a while to give the web-site a breather
307              
308             } # end of "foreach (@symbols)"
309              
310 0 0         return wantarray ? %fundquote : \%fundquote;
311             }
312              
313             1;
314              
315             =head1 NAME
316              
317             Finance::Quote::FTfunds - Obtain UK Unit Trust quotes from FT.com (Financial Times).
318              
319             =head1 SYNOPSIS
320              
321             $q = Finance::Quote->new;
322              
323             %info = Finance::Quote->fetch("ftfunds","<isin> ..."); # Only query FT.com using ISINs
324              
325             =head1 DESCRIPTION
326              
327             This module fetches information from the Financial Times Funds service,
328             http://funds.ft.com. There are over 47,000 UK Unit Trusts and OEICs quoted,
329             as well as many Offshore Funds and Exhange Traded Funds (ETFs). It converts
330             any funds quoted in GBX (pence) to GBP, dividing the price by 100 in the
331             process.
332              
333             Funds are identified by their ISIN code, a 12 character alphanumeric code.
334             Although the web site also allows searching by fund name, this version of
335             Finance::Quote::FTfunds only implements ISIN lookup. To determine the ISIN for
336             funds of interest to you, visit the funds.ft.com site and use the flexible search
337             facilities to identify the funds of interest. The factsheet display for any given
338             fund displays the ISIN along with other useful information.
339              
340             This module is loaded by default on a Finance::Quote object. It's
341             also possible to load it explicitly by placing "ftfunds" in the argument
342             list to Finance::Quote->new().
343              
344             Information obtained by this module may be covered by funds.ft.com
345             terms and conditions See http://funds.ft.com/ and http://ft.com for details.
346              
347             =head2 Stocks And Indices
348              
349             This module provides both the "ftfunds" and "ukfunds" fetch methods for
350             fetching UK and Offshore Unit Trusts and OEICs prices and other information
351             from funds.ft.com. Please use the "ukfunds" fetch method if you wish to have
352             failover with future sources for UK and Offshore Unit Trusts and OEICs - the
353             author has plans to develop Finance::Quote modules for the London Stock Exchange
354             and Morningstar unit trust services. Using the "ftfunds" method will guarantee
355             that your information only comes from the funds.ft.com website.
356              
357             =head1 LABELS RETURNED
358              
359             The following labels may be returned by Finance::Quote::ftfunds :
360              
361             name, currency, last, date, time, price, nav, source, method,
362             iso_date, net, p_change, success, errormsg.
363              
364              
365             =head1 SEE ALSO
366              
367             Financial Times websites, http://ft.com and http://funds.ft.com
368              
369              
370             =head1 AUTHOR
371              
372             Martin Sadler, E<lt>martinsadler@users.sourceforge.netE<gt>
373              
374             =head1 COPYRIGHT AND LICENSE
375              
376             Copyright (C) 2010 by Martin Sadler
377              
378             This library is free software; you can redistribute it and/or modify
379             it under the same terms as Perl itself, either Perl version 5.10.1 or,
380             at your option, any later version of Perl 5 you may have available.
381              
382              
383             =cut
384              
385             __END__