File Coverage

blib/lib/Finance/Nadex.pm
Criterion Covered Total %
statement 337 382 88.2
branch 158 264 59.8
condition 28 63 44.4
subroutine 38 42 90.4
pod 17 17 100.0
total 578 768 75.2


line stmt bran cond sub pod time code
1             package Finance::Nadex;
2              
3 13     13   1791659 use strict;
  13         115  
  13         400  
4 13     13   69 use warnings;
  13         30  
  13         529  
5             our $VERSION = '0.12';
6              
7 13     13   1510 use LWP::UserAgent;
  13         103267  
  13         355  
8 13     13   9248 use JSON;
  13         140065  
  13         83  
9 13     13   1714 use Carp;
  13         29  
  13         770  
10 13     13   5967 use Finance::Nadex::Order;
  13         71  
  13         433  
11 13     13   5419 use Finance::Nadex::Position;
  13         34  
  13         469  
12 13     13   5638 use Finance::Nadex::Contract;
  13         38  
  13         458  
13              
14 13     13   87 use constant LOGIN_URL => '/iDeal/v2/security/authenticate';
  13         28  
  13         961  
15 13     13   81 use constant SEND_ORDER_URL => '/iDeal/dma/workingorders';
  13         25  
  13         697  
16 13     13   72 use constant RETRIEVE_ORDERS_URL => '/iDeal/orders/workingorders';
  13         36  
  13         704  
17 13     13   84 use constant RETRIEVE_ORDER_URL => '/iDeal/markets/details/workingorders';
  13         27  
  13         569  
18 13     13   70 use constant CANCEL_ORDER_URL => '/iDeal/orders/workingorders/dma';
  13         37  
  13         589  
19 13     13   76 use constant RETRIEVE_POSITIONS_URL => '/iDeal/orders/positions';
  13         25  
  13         584  
20 13     13   72 use constant RETRIEVE_POSITION_URL => '/iDeal/markets/details/position';
  13         27  
  13         703  
21 13     13   97 use constant EPIC_URL => '/iDeal/markets/details';
  13         34  
  13         764  
22 13     13   99 use constant MARKET_LIST_URL => '/iDeal/markets/navigation';
  13         68  
  13         64298  
23              
24             my $session_id;
25              
26             # allows caller to use aliases for the market names in addition to those used by the exchange
27             my %index_name = (
28             'FTSE100', 'FTSE 100', 'G30', 'Germany 30',
29             'J225', 'Japan 225', 'TECH100', 'US Tech 100',
30             'US500', 'US 500', 'WALL30', 'Wall St 30',
31             'SC2000', 'US SmallCap 2000'
32             );
33              
34             sub balance {
35              
36 0     0 1 0 my $self = shift;
37              
38 0 0       0 croak "ERROR: Finance::Nadex::balance(): must be logged in\n"
39             unless $self->logged_in;
40              
41             # it appears the only way to force a
42             # balance refresh is to login again
43 0         0 $self->login();
44              
45 0   0     0 return $self->{'balance'} || undef;
46              
47             }
48              
49             sub cancel_all_orders {
50              
51 0     0 1 0 my $self = shift;
52              
53 0 0       0 croak "ERROR: Finance::Nadex::cancel_all_orders(): must be logged in\n"
54             unless $self->logged_in;
55 0         0 my @orders = $self->retrieve_orders();
56              
57 0 0       0 return if !scalar @orders;
58              
59 0         0 foreach my $order_entry (@orders) {
60 0         0 $self->cancel_order( id => $order_entry->id );
61             }
62              
63             }
64              
65             sub cancel_order {
66              
67 0     0 1 0 my $self = shift;
68 0         0 my %args = @_;
69              
70 0 0       0 croak "ERROR: Finance::Nadex::cancel_order(): must be logged in\n"
71             unless $self->logged_in;
72             croak
73             "ERROR: Finance::Nadex::cancel_order(): must specify a named argument 'id'\n"
74 0 0       0 unless exists $args{id};
75             croak "ERROR: Finance::Nadex::cancel_order(): invalid id\n"
76 0 0       0 unless $args{id};
77              
78 0         0 my $deal_id = $args{id};
79              
80 0         0 my $cancel_order_url = $self->{base_url}.CANCEL_ORDER_URL . '/' . $deal_id;
81              
82 0         0 my $cancel_time = time;
83 0         0 my $cancel_content = qq~
84             {
85             "lsServerName": "https://mdp.nadex.com:443",
86             "timeStamp": "$cancel_time"
87             }~;
88              
89 0         0 my $cancelled_response =
90             $self->_delete( $cancel_order_url, $cancel_content );
91              
92 0 0       0 return undef unless $cancelled_response;
93              
94 0         0 return $cancelled_response->{'dealReference'};
95             }
96              
97             sub create_order {
98              
99 18     18 1 6215 my $self = shift;
100 18         99 my %args = @_;
101              
102 18         39 my $order_time = time;
103              
104 18 100       52 croak "ERROR: Finance::Nadex::create_order(): must be logged in\n"
105             unless $self->logged_in;
106              
107             croak
108             "ERROR: Finance::Nadex::create_order(): must specify a named argument 'price'\n"
109 12 100       47 unless exists $args{price};
110             croak
111             "ERROR: Finance::Nadex::create_order(): must specify a named argument 'direction'\n"
112 10 100       29 unless exists $args{direction};
113             croak
114             "ERROR: Finance::Nadex::create_order(): must specify a named argument 'epic'\n"
115 9 100       28 unless exists $args{epic};
116             croak
117             "ERROR: Finance::Nadex::create_order(): must specify a named argument 'size'\n"
118 8 100       26 unless exists $args{size};
119              
120 7         16 $args{direction} = lc( $args{direction} );
121              
122             croak "ERROR: Finance::Nadex::create_order(): invalid epic\n"
123 7 50       14 unless $args{epic};
124              
125 7         20 my $contract = $self->get_contract( epic => $args{epic} );
126              
127             croak
128             "ERROR: Finance::Nadex::create_order(): named argument 'price' (->$args{price}<-) is not valid\n"
129 7 100       39 unless $self->_is_valid_price( $args{price}, $contract->type );
130             croak
131             "ERROR: Finance::Nadex::create_order(): named argument 'direction' (->$args{direction}<-) is not valid\n"
132 6 100       22 unless $self->_is_valid_direction( $args{direction} );
133 5 50       12 croak
134             "ERROR: Finance::Nadex::create_order(): named argument 'epic' (->$args{epic}<-) is not valid\n"
135             unless $contract;
136             croak
137             "ERROR: Finance::Nadex::create_order(): named argument 'size' (->$args{size}<-) is not valid\n"
138 5 100       13 unless $self->_is_valid_size( $args{size} );
139              
140             # the market accepts only + or - for the direction; this enables the aliases 'buy' and 'sell'
141 4 100       13 $args{direction} = '+' if $args{direction} eq 'buy';
142 4 100       14 $args{direction} = '-' if $args{direction} eq 'sell';
143              
144             # the price is dollars and cents for binaries and some instrument level
145             # for spreads; only format the price to a currency if the order type is 'binary'
146             $args{price} = sprintf( "%.2f", $args{price} )
147 4 100       11 if $contract->type eq 'binary';
148              
149 4         37 my $order_content = qq~
150             {
151             "direction": "$args{direction}",
152             "epic": "$args{epic}",
153             "limitLevel": null,
154             "lsServerName": "https://mdp.nadex.com:443",
155             "orderLevel": "$args{price}",
156             "orderSize": "$args{size}",
157             "orderType": "Limit",
158             "sizeForPandLCalculation": $args{size},
159             "stopLevel": null,
160             "timeInForce": "GoodTillCancelled",
161             "timeStamp": "$order_time"
162              
163             }~;
164              
165 4         15 my $order = $self->_post( $self->{base_url}.SEND_ORDER_URL, $order_content );
166              
167 4 50       13 my $deal_reference_id = $order->{dealReference} if defined $order;
168              
169 4         33 return $deal_reference_id;
170             }
171              
172             sub get_contract {
173              
174 7     7 1 10 my $self = shift;
175 7         16 my %args = @_;
176              
177 7 50       15 croak "ERROR: Finance::Nadex::get_contracts(): must be logged in\n"
178             unless $self->logged_in;
179              
180             croak
181             "ERROR: Finance::Nadex::get_contract(): must specify a named argument 'epic'\n"
182 7 50       19 unless exists $args{epic};
183             croak
184             "ERROR: Finance::Nadex::get_contract(): specified 'epic' (->$args{epic}<-) is not valid\n"
185 7 50       14 unless $args{epic};
186              
187 7         23 my $epic_url = $self->{base_url}.EPIC_URL . '/' . $args{epic};
188 7         18 my $epic_ref = $self->_get($epic_url);
189              
190 7 50       31 return unless $epic_ref;
191              
192             return
193             unless exists $epic_ref->{instrument}
194 7 50 33     43 && exists $epic_ref->{marketSnapshot};
195             return
196             unless $epic_ref->{instrument}{instrumentType}
197             && $epic_ref->{instrument}{marketName}
198 7 50 33     37 && $epic_ref->{instrument}{displayPrompt};
      33        
199              
200             return Finance::Nadex::Contract::_new(
201             {
202             instrumentType => $epic_ref->{instrument}{instrumentType},
203             epic => $args{epic},
204             displayOffer => $epic_ref->{marketSnapshot}{displayOffer},
205             displayBid => $epic_ref->{marketSnapshot}{displayBid},
206             displayBidSize => $epic_ref->{marketSnapshot}{displayBidSize},
207             displayOfferSize => $epic_ref->{marketSnapshot}{displayOfferSize},
208             instrumentName => $epic_ref->{instrument}{marketName},
209             displayPeriod => $epic_ref->{instrument}{displayPrompt}
210             }
211 7         73 );
212              
213             }
214              
215             sub get_contracts {
216              
217 5     5 1 2943 my $self = shift;
218 5         16 my %args = @_;
219 5         8 my $found;
220              
221 5 50       12 croak "ERROR: Finance::Nadex::get_contracts(): must be logged in\n"
222             unless $self->logged_in;
223              
224             croak
225             "ERROR: Finance::Nadex::get_contracts(): must specify a named argument 'market'\n"
226 5 100       32 unless exists $args{market};
227             croak
228             "ERROR: Finance::Nadex::get_contracts(): must specify a named argument 'instrument'\n"
229 3 100       22 unless exists $args{instrument};
230             croak
231             "ERROR: Finance::Nadex::get_conttracts(): must specify a named argument 'series'\n"
232 2 100       15 unless exists $args{series};
233              
234             croak "ERROR: Finance::Nadex::get_contracts(): invalid market\n"
235 1 50       4 unless $args{market};
236             croak "ERROR: Finance::Nadex::get_contracts(): invalid instrument\n"
237 1 50       5 unless $args{instrument};
238             croak "ERROR: Finance::Nadex::get_conttracts(): invalid series\n"
239 1 50       4 unless $args{series};
240              
241             $args{instrument} = $index_name{ $args{instrument} }
242 1 50       4 if exists $index_name{ $args{instrument} };
243              
244 1         9 my $market_list_ref = $self->_get($self->{base_url}.MARKET_LIST_URL);
245              
246 1 50       4 die
247             "ERROR: Finance::Nadex::get_contracts(): failed to retrieve the market list from the exchange\n"
248             if !$market_list_ref;
249              
250             my $market_id = $self->_get_market_id(
251             name => $args{market},
252 1         5 market_list_ref => $market_list_ref
253             );
254              
255 1 50       20 return unless $market_id;
256              
257             my $instruments_list_ref =
258 1         10 $self->_get( $self->{base_url}.MARKET_LIST_URL . '/' . $market_id );
259              
260 1 50       5 die
261             "ERROR: Finance::Nadex::get_contracts(): failed to retrieve the market list from the exchange for market $market_id\n"
262             if !$instruments_list_ref;
263              
264 1         2 my $instrument_id;
265 1         2 foreach my $instrument ( @{ $instruments_list_ref->{'hierarchy'} } ) {
  1         5  
266             $instrument_id = $instrument->{id}
267 1 50       6 if $instrument->{name} eq $args{instrument};
268             }
269              
270 1 50       7 return unless $instrument_id;
271              
272             my $instrument_list_ref =
273 1         22 $self->_get( $self->{base_url}.MARKET_LIST_URL . '/' . $instrument_id );
274              
275 1 50       16 die
276             "ERROR: Finance::Nadex::get_contracts(): failed to retrieve the market list from the exchange for market $market_id\n"
277             if !$instrument_list_ref;
278              
279 1         4 my $time_series_id;
280 1         3 foreach my $series ( @{ $instrument_list_ref->{'hierarchy'} } ) {
  1         3  
281 1 50       5 $time_series_id = $series->{id} if $series->{name} eq $args{series};
282             }
283              
284 1 50       4 return unless $time_series_id;
285              
286 1         2 my @contracts;
287             my $series_list_ref =
288 1         4 $self->_get( $self->{base_url}.MARKET_LIST_URL . '/' . $time_series_id );
289              
290 1 50       6 die
291             "ERROR: Finance::Nadex::get_contracts(): failed to retrieve the market list from the exchange for market $market_id\n"
292             if !$series_list_ref;
293              
294 1         2 foreach my $contract ( @{ $series_list_ref->{'markets'} } ) {
  1         4  
295 1         5 push( @contracts, Finance::Nadex::Contract::_new($contract) );
296             }
297              
298 1         14 return @contracts;
299             }
300              
301             sub get_epic {
302              
303 6     6 1 2767 my $self = shift;
304 6         23 my %args = @_;
305              
306 6         9 my $market_id;
307             my $instrument;
308              
309 6 50       14 croak "ERROR: Finance::Nadex::get_epic(): must be logged in\n"
310             unless $self->logged_in;
311              
312             croak
313             "ERROR: Finance::Nadex::get_epic(): must specify a named argument 'period'\n"
314 6 100       35 unless exists $args{period};
315             croak
316             "ERROR: Finance::Nadex::get_epic(): must specify a named argument 'market'\n"
317 4 50       9 unless exists $args{market};
318             croak
319             "ERROR: Finance::Nadex::get_epic(): must specify a named argument 'time'\n"
320             unless exists $args{time}
321             || ( exists $args{period}
322 4 100 66     35 && $args{period} =~ /^event$/i );
      100        
323             croak
324             "ERROR: Finance::Nadex::get_epic(): must specify a named argument 'instrument'\n"
325 3 100       14 unless exists $args{instrument};
326             croak
327             "ERROR: Finance::Nadex::get_epic(): must specify a named argument 'strike'\n"
328 2 50       5 unless exists $args{strike};
329              
330             croak "ERROR: Finance::Nadex::get_epic(): invalid period\n"
331 2 50       7 unless $args{period};
332             croak "ERROR: Finance::Nadex::get_epic(): invalid market\n"
333 2 50       6 unless $args{market};
334             croak "ERROR: Finance::Nadex::get_epic(): invalid time\n"
335             unless $args{time}
336             || ( exists $args{period}
337 2 50 33     15 && $args{period} =~ /^event$/i );
      66        
338             croak "ERROR: Finance::Nadex::get_epic(): invalid instrument\n"
339 2 50       4 unless $args{instrument};
340             croak "ERROR: Finance::Nadex::get_epic(): invalid strike\n"
341 2 50       7 unless $args{strike};
342              
343 2 50       8 $args{period} = ucfirst( lc( $args{period} ) ) if exists $args{period};
344              
345 2 100       7 $args{time} = lc( $args{time} ) if exists $args{time};
346              
347 2         11 my $market_list_ref = $self->_get($self->{base_url}.MARKET_LIST_URL);
348              
349 2 50       6 die
350             "ERROR: Finance::Nadex::get_epic(): failed to retrieve the market list from the exchange\n"
351             if !$market_list_ref;
352              
353             $market_id = $self->_get_market_id(
354             name => $args{market},
355 2         45 market_list_ref => $market_list_ref
356             );
357              
358 2 50       6 return undef unless $market_id;
359              
360 2         11 $market_list_ref = $self->_get( $self->{base_url}.MARKET_LIST_URL . "/$market_id" );
361              
362 2 50       7 die
363             "ERROR: Finance::Nadex::get_epic(): failed to retrieve the market list from the exchange for market $market_id\n"
364             if !$market_list_ref;
365              
366             $market_id = $self->_get_market_id(
367             name => $args{instrument},
368 2         6 market_list_ref => $market_list_ref
369             );
370              
371 2 50       5 return undef unless $market_id;
372              
373 2         8 $market_list_ref = $self->_get( $self->{base_url}.MARKET_LIST_URL . "/$market_id" );
374              
375 2 50       7 die
376             "ERROR: Finance::Nadex::get_epic(): failed to retrieve the market list from the exchange for market $market_id\n"
377             if !$market_list_ref;
378              
379 2         5 my $target_period_time;
380              
381             $target_period_time = "$args{period} ($args{time})"
382 2 100       10 if $args{period} eq 'Daily';
383 2 50       6 $target_period_time = "-$args{time}" if $args{period} eq 'Intraday';
384 2 50       5 $target_period_time = "$args{period}" if $args{period} eq 'Weekly';
385 2 100       5 $target_period_time = "Open" if $args{period} eq 'Event';
386              
387 2 50       6 croak
388             "ERROR: Finance::Nadex::get_epic(): invalid period; must be one of: daily, weekly, intraday, event\n"
389             if !$target_period_time;
390              
391 2         5 $market_id = $self->_get_market_id(
392             name => $target_period_time,
393             market_list_ref => $market_list_ref,
394             accept_match => 1
395             );
396              
397 2 50       5 return undef unless $market_id;
398              
399 2         9 $market_list_ref = $self->_get( $self->{base_url}.MARKET_LIST_URL . "/$market_id" );
400              
401 2 50       8 die
402             "ERROR: Finance::Nadex::get_epic(): failed to retrieve the market list from the exchange for market $market_id\n"
403             if !$market_list_ref;
404              
405 2         3 my $epic;
406 2         3 foreach my $market ( @{ $market_list_ref->{'markets'} } ) {
  2         8  
407 2 100       6 $args{time} = uc( $args{time} ) if exists $args{time};
408 2 100       18 $args{time} = "" if !exists $args{time};
409 2 50       50 if ( $market->{instrumentName} =~ /$args{strike}( \($args{time}\))?$/ )
410             {
411 2         6 $epic = $market->{epic};
412 2         5 last;
413             }
414             }
415              
416 2         16 return $epic;
417              
418             }
419              
420             sub get_market_instruments {
421              
422 2     2 1 1023 my $self = shift;
423 2         6 my %args = @_;
424              
425 2 50       7 croak "ERROR: Finance::Nadex::get_market_instruments(): must be logged in\n"
426             unless $self->logged_in;
427              
428             croak
429             "ERROR: Finance::Nadex::get_market_instruments(): must provide market as named argument 'name'\n"
430 2 100       29 unless exists $args{name};
431             croak
432             "ERROR: Finance::Nadex::get_market_instruments(): invalid market name\n"
433 1 50       5 unless $args{name};
434              
435 1         8 my $market_list_ref = $self->_get($self->{base_url}.MARKET_LIST_URL);
436              
437 1 50       5 die
438             "ERROR: Finance::Nadex::get_market_instruments(): failed to retrieve the market list from the exchange\n"
439             if !$market_list_ref;
440              
441             my $market_id = $self->_get_market_id(
442             name => $args{name},
443 1         7 market_list_ref => $market_list_ref
444             );
445              
446 1 50       5 return unless $market_id;
447              
448 1         41 $market_list_ref = $self->_get( $self->{base_url}.MARKET_LIST_URL . '/' . $market_id );
449              
450 1 50       6 die
451             "ERROR: Finance::Nadex::get_market_instruments(): failed to retrieve the market list from the exchange for market $market_id\n"
452             if !$market_list_ref;
453              
454 1         3 my @instruments;
455 1         2 foreach my $market ( @{ $market_list_ref->{'hierarchy'} } ) {
  1         4  
456 1         45 push( @instruments, $market->{name} );
457             }
458 1         18 return @instruments;
459             }
460              
461             sub get_markets {
462              
463 1     1 1 10 my $self = shift;
464              
465 1 50       3 croak "ERROR: Finance::Nadex::get_markets(): must be logged in\n"
466             unless $self->logged_in;
467              
468 1         7 my $market_list_ref = $self->_get($self->{base_url}.MARKET_LIST_URL);
469              
470 1 50       6 die
471             "ERROR: Finance::Nadex::get_markets(): failed to retrieve the market list from the exchange\n"
472             if !$market_list_ref;
473              
474 1         2 my @markets;
475 1         2 foreach my $market ( @{ $market_list_ref->{'hierarchy'} } ) {
  1         4  
476 1         4 push( @markets, $market->{name} );
477             }
478              
479 1         11 return @markets;
480             }
481              
482             sub get_time_series {
483              
484 4     4 1 2126 my $self = shift;
485 4         10 my %args = @_;
486 4         6 my $found;
487              
488 4 50       9 croak "ERROR: Finance::Nadex::get_time_series(): must be logged in\n"
489             unless $self->logged_in;
490              
491             $args{instrument} = $index_name{ $args{instrument} }
492 4 50 66     18 if exists $args{instrument} && $index_name{ $args{instrument} };
493              
494             croak
495             "ERROR: Finance::Nadex::get_time_series(): must specify a named argument 'market'\n"
496 4 100       31 unless exists $args{market};
497             croak
498             "ERROR: Finance::Nadex::get_time_series(): must specify a named argument 'instrument'\n"
499 2 100       14 unless exists $args{instrument};
500              
501 1         6 my $market_list_ref = $self->_get($self->{base_url}.MARKET_LIST_URL);
502              
503 1 50       3 die
504             "ERROR: Finance::Nadex::get_time_series(): failed to retrieve the market list from the exchange\n"
505             if !$market_list_ref;
506              
507             my $market_id = $self->_get_market_id(
508             name => $args{market},
509 1         4 market_list_ref => $market_list_ref
510             );
511              
512 1 50       5 return unless $market_id;
513              
514             my $instruments_list_ref =
515 1         7 $self->_get( $self->{base_url}.MARKET_LIST_URL . '/' . $market_id );
516              
517 1 50       7 die
518             "ERROR: Finance::Nadex::get_time_series(): failed to retrieve the market list from the exchange for market $market_id\n"
519             if !$market_list_ref;
520              
521 1         3 my $instrument_id;
522 1         24 foreach my $instrument ( @{ $instruments_list_ref->{'hierarchy'} } ) {
  1         6  
523             $instrument_id = $instrument->{id}
524 1 50       5 if $instrument->{name} eq $args{instrument};
525             $instrument_id = $instrument->{id}
526             if $instrument->{name} eq 'Forex'
527 1 50 33     5 && $args{market} eq '5 Minute Binaries';
528             $instrument_id = $instrument->{id}
529             if $instrument->{name} eq 'Indices'
530 1 50 33     5 && $args{market} eq '20 Minute Binaries';
531             }
532              
533 1 50       3 return unless $instrument_id;
534              
535             my $instrument_list_ref =
536 1         6 $self->_get( $self->{base_url}.MARKET_LIST_URL . '/' . $instrument_id );
537              
538 1 50       5 die
539             "ERROR: Finance::Nadex::get_time_series(): failed to retrieve the market list from the exchange for market $instrument_id\n"
540             if !$market_list_ref;
541              
542 1 50 33     8 if ( $args{market} eq '5 Minute Binaries'
543             || $args{market} eq '20 Minute Binaries' )
544             {
545 0         0 my @instruments;
546 0         0 foreach my $instrument ( @{ $instrument_list_ref->{'hierarchy'} } ) {
  0         0  
547             $instrument_id = $instrument->{id}
548 0 0       0 if $instrument->{name} eq $args{instrument};
549             }
550              
551 0 0       0 return unless $instrument_id;
552              
553             $instrument_list_ref =
554 0         0 $self->_get( $self->{base_url}.MARKET_LIST_URL . '/' . $instrument_id );
555              
556 0 0       0 die
557             "ERROR: Finance::Nadex::get_time_series(): failed to retrieve the market list from the exchange for market $instrument_id\n"
558             if !$market_list_ref;
559              
560 0         0 my @contracts;
561 0         0 foreach my $contract ( @{ $instrument_list_ref->{'markets'} } ) {
  0         0  
562 0         0 push( @contracts, Finance::Nadex::Contract::_new($contract) );
563             }
564 0         0 return @contracts;
565              
566             }
567              
568 1         2 my @time_series;
569 1         2 foreach my $series ( @{ $instrument_list_ref->{'hierarchy'} } ) {
  1         4  
570 1         3 push( @time_series, $series->{name} );
571             }
572              
573 1         24 return @time_series;
574              
575             }
576              
577             sub logged_in {
578              
579 65     65 1 123 my $self = shift;
580              
581 65 100       377 return 0 unless $self->{security_token};
582              
583 54 50       128 return 0 unless $self->{session_id};
584              
585 54         162 return 1;
586              
587             }
588              
589             sub login {
590              
591 12     12 1 552 my $self = shift;
592 12         56 my %args = @_;
593              
594             croak
595             "ERROR: Finance::Nadex::login(): must specify a named argument 'username'\n"
596 12 0 33     57 unless exists $args{username} || exists $self->{username};
597             croak
598             "ERROR: Finance::Nadex::login(): must specify a named argument 'password'\n"
599 12 0 33     109 unless exists $args{password} || exists $self->{password};
600              
601 12 50       56 $self->{username} = $args{username} if exists $args{username};
602 12 50       83 $self->{password} = $args{password} if exists $args{password};
603              
604 12         52 my $login_url = $self->{base_url}.LOGIN_URL;
605              
606 12         60 my $login_content = qq~
607             {
608             "advertisingId" : "",
609             "password": "$self->{password}",
610             "username": "$self->{username}"
611             }~;
612              
613 12         61 my $json_obj = $self->_post( $login_url, $login_content );
614              
615 12         80 $self->{user_agent}->cookie_jar->scan( \&_get_session_id );
616              
617 12         96 $self->{session_id} = $session_id;
618              
619 12         45 $self->{balance} = $json_obj->{accountInfo}->{available};
620              
621 12         91 return $self->logged_in();
622              
623             }
624              
625             sub new {
626              
627 23     23 1 160388 my $class = shift;
628 23         91 my %args = @_;
629              
630 23 50 33     137 if (exists $args{platform} && $args{platform} eq 'demo') {
631 0         0 $args{base_url} = 'https://demo-trade.nadex.com';
632             } else {
633 23         75 $args{base_url} = 'https://trade.nadex.com';
634             }
635              
636 23         160 my $ua = LWP::UserAgent->new( ssl_opts => { verify_hostname => 0 } );
637              
638 23         8154 push @{ $ua->requests_redirectable }, 'POST', 'DELETE';
  23         95  
639 23         473 $ua->agent(
640             "vendor=IG Group | applicationType=dxd | platform=Android | deviceType=generic | version=1.13.2"
641             );
642 23         1570 $ua->cookie_jar( { autosave => 1, ignore_discard => 1 } );
643              
644 23         11856 $args{user_agent} = $ua;
645              
646 23         104 bless \%args, __PACKAGE__;
647              
648             }
649              
650             sub retrieve_order {
651              
652 3     3 1 1475 my $self = shift;
653 3         9 my %args = @_;
654              
655 3 100       12 croak "ERROR: Finance::Nadex::retrieve_order(): must be logged in\n"
656             unless $self->logged_in;
657              
658             croak "ERROR: retrieve_order(): must specify a named argument 'id'\n"
659 2 100       27 unless exists $args{id};
660 1 50       6 croak "ERROR: retrieve_order(): invalid id\n" unless $args{id};
661              
662 1         3 my $order_id = $args{id};
663              
664 1         5 my $retrieve_order_url = $self->{base_url}.RETRIEVE_ORDER_URL . '/' . $order_id;
665              
666 1         5 my $order_ref = $self->_get($retrieve_order_url);
667              
668 1 50       6 return undef unless $order_ref;
669              
670 1         7 return Finance::Nadex::Order::_new($order_ref);
671              
672             }
673              
674             sub retrieve_orders {
675              
676 2     2 1 101 my $self = shift;
677              
678 2 100       6 croak "ERROR: Finance::Nadex::retrieve_orders(): must be logged in\n"
679             unless $self->logged_in();
680              
681 1         4 my $retrieve_orders_url = $self->{base_url}.RETRIEVE_ORDERS_URL;
682              
683 1         5 my $order_list_ref = $self->_get($retrieve_orders_url);
684              
685 1 50       4 die
686             "ERROR: Finance::Nadex::retrieve_orders(): failed to retrieve the order list from the exchange \n"
687             if !$order_list_ref;
688              
689 1         3 my $order_obj_list_ref = [];
690 1         3 foreach my $order (@$order_list_ref) {
691 1         16 push( @$order_obj_list_ref, Finance::Nadex::Order::_new($order) );
692             }
693              
694 1         14 return @$order_obj_list_ref;
695             }
696              
697             sub retrieve_position {
698              
699 3     3 1 995 my $self = shift;
700 3         10 my %args = @_;
701              
702 3 100       8 croak "ERROR: Finance::Nadex::retrieve_position(): must be logged in\n"
703             unless $self->logged_in;
704              
705             croak
706             "ERROR: Finance::Nadex::retrieve_position(): must specify a named argument 'id'\n"
707 2 100       18 unless exists $args{id};
708             croak "ERROR: Finance::Nadex::retrieve_position(): invalid id\n"
709 1 50       5 unless $args{id};
710              
711 1         3 my $position_id = $args{id};
712              
713 1         7 my $retrieve_positions_url = $self->{base_url}.RETRIEVE_POSITION_URL . '/' . $position_id;
714              
715 1         4 my $position_ref = $self->_get($retrieve_positions_url);
716              
717 1 50       4 return undef unless $position_ref;
718              
719 1         13 return Finance::Nadex::Position::_new($position_ref);
720              
721             }
722              
723             sub retrieve_positions {
724              
725 2     2 1 143 my $self = shift;
726              
727 2 100       8 croak "ERROR: Finance::Nadex::retrieve_positions(): must be logged in\n"
728             unless $self->logged_in;
729              
730 1         4 my $retrieve_positions_url = $self->{base_url}.RETRIEVE_POSITIONS_URL;
731              
732 1         6 my $position_list_ref = $self->_get($retrieve_positions_url);
733              
734 1 50       5 die
735             "ERROR: Finance::Nadex::retrieve_positions(): failed to retrieve the position list from the exchange\n"
736             if !$position_list_ref;
737              
738 1         3 my $position_obj_list_ref = [];
739 1         4 foreach my $position (@$position_list_ref) {
740 1         22 push( @$position_obj_list_ref,
741             Finance::Nadex::Position::_new($position) );
742             }
743              
744 1         17 return @$position_obj_list_ref;
745              
746             }
747              
748             sub _delete {
749              
750 0     0   0 my $self = shift;
751 0         0 my $url = shift;
752 0         0 my $delete_content = shift;
753              
754             my $response = $self->{user_agent}->delete(
755             $url,
756             'Accept' => 'application/json; charset=UTF-8',
757             'Content-Type' => 'application/json; charset=UTF-8',
758             'Accept-Encoding' => 'text/html',
759             'X-SECURITY-TOKEN' => $self->{security_token},
760 0         0 'X-DEVICE-USER-AGENT' => 'vendor=IG | applicationType=Nadex | platform=web | deviceType=phone | version=0.707.0+8fa77b16',
761             'Content' => $delete_content
762             );
763              
764 0 0       0 $self->{security_token} = $response->header('X-SECURITY-TOKEN') if $response->header('X-SECURITY-TOKEN');
765 0         0 $self->{code} = $response->code;
766 0         0 $self->{content} = $response->content;
767              
768             my $delete_response =
769 0         0 eval { JSON->new->utf8->decode( $response->content ); };
  0         0  
770              
771 0         0 return $delete_response;
772             }
773              
774             sub _get {
775              
776 29     29   56 my $self = shift;
777 29         44 my $url = shift;
778              
779             my $response = $self->{user_agent}->get(
780             $url,
781             'Accept' => 'application/json; charset=UTF-8',
782             'Content-Type' => 'application/json; charset=UTF-8',
783             'Accept-Encoding' => 'text/html',
784             'X-SECURITY-TOKEN' => $self->{security_token},
785 29         190 'X-DEVICE-USER-AGENT' => 'vendor=IG | applicationType=Nadex | platform=web | deviceType=phone | version=0.707.0+8fa77b16',
786             Content => ""
787             );
788              
789 29 50       73437 $self->{security_token} = $response->header('X-SECURITY-TOKEN') if $response->header('X-SECURITY-TOKEN');
790 29         2787 $self->{code} = $response->code;
791 29         327 $self->{content} = $response->content;
792              
793 29         338 my $json = eval { JSON->new->utf8->decode( $response->content ); };
  29         243  
794              
795 29         695 return $json;
796             }
797              
798             sub _post {
799              
800 16     16   35 my $self = shift;
801 16         34 my $url = shift;
802 16         41 my $post_content = shift;
803              
804             my $response = $self->{user_agent}->post(
805 16         171 $url,
806             'Accept' => 'application/json; charset=UTF-8',
807             'Content-Type' => 'application/json; charset=UTF-8',
808             'Accept-Encoding' => 'text/html',
809             'Accept-Language' => 'en-US,en;q=0.9',
810             'X-DEVICE-USER-AGENT' => 'vendor=IG | applicationType=Nadex | platform=web | deviceType=phone | version=0.707.0+8fa77b16',
811             Content => $post_content
812             );
813              
814 16 100       175648 $self->{security_token} = $response->header('X-SECURITY-TOKEN') if $response->header('X-SECURITY-TOKEN');
815 16         1539 $self->{code} = $response->code;
816 16   100     204 $self->{content} = $response->content || undef;
817              
818 16         265 my $json_obj = eval { JSON->new->utf8->decode( $response->content ); };
  16         319  
819              
820 16         469 return $json_obj;
821              
822             }
823              
824             sub _get_market_id {
825              
826 9     9   21 my $self = shift;
827 9         34 my %args = @_;
828 9         18 my $market_id;
829              
830 9         15 foreach my $market ( @{ $args{market_list_ref}->{'hierarchy'} } ) {
  9         78  
831 11 100 66     62 if ( !exists $args{accept_match} || $args{accept_match} == 0 ) {
832 9 100       42 if ( $market->{name} eq $args{name} ) {
833 7         19 $market_id = $market->{id};
834             }
835             }
836             else {
837 2 50 66     47 if ( $market->{name} =~ /$args{name}/
838             || $market->{name} eq $args{name} )
839             {
840 2         8 $market_id = $market->{id};
841             }
842             }
843             }
844              
845 9   50     66 return $market_id || undef;
846             }
847              
848             sub _get_session_id {
849              
850 11     11   430 my $key = $_[1];
851 11         27 my $val = $_[2];
852              
853 11 50       94 $session_id = $val if $key =~ /JSESSIONID/;
854              
855             }
856              
857             sub _is_valid_price {
858              
859 7     7   10 my $self = shift;
860 7         11 my $price = shift;
861 7         10 my $type = shift;
862              
863 7 100       46 return 0 if $price =~ /-|\+/;
864              
865 6 100       15 if ( $type eq 'binary' ) {
866 5 50       36 return 0 if $price !~ /^(\d+\.\d{1,2}|\.\d{1,2}|\d+)$/;
867              
868 5 50       23 if ( $price =~ /\.(\d+)/ ) {
869 5 0 33     22 return 0 if $1 != 0 && $1 != 25 && $1 != 50 && $1 != 75;
      33        
      0        
870             }
871             }
872              
873 6 100       13 if ( $type eq 'spread' ) {
874 1 50       7 return 0 if $price !~ /^(\d+|\d+\.\d{1,4})$/;
875             }
876              
877 6         16 return 1;
878             }
879              
880             sub _is_valid_direction {
881              
882 6     6   14 my $self = shift;
883 6         16 my $direction = shift;
884              
885 6         23 my %valid = qw( - 1 + 1 buy 1 sell 1);
886              
887 6 100       40 return exists $valid{$direction} ? 1 : 0;
888             }
889              
890             sub _is_valid_size {
891              
892 5     5   9 my $self = shift;
893 5         8 my $size = shift;
894              
895 5 100       37 return 0 if $size !~ /^\d+$/;
896              
897 4 50       13 return 0 if $size == 0;
898              
899 4         9 return 1;
900              
901             }
902              
903             =head1 NAME
904              
905             Finance::Nadex - Interact with the North American Derivatives Exchange
906              
907             =head1 VERSION
908              
909             Version 0.12
910              
911             =head1 SYNOPSIS
912              
913             Easily create orders, cancel orders, retrieve orders and retrieve positions on the North American Derivatives Exchange
914              
915              
916             use Finance::Nadex;
917              
918             # connects to the live platform when called as Finance::Nadex->new(); as an alternative
919             # it is possible to connect to the demo platform with Finance::Nadex->new(platform => 'demo')
920             my $client = Finance::Nadex->new();
921              
922             # must login to perform any actions, including simply querying market info
923             $client->login(username => 'yourusername', password => 'yourpassword');
924            
925             # get the available balance in the account
926             my $balance = $client->balance();
927            
928             # retrieve the epic (Nadex-assigned contract unique identifier) for the
929             # Daily, 3pm, GBP/USD > 1.5120 contract
930             my $epic = $client->get_epic( period => 'Daily', market => 'Forex (Binaries)', time => '3pm', instrument => 'GBP/USD', strike => '1.5120');
931            
932             # suppose $epic now contains 'NB.D.GBP-USD.OPT-23-17-23Jan15.IP';
933             # create an order to buy 3 of those contracts for $34.50 each
934             my $order_id = $client->create_order( price => '34.50', direction => 'buy', epic => $epic, size => 3 );
935            
936             # check the status of the order using the order id returned by the exchange;
937             # this call will return undef if the order doesn't exist; the order may not exist
938             # because the order was rejected by the exchange or because it was filled immediately
939             # and therefore is no longer a working order
940             my $order = $client->retrieve_order( id => $order_id );
941            
942             # let's assume the order was created and is still a working (or pending) order;
943             # get the details of the order using the accessor methods provided by Finance::Nadex::Order
944             print join(" ", $order->direction, $order->size, $order->contract, $order->price), "\n";
945              
946             # suppose the order has now been filled; this means we have one open position;
947             # get the open positions
948             my @positions = $client->retrieve_positions();
949            
950             # @positions now has 1 element which is a Finance::Nadex::Position; get its details
951             print join(" ", $positions[0]->id, $positions[0]->direction(), $positions[0]->size(), $positions[0]->contract(), $positions[0]->price), "\n";
952            
953             # get the current best bid (the price at which we could sell the contract back immediately)
954             my $bid = $positions[0]->bid();
955            
956             # suppose $bid is now $64.50 (we bought at $34.50, so we have a profit of $30);
957             # sell to close the position at $64.50
958             my $sell_to_close_order_id = $client->create_order( price => $bid, direction => 'sell', epic => $positions[0]->epic, size => $positions[0]->size() );
959              
960             # get all the time series (trading periods for contracts) currently
961             # available for GBP/USD binaries; the list of currently available markets
962             # can be obtained via a call to get_markets()
963             my @series = $client->get_time_series( market => 'Forex (Binaries)', instrument => 'GBP/USD' );
964            
965             # elements of @series are simply strings, such as '2pm-4pm' or 'Daily (3pm)';
966             # suppose one of the elements of series is '8pm-10pm'; get a list of
967             # contracts available for trading within that market
968             my @contracts = $client->get_contracts( market => 'Forex (Binaries)', instrument => 'GBP/USD', series => 'Daily (3pm)' );
969            
970             # @contracts now has a list in which each element is a Finance::Nadex::Contract; get
971             # the details of the available contracts using the accessors of Finance::Nadex::Contract
972             # including the current best bid and offer available on the exchange
973             foreach my $contract (@contracts) {
974             print join(" ", $contract->epic(), $contract->contract(), $contract->expirydate(), $contract->bid(), $contract->offer());
975             }
976            
977              
978             # cancel any remaining open orders
979             $client->cancel_all_orders();
980              
981            
982             =head1 SUBROUTINES/METHODS
983              
984             =head2 balance
985              
986             Retrieves the available account balance
987              
988             balance()
989              
990             Returns a number representing the available account balance
991              
992             =head2 cancel_all_orders
993              
994             Cancels all pending orders
995              
996             cancel_all_orders()
997              
998             Returns nothing
999              
1000             =head2 cancel_order
1001              
1002             Cancels the order with the specified order id
1003              
1004             cancel_order( id => 'NZ1234FGQ4AFFOPA12Z' )
1005              
1006             Returns the reference id created by the exchange for the cancelled order
1007              
1008             =head2 create_order
1009              
1010             Creates an order on the exchange with the specified parameters
1011              
1012             create_order( price => '34.50', direction => 'buy', epic => 'NB.D.GBP-USD.OPT-23-17-23Jan15.IP', size => 2 )
1013              
1014             price : the amount at which to buy or sell; the decimal portion of the number, if provided, must be .50 or .00 for binaries
1015              
1016             direction : one of 'buy', 'sell', '+', '-'
1017            
1018             size : the number of contracts to buy or sell
1019              
1020             epic : the unique identifier for the contract to be bought or sold
1021              
1022             Returns: the order id created by the exchange to identify the order
1023              
1024             Note: '+' is an alias for 'buy'; '-' is an alias for 'sell'; the get_epic() method can be used to get the identifier for the contract of interest
1025              
1026             =head2 get_contract
1027              
1028             Retrieves the contract specified by an epic
1029              
1030             get_contract( epic => 'NB.D.GBP-USD.OPT-23-17-23Jan15.IP' )
1031              
1032             epic : the unique identifier created by the exchange for the contract
1033              
1034             Returns a L instance for the specified epic
1035              
1036             =head2 get_contracts
1037              
1038             Retrieves all the contracts available for trading within the given time series for the specified market and instrument
1039              
1040             get_contracts( market => 'Forex (Binaries)', instrument => 'GBP/USD', series => 'Daily (3pm)' )
1041              
1042             market : the name of the market for which the contracts are to be retrieved
1043            
1044             instrument : the name of the instrument within the market for which contracts are to be retrieved; the
1045             instrument specified must be one of the instruments currently available for trading on the exchange
1046             for the provided market; the list of valid instruments can be obtained via get_market_instruments()
1047            
1048             Returns a list in which each element is a L instance for each contract in the specified
1049             time series
1050              
1051             =head2 get_epic
1052              
1053             Retrieves the epic(unique identifier) for a contract with the specified parameters
1054            
1055             get_epic(period => 'daily', strike => '1.5080', time => '3pm', instrument => 'GBP/USD', market => 'Forex (Binaries)')
1056              
1057             retrieves the epic with the specified parameters
1058            
1059             period : specifies the frequency or period of the contract being searched for; one of 'daily', 'intraday', 'weekly', or 'event'
1060            
1061             time : specifies the time at which the contract being searched for expires (not required when retrieving an event epic)
1062            
1063             instrument : the asset type from which the contract being searched for derives value; an index, currency, or commodity
1064            
1065             market : the market in which the specified contract exists (e.g. 'Forex (Binaries)', 'Indices (Binaries)'); must be one of markets returned by get_markets()
1066            
1067             strike : the level of the underlying asset for the desired contract (e.g. 1.5010)
1068            
1069             Returns the unique identifier of the contract
1070              
1071             =head2 get_market_instruments
1072              
1073             Retrieves the list of instruments associated with the specified market
1074              
1075             get_market_instruments( name => 'Forex (Binaries)' )
1076              
1077             name : the name of the market for which instruments are to be retrieved; this must match one of the names returned by get_markets()
1078            
1079             Returns a list in which each element is a string containing the name of an instrument available for trading in the markets
1080              
1081              
1082             =head2 get_markets
1083              
1084             Retrieves the list of available markets on the exchange; the list of market names returned can be used in a call to get_market_instruments() to get further
1085             information about the market
1086            
1087             get_markets()
1088              
1089             Returns a list in which each element is a string containing the name of a market in which instruments are traded
1090              
1091             =head2 get_time_series
1092              
1093             Retrieves the contract periods available for trading in the specified market for the given instrument
1094              
1095             get_time_series( market => 'Forex (Binaries)', instrument => 'AUD/USD' )
1096              
1097             market : the name of the market for which a time series is to be retrieved
1098            
1099             instrument : the name of the instrument within the market for which a time series is to be retrieved; the
1100             instrument specified must be one of the instruments currently available for trading on the exchange
1101             for the provided market; the list of valid instruments can be obtained via get_market_instruments()
1102              
1103             In the case of the '5 Minute Binaries' and '20 Minute Binaries' markets, returns a list in which each element is
1104             a L representing each contract available in the series; for all other markets, returns
1105             a string containing the name of a time series for the given market and instrument;
1106             a time series designates the period during which the contract is available for trading including the expiration time
1107              
1108             =head2 login
1109              
1110             Submits the specified username and password to the exchange for authorization
1111              
1112             login( username => 'someusername', password => 'somepassword' );
1113              
1114             username : the username of the account
1115            
1116             password : the password of the account
1117              
1118             Returns a true value on successful login; otherwise returns a false value
1119              
1120             =head2 logged_in
1121              
1122             Reports whether login was previously attempted and succeeded
1123              
1124             logged_in()
1125              
1126             Returns true if login was previously attempted and succeded; otherwise returns false
1127              
1128             =head2 new
1129              
1130             Creates an instance of a Finance::Nadex object
1131              
1132             new()
1133            
1134             Returns a reference to a Finance::Nadex instance
1135              
1136             =head2 retrieve_order
1137              
1138             Gets the details of the pending order with the specified order id
1139              
1140             retrieve_order( id => 'NZ1234FGQ4AFFOPA12Z' )
1141              
1142             Returns an instance of L
1143              
1144             =head2 retrieve_orders
1145              
1146             Gets the details of all pending orders
1147              
1148             retrieve_orders()
1149              
1150             Returns a list in which each element is a L
1151              
1152             =head2 retrieve_position
1153              
1154             Retrieves the details of an individual open position
1155              
1156             retrieve_position ( id => 'NA12DZ45BNVA12A9BQZ' )
1157              
1158             Returns a L object corresponding to the specified position id
1159              
1160             =head2 retrieve_positions
1161              
1162             Retrieves the details of all the open positions
1163              
1164             retrieve_positions()
1165              
1166             Returns a list in which each element is a L
1167              
1168             =head1 AUTHOR
1169              
1170             mhandisi, C<< >> AKA Charles Larry
1171              
1172             =head1 BUGS
1173              
1174             Please report any bugs or feature requests to C, or through
1175             the web interface at L. I will be notified, and then you will
1176             automatically be notified of progress on your bug as I make changes.
1177              
1178             =head1 TODO
1179              
1180             Add support for watchlists.
1181              
1182             =head1 SUPPORT
1183              
1184             You can find documentation for this module with the perldoc command.
1185              
1186             perldoc Finance::Nadex
1187              
1188              
1189             You can also look for information at:
1190              
1191             =over 4
1192              
1193             =item * RT: CPAN's request tracker (report bugs here)
1194              
1195             L
1196              
1197             =item * AnnoCPAN: Annotated CPAN documentation
1198              
1199             L
1200              
1201             =item * CPAN Ratings
1202              
1203             L
1204              
1205             =item * Search CPAN
1206              
1207             L
1208              
1209             =back
1210              
1211              
1212             =head1 ACKNOWLEDGEMENTS
1213              
1214              
1215             =head1 LICENSE AND COPYRIGHT
1216              
1217             Copyright 2015 mhandisi.
1218              
1219             This program is free software; you can redistribute it and/or modify it
1220             under the terms of the the Artistic License (2.0). You may obtain a
1221             copy of the full license at:
1222              
1223             L
1224              
1225             Any use, modification, and distribution of the Standard or Modified
1226             Versions is governed by this Artistic License. By using, modifying or
1227             distributing the Package, you accept this license. Do not use, modify,
1228             or distribute the Package, if you do not accept this license.
1229              
1230             If your Modified Version has been derived from a Modified Version made
1231             by someone other than you, you are nevertheless required to ensure that
1232             your Modified Version complies with the requirements of this license.
1233              
1234             This license does not grant you the right to use any trademark, service
1235             mark, tradename, or logo of the Copyright Holder.
1236              
1237             This license includes the non-exclusive, worldwide, free-of-charge
1238             patent license to make, have made, use, offer to sell, sell, import and
1239             otherwise transfer the Package with respect to any patent claims
1240             licensable by the Copyright Holder that are necessarily infringed by the
1241             Package. If you institute patent litigation (including a cross-claim or
1242             counterclaim) against any party alleging that the Package constitutes
1243             direct or contributory patent infringement, then this Artistic License
1244             to you shall terminate on the date that such litigation is filed.
1245              
1246             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
1247             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
1248             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
1249             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
1250             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
1251             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
1252             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
1253             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1254              
1255              
1256             =cut
1257              
1258             42; # End of Finance::Nadex