Finance::Quote::Casablanca - download Casablanca Stock Exchange quotes


Finance-Quote-Grab documentation Contained in the Finance-Quote-Grab distribution.

Index


Code Index:

NAME

Top

Finance::Quote::Casablanca - download Casablanca Stock Exchange quotes

SYNOPSIS

Top

 use Finance::Quote;
 my $fq = Finance::Quote->new ('Casablanca');
 my %quotes = $fq->fetch('casablanca','MNG','BCE');

DESCRIPTION

Top

This module downloads stock quotes from the Casablanca Stock Exchange,

http://www.casablanca-bourse.com

Using pages like,

http://www.casablanca-bourse.com/cgi/ASP/Donnees_Valeur/anglais/Donnes_valeurs.asp?ticker_valeur=MNG

The web site terms can be found at the end of the home page. As of June 2009 reproduction of information is for personal private use. It's your responsibility to ensure your use of this module complies with current and future terms.

Quotes are delayed by 20 minutes.

FIELDS

Top

The following standard F-Q fields are available

    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

ex_div is in ISO YYYY-MM-DD format, or if no dividend at all then the field is omitted.

Plus the following extra fields

    bid_quantity      number of shares at the bid
    ask_quantity      number of shares offered at the ask
    dollar_volume_both_sides
    year_high         \ as per year_range
    year_low          /
    net_profit        total company profit
    payout_percent    how much of net profit paid as dividends
    par_value         of each share
    shares_on_issue
    nominal_capital   par_value * shares_on_issue

cap (market capitalization) is last times shares_on_issue. net_profit is eps times shares_on_issue.

dollar_volume_both_sides is so named since it seems to add both the buyer's dollar value and seller's dollar value, ie. roughly "2 * last * volume".

SEE ALSO

Top

Finance::Quote, LWP

Casablanca Stock Exchange web site http://www.casablanca-bourse.com

HOME PAGE

Top

http://user42.tuxfamily.org/finance-quote-grab/index.html

LICENCE

Top

Copyright 2008, 2009, 2010 Kevin Ryde

Finance-Quote-Grab 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 3, or (at your option) any later version.

Finance-Quote-Grab 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 Finance-Quote-Grab; see the file COPYING. If not, see <http://www.gnu.org/licenses/>.


Finance-Quote-Grab documentation Contained in the Finance-Quote-Grab distribution.

# Copyright 2008, 2009, 2010 Kevin Ryde

# This file is part of Finance-Quote-Grab.
#
# Finance-Quote-Grab 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 3, or (at your option) any
# later version.
#
# Finance-Quote-Grab 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, see <http://www.gnu.org/licenses/>.

package Finance::Quote::Casablanca;
use strict;
use warnings;

use vars qw($VERSION %label_to_field);
$VERSION = 4;

use constant DEBUG => 0;


# ENHANCE-ME: "figure of affair" is total business turnover or some such


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 ("Finance::Quote::Casablanca/$VERSION " . $req->user_agent);
    if (DEBUG) { print $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>&nbsp;</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) {
      if (DEBUG) { require Data::Dumper;
                   print Data::Dumper->Dumper([$row],['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/ /; # &nbsp; converted by TableExtract HTML::Parser
        $value =~ s/\s//g;    # whitespace leading, trailing, and thousands sep
        $value =~ tr/,/./;    # comma for decimal point
        if ($value eq '-') { $value = ''; }  # '-' when no value

        $quotes->{$symbol,$field} = $value;
      }
    }
  }

  if (! defined $quotes->{$symbol,'last'}) {
    $quotes->{$symbol,'success'}  = 0;
    $quotes->{$symbol,'errormsg'} = 'Price tables not matched in HTML';
    return;
  }

  # make year_range out of year_high and year_low because the latter two
  # aren't standard FQ fields
  if (defined $quotes->{$symbol,'year_high'}
      && defined $quotes->{$symbol,'year_low'}) {
    $quotes->{$symbol,'year_range'}
      = $quotes->{$symbol,'year_high'} . '-' . $quotes->{$symbol,'year_low'};
  }

  # ex-div is d/m/y format like 17/07/2006, change it to ISO to be unambiguous
  # can be empty when no dividends, eg. ACRED
  # http://www.casablanca-bourse.com/cgi/ASP/Donnees_Valeur/anglais/Donnes_valeurs.asp?ticker_valeur=ACR
  #
  if (defined $quotes->{$symbol,'ex_div'}) {
    if ($quotes->{$symbol,'ex_div'} eq '') {
      delete $quotes->{$symbol,'ex_div'};
    } else {
      $quotes->{$symbol,'ex_div'} = _dmy_to_iso ($quotes->{$symbol,'ex_div'});
    }
  }
}

# $str is a d/m/y date string like 17/07/2006, return an ISO form like
# 2006-07-17
sub _dmy_to_iso {
  my ($str) = @_;
  my ($day, $month, $year) = split /\//, $str;
  return sprintf '%04d-%02d-%02d', $year, $month, $day;
}

1;
__END__