File Coverage

blib/lib/Finance/Quote/TwelveData.pm
Criterion Covered Total %
statement 24 80 30.0
branch 0 32 0.0
condition 0 6 0.0
subroutine 10 11 90.9
pod 0 4 0.0
total 34 133 25.5


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2             # This program is free software; you can redistribute it and/or modify
3             # it under the terms of the GNU General Public License as published by
4             # the Free Software Foundation; either version 2 of the License, or
5             # (at your option) any later version.
6             #
7             # This program is distributed in the hope that it will be useful,
8             # but WITHOUT ANY WARRANTY; without even the implied warranty of
9             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10             # GNU General Public License for more details.
11             #
12             # You should have received a copy of the GNU General Public License
13             # along with this program; if not, write to the Free Software
14             # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15             # 02110-1301, USA
16              
17             package Finance::Quote::TwelveData;
18 5     5   2645 use strict;
  5         13  
  5         220  
19              
20              
21 5     5   27 use constant DEBUG => $ENV{DEBUG};
  5         16  
  5         303  
22 5     5   31 use if DEBUG, 'Smart::Comments';
  5         10  
  5         25  
23              
24 5     5   174 use JSON qw( decode_json );
  5         22  
  5         27  
25 5     5   491 use HTTP::Request::Common;
  5         11  
  5         346  
26 5     5   34 use Text::Template;
  5         9  
  5         239  
27 5     5   43 use DateTime::Format::Strptime qw( strptime strftime );
  5         11  
  5         67  
28              
29             our $VERSION = '1.58_01'; # TRIAL VERSION
30              
31             my $URL = Text::Template->new(TYPE => 'STRING', SOURCE => 'https://api.twelvedata.com/quote?symbol={$symbol}&apikey={$token}');
32             my $THROTTLE = 1.05 * 60.0/8.0; # 5% more than maximum seconds / request for free tier
33             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';
34              
35             sub methods {
36 5     5 0 21 return ( twelvedata => \&twelvedata );
37             }
38              
39             sub parameters {
40 1     1 0 3 return ('API_KEY');
41             }
42              
43             {
44             our @labels = qw/symbol name exchange currency isodate currency open high low close/;
45              
46             sub labels {
47 5     5 0 14 return ( twelvedata => \@labels );
48             }
49             }
50              
51              
52             sub twelvedata {
53 0     0 0   my $quoter = shift;
54              
55 0           my @stocks = @_;
56 0           my $quantity = @stocks;
57 0           my ( %info, $reply, $url, $code, $desc, $body, $mark );
58 0           my $ua = $quoter->user_agent();
59 0           my $agent = $ua->agent();
60 0           $ua->agent($AGENT);
61              
62             my $token = exists $quoter->{module_specific_data}->{twelvedata}->{API_KEY} ?
63             $quoter->{module_specific_data}->{twelvedata}->{API_KEY} :
64 0 0         $ENV{"TWELVEDATA_API_KEY"};
65              
66 0           foreach my $symbol (@stocks) {
67              
68 0 0         if ( !defined $token ) {
69 0           $info{ $symbol, 'success' } = 0;
70 0           $info{ $symbol, 'errormsg' } =
71             'TwelveData API_KEY not defined. Get an API key at https://twelvedata.com';
72 0           next;
73             }
74              
75             # Rate limit - first time through loop, mark is negative
76 0           $mark -= time();
77             ### TwelveData Mark: $mark
78 0 0         sleep($mark) if $mark > 0;
79 0           $mark = time() + $THROTTLE;
80              
81 0           $url = $URL->fill_in(HASH => {symbol => $symbol, token => $token});
82 0           $reply = $ua->request(GET $url);
83            
84             ### url: $url
85             ### reply: $reply
86            
87 0           $code = $reply->code;
88 0           $desc = HTTP::Status::status_message($code);
89 0           $body = $reply->content;
90            
91 0 0         if ($code != 200) {
92 0           $info{ $symbol, 'success' } = 0;
93 0           $info{ $symbol, 'errormsg' } = $desc;
94 0           next;
95             }
96              
97 0           my $quote;
98 0           eval {$quote = JSON::decode_json $body};
  0            
99 0 0         if ($@) {
100 0           $info{ $symbol, 'success' } = 0;
101 0           $info{ $symbol, 'errormsg' } = $@;
102 0           next;
103             }
104              
105 0 0 0       if (exists $quote->{'status'} and $quote->{'status'} eq 'error') {
106 0           $info{ $symbol, 'success' } = 0;
107 0           $info{ $symbol, 'errormsg' } = $quote->{'message'};
108 0           next;
109             }
110              
111 0 0 0       if (not exists $quote->{'symbol'} or $quote->{'symbol'} ne $symbol) {
112 0           $info{ $symbol, 'success' } = 0;
113 0           $info{ $symbol, 'errormsg' } = "TwevleData return and unexpected json result";
114 0           next;
115             }
116              
117 0           $info{ $symbol, 'success' } = 1;
118 0           $info{ $symbol, 'symbol' } = $symbol;
119 0 0         $info{ $symbol, 'name'} = $quote->{'name'} if $quote->{'name'};
120 0 0         $info{ $symbol, 'exchange'} = $quote->{'exchange'} if $quote->{'exchange'};
121 0 0         $info{ $symbol, 'currency'} = $quote->{'currency'} if $quote->{'currency'};
122 0 0         $info{ $symbol, 'open' } = $quote->{'open'} if $quote->{'open'};
123 0 0         $info{ $symbol, 'high' } = $quote->{'high'} if $quote->{'high'};
124 0 0         $info{ $symbol, 'low' } = $quote->{'low'} if $quote->{'low'};
125 0 0         $info{ $symbol, 'close' } = $quote->{'close'} if $quote->{'close'};
126 0 0         $info{ $symbol, 'volume' } = $quote->{'volume'} if $quote->{'volume'};
127 0           $info{ $symbol, 'method' } = 'twelvedata';
128            
129 0           my $time = strptime('%s', int($quote->{'timestamp'}));
130 0           my $isodate = strftime('%F', $time);
131 0           $quoter->store_date( \%info, $symbol, { isodate => $isodate } );
132             }
133              
134 0           $ua->agent($agent);
135              
136 0 0         return wantarray() ? %info : \%info;
137             }
138             1;
139              
140             =head1 NAME
141              
142             Finance::Quote::TwelveData - Obtain quotes from https://twelvedata.com
143              
144             =head1 SYNOPSIS
145              
146             use Finance::Quote;
147            
148             $q = Finance::Quote->new('TwelveData', twelvedata => {API_KEY => 'your-twelvedata-api-key'});
149              
150             %info = $q->fetch('twelvedata', 'IBM', 'AAPL');
151              
152             =head1 DESCRIPTION
153              
154             This module fetches information from https://twelvedata.com.
155              
156             This module is loaded by default on a Finance::Quote object. It's
157             also possible to load it explicitly by placing "TwelveData" in the argument
158             list to Finance::Quote->new().
159              
160             This module provides the "twelvedata" fetch method.
161              
162             =head1 API_KEY
163              
164             https://twelvedata.com requires users to register and obtain an API key, which
165             is a secret value written in hexidecimal.
166              
167             The API key may be set by either providing a module specific hash to
168             Finance::Quote->new as in the above example, or by setting the environment
169             variable TWELVEDATA_API_KEY.
170              
171             =head2 FREE KEY LIMITS
172              
173             The TwelveData free key limits usage to:
174              
175             =over
176              
177             =item 800 queries per day
178              
179             =item 8 queries per minute
180              
181             =back
182              
183             =head1 LABELS RETURNED
184              
185             The following labels may be returned by Finance::Quote::TwelveData :
186             symbol name exchange currency isodate currency open high low close
187              
188             =cut