File Coverage

blib/lib/WWW/BetfairNG.pm
Criterion Covered Total %
statement 428 618 69.2
branch 66 194 34.0
condition 33 70 47.1
subroutine 57 59 96.6
pod 40 40 100.0
total 624 981 63.6


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