#!/usr/bin/perl -w # This module was written in November 2019 based on the # Finance::Quote::Fool.pm module and prior versions of Fool.pm # that carried the following copyrights: # # Copyright (C) 1998, Dj Padzensky # Copyright (C) 1998, 1999 Linas Vepstas # Copyright (C) 2000, Yannick LE NY # Copyright (C) 2000, Paul Fenwick # Copyright (C) 2000, Brent Neal # Copyright (C) 2001, Tobias Vancura # # This program 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 2 of the License, or # (at your option) any later version. # # This program 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 this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA # 02111-1307, USA package Finance::Quote::VanguardSite; require 5.005; our $VERSION = '1.49'; # VERSION use strict; use HTTP::Request::Common; use HTML::TableExtract; use HTML::TreeBuilder; use Text::Template; use Encode qw(decode); use Time::Piece; sub methods { return ( vanguardsite => \&vanguardsite ); } my @labels = qw/date isodate nav/; sub labels { return ( iexcloud => \@labels, ); } sub vanguardsite { my $quoter = shift; my @investments = @_; my (%info, $symbol, $url, $reply, $code, $desc, $body); my $ua = $quoter->user_agent(); my $quantity = @investments; foreach my $symbol (@investments) { my $t = localtime; my $t2 = $t - 7*86400; my $URL = Text::Template->new(TYPE => 'STRING', SOURCE => 'https://personal.vanguard.com/us/funds/tools/pricehistorysearch?radio=1&results=get&FundIntExt=INT&FundId={$symbol}&fundName={$symbol}&radiobutton2=1&beginDate='.$t2->mon.'%2F'.$t2->mday.'%2F'.$t2->year.'&endDate='.$t->mon.'%2F'.$t->mday.'%2F'.$t->year.'&year=#res'); # Vanguard price history doesn't like invalid dates, so we pass a date range from a week ago through today - this range will always return SOME valid data, unless markets close for several days. # Get the web page $url = $URL->fill_in(HASH => {symbol => $symbol}); $reply = $ua->request( GET $url); $code = $reply->code; $desc = HTTP::Status::status_message($code); $body = decode('UTF-8', $reply->content); if ($code != 200) { $info{ $symbol, 'success' } = 0; $info{ $symbol, 'errormsg' } = $desc; next; } # Extract first table with Date and either Price or NAV as headers. my $te = HTML::TableExtract->new( headers => [qw(Date Price|NAV)] ); $te->parse($body); my $ts = $te->first_table_found(); # It's a simple table - just dates and prices. my $currprice; my $currdate; # Iterate over table. We will end up passing the last row (most current price) to the constructor below. foreach my $row ($ts->rows) { $currprice = @$row[1]; $currdate = @$row[0]; } eval { $info{$symbol, 'symbol'} = $symbol; $info{$symbol, 'method'} = 'vanguardsite'; $info{$symbol, 'nav'} = $currprice =~ s/[\$,]//g ? $currprice : die('failed to parse last price'); $info{$symbol, 'currency'} = 'USD'; $info{$symbol, 'success'} = 1; $quoter->store_date( \%info, $symbol, { usdate => $currdate } ); } or do { $info{$symbol, 'errormsg'} = $@; $info{$symbol, 'success'} = 0; } } return wantarray() ? %info : \%info; } 1; =head1 NAME Finance::Quote::VanguardSite - Obtain day-end price (NAV) quotes for Vanguard investments from Vanguard.com. =head1 SYNOPSIS use Finance::Quote::VanguardSite; $q = Finance::Quote::VanguardSite->new; %stockinfo = $q->fetch("vanguardsite","1679"); =head1 DESCRIPTION This module obtains day-end price (NAV) quotes from Vanguard.com for any investment that has a Vanguard fund ID. Note that it will NOT return intraday prices for ETF's - look those up on another provider using their ticker if you want them. The symbol passed to the vanguardsite method must be the fund ID, not the ticker symbol. This module is loaded by default on a Finance::Quote object. It's also possible to load it explicity by placing "VanguardSite" in the argument list to Finance::Quote->new(). Information returned by this module is governed by Vanguard's terms and conditions. =head2 Labels Returned The following labels may be returned by Finance::Quote::VanguardSite: symbol (an internal Vanguard fund ID), nav, currency, method. =head1 SEE ALSO Vanguard, http://www.vanguard.com Finance::Quote. =cut