Sponsoring The Perl Toolchain Summit 2025: Help make this important event another success Learn more

#!/usr/bin/perl -w
#
# Copyright (C) 1998, Dj Padzensky <djpadz@padz.net>
# Copyright (C) 1998, 1999 Linas Vepstas <linas@linas.org>
# Copyright (C) 2000, Yannick LE NY <y-le-ny@ifrance.com>
# Copyright (C) 2000, Paul Fenwick <pjf@cpan.org>
# Copyright (C) 2000, Brent Neal <brentn@users.sourceforge.net>
# Copyright (C) 2000, Volker Stuerzl <volker.stuerzl@gmx.de>
# Copyright (C) 2006, Klaus Dahlke <klaus.dahlke@gmx.de>
#
# 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
#
#
# This code derived from Padzensky's work on package Finance::YahooQuote,
# but extends its capabilites to encompas a greater number of data sources.
#
# This code was developed as part of GnuCash <http://www.gnucash.org/>
#
# $Id: DWS.pm,v 1.7 2006/04/08 19:54:55 hampton Exp $
require 5.005;
require Crypt::SSLeay;
use strict;
our $VERSION = '1.24'; # VERSION
sub methods {
return(dwsfunds => \&dwsfunds);
}
sub labels {
return(dwsfunds => [qw/exchange name date isodate price method/]);
}
# =======================================================================
# The dwsfunds routine gets quotes of DWS funds (Deutsche Bank Gruppe)
# On their website DWS provides a csv file in the format
# symbol1,price1,date1
# symbol2,price2,date2
# ...
#
# This subroutine was written by Volker Stuerzl <volker.stuerzl@gmx.de>
#
# Version 2.0 as new webpage provides the data
# 2006-03-19: Klaus Dahlke
# Since DWS has changed its format and the data are not available via
# csv-file download, the respective web-page is avaluated. There are
# some limitations currently, especially with the name
#
# 2007-01-19: Stephan Ebelt
# - fixed thousands and decimal separator handling
# - check symbol against ISIN as well
# - populate the exchange field with the DWS subsidiary that actually
# runs the fund
# - "improved" the name extraction (this fix is questionable, but does what I
# want for the moment...), falls back to the string length assumption
# from the last version if there is no match
# - fixed indent
#
# 2007-01-26: Stephan Ebelt
# - fixed 'unitialized value' warnings
#
sub dwsfunds {
my $quoter = shift;
my @funds = @_;
return unless @funds;
my $ua = $quoter->user_agent;
my (%fundhash, @q, %info);
my (
$html_string, $te, $ts, $row, @cells, $ce, @ce1, @ce2, @ce22, @ce4, $last,
$wkn, $isin, $exchange, $date, $name, $type
);
# define DWS 'Fondsart' (engl: classifications) as used on the page
# - these strings are used to break down the real name later
# - hardcoding at its best... but not much choice in order to get more
# correct results
my @dws_fund_classifications = (
'Versicherungsfonds',
'Aktienfonds',
'Gemischte Fonds',
'Mitarbeiterfonds',
'Rentenfonds',
'Geldmarktnahe Fonds',
'Dachfonds',
'Kursgewinn-orientierte Fonds',
'AS-Fonds',
'Spezialit.ten', # note the dot ;-)
'Geldmarktfonds',
'Trading Fonds',
'DVG Fonds',
'Wandelanleihen-/ Genussscheinfonds',
'n/a'
);
# create hash of all funds requested
foreach my $fund (@funds) {
$fundhash{$fund} = 0;
}
# get page containing the tables with prices etc
my $response = $ua->request(GET $DWS_URL);
if ($response->is_success) {
$html_string =$response->content;
$te = new HTML::TableExtract->new( depth => 3, count => 1 );
$te->parse($html_string);
$ts=$te->table_state(3,1);
} else {
# retrieval error - flag an error and return right away
foreach my $fund (@funds) {
%info = _dwsfunds_error(@funds, 'HTTP error: ' . $response->status_line);
return wantarray() ? %info : \%info;
}
return wantarray() ? %info : \%info;
}
#
# loop the table rows...
#
foreach $row ($ts->rows) {
@cells =@$row;
# replace line breaks
#
foreach $ce (@cells) {
next unless $ce;
$ce =~ s/\n/:/g;
}
# verify cell count
if( $#cells != 4 ) {
%info = _dwsfunds_error(@funds, "parse error: cells=$#cells, expected cells=4");
return wantarray() ? %info : \%info;
}
# get fond name and exchange
#
@ce1=split(/:/, $cells[1]);
$name = $ce1[0];
$exchange = $ce1[1];
# get date and last price
#
@ce2=split(/:/, $cells[2]);
$date = $ce2[0];
$last = $ce2[2];
#
# wkn or isin is the source
#
@ce4=split(/:/, $cells[4]);
$wkn=$ce4[1];
$isin=$ce4[2];
# match the fund by symbol
foreach my $fund (@funds) {
if ( ($wkn eq $fund) or ($isin eq $fund) ) {
# attempt to separate the name-classification contruct
my $name_ok = 0;
foreach my $t (@dws_fund_classifications) {
if( $name =~ /$t/ ) {
$type = $t;
my @n = split(/$t/, $name);
$name = $n[0];
$name_ok = 1;
last();
}
}
if( ! $name_ok ) {
# fall back - the last 50 characters are either blanks or fond classification
$name = substr($name, 0, length($name)-50);
$info{$fund, "errormsg"} = "name-classification separation failed, guessing...";
}
# mangle last price (thousands/decimal separators, ...)
# - note the decimal separator is hardcoded to ',' (comma)
# - keep arbitrary precision and any eventually following unit (%, $, ...)
if( $last =~ /^(.*),(\d*.{1})$/ ) {
my @tmp = ( $1, $2 );
$tmp[0] =~ s/\.//g;
$last = join('.', @tmp);
}
# finaly, build up the result
$info{$fund,"exchange"} = $exchange;
$info{$fund,"symbol"} = $fund;
$quoter->store_date(\%info, $fund, {eurodate => $date});
$info{$fund,"name"} = $name;
$info{$fund,"last"} = $last;
$info{$fund,"price"} = $last;
$info{$fund,"method"} = "dwsfunds";
$info{$fund,"currency"} = "EUR";
$info{$fund,"success"} = 1;
$fundhash{$fund} = 1;
}
}
}
# make sure a value is returned for every fund requested
foreach my $fund (keys %fundhash) {
if ($fundhash{$fund} == 0) {
$info{$fund, "success"} = 0;
$info{$fund, "errormsg"} = 'No data returned';
}
}
return wantarray() ? %info : \%info;
}
# - populate %info with errormsg and status code set for all requested symbols
# - return a hash ready to pass back to fetch()
sub _dwsfunds_error {
my @symbols = shift;
my $msg = shift;
my %info;
foreach my $s (@symbols) {
$info{$s, "success"} = 0;
$info{$s, "errormsg"} = $msg;
}
return(%info);
}
1;
=head1 NAME
Finance::Quote::DWS - Obtain quotes from DWS (Deutsche Bank Gruppe).
=head1 SYNOPSIS
use Finance::Quote;
$q = Finance::Quote->new;
%fundinfo = $q->fetch("dwsfunds","847402", "DE0008474024", ...);
=head1 DESCRIPTION
This module obtains information about DWS managed funds. Query it with
German WKN and/or international ISIN symbols.
Information returned by this module is governed by DWS's terms
and conditions.
=head1 LABELS RETURNED
The following labels may be returned by Finance::Quote::DWS:
exchange, name, date, price, last.
=head1 SEE ALSO
DWS (Deutsche Bank Gruppe), http://www.dws.de/
=cut