Skip Menu |

This queue is for tickets about the Finance-Quote CPAN distribution.

Report information
The Basics
Id: 103372
Status: new
Priority: 0/
Queue: Finance-Quote

People
Owner: Nobody in particular
Requestors: paul.polak [...] gmail.com
Cc:
AdminCc:

Bug Information
Severity: Critical
Broken in: 1.17
Fixed in: (no value)



Subject: FinanceCanada.pm module - fix attached.
The FinanceCanada module was broken since canada.finance.com changed its website for financial information. The new module now uses www.financialpost.com, and can retrieve quotes for both stocks and mutual funds. The module can retrieve information from multiple exchanges (TOR, NYSE, etc.) and because symbols are not unique among different exchanges, it is worthwhile to check the stock symbol / ID on the financialpost.com site before using the module to ensure correct information is retrieved. Attached is the updated F::Q module.
Subject: FinanceCanada.pm
#!/usr/bin/perl -w # # FinanceCanada.pm # # Version 0.1 Initial version # # Version 0.2 Rewrite by David Hampton <hampton@employees.org> for # changed web site. # # Version 0.3 Rewrite by linux_slacker <paul.polak[AT]gmail[DOT]com> for # changed web site package Finance::Quote::FinanceCanada; require 5.004; use strict; use LWP::UserAgent; use HTTP::Request::Common; use HTML::TokeParser::Simple; my $VERSION = '0.3'; my $FINANCECANADA_MAINURL = ("http://www.financialpost.com/"); my $FINANCECANADA_STOCKSYM_URL = "http://idms.financialpost.com/stocks/company_overview.idms?SYMBOL="; my $FINANCECANADA_STOCKID_URL = "http://idms.financialpost.com/stocks/company_overview.idms?ID_NOTATION="; my $FINANCECANADA_FUND_URL = "http://idms.financialpost.com/funds/snapshot.idms?FUND_KEY="; sub methods { return (canada => \&financecanada, financecanada => \&financecanada); } sub labels { my @labels = qw/method source name symbol currency last date isodate nav price/; return (canada => \@labels, financecanada => \@labels); } sub financecanada { my $quoter = shift; my @symbols = @_; my %info; return unless @symbols; my $ua = $quoter->user_agent; foreach my $symbol (@symbols) { $info{$symbol, "success"} = 0; $info{$symbol, "symbol"} = $symbol; $info{$symbol, "method"} = "financecanada"; $info{$symbol, "source"} = $FINANCECANADA_MAINURL; my @sites; # Figure out which URLs we should use if ($symbol =~ /^[A-Za-z]/) { @sites = ($FINANCECANADA_STOCKSYM_URL); } else { @sites = ($symbol =~ /^\d{5}$/) ? ($FINANCECANADA_FUND_URL, $FINANCECANADA_STOCKID_URL) : ($FINANCECANADA_STOCKID_URL, $FINANCECANADA_FUND_URL); } foreach my $root (@sites) { my $url = $root.$symbol; my $response = $ua->request(GET $url); if (!$response->is_success) { $info{$symbol, "errormsg"} = "Error contacting URL"; next; } my $parser = HTML::TokeParser::Simple->new(string => $response->content); my %ret = ($root eq $FINANCECANADA_FUND_URL) ? ParseFund(\$parser) : ParseStock(\$parser); for my $key (keys %ret) { $info{$symbol, $key} = $ret{$key}; } last if ($info{$symbol, "success"}); } if ($info{$symbol, "success"} == 1) { if (!defined($info{$symbol, "currency"})) { $info{$symbol, "currency"} = "CAD"; } # Use current day at GMT time, since no date given with quote my ($day, $month, $year) = getGMTDate(); $quoter->store_date(\%info, $symbol, {month => $month, day => $day, year => $year}); $info{$symbol, "timezone"} = "GMT"; foreach (keys %info) { $info{$_} =~ s/\$//; } if (defined($info{$symbol, "high"}) && defined($info{$symbol, "low"})) { $info{$symbol, "day_range"} = $info{$symbol, "low"} . " - " . $info{$symbol, "high"}; } if (defined($info{$symbol, "year_high"}) && defined($info{$symbol, "year_low"})) { $info{$symbol, "year_range"} = $info{$symbol, "year_low"}." - ".$info{$symbol, "year_high"}; } } else { $info{$symbol, "errormsg"} = "Cannot parse quote data"; } } return wantarray() ? %info : \%info; } sub ParseStock($) { my $ref = shift; my $parser = $$ref; my %info; while (my $div = $parser->get_tag('div')) { my $id = $div->get_attr('id'); if ($id eq "IDMScontainer") { my $header = $parser->get_tag('h2'); my $name = trim($parser->get_trimmed_text('/h2')); $name =~ s/&nbsp;//g; # Should have at least 1 alphabetic character... last unless ($name =~ /[A-Za-z]/); $info{"name"} = $name; } elsif ($id eq "fundProfile") { for (my $i=0; $i<6; $i++) { my $span = $parser->get_tag('span'); my $class = $span->get_attr('class'); if ($class eq "price") { my $price = removedollar($parser->get_trimmed_text('/span')); last unless ($price =~ /^\d/); $info{"price"} = $price; $info{"success"} = 1; } elsif (($class eq "positive") || ($class eq "negative")) { my $raw = $parser->get_trimmed_text('/span'); $raw =~ /^(\-)?\$((\d)+(\.\d\d))/; $info{"net"} = $1.$2; } elsif ($class eq "high") { $info{"high"} = removedollar($parser->get_trimmed_text('/span')); } elsif ($class eq "low") { $info{"low"} = removedollar($parser->get_trimmed_text('/span')); } elsif ($class eq "volume") { $info{"volume"} = $parser->get_trimmed_text('/span'); } } } elsif ($id eq "quoteDetail") { for (my $i=0; $i<18; $i++) { my $th = $parser->get_tag('th'); if ($i == 3) { $info{"year_high"} = removedollar($parser->get_trimmed_text('th')); } elsif ($i == 5) { $info{"cap"} = removedollar($parser->get_trimmed_text('th')); } elsif ($i == 9) { $info{"year_low"} = removedollar($parser->get_trimmed_text('th')); } elsif ($i == 15) { $info{"exchange"} = $parser->get_trimmed_text('th'); } } } } return %info; } sub ParseFund($) { my $ref = shift; my $parser = $$ref; my %info; my $idmscount = 0; while (my $div = $parser->get_tag('div')) { my $id = $div->get_attr('id'); if ($id eq "IDMScontainer") { $idmscount++; if ($idmscount == 1) { my $header = $parser->get_tag('h2'); my $name = trim($parser->get_trimmed_text('/h2')); $name =~ s/&nbsp;//g; # Should have at least 1 alphabetic character... last unless ($name =~ /[A-Za-z]/); $info{"name"} = $name; } else { while (my $tr = $parser->get_tag('tr')) { my $tdcount = 0; while (my $td = $parser->get_tag('td')) { $tdcount++; if ($tdcount == 1) { my $price = removedollar($parser->get_trimmed_text('/td')); last unless ($price =~ /^\d/); $info{"nav"} = $price; $info{"success"} = 1; } elsif ($tdcount == 2) { $info{"net"} = removedollar($parser->get_trimmed_text('/span')); } elsif ($tdcount == 7) { $info{"currency"} = $parser->get_trimmed_text('/td'); } } } } } } return %info; } sub getGMTDate() { my @timeData = gmtime(time); my $day = $timeData[3]; my $month = 1 + $timeData[4]; my $year = 1900 + $timeData[5]; return ($day, $month, $year); } sub trim($) { my $string = shift; $string =~ s/^\s+//; $string =~ s/\s+$//; return $string; } sub removedollar($) { my $raw = shift; $raw =~ /^\$?(.*)$/; return $1; } 1; =head1 NAME Finance::Quote::FinanceCanada - Obtain stock and fund prices from www.financialpost.com =head1 SYNOPSIS use Finance::Quote; $q = Finance::Quote->new; # Can failover to other methods %quotes = $q->fetch("canada", "stock_code"); # Use this module only %quotes = $q->fetch("financecanada", "stock_code"); =head1 DESCRIPTION This module obtains information about Canadian stocks and funds from www.financialpost.com. The information source "canada" can be used if the information source is unimportant, or "financecanada" to specifically use www.financialpost.com. =head1 STOCK_CODE Canadian stocks/mutual funds do not have a unique symbol identifier on www.financialpost.com. For example, the symbol "T" can refer to stock quotes from either "Telus" on the Toronto Stock Exchange (TOR) or "AT&T Inc." on NYSE. The simplest way to fetch the ID for a particular stock/fund is to go to www.financialpost.com, search for your particular stock/fund, and note the symbol 'id' in the site URL. Note that www.financialpost.com uses different URLs for stocks and funds. This module attempts to guess which URL to use based regular expressions tests on the symbol. =head1 LABELS RETURNED Information available from financecanada may include the following labels: method source name symbol currency date nav last price =head1 SEE ALSO Finance Canada.com website - http://www.financialpost.com/ Finance::Quote =cut