File Coverage

blib/lib/WWW/BetfairNG.pm
Criterion Covered Total %
statement 423 613 69.0
branch 66 194 34.0
condition 32 68 47.0
subroutine 56 58 96.5
pod 39 39 100.0
total 616 972 63.3


line stmt bran cond sub pod time code
1             package WWW::BetfairNG;
2 9     9   148027 use strict;
  9         10  
  9         234  
3 9     9   29 use warnings;
  9         8  
  9         176  
4 9     9   5188 use HTTP::Tiny;
  9         279218  
  9         285  
5 9     9   3492 use JSON::MaybeXS;
  9         49610  
  9         446  
6 9     9   4339 use IO::Uncompress::Gunzip qw/gunzip $GunzipError/;
  9         245882  
  9         824  
7 9     9   52 use Carp qw /croak/;
  9         13  
  9         343  
8              
9             # Define Betfair Endpoints
10 9     9   34 use constant BF_BETTING_ENDPOINT => 'https://api.betfair.com/exchange/betting/rest/v1/';
  9         14  
  9         419  
11 9     9   36 use constant BF_C_LOGIN_ENDPOINT => 'https://identitysso.betfair.com/api/certlogin/';
  9         10  
  9         341  
12 9     9   28 use constant BF_LOGIN_ENDPOINT => 'https://identitysso.betfair.com/api/login/';
  9         10  
  9         292  
13 9     9   93 use constant BF_LOGOUT_ENDPOINT => 'https://identitysso.betfair.com/api/logout/';
  9         10  
  9         305  
14 9     9   28 use constant BF_KPALIVE_ENDPOINT => 'https://identitysso.betfair.com/api/keepAlive/';
  9         10  
  9         364  
15 9     9   27 use constant BF_ACCOUNT_ENDPOINT => 'https://api.betfair.com/exchange/account/rest/v1.0/';
  9         7  
  9         304  
16 9     9   28 use constant BF_HRTBEAT_ENDPOINT => 'https://api.betfair.com/exchange/heartbeat/json-rpc/v1/';
  9         9  
  9         305  
17 9     9   34 use constant BF_RSTATUS_ENDPOINT => 'https://api.betfair.com/exchange/scores/json-rpc/v1/';
  9         11  
  9         42468  
18              
19             =head1 NAME
20              
21             WWW::BetfairNG - Object-oriented Perl interface to the Betfair JSON API
22              
23             =head1 VERSION
24              
25             Version 0.13
26              
27             =cut
28              
29             our $VERSION = '0.13';
30              
31             =head1 SYNOPSIS
32              
33             use WWW::BetfairNG;
34              
35             my $bf = WWW::BetfairNG->new();
36             $bf->ssl_cert();
37             $bf->ssl_key();
38             $bf->app_key();
39              
40             $bf->login({username => , password => });
41             ...
42             $bf->keepAlive();
43             ...
44             $bf->logout();
45              
46             =head1 DESCRIPTION
47              
48             Betfair is an online betting exchange which allows registered users to interact with it
49             using a JSON-based API. This module provides an interface to that service which handles
50             the JSON exchange, taking and returning perl data structures (usually hashrefs). Although
51             there is an option to thoroughly check parameters before sending a request, and a listing
52             of the BETFAIR DATA TYPES is provided below, it requires a level of understanding of the
53             Betfair API which is best gained from their own documentation, available from
54             L
55              
56             To use this library, you will need a funded Betfair account and an application key. To use
57             the non-interactive log in, you will also need an SSL certificate and key (in seperate
58             files, rather than a single .pem file). Details of how to create or obtain these, and how
59             to register your certificate with Betfair are also available on the above website. The
60             interactive login does not require an SSL certificate or key and is therefore easier to
61             set up, but Betfair strongly recommend that unattended bots use the non-interactive
62             version.
63              
64             =head1 METHODS
65              
66             =head2 Construction and Setup
67              
68             =head3 new([$parameters])
69              
70             my $bf = new WWW::BetfairNG; OR
71             my $bf = WWW::BetfairNG->new(); OR
72             my $bf = WWW::BetfairNG->new({
73             ssl_cert => '',
74             ssl_key => '',
75             app_key => '',
76             });
77              
78             Creates a new instance of the WWW::BetfairNG class. Takes an optional hash or hash
79             reference of configurable attributes to set the application key and/or paths to ssl cert
80             and key files. (These may also be set after instantiation via the accessors described
81             below, but in any case the ssl cert and key need to be present for a successful
82             non-interactive login). The application key is required for most of the API calls, but not
83             for login/logout or 'getDeveloperAppKeys', so if necessary the key can be retrieved from
84             Betfair and then passed to the object using $bf->app_key. If logging in is not possible
85             for some reason, but an active session token can be obtained by other means, this may also
86             be passed to the new object using {session => }; the object will then
87             behave as if it were logged in.
88              
89             =cut
90              
91             sub new {
92 11     11 1 19344 my $class = shift;
93             # set attributes configurable at instantiation
94 11         45 my $self = {
95             ssl_cert => '',
96             ssl_key => '',
97             app_key => '',
98             session => '',
99             };
100             # check if we were passed any configurable parameters and load them
101 11 100       39 if (@_) {
102 3         4 my $params = shift;
103 3 100       12 unless(ref($params) eq 'HASH') {
104 1         202 croak 'Parameters must be a hash ref or anonymous hash';
105             }
106 2         7 for my $key (keys %$params) {
107 4 100       8 unless (exists $self->{$key}) {
108 1         139 croak "Unknown key value $key in parameter hash";
109             }
110 3         7 $self->{$key} = $params->{$key};
111             }
112             }
113             # set non-configurable attributes
114             $self->{error} = 'OK',
115 9         28 $self->{response} = {};
116 9         15 $self->{p_check} = 0;
117 9         23 $self->{bet_end_pt} = BF_BETTING_ENDPOINT;
118 9         16 $self->{acc_end_pt} = BF_ACCOUNT_ENDPOINT;
119 9         15 $self->{data_types} = {};
120             # Create an HTTP::Tiny object to do all the heavy lifting
121 9         85 my $client = HTTP::Tiny->new(
122             timeout => 5,
123             agent => "WWW::BetfairNG/$VERSION",
124             default_headers => {'Content-Type' => 'application/json',
125             'Accept' => 'application/json',
126             'Accept-Encoding' => 'gzip'
127             });
128 9         575 $self->{client} = $client;
129 9         15 my $obj = bless $self, $class;
130 9         22 return $obj;
131             }
132              
133             =head2 Accessors
134              
135             =head3 ssl_cert([])
136              
137             my $cert_file = $bf->ssl_cert();
138             $bf->ssl_cert('');
139              
140             Gets or sets the path to the file containing the client certificate required for
141             non-interactive login. Default is '', so this needs to be set for a sucessful login. See
142             Betfair documentation for details on how to create and register client SSL certificates
143             and keys.
144              
145             =cut
146              
147             sub ssl_cert {
148 14     14 1 783 my $self = shift;
149 14 100       34 if (@_){$self->{ssl_cert} = shift};
  4         11  
150 14         50 return $self->{ssl_cert};
151             }
152              
153             =head3 ssl_key([])
154              
155             my $key_file = $bf->ssl_key();
156             $bf->ssl_key('');
157              
158             Gets or sets the path to the file containing the client key required for
159             non-interactive login. Default is '', so this needs to be set for a sucessful
160             login. See Betfair documentation for details on how to create and register client SSL
161             certificates and keys.
162              
163             =cut
164              
165             sub ssl_key {
166 13     13 1 22 my $self = shift;
167 13 100       31 if (@_){$self->{ssl_key} = shift};
  4         8  
168 13         52 return $self->{ssl_key};
169             }
170              
171             =head3 app_key([])
172              
173             my $app_key = $bf->app_key();
174             $bf->app_key('');
175              
176             Gets or sets the application key required for most communications with the API. This key
177             is not required to log in or to use 'getDeveloperAppKeys', so it may be retrieved from
178             Betfair and then passed to the object using this accessor. It may also be possible to
179             create the app keys using 'createDeveloperAppKeys', but as this call fails if keys already
180             exist, it was not possible to test this. See Betfair documentation for how to obtain
181             Application Keys using their API-NG Visualiser.
182              
183             =cut
184              
185             sub app_key {
186 87     87 1 95 my $self = shift;
187 87 100       181 if (@_) {
188 6         11 $self->{app_key} = shift;
189             }
190 87         318 return $self->{app_key};
191             }
192              
193             =head3 session()
194              
195             my $session_token = $bf->session();
196             $bf->session('');
197              
198             Gets or sets the current Session Token. Contains '' if logged out. Normally this is set
199             automatically at login and after keepAlive, and unset at logout, but it can be set by hand
200             if necessary.
201              
202             =cut
203              
204             sub session {
205 194     194 1 2357 my $self = shift;
206 194 100       387 if (@_){
207 59         76 $self->{session} = shift;
208             }
209 194         665 return $self->{session};
210             }
211              
212             =head3 check_parameters()
213              
214             my $check = $bf->check_parameters();
215             $bf->check_parameters('');
216              
217             Gets or sets a flag telling the object whether or not it should do a detailed check on the
218             validity of parameters passed to the API methods. If this is set, the parameter hash will
219             be checked before it is sent to the API, and any errors in construction will result in the
220             method call immediately returning '0' and C<< $bf->error >> being set to a message
221             detailing the precise problem. Only the first error found will be returned, so several
222             iterations may be necessary to fix a badly broken parameter hash. If the flag is not set,
223             any parameters that are a valid hashref or anonymous hash will be passed straight to
224             Betfair, and errors in the construction will result in a Betfair error, which will usually
225             be more general (i.e. cryptic and unhelpful). As some parameter hashes can be quite
226             complicated, there is a performance hit incurred by turning parameter checking on. For
227             this reason, the default is to NOT check parameters, although you should turn it on during
228             development and for debugging.
229              
230             =cut
231              
232             sub check_parameters {
233 30     30 1 46 my $self = shift;
234 30 100       82 if (@_){
235 2         4 my $current_state = $self->{p_check};
236 2         3 my $flag = shift;
237 2 100       6 $self->{p_check} = $flag ? 1 : 0;
238 2 50       6 unless ($self->{p_check} == $current_state) {
239 2 100       11 $self->{data_types} = $self->{p_check} ? $self->_load_data_types() : {};
240             }
241             }
242 30         155 return $self->{p_check};
243             }
244              
245             =head3 australian() *DEPRECATED*
246              
247             my $is_aus = $bf->australian();
248             $bf->australian('');
249              
250             Betfair previously used seperate URLs for Australian racing, and this method implemented
251             the switching between those URLs. From 2016-09-20 the Australian exchange was integrated
252             into the main exchange, making this method unnecessary. From 2017-01-04 calls to the
253             Australian endpoints WILL NO LONGER WORK.
254              
255             The method has been retained in this version for backwards compatibility, but no longer
256             changes the endpoints. It exists purely to avoid breaking existing third party code. If
257             your application uses this method, you are STRONGLY RECOMMENDED to remove any references
258             to it, as it will be removed in future versions.
259              
260             =cut
261              
262             sub australian {
263 0     0 1 0 my $self = shift;
264 0 0       0 if (@_){
265 0         0 my $current_state = $self->{australian};
266 0         0 my $flag = shift;
267 0 0       0 $self->{australian} = $flag ? 1 : 0;
268             }
269 0         0 return $self->{australian};
270             }
271              
272             =head3 error()
273              
274             my $err_str = $bf->error();
275              
276             Read-only string containing the last error encountered. This is not reset by sucessful
277             calls, so the return value of the method needs to be checked to determine success or
278             failure (all methods return '0' if any error is encountered):
279              
280             unless ($ret_value = $bf->someCall($parameters) {
281             $err_str = $bf->error();
282             print "someCall FAILED : $err_str\n";
283            
284             }
285              
286             Errors at any stage will populate this string, including connection timeouts and HTTP
287             errors. If the call makes it as far as the Betfair API before failing (for instance, a
288             lack of available funds), the decoded JSON response will be available in $bf->response and
289             may well contain more detailed and descriptive error messages, so this is probably the
290             best place to look if the high level Betfair error string returned in $bf->error() is
291             vague or ambiguous. (This is especially useful in cases where a number of bets are
292             submitted for processing, and one of them fails - this usually makes the whole call fail,
293             and the only way to find the culprit is to dig through the response and find the bet which
294             caused the problem).
295              
296             =cut
297              
298             sub error {
299 115     115 1 2027 my $self = shift;
300 115         526 return $self->{error};
301             }
302              
303             =head3 response()
304              
305             my $resp = $bf->response();
306              
307             Read-only hash ref containing the last successful response from the API (for certain
308             values of 'successful'). If an API call succeeds completely, it will return a hash
309             reference containing the decoded JSON response (which will be identical to $bf->response),
310             so in this case, $bf->response() is pretty much redundant. If ANY error is encountered,
311             the return value from the API call will be '0', and in this case more details on the
312             specific error can often be found by examining $bf->response(). (Obviously this only works
313             for calls which fail after reaching the API; an HTTP 404 error, for example, will leave
314             the response from the previous successful API call in $bf->response).
315              
316             =cut
317              
318             sub response {
319 3     3 1 6 my $self = shift;
320 3         16 return $self->{response};
321             }
322              
323              
324             =head1 API CALLS
325              
326             These are generally of the form '$return_value = $bf->someCall($parameters)', where
327             '$parameters' is a hash reference (or anonymous hash) containing one or more BETFAIR
328             DATA TYPES (described below), and $return_value is a hash or array reference, again
329             containing one or more BETFAIR DATA TYPES. Many of these data types are straightforward
330             lists or hashes of scalars, but some are quite complex structures. Depending on the
331             call, some parameters may be required (RQD) and others may be optional (OPT). If
332             $bf->check_parameters() is set to 'true', the parameter hash will be checked before it
333             is sent to the API, and any errors in construction will result in the method call
334             immediately returning '0' and C<< $bf->error >> being set to a message detailing the
335             precise problem. If $bf->check_parameters() is set to 'false' (the default), the
336             parameter hash is sent 'as is' to Betfair, and any problems with it's construction will
337             result in a Betfair error message. Any error in a call, for whatever reason, will
338             result in a $return_value of '0'. In this case, $bf->error() will contain a string
339             describing the error and further details of the error may be found by examining
340             $bf->response().
341              
342              
343             =head2 Session Methods
344              
345             =head3 login({username => 'username', password => 'password'})
346              
347             my $return_value = $bf->login({username => 'username', password => 'password'});
348              
349             Logs in to the application using the supplied username and password. For a successful
350             login, 'ssl_cert' and 'ssl_key' must already be set. Returns '1' if the login succeeded,
351             '0' if any errors were encountered.
352              
353             =cut
354              
355             sub login {
356 7     7 1 10 my $self = shift;
357 7 100       17 unless (@_) {
358 1         3 $self->{error} = 'Username and Password Required';
359 1         4 return 0;
360             }
361 6         7 my $params = shift;
362 6 100       16 unless(ref($params) eq 'HASH') {
363 1         4 $self->{error} = 'Parameters must be a hash ref or anonymous hash';
364 1         4 return 0;
365             }
366 5 100 33     18 unless ($params->{username} and $params->{password}) {
367 2         3 $self->{error} = 'Username and Password Required';
368 2         7 return 0;
369             }
370 3         7 my $cert_file = $self->ssl_cert();
371 3 100       7 unless ($cert_file) {
372 1         1 $self->{error} = 'SSL Client Certificate Required';
373 1         3 return 0;
374             }
375 2         4 my $key_file = $self->ssl_key();
376 2 100       5 unless ($key_file) {
377 1         3 $self->{error} = 'SSL Client Key Required';
378 1         4 return 0;
379             }
380 1         3 my $got_app_key = $self->app_key;
381 1 50       3 $self->app_key('login') unless $got_app_key;
382 1         6 my $login_client = HTTP::Tiny->new(
383             agent => "WWW::BetfairNG/$VERSION",
384             SSL_options => {
385             'SSL_cert_file' => $self->ssl_cert,
386             'SSL_key_file' => $self->ssl_key,
387             },
388             default_headers => {
389             'X-Application' => $self->app_key,
390             'Accept' => 'application/json',
391             });
392 1         45 my $formdata = {username => $params->{username}, password => $params->{password}};
393 1         2 my $url = BF_C_LOGIN_ENDPOINT;
394 1         5 my $response = $login_client->post_form($url, $formdata);
395 1 50       78011 $self->app_key(undef) unless $got_app_key;
396 1 50       3 unless ($response->{success}) {
397 1         7 $self->{error} = $response->{status}.' '.$response->{reason}.' '.$response->{content};
398 1         20 return 0;
399             }
400 0         0 $self->{response} = decode_json($response->{content});
401 0 0       0 unless ($self->{response}->{loginStatus} eq 'SUCCESS') {
402 0         0 $self->{error} = $self->{response}->{loginStatus};
403 0         0 return 0;
404             }
405 0         0 $self->session($self->{response}->{sessionToken});
406 0         0 return 1;
407             }
408              
409             =head3 interactiveLogin({username => 'username', password => 'password'})
410              
411             my $return_value = $bf->interactiveLogin({username => 'username',
412             password => 'password'});
413              
414             Logs in to the application using the supplied username and password. This method doesn't
415             use SSL certificates, so it will work without setting those up. However, Betfair STRONGLY
416             RECOMMEND that unattended bots use the non-interactive login ($bf->login()). Returns '1'
417             if the login succeeded, '0' if any errors were encountered.
418              
419             =cut
420              
421             sub interactiveLogin {
422 4     4 1 1037 my $self = shift;
423 4 100       10 unless (@_) {
424 1         22 $self->{error} = 'Username and Password Required';
425 1         5 return 0;
426             }
427 3         3 my $params = shift;
428 3 100       9 unless(ref($params) eq 'HASH') {
429 1         2 $self->{error} = 'Parameters must be a hash ref or anonymous hash';
430 1         3 return 0;
431             }
432 2 50 33     11 unless ($params->{username} and $params->{password}) {
433 2         3 $self->{error} = 'Username and Password Required';
434 2         7 return 0;
435             }
436 0         0 my $got_app_key = $self->app_key;
437 0 0       0 $self->app_key('login') unless $got_app_key;
438 0         0 my $login_client = HTTP::Tiny->new(
439             agent => "WWW::BetfairNG/$VERSION",
440             default_headers => {'X-Application' => $self->app_key,
441             'Accept' => 'application/json',
442             });
443 0         0 my $formdata = {username => $params->{username}, password => $params->{password}};
444 0         0 my $url = BF_LOGIN_ENDPOINT;
445 0         0 my $response = $login_client->post_form($url, $formdata);
446 0 0       0 $self->app_key(undef) unless $got_app_key;
447 0 0       0 unless ($response->{success}) {
448 0         0 $self->{error} = $response->{status}.' '.$response->{reason}.' '.$response->{content};
449 0         0 return 0;
450             }
451 0         0 $self->{response} = decode_json($response->{content});
452 0 0       0 unless ($self->{response}->{status} eq 'SUCCESS') {
453 0         0 $self->{error} = $self->{response}->{status};
454 0         0 return 0;
455             }
456 0         0 $self->session($self->{response}->{token});
457 0         0 return 1;
458             }
459              
460             =head3 logout()
461              
462             my $return_value = $bf->logout();
463              
464             Logs out of the application. Returns '1' if the logout succeeded,'0' if any errors were
465             encountered.
466              
467             =cut
468              
469             sub logout {
470 1     1 1 2 my $self = shift;
471 1 50       3 unless ($self->session){
472 1         3 $self->{error} = 'Not logged in';
473 1         3 return 0;
474             }
475 0         0 my $options = {
476             headers => {
477             'X-Application' => $self->app_key,
478             'X-Authentication' => $self->session,
479             'Connection' => 'Close'
480             }
481             };
482 0         0 my $url = BF_LOGOUT_ENDPOINT;
483 0         0 my $response = $self->{client}->get($url, $options);
484 0 0       0 unless ($response->{success}) {
485 0         0 $self->{error} = $response->{status}.' '.$response->{reason}.' '.$response->{content};
486 0         0 return 0;
487             }
488 0         0 my $content = $self->_gunzip($response->{content});
489 0 0       0 return 0 unless ($content);
490 0         0 $self->{response} = decode_json($content);
491 0 0       0 unless ($self->{response}->{status} eq 'SUCCESS') {
492 0         0 $self->{error} = $self->{response}->{status};
493 0         0 return 0;
494             }
495 0         0 $self->session('');
496 0         0 return 1;
497             }
498              
499             =head3 keepAlive()
500              
501             my $return_value = $bf->keepAlive();
502              
503             Sends a 'Keep Alive' message to the host. Without this, the session will time out after
504             about four hours. Unlike the SOAP interface, other API calls do NOT reset the timeout;
505             it has to be done explicitly with a 'keepAlive'. Returns '1' if the keepAlive succeeded,
506             '0' if any errors were encountered.
507              
508             =cut
509              
510             sub keepAlive {
511 2     2 1 3 my $self = shift;
512 2 100       5 unless ($self->session){
513 1         3 $self->{error} = 'Not logged in';
514 1         4 return 0;
515             }
516 1 50       3 unless ($self->app_key){
517 1         3 $self->{error} = 'No application key set';
518 1         3 return 0;
519             }
520              
521 0         0 my $options = {headers => {'X-Application' => $self->app_key,
522             'X-Authentication' => $self->session}};
523 0         0 my $url = BF_KPALIVE_ENDPOINT;
524 0         0 my $response = $self->{client}->get($url, $options);
525 0 0       0 unless ($response->{success}) {
526 0         0 $self->{error} = $response->{status}.' '.$response->{reason}.' '.$response->{content};
527 0         0 return 0;
528             }
529 0         0 my $content = $self->_gunzip($response->{content});
530 0 0       0 return 0 unless ($content);
531 0         0 $self->{response} = decode_json($content);
532 0 0       0 unless ($self->{response}->{status} eq 'SUCCESS') {
533 0         0 $self->{error} = $self->{response}->{status};
534 0         0 return 0;
535             }
536 0         0 $self->session($self->{response}->{token});
537 0         0 return 1;
538             }
539              
540             =head2 Betting Operations
541              
542             The descriptions of these methods are taken directly from the Betfair documentation. A
543             listing is given of parameters which can be passed to each method together with their data
544             type (BETFAIR DATA TYPES are described below). Required parameters are marked as RQD and
545             optional ones as OPT. If a parameter is marked as RQD, you need to pass it even if it
546             contains no data, so a MarketFilter which selects all markets would be passed as:
547              
548             filter => {}
549              
550             =head3 listCompetitions($parameters)
551              
552             my $return_value = $bf->listCompetitions({filter => {}});
553              
554             Returns a list of Competitions (i.e., World Cup 2013) associated with the markets selected
555             by the MarketFilter. Currently only Football markets have an associated competition.
556              
557             Parameters
558              
559             filter MarketFilter RQD
560             locale String (ISO 3166) OPT
561              
562             Return Value
563              
564             Array Ref CompetitionResult
565              
566             =cut
567              
568             sub listCompetitions {
569 4     4 1 623 my $self = shift;
570 4   50     11 my $params = shift || {};
571 4         8 my $url = $self->{bet_end_pt}.'listCompetitions/';
572 4         11 my $result = $self->_callAPI($url, $params);
573 4         19 return $result;
574             }
575              
576             =head3 listCountries($parameters)
577              
578             my $return_value = $bf->listCountries({filter => {}});
579              
580             Returns a list of Countries associated with the markets selected by the MarketFilter.
581              
582             Parameters
583              
584             filter MarketFilter RQD
585             locale String (ISO 3166) OPT
586              
587             Return Value
588              
589             Array Ref CountryCodeResult
590              
591             =cut
592              
593             sub listCountries {
594 4     4 1 659 my $self = shift;
595 4   50     11 my $params = shift || {};
596 4         10 my $url = $self->{bet_end_pt}.'listCountries/';
597 4         321 my $result = $self->_callAPI($url, $params);
598 4         18 return $result;
599             }
600              
601             =head3 listCurrentOrders([$parameters])
602              
603             my $return_value = $bf->listCurrentOrders();
604              
605             Returns a list of your current orders. Optionally you can filter and sort your current
606             orders using the various parameters, setting none of the parameters will return all of
607             your current orders, up to a maximum of 1000 bets, ordered BY_BET and sorted
608             EARLIEST_TO_LATEST. To retrieve more than 1000 orders, you need to make use of the
609             fromRecord and recordCount parameters.
610              
611             Parameters
612              
613             betIds Array of Strings OPT
614             marketIds Array of Strings OPT
615             orderProjection OrderProjection OPT
616             customerOrderRefs Array of Strings OPT
617             customerStrategyRefs Array of Strings OPT
618             dateRange TimeRange OPT
619             orderBy OrderBy OPT
620             sortDir SortDir OPT
621             fromRecord Integer OPT
622             recordCount Integer OPT
623              
624             Return Value
625              
626             currentOrders Array of CurrentOrderSummary
627             moreAvailable Boolean
628              
629             =cut
630              
631             sub listCurrentOrders {
632 3     3 1 480 my $self = shift;
633 3   50     10 my $params = shift || {};
634 3         9 my $url = $self->{bet_end_pt}.'listCurrentOrders/';
635 3         7 my $result = $self->_callAPI($url, $params);
636 3         14 return $result;
637             }
638              
639             =head3 listClearedOrders([$parameters])
640              
641             my $return_value = $bf->listClearedOrders({betStatus => 'SETTLED'});
642              
643             Returns a list of settled bets based on the bet status, ordered by settled date. To
644             retrieve more than 1000 records, you need to make use of the fromRecord and recordCount
645             parameters. (NOTE The default ordering is DESCENDING settled date, so most recently
646             settled is listed first).
647              
648             Parameters
649              
650             betStatus BetStatus RQD
651             eventTypeIds Array of Strings OPT
652             eventIds Array of Strings OPT
653             marketIds Array of Strings OPT
654             runnerIds Array of Strings OPT
655             betIds Array of Strings OPT
656             customerOrderRefs Array of Strings OPT
657             customerStrategyRefs Array of Strings OPT
658             side Side OPT
659             settledDateRange TimeRange OPT
660             groupBy GroupBy OPT
661             includeItemDescription Boolean OPT
662             locale String OPT
663             fromRecord Integer OPT
664             recordCount Integer OPT
665              
666             Return Value
667              
668             clearedOrders Array of ClearedOrderSummary
669             moreAvailable Boolean
670              
671             =cut
672              
673             sub listClearedOrders {
674 4     4 1 482 my $self = shift;
675 4   50     16 my $params = shift || {};
676 4         7 my $url = $self->{bet_end_pt}.'listClearedOrders/';
677 4         8 my $result = $self->_callAPI($url, $params);
678 4         19 return $result;
679             }
680              
681             =head3 listEvents($parameters)
682              
683             my $return_value = $bf->listEvents({filter => {}});
684              
685             Returns a list of Events associated with the markets selected by the MarketFilter.
686              
687             Parameters
688              
689             filter MarketFilter RQD
690             locale String (ISO 3166) OPT
691              
692             Return Value
693              
694             Array Ref EventResult
695              
696             =cut
697              
698             sub listEvents {
699 4     4 1 463 my $self = shift;
700 4   50     11 my $params = shift || {};
701 4         10 my $url = $self->{bet_end_pt}.'listEvents/';
702 4         8 my $result = $self->_callAPI($url, $params);
703 4         16 return $result;
704             }
705              
706             =head3 listEventTypes($parameters)
707              
708             my $return_value = $bf->listEventTypes({filter => {}});
709              
710             Returns a list of Event Types (i.e. Sports) associated with the markets selected
711             by the MarketFilter.
712              
713             Parameters
714              
715             filter MarketFilter RQD
716             locale String (ISO 3166) OPT
717              
718             Return Value
719              
720             Array Ref EventTypeResult
721              
722             =cut
723              
724             sub listEventTypes {
725 4     4 1 1081 my $self = shift;
726 4   50     12 my $params = shift || {};
727 4         26 my $url = $self->{bet_end_pt}.'listEventTypes/';
728 4         10 my $result = $self->_callAPI($url, $params);
729 4         23 return $result;
730             }
731              
732             =head3 listMarketBook($parameters)
733              
734             my $return_value = $bf->listMarketBook({marketIds => []});
735              
736             Returns a list of dynamic data about markets. Dynamic data includes prices, the status of
737             the market, the status of selections, the traded volume, and the status of any orders you
738             have placed in the market. Calls to listMarketBook should be made up to a maximum of 5
739             times per second to a single marketId.
740              
741             Parameters
742              
743             marketIds Array of Strings RQD
744             priceProjection PriceProjection OPT
745             orderProjection OrderProjection OPT
746             matchProjection MatchProjection OPT
747             includeOverallPosition Boolean OPT
748             partitionMatchedByStrategyRef Boolean OPT
749             customerStrategyRefs Array of Strings OPT
750             currencyCode String OPT
751             locale String OPT
752              
753             Return Value
754              
755             Array Ref MarketBook
756              
757             =cut
758              
759             sub listMarketBook {
760 4     4 1 585 my $self = shift;
761 4   50     13 my $params = shift || {};
762 4         8 my $url = $self->{bet_end_pt}.'listMarketBook/';
763 4         10 my $result = $self->_callAPI($url, $params);
764 4         15 return $result;
765             }
766              
767             =head3 listMarketCatalogue($parameters)
768              
769             my $return_value = $bf->listMarketCatalogue({filter => {}, maxResults => 1});
770              
771             Returns a list of information about markets that does not change (or changes very rarely).
772             You use listMarketCatalogue to retrieve the name of the market, the names of selections
773             and other information about markets. Market Data Request Limits apply to requests made
774             to listMarketCatalogue.
775              
776             Parameters
777              
778             filter MarketFilter RQD
779             marketProjection Array of MarketProjection OPT
780             sort MarketSort OPT
781             maxResults Integer RQD
782             locale String OPT
783              
784             Return Value
785              
786             Array Ref MarketCatalogue
787              
788             =cut
789              
790             sub listMarketCatalogue {
791 5     5 1 604 my $self = shift;
792 5   50     16 my $params = shift || {};
793 5         12 my $url = $self->{bet_end_pt}.'listMarketCatalogue/';
794 5         10 my $result = $self->_callAPI($url, $params);
795 5         20 return $result;
796             }
797              
798             =head3 listMarketProfitAndLoss($parameters)
799              
800             my $return_value = $bf->listMarketProfitAndLoss({marketIds => []});
801              
802             Retrieve profit and loss for a given list of markets. The values are calculated using
803             matched bets and optionally settled bets. Only odds (MarketBettingType = ODDS) markets
804             are implemented, markets of other types are silently ignored.
805              
806             Parameters
807              
808             marketIds Array of Strings RQD
809             includeSettledBets Boolean OPT
810             includeBspBets Boolean OPT
811             netOfCommission Boolean OPT
812              
813             Return Value
814              
815             Array Ref MarketProfitAndLoss
816              
817             =cut
818              
819             sub listMarketProfitAndLoss {
820 4     4 1 646 my $self = shift;
821 4   50     13 my $params = shift || {};
822 4         10 my $url = $self->{bet_end_pt}.'listMarketProfitAndLoss/';
823 4         9 my $result = $self->_callAPI($url, $params);
824 4         18 return $result;
825             }
826              
827             =head3 listMarketTypes($parameters)
828              
829             my $return_value = $bf->listMarketTypes({filter => {}});
830              
831             Returns a list of market types (i.e. MATCH_ODDS, NEXT_GOAL) associated with the markets
832             selected by the MarketFilter. The market types are always the same, regardless of locale
833              
834             Parameters
835              
836             filter MarketFilter RQD
837             locale String (ISO 3166) OPT
838              
839             Return Value
840              
841             Array Ref MarketTypeResult
842              
843             =cut
844              
845             sub listMarketTypes {
846 4     4 1 582 my $self = shift;
847 4   50     10 my $params = shift || {};
848 4         10 my $url = $self->{bet_end_pt}.'listMarketTypes/';
849 4         8 my $result = $self->_callAPI($url, $params);
850 4         15 return $result;
851             }
852              
853             =head3 listTimeRanges($parameters)
854              
855             my $return_value = $bf->listTimeRanges({filter => {}, granularity => 'DAYS'});
856              
857             Returns a list of time ranges in the granularity specified in the request (i.e. 3PM
858             to 4PM, Aug 14th to Aug 15th) associated with the markets selected by the MarketFilter.
859              
860             Parameters
861              
862             filter MarketFilter RQD
863             granularity TimeGranularity RQD
864              
865             Return Value
866              
867             Array Ref TimeRangeResult
868              
869             =cut
870              
871             sub listTimeRanges {
872 5     5 1 603 my $self = shift;
873 5   50     11 my $params = shift || {};
874 5         13 my $url = $self->{bet_end_pt}.'listTimeRanges/';
875 5         9 my $result = $self->_callAPI($url, $params);
876 5         21 return $result;
877             }
878              
879             =head3 listVenues($parameters)
880              
881             my $return_value = $bf->listVenues({filter => {}});
882              
883             Returns a list of Venues (i.e. Cheltenham, Ascot) associated with the markets
884             selected by the MarketFilter. Currently, only Horse Racing markets are associated
885             with a Venue.
886              
887             Parameters
888              
889             filter MarketFilter RQD
890             locale String (ISO 3166) OPT
891              
892             Return Value
893              
894             Array Ref VenueResult
895              
896             =cut
897              
898             sub listVenues {
899 4     4 1 698 my $self = shift;
900 4   50     12 my $params = shift || {};
901 4         8 my $url = $self->{bet_end_pt}.'listVenues/';
902 4         15 my $result = $self->_callAPI($url, $params);
903 4         18 return $result;
904             }
905              
906             =head3 placeOrders($parameters)
907              
908             my $return_value = $bf->placeOrders({marketId => ,
909             instructions => [{
910             selectionId => ,
911             handicap => "0",
912             side => "BACK",
913             orderType => "LIMIT",
914             limitOrder => {
915             size => ,
916             price => ,
917             persistenceType => "LAPSE"
918             }
919             }]
920             });
921              
922             Place new orders into market. This operation is atomic in that all orders will
923             be placed or none will be placed. Please note that additional bet sizing rules
924             apply to bets placed into the Italian Exchange.
925              
926             Parameters
927              
928             marketId String RQD
929             instructions Array of PlaceInstruction RQD
930             customerRef String OPT
931             marketVersion MarketVersion OPT
932             customerStrategyRef String OPT
933             async Boolean OPT
934              
935              
936             Return Value
937              
938             customerRef String
939             status ExecutionReportStatus
940             errorCode ExecutionReportErrorCode
941             marketId String
942             instructionReports Array of PlaceInstructionReport
943              
944             =cut
945              
946             sub placeOrders {
947 5     5 1 664 my $self = shift;
948 5   50     13 my $params = shift || {};
949 5         11 my $url = $self->{bet_end_pt}.'placeOrders/';
950 5         11 my $result = $self->_callAPI($url, $params);
951 5 50       13 if ($result) {
952 0         0 my $status = $result->{status};
953 0 0       0 unless ($status eq 'SUCCESS') {
954 0         0 $self->{error} = $status;
955 0 0       0 if ($result->{errorCode}) {
956 0         0 $self->{error} .= " : ".$result->{errorCode};
957             }
958 0         0 return 0;
959             }
960             }
961 5         22 return $result;
962             }
963              
964             =head3 cancelOrders([$parameters])
965              
966             my $return_value = $bf->cancelOrders();
967              
968             Cancel all bets OR cancel all bets on a market OR fully or partially cancel
969             particular orders on a market. Only LIMIT orders can be cancelled or partially
970             cancelled once placed. Calling this with no parameters will CANCEL ALL BETS.
971              
972             Parameters
973              
974             marketId String OPT
975             instructions Array of CancelInstruction OPT
976             customerRef String OPT
977              
978             Return Value
979              
980             customerRef String
981             status ExecutionReportStatus
982             errorCode ExecutionReportErrorCode
983             marketId String
984             instructionReports Array of CancelInstructionReport
985              
986             =cut
987              
988             sub cancelOrders {
989 3     3 1 576 my $self = shift;
990 3   50     10 my $params = shift || {};
991 3         8 my $url = $self->{bet_end_pt}.'cancelOrders/';
992 3         9 my $result = $self->_callAPI($url, $params);
993 3         14 return $result;
994             }
995              
996              
997             =head3 replaceOrders($parameters)
998              
999             my $return_value = $bf->replaceOrders({marketId => ,
1000             instructions => [{
1001             betId => ,
1002             newPrice =>
1003             }]
1004             });
1005              
1006             This operation is logically a bulk cancel followed by a bulk place. The
1007             cancel is completed first then the new orders are placed. The new orders
1008             will be placed atomically in that they will all be placed or none will be
1009             placed. In the case where the new orders cannot be placed the cancellations
1010             will not be rolled back.
1011              
1012             Parameters
1013              
1014             marketId String RQD
1015             instructions Array of ReplaceInstruction RQD
1016             customerRef String OPT
1017             marketVersion MarketVersion OPT
1018             async Boolean OPT
1019              
1020             Return Value
1021              
1022             customerRef String
1023             status ExecutionReportStatus
1024             errorCode ExecutionReportErrorCode
1025             marketId String
1026             instructionReports Array of ReplaceInstructionReport
1027              
1028             =cut
1029              
1030             sub replaceOrders {
1031 5     5 1 573 my $self = shift;
1032 5   50     14 my $params = shift || {};
1033 5         9 my $url = $self->{bet_end_pt}.'replaceOrders/';
1034 5         12 my $result = $self->_callAPI($url, $params);
1035 5 50       12 if ($result) {
1036 0         0 my $status = $result->{status};
1037 0 0       0 unless ($status eq 'SUCCESS') {
1038 0         0 $self->{error} = $status;
1039 0 0       0 if ($result->{errorCode}) {
1040 0         0 $self->{error} .= " : ".$result->{errorCode};
1041             }
1042 0         0 return 0;
1043             }
1044             }
1045 5         19 return $result;
1046             }
1047              
1048             =head3 updateOrders($parameters)
1049              
1050             my $return_value = $bf->updateOrders({marketId => ,
1051             instructions => [{
1052             betId => ,
1053             newPersistenceType => "LAPSE"
1054             }]
1055             });
1056              
1057             Update non-exposure changing fields.
1058              
1059             Parameters
1060              
1061             marketId String RQD
1062             instructions Array of UpdateInstruction RQD
1063             customerRef String OPT
1064              
1065             Return Value
1066              
1067             customerRef String
1068             status ExecutionReportStatus
1069             errorCode ExecutionReportErrorCode
1070             marketId String
1071             instructionReports Array of UpdateInstructionReport
1072              
1073             =cut
1074              
1075             sub updateOrders {
1076 5     5 1 553 my $self = shift;
1077 5   50     16 my $params = shift || {};
1078 5         13 my $url = $self->{bet_end_pt}.'updateOrders/';
1079 5         12 my $result = $self->_callAPI($url, $params);
1080 5 50       11 if ($result) {
1081 0         0 my $status = $result->{status};
1082 0 0       0 unless ($status eq 'SUCCESS') {
1083 0         0 $self->{error} = $status;
1084 0 0       0 if ($result->{errorCode}) {
1085 0         0 $self->{error} .= " : ".$result->{errorCode};
1086             }
1087 0         0 return 0;
1088             }
1089             }
1090 5         22 return $result;
1091             }
1092              
1093             =head2 Accounts Operations
1094              
1095             As with the Betting Operations, the descriptions of these methods are taken directly from
1096             the Betfair documentation. Once again, required parameters are denoted by RQD and optional
1097             ones by OPT. Some parameters are described in terms of BETFAIR DATA TYPES, which are
1098             described below.
1099              
1100             =head3 createDeveloperAppKeys($parameters)
1101              
1102             my $return_value = $bf->createDeveloperAppKeys();
1103              
1104             Create two application keys for given user; one active and the other delayed. NOTE as this
1105             call fails if the keys have already been created, it has NOT BEEN TESTED.
1106              
1107             Parameters
1108              
1109             appName String RQD
1110              
1111             Return Value
1112              
1113             appName String
1114             appId Long
1115             appVersions Array of DeveloperAppVersion
1116              
1117             =cut
1118              
1119             sub createDeveloperAppKeys {
1120 4     4 1 549 my $self = shift;
1121 4   50     15 my $params = shift || {};
1122 4         11 my $url = $self->{acc_end_pt}.'createDeveloperAppKeys/';
1123 4         9 my $result = $self->_callAPI($url, $params);
1124 4         31 return $result;
1125             }
1126              
1127             =head3 getAccountDetails()
1128              
1129             my $return_value = $bf->getAccountDetails();
1130              
1131             Returns the details relating [to] your account, including your discount rate and Betfair
1132             point balance. Takes no parameters.
1133              
1134             Return Value
1135              
1136             currencyCode String
1137             firstName String
1138             lastName String
1139             localeCode String
1140             region String
1141             timezone String
1142             discountRate Double
1143             pointsBalance Integer
1144             countryCode String
1145              
1146             =cut
1147              
1148             sub getAccountDetails {
1149 3     3 1 1311 my $self = shift;
1150 3   50     14 my $params = shift || {};
1151 3         30 my $url = $self->{acc_end_pt}.'getAccountDetails/';
1152 3         9 my $result = $self->_callAPI($url, $params);
1153 3         17 return $result;
1154             }
1155              
1156             =head3 getAccountFunds()
1157              
1158             my $return_value = $bf->getAccountFunds([$parameters]);
1159              
1160             Get available to bet amount. The optional parameter 'wallet' was
1161             previously used to access Australian funds, but since 2016-09-20
1162             these have been included in the main (UK) wallet.
1163              
1164             Parameters
1165              
1166             wallet Wallet OPT - DEPRECATED
1167              
1168             Return Value
1169              
1170             availableToBetBalance Double
1171             exposure Double
1172             retainedCommission Double
1173             exposureLimit Double
1174             discountRate Double
1175             pointsBalance Integer
1176              
1177             =cut
1178              
1179             sub getAccountFunds {
1180 3     3 1 529 my $self = shift;
1181 3   50     13 my $params = shift || {};
1182 3         9 my $url = $self->{acc_end_pt}.'getAccountFunds/';
1183 3         10 my $result = $self->_callAPI($url, $params);
1184 3         19 return $result;
1185             }
1186              
1187             =head3 getDeveloperAppKeys()
1188              
1189             my $return_value = $bf->getDeveloperAppKeys();
1190              
1191             Get all application keys owned by the given developer/vendor. Takes no parameters.
1192              
1193             Return Value
1194              
1195             Array Ref DeveloperApp
1196              
1197             =cut
1198              
1199             sub getDeveloperAppKeys {
1200 3     3 1 568 my $self = shift;
1201 3   50     12 my $params = shift || {};
1202 3         9 my $url = $self->{acc_end_pt}.'getDeveloperAppKeys/';
1203 3         9 my $result = $self->_callAPI($url, $params);
1204 3         17 return $result;
1205             }
1206              
1207             =head3 getAccountStatement([$parameters])
1208              
1209             my $return_value = $bf->getAccountStatement();
1210              
1211             Get Account Statement.
1212              
1213             Parameters
1214              
1215             locale String OPT
1216             fromRecord Integer OPT
1217             recordCount Integer OPT
1218             itemDateRange TimeRange OPT
1219             includeItem IncludeItem OPT
1220             wallet Wallet OPT
1221              
1222             Return Value
1223              
1224             accountStatement Array of StatementItem
1225             moreAvailable Boolean
1226              
1227             =cut
1228              
1229             sub getAccountStatement {
1230 3     3 1 667 my $self = shift;
1231 3   50     10 my $params = shift || {};
1232 3         8 my $url = $self->{acc_end_pt}.'getAccountStatement/';
1233 3         8 my $result = $self->_callAPI($url, $params);
1234 3         48 return $result;
1235             }
1236              
1237             =head3 listCurrencyRates([$parameters])
1238              
1239             my $return_value = $bf->listCurrencyRates();
1240              
1241             Returns a list of currency rates based on given currency.
1242              
1243             Parameters
1244              
1245             fromCurrency String OPT
1246              
1247             Return Value
1248              
1249             Array Ref CurrencyRate
1250              
1251             =cut
1252              
1253             sub listCurrencyRates {
1254 3     3 1 573 my $self = shift;
1255 3   50     10 my $params = shift || {};
1256 3         9 my $url = $self->{acc_end_pt}.'listCurrencyRates/';
1257 3         8 my $result = $self->_callAPI($url, $params);
1258 3         12 return $result;
1259             }
1260              
1261             =head3 transferFunds($parameters) - DEPRECATED
1262              
1263             my $return_value = $bf->transferFunds({from => 'UK',
1264             to => 'AUSTRALIAN',
1265             amount => });
1266              
1267             Transfer funds between different wallets. With the removal of the Australian
1268             wallet on 2016-09-20 this method is currently DEPRECATED, although it has been
1269             retained as the introduction of alternative wallets for ringfencing funds etc.
1270             has been mooted by Betfair on the forum.
1271              
1272             Parameters
1273              
1274             from Wallet RQD
1275             to Wallet RQD
1276             amount Double RQD
1277              
1278             Return Value
1279              
1280             transactionId String
1281              
1282             =cut
1283              
1284             sub transferFunds {
1285 6     6 1 532 my $self = shift;
1286 6   50     17 my $params = shift || {};
1287 6         13 my $url = $self->{acc_end_pt}.'transferFunds/';
1288 6         14 my $result = $self->_callAPI($url, $params);
1289 6         25 return $result;
1290             }
1291              
1292             =head2 Navigation Data for Applications
1293              
1294             This has only one method (navigationMenu()), which retrieves the full Betfair navigation
1295             menu from a compressed file which is updated every five minutes.
1296              
1297             =head3 navigationMenu()
1298              
1299             my $menu = $bf->navigationMenu()
1300              
1301             Returns a huge hash containing descriptions of all Betfair markets arranged in a tree
1302             structure. The root of the tree is a GROUP entity called 'ROOT', from which hang a
1303             number of EVENT_TYPE entities. Each of these can have a number of GROUP or EVENT
1304             entities as children, which in turn can have GROUP or EVENT children of their own.
1305             EVENTs may also have individual MARKETs as children, whereas GROUPs may not. MARKETs
1306             never have childen, and so are always leaf-nodes, but be aware that the same MARKET
1307             may appear at the end of more than one branch of the tree. This is especially true where
1308             RACEs are concerned; a RACE is yet another entity, which currently may only hang off the
1309             EVENT_TYPE identified by the id '7' and the name 'Horse Racing'. A RACE may only have
1310             MARKETs as children, and these will typically also appear elsewhere in the tree.
1311             Takes no parameters (so it's all or nothing at all).
1312              
1313             Return Value
1314              
1315             children Array of EVENT_TYPE
1316             id Integer (always '0' for ROOT)
1317             name String (always 'ROOT' for ROOT)
1318             type Menu entity type (always 'GROUP' for ROOT)
1319              
1320             Menu Entity Types
1321              
1322             EVENT_TYPE
1323              
1324             children Array of GROUP, EVENT and/or RACE
1325             id String, will be the same as EventType id
1326             name String, will be the same as EventType name
1327             type Menu entity type (EVENT_TYPE)
1328              
1329              
1330             GROUP
1331              
1332             children Array of GROUP and/or EVENT
1333             id String
1334             name String
1335             type Menu entity type (GROUP)
1336              
1337             EVENT
1338              
1339             children Array of GROUP, EVENT and/or MARKET
1340             id String, will be the same as Event id
1341             name String, will be the same as Event name
1342             countryCode ISO 3166 2-Character Country Code
1343             type Menu entity type (EVENT)
1344              
1345             RACE
1346              
1347             children Array of MARKET
1348             id String
1349             name String
1350             type Menu entity type (RACE)
1351             startTime Date
1352             countryCode ISO 3166 2-Character Country Code
1353             venue String (Course name in full)
1354              
1355             MARKET
1356              
1357             exchangeId String (Currently always '1')
1358             id String, will be the same as Market id
1359             marketStartTime Date
1360             marketType MarketType (e.g. 'WIN', 'PLACE')
1361             numberOfWinners No. of winners (used in 'PLACE' markets)
1362             name String, will be the same as Market name
1363             type Menu entity type (MARKET)
1364              
1365             =cut
1366              
1367             sub navigationMenu {
1368 3     3 1 979 my $self = shift;
1369 3         4 my $params = {};
1370             # Can't use _callAPI because we need a 'get' not a 'post'
1371 3 100       18 unless ($self->session){
1372 1         2 $self->{error} = 'Not logged in';
1373 1         5 return 0;
1374             }
1375 2 100       5 unless ($self->app_key){
1376 1         2 $self->{error} = 'No application key set';
1377 1         4 return 0;
1378             }
1379             # Can't use default client because we need a longer timeout
1380 1         7 my $client = HTTP::Tiny->new(
1381             timeout => 30,
1382             agent => "WWW::BetfairNG/$VERSION",
1383             verify_SSL => 1,
1384             default_headers => {'Content-Type' => 'application/json',
1385             'Accept' => 'application/json',
1386             'Accept-Encoding' => 'gzip'
1387             }
1388             );
1389 1         42 my $url = $self->{bet_end_pt}.'en/navigation/menu.json';
1390 1         2 my $options = {
1391             headers => {
1392             'X-Authentication' => $self->session,
1393             'X-Application' => $self->app_key
1394             }
1395             };
1396 1         20 my $response = $client->get($url, $options);
1397 1 50       234483 unless ($response->{success}) {
1398 1         6 $self->{error} = $response->{status}.' '.$response->{reason}.' '.$response->{content};
1399 1         21 return 0;
1400             }
1401 0         0 my $content = $self->_gunzip($response->{content});
1402 0 0       0 return 0 unless ($content);
1403 0         0 $self->{response} = decode_json($content);
1404 0         0 return $self->response;
1405             }
1406              
1407             =head2 Heartbeat API
1408              
1409             This Heartbeat operation is provided to allow customers to automatically cancel their
1410             unmatched bets in the event of their API client losing connectivity with the Betfair API.
1411              
1412             =head3 heartbeat($parameters)
1413              
1414             my $return_value = $bf->heartbeat({preferredTimeoutSeconds => });
1415              
1416             This heartbeat operation is provided to help customers have their positions managed
1417             automatically in the event of their API clients losing connectivity with the Betfair
1418             API. If a heartbeat request is not received within a prescribed time period, then Betfair
1419             will attempt to cancel all 'LIMIT' type bets for the given customer on the given
1420             exchange. There is no guarantee that this service will result in all bets being cancelled
1421             as there are a number of circumstances where bets are unable to be cancelled. Manual
1422             intervention is strongly advised in the event of a loss of connectivity to ensure that
1423             positions are correctly managed. If this service becomes unavailable for any reason, then
1424             your heartbeat will be unregistered automatically to avoid bets being inadvertently
1425             cancelled upon resumption of service. you should manage your position manually until the
1426             service is resumed. Heartbeat data may also be lost in the unlikely event of nodes failing
1427             within the cluster, which may result in your position not being managed until a subsequent
1428             heartbeat request is received.
1429              
1430             Parameters
1431              
1432             preferredTimeoutSeconds Integer RQD
1433              
1434             Return Value
1435              
1436             actionPerformed ActionPerformed
1437             actualTimeoutSeconds Integer
1438              
1439             =cut
1440              
1441             sub heartbeat {
1442 3     3 1 948 my $self = shift;
1443 3   50     7 my $params = shift || {};
1444 3         23 my $url = BF_HRTBEAT_ENDPOINT;
1445 3         3 my $action = 'HeartbeatAPING/v1.0/heartbeat';
1446 3         5 my $result = $self->_callRPC($url, $action, $params);
1447 3         14 return $result;
1448             }
1449              
1450             =head2 Race Status API
1451              
1452             The listRaceDetails operation is provided to allow customers to establish the status of a
1453             horse or greyhound race market both prior to and after the start of the race. This
1454             information is available for UK and Ireland races only.
1455              
1456             =head3 listRaceDetails($parameters)
1457              
1458             my $return_value = $bf->listRaceDetails();
1459              
1460             Search for races to get their details. 'meetingIds' optionally restricts the results to
1461             the specified meeting IDs. The unique Id for the meeting equivalent to the eventId for
1462             that specific race as returned by listEvents. 'raceIds' optionally restricts the results
1463             to the specified race IDs. The unique Id for the race in the format meetingid.raceTime
1464             (hhmm). raceTime is in UTC.
1465              
1466             Parameters
1467              
1468             meetingIds Array of Strings OPT
1469             raceIds Array of Strings OPT
1470              
1471             Return Value
1472              
1473             ArrayRef RaceDetails
1474              
1475             =cut
1476              
1477             sub listRaceDetails {
1478 2     2 1 947 my $self = shift;
1479 2   50     7 my $params = shift || {};
1480 2         14 my $url = BF_RSTATUS_ENDPOINT;
1481 2         3 my $action = 'ScoresAPING/v1.0/listRaceDetails';
1482 2         7 my $result = $self->_callRPC($url, $action, $params);
1483 2         11 return $result;
1484             }
1485              
1486             #===============================#
1487             # Private Methods and Functions #
1488             #===============================#
1489              
1490             # Called by all API methods EXCEPT navigationMenu to do the talking to Betfair.
1491             # =============================================================================
1492             sub _callAPI {
1493 92     92   128 my ($self, $url, $params) = @_;
1494 92 100       171 unless ($self->session){
1495 46         47 $self->{error} = 'Not logged in';
1496 46         56 return 0;
1497             }
1498 46 100 100     116 unless ($self->app_key or ($url =~ /DeveloperAppKeys/)){
1499 21         25 $self->{error} = 'No application key set';
1500 21         25 return 0;
1501             }
1502 25 50       115 unless(ref($params) eq 'HASH') {
1503 0         0 $self->{error} = 'Parameters must be a hash ref or anonymous hash';
1504 0         0 return 0;
1505             }
1506 25 50       88 if ($self->check_parameters) {
1507 0         0 my $caller = [caller 1]->[3];
1508 0         0 $caller =~ s/^.+:://;
1509 0 0       0 return 0 unless $self->_check_parameter($caller, $params);
1510             }
1511 25         58 my $options = {
1512             headers => {
1513             'X-Authentication' => $self->session,
1514             },
1515             content => encode_json($params)
1516             };
1517 25 100       126 unless ($url =~ /DeveloperAppKeys/) {
1518 21         44 $options->{headers}{'X-Application'} = $self->app_key;
1519             }
1520 25         768 my $response = $self->{client}->post($url, $options);
1521 25 50       1515854 unless ($response->{success}) {
1522 25 50       104 if ($response->{status} == 400) {
1523 25         141 my $content = $self->_gunzip($response->{content});
1524 25         365 $self->{response} = decode_json($content);
1525             $self->{error} = $self->{response}->{detail}->{APINGException}->{errorCode} ||
1526 25   66     235 $response->{status}.' '.$response->{reason}.' '.$response->{content};
1527             }
1528             else {
1529             $self->{error} = $response->{status}.' '.$response->{reason}.' '
1530 0         0 .$response->{content};
1531             }
1532 25         154 return 0;
1533             }
1534 0         0 my $content = $self->_gunzip($response->{content});
1535 0 0       0 return 0 unless ($content);
1536 0         0 $self->{response} = decode_json($content);
1537 0         0 return $self->{response};
1538             }
1539              
1540             # Called by Heartbeat and Race Status methods to do the talking to Betfair.
1541             # =========================================================================#
1542             # #
1543             # (Betfair generally supports both a JSON-REST and a JSON-RPC interface to #
1544             # the API, and we use REST. However, Heartbeat and Race Status only allow #
1545             # RPC, so we need a function for that as well. #
1546             # #
1547             # =========================================================================#
1548             sub _callRPC {
1549 5     5   9 my ($self, $url, $action, $params) = @_;
1550 5 100       10 unless ($self->session){
1551 3         6 $self->{error} = 'Not logged in';
1552 3         9 return 0;
1553             }
1554 2 50       7 unless ($self->app_key){
1555 2         5 $self->{error} = 'No application key set';
1556 2         2 return 0;
1557             }
1558 0 0       0 unless(ref($params) eq 'HASH') {
1559 0         0 $self->{error} = 'Parameters must be a hash ref or anonymous hash';
1560 0         0 return 0;
1561             }
1562 0 0       0 if ($self->check_parameters) {
1563 0         0 my ($method_name) = $action =~ /\/(\w+)$/;
1564 0 0       0 return 0 unless $self->_check_parameter($method_name, $params);
1565             }
1566 0         0 my $post = { params => $params, jsonrpc => "2.0", method => $action, id => 1};
1567 0         0 my $options = {
1568             headers => {
1569             'X-Authentication' => $self->session,
1570             'X-Application' => $self->app_key,
1571             },
1572             content => encode_json($post)
1573             };
1574 0         0 my $response = $self->{client}->post($url, $options);
1575 0 0       0 unless ($response->{success}) {
1576 0 0       0 if ($response->{status} == 400) {
1577 0         0 my $content = $self->_gunzip($response->{content});
1578 0         0 $self->{response} = decode_json($content);
1579             $self->{error} = $self->{response}->{detail}->{APINGException}->{errorCode} ||
1580 0   0     0 $response->{status}.' '.$response->{reason}.' '.$response->{content};
1581             }
1582             else {
1583             $self->{error} = $response->{status}.' '.$response->{reason}.' '
1584 0         0 .$response->{content};
1585             }
1586 0         0 return 0;
1587             }
1588 0         0 my $content = $self->_gunzip($response->{content});
1589 0 0       0 return 0 unless ($content);
1590 0         0 $self->{response} = decode_json($content);
1591 0 0       0 if ($self->{response}->{error}) {
1592 0         0 $self->{error} = $self->{response}->{error}->{message};
1593 0         0 return 0;
1594             }
1595 0 0       0 if ($self->{response}->{result}) {
1596 0         0 return $self->{response}->{result};
1597             }
1598             else {
1599 0         0 $self->{error} = "Empty reply";
1600 0         0 return 0;
1601             }
1602             }
1603              
1604             # HTTP::Tiny doesn't have built-in decompression so we do it here
1605             # ===============================================================
1606             sub _gunzip {
1607 25     25   39 my $self = shift;
1608 25         48 my $input = shift;
1609 25 50       59 unless ($input) {
1610 0         0 $self->{error} = "gunzip failed : empty input string";
1611 0         0 return 0;
1612             }
1613 25         30 my $output;
1614 25         164 my $status = gunzip(\$input => \$output);
1615 25 50       27777 unless ($status) {
1616 0         0 $self->{error} = "gunzip failed : $GunzipError";
1617 0         0 return 0;
1618             }
1619 25         58 return $output;
1620             }
1621              
1622             # We check parameters recursively, but only when $bf->check_parameters is TRUE
1623             # ============================================================================
1624             sub _check_parameter {
1625 0     0   0 my $self = shift;
1626 0         0 my ($name, $parameter) = @_;
1627 0 0       0 unless (exists $self->{data_types}{$name}) {
1628 0         0 $self->{error} = "Unknown parameter '$name'";
1629 0         0 return 0;
1630             }
1631 0         0 my $def = $self->{data_types}{$name};
1632 0 0       0 if ($def->{type} eq 'HASH') {
    0          
    0          
    0          
1633 0 0       0 unless (ref($parameter) eq 'HASH') {
1634 0         0 $self->{error} = "Parameter '$name' should be a hashref";
1635 0         0 return 0;
1636             }
1637 0         0 my %fields = ((map {$_ => [1, 0]} @{$def->{required}}),
  0         0  
1638 0         0 (map {$_ => [0, 0]} @{$def->{allowed}}));
  0         0  
  0         0  
1639 0         0 while (my ($key, $value) = each %$parameter) {
1640 0 0       0 unless (exists $fields{$key}) {
1641 0         0 $self->{error} = "Invalid parameter '$key' in '$name'";
1642 0         0 return 0;
1643             }
1644             # Special cases - I hate putting these in, but if we are going to check
1645             # parameters, we ought to check them properly, even if it means spoiling
1646             # the abstraction of the checking subroutine. God I hate Betfair
1647 0         0 my $check_key = $key;
1648 0 0       0 if ($key eq 'instructions') {
1649 0         0 my ($prefix) = $name =~ m/^(.+)Orders$/;
1650 0         0 $check_key = $prefix.'Instructions';
1651             }
1652 0 0 0     0 if (($key eq 'from') or ($key eq 'to')) {
1653 0 0       0 if ($name eq 'transferFunds') {
1654 0         0 $check_key = $key.'Wallet';
1655             }
1656             }
1657 0 0       0 unless ($self->_check_parameter($check_key, $value)) {
1658             # DON'T set error - already set recursively
1659 0         0 return 0;
1660             }
1661 0         0 $fields{$key}[1]++;
1662             }
1663 0 0       0 if (my @missing = grep {($fields{$_}[0] == 1) and ($fields{$_}[1] == 0)}
  0 0       0  
1664             keys %fields){
1665 0 0       0 $self->{error} = "The following required parameter".
1666             (@missing == 1 ? " is" : "s are").
1667             " missing from '$name' - ";
1668 0         0 $self->{error} .= join(", ", @missing);
1669 0         0 return 0;
1670             }
1671 0 0       0 if (my @repeated = grep {($fields{$_}[1] > 1)}
  0         0  
1672             keys %fields){
1673 0 0       0 $self->{error} = "The following parameter".(@repeated == 1 ? " is" : "s are").
1674             " repeated in '$name' - ";
1675 0         0 $self->{error} .= join(", ", @repeated);
1676 0         0 return 0;
1677             }
1678             }
1679             elsif ($def->{type} eq 'ARRAY') {
1680 0 0       0 unless (ref($parameter) eq 'ARRAY') {
1681 0         0 $self->{error} = "Parameter '$name' should be an arrayref";
1682 0         0 return 0;
1683             }
1684 0 0       0 unless (@$parameter > 0) {
1685 0         0 $self->{error} = "parameter '$name' can't be an empty array";
1686 0         0 return 0;
1687             }
1688 0         0 my $key = $def->{array_of};
1689 0         0 foreach my $value (@$parameter) {
1690 0 0       0 unless ($self->_check_parameter($key, $value)) {
1691             # DON'T set error - already set recursively
1692 0         0 return 0;
1693             }
1694             }
1695             }
1696             elsif ($def->{type} eq 'ENUM') {
1697 0 0       0 if (my $type = ref($parameter)) {
1698 0         0 $type = lc($type);
1699 0         0 $self->{error} = "Parameter '$name' should be a scalar, not a reference to a";
1700 0 0       0 $self->{error} .= ($type eq 'array' ? 'n ' : ' ').$type;
1701 0         0 return 0;
1702             }
1703 0 0       0 unless (grep {$_ eq $parameter} @{$def->{allowed}}) {
  0         0  
  0         0  
1704 0         0 $self->{error} = "'$parameter' is not a valid value for '$name', ";
1705 0         0 $self->{error} .= "valid values are - ";
1706 0         0 $self->{error} .= join(", ", @{$def->{allowed}});
  0         0  
1707 0         0 return 0;
1708             }
1709             }
1710             elsif ($def->{type} eq 'SCALAR') {
1711 0 0       0 if (my $type = ref($parameter)) {
1712 0         0 $type = lc($type);
1713 0         0 $self->{error} = "Parameter '$name' should be a scalar, not a reference to a";
1714 0 0       0 $self->{error} .= ($type eq 'array' ? 'n ' : ' ').$type;
1715 0         0 return 0;
1716             }
1717 0 0       0 unless ($parameter =~ $def->{allowed}) {
1718 0         0 $self->{error} = "'$parameter' is not a valid value for '$name' - ";
1719 0         0 $self->{error} .= "valid values are of the form '".$def->{example}."'";
1720 0         0 return 0;
1721             }
1722             }
1723 0         0 return 1;
1724             }
1725              
1726             # If $bf->check_parameters is turned ON, we load the Betfair Data Type definitions
1727             # ================================================================================
1728             sub _load_data_types {
1729 1     1   1 my $self = shift;
1730              
1731             # Start with some basic data types
1732 1         16 my $long = {
1733             type => 'SCALAR',
1734             allowed => qr/^\d+$/,
1735             example => '123456789'
1736             };
1737 1         8 my $double = {
1738             type => 'SCALAR',
1739             allowed => qr/^[\d\.]+$/,
1740             example => '3.14'
1741             };
1742 1         7 my $integer = {
1743             type => 'SCALAR',
1744             allowed => qr/^\d+$/,
1745             example => '255'
1746             };
1747 1         6 my $string = {
1748             type => 'SCALAR',
1749             allowed => qr/^.+$/,
1750             example => 'Some Text'
1751             };
1752 1         6 my $boolean = {
1753             type => 'SCALAR',
1754             allowed => qr/^[01]$/,
1755             example => '0 or 1'
1756             };
1757 1         12 my $date = {
1758             type => 'SCALAR',
1759             allowed => qr/^\d\d\d\d-\d\d-\d\d[T ]\d\d:\d\dZ$/,
1760             example => '2007-04-05T14:30Z'
1761             };
1762              
1763              
1764              
1765             # main type_defs hash
1766 1         165 my $type_defs = {
1767              
1768             # simple types
1769             version => $long,
1770             amount => $double,
1771             fromRecord => $integer,
1772             recordCount => $integer,
1773             maxResults => $integer,
1774             customerRef => $string,
1775             appName => $string,
1776             textQuery => $string,
1777             venue => $string,
1778             exchangeId => $string, # for now, until this feature is implemented
1779             marketTypeCode => $string, # for now, until Betfair publish an Enum
1780             includeItemDescription => $boolean,
1781             includeSettledBets => $boolean,
1782             includeBspBets => $boolean,
1783             netOfCommission => $boolean,
1784             bspOnly => $boolean,
1785             turnInPlayEnabled => $boolean,
1786             inPlayOnly => $boolean,
1787             async => $boolean,
1788             includeOverallPosition => $boolean,
1789             partitionMatchedByStrategyRef => $boolean,
1790             from => $date,
1791             to => $date,
1792              
1793             # method names
1794             listCompetitions => {
1795             type => 'HASH',
1796             required => [qw/filter/],
1797             allowed => [qw/locale/],
1798             },
1799             listCountries => {
1800             type => 'HASH',
1801             required => [qw/filter/],
1802             allowed => [qw/locale/],
1803             },
1804             listCurrentOrders => {
1805             type => 'HASH',
1806             required => [qw//],
1807             allowed => [qw/betIds marketIds orderProjection dateRange
1808             customerOrderRefs customerStrategyRefs
1809             orderBy sortDir fromRecord recordCount/],
1810             },
1811             listClearedOrders => {
1812             type => 'HASH',
1813             required => [qw/betStatus/],
1814             allowed => [qw/eventTypeIds eventIds marketIds runnerIds
1815             betIds side settledDateRange groupBy locale
1816             customerOrderRefs customerStrategyRefs
1817             includeItemDescription fromRecord recordCount/],
1818             },
1819             listEvents => {
1820             type => 'HASH',
1821             required => [qw/filter/],
1822             allowed => [qw/locale/],
1823             },
1824             listEventTypes => {
1825             type => 'HASH',
1826             required => [qw/filter/],
1827             allowed => [qw/locale/],
1828             },
1829             listMarketBook => {
1830             type => 'HASH',
1831             required => [qw/marketIds/],
1832             allowed => [qw/priceProjection orderProjection matchProjection
1833             includeOverallPosition partitionMatchedByStrategyRef
1834             customerOrderRefs customerStrategyRefs
1835             currencyCode locale/],
1836             },
1837             listMarketCatalogue => {
1838             type => 'HASH',
1839             required => [qw/filter maxResults/],
1840             allowed => [qw/marketProjection sort locale/],
1841             },
1842             listMarketProfitAndLoss => {
1843             type => 'HASH',
1844             required => [qw/marketIds/],
1845             allowed => [qw/includeSettledBets includeBspBets
1846             netOfCommission/],
1847             },
1848             listMarketTypes => {
1849             type => 'HASH',
1850             required => [qw/filter/],
1851             allowed => [qw/locale/],
1852             },
1853             listTimeRanges => {
1854             type => 'HASH',
1855             required => [qw/filter granularity/],
1856             allowed => [qw//],
1857             },
1858             listVenues => {
1859             type => 'HASH',
1860             required => [qw/filter/],
1861             allowed => [qw/locale/],
1862             },
1863             placeOrders => {
1864             type => 'HASH',
1865             required => [qw/marketId instructions/],
1866             allowed => [qw/customerRef marketVersion
1867             customerOrderRef customerStrategyRef async/],
1868             },
1869             cancelOrders => {
1870             type => 'HASH',
1871             required => [qw//],
1872             allowed => [qw/marketId instructions customerRef/],
1873             },
1874             replaceOrders => {
1875             type => 'HASH',
1876             required => [qw/marketId instructions/],
1877             allowed => [qw/customerRef marketVersion async/],
1878             },
1879             updateOrders => {
1880             type => 'HASH',
1881             required => [qw/marketId instructions/],
1882             allowed => [qw/customerRef/],
1883             },
1884             createDeveloperAppKeys => {
1885             type => 'HASH',
1886             required => [qw/appName/],
1887             allowed => [qw//],
1888             },
1889             getAccountDetails => {
1890             type => 'HASH',
1891             required => [qw//],
1892             allowed => [qw//],
1893             },
1894             getAccountFunds => {
1895             type => 'HASH',
1896             required => [qw//],
1897             allowed => [qw/wallet/],
1898             },
1899             getDeveloperAppKeys => {
1900             type => 'HASH',
1901             required => [qw//],
1902             allowed => [qw//],
1903             },
1904             getAccountStatement => {
1905             type => 'HASH',
1906             required => [qw//],
1907             allowed => [qw/locale fromRecord recordCount itemDateRange
1908             includeItem wallet/],
1909             },
1910             listCurrencyRates => {
1911             type => 'HASH',
1912             required => [qw//],
1913             allowed => [qw/fromCurrency/],
1914             },
1915             transferFunds => {
1916             type => 'HASH',
1917             required => [qw/from to amount/],
1918             allowed => [qw//],
1919             },
1920              
1921             heartbeat => {
1922             type => 'HASH',
1923             required => [qw/preferredTimeoutSeconds/],
1924             allowed => [qw//],
1925             },
1926              
1927             listRaceDetails => {
1928             type => 'HASH',
1929             required => [qw//],
1930             allowed => [qw/meetingIds raceIds/],
1931             },
1932              
1933             # arrays
1934             betIds => {
1935             type => 'ARRAY',
1936             array_of => 'betId',
1937             },
1938             marketIds => {
1939             type => 'ARRAY',
1940             array_of => 'marketId',
1941             },
1942             eventTypeIds => {
1943             type => 'ARRAY',
1944             array_of => 'eventTypeId',
1945             },
1946             eventIds => {
1947             type => 'ARRAY',
1948             array_of => 'eventId',
1949             },
1950             runnerIds => {
1951             type => 'ARRAY',
1952             array_of => 'runnerId',
1953             },
1954             marketProjection => {
1955             type => 'ARRAY',
1956             array_of => 'MarketProjection',
1957             },
1958             placeInstructions => {
1959             type => 'ARRAY',
1960             array_of => 'PlaceInstruction',
1961             },
1962             cancelInstructions => {
1963             type => 'ARRAY',
1964             array_of => 'CancelInstruction',
1965             },
1966             replaceInstructions => {
1967             type => 'ARRAY',
1968             array_of => 'ReplaceInstruction',
1969             },
1970             updateInstructions => {
1971             type => 'ARRAY',
1972             array_of => 'UpdateInstruction',
1973             },
1974             exchangeIds => {
1975             type => 'ARRAY',
1976             array_of => 'exchangeId',
1977             },
1978             competitionIds => {
1979             type => 'ARRAY',
1980             array_of => 'competitionId',
1981             },
1982             venues => {
1983             type => 'ARRAY',
1984             array_of => 'venue',
1985             },
1986             marketBettingTypes => {
1987             type => 'ARRAY',
1988             array_of => 'MarketBettingType',
1989             },
1990             marketCountries => {
1991             type => 'ARRAY',
1992             array_of => 'country',
1993             },
1994             marketTypeCodes => {
1995             type => 'ARRAY',
1996             array_of => 'marketTypeCode',
1997             },
1998             withOrders => {
1999             type => 'ARRAY',
2000             array_of => 'OrderStatus',
2001             },
2002             meetingIds => {
2003             type => 'ARRAY',
2004             array_of => 'meetingId',
2005             },
2006             raceIds => {
2007             type => 'ARRAY',
2008             array_of => 'raceId',
2009             },
2010             customerStrategyRefs => {
2011             type => 'ARRAY',
2012             array_of => 'customerStrategyRef',
2013             },
2014             customerOrderRefs => {
2015             type => 'ARRAY',
2016             array_of => 'customerOrderRef',
2017             },
2018             };
2019              
2020             # Common scalars
2021             $type_defs->{locale} = {
2022 1         10 type => 'SCALAR',
2023             allowed => qr/^[A-Z]{2}$/,
2024             example => 'GB'
2025             };
2026 1         3 $type_defs->{country} = $type_defs->{locale};
2027             $type_defs->{betId} = {
2028 1         8 type => 'SCALAR',
2029             allowed => qr/^\d{10,15}$/,
2030             example => '42676999999'
2031             };
2032             $type_defs->{marketId} = {
2033 1         5 type => 'SCALAR',
2034             allowed => qr/[12]\.\d+$/,
2035             example => '1.116099999'
2036             };
2037             $type_defs->{eventTypeId} = {
2038 1         5 type => 'SCALAR',
2039             allowed => qr/^\d{1,20}$/,
2040             example => '7'
2041             };
2042             $type_defs->{eventId} = {
2043 1         4 type => 'SCALAR',
2044             allowed => qr/^\d{8,10}$/,
2045             example => '27292599'
2046             };
2047             $type_defs->{runnerId} = {
2048 1         4 type => 'SCALAR',
2049             allowed => qr/^\d{1,10}$/,
2050             example => '6750999'
2051             };
2052             $type_defs->{currencyCode} = {
2053 1         5 type => 'SCALAR',
2054             allowed => qr/^[A-Z]{3}$/,
2055             example => 'GBP'
2056             };
2057 1         2 $type_defs->{fromCurrency} = $type_defs->{currencyCode};
2058             $type_defs->{competitionId} = {
2059 1         4 type => 'SCALAR',
2060             allowed => qr/^\d{1,10}$/,
2061             example => '409999'
2062             };
2063             $type_defs->{preferredTimeoutSeconds} = {
2064 1         5 type => 'SCALAR',
2065             allowed => qr/^\d{1,3}$/,
2066             example => '180'
2067             };
2068 1         2 $type_defs->{meetingId} = $type_defs->{eventId};
2069             $type_defs->{raceId} = {
2070 1         5 type => 'SCALAR',
2071             allowed => qr/^\d{8,10}\.(0[0-9]|1[0-9]|2[0-3])[0-5][0-9]$/,
2072             example => '27292599.1430'
2073             };
2074             $type_defs->{customerStrategyRef} = {
2075 1         8 type => 'SCALAR',
2076             allowed => qr/^\w{1,15}$/,
2077             example => 'SVM_Place_01'
2078             };
2079             $type_defs->{customerOrderRef} = {
2080 1         5 type => 'SCALAR',
2081             allowed => qr/^\w{1,32}$/,
2082             example => 'ORD_42251b'
2083             };
2084              
2085              
2086             # betfair data types (all the following pod is still inside the _load_data_types sub)
2087             # each type and any sub-types are loaded into the hash following their pod entry.
2088              
2089              
2090             =head1 BETFAIR DATA TYPES
2091              
2092             This is an alphabetical list of all the data types defined by Betfair. It includes
2093             enumerations, which are just sets of allowable string values. Higher level types may
2094             contain lower level types, which can be followed down until simple scalars are
2095             reached. Some elements of complex data types are required, while others are optional -
2096             these are denoted by RQD and OPT respectively. Simple scalar type definitions (Long,
2097             Double, Integer, String, Boolean, Date) have been retained for convenience. 'Date' is
2098             a string in ISO 8601 format (e.g. '2007-04-05T14:30Z').
2099              
2100             =head3 ActionPerformed
2101              
2102             Enumeration
2103              
2104             NONE No action was performed since last heartbeat
2105             CANCELLATION_REQUEST_SUBMITTED A request to cancel all unmatched bets was submitted
2106             ALL_BETS_CANCELLED All unmatched bets were cancelled since last heartbeat
2107             SOME_BETS_NOT_CANCELLED Not all unmatched bets were cancelled
2108             CANCELLATION_REQUEST_ERROR There was an error requesting cancellation
2109             CANCELLATION_STATUS_UNKNOWN There was no response from requesting cancellation
2110              
2111             =head3 BetStatus
2112              
2113             Enumeration
2114              
2115             SETTLED A matched bet that was settled normally.
2116             VOIDED A matched bet that was subsequently voided by Betfair.
2117             LAPSED Unmatched bet that was cancelled by Betfair (for example at turn in play).
2118             CANCELLED Unmatched bet that was cancelled by an explicit customer action.
2119              
2120             =cut
2121              
2122             $type_defs->{BetStatus} = {
2123 1         7 type => 'ENUM',
2124             allowed => [qw/SETTLED VOIDED LAPSED CANCELLED/],
2125             };
2126 1         1 $type_defs->{betStatus} = $type_defs->{BetStatus};
2127              
2128             =head3 BetTargetType
2129              
2130             Enumeration
2131              
2132              
2133             BACKERS_PROFIT The payout requested minus the size at which this LimitOrder is to be placed.
2134             PAYOUT The total payout requested on a LimitOrder.
2135              
2136             =cut
2137              
2138             $type_defs->{BetTargetType} = {
2139 1         4 type => 'ENUM',
2140             allowed => [qw/BACKERS_PROFIT PAYOUT/],
2141             };
2142 1         1 $type_defs->{betTargetType} = $type_defs->{BetTargetType};
2143              
2144             =head3 CancelInstruction
2145              
2146             betId String RQD
2147             sizeReduction Double OPT
2148              
2149             =cut
2150              
2151             $type_defs->{CancelInstruction} = {
2152 1         3 type => 'HASH',
2153             required => [qw/betId/],
2154             allowed => [qw/sizeReduction/],
2155             };
2156 1         1 $type_defs->{sizeReduction} = $double;
2157              
2158             =head3 CancelInstructionReport
2159              
2160             status InstructionReportStatus
2161             errorCode InstructionReportErrorCode
2162             instruction CancelInstruction
2163             sizeCancelled Double
2164             cancelledDate Date
2165              
2166             =head3 ClearedOrderSummary
2167              
2168             eventTypeId String
2169             eventId String
2170             marketId String
2171             selectionId Long
2172             handicap Double
2173             betId String
2174             placedDate Date
2175             persistenceType PersistenceType
2176             orderType OrderType
2177             side Side
2178             itemDescription ItemDescription
2179             priceRequested Double
2180             settledDate Date
2181             betCount Integer
2182             commission Double
2183             priceMatched Double
2184             priceReduced Boolean
2185             sizeSettled Double
2186             profit Double
2187             sizeCancelled Double
2188             lastMatchedDate Date
2189             betOutcome String
2190              
2191             =head3 Competition
2192              
2193             id String
2194             name String
2195              
2196             =head3 CompetitionResult
2197              
2198             competition Competition
2199             marketCount Integer
2200             competitionRegion String
2201              
2202             =head3 CountryCodeResult
2203              
2204             countryCode String
2205             marketCount Integer
2206              
2207             =head3 CurrencyRate
2208              
2209             currencyCode String (Three letter ISO 4217 code)
2210             rate Double
2211              
2212             =head3 CurrentOrderSummary
2213              
2214             betId String
2215             marketId String
2216             selectionId Long
2217             handicap Double
2218             priceSize PriceSize
2219             bspLiability Double
2220             side Side
2221             status OrderStatus
2222             persistenceType PersistenceType
2223             orderType OrderType
2224             placedDate Date
2225             matchedDate Date
2226             averagePriceMatched Double
2227             sizeMatched Double
2228             sizeRemaining Double
2229             sizeLapsed Double
2230             sizeCancelled Double
2231             sizeVoided Double
2232             regulatorAuthCode String
2233             regulatorCode String
2234              
2235             =head3 DeveloperApp
2236              
2237             appName String
2238             appId Long
2239             appVersions Array of DeveloperAppVersion
2240              
2241             =head3 DeveloperAppVersion
2242              
2243             owner String
2244             versionId Long
2245             version String
2246             applicationKey String
2247             delayData Boolean
2248             subscriptionRequired Boolean
2249             ownerManaged Boolean
2250             active Boolean
2251              
2252             =head3 Event
2253              
2254             id String
2255             name String
2256             countryCode String
2257             timezone String
2258             venue String
2259             openDate Date
2260              
2261             =head3 EventResult
2262              
2263             event Event
2264             marketCount Integer
2265              
2266             =head3 EventType
2267              
2268             id String
2269             name String
2270              
2271             =head3 EventTypeResult
2272              
2273             eventType EventType
2274             marketCount Integer
2275              
2276             =head3 ExBestOffersOverrides
2277              
2278             bestPricesDepth Integer OPT
2279             rollupModel RollupModel OPT
2280             rollupLimit Integer OPT
2281             rollupLiabilityThreshold Double OPT
2282             rollupLiabilityFactor Integer OPT
2283              
2284             =cut
2285              
2286             $type_defs->{ExBestOffersOverrides} = {
2287 1         3 type => 'HASH',
2288             required => [qw//],
2289             allowed => [qw/bestPricesDepth rollupModel rollupLimit
2290             rollupLiabilityThreshold rollupLiabilityFactor/],
2291             };
2292 1         2 $type_defs->{bestPricesDepth} = $integer;
2293 1         1 $type_defs->{rollupLimit} = $integer;
2294 1         2 $type_defs->{rollupLiabilityThreshold} = $double;
2295 1         1 $type_defs->{rollupLiabilityFactor} = $integer;
2296 1         2 $type_defs->{exBestOffersOverrides} = $type_defs->{ExBestOffersOverrides};
2297              
2298             =head3 ExchangePrices
2299              
2300             availableToBack Array of PriceSize
2301             availableToLay Array of PriceSize
2302             tradedVolume Array of PriceSize
2303              
2304             =head3 ExecutionReportErrorCode
2305              
2306             Enumeration
2307              
2308             ERROR_IN_MATCHER The matcher is not healthy.
2309             PROCESSED_WITH_ERRORS The order itself has been accepted, but at least one action has generated errors.
2310             BET_ACTION_ERROR There is an error with an action that has caused the entire order to be rejected.
2311             INVALID_ACCOUNT_STATE Order rejected due to the account's status (suspended, inactive, dup cards).
2312             INVALID_WALLET_STATUS Order rejected due to the account's wallet's status.
2313             INSUFFICIENT_FUNDS Account has exceeded its exposure limit or available to bet limit.
2314             LOSS_LIMIT_EXCEEDED The account has exceed the self imposed loss limit.
2315             MARKET_SUSPENDED Market is suspended.
2316             MARKET_NOT_OPEN_FOR_BETTING Market is not open for betting. It is either not yet active, suspended or closed.
2317             DUPLICATE_TRANSACTION duplicate customer reference data submitted.
2318             INVALID_ORDER Order cannot be accepted by the matcher due to the combination of actions.
2319             INVALID_MARKET_ID Market doesn't exist.
2320             PERMISSION_DENIED Business rules do not allow order to be placed.
2321             DUPLICATE_BETIDS duplicate bet ids found.
2322             NO_ACTION_REQUIRED Order hasn't been passed to matcher as system detected there will be no change.
2323             SERVICE_UNAVAILABLE The requested service is unavailable.
2324             REJECTED_BY_REGULATOR The regulator rejected the order.
2325              
2326             =head3 ExecutionReportStatus
2327              
2328             Enumeration
2329              
2330             SUCCESS Order processed successfully.
2331             FAILURE Order failed.
2332             PROCESSED_WITH_ERRORS The order itself has been accepted, but at least one action has generated errors.
2333             TIMEOUT Order timed out.
2334              
2335             =head3 GroupBy
2336              
2337             Enumeration
2338              
2339             EVENT_TYPE A roll up on a specified event type.
2340             EVENT A roll up on a specified event.
2341             MARKET A roll up on a specified market.
2342             SIDE An averaged roll up on the specified side of a specified selection.
2343             BET The P&L, commission paid, side and regulatory information etc, about each individual bet order
2344              
2345             =cut
2346              
2347             $type_defs->{GroupBy} = {
2348 1         3 type => 'ENUM',
2349             allowed => [qw/EVENT_TYPE EVENT MARKET SIDE BET/],
2350             };
2351 1         2 $type_defs->{groupBy} = $type_defs->{GroupBy};
2352              
2353             =head3 IncludeItem
2354              
2355             Enumeration
2356              
2357             ALL Include all items.
2358             DEPOSITS_WITHDRAWALS Include payments only.
2359             EXCHANGE Include exchange bets only.
2360             POKER_ROOM include poker transactions only.
2361              
2362             =cut
2363              
2364             $type_defs->{IncludeItem} = {
2365 1         2 type => 'ENUM',
2366             allowed => [qw/ALL DEPOSITS_WITHDRAWALS EXCHANGE POKER_ROOM/],
2367             };
2368 1         2 $type_defs->{includeItem} = $type_defs->{IncludeItem};
2369              
2370             =head3 InstructionReportErrorCode
2371              
2372             Enumeration
2373              
2374             INVALID_BET_SIZE Bet size is invalid for your currency or your regulator.
2375             INVALID_RUNNER Runner does not exist, includes vacant traps in greyhound racing.
2376             BET_TAKEN_OR_LAPSED Bet cannot be cancelled or modified as it has already been taken or has lapsed.
2377             BET_IN_PROGRESS No result was received from the matcher in a timeout configured for the system.
2378             RUNNER_REMOVED Runner has been removed from the event.
2379             MARKET_NOT_OPEN_FOR_BETTING Attempt to edit a bet on a market that has closed.
2380             LOSS_LIMIT_EXCEEDED The action has caused the account to exceed the self imposed loss limit.
2381             MARKET_NOT_OPEN_FOR_BSP_BETTING Market now closed to bsp betting. Turned in-play or has been reconciled.
2382             INVALID_PRICE_EDIT Attempt to edit down a bsp limit on close lay bet, or edit up a back bet.
2383             INVALID_ODDS Odds not on price ladder - either edit or placement.
2384             INSUFFICIENT_FUNDS Insufficient funds available to cover the bet action.
2385             INVALID_PERSISTENCE_TYPE Invalid persistence type for this market.
2386             ERROR_IN_MATCHER A problem with the matcher prevented this action completing successfully
2387             INVALID_BACK_LAY_COMBINATION The order contains a back and a lay for the same runner at overlapping prices.
2388             ERROR_IN_ORDER The action failed because the parent order failed.
2389             INVALID_BID_TYPE Bid type is mandatory.
2390             INVALID_BET_ID Bet for id supplied has not been found.
2391             CANCELLED_NOT_PLACED Bet cancelled but replacement bet was not placed.
2392             RELATED_ACTION_FAILED Action failed due to the failure of a action on which this action is dependent.
2393             NO_ACTION_REQUIRED The action does not result in any state change.
2394              
2395             =head3 InstructionReportStatus
2396              
2397             Enumeration
2398              
2399             SUCCESS Action succeeded.
2400             FAILURE Action failed.
2401             TIMEOUT Action Timed out.
2402              
2403             =head3 ItemClass
2404              
2405             Enumeration
2406              
2407             UNKNOWN Statement item not mapped to a specific class.
2408              
2409             =head3 ItemDescription
2410              
2411             eventTypeDesc String
2412             eventDesc String
2413             marketDesc String
2414             marketStartTime Date
2415             runnerDesc String
2416             numberOfWinners Integer
2417             marketType String
2418             eachWayDivisor Double
2419              
2420             =head3 LimitOnCloseOrder
2421              
2422             liability Double REQ
2423             price Double REQ
2424              
2425             =cut
2426              
2427             $type_defs->{LimitOnCloseOrder} = {
2428 1         3 type => 'HASH',
2429             required => [qw/liability price/],
2430             allowed => [qw//],
2431             };
2432 1         2 $type_defs->{liability} = $double;
2433 1         1 $type_defs->{price} = $double;
2434 1         2 $type_defs->{limitOnCloseOrder} = $type_defs->{LimitOnCloseOrder};
2435              
2436             =head3 LimitOrder
2437              
2438             size Double REQ/OPT*
2439             price Double REQ
2440             persistenceType PersistenceType REQ
2441             timeInForce TimeInForce OPT
2442             minFillSize Double OPT
2443             betTargetType BetTargetType OPT/REQ*
2444             betTargetSize Double OPT/REQ*
2445              
2446             * Must specify EITHER size OR target type and target size
2447              
2448             =cut
2449              
2450             $type_defs->{LimitOrder} = {
2451 1         6 type => 'HASH',
2452             required => [qw/price persistenceType/],
2453             allowed => [qw/size timeInForce minFillSize betTargetType betTargetSize/],
2454             };
2455 1         1 $type_defs->{size} = $double;
2456 1         4 $type_defs->{minFillSize} = $double;
2457 1         3 $type_defs->{betTargetSize} = $double;
2458 1         1 $type_defs->{limitOrder} = $type_defs->{LimitOrder};
2459              
2460             =head3 MarketBettingType
2461              
2462             Enumeration
2463              
2464             ODDS Odds Market.
2465             LINE Line Market.
2466             RANGE Range Market.
2467             ASIAN_HANDICAP_DOUBLE_LINE Asian Handicap Market.
2468             ASIAN_HANDICAP_SINGLE_LINE Asian Single Line Market.
2469             FIXED_ODDS Sportsbook Odds Market.
2470              
2471             =cut
2472              
2473             $type_defs->{MarketBettingType} = {
2474 1         7 type => 'ENUM',
2475             allowed => [qw/ODDS LINE RANGE ASIAN_HANDICAP_DOUBLE_LINE
2476             ASIAN_HANDICAP_SINGLE_LINE FIXED_ODDS/],
2477             };
2478              
2479             =head3 MarketBook
2480              
2481             marketId String
2482             isMarketDataDelayed Boolean
2483             status MarketStatus
2484             betDelay Integer
2485             bspReconciled Boolean
2486             complete Boolean
2487             inplay Boolean
2488             numberOfWinners Integer
2489             numberOfRunners Integer
2490             numberOfActiveRunners Integer
2491             lastMatchTime Date
2492             totalMatched Double
2493             totalAvailable Double
2494             crossMatching Boolean
2495             runnersVoidable Boolean
2496             version Long
2497             runners Array of Runner
2498              
2499             =head3 MarketCatalogue
2500              
2501             marketId String
2502             marketName String
2503             marketStartTime Date
2504             description MarketDescription
2505             totalMatched Double
2506             runners Array of RunnerCatalog
2507             eventType EventType
2508             competition Competition
2509             event Event
2510              
2511             =head3 MarketDescription
2512              
2513             persistenceEnabled Boolean
2514             bspMarket Boolean
2515             marketTime Date
2516             suspendTime Date
2517             settleTime Date
2518             bettingType MarketBettingType
2519             turnInPlayEnabled Boolean
2520             marketType String
2521             regulator String
2522             marketBaseRate Double
2523             discountAllowed Boolean
2524             wallet String
2525             rules String
2526             rulesHasDate Boolean
2527             eachWayDivisor Double
2528             clarifications String
2529              
2530             =head3 MarketFilter
2531              
2532             textQuery String OPT
2533             exchangeIds Array of String OPT
2534             eventTypeIds Array of String OPT
2535             eventIds Array of String OPT
2536             competitionIds Array of String OPT
2537             marketIds Array of String OPT
2538             venues Array of String OPT
2539             bspOnly Boolean OPT
2540             turnInPlayEnabled Boolean OPT
2541             inPlayOnly Boolean OPT
2542             marketBettingTypes Array of MarketBettingType OPT
2543             marketCountries Array of String OPT
2544             marketTypeCodes Array of String OPT
2545             marketStartTime TimeRange OPT
2546             withOrders Array of OrderStatus OPT
2547              
2548             =cut
2549              
2550             $type_defs->{MarketFilter} = {
2551 1         5 type => 'HASH',
2552             required => [qw//],
2553             allowed => [qw/textQuery exchangeIds eventTypeIds eventIds
2554             competitionIds marketIds venues bspOnly
2555             turnInPlayEnabled inPlayOnly marketBettingTypes
2556             marketCountries marketTypeCodes marketStartTime
2557             withOrders/],
2558             };
2559 1         1 $type_defs->{filter} = $type_defs->{MarketFilter};
2560              
2561             =head3 MarketOnCloseOrder
2562              
2563             liability Double REQ
2564              
2565             =cut
2566              
2567             $type_defs->{MarketOnCloseOrder} = {
2568 1         5 type => 'HASH',
2569             required => [qw/liability/],
2570             allowed => [qw//],
2571             };
2572 1         2 $type_defs->{marketOnCloseOrder} = $type_defs->{MarketOnCloseOrder};
2573              
2574             =head3 MarketProfitAndLoss
2575              
2576             marketId String
2577             commissionApplied Double
2578             profitAndLosses Array of RunnerProfitAndLoss
2579              
2580             =head3 MarketProjection
2581              
2582             Enumeration
2583              
2584             COMPETITION If not selected then the competition will not be returned with marketCatalogue.
2585             EVENT If not selected then the event will not be returned with marketCatalogue.
2586             EVENT_TYPE If not selected then the eventType will not be returned with marketCatalogue.
2587             MARKET_START_TIME If not selected then the start time will not be returned with marketCatalogue.
2588             MARKET_DESCRIPTION If not selected then the description will not be returned with marketCatalogue.
2589             RUNNER_DESCRIPTION If not selected then the runners will not be returned with marketCatalogue.
2590             RUNNER_METADATA If not selected then the runner metadata will not be returned with marketCatalogue.
2591              
2592             =cut
2593              
2594             $type_defs->{MarketProjection} = {
2595 1         4 type => 'ENUM',
2596             allowed => [qw/COMPETITION EVENT EVENT_TYPE MARKET_START_TIME
2597             MARKET_DESCRIPTION RUNNER_DESCRIPTION RUNNER_METADATA/],
2598             };
2599              
2600             =head3 MarketSort
2601              
2602             Enumeration
2603              
2604             MINIMUM_TRADED Minimum traded volume
2605             MAXIMUM_TRADED Maximum traded volume
2606             MINIMUM_AVAILABLE Minimum available to match
2607             MAXIMUM_AVAILABLE Maximum available to match
2608             FIRST_TO_START The closest markets based on their expected start time
2609             LAST_TO_START The most distant markets based on their expected start time
2610              
2611             =cut
2612              
2613             $type_defs->{MarketSort} = {
2614 1         3 type => 'ENUM',
2615             allowed => [qw/MINIMUM_TRADED MAXIMUM_TRADED MINIMUM_AVAILABLE
2616             MAXIMUM_AVAILABLE FIRST_TO_START LAST_TO_START/],
2617             };
2618 1         1 $type_defs->{sort} = $type_defs->{MarketSort};
2619              
2620             =head3 MarketStatus
2621              
2622             Enumeration
2623              
2624             INACTIVE Inactive Market
2625             OPEN Open Market
2626             SUSPENDED Suspended Market
2627             CLOSED Closed Market
2628              
2629             =cut
2630              
2631             $type_defs->{MarketStatus} = {
2632 1         3 type => 'ENUM',
2633             allowed => [qw/INACTIVE OPEN SUSPENDED CLOSED/],
2634             };
2635              
2636             =head3 MarketTypeResult
2637              
2638             marketType String
2639             marketCount Integer
2640              
2641             =head3 MarketVersion
2642              
2643             version Long REQ
2644              
2645             =cut
2646              
2647             $type_defs->{MarketVersion} = {
2648 1         2 type => 'HASH',
2649             required => [qw/version/],
2650             allowed => [qw//],
2651             };
2652 1         2 $type_defs->{marketVersion} = $type_defs->{MarketVersion};
2653              
2654             =head3 Match
2655              
2656             betId String
2657             matchId String
2658             side Side
2659             price Double
2660             size Double
2661             matchDate Date
2662              
2663             =head3 MatchProjection
2664              
2665             Enumeration
2666              
2667             NO_ROLLUP No rollup, return raw fragments.
2668             ROLLED_UP_BY_PRICE Rollup matched amounts by distinct matched prices per side.
2669             ROLLED_UP_BY_AVG_PRICE Rollup matched amounts by average matched price per side.
2670              
2671             =cut
2672              
2673             $type_defs->{MatchProjection} = {
2674 1         3 type => 'ENUM',
2675             allowed => [qw/NO_ROLLUP ROLLED_UP_BY_PRICE ROLLED_UP_BY_AVG_PRICE/],
2676             };
2677 1         2 $type_defs->{matchProjection} = $type_defs->{MatchProjection};
2678              
2679             =head3 Order
2680              
2681             betId String
2682             orderType OrderType
2683             status OrderStatus
2684             persistenceType PersistenceType
2685             side Side
2686             price Double
2687             size Double
2688             bspLiability Double
2689             placedDate Date
2690             avgPriceMatched Double
2691             sizeMatched Double
2692             sizeRemaining Double
2693             sizeLapsed Double
2694             sizeCancelled Double
2695             sizeVoided Double
2696              
2697             =head3 OrderBy
2698              
2699             Enumeration
2700              
2701             BY_BET Deprecated Use BY_PLACE_TIME instead. Order by placed time, then bet id.
2702             BY_MARKET Order by market id, then placed time, then bet id.
2703             BY_MATCH_TIME Order by time of last matched fragment (if any), then placed time, then bet id.
2704             BY_PLACE_TIME Order by placed time, then bet id. This is an alias of to be deprecated BY_BET.
2705             BY_SETTLED_TIME Order by time of last settled fragment, last match time, placed time, bet id.
2706             BY_VOID_TIME Order by time of last voided fragment, last match time, placed time, bet id.
2707              
2708             =cut
2709              
2710             $type_defs->{OrderBy} = {
2711 1         3 type => 'ENUM',
2712             allowed => [qw/BY_BET BY_MARKET BY_MATCH_TIME BY_PLACE_TIME
2713             BY_SETTLED_TIME BY_VOID_TIME/],
2714             };
2715 1         1 $type_defs->{orderBy} = $type_defs->{OrderBy};
2716              
2717             =head3 OrderProjection
2718              
2719             Enumeration
2720              
2721             ALL EXECUTABLE and EXECUTION_COMPLETE orders.
2722             EXECUTABLE An order that has a remaining unmatched portion.
2723             EXECUTION_COMPLETE An order that does not have any remaining unmatched portion.
2724              
2725             =cut
2726              
2727             $type_defs->{OrderProjection} = {
2728 1         3 type => 'ENUM',
2729             allowed => [qw/ALL EXECUTABLE EXECUTION_COMPLETE/],
2730             };
2731 1         2 $type_defs->{orderProjection} = $type_defs->{OrderProjection};
2732              
2733             =head3 OrderStatus
2734              
2735             Enumeration
2736              
2737             PENDING An asynchronous order is yet to be processed. NOT A VALID SEARCH CRITERIA.
2738             EXECUTION_COMPLETE An order that does not have any remaining unmatched portion.
2739             EXECUTABLE An order that has a remaining unmatched portion.
2740             EXPIRED Unfilled FILL_OR_KILL order. NOT A VALID SEARCH CRITERIA.
2741              
2742             =cut
2743              
2744             $type_defs->{OrderStatus} = {
2745 1         2 type => 'ENUM',
2746             allowed => [qw/EXECUTION_COMPLETE EXECUTABLE/],
2747             };
2748              
2749             =head3 OrderType
2750              
2751             Enumeration
2752              
2753             LIMIT A normal exchange limit order for immediate execution.
2754             LIMIT_ON_CLOSE Limit order for the auction (SP).
2755             MARKET_ON_CLOSE Market order for the auction (SP).
2756              
2757             =cut
2758              
2759             $type_defs->{OrderType} = {
2760 1         11 type => 'ENUM',
2761             allowed => [qw/LIMIT LIMIT_ON_CLOSE MARKET_ON_CLOSE/],
2762             };
2763 1         2 $type_defs->{orderType} = $type_defs->{OrderType};
2764              
2765             =head3 PersistenceType
2766              
2767             Enumeration
2768              
2769             LAPSE Lapse the order when the market is turned in-play.
2770             PERSIST Persist the order to in-play.
2771             MARKET_ON_CLOSE Put the order into the auction (SP) at turn-in-play.
2772              
2773             =cut
2774              
2775             $type_defs->{PersistenceType} = {
2776 1         6 type => 'ENUM',
2777             allowed => [qw/LAPSE PERSIST MARKET_ON_CLOSE/],
2778             };
2779 1         2 $type_defs->{persistenceType} = $type_defs->{PersistenceType};
2780 1         1 $type_defs->{newPersistenceType} = $type_defs->{PersistenceType};
2781              
2782             =head3 PlaceInstruction
2783              
2784             orderType OrderType RQD
2785             selectionId Long RQD
2786             handicap Double OPT
2787             side Side RQD
2788             limitOrder LimitOrder OPT/RQD \
2789             limitOnCloseOrder LimitOnCloseOrder OPT/RQD > Depending on OrderType
2790             marketOnCloseOrder MarketOnCloseOrder OPT/RQD /
2791              
2792             =cut
2793              
2794             $type_defs->{PlaceInstruction} = {
2795 1         4 type => 'HASH',
2796             required => [qw/orderType selectionId side/],
2797             allowed => [qw/handicap limitOrder limitOnCloseOrder
2798             marketOnCloseOrder customerOrderRef/],
2799             };
2800 1         2 $type_defs->{selectionId} = $type_defs->{runnerId};
2801 1         1 $type_defs->{handicap} = $double;
2802              
2803             =head3 PlaceInstructionReport
2804              
2805             status InstructionReportStatus
2806             errorCode InstructionReportErrorCode
2807             instruction PlaceInstruction
2808             betId String
2809             placedDate Date
2810             averagePriceMatched Double
2811             sizeMatched Double
2812              
2813             =head3 PriceData
2814              
2815             Enumeration
2816              
2817             SP_AVAILABLE Amount available for the BSP auction.
2818             SP_TRADED Amount traded in the BSP auction.
2819             EX_BEST_OFFERS Only the best prices available for each runner, to requested price depth.
2820             EX_ALL_OFFERS EX_ALL_OFFERS trumps EX_BEST_OFFERS if both settings are present.
2821             EX_TRADED Amount traded on the exchange.
2822              
2823             =cut
2824              
2825             $type_defs->{PriceData} = {
2826 1         4 type => 'ENUM',
2827             allowed => [qw/SP_AVAILABLE SP_TRADED EX_BEST_OFFERS EX_ALL_OFFERS
2828             EX_TRADED/],
2829             };
2830              
2831             =head3 PriceProjection
2832              
2833             priceData Array of PriceData OPT
2834             exBestOffersOverrides ExBestOffersOverrides OPT
2835             virtualise Boolean OPT
2836             rolloverStakes Boolean OPT
2837              
2838             =cut
2839              
2840             $type_defs->{PriceProjection} = {
2841 1         2 type => 'HASH',
2842             required => [qw//],
2843             allowed => [qw/priceData exBestOffersOverrides virtualise rolloverStakes/],
2844             };
2845             $type_defs->{priceData} = {
2846 1         1 type => 'ARRAY',
2847             array_of => 'PriceData',
2848             };
2849 1         2 $type_defs->{virtualise} = $boolean;
2850 1         2 $type_defs->{rolloverStakes} = $boolean;
2851 1         1 $type_defs->{priceProjection} = $type_defs->{PriceProjection};
2852              
2853             =head3 PriceSize
2854              
2855             price Double
2856             size Double
2857              
2858             =head3 ReplaceInstruction
2859              
2860             betId String RQD
2861             newPrice Double RQD
2862              
2863             =cut
2864              
2865             $type_defs->{ReplaceInstruction} = {
2866 1         3 type => 'HASH',
2867             required => [qw/betId newPrice/],
2868             allowed => [qw//],
2869             };
2870 1         1 $type_defs->{newPrice} = $double;
2871              
2872             =head3 RaceDetails
2873              
2874             meetingId String
2875             raceId String
2876             raceStatus RaceStatus
2877             lastUpdated Date
2878             responseCode ResponseCode
2879              
2880             =head3 RaceStatus
2881              
2882             Enumeration
2883              
2884             DORMANT There is no data available for this race
2885             DELAYED The start of the race has been delayed
2886             PARADING The horses are in the parade ring
2887             GOINGDOWN The horses are going down to the starting post
2888             GOINGBEHIND The horses are going behind the stalls
2889             ATTHEPOST The horses are at the post
2890             UNDERORDERS The horses are loaded into the stalls/race is about to start
2891             OFF The race has started
2892             FINISHED The race has finished
2893             FALSESTART There has been a false start
2894             PHOTOGRAPH The result of the race is subject to a photo finish
2895             RESULT The result of the race has been announced
2896             WEIGHEDIN The jockeys have weighed in
2897             RACEVOID The race has been declared void
2898             ABANDONED The meeting has been cancelled
2899             APPROACHING The greyhounds are approaching the traps
2900             GOINGINTRAPS The greyhounds are being put in the traps
2901             HARERUNNING The hare has been started
2902             FINALRESULT The result cannot be changed for betting purposes.
2903             NORACE The race has been declared a no race
2904             RERUN The race will be rerun
2905              
2906             =head3 ReplaceInstructionReport
2907              
2908             status InstructionReportStatus
2909             errorCode InstructionReportErrorCode
2910             cancelInstructionReport CancelInstructionReport
2911             placeInstructionReport PlaceInstructionReport
2912              
2913             =head3 ResponseCode
2914              
2915             Enumeration
2916              
2917             OK Data returned successfully
2918             NO_NEW_UPDATES No updates since the passes UpdateSequence
2919             NO_LIVE_DATA_AVAILABLE Event scores are no longer available
2920             SERVICE_UNAVAILABLE Data feed for the event type is currently unavailable
2921             UNEXPECTED_ERROR An unexpected error occurred retrieving score data
2922             LIVE_DATA_TEMPORARILY_UNAVAILABLE Live Data feed is temporarily unavailable
2923              
2924             =head3 RollupModel
2925              
2926             Enumeration
2927              
2928             STAKE The volumes will be rolled up to the minimum value which is >= rollupLimit.
2929             PAYOUT The volumes will be rolled up to the minimum value where the payout( price * volume ) is >= rollupLimit.
2930             MANAGED_LIABILITY The volumes will be rolled up to the minimum value which is >= rollupLimit, until a lay price threshold.
2931             NONE No rollup will be applied.
2932              
2933             =cut
2934              
2935             $type_defs->{RollupModel} = {
2936 1         4 type => 'ENUM',
2937             allowed => [qw/STAKE PAYOUT MANAGED_LIABILITY NONE/],
2938             };
2939 1         1 $type_defs->{rollupModel} = $type_defs->{RollupModel};
2940              
2941             =head3 Runner
2942              
2943             selectionId Long
2944             handicap Double
2945             status RunnerStatus
2946             adjustmentFactor Double
2947             lastPriceTraded Double
2948             totalMatched Double
2949             removalDate Date
2950             sp StartingPrices
2951             ex ExchangePrices
2952             orders Array of Order
2953             matches Array of Match
2954              
2955             =head3 RunnerCatalog
2956              
2957             selectionId Long
2958             runnerName String
2959             handicap Double
2960             sortPriority Integer
2961             metadata Hash of Metadata
2962              
2963              
2964             =head3 RunnerProfitAndLoss
2965              
2966             selectionId String
2967             ifWin Double
2968             ifLose Double
2969              
2970             =head3 RunnerStatus
2971              
2972             Enumeration
2973              
2974             ACTIVE Active in a live market.
2975             WINNER Winner in a settled market.
2976             LOSER Loser in a settled market.
2977             PLACED The runner was placed, applies to EACH_WAY marketTypes only.
2978             REMOVED_VACANT Vacant (e.g. Trap in a dog race).
2979             REMOVED Removed from the market.
2980             HIDDEN Hidden from the market.
2981              
2982             =cut
2983              
2984             $type_defs->{RunnerStatus} = {
2985 1         4 type => 'ENUM',
2986             allowed => [qw/ACTIVE WINNER LOSER PLACED REMOVED_VACANT REMOVED HIDDEN/],
2987             };
2988              
2989             =head3 Side
2990              
2991             Enumeration
2992              
2993             BACK To bet on the selection to win.
2994             LAY To bet on the selection to lose.
2995              
2996             =cut
2997              
2998             $type_defs->{Side} = {
2999 1         9 type => 'ENUM',
3000             allowed => [qw/BACK LAY/],
3001             };
3002 1         2 $type_defs->{side} = $type_defs->{Side};
3003              
3004             =head3 SortDir
3005              
3006             Enumeration
3007              
3008             EARLIEST_TO_LATEST Order from earliest value to latest.
3009             LATEST_TO_EARLIEST Order from latest value to earliest.
3010              
3011             =cut
3012              
3013             $type_defs->{SortDir} = {
3014 1         3 type => 'ENUM',
3015             allowed => [qw/EARLIEST_TO_LATEST LATEST_TO_EARLIEST/],
3016             };
3017 1         2 $type_defs->{sortDir} = $type_defs->{SortDir};
3018              
3019             =head3 StartingPrices
3020              
3021             nearPrice Double
3022             farPrice Double
3023             backStakeTaken Array of PriceSize
3024             layLiabilityTaken Array of PriceSize
3025             actualSP Double
3026              
3027             =head3 StatementItem
3028              
3029             refId String
3030             itemDate Date
3031             amount Double
3032             balance Double
3033             itemClass ItemClass
3034             itemClassData Hash of ItemClassData
3035             legacyData StatementLegacyData
3036              
3037             =head3 StatementLegacyData
3038              
3039             avgPrice Double
3040             betSize Double
3041             betType String
3042             betCategoryType String
3043             commissionRate String
3044             eventId Long
3045             eventTypeId Long
3046             fullMarketName String
3047             grossBetAmount Double
3048             marketName String
3049             marketType String
3050             placedDate Date
3051             selectionId Long
3052             selectionName String
3053             startDate Date
3054             transactionType String
3055             transactionId Long
3056             winLose String
3057              
3058             =head3 TimeGranularity
3059              
3060             Enumeration
3061              
3062             DAYS Days.
3063             HOURS Hours.
3064             MINUTES Minutes.
3065              
3066             =cut
3067              
3068             $type_defs->{TimeGranularity} = {
3069 1         3 type => 'ENUM',
3070             allowed => [qw/DAYS HOURS MINUTES/],
3071             };
3072 1         1 $type_defs->{granularity} = $type_defs->{TimeGranularity};
3073              
3074             =head3 TimeInForce
3075              
3076             Enumeration
3077              
3078             FILL_OR_KILL Execute the transaction immediately or not at all.
3079              
3080             =cut
3081              
3082             $type_defs->{TimeInForce} = {
3083 1         3 type => 'ENUM',
3084             allowed => [qw/FILL_OR_KILL/],
3085             };
3086 1         1 $type_defs->{timeInForce} = $type_defs->{TimeInForce};
3087              
3088             =head3 TimeRange
3089              
3090             from Date OPT
3091             to Date OPT
3092              
3093             =cut
3094              
3095             $type_defs->{TimeRange} = {
3096 1         5 type => 'HASH',
3097             required => [qw//],
3098             allowed => [qw/from to/],
3099             };
3100 1         1 $type_defs->{dateRange} = $type_defs->{TimeRange};
3101 1         2 $type_defs->{settledDateRange} = $type_defs->{TimeRange};
3102 1         1 $type_defs->{itemDateRange} = $type_defs->{TimeRange};
3103 1         1 $type_defs->{marketStartTime} = $type_defs->{TimeRange};
3104              
3105             =head3 TimeRangeResult
3106              
3107             timeRange TimeRange
3108             marketCount Integer
3109              
3110             =head3 UpdateInstruction
3111              
3112             betId String RQD
3113             newPersistenceType PersistenceType RQD
3114              
3115             =cut
3116              
3117             $type_defs->{UpdateInstruction} = {
3118 1         6 type => 'HASH',
3119             required => [qw/betId newPersistenceType/],
3120             allowed => [qw//],
3121             };
3122              
3123             =head3 UpdateInstructionReport
3124              
3125             status InstructionReportStatus
3126             errorCode InstructionReportErrorCode
3127             instruction UpdateInstruction
3128              
3129             =head3 Wallet
3130              
3131             Enumeration
3132              
3133             UK UK Exchange wallet.
3134             AUSTRALIAN Australian Exchange wallet. DEPRECATED
3135              
3136             =cut
3137              
3138             $type_defs->{Wallet} = {
3139 1         2 type => 'ENUM',
3140             allowed => [qw/UK AUSTRALIAN/],
3141             };
3142 1         2 $type_defs->{wallet} = $type_defs->{Wallet};
3143 1         2 $type_defs->{fromWallet} = $type_defs->{Wallet};
3144 1         1 $type_defs->{toWallet} = $type_defs->{Wallet};
3145              
3146             # A Dirty Hack datatype, because 'instructions' is ambiguous, and could refer
3147             # to place-, cancel-, replace- or updateOrders methods. God I hate Betfair.
3148             $type_defs->{Instruction} = {
3149 1         4 type => 'HASH',
3150             required => [qw//],
3151             allowed => [qw/orderType selectionId side handicap limitOrder
3152             limitOnCloseOrder marketOnCloseOrder betId sizeReduction
3153             newPrice newPersistenceType/],
3154             };
3155              
3156              
3157              
3158 1         4 return $type_defs;
3159             }
3160              
3161              
3162             1;
3163              
3164             =head1 THREADS
3165              
3166             Because the betfair object maintains a persistent encrypted connection to the Betfair
3167             servers, it should NOT be considered 100% thread-safe. In particular, using the same $bf
3168             object to make API calls across different threads will usually result in disaster.
3169             In practice, there are at least two ways to solve this problem and use WWW::BetfairNG
3170             safely in threaded applications:-
3171              
3172             =head2 'Postbox' Thread
3173              
3174             If simultaneous or overlapping calls to betfair are not required, one solution is to make
3175             all calls from a single, dedicated thread. This thread can wait on a queue created by
3176             Thread::Queue for requests from other threads, and return the result to them, again
3177             via a queue. Only one $bf object is required in this scenario, which may be created by
3178             the 'postbox' thread itself or, less robustly, by the parent thread before the 'postbox'
3179             is spawned. In the latter case, no other thread (including the parent) should use the $bf
3180             object once the 'postbox' has started using it.
3181              
3182             =head2 Multiple Objects
3183              
3184             If you need to make simultaneous or overlapping calls to betfair, you can create a new $bf
3185             object in each thread that makes betfair calls. As betfair sessions are identified by a
3186             simple scalar session token, a single login will create a session which CAN be safely
3187             shared across threads:-
3188              
3189             use WWW::BetfairNG;
3190             use threads;
3191             use threads::shared;
3192              
3193             my $parent_bf = WWW::BetfairNG->new({
3194             ssl_cert => '',
3195             ssl_key => '',
3196             app_key => '',
3197             });
3198             $parent_bf->login({username => , password => })
3199             or die;
3200             my $session :shared = $parent_bf->session();
3201              
3202             my $child = threads->create(sub {
3203             # Create a new bf object in the child - no need for ssl cert and key
3204             my $child_bf = WWW::BetfairNG->new({app_key => ''});
3205             # Assign the shared session token - $child_bf will then be logged in
3206             $child_bf->session($session);
3207              
3208             # Make any required API calls in the child using $child_bf
3209             });
3210              
3211             # Freely make API calls using $parent_bf
3212              
3213             $child->join;
3214             $parent_bf->logout; # Logs out any children using the same session token
3215             exit 0;
3216              
3217             In particular, keepAlive calls only need to be made in one thread to affect all threads
3218             using the same session token, and logging out in any thread will log out all threads
3219             using the same session token.
3220              
3221             =head1 SEE ALSO
3222              
3223             The Betfair Developer's Website L
3224             In particular, the Sports API Documentation and the Forum.
3225              
3226             =head1 AUTHOR
3227              
3228             Myrddin Wyllt, Emyrddinwyllt@tiscali.co.ukE
3229              
3230             =head1 ACKNOWLEDGEMENTS
3231              
3232             Main inspiration for this was David Farrell's WWW::betfair module,
3233             which was written for the v6 SOAP interface. Thanks also to Carl
3234             O'Rourke for suggestions on clarifying error messages, Colin
3235             Magee for the suggestion to extend the timeout period for the
3236             navigationMenu call and David Halstead for spotting a bug in the
3237             selectionId parameter check.
3238              
3239             =head1 COPYRIGHT AND LICENSE
3240              
3241             Copyright (C) 2017 by Myrddin Wyllt
3242              
3243             This library is free software; you can redistribute it and/or modify
3244             it under the same terms as Perl itself.
3245              
3246             =cut