Finance-Quote/lib/Finance/Quote/TwelveData.pm
#!/usr/bin/perl -w
# vi: set ts=2 sw=2 noai ic showmode showmatch:
# 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
package Finance::Quote::TwelveData;
use strict;
use constant DEBUG => $ENV{DEBUG};
use if DEBUG, 'Smart::Comments';
use JSON qw( decode_json );
use HTTP::Request::Common;
use Text::Template;
use DateTime::Format::Strptime qw( strptime strftime );
our $VERSION = '1.67_01'; # TRIAL VERSION
my $URL = Text::Template->new(TYPE => 'STRING', SOURCE => 'https://api.twelvedata.com/quote?symbol={$symbol}&apikey={$token}');
my $THROTTLE = 1.05 * 60.0/8.0; # 5% more than maximum seconds / request for free tier
my $AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36';
our $DISPLAY = 'TwelveData';
our $FEATURES = {'API_KEY' => 'registered user API key'};
our @LABELS = qw/symbol name exchange currency isodate currency open high low close last/;
our $METHODHASH = {subroutine => \&twelvedata,
display => $DISPLAY,
labels => \@LABELS,
features => $FEATURES};
sub methodinfo {
return (
twelvedata => $METHODHASH,
usa => $METHODHASH,
nyse => $METHODHASH,
nasdaq => $METHODHASH,
);
}
sub methods {
my %m = methodinfo(); return map {$_ => $m{$_}{subroutine} } keys %m;
}
sub labels {
my %m = methodinfo(); return map {$_ => [@{$m{$_}{labels}}] } keys %m;
}
sub parameters {
return ('API_KEY');
}
sub twelvedata {
my $quoter = shift;
my @stocks = @_;
my $quantity = @stocks;
my ( %info, $reply, $url, $code, $desc, $body, $mark );
my $ua = $quoter->user_agent();
my $agent = $ua->agent();
$ua->agent($AGENT);
my $token = exists $quoter->{module_specific_data}->{twelvedata}->{API_KEY} ?
$quoter->{module_specific_data}->{twelvedata}->{API_KEY} :
$ENV{"TWELVEDATA_API_KEY"};
foreach my $symbol (@stocks) {
if ( !defined $token ) {
$info{ $symbol, 'success' } = 0;
$info{ $symbol, 'errormsg' } =
'TwelveData API_KEY not defined. Get an API key at https://twelvedata.com';
next;
}
# Rate limit - first time through loop, mark is negative
$mark -= time();
### TwelveData Mark: $mark
sleep($mark) if $mark > 0;
$mark = time() + $THROTTLE;
$url = $URL->fill_in(HASH => {symbol => $symbol, token => $token});
$reply = $ua->request(GET $url);
### url: $url
### reply: $reply
$code = $reply->code;
$desc = HTTP::Status::status_message($code);
$body = $reply->content;
if ($code != 200) {
$info{ $symbol, 'success' } = 0;
$info{ $symbol, 'errormsg' } = $desc;
next;
}
my $quote;
eval {$quote = JSON::decode_json $body};
if ($@) {
$info{ $symbol, 'success' } = 0;
$info{ $symbol, 'errormsg' } = $@;
next;
}
if (exists $quote->{'status'} and $quote->{'status'} eq 'error') {
$info{ $symbol, 'success' } = 0;
$info{ $symbol, 'errormsg' } = $quote->{'message'};
next;
}
if (not exists $quote->{'symbol'} or $quote->{'symbol'} ne $symbol) {
$info{ $symbol, 'success' } = 0;
$info{ $symbol, 'errormsg' } = "TwevleData return and unexpected json result";
next;
}
$info{ $symbol, 'success' } = 1;
$info{ $symbol, 'symbol' } = $symbol;
$info{ $symbol, 'name'} = $quote->{'name'} if $quote->{'name'};
$info{ $symbol, 'exchange'} = $quote->{'exchange'} if $quote->{'exchange'};
$info{ $symbol, 'currency'} = $quote->{'currency'} if $quote->{'currency'};
$info{ $symbol, 'open' } = $quote->{'open'} if $quote->{'open'};
$info{ $symbol, 'high' } = $quote->{'high'} if $quote->{'high'};
$info{ $symbol, 'low' } = $quote->{'low'} if $quote->{'low'};
$info{ $symbol, 'close' } = $quote->{'close'} if $quote->{'close'};
$info{ $symbol, 'last' } = $quote->{'close'} if $quote->{'close'};
$info{ $symbol, 'volume' } = $quote->{'volume'} if $quote->{'volume'};
$info{ $symbol, 'method' } = 'twelvedata';
my $time = strptime('%s', int($quote->{'timestamp'}));
my $isodate = strftime('%F', $time);
$quoter->store_date( \%info, $symbol, { isodate => $isodate } );
}
$ua->agent($agent);
return wantarray() ? %info : \%info;
}
1;
=head1 NAME
Finance::Quote::TwelveData - Obtain quotes from https://twelvedata.com
=head1 SYNOPSIS
use Finance::Quote;
$q = Finance::Quote->new('TwelveData', twelvedata => {API_KEY => 'your-twelvedata-api-key'});
%info = $q->fetch('twelvedata', 'IBM', 'AAPL');
=head1 DESCRIPTION
This module fetches information from https://twelvedata.com.
This module is loaded by default on a Finance::Quote object. It's
also possible to load it explicitly by placing "TwelveData" in the argument
list to Finance::Quote->new().
This module provides the "twelvedata" fetch method.
=head1 API_KEY
https://twelvedata.com requires users to register and obtain an API key, which
is a secret value written in hexidecimal.
The API key may be set by either providing a module specific hash to
Finance::Quote->new as in the above example, or by setting the environment
variable TWELVEDATA_API_KEY.
=head2 FREE KEY LIMITS
The TwelveData free key limits usage to:
=over
=item 800 queries per day
=item 8 queries per minute
=back
=head1 LABELS RETURNED
The following labels may be returned by Finance::Quote::TwelveData :
symbol name exchange currency isodate currency open high low close last
=cut