—#!/usr/bin/perl -w
# Copyright (C) 2023, Stephan Gambke <s7eph4n@gmail.com>
# 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., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA
require
5.005;
use
strict;
use
warnings;
package
Finance::Quote::Consorsbank;
use
LWP::UserAgent;
use
DateTime;
use
if
DEBUG,
'Smart::Comments'
;
use
if
DEBUG,
'Data::Dumper'
;
our
$VERSION
=
'1.64_03'
;
# TRIAL VERSION
sub
methods {
return
(
consorsbank
=> \
&consorsbank
,
europe
=> \
&consorsbank
);
}
{
# Correspondence of FQ labels to Consorsbank API fields
# success Did the stock successfully return information? (true/false)
# errormsg Info.Errors.ERROR_MESSAGE If success is false, this field may contain the reason why.
# symbol Info.ID Lookup symbol (ISIN, WKN, ticker symbol)
# name BasicV1.NAME_SECURITY Company or Mutual Fund Name
# method 'consorsbank' The module (as could be passed to fetch) which found this information.
# source Source URL, either general website or direct human-readable deep link
# exchange CONSORS_EXCHANGE_NAME The exchange the information was obtained from.
# currency ISO_CURRENCY ISO currency code
# ask ASK Ask
# avg_vol Average Daily Vol
# bid BID Bid
# cap Market Capitalization
# close PREVIOUS_LAST Previous Close
# date DATETIME_PRICE Last Trade Date (MM/DD/YY format)
# day_range HIGH, LOW Day's Range
# div Dividend per Share
# div_date Dividend Pay Date
# div_yield Dividend Yield
# eps Earnings per Share
# ex_div Ex-Dividend Date.
# high HIGH Highest trade today
# last PRICE Last Price
# low LOW Lowest trade today
# nav Net Asset Value
# net PERFORMANCE Net Change
# open FIRST Today's Open
# p_change PERFORMANCE_PCT Percent Change from previous day's close
# pe P/E Ratio
# time DATETIME_PRICE Last Trade Time
# type The type of equity returned
# volume TOTAL_VOLUME Volume
# year_range HIGH_PRICE_1_YEAR - LOW_PRICE_1_YEAR 52-Week Range
# yield Yield (usually 30 day avg)
my
@labels
=
qw/
symbol
name
method
source
exchange
currency
ask
bid
close
date
day_range
high
last
low
net
open
p_change
volume
year_range
/
;
# Function that lists the data items available from Consorsbank
sub
labels {
return
(
consorsbank
=> \
@labels
,
europe
=> \
@labels
);
}
}
sub
consorsbank {
# a Finance::Quote object
my
Finance::Quote
$quoter
=
shift
;
# a list of zero or more symbol names
my
@symbols
=
@_
or
return
;
# user_agent() provides a ready-to-use LWP::UserAgent
my
$ua
=
$quoter
->user_agent;
my
%info
;
for
my
$symbol
(
@symbols
) {
### $symbol
$info
{
$symbol
,
'symbol'
} =
$symbol
;
$info
{
$symbol
,
'success'
} = 1;
$info
{
$symbol
,
'errormsg'
} =
''
;
my
$query
=
$CONSORS_URL
.
"id=$symbol&field=QuotesV1&field=BasicV1"
;
my
$response
=
$ua
->get(
$query
);
unless
(
$response
->is_success) {
$info
{
$symbol
,
'success'
} = 0;
$info
{
$symbol
,
'errormsg'
} =
"Unable to fetch data from the Consorsbank server for $symbol. Error: "
.
$response
->status_line;
next
;
}
unless
(
$response
->header(
'content-type'
) =~ m|application/json|i) {
$info
{
$symbol
,
'success'
} = 0;
$info
{
$symbol
,
'errormsg'
} =
"Invalid content-type from Consorsbank server for $symbol. Expected: application/json, received: "
.
$response
->header(
'content-type'
);
next
;
}
my
$json
=
$response
->content;
### [<here>] $json:
### $json
my
$data
;
eval
{
$data
= JSON::decode_json(
$json
) };
if
($@) {
$info
{
$symbol
,
'success'
} = 0;
$info
{
$symbol
,
'errormsg'
} =
"Failed to parse JSON data for $symbol. Error: $@."
;
### $@
next
;
}
### [<here>] $data:
### $data
if
(
defined
$data
->[0]{
'Info'
}{
'Errors'
} ){
### API Error: $data->[0]{'Info'}{'Errors'}
$info
{
$symbol
,
'success'
} = 0;
if
(
$data
->[0]{
'Info'
}{
'Errors'
}[0]{
'ERROR_CODE'
} eq
'IDMS'
){
$info
{
$symbol
,
'errormsg'
} =
"Invalid symbol: $symbol"
;
}
else
{
$info
{
$symbol
,
'errormsg'
} =
$data
->[0]{
'Info'
}{
'Errors'
}[0]{
'ERROR_MESSAGE'
}
}
next
;
}
my
$quote
=
$data
->[0]{
'QuotesV1'
}[0];
### [<here>] $symbol:
### $symbol
$info
{
$symbol
,
'symbol'
} =
$data
->[0]{
'Info'
}{
'ID'
}
if
(
defined
$data
->[0]{
'Info'
}{
'ID'
}) ;
$info
{
$symbol
,
'name'
} =
$data
->[0]{
'BasicV1'
}{
'NAME_SECURITY'
}
if
(
defined
$data
->[0]{
'BasicV1'
}{
'NAME_SECURITY'
});
$info
{
$symbol
,
'method'
} =
'consorsbank'
;
$info
{
$symbol
,
'source'
} =
$CONSORS_SOURCE_BASE_URL
.
$data
->[0]{
'Info'
}{
'ID'
};
$info
{
$symbol
,
'day_range'
} =
$quote
->{
'HIGH'
} -
$quote
->{
'LOW'
}
if
(
defined
$quote
->{
'HIGH'
} &&
defined
$quote
->{
'LOW'
});
$info
{
$symbol
,
'year_range'
} =
$quote
->{
'HIGH_PRICE_1_YEAR'
} -
$quote
->{
'LOW_PRICE_1_YEAR'
}
if
(
defined
$quote
->{
'HIGH_PRICE_1_YEAR'
} &&
defined
$quote
->{
'LOW_PRICE_1_YEAR'
});
my
%mapping
= (
'exchange'
=>
'CONSORS_EXCHANGE_NAME'
,
'currency'
=>
'ISO_CURRENCY'
,
'ask'
=>
'ASK'
,
'bid'
=>
'BID'
,
'close'
=>
'PREVIOUS_LAST'
,
'high'
=>
'HIGH'
,
'last'
=>
'PRICE'
,
'low'
=>
'LOW'
,
'net'
=>
'PERFORMANCE'
,
'open'
=>
'FIRST'
,
'p_change'
=>
'PERFORMANCE_PCT'
,
'volume'
=>
'TOTAL_VOLUME'
);
while
((
my
$fqkey
,
my
$cbkey
) =
each
(
%mapping
)) {
$info
{
$symbol
,
$fqkey
} =
$quote
->{
$cbkey
}
if
(
defined
$quote
->{
$cbkey
});
}
$quote
->{
'DATETIME_PRICE'
} = DateTime->now->iso8601
unless
defined
$quote
->{
'DATETIME_PRICE'
};
(
$info
{
$symbol
,
'date'
},
$info
{
$symbol
,
'time'
}) =
split
/T/,
$quote
->{
'DATETIME_PRICE'
};
$quoter
->store_date(\
%info
,
$symbol
, {
isodate
=>
$info
{
$symbol
,
'date'
} });
unless
(
defined
$info
{
$symbol
,
'last'
} ) {
$info
{
$symbol
,
'success'
} = 0;
$info
{
$symbol
,
'errormsg'
} =
"The server did not return a price for $symbol."
;
next
}
}
### [<here>] %info:
### %info
return
wantarray
() ?
%info
: \
%info
;
}
1;
__END__
=head1 NAME
Finance::Quote::Consorsbank - Obtain quotes from Consorsbank.
=head1 SYNOPSIS
use Finance::Quote;
$q = Finance::Quote->new;
%stockinfo = $q->fetch("consorsbank","DE0007664005"); # Only query consorsbank using ISIN.
%stockinfo = $q->fetch("consorsbank","766400"); # Only query consorsbank using WKN.
%stockinfo = $q->fetch("europe","DE0007664005"); # Failover to other sources OK.
=head1 DESCRIPTION
This module obtains information from Consorsbank (https://www.consorsbank.de).
It accepts ISIN or German WKN as requested symbol.
This module is loaded by default on a Finance::Quote object. It's
also possible to load it explicitly by placing "Consorsbank" in the argument
list to Finance::Quote->new().
This module provides both the "consorsbank" and "europe" fetch methods.
Please use the "europe" fetch method if you wish to have failover with other
sources for European stock exchanges. Using the "consorsbank" method will
guarantee that your information only comes from the Consorsbank service.
=head1 LABELS RETURNED
The following labels may be returned by Finance::Quote::Consorsbank:
ask, bid, close, date, day_range, high, last, low, net, open, p_change, volume, year_range
=cut