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 |