Finance-Quote-Grab
view release on metacpan or search on metacpan
devel/Casablanca.pm view on Meta::CPAN
sub methods {
return (casablanca => \&casablanca_quotes);
}
sub labels {
return (casablanca => [ qw(date isodate name currency
bid ask
open high low last close p_change volume
year_range
eps
div ex_div div_yield
cap
method source success errormsg
bid_quantity
ask_quantity
dollar_volume_both_sides
year_high
year_low
net_profit
payout_percent
par_value
shares_on_issue
nominal_capital
) ]);
}
sub currency_fields {
return (Finance::Quote::default_currency_fields(),
qw(dollar_volume_both_sides
year_high
year_low
net_profit
par_value
nominal_capital));
}
# Similar information is on the stocks-by-sector page
#
# http://www.casablanca-bourse.com/cgi/ASP/Marche_Central/sectors_en.asp
#
# but there's no bid/offer there, so the individual pages are used.
#
use constant QUOTE_BASE_URL =>
'http://www.casablanca-bourse.com/cgi/ASP/Donnees_Valeur/anglais/Donnes_valeurs.asp?ticker_valeur=';
sub make_url {
my ($symbol) = @_;
require URI::Escape;
return QUOTE_BASE_URL . URI::Escape::uri_escape($symbol);
}
sub casablanca_quotes {
my ($fq, @symbol_list) = @_;
my $ua = $fq->user_agent;
my %quotes;
foreach my $symbol (@symbol_list) {
my $url = make_url ($symbol);
my $req = HTTP::Request->new ('GET', $url);
$ua->prepare_request ($req);
$req->accept_decodable; # we know decoded_content() below
$req->user_agent (__PACKAGE__."/$VERSION " . $req->user_agent);
### Request: $req->as_string
my $resp = $ua->request ($req);
resp_to_quotes ($fq, $symbol, $resp, \%quotes);
}
return wantarray() ? %quotes : \%quotes;
}
# The "Reference price" seems to be the previous close, ie. reference for
# the change amount. The volume is a dollar volume, and it seems to be
# doubled, roughly 2 * price * "Total securities traded".
#
%label_to_field = ('currentprice' => 'last',
'opening' => 'open',
'referenceprice' => 'close', # ie. previous
'todayshigh' => 'high',
'todayslow' => 'low',
'percentchange' => 'p_change',
'actualcapital' => 'cap', # think this is market cap
'volume' => 'dollar_volume_both_sides',
'totalsecuritiestraded' => 'volume',
'bestbuyersask' => 'ask',
'quantityofbestbuyersask' => 'ask_quantity',
'bestsellersbid' => 'bid',
'quantityofbestsellersbid' => 'bid_quantity',
'yearshigh' => 'year_high',
'yearslow' => 'year_low',
# is exercice the results year for other figures?
'exercice' => undef,
'capital' => 'nominal_capital',
# total business revenue, or turnover ?
'figureofaffair' => undef,
'dividend' => 'div', # amount
'numberofshare' => 'shares_on_issue',
'netresult' => 'net_profit', # total
'ex-datedividend' => 'ex_div', # date
'nominalvalue' => 'par_value',
'payout(en%)' => 'payout_percent',
'dividendyield(%)' => 'div_yield',
'earningpershare' => 'eps',
# empty label
'' => undef,
);
# store to hashref $quotes for $symbol based on HTTP::Response in $resp
sub resp_to_quotes {
my ($fq, $symbol, $resp, $quotes) = @_;
$quotes->{$symbol,'method'} = 'casablanca';
$quotes->{$symbol,'currency'} = 'MAD';
$quotes->{$symbol,'source'} = __PACKAGE__;
$quotes->{$symbol,'success'} = 1;
# defaults to latin1, which is right
my $content = $resp->decoded_content (raise_error => 1, charset => 'none');
if (! $resp->is_success) {
$quotes->{$symbol,'success'} = 0;
$quotes->{$symbol,'errormsg'} = $resp->status_line;
return;
}
$content =~ tr/\240/ /;
if ($content =~ /Pas de r.sultat/) {
$quotes->{$symbol,'success'} = 0;
$quotes->{$symbol,'errormsg'} = 'No information';
return;
}
# Pick out the name, eg.
# <center>
# <p> </p>
# <p><font color="#004496" face="Verdana" size="-1"><b>AUTO NEJMA </b></font><br>
if ($content !~ /<[bB]>([A-Z][^<\r\n]+)/) {
$quotes->{$symbol,'success'} = 0;
$quotes->{$symbol,'errormsg'} = 'Cannot find stock name in page';
return;
}
my $name = $1;
$name =~ s/\s+$//; # trailing whitespace
$quotes->{$symbol,'name'} = $name;
# Pick out the date, eg.
# <b>Session of:</b> </font><font color="#004496" face="Verdana" size="-2"><b>
# 13/04/2007 </b></font> </td>
# Match first d/m/y after "Session of" (possibly crossing newlines)
if ($content !~ m{Session of.*?([0-9]{1,2}/[0-9]{1,2}/[0-9]{4})}s) {
$quotes->{$symbol,'success'} = 0;
$quotes->{$symbol,'errormsg'} = 'Cannot find date in page';
return;
}
$fq->store_date($quotes, $symbol, {eurodate => $1});
require HTML::TableExtract;
my $te = HTML::TableExtract->new;
$te->parse ($content);
foreach my $ts ($te->tables) {
my $rows = $ts->rows;
foreach my $row (@$rows) {
### $row
for (my $i = 0; $i <= $#$row-1; $i += 2) {
my $label = $row->[$i];
if (! defined $label) { next; }
$label = lc $label;
$label =~ s/[\s:']//g; # collapse for matching
# if (DEBUG) {
# if (! exists $label_to_field{$label}) {
# print "Unrecognised label: '$label'\n";
# }
# }
my $field = $label_to_field{$label} or next;
my $value = $row->[$i+1];
$value =~ tr/\240/ /; # converted by TableExtract HTML::Parser
( run in 1.332 second using v1.01-cache-2.11-cpan-5735350b133 )