line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Finance::IG; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# use 5.010000; I cannot get this to work, trying to say it should run with perl 5.10 or greater and should be fine with 5.32 |
4
|
|
|
|
|
|
|
# but get message ! Installing the dependencies failed: Your Perl (5.032001) is not in the range '5.10' |
5
|
1
|
|
|
1
|
|
73651
|
use strict; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
33
|
|
6
|
1
|
|
|
1
|
|
5
|
no strict 'refs'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
23
|
|
7
|
1
|
|
|
1
|
|
17
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
58
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=encoding utf8 |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=head1 NAME |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
Finance::IG - - Module for doing useful stuff with IG Markets REST API. |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 DESCRIPTION |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
This is very much a first draft, but will enable you to get simple arrays of positions, print them out possily some simple trading. |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
Its proof of concept in perl beyond anything else, extend it as you need to. |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
I have only used it for spreadbet accounts, it would be simple to extend to CFD's but I dont have CFD data or an interest in CFD's so have not done this. |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
You will need an API key to use this module, available free from IG Markets. |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=head1 VERSION |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
Version 0.102 |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
=cut |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
our $VERSION = '0.102'; |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
=head1 SYNOPSIS |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
use Finance::IG; |
37
|
|
|
|
|
|
|
use strict; |
38
|
|
|
|
|
|
|
no strict 'refs'; |
39
|
|
|
|
|
|
|
use warnings; |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
my $ig=iFinance::IG->new( |
42
|
|
|
|
|
|
|
username=> "demome", |
43
|
|
|
|
|
|
|
password=> "mypassword", |
44
|
|
|
|
|
|
|
apikey=> "4398232394029341776153276512736icab", |
45
|
|
|
|
|
|
|
isdemo=>0, |
46
|
|
|
|
|
|
|
); |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
my $p=$ig->positions(); # Get a list of positions |
49
|
|
|
|
|
|
|
$p=$ig->agg($p,$sortlist); # Aggregate them, so one item per instrument. |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
my $format="%-41sinstrumentName %+6.2fsize %-9.2flevel ". |
52
|
|
|
|
|
|
|
"%-9.2fbid £%-8.2fprofit %5.1fprofitpc%% £%10.2fatrisk\n", |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
$ig->printpos("stdout" , [], $format); |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
for my $position (@$p) |
57
|
|
|
|
|
|
|
{ |
58
|
|
|
|
|
|
|
$ig-> printpos("stdout" ,$position,$format); |
59
|
|
|
|
|
|
|
} |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
=head1 UTILITIES |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
The utility igdisp.pl is installed with this module and may be used to list your positions on IG. A help message can be obtained with igdisp.pl -h |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
=head1 SUBROUTINES/METHODS |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
This is a list of currently implemented methods |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=head2 new |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
Normal parameters, as above. |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
col=>1 |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
Causes Finance::IG to try to use Term::Chrome to do some simple coloration of output. |
76
|
|
|
|
|
|
|
If Term::Chrome is not installed, it will be silently ignored. See printpos. |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=head2 login |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
Originally needed to be called once after new and before other calls. Now this is done automatically, |
81
|
|
|
|
|
|
|
so you do not need to use this or be aware of it. Look for a 401 error if your password is |
82
|
|
|
|
|
|
|
wrong. |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
No Parameters. |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
=head2 printpos print out a formatted hash as one line |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
Parameters |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
file - can be a file handle or the string stdout or the glob *STDOUT |
92
|
|
|
|
|
|
|
A position of other shallow hash, |
93
|
|
|
|
|
|
|
A format string. The format string is similar to a printf format string, for example %s says print out a string |
94
|
|
|
|
|
|
|
however, the name of the item to be printed follows the letter, eg %sinstrumentName print the string instrument name. |
95
|
|
|
|
|
|
|
optional up |
96
|
|
|
|
|
|
|
optional down |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
A title line can be printed by either passing an array ref instead of a position, in which case the array ref can contain |
99
|
|
|
|
|
|
|
the titles to print. If the array is empty then the titles will be generated from the format string. |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
up and down can be provided and represent negative and posite limits on dbid element by default. |
102
|
|
|
|
|
|
|
Alternatively, provide up only and make it a subroutine ref. |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
The subroutime takes parameter a position, and should return escape characters (from Term::Chrome to colorise the line. |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
=head2 transactions - retrieve transactions history |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
transactions(++$page,Time::Piece->strptime("2020-01-01","%Y-%m-%d-%H.%M"),scalar localtime) |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
Parameters |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
Paging number, start at 1 |
113
|
|
|
|
|
|
|
Start time, can be a string or a Time::Piece |
114
|
|
|
|
|
|
|
Endtime |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
return a reference to an array of transactions for that time span. Each transaction is a hash of data. |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
=cut |
119
|
|
|
|
|
|
|
|
120
|
1
|
|
|
1
|
|
635
|
use Moose; |
|
1
|
|
|
|
|
483214
|
|
|
1
|
|
|
|
|
8
|
|
121
|
1
|
|
|
1
|
|
8386
|
use JSON; |
|
1
|
|
|
|
|
11161
|
|
|
1
|
|
|
|
|
5
|
|
122
|
1
|
|
|
1
|
|
695
|
use REST::Client; |
|
1
|
|
|
|
|
46198
|
|
|
1
|
|
|
|
|
42
|
|
123
|
|
|
|
|
|
|
#use Data::Dump qw(dump); # used in some commented out debug statements |
124
|
|
|
|
|
|
|
#use Scalar::Util; |
125
|
1
|
|
|
1
|
|
604
|
use Time::Piece; |
|
1
|
|
|
|
|
8084
|
|
|
1
|
|
|
|
|
5
|
|
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
BEGIN { |
128
|
1
|
50
|
|
1
|
|
213
|
if (eval("require Term::Chrome")) |
129
|
|
|
|
|
|
|
{ |
130
|
0
|
|
|
|
|
0
|
Term::Chrome->import(); |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
else |
133
|
|
|
|
|
|
|
{ |
134
|
1
|
|
|
0
|
1
|
5
|
map { eval ("sub $_ {}") } qw(Red Blue Bold Reset Underline Green color); # need these to avoid compile time errors. |
|
7
|
|
|
0
|
1
|
3491
|
|
|
|
|
|
0
|
1
|
|
|
|
|
|
|
0
|
1
|
|
|
|
|
|
|
0
|
1
|
|
|
|
|
|
|
0
|
1
|
|
|
|
|
|
|
0
|
1
|
|
|
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
has 'apikey' => ( |
138
|
|
|
|
|
|
|
is=>'ro', |
139
|
|
|
|
|
|
|
isa=>'Str', |
140
|
|
|
|
|
|
|
required=>1, |
141
|
|
|
|
|
|
|
); |
142
|
|
|
|
|
|
|
has 'username' => ( |
143
|
|
|
|
|
|
|
is=>'ro', |
144
|
|
|
|
|
|
|
isa=>'Str', |
145
|
|
|
|
|
|
|
required=>1, |
146
|
|
|
|
|
|
|
); |
147
|
|
|
|
|
|
|
has 'password' => ( |
148
|
|
|
|
|
|
|
is=>'ro', |
149
|
|
|
|
|
|
|
isa=>'Str', |
150
|
|
|
|
|
|
|
required=>1, |
151
|
|
|
|
|
|
|
); |
152
|
|
|
|
|
|
|
has 'isdemo' => ( |
153
|
|
|
|
|
|
|
is=>'ro', |
154
|
|
|
|
|
|
|
isa=>'Bool', |
155
|
|
|
|
|
|
|
required=>1, |
156
|
|
|
|
|
|
|
); |
157
|
|
|
|
|
|
|
has 'CST' => ( |
158
|
|
|
|
|
|
|
is=>'rw', |
159
|
|
|
|
|
|
|
isa=>'Str', |
160
|
|
|
|
|
|
|
); |
161
|
|
|
|
|
|
|
has 'XSECURITYTOKEN' => ( |
162
|
|
|
|
|
|
|
is=>'rw', |
163
|
|
|
|
|
|
|
isa=>'Str', |
164
|
|
|
|
|
|
|
); |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
has 'XSTTIME' => ( |
167
|
|
|
|
|
|
|
is=>'rw', |
168
|
|
|
|
|
|
|
isa=>'Int', |
169
|
|
|
|
|
|
|
); |
170
|
|
|
|
|
|
|
has 'col' => ( # set to 1 to use Term::Chrome for coloration. |
171
|
|
|
|
|
|
|
is=>'rw', |
172
|
|
|
|
|
|
|
isa=>'Bool', |
173
|
|
|
|
|
|
|
default=>0, |
174
|
|
|
|
|
|
|
); |
175
|
|
|
|
|
|
|
has 'uds' => ( |
176
|
|
|
|
|
|
|
is=>'rw', |
177
|
|
|
|
|
|
|
isa=>'Str', |
178
|
|
|
|
|
|
|
default=>'', |
179
|
|
|
|
|
|
|
); |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
around 'new' => sub { |
182
|
|
|
|
|
|
|
my $orig = shift; |
183
|
|
|
|
|
|
|
my $self = shift; |
184
|
|
|
|
|
|
|
my $r; |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
$r=$self->$orig(@_); |
187
|
|
|
|
|
|
|
$r->login; |
188
|
|
|
|
|
|
|
return $r; |
189
|
|
|
|
|
|
|
}; |
190
|
|
|
|
|
|
|
sub _url |
191
|
|
|
|
|
|
|
{ |
192
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
193
|
0
|
0
|
|
|
|
|
return 'https://demo-api.ig.com/gateway/deal' if ( $self->isdemo); |
194
|
0
|
|
|
|
|
|
return 'https://api.ig.com/gateway/deal'; |
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
########################################################################## |
199
|
|
|
|
|
|
|
=head2 login - loginto the account. |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
Parameters - none |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
login to the object, using the parameters provided to new. |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
You should call this just once per object after calling new. |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
=cut |
208
|
|
|
|
|
|
|
########################################################################## |
209
|
|
|
|
|
|
|
sub login { |
210
|
0
|
|
|
0
|
1
|
|
my ($self) = @_; |
211
|
0
|
|
|
|
|
|
my $headers = |
212
|
|
|
|
|
|
|
{ |
213
|
|
|
|
|
|
|
'Content-Type' => 'application/json; charset=UTF-8', |
214
|
|
|
|
|
|
|
'Accept' => 'application/json; charset=UTF-8', |
215
|
|
|
|
|
|
|
VERSION => 2, |
216
|
|
|
|
|
|
|
'X-IG-API-KEY'=> $self->apikey |
217
|
|
|
|
|
|
|
}; |
218
|
0
|
|
|
|
|
|
my $data = { |
219
|
|
|
|
|
|
|
identifier => $self->username, |
220
|
|
|
|
|
|
|
password => $self->password, |
221
|
|
|
|
|
|
|
}; |
222
|
|
|
|
|
|
|
# my $jdata = encode_json($data); |
223
|
0
|
|
|
|
|
|
my $jdata=JSON->new->canonical->encode($data); |
224
|
|
|
|
|
|
|
|
225
|
0
|
|
|
|
|
|
my $client = REST::Client->new(); |
226
|
0
|
|
|
|
|
|
$client->setHost($self->_url); |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
|
229
|
0
|
|
|
|
|
|
$client->POST ( |
230
|
|
|
|
|
|
|
'/session', |
231
|
|
|
|
|
|
|
$jdata, |
232
|
|
|
|
|
|
|
$headers |
233
|
|
|
|
|
|
|
); |
234
|
0
|
|
|
|
|
|
my $code=$client->responseCode(); |
235
|
0
|
0
|
|
|
|
|
die "response code from login $code" if ($code!=200); |
236
|
0
|
|
0
|
|
|
|
$self->CST($client->responseHeader('CST') // die "No CST header in login response"); |
237
|
0
|
|
0
|
|
|
|
$self->XSECURITYTOKEN($client->responseHeader('X-SECURITY-TOKEN') // die "No X-SECURITY-TOKEN in login response header"); |
238
|
0
|
|
|
|
|
|
$self->XSTTIME(time()); |
239
|
0
|
|
|
|
|
|
return; |
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
########################################################################## |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
=head2 flatten |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
Parameters |
246
|
|
|
|
|
|
|
1 Ref to array of hashes to flatten or a ref to a hash to flatten |
247
|
|
|
|
|
|
|
2 ref to an array of items to flatten, or just a single item name. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
Typical use of this is for a position that as it comes back from IG contains a market and a position |
250
|
|
|
|
|
|
|
byut we would prefer all items at the top level. This would moves all the keys of position and market up one level and |
251
|
|
|
|
|
|
|
would remove the keys market and position. |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
$self->flatten($hash, [qw(market position)]); |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
=cut |
256
|
|
|
|
|
|
|
########################################################################## |
257
|
|
|
|
|
|
|
sub flatten |
258
|
|
|
|
|
|
|
{ |
259
|
0
|
|
|
0
|
1
|
|
my ($self)=shift; |
260
|
0
|
|
|
|
|
|
my ($hash)=shift; |
261
|
0
|
|
|
|
|
|
my ($toflatten)=shift; |
262
|
|
|
|
|
|
|
|
263
|
0
|
0
|
|
|
|
|
$hash=[$hash] if (ref($hash) ne 'ARRAY'); |
264
|
0
|
0
|
|
|
|
|
$toflatten=[$toflatten] if (ref($toflatten) ne 'ARRAY'); |
265
|
|
|
|
|
|
|
|
266
|
0
|
|
|
|
|
|
for my $h (@$hash) |
267
|
|
|
|
|
|
|
{ |
268
|
0
|
|
|
|
|
|
for my $key (@$toflatten) |
269
|
|
|
|
|
|
|
{ |
270
|
0
|
0
|
|
|
|
|
if (exists($h->{$key})) |
271
|
|
|
|
|
|
|
{ |
272
|
0
|
0
|
|
|
|
|
if (defined($h->{$key})) |
273
|
|
|
|
|
|
|
{ |
274
|
0
|
0
|
|
|
|
|
die "key $key to flatten is not a hash" if (ref($h->{$key}) ne 'HASH'); |
275
|
0
|
|
|
|
|
|
for my $subkey (keys %{$h->{$key}}) |
|
0
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
{ |
277
|
0
|
0
|
|
|
|
|
die "subkey exists $subkey" if (exists($h->{$subkey})); |
278
|
0
|
|
|
|
|
|
$h->{$subkey}=$h->{$key}->{$subkey}; |
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
} |
281
|
0
|
|
|
|
|
|
delete $h->{$key}; |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
sub transactions |
287
|
|
|
|
|
|
|
{ |
288
|
|
|
|
|
|
|
|
289
|
0
|
|
|
0
|
1
|
|
my ($self) = shift; |
290
|
0
|
|
|
|
|
|
my ($pageNumber)=shift; |
291
|
0
|
|
|
|
|
|
my ($from) =shift; |
292
|
0
|
|
|
|
|
|
my ($to)=shift; |
293
|
|
|
|
|
|
|
|
294
|
0
|
|
|
|
|
|
my $pageSize=50; |
295
|
|
|
|
|
|
|
|
296
|
0
|
|
0
|
|
|
|
$from//=''; |
297
|
0
|
|
0
|
|
|
|
$to//=''; |
298
|
|
|
|
|
|
|
|
299
|
0
|
0
|
|
|
|
|
if (ref($to) eq 'Time::Piece') |
300
|
|
|
|
|
|
|
{ |
301
|
0
|
|
|
|
|
|
$to=$to->strftime("%Y-%m-%dT%H:%M:%S"); |
302
|
|
|
|
|
|
|
} |
303
|
0
|
0
|
|
|
|
|
if (ref($from) eq 'Time::Piece') |
304
|
|
|
|
|
|
|
{ |
305
|
0
|
|
|
|
|
|
$from=$from->strftime("%Y-%m-%dT%H:%M:%S"); |
306
|
|
|
|
|
|
|
} |
307
|
0
|
0
|
|
|
|
|
$to=~m/^[-0-9T:]*$/ or die "Invalid date format for 'to' $to, is a ".ref(\$to); |
308
|
0
|
0
|
|
|
|
|
$from=~m/^[-0-9T:]*$/ or die "Invalid date format for 'from' $from"; |
309
|
|
|
|
|
|
|
|
310
|
0
|
|
|
|
|
|
my $headers = { |
311
|
|
|
|
|
|
|
'Content-Type' => 'application/json; charset=UTF-8', |
312
|
|
|
|
|
|
|
'Accept' => 'application/json; charset=UTF-8', |
313
|
|
|
|
|
|
|
VERSION => 2, |
314
|
|
|
|
|
|
|
CST=>$self->CST, |
315
|
|
|
|
|
|
|
'X-SECURITY-TOKEN'=> $self->XSECURITYTOKEN, |
316
|
|
|
|
|
|
|
'X-IG-API-KEY'=> $self->apikey, |
317
|
|
|
|
|
|
|
}; |
318
|
|
|
|
|
|
|
#my $jheaders = encode_json($headers); |
319
|
0
|
|
|
|
|
|
my $jheaders=JSON->new->canonical->encode($headers); |
320
|
|
|
|
|
|
|
|
321
|
0
|
|
|
|
|
|
my $client = REST::Client->new(); |
322
|
0
|
|
|
|
|
|
$client->setHost($self->_url); |
323
|
|
|
|
|
|
|
|
324
|
0
|
0
|
|
|
|
|
$from and $from="from=$from"; |
325
|
0
|
0
|
|
|
|
|
$to and $to="to=$to"; |
326
|
0
|
|
|
|
|
|
my $rpage=$pageNumber; # requested page number as integer, 1 is first |
327
|
0
|
0
|
|
|
|
|
$pageNumber and $pageNumber="pageNumber=$pageNumber"; |
328
|
0
|
0
|
|
|
|
|
$pageSize and $pageSize="pageSize=$pageSize"; |
329
|
|
|
|
|
|
|
|
330
|
0
|
0
|
|
|
|
|
my $url=join '', map { $_?'&'.$_:'' } ($from,$to,$pageNumber,$pageSize); |
|
0
|
|
|
|
|
|
|
331
|
0
|
|
|
|
|
|
$url=~s/^&//; |
332
|
0
|
0
|
|
|
|
|
$url='?'.$url if ($url); |
333
|
|
|
|
|
|
|
|
334
|
0
|
|
|
|
|
|
$url='/history/transactions'.$url; |
335
|
0
|
|
|
|
|
|
$client->GET ( |
336
|
|
|
|
|
|
|
$url, |
337
|
|
|
|
|
|
|
$headers |
338
|
|
|
|
|
|
|
); |
339
|
|
|
|
|
|
|
|
340
|
0
|
|
|
|
|
|
my $code=$client->responseCode(); |
341
|
0
|
0
|
|
|
|
|
if ($code==200) |
342
|
|
|
|
|
|
|
{ |
343
|
0
|
|
|
|
|
|
my $resp=decode_json($client->responseContent()); |
344
|
|
|
|
|
|
|
# $resp=$self->flatten($resp,[qw/transactions metadata/]); |
345
|
|
|
|
|
|
|
#die encode_json($resp); |
346
|
|
|
|
|
|
|
|
347
|
0
|
|
|
|
|
|
my @activities=@{$resp->{transactions}}; |
|
0
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
# pncerint encode_json( $resp->{metadata} ); |
349
|
|
|
|
|
|
|
# {"pageData":{"totalPages":11,"pageNumber":11,"pageSize":50},"size":534}***** 34 |
350
|
0
|
0
|
|
|
|
|
return undef if ($rpage > $resp->{metadata}->{pageData}->{pageNumber}); |
351
|
|
|
|
|
|
|
# return undef if (@activities==0); |
352
|
0
|
|
|
|
|
|
return \@activities; |
353
|
|
|
|
|
|
|
} |
354
|
|
|
|
|
|
|
else |
355
|
|
|
|
|
|
|
{ |
356
|
0
|
|
|
|
|
|
print "failed $code: ".$client->responseContent()."\n"; |
357
|
0
|
|
|
|
|
|
return undef; |
358
|
|
|
|
|
|
|
} |
359
|
|
|
|
|
|
|
} |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
# example from/ to sting format: |
362
|
|
|
|
|
|
|
# 2020-10-28 |
363
|
|
|
|
|
|
|
# 2020-10-28T15:30 |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
# keys in retirn, when called with detailed=1 |
366
|
|
|
|
|
|
|
# type, goodTillDate, actions(ARRAY) , epic, direction, level, channel, marketName, date, dealReference, guaranteedStop, stopLevel, size, currency, stopDistance, trailingStep, status, trailingStopDistance, limitLevel, description, dealId, period, limitDistance |
367
|
|
|
|
|
|
|
# without: |
368
|
|
|
|
|
|
|
# period, details, date, dealId, epic, description, channel, status, type |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
sub history |
371
|
|
|
|
|
|
|
{ |
372
|
0
|
|
|
0
|
1
|
|
my ($self) = shift; |
373
|
0
|
|
|
|
|
|
my ($detailed)=shift; ## undef, not detailed, 1 for detailed. |
374
|
0
|
|
|
|
|
|
my ($pageNumber)=shift; |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
|
377
|
0
|
|
|
|
|
|
my ($from) = shift; |
378
|
0
|
|
|
|
|
|
my ($to) = shift; |
379
|
|
|
|
|
|
|
|
380
|
0
|
|
0
|
|
|
|
$pageNumber//=''; |
381
|
0
|
|
|
|
|
|
my $pageSize=50; |
382
|
|
|
|
|
|
|
|
383
|
0
|
|
0
|
|
|
|
$from//=''; |
384
|
0
|
|
0
|
|
|
|
$to//=''; |
385
|
|
|
|
|
|
|
|
386
|
0
|
0
|
|
|
|
|
if (ref($to) eq 'Time::Piece') |
387
|
|
|
|
|
|
|
{ |
388
|
0
|
|
|
|
|
|
$to=$to->strftime("%Y-%m-%dT%H:%M:%S"); |
389
|
|
|
|
|
|
|
} |
390
|
0
|
0
|
|
|
|
|
if (ref($from) eq 'Time::Piece') |
391
|
|
|
|
|
|
|
{ |
392
|
0
|
|
|
|
|
|
$from=$from->strftime("%Y-%m-%dT%H:%M:%S"); |
393
|
|
|
|
|
|
|
} |
394
|
|
|
|
|
|
|
|
395
|
0
|
0
|
|
|
|
|
$to=~m/^[-0-9T:]*$/ or die "Invalid date format for 'to' $to"; |
396
|
0
|
0
|
|
|
|
|
$from=~m/^[-0-9T:]*$/ or die "Invalid date format for 'from' $from"; |
397
|
|
|
|
|
|
|
|
398
|
0
|
|
|
|
|
|
my $headers = { |
399
|
|
|
|
|
|
|
'Content-Type' => 'application/json; charset=UTF-8', |
400
|
|
|
|
|
|
|
'Accept' => 'application/json; charset=UTF-8', |
401
|
|
|
|
|
|
|
VERSION => 2, |
402
|
|
|
|
|
|
|
CST=>$self->CST, |
403
|
|
|
|
|
|
|
'X-SECURITY-TOKEN'=> $self->XSECURITYTOKEN, |
404
|
|
|
|
|
|
|
'X-IG-API-KEY'=> $self->apikey, |
405
|
|
|
|
|
|
|
}; |
406
|
|
|
|
|
|
|
#my $jheaders = encode_json($headers); |
407
|
0
|
|
|
|
|
|
my $jheaders=JSON->new->canonical->encode($headers); |
408
|
|
|
|
|
|
|
|
409
|
0
|
|
|
|
|
|
my $client = REST::Client->new(); |
410
|
0
|
|
|
|
|
|
$client->setHost($self->_url); |
411
|
0
|
0
|
|
|
|
|
$from="from=$from" if ($from ne ''); |
412
|
0
|
0
|
|
|
|
|
$to="to=$to" if ($to ne ''); |
413
|
0
|
0
|
|
|
|
|
if ($detailed) |
414
|
|
|
|
|
|
|
{ |
415
|
0
|
|
|
|
|
|
$detailed="detailed=true" |
416
|
|
|
|
|
|
|
} |
417
|
|
|
|
|
|
|
else |
418
|
|
|
|
|
|
|
{ |
419
|
0
|
|
|
|
|
|
$detailed=''; |
420
|
|
|
|
|
|
|
} |
421
|
|
|
|
|
|
|
|
422
|
0
|
0
|
|
|
|
|
$pageNumber="pageNumber=$pageNumber" if ($pageNumber); |
423
|
0
|
|
0
|
|
|
|
$pageSize//=''; |
424
|
0
|
0
|
|
|
|
|
$pageSize="pageSize=$pageSize" if ($pageSize); |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
# my $sep='?'; |
427
|
|
|
|
|
|
|
# map { $_ eq '' or $_=$sep.$_ and $sep='&'} ($from,$to,$detailed,$pageNumber,$pageSize); |
428
|
|
|
|
|
|
|
|
429
|
0
|
0
|
|
|
|
|
my $url=join '', map { $_?'&'.$_:'' } ($from,$to,$detailed,$pageNumber,$pageSize); |
|
0
|
|
|
|
|
|
|
430
|
0
|
|
|
|
|
|
$url=~s/^&//; |
431
|
0
|
0
|
|
|
|
|
$url='?'.$url if ($url); |
432
|
|
|
|
|
|
|
|
433
|
0
|
|
|
|
|
|
$url='/history/activity'.$url; |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
# die $url; |
436
|
|
|
|
|
|
|
|
437
|
0
|
|
|
|
|
|
$client->GET ( |
438
|
|
|
|
|
|
|
$url, |
439
|
|
|
|
|
|
|
$headers |
440
|
|
|
|
|
|
|
); |
441
|
|
|
|
|
|
|
|
442
|
0
|
|
|
|
|
|
my $code=$client->responseCode(); |
443
|
|
|
|
|
|
|
|
444
|
0
|
0
|
|
|
|
|
if ($code==200) |
445
|
|
|
|
|
|
|
{ |
446
|
0
|
|
|
|
|
|
my $resp=decode_json($client->responseContent()); |
447
|
0
|
|
|
|
|
|
my @activities=@{$resp->{activities}}; |
|
0
|
|
|
|
|
|
|
448
|
0
|
0
|
|
|
|
|
return undef if (@activities==0); |
449
|
0
|
|
|
|
|
|
$self->flatten(\@activities,'details'); |
450
|
0
|
|
|
|
|
|
return \@activities; |
451
|
|
|
|
|
|
|
} |
452
|
|
|
|
|
|
|
else |
453
|
|
|
|
|
|
|
{ |
454
|
0
|
|
|
|
|
|
print "failed $code: ".$client->responseContent()."\n"; |
455
|
0
|
|
|
|
|
|
return undef; |
456
|
|
|
|
|
|
|
} |
457
|
|
|
|
|
|
|
} |
458
|
|
|
|
|
|
|
# example response: |
459
|
|
|
|
|
|
|
#{"metadata":{"paging":{"size":50,"next":"/history/activity?version=3&from=2020-10-28T00:00:00&to=2020-10-29T16:41:45&detailed=false&pageSize=50"}} |
460
|
|
|
|
|
|
|
# "activities": [.... ] |
461
|
|
|
|
|
|
|
# } |
462
|
|
|
|
|
|
|
# each activity looks like: |
463
|
|
|
|
|
|
|
#{ |
464
|
|
|
|
|
|
|
# details=>null, |
465
|
|
|
|
|
|
|
# dealId=>"DIAAAAFRS39HJAK", |
466
|
|
|
|
|
|
|
# period=>"DFB", |
467
|
|
|
|
|
|
|
# type=>"POSITION", |
468
|
|
|
|
|
|
|
# epic=>"UA.D.ATVI.DAILY.IP", |
469
|
|
|
|
|
|
|
# description=>"Position partially closed=> J6GK8WA9", |
470
|
|
|
|
|
|
|
# date=>"2020-10-29T17:47:46", |
471
|
|
|
|
|
|
|
# status=>"ACCEPTED", |
472
|
|
|
|
|
|
|
# channel=>"SYSTEM" |
473
|
|
|
|
|
|
|
# |
474
|
|
|
|
|
|
|
# or with detail |
475
|
|
|
|
|
|
|
# {"activities": |
476
|
|
|
|
|
|
|
# [ |
477
|
|
|
|
|
|
|
# [ |
478
|
|
|
|
|
|
|
# {"date":"2020-11-19T18:41:04", |
479
|
|
|
|
|
|
|
# "epic":"UC.D.MU.DAILY.IP", |
480
|
|
|
|
|
|
|
# "period":"DFB", |
481
|
|
|
|
|
|
|
# "dealId":"DIAAAAFVXZV5LA5", |
482
|
|
|
|
|
|
|
# "channel":"WEB", |
483
|
|
|
|
|
|
|
# "type":"POSITION", |
484
|
|
|
|
|
|
|
# "status":"ACCEPTED", |
485
|
|
|
|
|
|
|
# "description":"Position opened: VXZV5LA5", |
486
|
|
|
|
|
|
|
# "details": |
487
|
|
|
|
|
|
|
# { |
488
|
|
|
|
|
|
|
# "dealReference":"6XQESB1EQGWY4FR2", |
489
|
|
|
|
|
|
|
# "actions": |
490
|
|
|
|
|
|
|
# [ |
491
|
|
|
|
|
|
|
# {"actionType":"POSITION_OPENED", |
492
|
|
|
|
|
|
|
# "affectedDealId":"DIAAAAFVXZV5LA5" |
493
|
|
|
|
|
|
|
# } |
494
|
|
|
|
|
|
|
# ], |
495
|
|
|
|
|
|
|
# "marketName":"Micron Technology Inc (All Sessions)", |
496
|
|
|
|
|
|
|
# "goodTillDate":null, |
497
|
|
|
|
|
|
|
# "currency":"GBP", |
498
|
|
|
|
|
|
|
# "size":0.4, |
499
|
|
|
|
|
|
|
# "direction":"BUY", |
500
|
|
|
|
|
|
|
# "level":6123, |
501
|
|
|
|
|
|
|
# "stopLevel":null, |
502
|
|
|
|
|
|
|
# "stopDistance":null, |
503
|
|
|
|
|
|
|
# "guaranteedStop":false, |
504
|
|
|
|
|
|
|
# "trailingStopDistance":null, |
505
|
|
|
|
|
|
|
# "trailingStep":null, |
506
|
|
|
|
|
|
|
# "limitLevel":null, |
507
|
|
|
|
|
|
|
# "limitDistance":null |
508
|
|
|
|
|
|
|
# } |
509
|
|
|
|
|
|
|
# }, |
510
|
|
|
|
|
|
|
# {"date":"2020-11-17T11:33:52", |
511
|
|
|
|
|
|
|
#"epic":"KA.D.FSTA.DAILY.IP", |
512
|
|
|
|
|
|
|
#"period":"DFB", |
513
|
|
|
|
|
|
|
#"dealId":"DIAAAAFVEFD4GAG", |
514
|
|
|
|
|
|
|
#"channel":"WEB", |
515
|
|
|
|
|
|
|
#"type":"POSITION", |
516
|
|
|
|
|
|
|
#"status":"ACCEPTED", |
517
|
|
|
|
|
|
|
#"description":"Position/s closed: HH93GXAZ", |
518
|
|
|
|
|
|
|
#"details":{"dealReference":"6XQESB1EQAZNR6V3", |
519
|
|
|
|
|
|
|
#"actions":[{"actionType":"POSITION_CLOSED", |
520
|
|
|
|
|
|
|
#"affectedDealId":"DIAAAAFHH93GXAZ"}], |
521
|
|
|
|
|
|
|
#"marketName":"Fuller Smith & Turner", |
522
|
|
|
|
|
|
|
#"goodTillDate":null, |
523
|
|
|
|
|
|
|
#"currency":"GBP", |
524
|
|
|
|
|
|
|
#"size":1, |
525
|
|
|
|
|
|
|
#"direction":"SELL", |
526
|
|
|
|
|
|
|
#"level":726.2, |
527
|
|
|
|
|
|
|
#"stopLevel":null, |
528
|
|
|
|
|
|
|
#"stopDistance":null, |
529
|
|
|
|
|
|
|
#"guaranteedStop":false, |
530
|
|
|
|
|
|
|
#"trailingStopDistance":null, |
531
|
|
|
|
|
|
|
#"trailingStep":null, |
532
|
|
|
|
|
|
|
#"limitLevel":null, |
533
|
|
|
|
|
|
|
#"limitDistance":null}}, |
534
|
|
|
|
|
|
|
#} |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
# with detailed=1 |
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
#{ |
539
|
|
|
|
|
|
|
# "activities": [ |
540
|
|
|
|
|
|
|
# { |
541
|
|
|
|
|
|
|
# "date": "2020-11-19T18:41:04", |
542
|
|
|
|
|
|
|
# "epic": "UC.D.MU.DAILY.IP", |
543
|
|
|
|
|
|
|
# "period": "DFB", |
544
|
|
|
|
|
|
|
# "dealId": "DIAAAAFVXZV5LA5", |
545
|
|
|
|
|
|
|
# "channel": "WEB", |
546
|
|
|
|
|
|
|
# "type": "POSITION", |
547
|
|
|
|
|
|
|
# "status": "ACCEPTED", |
548
|
|
|
|
|
|
|
# "description": "Position opened: VXZV5LA5", |
549
|
|
|
|
|
|
|
# "details": { |
550
|
|
|
|
|
|
|
# "dealReference": "6XQESB1EQGWY4FR2", |
551
|
|
|
|
|
|
|
# "actions": [ |
552
|
|
|
|
|
|
|
# { |
553
|
|
|
|
|
|
|
# "actionType": "POSITION_OPENED", |
554
|
|
|
|
|
|
|
# "affectedDealId": "DIAAAAFVXZV5LA5" |
555
|
|
|
|
|
|
|
# } |
556
|
|
|
|
|
|
|
# ], |
557
|
|
|
|
|
|
|
# "marketName": "Micron Technology Inc (All Sessions)", |
558
|
|
|
|
|
|
|
# "goodTillDate": null, |
559
|
|
|
|
|
|
|
# "currency": "GBP", |
560
|
|
|
|
|
|
|
# "size": 0.4, |
561
|
|
|
|
|
|
|
# "direction": "BUY", |
562
|
|
|
|
|
|
|
# "level": 6123, |
563
|
|
|
|
|
|
|
# "stopLevel": null, |
564
|
|
|
|
|
|
|
# "stopDistance": null, |
565
|
|
|
|
|
|
|
# "guaranteedStop": false, |
566
|
|
|
|
|
|
|
# "trailingStopDistance": null, |
567
|
|
|
|
|
|
|
# "trailingStep": null, |
568
|
|
|
|
|
|
|
# "limitLevel": null, |
569
|
|
|
|
|
|
|
# "limitDistance": null |
570
|
|
|
|
|
|
|
# } |
571
|
|
|
|
|
|
|
# }, |
572
|
|
|
|
|
|
|
# { |
573
|
|
|
|
|
|
|
# "date": "2020-11-17T11:33:52", |
574
|
|
|
|
|
|
|
# "epic": "KA.D.FSTA.DAILY.IP", |
575
|
|
|
|
|
|
|
# "period": "DFB", |
576
|
|
|
|
|
|
|
# "dealId": "DIAAAAFVEFD4GAG", |
577
|
|
|
|
|
|
|
# "channel": "WEB", |
578
|
|
|
|
|
|
|
# "type": "POSITION", |
579
|
|
|
|
|
|
|
# "status": "ACCEPTED", |
580
|
|
|
|
|
|
|
# "description": "Position/s closed: HH93GXAZ", |
581
|
|
|
|
|
|
|
# "details": { |
582
|
|
|
|
|
|
|
# "dealReference": "6XQESB1EQAZNR6V3", |
583
|
|
|
|
|
|
|
# "actions": [ |
584
|
|
|
|
|
|
|
# { |
585
|
|
|
|
|
|
|
# "actionType": "POSITION_CLOSED", |
586
|
|
|
|
|
|
|
# "affectedDealId": "DIAAAAFHH93GXAZ" |
587
|
|
|
|
|
|
|
# } |
588
|
|
|
|
|
|
|
# ], |
589
|
|
|
|
|
|
|
# "marketName": "Fuller Smith & Turner", |
590
|
|
|
|
|
|
|
# "goodTillDate": null, |
591
|
|
|
|
|
|
|
# "currency": "GBP", |
592
|
|
|
|
|
|
|
# "size": 1, |
593
|
|
|
|
|
|
|
# "direction": "SELL", |
594
|
|
|
|
|
|
|
# "level": 726.2, |
595
|
|
|
|
|
|
|
# "stopLevel": null, |
596
|
|
|
|
|
|
|
# "stopDistance": null, |
597
|
|
|
|
|
|
|
# "guaranteedStop": false, |
598
|
|
|
|
|
|
|
# "trailingStopDistance": null, |
599
|
|
|
|
|
|
|
# "trailingStep": null, |
600
|
|
|
|
|
|
|
# "limitLevel": null, |
601
|
|
|
|
|
|
|
# "limitDistance": null |
602
|
|
|
|
|
|
|
# } |
603
|
|
|
|
|
|
|
# }, |
604
|
|
|
|
|
|
|
# { |
605
|
|
|
|
|
|
|
# "date": "2020-11-17T11:33:09", |
606
|
|
|
|
|
|
|
# "epic": "KA.D.FSTA.DAILY.IP", |
607
|
|
|
|
|
|
|
# "period": "DFB", |
608
|
|
|
|
|
|
|
# "dealId": "DIAAAAFVEFBBKA4", |
609
|
|
|
|
|
|
|
# "channel": "WEB", |
610
|
|
|
|
|
|
|
# "type": "POSITION", |
611
|
|
|
|
|
|
|
# "status": "ACCEPTED", |
612
|
|
|
|
|
|
|
# "description": "Position opened: VEFBBKA4", |
613
|
|
|
|
|
|
|
# "details": { |
614
|
|
|
|
|
|
|
# "dealReference": "6XQESB1EQAZKR1V2", |
615
|
|
|
|
|
|
|
# "actions": [ |
616
|
|
|
|
|
|
|
# { |
617
|
|
|
|
|
|
|
# "actionType": "POSITION_OPENED", |
618
|
|
|
|
|
|
|
# "affectedDealId": "DIAAAAFVEFBBKA4" |
619
|
|
|
|
|
|
|
# } |
620
|
|
|
|
|
|
|
# ], |
621
|
|
|
|
|
|
|
# "marketName": "Fuller Smith & Turner", |
622
|
|
|
|
|
|
|
# "goodTillDate": null, |
623
|
|
|
|
|
|
|
# "currency": "GBP", |
624
|
|
|
|
|
|
|
# "size": 2, |
625
|
|
|
|
|
|
|
# "direction": "BUY", |
626
|
|
|
|
|
|
|
# "level": 779.9, |
627
|
|
|
|
|
|
|
# "stopLevel": null, |
628
|
|
|
|
|
|
|
# "stopDistance": null, |
629
|
|
|
|
|
|
|
# "guaranteedStop": false, |
630
|
|
|
|
|
|
|
# "trailingStopDistance": null, |
631
|
|
|
|
|
|
|
# "trailingStep": null, |
632
|
|
|
|
|
|
|
# "limitLevel": null, |
633
|
|
|
|
|
|
|
# "limitDistance": null |
634
|
|
|
|
|
|
|
# } |
635
|
|
|
|
|
|
|
# }, |
636
|
|
|
|
|
|
|
# { |
637
|
|
|
|
|
|
|
# "date": "2020-11-16T17:17:29", |
638
|
|
|
|
|
|
|
# "epic": "UD.D.WIXUS.DAILY.IP", |
639
|
|
|
|
|
|
|
# "period": "DFB", |
640
|
|
|
|
|
|
|
# "dealId": "DIAAAAFU94TQRAR", |
641
|
|
|
|
|
|
|
# "channel": "WEB", |
642
|
|
|
|
|
|
|
# "type": "POSITION", |
643
|
|
|
|
|
|
|
# "status": "ACCEPTED", |
644
|
|
|
|
|
|
|
# "description": "Position opened: U94TQRAR", |
645
|
|
|
|
|
|
|
# "details": { |
646
|
|
|
|
|
|
|
# "dealReference": "6XQESB1EQ90XNSR2", |
647
|
|
|
|
|
|
|
# "actions": [ |
648
|
|
|
|
|
|
|
# { |
649
|
|
|
|
|
|
|
# "actionType": "POSITION_OPENED", |
650
|
|
|
|
|
|
|
# "affectedDealId": "DIAAAAFU94TQRAR" |
651
|
|
|
|
|
|
|
# } |
652
|
|
|
|
|
|
|
# ], |
653
|
|
|
|
|
|
|
# "marketName": "Wix.com Ltd", |
654
|
|
|
|
|
|
|
# "goodTillDate": null, |
655
|
|
|
|
|
|
|
# "currency": "GBP", |
656
|
|
|
|
|
|
|
# "size": 0.31, |
657
|
|
|
|
|
|
|
# "direction": "BUY", |
658
|
|
|
|
|
|
|
# "level": 24142, |
659
|
|
|
|
|
|
|
# "stopLevel": null, |
660
|
|
|
|
|
|
|
# "stopDistance": null, |
661
|
|
|
|
|
|
|
# "guaranteedStop": false, |
662
|
|
|
|
|
|
|
# "trailingStopDistance": null, |
663
|
|
|
|
|
|
|
# "trailingStep": null, |
664
|
|
|
|
|
|
|
# "limitLevel": null, |
665
|
|
|
|
|
|
|
# "limitDistance": null |
666
|
|
|
|
|
|
|
# } |
667
|
|
|
|
|
|
|
# }, |
668
|
|
|
|
|
|
|
# { |
669
|
|
|
|
|
|
|
# "date": "2020-11-16T17:08:33", |
670
|
|
|
|
|
|
|
# "epic": "UD.D.ZMUS.DAILY.IP", |
671
|
|
|
|
|
|
|
# "period": "DFB", |
672
|
|
|
|
|
|
|
# "dealId": "DIAAAAFU924B7A3", |
673
|
|
|
|
|
|
|
# etc.... |
674
|
|
|
|
|
|
|
########################################################################## |
675
|
|
|
|
|
|
|
# |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
=head2 accounts - retrieve a list of accounts |
678
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
Parameters - none |
680
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
Return value - Array ref containing hashes of accounts. |
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
=cut |
684
|
|
|
|
|
|
|
########################################################################## |
685
|
|
|
|
|
|
|
sub accounts |
686
|
|
|
|
|
|
|
{ |
687
|
0
|
|
|
0
|
1
|
|
my ($self) = shift; |
688
|
|
|
|
|
|
|
|
689
|
0
|
|
|
|
|
|
my $headers = { |
690
|
|
|
|
|
|
|
'Content-Type' => 'application/json; charset=UTF-8', |
691
|
|
|
|
|
|
|
'Accept' => 'application/json; charset=UTF-8', |
692
|
|
|
|
|
|
|
VERSION => 1, |
693
|
|
|
|
|
|
|
CST=>$self->CST, |
694
|
|
|
|
|
|
|
'X-SECURITY-TOKEN'=> $self->XSECURITYTOKEN, |
695
|
|
|
|
|
|
|
'X-IG-API-KEY'=> $self->apikey, |
696
|
|
|
|
|
|
|
}; |
697
|
|
|
|
|
|
|
#my $jheaders = encode_json($headers); |
698
|
0
|
|
|
|
|
|
my $jheaders=JSON->new->canonical->encode($headers); |
699
|
|
|
|
|
|
|
|
700
|
0
|
|
|
|
|
|
my $client = REST::Client->new(); |
701
|
0
|
|
|
|
|
|
$client->setHost($self->_url); |
702
|
0
|
|
|
|
|
|
my $r=$client->GET ( '/accounts', $headers); |
703
|
|
|
|
|
|
|
|
704
|
0
|
|
|
|
|
|
my $resp=decode_json($client->responseContent()); |
705
|
|
|
|
|
|
|
|
706
|
0
|
|
|
|
|
|
my $accounts=[]; |
707
|
0
|
|
|
|
|
|
@$accounts=@{$resp->{accounts}}; |
|
0
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
|
709
|
0
|
|
|
|
|
|
return $accounts; |
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
} |
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
# Typical return data: |
714
|
|
|
|
|
|
|
#[ |
715
|
|
|
|
|
|
|
# {"accountId":"...", |
716
|
|
|
|
|
|
|
# "status":"ENABLED", |
717
|
|
|
|
|
|
|
# "canTransferFrom":true, |
718
|
|
|
|
|
|
|
# "preferred":true, |
719
|
|
|
|
|
|
|
# "accountAlias":null, |
720
|
|
|
|
|
|
|
# "accountType":"SPREADBET", |
721
|
|
|
|
|
|
|
# "accountName":"Spread bet", |
722
|
|
|
|
|
|
|
# "balance":{ |
723
|
|
|
|
|
|
|
# "deposit":89051.36, |
724
|
|
|
|
|
|
|
# "balance":152475.8, |
725
|
|
|
|
|
|
|
# "available":85942.65, |
726
|
|
|
|
|
|
|
# "profitLoss":22518.21 |
727
|
|
|
|
|
|
|
# }, |
728
|
|
|
|
|
|
|
# "canTransferTo":true, |
729
|
|
|
|
|
|
|
# "currency":"GBP" |
730
|
|
|
|
|
|
|
# }, |
731
|
|
|
|
|
|
|
# {"accountId":"...", |
732
|
|
|
|
|
|
|
# "status":"ENABLED", |
733
|
|
|
|
|
|
|
# "canTransferFrom":true, |
734
|
|
|
|
|
|
|
# "preferred":false, |
735
|
|
|
|
|
|
|
# "accountAlias":null, |
736
|
|
|
|
|
|
|
# "accountType":"CFD", |
737
|
|
|
|
|
|
|
# "accountName":"CFD", |
738
|
|
|
|
|
|
|
# "balance":{ |
739
|
|
|
|
|
|
|
# "available":0, |
740
|
|
|
|
|
|
|
# "profitLoss":0, |
741
|
|
|
|
|
|
|
# "balance":0, |
742
|
|
|
|
|
|
|
# "deposit":0 |
743
|
|
|
|
|
|
|
# }, |
744
|
|
|
|
|
|
|
# "canTransferTo":true, |
745
|
|
|
|
|
|
|
# "currency":"GBP" |
746
|
|
|
|
|
|
|
# } |
747
|
|
|
|
|
|
|
#] |
748
|
|
|
|
|
|
|
########################################################################## |
749
|
|
|
|
|
|
|
# |
750
|
|
|
|
|
|
|
# Return a ref to an array of positions. Each position is |
751
|
|
|
|
|
|
|
# a variable structure deep hash |
752
|
|
|
|
|
|
|
# |
753
|
|
|
|
|
|
|
########################################################################## |
754
|
|
|
|
|
|
|
sub positions |
755
|
|
|
|
|
|
|
{ |
756
|
0
|
|
|
0
|
1
|
|
my ($self) = shift; |
757
|
|
|
|
|
|
|
|
758
|
0
|
|
|
|
|
|
my $headers = { |
759
|
|
|
|
|
|
|
'Content-Type' => 'application/json; charset=UTF-8', |
760
|
|
|
|
|
|
|
'Accept' => 'application/json; charset=UTF-8', |
761
|
|
|
|
|
|
|
VERSION => 2, |
762
|
|
|
|
|
|
|
# 'IG-ACCOUNT-ID'=> $accountid, |
763
|
|
|
|
|
|
|
CST=>$self->CST, |
764
|
|
|
|
|
|
|
'X-SECURITY-TOKEN'=> $self->XSECURITYTOKEN, |
765
|
|
|
|
|
|
|
'X-IG-API-KEY'=> $self->apikey, |
766
|
|
|
|
|
|
|
}; |
767
|
|
|
|
|
|
|
#my $jheaders=JSON->new->canonical->encode($headers); # for debug |
768
|
|
|
|
|
|
|
|
769
|
0
|
|
|
|
|
|
my $client = REST::Client->new(); |
770
|
0
|
|
|
|
|
|
$client->setHost($self->_url); |
771
|
|
|
|
|
|
|
#my $r; |
772
|
|
|
|
|
|
|
# $headers->{VERSION}=2; |
773
|
|
|
|
|
|
|
#$r=$client->GET ( |
774
|
0
|
|
|
|
|
|
$client->GET ( '/positions', |
775
|
|
|
|
|
|
|
$headers |
776
|
|
|
|
|
|
|
); |
777
|
0
|
|
|
|
|
|
my $resp=decode_json($client->responseContent()); |
778
|
|
|
|
|
|
|
|
779
|
0
|
|
|
|
|
|
my $positions=[]; |
780
|
0
|
|
|
|
|
|
@$positions=@{$resp->{positions}}; |
|
0
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
|
782
|
0
|
|
|
|
|
|
return $positions; |
783
|
|
|
|
|
|
|
} |
784
|
|
|
|
|
|
|
# example of the structure of a position |
785
|
|
|
|
|
|
|
# Regeneron Pharmaceuticals Inc, 0.06 |
786
|
|
|
|
|
|
|
# { |
787
|
|
|
|
|
|
|
# "position" : { |
788
|
|
|
|
|
|
|
# "trailingStopDistance" : null, |
789
|
|
|
|
|
|
|
# "size" : 0.06, |
790
|
|
|
|
|
|
|
# "limitedRiskPremium" : null, |
791
|
|
|
|
|
|
|
# "stopLevel" : 50128, |
792
|
|
|
|
|
|
|
# "direction" : "BUY", |
793
|
|
|
|
|
|
|
# "level" : 50303, |
794
|
|
|
|
|
|
|
# "dealReference" : "6XQESB1E506WW334", |
795
|
|
|
|
|
|
|
# "controlledRisk" : false, |
796
|
|
|
|
|
|
|
# "currency" : "GBP", |
797
|
|
|
|
|
|
|
# "contractSize" : 1, |
798
|
|
|
|
|
|
|
# "createdDateUTC" : "2020-04-03T14:26:07", |
799
|
|
|
|
|
|
|
# "trailingStep" : null, |
800
|
|
|
|
|
|
|
# "createdDate" : "2020/04/03 15:26:07:000", |
801
|
|
|
|
|
|
|
# "limitLevel" : null, |
802
|
|
|
|
|
|
|
# "dealId" : "DIAAAAEL2T7AEAS" |
803
|
|
|
|
|
|
|
# }, |
804
|
|
|
|
|
|
|
# "market" : { |
805
|
|
|
|
|
|
|
# "lotSize" : 1, |
806
|
|
|
|
|
|
|
# "marketStatus" : "EDITS_ONLY", |
807
|
|
|
|
|
|
|
# "instrumentType" : "SHARES", |
808
|
|
|
|
|
|
|
# "expiry" : "DFB", |
809
|
|
|
|
|
|
|
# "streamingPricesAvailable" : false, |
810
|
|
|
|
|
|
|
# "instrumentName" : "Regeneron Pharmaceuticals Inc", |
811
|
|
|
|
|
|
|
# "offer" : 60261, |
812
|
|
|
|
|
|
|
# "delayTime" : 0, |
813
|
|
|
|
|
|
|
# "updateTime" : "20:59:56", |
814
|
|
|
|
|
|
|
# "high" : 61455, |
815
|
|
|
|
|
|
|
# "percentageChange" : -2.01, |
816
|
|
|
|
|
|
|
# "netChange" : -1236, |
817
|
|
|
|
|
|
|
# "low" : 59886, |
818
|
|
|
|
|
|
|
# "bid" : 60261, |
819
|
|
|
|
|
|
|
# "updateTimeUTC" : "19:59:56", |
820
|
|
|
|
|
|
|
# "scalingFactor" : 1, |
821
|
|
|
|
|
|
|
# "epic" : "UC.D.REGN.DAILY.IP" |
822
|
|
|
|
|
|
|
# } |
823
|
|
|
|
|
|
|
# } |
824
|
|
|
|
|
|
|
##################################################################### |
825
|
|
|
|
|
|
|
# Aggregate an array of positions into an array of unique |
826
|
|
|
|
|
|
|
# positions with 1 element per instrument, Items will be combined |
827
|
|
|
|
|
|
|
# where more than one position is combined, in a field dependent way. |
828
|
|
|
|
|
|
|
# for exeample sizes will be added as will be profit |
829
|
|
|
|
|
|
|
# a reference to an array is expected and a reference to a new array |
830
|
|
|
|
|
|
|
# returned. |
831
|
|
|
|
|
|
|
##################################################################### |
832
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
=head2 agg - aggregate positions into a flattened 1 element per instrument form. |
834
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
Parameters |
836
|
|
|
|
|
|
|
|
837
|
|
|
|
|
|
|
1 Reference to an array of positions |
838
|
|
|
|
|
|
|
2 (Optional) Ref to an array of keys to sort on |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
agg does three things actually. First, it joins together multiple positions of the same instrument, |
841
|
|
|
|
|
|
|
generating sensible values for things like profit/loss and size |
842
|
|
|
|
|
|
|
|
843
|
|
|
|
|
|
|
Second, it performs some flattening of the deep structure for a position which comes from IG. |
844
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
Third it sorts the result. The default sort order I use is -profitpc instrumentName, but |
846
|
|
|
|
|
|
|
you can provide a 2rd parameter, a reference to an array of items to sort by. |
847
|
|
|
|
|
|
|
Each item can optionally be preceeded by - to reverse the prder. If the first item equates equal, then |
848
|
|
|
|
|
|
|
the next item is used. |
849
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
=cut |
851
|
|
|
|
|
|
|
##################################################################### |
852
|
|
|
|
|
|
|
sub agg |
853
|
|
|
|
|
|
|
{ |
854
|
0
|
|
|
0
|
1
|
|
my ($self,$positions,$sortlist)=@_; |
855
|
0
|
|
|
|
|
|
my %totals; # aggregated totals as arrays of individuals. |
856
|
|
|
|
|
|
|
|
857
|
0
|
|
|
|
|
|
$self->flatten($positions, [qw/market position/]); |
858
|
0
|
|
|
|
|
|
for my $position (@$positions) |
859
|
|
|
|
|
|
|
{ |
860
|
|
|
|
|
|
|
|
861
|
0
|
|
|
|
|
|
my $json = JSON->new; |
862
|
0
|
0
|
|
|
|
|
$position->{size}=-abs($position->{size}) if ($position->{direction} eq 'SELL'); |
863
|
|
|
|
|
|
|
# $position->{size}= -abs($position->{size}) if ($position->{direction}//'' ne 'BUY'); |
864
|
0
|
|
|
|
|
|
$position->{profit}=($self->fetch($position,'bid')-$self->fetch($position,'level'))*$self->fetch($position,'size'); |
865
|
|
|
|
|
|
|
|
866
|
0
|
0
|
|
|
|
|
$position->{held}=Time::Piece->strptime($position->{createdDateUTC},"%Y-%m-%dT%H:%M:%S") or die "strptime failed for ".$position->{createdDateOnly}; |
867
|
0
|
|
|
|
|
|
$position->{held}=(gmtime()-$position->{held})/(24*3600); |
868
|
|
|
|
|
|
|
|
869
|
0
|
|
0
|
|
|
|
my $ra=($totals{$position->{instrumentName}}||=[]); |
870
|
0
|
|
|
|
|
|
push(@$ra,$position); |
871
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
} |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
# totals is a hash on instrument name each element is a pointer to an array of positions for the same instrument. |
875
|
|
|
|
|
|
|
|
876
|
0
|
|
|
|
|
|
my $aggregated=[]; |
877
|
0
|
|
|
|
|
|
for my $total (values %totals) |
878
|
|
|
|
|
|
|
{ # for one particular name |
879
|
0
|
|
|
|
|
|
my $position={}; # initialise the new aggregate position |
880
|
|
|
|
|
|
|
|
881
|
0
|
|
|
|
|
|
$position->{profit}=0; |
882
|
0
|
|
|
|
|
|
$position->{size}=0; |
883
|
0
|
|
|
|
|
|
$position->{held}=0; |
884
|
0
|
|
|
|
|
|
$position->{stopLevel}=[]; |
885
|
0
|
|
|
|
|
|
$position->{createdDate}=[]; |
886
|
0
|
|
|
|
|
|
$position->{createdDateUTC}=[]; |
887
|
|
|
|
|
|
|
|
888
|
0
|
|
|
|
|
|
for my $subtotal ( @$total) # go through all the positions for that one name |
889
|
|
|
|
|
|
|
{ |
890
|
0
|
|
0
|
|
|
|
$position->{instrumentName}//=$subtotal->{instrumentName}; |
891
|
0
|
|
|
|
|
|
$position->{size}+=$subtotal->{size}; |
892
|
0
|
|
|
|
|
|
my $h; |
893
|
0
|
0
|
|
|
|
|
$h=Time::Piece->strptime($subtotal->{createdDateUTC},"%Y-%m-%dT%H:%M:%S") or die "strptime failed for ".$subtotal->{createdDateOnly}; |
894
|
0
|
|
|
|
|
|
$h=(gmtime()-$h)/(24*3600); |
895
|
0
|
|
|
|
|
|
$h=int($h*10)/10; |
896
|
0
|
|
|
|
|
|
$subtotal->{held}=$h; |
897
|
0
|
|
|
|
|
|
$position->{held}+=$subtotal->{held}*$subtotal->{size}; # this is a size-weighted average. Needs division by total size. |
898
|
0
|
|
0
|
|
|
|
$position->{bid}//=$subtotal->{bid}; |
899
|
0
|
|
|
|
|
|
$position->{profit}+=$subtotal->{profit} ; |
900
|
0
|
|
0
|
|
|
|
$position->{epic}//=$subtotal->{epic}; |
901
|
|
|
|
|
|
|
|
902
|
0
|
|
0
|
|
|
|
$position->{currency}//=$subtotal->{currency}; |
903
|
0
|
|
0
|
|
|
|
$position->{marketStatus}//=$subtotal->{marketStatus}; |
904
|
|
|
|
|
|
|
|
905
|
0
|
0
|
|
|
|
|
push(@{$position->{stopLevel}},$subtotal->{stopLevel}) if $subtotal->{stopLevel}; |
|
0
|
|
|
|
|
|
|
906
|
0
|
|
|
|
|
|
push(@{$position->{createdDate}},$subtotal->{createdDate}); |
|
0
|
|
|
|
|
|
|
907
|
0
|
|
|
|
|
|
push(@{$position->{createdDateUTC}},$subtotal->{createdDateUTC}); |
|
0
|
|
|
|
|
|
|
908
|
|
|
|
|
|
|
} |
909
|
|
|
|
|
|
|
|
910
|
|
|
|
|
|
|
# now we have various housekeeping to do in some cases, eg where an average is calculated as a sum above, we divide by the number to get a true mean. |
911
|
|
|
|
|
|
|
########### |
912
|
|
|
|
|
|
|
|
913
|
0
|
0
|
|
|
|
|
$position->{held}=sprintf("%0.1f",$position->{held}/$position->{size}); $position->{held}.=" av" if (@$total>1); |
|
0
|
|
|
|
|
|
|
914
|
|
|
|
|
|
|
|
915
|
|
|
|
|
|
|
|
916
|
0
|
|
|
|
|
|
$position->{level}=$position->{bid}-$position->{profit}/$position->{size}; # open level for multiple positions |
917
|
|
|
|
|
|
|
|
918
|
0
|
0
|
|
|
|
|
$position->{profitpc}=int(0.5+1000*$position->{profit}/($position->{level}*abs($position->{size})))/10 if ($position->{level}>0); |
919
|
|
|
|
|
|
|
|
920
|
0
|
|
|
|
|
|
$position->{atrisk}=$position->{bid}*$position->{size}; |
921
|
|
|
|
|
|
|
|
922
|
0
|
|
|
|
|
|
$position->{createdDate}=$self->sortrange($position->{createdDate}); |
923
|
0
|
|
|
|
|
|
$position->{createdDateUTC}=$self->sortrange($position->{createdDateUTC}); |
924
|
0
|
|
|
|
|
|
$position->{createdDateOnly}=$position->{createdDate}; |
925
|
0
|
|
|
|
|
|
$position->{createdDateOnly}=~s/T[^-]+//g; |
926
|
|
|
|
|
|
|
|
927
|
0
|
0
|
|
|
|
|
$position->{slpc}=join(',',map { $_?(int(1000.0*$_/$position->{bid})/10):''} @{$position->{stopLevel}}); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
928
|
0
|
|
|
|
|
|
$position->{stopLevel}=join(',',@{$position->{stopLevel}}); |
|
0
|
|
|
|
|
|
|
929
|
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
########### |
931
|
|
|
|
|
|
|
# end of aggregated operations |
932
|
|
|
|
|
|
|
|
933
|
|
|
|
|
|
|
|
934
|
0
|
|
|
|
|
|
push(@$aggregated,$position); |
935
|
|
|
|
|
|
|
} |
936
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
# @$aggregated=sort { $b->{profitpc}<=>$a->{profitpc} } @$aggregated; |
938
|
0
|
|
0
|
|
|
|
$sortlist//=[qw(-profitpc instrumentName)]; # default sort |
939
|
0
|
|
|
|
|
|
$self->sorter($sortlist,$aggregated); |
940
|
0
|
|
|
|
|
|
return $aggregated; |
941
|
|
|
|
|
|
|
|
942
|
|
|
|
|
|
|
} |
943
|
|
|
|
|
|
|
# like agg, but do not do actual aggregation. |
944
|
|
|
|
|
|
|
# so we sort, add certain extra characteristics but thats all. |
945
|
|
|
|
|
|
|
########################################################################## |
946
|
|
|
|
|
|
|
# |
947
|
|
|
|
|
|
|
|
948
|
|
|
|
|
|
|
=head2 nonagg - like agg but do not do actual aggregation |
949
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
Parameters |
951
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
1 Reference to an array of positions |
953
|
|
|
|
|
|
|
2 (Optional) Ref to an array of keys to sort on |
954
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
Return value - Array ref containing hashes of accounts. Should be the same size as the original. |
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
=cut |
958
|
|
|
|
|
|
|
########################################################################## |
959
|
|
|
|
|
|
|
#sub nonagg |
960
|
|
|
|
|
|
|
#{ |
961
|
|
|
|
|
|
|
# my ($self,$positions,$sortlist)=@_; |
962
|
|
|
|
|
|
|
# my %totals; # aggregated totals as arrays of individuals. |
963
|
|
|
|
|
|
|
# |
964
|
|
|
|
|
|
|
# $self->flatten($positions, [qw/market position/]); |
965
|
|
|
|
|
|
|
# for my $position (@$positions) |
966
|
|
|
|
|
|
|
# { |
967
|
|
|
|
|
|
|
# |
968
|
|
|
|
|
|
|
# my $json = JSON->new; |
969
|
|
|
|
|
|
|
# |
970
|
|
|
|
|
|
|
# $position->{profit}=($self->fetch($position,'bid')-$self->fetch($position,'level'))*$self->fetch($position,'size'); |
971
|
|
|
|
|
|
|
# # create new profits element |
972
|
|
|
|
|
|
|
# |
973
|
|
|
|
|
|
|
# my $open=$position->{bid}-$position->{profit}/$position->{size}; |
974
|
|
|
|
|
|
|
# $position->{level}=$open; |
975
|
|
|
|
|
|
|
# $position->{profitpc}=int(0.5+1000*$position->{profit}/($position->{level}*$position->{size}))/10; |
976
|
|
|
|
|
|
|
# $position->{atrisk}=$position->{bid}*$position->{size}; |
977
|
|
|
|
|
|
|
# $position->{createdDateOnly}=$position->{createdDate}; |
978
|
|
|
|
|
|
|
# $position->{createdDateOnly}=~s/ .*$//; |
979
|
|
|
|
|
|
|
# } |
980
|
|
|
|
|
|
|
# |
981
|
|
|
|
|
|
|
# $sortlist//=[qw(-profitpc instrumentName)]; # default sort |
982
|
|
|
|
|
|
|
# $self->sorter($sortlist,$positions); |
983
|
|
|
|
|
|
|
# return $positions; |
984
|
|
|
|
|
|
|
#} |
985
|
|
|
|
|
|
|
sub nonagg |
986
|
|
|
|
|
|
|
{ |
987
|
0
|
|
|
0
|
1
|
|
my ($self,$positions,$sortlist)=@_; |
988
|
0
|
|
|
|
|
|
my %totals; # aggregated totals as arrays of individuals. |
989
|
|
|
|
|
|
|
|
990
|
0
|
|
|
|
|
|
$self->flatten($positions, [qw/market position/]); |
991
|
0
|
|
|
|
|
|
for my $position (@$positions) |
992
|
|
|
|
|
|
|
{ |
993
|
|
|
|
|
|
|
|
994
|
0
|
|
|
|
|
|
my $json = JSON->new; |
995
|
|
|
|
|
|
|
|
996
|
0
|
0
|
|
|
|
|
$position->{size}=-abs($position->{size}) if ($position->{direction} eq 'SELL'); |
997
|
0
|
|
|
|
|
|
$position->{profit}=($position->{bid}-$position->{level})*$position->{size}; |
998
|
|
|
|
|
|
|
# create new profits element |
999
|
|
|
|
|
|
|
|
1000
|
|
|
|
|
|
|
# my $open=$position->{bid}-$position->{profit}/$position->{size}; |
1001
|
|
|
|
|
|
|
# $position->{level}=$open; |
1002
|
0
|
|
|
|
|
|
$position->{profitpc}=int(0.5+1000*$position->{profit}/($position->{level}*abs($position->{size})))/10; |
1003
|
0
|
|
|
|
|
|
$position->{atrisk}=$position->{bid}*$position->{size}; |
1004
|
0
|
|
|
|
|
|
$position->{createdDateOnly}=$position->{createdDate}; |
1005
|
0
|
|
|
|
|
|
$position->{createdDateOnly}=~s/ .*$//; |
1006
|
0
|
0
|
|
|
|
|
$position->{held}=Time::Piece->strptime($position->{createdDateUTC},"%Y-%m-%dT%H:%M:%S") or die "strptime failed for ".$position->{createdDateOnly}; |
1007
|
0
|
|
|
|
|
|
$position->{held}=(gmtime()-$position->{held})/(24*3600); |
1008
|
0
|
|
|
|
|
|
$position->{held}=int($position->{held}*10+0.5)/10; |
1009
|
0
|
|
|
|
|
|
$position->{dailyp}=''; |
1010
|
0
|
0
|
|
|
|
|
$position->{dailyp}=((1+$position->{profitpc}/100.0)**(1/$position->{held})-1)*100 if ($position->{held}>0); |
1011
|
|
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
} |
1013
|
|
|
|
|
|
|
|
1014
|
0
|
|
0
|
|
|
|
$sortlist//=[qw(-profitpc instrumentName)]; # default sort |
1015
|
0
|
|
|
|
|
|
$self->sorter($sortlist,$positions); |
1016
|
0
|
|
|
|
|
|
return $positions; |
1017
|
|
|
|
|
|
|
} |
1018
|
|
|
|
|
|
|
#################################################################### |
1019
|
|
|
|
|
|
|
# General array sort function. |
1020
|
|
|
|
|
|
|
# Given an array of hash refs, and a sort key |
1021
|
|
|
|
|
|
|
# considtying of an array of an array of keys to the hashes |
1022
|
|
|
|
|
|
|
# sort in place the array. |
1023
|
|
|
|
|
|
|
# |
1024
|
|
|
|
|
|
|
# sortkey, arrayref of keys. Sort order direction reversed |
1025
|
|
|
|
|
|
|
# if key has - appended to start, eg -profitpc gives largest first |
1026
|
|
|
|
|
|
|
# pos array eo be sorted, its an inplace sort. |
1027
|
|
|
|
|
|
|
# uses the determinant $x eq $x+0 to determine if numeric or not. |
1028
|
|
|
|
|
|
|
# improvements: may need to use a deep fetch to locate the items |
1029
|
|
|
|
|
|
|
#################################################################### |
1030
|
|
|
|
|
|
|
|
1031
|
|
|
|
|
|
|
=head2 sorter - general array sort function for an array of hashes |
1032
|
|
|
|
|
|
|
|
1033
|
|
|
|
|
|
|
Parameters |
1034
|
|
|
|
|
|
|
|
1035
|
|
|
|
|
|
|
1 Ref to array of keys to sort. Each my be prefixed with a - to |
1036
|
|
|
|
|
|
|
reverse the order on that key. If keys compare equal the next key is used. |
1037
|
|
|
|
|
|
|
2 Ref to an array of positions to sort. |
1038
|
|
|
|
|
|
|
|
1039
|
|
|
|
|
|
|
The array is sorted in-place. A numeric comparison is done if for |
1040
|
|
|
|
|
|
|
both items $x == $x+0 |
1041
|
|
|
|
|
|
|
|
1042
|
|
|
|
|
|
|
Formatted datetimes are correctly sorted. |
1043
|
|
|
|
|
|
|
|
1044
|
|
|
|
|
|
|
=cut |
1045
|
|
|
|
|
|
|
#################################################################### |
1046
|
|
|
|
|
|
|
sub sorter |
1047
|
|
|
|
|
|
|
{ |
1048
|
0
|
|
|
0
|
1
|
|
my ($self,$sortkey,$pos)=@_; |
1049
|
|
|
|
|
|
|
|
1050
|
|
|
|
|
|
|
@$pos= sort { |
1051
|
0
|
|
|
|
|
|
my ($result)=0; |
|
0
|
|
|
|
|
|
|
1052
|
0
|
|
|
|
|
|
for my $fkey (@$sortkey) |
1053
|
|
|
|
|
|
|
{ |
1054
|
0
|
|
|
|
|
|
my $key=$fkey; |
1055
|
0
|
|
|
|
|
|
my $dir=1; |
1056
|
0
|
0
|
|
|
|
|
$dir=-1 if ($key=~s/^-//); |
1057
|
|
|
|
|
|
|
# die "key=$key value=$b->{createdDateUTC} keys are ".join(', ',keys %$a); ; |
1058
|
0
|
0
|
0
|
|
|
|
next if (!exists($a->{$key}) or !exists($b->{$key})); |
1059
|
0
|
|
|
|
|
|
my ($x1,$x2)=($a->{$key},$b->{$key}); |
1060
|
0
|
|
|
|
|
|
map { s/[£%]//g } ($x1,$x2); |
|
0
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
|
1062
|
1
|
|
|
1
|
|
10
|
{ no warnings qw(numeric); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
5515
|
|
|
0
|
|
|
|
|
|
|
1063
|
0
|
|
|
|
|
|
my $warning; |
1064
|
|
|
|
|
|
|
|
1065
|
0
|
0
|
0
|
|
|
|
if ($x1 eq $x1+0 and $x2 eq $x2+0) |
1066
|
|
|
|
|
|
|
{ |
1067
|
0
|
|
|
|
|
|
$result=$x1<=>$x2; |
1068
|
|
|
|
|
|
|
} |
1069
|
|
|
|
|
|
|
else |
1070
|
|
|
|
|
|
|
{ # note that this correctly handles a formatted date |
1071
|
0
|
|
|
|
|
|
$result=$x1 cmp $x2; |
1072
|
|
|
|
|
|
|
} |
1073
|
|
|
|
|
|
|
} |
1074
|
0
|
0
|
|
|
|
|
return $result*$dir if ($result); |
1075
|
|
|
|
|
|
|
} |
1076
|
0
|
|
|
|
|
|
return 0; |
1077
|
|
|
|
|
|
|
} |
1078
|
|
|
|
|
|
|
@$pos; |
1079
|
|
|
|
|
|
|
|
1080
|
|
|
|
|
|
|
} |
1081
|
|
|
|
|
|
|
#################################################################### |
1082
|
|
|
|
|
|
|
# The idea is this will close all the supplied positions, optionally returning a reference to |
1083
|
|
|
|
|
|
|
# either/both an array of closed/non closed positions; |
1084
|
|
|
|
|
|
|
# This is not quite working yet, needs more work, |
1085
|
|
|
|
|
|
|
#################################################################### |
1086
|
|
|
|
|
|
|
|
1087
|
|
|
|
|
|
|
=head2 close - close the supplied positions. |
1088
|
|
|
|
|
|
|
|
1089
|
|
|
|
|
|
|
|
1090
|
|
|
|
|
|
|
|
1091
|
|
|
|
|
|
|
Parameters |
1092
|
|
|
|
|
|
|
|
1093
|
|
|
|
|
|
|
1 Ref to array of positions to close. |
1094
|
|
|
|
|
|
|
reverse the order on that key. |
1095
|
|
|
|
|
|
|
2/3 ref to done / notdone arrays to sort succesful / failed |
1096
|
|
|
|
|
|
|
closes in to. |
1097
|
|
|
|
|
|
|
|
1098
|
|
|
|
|
|
|
The idea is this will close all the supplied positions, optionally returning a reference to |
1099
|
|
|
|
|
|
|
|
1100
|
|
|
|
|
|
|
|
1101
|
|
|
|
|
|
|
=head3 Status - very experimental. |
1102
|
|
|
|
|
|
|
|
1103
|
|
|
|
|
|
|
Contains die / print statements that you may wish to remove |
1104
|
|
|
|
|
|
|
|
1105
|
|
|
|
|
|
|
=cut |
1106
|
|
|
|
|
|
|
#################################################################### |
1107
|
|
|
|
|
|
|
sub close |
1108
|
|
|
|
|
|
|
{ |
1109
|
0
|
|
|
0
|
1
|
|
my $self=shift; |
1110
|
0
|
|
|
|
|
|
my $positions=shift; # to close |
1111
|
0
|
|
|
|
|
|
my $done=shift; |
1112
|
0
|
|
|
|
|
|
my $notdone=shift; |
1113
|
|
|
|
|
|
|
|
1114
|
0
|
|
|
|
|
|
my $verbose=0; |
1115
|
|
|
|
|
|
|
|
1116
|
0
|
|
|
|
|
|
my @done; |
1117
|
|
|
|
|
|
|
my @notdone; |
1118
|
|
|
|
|
|
|
|
1119
|
0
|
|
|
|
|
|
my $headers = { |
1120
|
|
|
|
|
|
|
'Content-Type' => 'application/json; charset=UTF-8', |
1121
|
|
|
|
|
|
|
'Accept' => 'application/json; charset=UTF-8', |
1122
|
|
|
|
|
|
|
VERSION => 1, |
1123
|
|
|
|
|
|
|
# 'IG-ACCOUNT-ID'=> $accountid, |
1124
|
|
|
|
|
|
|
CST=>$self->CST, |
1125
|
|
|
|
|
|
|
'X-SECURITY-TOKEN'=> $self->XSECURITYTOKEN, |
1126
|
|
|
|
|
|
|
'X-IG-API-KEY'=> $self->apikey, |
1127
|
|
|
|
|
|
|
'_method'=>'DELETE', |
1128
|
|
|
|
|
|
|
}; |
1129
|
|
|
|
|
|
|
|
1130
|
0
|
|
|
|
|
|
my $data = { |
1131
|
|
|
|
|
|
|
#encryptedPassword => "false", |
1132
|
|
|
|
|
|
|
#identifier => $self->username, |
1133
|
|
|
|
|
|
|
#password => $self->password |
1134
|
|
|
|
|
|
|
#direction => 'BUY', |
1135
|
|
|
|
|
|
|
# epic=> |
1136
|
|
|
|
|
|
|
# expiry=> |
1137
|
|
|
|
|
|
|
orderType=>'MARKET', |
1138
|
|
|
|
|
|
|
#size=>0.1 |
1139
|
|
|
|
|
|
|
##guaranteedStop=>'false', |
1140
|
|
|
|
|
|
|
forceOpen=>'true', |
1141
|
|
|
|
|
|
|
#timeInForce => "EXECUTE_AND_ELIMINATE", # "GOOD_TILL_CANCELLED" |
1142
|
|
|
|
|
|
|
timeInForce => "", # "GOOD_TILL_CANCELLED" |
1143
|
|
|
|
|
|
|
}; |
1144
|
0
|
|
|
|
|
|
my $client = REST::Client->new(); |
1145
|
|
|
|
|
|
|
|
1146
|
0
|
|
|
|
|
|
$client->setHost($self->_url); |
1147
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
|
1149
|
0
|
|
|
|
|
|
my %existhash; |
1150
|
0
|
|
|
|
|
|
map { $existhash{$self->fetch($_,'epic')}=$_ } @$positions; # creat a hash on epic |
|
0
|
|
|
|
|
|
|
1151
|
|
|
|
|
|
|
|
1152
|
0
|
|
|
|
|
|
for my $position (@$positions) |
1153
|
|
|
|
|
|
|
{ |
1154
|
|
|
|
|
|
|
# die dump($position); |
1155
|
|
|
|
|
|
|
|
1156
|
0
|
|
|
|
|
|
my $existingsize=0; |
1157
|
0
|
|
|
|
|
|
my $epic=$self->fetch($position,'epic'); |
1158
|
0
|
|
|
|
|
|
my $name=$self->fetch($position,'instrumentName'); |
1159
|
|
|
|
|
|
|
|
1160
|
0
|
|
|
|
|
|
my $ms=$self->fetch($position,'marketStatus'); |
1161
|
|
|
|
|
|
|
|
1162
|
0
|
0
|
|
|
|
|
if ($ms ne 'TRADEABLE') |
1163
|
|
|
|
|
|
|
{ |
1164
|
0
|
|
|
|
|
|
push(@notdone,$position); |
1165
|
0
|
|
|
|
|
|
print "$name, market status is $ms\n"; |
1166
|
0
|
|
|
|
|
|
next; |
1167
|
|
|
|
|
|
|
} |
1168
|
|
|
|
|
|
|
|
1169
|
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
#$data->{epic}=$self->fetch($position,'epic'); |
1171
|
0
|
|
|
|
|
|
$data->{epic}=$epic; |
1172
|
0
|
|
|
|
|
|
$data->{size}=$self->fetch($position,'size'); |
1173
|
|
|
|
|
|
|
# $data->{currencyCode}=$self->fetch($position,'currency'); |
1174
|
0
|
|
|
|
|
|
$data->{expiry}='DFB'; |
1175
|
|
|
|
|
|
|
# $data->{expiry}='-'; |
1176
|
0
|
|
|
|
|
|
$data->{direction}='SELL'; |
1177
|
|
|
|
|
|
|
|
1178
|
|
|
|
|
|
|
#my $jdata = encode_json($data); |
1179
|
0
|
|
|
|
|
|
my $jdata=JSON->new->canonical->encode($data); |
1180
|
0
|
|
|
|
|
|
$client->PUT ( |
1181
|
|
|
|
|
|
|
'/positions/otc', |
1182
|
|
|
|
|
|
|
$jdata, |
1183
|
|
|
|
|
|
|
$headers |
1184
|
|
|
|
|
|
|
); |
1185
|
0
|
|
|
|
|
|
my $code=$client->responseCode(); |
1186
|
0
|
0
|
|
|
|
|
if ($code==200) |
1187
|
|
|
|
|
|
|
{ |
1188
|
0
|
|
|
|
|
|
my $resp=decode_json($client->responseContent()); |
1189
|
0
|
|
|
|
|
|
my $dealReference=$resp->{dealReference}; |
1190
|
0
|
|
|
|
|
|
print "$name, dr=$dealReference\n"; |
1191
|
0
|
0
|
0
|
|
|
|
if (defined $dealReference and length($dealReference)>5) |
1192
|
|
|
|
|
|
|
{ |
1193
|
0
|
|
|
|
|
|
push(@done,$position); |
1194
|
0
|
|
|
|
|
|
die; |
1195
|
0
|
|
|
|
|
|
next; |
1196
|
0
|
|
|
|
|
|
next; |
1197
|
|
|
|
|
|
|
} |
1198
|
|
|
|
|
|
|
} |
1199
|
|
|
|
|
|
|
else |
1200
|
|
|
|
|
|
|
{ |
1201
|
0
|
|
|
|
|
|
print "$name failed $code: ".$client->responseContent()."\n"; |
1202
|
0
|
|
|
|
|
|
push(@notdone,$position); |
1203
|
|
|
|
|
|
|
} |
1204
|
|
|
|
|
|
|
} |
1205
|
0
|
0
|
|
|
|
|
@$done=@done if ($done); |
1206
|
0
|
0
|
|
|
|
|
@$notdone=@notdone if ($notdone); |
1207
|
|
|
|
|
|
|
|
1208
|
0
|
|
|
|
|
|
printf "done=%d notdone=%d\n",0+@done,0+@notdone; |
1209
|
0
|
|
|
|
|
|
print "notdone:\n"; |
1210
|
|
|
|
|
|
|
|
1211
|
0
|
|
|
|
|
|
my $cpc='%%'; |
1212
|
0
|
|
|
|
|
|
my $format="%-41sinstrumentName %+4.2fsize %-9.2flevel ". |
1213
|
|
|
|
|
|
|
"%-8.2fbid £%-8.2fprofit %4.1fprofitpc%% £%10.2fatrisk %-9sstopLevel %-4sslpc$cpc\n"; |
1214
|
|
|
|
|
|
|
|
1215
|
0
|
|
|
|
|
|
$self->printpos("stdout" , ['Name','Size','Open','Latest','P/L','P/L%','Value','Stop','Stop'], $format); |
1216
|
|
|
|
|
|
|
|
1217
|
0
|
|
|
|
|
|
map { $self->printpos("stdout" , $_, $format) } @notdone; |
|
0
|
|
|
|
|
|
|
1218
|
|
|
|
|
|
|
|
1219
|
|
|
|
|
|
|
} |
1220
|
|
|
|
|
|
|
##################################################################### |
1221
|
|
|
|
|
|
|
# given a ref to an array of positions, attempt to buy the same |
1222
|
|
|
|
|
|
|
# position in this object. |
1223
|
|
|
|
|
|
|
# if the position already exists or is succesfully brought, count as success. |
1224
|
|
|
|
|
|
|
# If the buy fails, include it in the returned list. |
1225
|
|
|
|
|
|
|
# If all buys succesful then return an empty list. |
1226
|
|
|
|
|
|
|
# done and notdone references may be supplied and if they are these should point to arrays |
1227
|
|
|
|
|
|
|
# of the succesful and unsuccesful positions. |
1228
|
|
|
|
|
|
|
# return value is NOT now used. |
1229
|
|
|
|
|
|
|
# ignortradeable ... use this if the positionis an old one, so that tradeable status could |
1230
|
|
|
|
|
|
|
# be out of date. |
1231
|
|
|
|
|
|
|
##################################################################### |
1232
|
|
|
|
|
|
|
|
1233
|
|
|
|
|
|
|
=head2 buy - attempt to buy a number of instruments. |
1234
|
|
|
|
|
|
|
|
1235
|
|
|
|
|
|
|
Parameters |
1236
|
|
|
|
|
|
|
|
1237
|
|
|
|
|
|
|
1 Reference to an array of positions |
1238
|
|
|
|
|
|
|
2 Optional ref to an array done, to be filled with succesful buys |
1239
|
|
|
|
|
|
|
3 Optional ref to an array notdone, to be filled with the failed |
1240
|
|
|
|
|
|
|
4 ignore tradeable, one of the fields in a position relates to the market |
1241
|
|
|
|
|
|
|
being open or closed (TRADEABLE) If this field is current, its a |
1242
|
|
|
|
|
|
|
good indication to skip this one (place it in the notdone array. |
1243
|
|
|
|
|
|
|
But if its out of date then setting this flag 1 attempts the trade |
1244
|
|
|
|
|
|
|
anyway. |
1245
|
|
|
|
|
|
|
|
1246
|
|
|
|
|
|
|
Attempt to buy positions. I have used this to move positions |
1247
|
|
|
|
|
|
|
between a demo account and real account or vice-versa. |
1248
|
|
|
|
|
|
|
|
1249
|
|
|
|
|
|
|
=head3 Status - very experimental. |
1250
|
|
|
|
|
|
|
|
1251
|
|
|
|
|
|
|
Contains print statements that should |
1252
|
|
|
|
|
|
|
probably be removed. |
1253
|
|
|
|
|
|
|
|
1254
|
|
|
|
|
|
|
=cut |
1255
|
|
|
|
|
|
|
##################################################################### |
1256
|
|
|
|
|
|
|
sub buy |
1257
|
|
|
|
|
|
|
{ |
1258
|
0
|
|
|
0
|
1
|
|
my $self=shift; |
1259
|
0
|
|
|
|
|
|
my $positions=shift; # to buy |
1260
|
0
|
|
|
|
|
|
my $done=shift; |
1261
|
0
|
|
|
|
|
|
my $notdone=shift; |
1262
|
0
|
|
|
|
|
|
my $ignoretradeable=shift; |
1263
|
|
|
|
|
|
|
|
1264
|
0
|
|
|
|
|
|
my $verbose=0; |
1265
|
|
|
|
|
|
|
|
1266
|
0
|
|
|
|
|
|
my @done; |
1267
|
|
|
|
|
|
|
my @notdone; |
1268
|
0
|
|
|
|
|
|
my $headers = { |
1269
|
|
|
|
|
|
|
'Content-Type' => 'application/json; charset=UTF-8', |
1270
|
|
|
|
|
|
|
'Accept' => 'application/json; charset=UTF-8', |
1271
|
|
|
|
|
|
|
VERSION => 2, |
1272
|
|
|
|
|
|
|
# 'IG-ACCOUNT-ID'=> $accountid, |
1273
|
|
|
|
|
|
|
CST=>$self->CST, |
1274
|
|
|
|
|
|
|
'X-SECURITY-TOKEN'=> $self->XSECURITYTOKEN, |
1275
|
|
|
|
|
|
|
'X-IG-API-KEY'=> $self->apikey, |
1276
|
|
|
|
|
|
|
}; |
1277
|
|
|
|
|
|
|
|
1278
|
0
|
|
|
|
|
|
my $data = { |
1279
|
|
|
|
|
|
|
direction => 'BUY', |
1280
|
|
|
|
|
|
|
#epic=> |
1281
|
|
|
|
|
|
|
#size=>0.1 |
1282
|
|
|
|
|
|
|
orderType=>'MARKET', |
1283
|
|
|
|
|
|
|
guaranteedStop=>'false', |
1284
|
|
|
|
|
|
|
forceOpen=>'false', |
1285
|
|
|
|
|
|
|
timeInForce => "EXECUTE_AND_ELIMINATE", # "GOOD_TILL_CANCELLED" |
1286
|
|
|
|
|
|
|
}; |
1287
|
0
|
|
|
|
|
|
my $client = REST::Client->new(); |
1288
|
0
|
|
|
|
|
|
$client->setHost($self->_url); |
1289
|
|
|
|
|
|
|
|
1290
|
0
|
|
|
|
|
|
my $existing=$self->positions; |
1291
|
0
|
|
|
|
|
|
my %existhash; |
1292
|
0
|
|
|
|
|
|
map { $existhash{$self->fetch($_,'epic')}=$_ } @$existing; |
|
0
|
|
|
|
|
|
|
1293
|
|
|
|
|
|
|
|
1294
|
0
|
|
|
|
|
|
for my $position (@$positions) |
1295
|
|
|
|
|
|
|
{ |
1296
|
|
|
|
|
|
|
# die dump($position); |
1297
|
|
|
|
|
|
|
|
1298
|
0
|
|
|
|
|
|
my $existingsize=0; |
1299
|
0
|
|
|
|
|
|
my $epic=$self->fetch($position,'epic'); |
1300
|
0
|
|
|
|
|
|
my $name=$self->fetch($position,'instrumentName'); |
1301
|
|
|
|
|
|
|
|
1302
|
0
|
|
|
|
|
|
my $ms=$self->fetch($position,'marketStatus'); |
1303
|
|
|
|
|
|
|
|
1304
|
|
|
|
|
|
|
|
1305
|
0
|
0
|
|
|
|
|
if (exists $existhash{$epic}) |
1306
|
|
|
|
|
|
|
{ |
1307
|
0
|
|
|
|
|
|
my $existingposition=$existhash{$epic}; |
1308
|
0
|
|
|
|
|
|
$existingsize=$self->fetch($existingposition,'size'); |
1309
|
|
|
|
|
|
|
} |
1310
|
|
|
|
|
|
|
|
1311
|
0
|
|
|
|
|
|
my $demandsize=$self->fetch($position,'size'); |
1312
|
0
|
|
|
|
|
|
my $wantedsize=$demandsize-$existingsize; |
1313
|
|
|
|
|
|
|
|
1314
|
0
|
|
|
|
|
|
print "existingsize=$existingsize wantedsize=$wantedsize, demandsize=$demandsize\n"; |
1315
|
0
|
0
|
|
|
|
|
if ($wantedsize<=0) |
1316
|
|
|
|
|
|
|
{ |
1317
|
0
|
|
|
|
|
|
push(@done,$position); |
1318
|
0
|
|
|
|
|
|
print "$name, not needed\n"; |
1319
|
0
|
|
|
|
|
|
next; |
1320
|
|
|
|
|
|
|
} |
1321
|
|
|
|
|
|
|
|
1322
|
0
|
0
|
0
|
|
|
|
if ($ms ne 'TRADEABLE' and !$ignoretradeable) |
1323
|
|
|
|
|
|
|
{ |
1324
|
0
|
|
|
|
|
|
push(@notdone,$position); |
1325
|
0
|
|
|
|
|
|
print "$name, market status is $ms\n"; |
1326
|
0
|
|
|
|
|
|
next; |
1327
|
|
|
|
|
|
|
} |
1328
|
|
|
|
|
|
|
|
1329
|
|
|
|
|
|
|
|
1330
|
|
|
|
|
|
|
#$data->{epic}=$self->fetch($position,'epic'); |
1331
|
0
|
|
|
|
|
|
$data->{epic}=$epic; |
1332
|
0
|
|
|
|
|
|
$data->{size}=$wantedsize; |
1333
|
0
|
|
|
|
|
|
$data->{currencyCode}=$self->fetch($position,'currency'); |
1334
|
0
|
|
|
|
|
|
$data->{expiry}='DFB'; |
1335
|
|
|
|
|
|
|
|
1336
|
|
|
|
|
|
|
#my $jdata = encode_json($data); |
1337
|
0
|
|
|
|
|
|
my $jdata=JSON->new->canonical->encode($data); |
1338
|
|
|
|
|
|
|
# die $jdata; |
1339
|
0
|
|
|
|
|
|
print "$data->{direction}: $position->{instrumentName} $position->{size}\n"; |
1340
|
0
|
|
|
|
|
|
$client->POST ( |
1341
|
|
|
|
|
|
|
'/positions/otc', |
1342
|
|
|
|
|
|
|
$jdata, |
1343
|
|
|
|
|
|
|
$headers |
1344
|
|
|
|
|
|
|
); |
1345
|
0
|
|
|
|
|
|
my $code=$client->responseCode(); |
1346
|
0
|
0
|
|
|
|
|
if ($code==200) |
1347
|
|
|
|
|
|
|
{ |
1348
|
0
|
|
|
|
|
|
print "200: ".$client->responseContent()."\n"; |
1349
|
0
|
|
|
|
|
|
my $resp=decode_json($client->responseContent()); |
1350
|
0
|
|
|
|
|
|
my $dealReference=$resp->{dealReference}; |
1351
|
0
|
|
|
|
|
|
print "$name, dr=$dealReference\n"; |
1352
|
0
|
0
|
0
|
|
|
|
if (defined $dealReference and length($dealReference)>5) |
1353
|
|
|
|
|
|
|
{ |
1354
|
0
|
|
|
|
|
|
push(@done,$position); |
1355
|
0
|
|
|
|
|
|
next; |
1356
|
|
|
|
|
|
|
} |
1357
|
|
|
|
|
|
|
} |
1358
|
0
|
|
|
|
|
|
print "$name, failed code $code \n"; |
1359
|
0
|
|
|
|
|
|
push(@notdone,$position); |
1360
|
|
|
|
|
|
|
} |
1361
|
0
|
0
|
|
|
|
|
@$done=@done if ($done); |
1362
|
0
|
0
|
|
|
|
|
@$notdone=@notdone if ($notdone); |
1363
|
0
|
|
|
|
|
|
printf "done=%d notdone=%d\n",0+@done,0+@notdone; |
1364
|
0
|
|
|
|
|
|
print "notdone:\n"; |
1365
|
|
|
|
|
|
|
|
1366
|
0
|
|
|
|
|
|
return; |
1367
|
|
|
|
|
|
|
|
1368
|
0
|
|
|
|
|
|
my $format="%-41sinstrumentName %+4.2fsize %-9.2flevel ". |
1369
|
|
|
|
|
|
|
"%-8.2fbid £%-8.2fprofit %4.1fprofitpc%% £%10.2fatrisk\n"; |
1370
|
|
|
|
|
|
|
|
1371
|
0
|
|
|
|
|
|
$self->printpos("stdout" , ['Name','Size','Open','Latest','P/L','P/L%','Value','Stop','Stop'], $format); |
1372
|
|
|
|
|
|
|
|
1373
|
0
|
|
|
|
|
|
map { $self->printpos("stdout" , $_, $format) } @notdone; |
|
0
|
|
|
|
|
|
|
1374
|
|
|
|
|
|
|
|
1375
|
|
|
|
|
|
|
} |
1376
|
|
|
|
|
|
|
|
1377
|
|
|
|
|
|
|
##################################################################### |
1378
|
|
|
|
|
|
|
|
1379
|
|
|
|
|
|
|
=head2 prices - Obtain historical prices |
1380
|
|
|
|
|
|
|
|
1381
|
|
|
|
|
|
|
Obtain historical price information on an instrument. |
1382
|
|
|
|
|
|
|
|
1383
|
|
|
|
|
|
|
=head3 Parameters |
1384
|
|
|
|
|
|
|
|
1385
|
|
|
|
|
|
|
Unused parameters should be set as undef or ''. (either); |
1386
|
|
|
|
|
|
|
|
1387
|
|
|
|
|
|
|
1 A aubstring to be searched for in the name. Eg "UB.D.FTNT.DAILY.IP" |
1388
|
|
|
|
|
|
|
|
1389
|
|
|
|
|
|
|
2 Resolution. Should be one of the IG defined strings (left) or (in my opinion more memorable) aliases (right) |
1390
|
|
|
|
|
|
|
|
1391
|
|
|
|
|
|
|
DAY 1d |
1392
|
|
|
|
|
|
|
HOUR 1h |
1393
|
|
|
|
|
|
|
HOUR_2 1h |
1394
|
|
|
|
|
|
|
HOUR_3 2h |
1395
|
|
|
|
|
|
|
HOUR_4 3h |
1396
|
|
|
|
|
|
|
MINUTE 1m |
1397
|
|
|
|
|
|
|
MINUTE_2 2m |
1398
|
|
|
|
|
|
|
MINUTE_3 3m |
1399
|
|
|
|
|
|
|
MINUTE_5 5m |
1400
|
|
|
|
|
|
|
MINUTE_10 10m |
1401
|
|
|
|
|
|
|
MINUTE_15 15m |
1402
|
|
|
|
|
|
|
MINUTE_30 30m |
1403
|
|
|
|
|
|
|
SECOND 1s |
1404
|
|
|
|
|
|
|
WEEK 1w |
1405
|
|
|
|
|
|
|
MONTH 1M |
1406
|
|
|
|
|
|
|
|
1407
|
|
|
|
|
|
|
4, 5 pageNumber, pageSize What page to produce, and how many items on it. |
1408
|
|
|
|
|
|
|
|
1409
|
|
|
|
|
|
|
6, 7 from , to (dates) can be a string of the form 2021-01-01T16:15:00 or a Time::Piece |
1410
|
|
|
|
|
|
|
|
1411
|
|
|
|
|
|
|
8 max Limits the number of price points (not applicable if a date range has been specified) |
1412
|
|
|
|
|
|
|
|
1413
|
|
|
|
|
|
|
|
1414
|
|
|
|
|
|
|
|
1415
|
|
|
|
|
|
|
|
1416
|
|
|
|
|
|
|
|
1417
|
|
|
|
|
|
|
=cut |
1418
|
|
|
|
|
|
|
|
1419
|
|
|
|
|
|
|
##################################################################### |
1420
|
|
|
|
|
|
|
# Historical prices |
1421
|
|
|
|
|
|
|
# epic, resolution , pagenum, pagessize, from.to max |
1422
|
|
|
|
|
|
|
##################################################################### |
1423
|
|
|
|
|
|
|
sub prices |
1424
|
|
|
|
|
|
|
{ |
1425
|
|
|
|
|
|
|
|
1426
|
0
|
|
|
0
|
1
|
|
my $self=shift; |
1427
|
0
|
|
|
|
|
|
my $epic=shift; |
1428
|
0
|
|
|
|
|
|
my $resolution=shift; |
1429
|
0
|
|
|
|
|
|
my $pagenumber=shift; |
1430
|
0
|
|
|
|
|
|
my $pagesize=shift; |
1431
|
|
|
|
|
|
|
|
1432
|
0
|
|
|
|
|
|
my $from=shift; |
1433
|
0
|
|
|
|
|
|
my $to=shift; |
1434
|
0
|
|
|
|
|
|
my $max=shift; |
1435
|
|
|
|
|
|
|
|
1436
|
|
|
|
|
|
|
|
1437
|
0
|
0
|
|
|
|
|
if (ref($to) eq 'Time::Piece') |
1438
|
|
|
|
|
|
|
{ |
1439
|
0
|
|
|
|
|
|
$to=$to->strftime("%Y-%m-%dT%H:%M:%S"); |
1440
|
|
|
|
|
|
|
} |
1441
|
0
|
0
|
|
|
|
|
if (ref($from) eq 'Time::Piece') |
1442
|
|
|
|
|
|
|
{ |
1443
|
0
|
|
|
|
|
|
$from=$from->strftime("%Y-%m-%dT%H:%M:%S"); |
1444
|
|
|
|
|
|
|
} |
1445
|
|
|
|
|
|
|
|
1446
|
0
|
|
0
|
|
|
|
$pagesize//=1; # set a default of 1 item per page |
1447
|
|
|
|
|
|
|
# $pagenumber=1; # set a default of page 1, not needed as already set as defult |
1448
|
|
|
|
|
|
|
|
1449
|
0
|
|
|
|
|
|
my $headers = { |
1450
|
|
|
|
|
|
|
'Content-Type' => 'application/json; charset=UTF-8', |
1451
|
|
|
|
|
|
|
'Accept' => 'application/json; charset=UTF-8', |
1452
|
|
|
|
|
|
|
VERSION => 3, |
1453
|
|
|
|
|
|
|
# 'IG-ACCOUNT-ID'=> $accountid, |
1454
|
|
|
|
|
|
|
CST=>$self->CST, |
1455
|
|
|
|
|
|
|
'X-SECURITY-TOKEN'=> $self->XSECURITYTOKEN, |
1456
|
|
|
|
|
|
|
'X-IG-API-KEY'=> $self->apikey, |
1457
|
|
|
|
|
|
|
}; |
1458
|
|
|
|
|
|
|
|
1459
|
0
|
|
|
|
|
|
$resolution="MINUTE_10"; |
1460
|
0
|
|
|
|
|
|
$resolution="HOUR_4"; |
1461
|
|
|
|
|
|
|
|
1462
|
|
|
|
|
|
|
# An alternative and more memorable resolution constants. IG values can also be used. |
1463
|
0
|
0
|
|
|
|
|
$resolution="DAY" if ($resolution eq '1d'); |
1464
|
0
|
0
|
|
|
|
|
$resolution="HOUR" if ($resolution eq'1h'); |
1465
|
0
|
0
|
|
|
|
|
$resolution="HOUR_2" if ($resolution eq '1h'); |
1466
|
0
|
0
|
|
|
|
|
$resolution="HOUR_3" if ($resolution eq '2h'); |
1467
|
0
|
0
|
|
|
|
|
$resolution="HOUR_4" if ($resolution eq '3h'); |
1468
|
0
|
0
|
|
|
|
|
$resolution="MINUTE" if ($resolution eq '1m'); |
1469
|
0
|
0
|
|
|
|
|
$resolution="MINUTE_2" if ($resolution eq '2m'); |
1470
|
0
|
0
|
|
|
|
|
$resolution="MINUTE_3" if ($resolution eq '3m'); |
1471
|
0
|
0
|
|
|
|
|
$resolution="MINUTE_5" if ($resolution eq '5m'); |
1472
|
0
|
0
|
|
|
|
|
$resolution="MINUTE_10" if ($resolution eq '10m'); |
1473
|
0
|
0
|
|
|
|
|
$resolution="MINUTE_15" if ($resolution eq '15m'); |
1474
|
0
|
0
|
|
|
|
|
$resolution="MINUTE_30" if ($resolution eq '30m'); |
1475
|
0
|
0
|
|
|
|
|
$resolution="SECOND" if ($resolution eq '1s'); |
1476
|
0
|
0
|
|
|
|
|
$resolution="WEEK" if ($resolution eq '1w'); |
1477
|
0
|
0
|
|
|
|
|
$resolution="MONTH" if ($resolution eq '1M'); |
1478
|
|
|
|
|
|
|
|
1479
|
|
|
|
|
|
|
defined $resolution and |
1480
|
0
|
0
|
0
|
|
|
|
(0==grep { $resolution eq $_} qw(DAY HOUR HOUR_2 HOUR_3 HOUR_4 MINUTE MINUTE_10 MINUTE_15 MINUTE_2 MINUTE_3 MINUTE_30 MINUTE_5 MONTH SECOND WEEK)) and |
|
0
|
|
|
|
|
|
|
1481
|
|
|
|
|
|
|
die "Resolution is '$resolution', not recognised"; |
1482
|
|
|
|
|
|
|
|
1483
|
|
|
|
|
|
|
#my $jheaders=JSON->new->canonical->encode($headers); |
1484
|
|
|
|
|
|
|
|
1485
|
0
|
|
|
|
|
|
my $client = REST::Client->new(); |
1486
|
0
|
|
|
|
|
|
$client->setHost($self->_url); |
1487
|
|
|
|
|
|
|
#my $r; |
1488
|
|
|
|
|
|
|
|
1489
|
0
|
|
|
|
|
|
my $values={ |
1490
|
|
|
|
|
|
|
pageNumber=>$pagenumber, |
1491
|
|
|
|
|
|
|
pageSize=>$pagesize, |
1492
|
|
|
|
|
|
|
resolution=>$resolution, |
1493
|
|
|
|
|
|
|
from=>$from, |
1494
|
|
|
|
|
|
|
to=>$to, |
1495
|
|
|
|
|
|
|
max=>$max, |
1496
|
|
|
|
|
|
|
} ; |
1497
|
|
|
|
|
|
|
|
1498
|
0
|
|
|
|
|
|
delete @$values{ grep {!$values->{$_} } keys %$values} ; # delete all empty or undef values |
|
0
|
|
|
|
|
|
|
1499
|
0
|
|
|
|
|
|
map { $values->{$_}=$_."=".$values->{$_} } keys %$values ; |
|
0
|
|
|
|
|
|
|
1500
|
|
|
|
|
|
|
|
1501
|
0
|
|
|
|
|
|
my $url; |
1502
|
0
|
|
|
|
|
|
$url=join('&',sort values(%$values)); |
1503
|
0
|
0
|
|
|
|
|
$url='?'.$url if ($url); |
1504
|
0
|
|
|
|
|
|
$url="prices/$epic".$url; |
1505
|
|
|
|
|
|
|
|
1506
|
|
|
|
|
|
|
|
1507
|
0
|
|
|
|
|
|
$client->GET ( $url, |
1508
|
|
|
|
|
|
|
$headers |
1509
|
|
|
|
|
|
|
); |
1510
|
|
|
|
|
|
|
|
1511
|
|
|
|
|
|
|
|
1512
|
0
|
|
|
|
|
|
my $resp=decode_json($client->responseContent()); |
1513
|
|
|
|
|
|
|
|
1514
|
|
|
|
|
|
|
|
1515
|
|
|
|
|
|
|
|
1516
|
0
|
|
|
|
|
|
$self->flatten_withunder($resp); |
1517
|
|
|
|
|
|
|
# print JSON->new->canonical->pretty->encode($resp); exit; |
1518
|
|
|
|
|
|
|
|
1519
|
0
|
|
|
|
|
|
return $resp; |
1520
|
|
|
|
|
|
|
} |
1521
|
|
|
|
|
|
|
##################################################################### |
1522
|
|
|
|
|
|
|
# flatten_withunder |
1523
|
|
|
|
|
|
|
# flattens a deep hash, 3 levels max, where complex hashes are |
1524
|
|
|
|
|
|
|
# removed and replace with _ joined shallow hash values |
1525
|
|
|
|
|
|
|
# for exapmple: |
1526
|
|
|
|
|
|
|
# { |
1527
|
|
|
|
|
|
|
# "metadata" : { |
1528
|
|
|
|
|
|
|
# "allowance" : { |
1529
|
|
|
|
|
|
|
# "allowanceExpiry" : 530567, |
1530
|
|
|
|
|
|
|
# "remainingAllowance" : 9557, |
1531
|
|
|
|
|
|
|
# "totalAllowance" : 10000 |
1532
|
|
|
|
|
|
|
# }, |
1533
|
|
|
|
|
|
|
# ... |
1534
|
|
|
|
|
|
|
# |
1535
|
|
|
|
|
|
|
# becomes |
1536
|
|
|
|
|
|
|
# { |
1537
|
|
|
|
|
|
|
# "metadata_allowance_allowanceExpiry" : 530473, |
1538
|
|
|
|
|
|
|
# "metadata_allowance_remainingAllowance" : 9556, |
1539
|
|
|
|
|
|
|
# "metadata_allowance_totalAllowance" : 10000, |
1540
|
|
|
|
|
|
|
# ... |
1541
|
|
|
|
|
|
|
# The advantage of a flattened structure is its easier to print. |
1542
|
|
|
|
|
|
|
|
1543
|
|
|
|
|
|
|
##################################################################### |
1544
|
|
|
|
|
|
|
|
1545
|
|
|
|
|
|
|
=head2 flatten_withunder |
1546
|
|
|
|
|
|
|
|
1547
|
|
|
|
|
|
|
Flatten a deep structure, up to 3 layers deep using underscores to create new keys by concatenating deeper keys. |
1548
|
|
|
|
|
|
|
Deep keys are removed. More than 3 layers can be removed by calling multiply. |
1549
|
|
|
|
|
|
|
|
1550
|
|
|
|
|
|
|
=head3 Parameters |
1551
|
|
|
|
|
|
|
|
1552
|
|
|
|
|
|
|
One or more scalers to opperate on or an array. Each will be flattened |
1553
|
|
|
|
|
|
|
where there are hashes or hashes or hashes of hashes of hashes |
1554
|
|
|
|
|
|
|
to a single depth, with elements joined by underscores |
1555
|
|
|
|
|
|
|
|
1556
|
|
|
|
|
|
|
=head3 Example |
1557
|
|
|
|
|
|
|
|
1558
|
|
|
|
|
|
|
{ |
1559
|
|
|
|
|
|
|
"metadata" : { |
1560
|
|
|
|
|
|
|
"allowance" : { |
1561
|
|
|
|
|
|
|
"allowanceExpiry" : 530567, |
1562
|
|
|
|
|
|
|
"remainingAllowance" : 9557, |
1563
|
|
|
|
|
|
|
"totalAllowance" : 10000 |
1564
|
|
|
|
|
|
|
}, |
1565
|
|
|
|
|
|
|
... |
1566
|
|
|
|
|
|
|
|
1567
|
|
|
|
|
|
|
becomes |
1568
|
|
|
|
|
|
|
{ |
1569
|
|
|
|
|
|
|
"metadata_allowance_allowanceExpiry" : 530473, |
1570
|
|
|
|
|
|
|
"metadata_allowance_remainingAllowance" : 9556, |
1571
|
|
|
|
|
|
|
"metadata_allowance_totalAllowance" : 10000, |
1572
|
|
|
|
|
|
|
... |
1573
|
|
|
|
|
|
|
|
1574
|
|
|
|
|
|
|
The advantage of a flattened structure is its easier to print with existing fuunctions like printpos |
1575
|
|
|
|
|
|
|
|
1576
|
|
|
|
|
|
|
=cut |
1577
|
|
|
|
|
|
|
|
1578
|
|
|
|
|
|
|
##################################################################### |
1579
|
|
|
|
|
|
|
sub flatten_withunder |
1580
|
|
|
|
|
|
|
{ |
1581
|
0
|
|
|
0
|
1
|
|
my ($self)=shift; |
1582
|
0
|
|
|
|
|
|
my (@items)=@_; |
1583
|
0
|
|
|
|
|
|
my $fudebug=0; |
1584
|
0
|
0
|
|
|
|
|
$fudebug and printf "%d items to process\n",0+@items; |
1585
|
0
|
|
|
|
|
|
for my $item (@items) |
1586
|
|
|
|
|
|
|
{ |
1587
|
0
|
0
|
|
|
|
|
$fudebug and print "item is a ".ref($item)."\n"; |
1588
|
0
|
0
|
|
|
|
|
return if (ref($item)eq ''); |
1589
|
0
|
0
|
|
|
|
|
if (ref($item) eq 'HASH') |
1590
|
|
|
|
|
|
|
{ |
1591
|
0
|
0
|
|
|
|
|
$fudebug and print "is a hash\n"; |
1592
|
0
|
|
|
|
|
|
for my $key (keys %$item) |
1593
|
|
|
|
|
|
|
{ |
1594
|
0
|
0
|
|
|
|
|
$fudebug and print "key1 $key\n"; |
1595
|
0
|
0
|
|
|
|
|
if (ref($item->{$key}) eq 'HASH') |
1596
|
|
|
|
|
|
|
{ |
1597
|
0
|
|
|
|
|
|
for my $key2 (keys %{$item->{$key}}) |
|
0
|
|
|
|
|
|
|
1598
|
|
|
|
|
|
|
{ |
1599
|
0
|
0
|
|
|
|
|
$fudebug and print "keyr2 $key2\n"; |
1600
|
0
|
|
|
|
|
|
$item->{$key."_".$key2}=$item->{$key}->{$key2}; |
1601
|
0
|
0
|
|
|
|
|
$fudebug and printf "creating $key"."_"."$key2 as a %s\n",ref($item->{$key}->{$key2}); |
1602
|
|
|
|
|
|
|
|
1603
|
|
|
|
|
|
|
# $self->flatten_withunder($item->{$key}) if (ref($item->{$key}->{$key2}) eq 'HASH'); |
1604
|
0
|
0
|
|
|
|
|
if (ref($item->{$key}->{$key2}) eq 'HASH') |
1605
|
|
|
|
|
|
|
{ |
1606
|
0
|
|
|
|
|
|
for my $key3 (keys %{$item->{$key}->{$key2}}) |
|
0
|
|
|
|
|
|
|
1607
|
|
|
|
|
|
|
{ |
1608
|
0
|
0
|
|
|
|
|
$fudebug and print "key3 $key3\n"; |
1609
|
0
|
|
|
|
|
|
$item->{$key."_".$key2."_".$key3}=$item->{$key}->{$key2}->{$key3}; |
1610
|
0
|
0
|
|
|
|
|
$fudebug and printf "creating $key"."_$key2"."_$key3 as a %s\n",ref($item->{$key}->{$key2}->{$key3}); |
1611
|
|
|
|
|
|
|
} |
1612
|
0
|
0
|
|
|
|
|
$fudebug and print "deleting $key->$key2 and $key _$key2\n"; |
1613
|
0
|
|
|
|
|
|
delete $item->{$key}->{$key2}; |
1614
|
0
|
|
|
|
|
|
delete $item->{$key."_".$key2}; |
1615
|
|
|
|
|
|
|
} |
1616
|
|
|
|
|
|
|
} |
1617
|
0
|
0
|
|
|
|
|
$fudebug and print "deleting: $key\n"; |
1618
|
0
|
|
|
|
|
|
delete $item->{$key}; |
1619
|
|
|
|
|
|
|
} |
1620
|
0
|
0
|
|
|
|
|
if (ref($item->{$key}) eq 'ARRAY') |
1621
|
|
|
|
|
|
|
{ |
1622
|
0
|
0
|
|
|
|
|
$fudebug and print "$key is array ref\n"; |
1623
|
0
|
|
|
|
|
|
for (@{$item->{$key}}) |
|
0
|
|
|
|
|
|
|
1624
|
|
|
|
|
|
|
{ |
1625
|
0
|
|
|
|
|
|
$self->flatten_withunder($_); |
1626
|
|
|
|
|
|
|
} |
1627
|
|
|
|
|
|
|
} |
1628
|
|
|
|
|
|
|
} |
1629
|
|
|
|
|
|
|
} |
1630
|
0
|
0
|
|
|
|
|
if (ref($item) eq 'ARRAY') |
1631
|
|
|
|
|
|
|
{ |
1632
|
0
|
0
|
|
|
|
|
$fudebug and print "is an array\n"; |
1633
|
0
|
|
|
|
|
|
for (@$item) |
1634
|
|
|
|
|
|
|
{ |
1635
|
0
|
|
|
|
|
|
$self->flatten_withunder($_); |
1636
|
|
|
|
|
|
|
} |
1637
|
|
|
|
|
|
|
} |
1638
|
|
|
|
|
|
|
} |
1639
|
0
|
0
|
|
|
|
|
$fudebug and print "processed\n"; |
1640
|
|
|
|
|
|
|
} |
1641
|
|
|
|
|
|
|
|
1642
|
|
|
|
|
|
|
|
1643
|
|
|
|
|
|
|
##################################################################### |
1644
|
|
|
|
|
|
|
# uses known structure of supplied deep hash to search for item |
1645
|
|
|
|
|
|
|
# should probably replace with a more generalised deep fetch function. |
1646
|
|
|
|
|
|
|
##################################################################### |
1647
|
|
|
|
|
|
|
|
1648
|
|
|
|
|
|
|
=head2 fetch |
1649
|
|
|
|
|
|
|
|
1650
|
|
|
|
|
|
|
This function is a way to hide the various structures a position may have |
1651
|
|
|
|
|
|
|
|
1652
|
|
|
|
|
|
|
Obsolete but still used sometimes. |
1653
|
|
|
|
|
|
|
|
1654
|
|
|
|
|
|
|
Parameters |
1655
|
|
|
|
|
|
|
|
1656
|
|
|
|
|
|
|
1 A position hash ref, $h |
1657
|
|
|
|
|
|
|
2 The name of the item to be retrieved. |
1658
|
|
|
|
|
|
|
|
1659
|
|
|
|
|
|
|
Returns undef if not found, or the value of item if it is. |
1660
|
|
|
|
|
|
|
|
1661
|
|
|
|
|
|
|
The function looks first in $h->{item} then |
1662
|
|
|
|
|
|
|
in $h->{position}=>{item} and then in $h->{market}->{item} |
1663
|
|
|
|
|
|
|
|
1664
|
|
|
|
|
|
|
Its only useful with positions, not hashes in general. |
1665
|
|
|
|
|
|
|
|
1666
|
|
|
|
|
|
|
=cut |
1667
|
|
|
|
|
|
|
##################################################################### |
1668
|
|
|
|
|
|
|
sub fetch |
1669
|
|
|
|
|
|
|
{ |
1670
|
0
|
|
|
0
|
1
|
|
my ($self,$position,$item)=@_; |
1671
|
|
|
|
|
|
|
|
1672
|
|
|
|
|
|
|
# return "NOT A HASREF $position"if (ref($position) ne 'HASH'); |
1673
|
0
|
0
|
|
|
|
|
die "supplied position $position to fetch() is not a HASHREF" if (ref($position) ne 'HASH'); |
1674
|
0
|
0
|
|
|
|
|
defined $item or die "fetch, item undefined"; |
1675
|
0
|
|
|
|
|
|
my $p=$position->{position}; |
1676
|
0
|
|
|
|
|
|
my $m=$position->{market}; |
1677
|
|
|
|
|
|
|
|
1678
|
0
|
0
|
|
|
|
|
if (exists $position->{$item}) { return $position->{$item}; } |
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1679
|
0
|
|
|
|
|
|
elsif (exists $p->{$item}) { return $p->{$item}; } |
1680
|
0
|
|
|
|
|
|
elsif (exists $m->{$item}) { return $m->{$item}; } |
1681
|
|
|
|
|
|
|
else { |
1682
|
0
|
|
|
|
|
|
return undef; |
1683
|
|
|
|
|
|
|
} |
1684
|
|
|
|
|
|
|
|
1685
|
|
|
|
|
|
|
} |
1686
|
|
|
|
|
|
|
|
1687
|
|
|
|
|
|
|
##################################################################### |
1688
|
|
|
|
|
|
|
# given an instrument name in search, look for it inside the instrumentName, and return |
1689
|
|
|
|
|
|
|
# the epic. Fail if result is not 1 item. |
1690
|
|
|
|
|
|
|
# used for filling in the epic (a unique identifier) in old data files |
1691
|
|
|
|
|
|
|
# where I forgot to store it. |
1692
|
|
|
|
|
|
|
##################################################################### |
1693
|
|
|
|
|
|
|
|
1694
|
|
|
|
|
|
|
=head2 epicsearch |
1695
|
|
|
|
|
|
|
|
1696
|
|
|
|
|
|
|
Find the epic (unique identifier) for an instrument from the underlying share. |
1697
|
|
|
|
|
|
|
|
1698
|
|
|
|
|
|
|
This function calls IG's search API looking for a match to the name. If found |
1699
|
|
|
|
|
|
|
the value of the epic is returned. |
1700
|
|
|
|
|
|
|
|
1701
|
|
|
|
|
|
|
=head3 Status - very experimental. Seems to work well. |
1702
|
|
|
|
|
|
|
|
1703
|
|
|
|
|
|
|
Contains print and die statements. Useful if you forgot to record the epic. |
1704
|
|
|
|
|
|
|
|
1705
|
|
|
|
|
|
|
=cut |
1706
|
|
|
|
|
|
|
|
1707
|
|
|
|
|
|
|
##################################################################### |
1708
|
|
|
|
|
|
|
sub epicsearch |
1709
|
|
|
|
|
|
|
{ |
1710
|
0
|
|
|
0
|
1
|
|
my ($self,$search)=@_; |
1711
|
0
|
|
|
|
|
|
my $headers = |
1712
|
|
|
|
|
|
|
{ |
1713
|
|
|
|
|
|
|
'Content-Type' => 'application/json; charset=UTF-8', |
1714
|
|
|
|
|
|
|
'Accept' => 'application/json; charset=UTF-8', |
1715
|
|
|
|
|
|
|
VERSION => 1, |
1716
|
|
|
|
|
|
|
CST=>$self->CST, |
1717
|
|
|
|
|
|
|
'X-SECURITY-TOKEN'=> $self->XSECURITYTOKEN, |
1718
|
|
|
|
|
|
|
'X-IG-API-KEY'=> $self->apikey, |
1719
|
|
|
|
|
|
|
}; |
1720
|
|
|
|
|
|
|
#my $jheaders = encode_json($headers); |
1721
|
0
|
|
|
|
|
|
my $jheaders=JSON->new->canonical->encode($headers); |
1722
|
0
|
|
|
|
|
|
my $client = REST::Client->new(); |
1723
|
0
|
|
|
|
|
|
$client->setHost($self->_url); |
1724
|
0
|
|
|
|
|
|
$search=~s#/#%2F#g; |
1725
|
0
|
|
|
|
|
|
my $url="/markets?searchTerm=$search"; |
1726
|
0
|
|
|
|
|
|
$search=~s#%2F#/#g; |
1727
|
0
|
|
|
|
|
|
$url=~s/ /%20/g; |
1728
|
0
|
|
|
|
|
|
my $r=$client->GET ( $url, $headers); |
1729
|
|
|
|
|
|
|
|
1730
|
|
|
|
|
|
|
# my $resp=decode_json($client->responseContent()); |
1731
|
|
|
|
|
|
|
|
1732
|
|
|
|
|
|
|
|
1733
|
|
|
|
|
|
|
#print "url=$url\n"; |
1734
|
0
|
|
|
|
|
|
my $code; |
1735
|
|
|
|
|
|
|
|
1736
|
0
|
|
|
|
|
|
$code=$client->responseCode(); |
1737
|
|
|
|
|
|
|
|
1738
|
0
|
|
|
|
|
|
my $retried=0; |
1739
|
0
|
|
0
|
|
|
|
while ($code==403 and $retried<4) |
1740
|
|
|
|
|
|
|
{ |
1741
|
0
|
|
|
|
|
|
sleep 10; |
1742
|
0
|
|
|
|
|
|
$retried++; |
1743
|
0
|
|
|
|
|
|
$r=$client->GET ( $url, $headers); |
1744
|
0
|
|
|
|
|
|
$code=$client->responseCode(); |
1745
|
|
|
|
|
|
|
# print "search retried\n"; |
1746
|
|
|
|
|
|
|
} |
1747
|
|
|
|
|
|
|
|
1748
|
0
|
0
|
|
|
|
|
die "response code from url='$url' code=$code retried $retried times" if ($code!=200); |
1749
|
|
|
|
|
|
|
|
1750
|
0
|
|
|
|
|
|
my $markets=decode_json($client->responseContent); |
1751
|
|
|
|
|
|
|
# print JSON->new->ascii->pretty->encode($markets)."\n"; |
1752
|
|
|
|
|
|
|
|
1753
|
0
|
|
|
|
|
|
my @wantedmarkets=grep { $_->{expiry} eq 'DFB' } @{$markets->{markets}}; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
1754
|
0
|
|
|
|
|
|
@wantedmarkets=grep { $self->_nothe($self->fetch($_,'instrumentName') , $search) } @wantedmarkets; |
|
0
|
|
|
|
|
|
|
1755
|
|
|
|
|
|
|
|
1756
|
0
|
|
|
|
|
|
@wantedmarkets=map { $_->{epic} } @wantedmarkets; |
|
0
|
|
|
|
|
|
|
1757
|
0
|
0
|
|
|
|
|
die "Zero epics found for search $search" if (@wantedmarkets==0); |
1758
|
0
|
0
|
|
|
|
|
die "Multiple epics found @wantedmarkets for search $search" if (@wantedmarkets!=1); |
1759
|
|
|
|
|
|
|
|
1760
|
0
|
|
|
|
|
|
return $wantedmarkets[0]; |
1761
|
|
|
|
|
|
|
|
1762
|
|
|
|
|
|
|
} |
1763
|
|
|
|
|
|
|
##################################################### |
1764
|
|
|
|
|
|
|
# remove a trailing 'the' |
1765
|
|
|
|
|
|
|
##################################################### |
1766
|
|
|
|
|
|
|
sub _nothe |
1767
|
|
|
|
|
|
|
{ |
1768
|
0
|
|
|
0
|
|
|
my ($self,$x,$y)=@_; |
1769
|
|
|
|
|
|
|
|
1770
|
|
|
|
|
|
|
# print "comparing $x $y \n"; |
1771
|
0
|
|
|
|
|
|
$x=~s#/.*$##; |
1772
|
0
|
|
|
|
|
|
$y=~s#/.*$##; |
1773
|
|
|
|
|
|
|
|
1774
|
0
|
|
|
|
|
|
return $x eq $y; |
1775
|
|
|
|
|
|
|
} |
1776
|
|
|
|
|
|
|
# so this is used to read one of my old data files. |
1777
|
|
|
|
|
|
|
################################################################################## |
1778
|
|
|
|
|
|
|
# Reads am ascii file - older format and returns a list of positions, |
1779
|
|
|
|
|
|
|
# a hashref keyed on epic. |
1780
|
|
|
|
|
|
|
################################################################################## |
1781
|
|
|
|
|
|
|
|
1782
|
|
|
|
|
|
|
=head2 readfile_oldformat |
1783
|
|
|
|
|
|
|
|
1784
|
|
|
|
|
|
|
|
1785
|
|
|
|
|
|
|
Parameters |
1786
|
|
|
|
|
|
|
|
1787
|
|
|
|
|
|
|
1 Path to a file to read |
1788
|
|
|
|
|
|
|
|
1789
|
|
|
|
|
|
|
A file readable by this function may be generated by using printpos with format as follows: |
1790
|
|
|
|
|
|
|
"%-41sinstrumentName %+6.2fsize %-9.2flevel ". |
1791
|
|
|
|
|
|
|
"%-9.2fbid £%-8.2fprofit %5.1fprofitpc%% £%10.2fatrisk\n", |
1792
|
|
|
|
|
|
|
|
1793
|
|
|
|
|
|
|
This file was originally generated to be human readable so reading by machine is a stretch. |
1794
|
|
|
|
|
|
|
|
1795
|
|
|
|
|
|
|
=head3 Status - downright broken (for you). Sorry! |
1796
|
|
|
|
|
|
|
|
1797
|
|
|
|
|
|
|
May contains print and die statements. Contaions hardcoded paths that will need to be |
1798
|
|
|
|
|
|
|
changed. |
1799
|
|
|
|
|
|
|
|
1800
|
|
|
|
|
|
|
=cut |
1801
|
|
|
|
|
|
|
################################################################################## |
1802
|
|
|
|
|
|
|
sub readfile_oldformat |
1803
|
|
|
|
|
|
|
{ |
1804
|
0
|
|
|
0
|
1
|
|
my ($self, $f,$writenewfile)=@_; |
1805
|
0
|
|
|
|
|
|
my $positions={}; |
1806
|
0
|
|
|
|
|
|
my $totalline; |
1807
|
0
|
|
|
|
|
|
$f="/home/mark/igrec/results/$f"; |
1808
|
0
|
0
|
|
|
|
|
open(F,$f) or die "cannot open $f"; |
1809
|
|
|
|
|
|
|
#Roku Inc +0.38 16501.00 21842.0 £2029.58 32.4% £ 8299.96 |
1810
|
0
|
|
|
|
|
|
my @fieldhashnames=qw(epic instrumentName size level bid profit profitpc atrisk); |
1811
|
0
|
|
|
|
|
|
while (<F>) |
1812
|
|
|
|
|
|
|
{ |
1813
|
0
|
|
|
|
|
|
my @fields; |
1814
|
0
|
|
|
|
|
|
my @names=@fieldhashnames; |
1815
|
0
|
|
|
|
|
|
my $position={}; |
1816
|
|
|
|
|
|
|
|
1817
|
0
|
|
|
|
|
|
chomp; |
1818
|
0
|
0
|
|
|
|
|
if (m/\|/) |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1819
|
|
|
|
|
|
|
{ |
1820
|
0
|
|
|
|
|
|
die; |
1821
|
|
|
|
|
|
|
} |
1822
|
|
|
|
|
|
|
elsif (m/^Name/) |
1823
|
|
|
|
|
|
|
{ |
1824
|
0
|
|
|
|
|
|
s/[£%]//g; |
1825
|
0
|
|
|
|
|
|
@fields=split(/ +/); |
1826
|
0
|
|
|
|
|
|
unshift(@fields,'Epic'); |
1827
|
|
|
|
|
|
|
# print "#".join("\|",@fields)."\n"; |
1828
|
|
|
|
|
|
|
} |
1829
|
|
|
|
|
|
|
elsif (m/^Total/) |
1830
|
|
|
|
|
|
|
{ |
1831
|
0
|
|
|
|
|
|
$totalline=$_; |
1832
|
|
|
|
|
|
|
} |
1833
|
|
|
|
|
|
|
else |
1834
|
|
|
|
|
|
|
{ |
1835
|
0
|
|
|
|
|
|
my $name=substr($_,0,42); |
1836
|
0
|
|
|
|
|
|
my $line=substr($_,43); |
1837
|
0
|
|
|
|
|
|
$name=~s/ +$//; |
1838
|
0
|
|
|
|
|
|
$line=~s/[\$£%]//g; |
1839
|
0
|
|
|
|
|
|
@fields=split(/ +/,$line); |
1840
|
0
|
|
|
|
|
|
my $epic=$self->epicsearch($name); |
1841
|
0
|
|
|
|
|
|
unshift(@fields,$epic,$name); |
1842
|
|
|
|
|
|
|
#die "$line\n@fields\n@names"; |
1843
|
0
|
|
|
|
|
|
while (@names) |
1844
|
|
|
|
|
|
|
{ |
1845
|
0
|
|
|
|
|
|
$position->{shift(@names)}=shift(@fields); |
1846
|
|
|
|
|
|
|
} |
1847
|
0
|
|
|
|
|
|
$positions->{$epic}=$position; |
1848
|
|
|
|
|
|
|
} |
1849
|
|
|
|
|
|
|
} |
1850
|
|
|
|
|
|
|
# close F; |
1851
|
0
|
0
|
|
|
|
|
if ($writenewfile) |
1852
|
|
|
|
|
|
|
{ |
1853
|
0
|
|
|
|
|
|
$f=~s/results/r2/; |
1854
|
0
|
0
|
|
|
|
|
if (! -e $f) |
1855
|
|
|
|
|
|
|
{ |
1856
|
0
|
0
|
|
|
|
|
open(my $g,">" , $f) or die "Cannot open $f for write"; |
1857
|
0
|
|
|
|
|
|
my $format= "%sepic|%sinstrumentName|%0.2fsize|%-0.2flevel|". |
1858
|
|
|
|
|
|
|
"%-0.2fbid|£%-0.2fprofit|%0.1fprofitpc%%|£%0.2fatrisk\n", |
1859
|
|
|
|
|
|
|
print $g "Epic|Instrumentname|Size|Level|Bid|Profit£|Profitpc%|Atrisk£\n"; |
1860
|
0
|
|
|
|
|
|
my $a=$self->agg([values %$positions]); |
1861
|
0
|
|
|
|
|
|
for (@$a) |
1862
|
|
|
|
|
|
|
{ |
1863
|
0
|
|
|
|
|
|
$self->printpos($g,$_,$format); |
1864
|
|
|
|
|
|
|
} |
1865
|
0
|
|
|
|
|
|
print $g $totalline."\n"; |
1866
|
|
|
|
|
|
|
} |
1867
|
|
|
|
|
|
|
} |
1868
|
0
|
|
|
|
|
|
return $positions; |
1869
|
|
|
|
|
|
|
} |
1870
|
|
|
|
|
|
|
################################################################################## |
1871
|
|
|
|
|
|
|
# Reads am ascii file and returns a list of positions, |
1872
|
|
|
|
|
|
|
# a hashref keyed on epic. |
1873
|
|
|
|
|
|
|
################################################################################## |
1874
|
|
|
|
|
|
|
|
1875
|
|
|
|
|
|
|
=head2 readfile |
1876
|
|
|
|
|
|
|
|
1877
|
|
|
|
|
|
|
|
1878
|
|
|
|
|
|
|
Parameters |
1879
|
|
|
|
|
|
|
|
1880
|
|
|
|
|
|
|
1 Path to a file to read |
1881
|
|
|
|
|
|
|
|
1882
|
|
|
|
|
|
|
A file readable by this function may be generated by using printpos with format as follows: |
1883
|
|
|
|
|
|
|
"%sepic|%sinstrumentName|%0.2fsize|%-0.2flevel|". |
1884
|
|
|
|
|
|
|
"%-0.2fbid|£%-0.2fprofit|%0.1fprofitpc%%|£%0.2fatrisk|%smarketStatus\n", |
1885
|
|
|
|
|
|
|
|
1886
|
|
|
|
|
|
|
=head3 Status - downright broken (for you). Sorry! |
1887
|
|
|
|
|
|
|
|
1888
|
|
|
|
|
|
|
The function contains a hardcoded path for reading the files. You would need a |
1889
|
|
|
|
|
|
|
crontab entry to generate them. |
1890
|
|
|
|
|
|
|
|
1891
|
|
|
|
|
|
|
May contain print and die statements. Contains hardcoded paths that will need to be |
1892
|
|
|
|
|
|
|
changed. |
1893
|
|
|
|
|
|
|
|
1894
|
|
|
|
|
|
|
=cut |
1895
|
|
|
|
|
|
|
################################################################################## |
1896
|
|
|
|
|
|
|
sub readfile |
1897
|
|
|
|
|
|
|
{ |
1898
|
0
|
|
|
0
|
1
|
|
my ($self,$f)=@_; |
1899
|
|
|
|
|
|
|
|
1900
|
0
|
|
|
|
|
|
my $positions={}; |
1901
|
0
|
|
|
|
|
|
$f="/home/mark/igrec/r2/$f"; |
1902
|
0
|
0
|
|
|
|
|
open(F,$f) or die "cannot open $f"; |
1903
|
0
|
|
|
|
|
|
my @fieldhashnames=qw(epic instrumentName size level bid profit profitpc atrisk tradeable); |
1904
|
0
|
|
|
|
|
|
my $ln=0; |
1905
|
0
|
|
|
|
|
|
while (<F>) |
1906
|
|
|
|
|
|
|
{ |
1907
|
0
|
|
|
|
|
|
my @fields; |
1908
|
0
|
|
|
|
|
|
my @names=@fieldhashnames; |
1909
|
0
|
|
|
|
|
|
my $position={}; |
1910
|
|
|
|
|
|
|
|
1911
|
0
|
|
|
|
|
|
$ln++; |
1912
|
0
|
|
|
|
|
|
chomp; |
1913
|
0
|
0
|
|
|
|
|
if (m/^Total/) |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1914
|
|
|
|
|
|
|
{ |
1915
|
0
|
|
|
|
|
|
next; |
1916
|
|
|
|
|
|
|
} |
1917
|
|
|
|
|
|
|
elsif (m/ Positions$/) |
1918
|
|
|
|
|
|
|
{ |
1919
|
0
|
|
|
|
|
|
next; |
1920
|
|
|
|
|
|
|
} |
1921
|
|
|
|
|
|
|
elsif (m/^ *$/) |
1922
|
|
|
|
|
|
|
{ |
1923
|
0
|
|
|
|
|
|
next; |
1924
|
|
|
|
|
|
|
} |
1925
|
|
|
|
|
|
|
elsif (m/#/) |
1926
|
|
|
|
|
|
|
{ |
1927
|
0
|
|
|
|
|
|
next; |
1928
|
|
|
|
|
|
|
} |
1929
|
|
|
|
|
|
|
elsif (!m/\|/) |
1930
|
|
|
|
|
|
|
{ |
1931
|
0
|
|
|
|
|
|
die "No | lin line $ln file $f"; |
1932
|
|
|
|
|
|
|
} |
1933
|
|
|
|
|
|
|
elsif (m/Epic/) |
1934
|
|
|
|
|
|
|
{ |
1935
|
0
|
|
|
|
|
|
next; |
1936
|
|
|
|
|
|
|
} |
1937
|
|
|
|
|
|
|
else |
1938
|
|
|
|
|
|
|
{ |
1939
|
0
|
|
|
|
|
|
s/[£&]//g; |
1940
|
0
|
|
|
|
|
|
@fields=split(/\|/); |
1941
|
0
|
|
|
|
|
|
for my $fieldname (@fieldhashnames) |
1942
|
|
|
|
|
|
|
{ |
1943
|
0
|
0
|
|
|
|
|
die if (!defined $names[0]); |
1944
|
|
|
|
|
|
|
#print "names[0]=$names[0]\n"; |
1945
|
0
|
|
|
|
|
|
$position->{$fieldname}=shift(@fields); |
1946
|
|
|
|
|
|
|
} |
1947
|
0
|
|
|
|
|
|
$positions->{$position->{epic}}=$position; |
1948
|
0
|
|
0
|
|
|
|
$position->{marketStatus}//=''; # older files do not record this. |
1949
|
|
|
|
|
|
|
} |
1950
|
|
|
|
|
|
|
} |
1951
|
0
|
|
|
|
|
|
return $positions; |
1952
|
|
|
|
|
|
|
} |
1953
|
|
|
|
|
|
|
##################################################################### |
1954
|
|
|
|
|
|
|
# format strings contained embedded printf specifiers followed by |
1955
|
|
|
|
|
|
|
# a hash element name . |
1956
|
|
|
|
|
|
|
# |
1957
|
|
|
|
|
|
|
# eg "%sdate %sdescription %sepic %sstatus\n"; |
1958
|
|
|
|
|
|
|
# eg "%-20sdate %-30sdescription %-20sepic %-15sstatus\n"; |
1959
|
|
|
|
|
|
|
# eg |
1960
|
|
|
|
|
|
|
# "%sepic|%sinstrumentName|%6.2fsize|%-9.2flevel|". |
1961
|
|
|
|
|
|
|
# "%-9.2fbid|£%-8.2fprofit|%5.1fprofitpc%%|£%10.2fatrisk\n", |
1962
|
|
|
|
|
|
|
#eg |
1963
|
|
|
|
|
|
|
# "%-41sinstrumentName %+6.2fsize %-9.2flevel ". |
1964
|
|
|
|
|
|
|
# "%-9.2fbid £%-8.2fprofit %5.1fprofitpc%% £%10.2fatrisk\n", |
1965
|
|
|
|
|
|
|
# Arguments: |
1966
|
|
|
|
|
|
|
# 1) An IG object ref. (self) Is not really used. |
1967
|
|
|
|
|
|
|
# 2) Either "stdout" or an open writable file handle. |
1968
|
|
|
|
|
|
|
# 3) A hash possibly deep, with items. Ig the item is not found directly in the hash, |
1969
|
|
|
|
|
|
|
# the $self->fetch function is used for access. If still not found |
1970
|
|
|
|
|
|
|
# then "UNDEF" is printed. |
1971
|
|
|
|
|
|
|
# CHANGED to $self->uds |
1972
|
|
|
|
|
|
|
# OR: If this is an array ref, then a title line is ptinted using the format string |
1973
|
|
|
|
|
|
|
# and the referenced array of titles |
1974
|
|
|
|
|
|
|
# OR: If empty dtring ort undef, derive titles from the format |
1975
|
|
|
|
|
|
|
# string and print a title line. |
1976
|
|
|
|
|
|
|
# 4) A formatting string. Can contain text, containing embedded |
1977
|
|
|
|
|
|
|
# format instructions like %6.2fsize here %6.2f is a print f |
1978
|
|
|
|
|
|
|
# specifier and size is the name of the item to retrieve from the hash. |
1979
|
|
|
|
|
|
|
# 5,6) up /down can be percent gives green if > up, bold green if > 5*up. |
1980
|
|
|
|
|
|
|
# can be a coloration function of position. |
1981
|
|
|
|
|
|
|
# just one function, so no down ever. |
1982
|
|
|
|
|
|
|
# function takes argument position, and returns optional colors |
1983
|
|
|
|
|
|
|
##################################################################### |
1984
|
|
|
|
|
|
|
|
1985
|
|
|
|
|
|
|
=head2 printpos |
1986
|
|
|
|
|
|
|
|
1987
|
|
|
|
|
|
|
=head3 Parmeters |
1988
|
|
|
|
|
|
|
|
1989
|
|
|
|
|
|
|
A file handle or the word stdout, all output sent here. |
1990
|
|
|
|
|
|
|
|
1991
|
|
|
|
|
|
|
A hashref of items to print |
1992
|
|
|
|
|
|
|
OR: If this is an array ref, then a title line is ptinted using the format string |
1993
|
|
|
|
|
|
|
and the referenced array of titles |
1994
|
|
|
|
|
|
|
OR: If empty string or undef, derive titles from the format |
1995
|
|
|
|
|
|
|
string and print a title line. |
1996
|
|
|
|
|
|
|
|
1997
|
|
|
|
|
|
|
A formatting string. Can contain text, containing embedded |
1998
|
|
|
|
|
|
|
format instructions like %6.2fsize here %6.2f is a print f |
1999
|
|
|
|
|
|
|
specifier and size is the name of the item to retrieve from the hash. |
2000
|
|
|
|
|
|
|
|
2001
|
|
|
|
|
|
|
OPTIONAL up can be percent gives green if > up, bold green if > 5*up. |
2002
|
|
|
|
|
|
|
can be a coloration function of position. Just one function, so no down ever if a function is given |
2003
|
|
|
|
|
|
|
function takes argument position, and returns optional colors |
2004
|
|
|
|
|
|
|
|
2005
|
|
|
|
|
|
|
OPTIONAL down can be percent gives red if <down , bold red if < 5*down. |
2006
|
|
|
|
|
|
|
|
2007
|
|
|
|
|
|
|
=head3 Description |
2008
|
|
|
|
|
|
|
|
2009
|
|
|
|
|
|
|
This is a very general function will work with any hash. |
2010
|
|
|
|
|
|
|
|
2011
|
|
|
|
|
|
|
=cut |
2012
|
|
|
|
|
|
|
##################################################################### |
2013
|
|
|
|
|
|
|
sub printpos |
2014
|
|
|
|
|
|
|
{ |
2015
|
|
|
|
|
|
|
|
2016
|
0
|
|
|
0
|
1
|
|
my ($self,$out,$position,$format,$up,$down)=@_; |
2017
|
|
|
|
|
|
|
|
2018
|
0
|
|
|
|
|
|
my $colsub; |
2019
|
|
|
|
|
|
|
|
2020
|
0
|
0
|
|
|
|
|
$out=*STDOUT if ($out eq "stdout"); |
2021
|
|
|
|
|
|
|
|
2022
|
0
|
0
|
0
|
|
|
|
$down=-$up if (defined $up and ref($up) eq '' and !defined $down) ; |
|
|
|
0
|
|
|
|
|
2023
|
|
|
|
|
|
|
|
2024
|
0
|
0
|
0
|
|
|
|
if (defined $up and ref($up) ne 'CODE') |
2025
|
|
|
|
|
|
|
{ |
2026
|
|
|
|
|
|
|
$colsub=sub |
2027
|
|
|
|
|
|
|
{ |
2028
|
0
|
|
|
0
|
|
|
my ($position)=shift; |
2029
|
0
|
|
|
|
|
|
my $v1=$position->{dbid}; |
2030
|
0
|
|
|
|
|
|
my $col=''; |
2031
|
0
|
|
|
|
|
|
$v1=~s/%//; |
2032
|
0
|
0
|
0
|
|
|
|
$col=Green if (defined $up and $v1>$up); |
2033
|
0
|
0
|
0
|
|
|
|
$col=Red if (defined $down and $v1<$down); |
2034
|
0
|
0
|
0
|
|
|
|
$col=Green+Bold if (defined $up and $v1>$up*5); |
2035
|
0
|
0
|
0
|
|
|
|
$col=Red+Bold if (defined $down and $v1<5*$down); |
2036
|
0
|
|
|
|
|
|
return $col; |
2037
|
0
|
|
|
|
|
|
}; |
2038
|
|
|
|
|
|
|
} |
2039
|
0
|
0
|
0
|
|
|
|
$colsub=$up if (defined $up and ref($up) eq 'CODE'); |
2040
|
0
|
0
|
|
0
|
|
|
$colsub=sub {''} if (!defined $up); |
|
0
|
|
|
|
|
|
|
2041
|
|
|
|
|
|
|
|
2042
|
|
|
|
|
|
|
|
2043
|
0
|
|
|
|
|
|
my $titles=$format; |
2044
|
0
|
0
|
|
|
|
|
if (ref($position) eq 'ARRAY') # its titles to print! |
2045
|
|
|
|
|
|
|
{ |
2046
|
|
|
|
|
|
|
#$format=~s/%[-+]/%/g; |
2047
|
|
|
|
|
|
|
#print "$format\n"; |
2048
|
0
|
|
|
|
|
|
while ($format=~m/[-+]?([0-9]+)\.([0-9]+)/) |
2049
|
|
|
|
|
|
|
{ |
2050
|
0
|
|
|
|
|
|
my $x; |
2051
|
0
|
|
|
|
|
|
$x=$1; |
2052
|
0
|
0
|
|
|
|
|
abs($2)>abs($x) and $x=$2; |
2053
|
0
|
|
|
|
|
|
$format=~s/%([-+]?)([0-9]+)\.([0-9]+)/%$1$x/; |
2054
|
|
|
|
|
|
|
} |
2055
|
|
|
|
|
|
|
#print "#1 $format\n"; |
2056
|
0
|
|
|
|
|
|
$format=~s/%\+\+/%+/g; |
2057
|
|
|
|
|
|
|
#print "#2 $format\n"; |
2058
|
0
|
|
|
|
|
|
$format=~s/%([-\+]?[0-9]+)\.[0-9]+/%$1/g; |
2059
|
|
|
|
|
|
|
#print "#3 $format\n"; |
2060
|
0
|
|
|
|
|
|
$format=~s/%([-\+]?[0-9]+)[fd]/%$1s/g; |
2061
|
|
|
|
|
|
|
#print "#4 $format\n"; |
2062
|
0
|
|
|
|
|
|
$format=~s/%([-\+]?[0-9]*)([a-zA-Z_][a-zA-Z0-9_]*)/%$1s/g; |
2063
|
|
|
|
|
|
|
#die $format; |
2064
|
|
|
|
|
|
|
# print "$format\n"; exit; |
2065
|
|
|
|
|
|
|
#$"=":"; print "@$position\n"; |
2066
|
|
|
|
|
|
|
|
2067
|
|
|
|
|
|
|
|
2068
|
0
|
|
|
|
|
|
$format=~s/[\x82\x83\xc3]+//g; # so we get some strange characters like ÃÂ occuring in pairs. Not sure why. This removes them. |
2069
|
|
|
|
|
|
|
#$format="%-41s %+7s %11s %-10s £%-10s %5s%% £%12s %-9s %-4s"; |
2070
|
|
|
|
|
|
|
#print "$format\n"; #exit; |
2071
|
0
|
0
|
0
|
|
|
|
print $out Bold if ($self->col and defined $INC{'Term/Chrome.pm'}); |
2072
|
|
|
|
|
|
|
# print "format='$format' @$position\n"; |
2073
|
0
|
|
|
|
|
|
printf $out $format,@$position; |
2074
|
0
|
0
|
0
|
|
|
|
print $out Reset if ($self->col and defined $INC{'Term/Chrome.pm'}); |
2075
|
0
|
|
|
|
|
|
return; |
2076
|
|
|
|
|
|
|
} |
2077
|
|
|
|
|
|
|
|
2078
|
|
|
|
|
|
|
# auto generated title list from the names |
2079
|
0
|
0
|
0
|
|
|
|
if (!defined $position or $position eq '') |
2080
|
|
|
|
|
|
|
{ |
2081
|
0
|
|
|
|
|
|
$titles=~s/\n//g; |
2082
|
0
|
|
|
|
|
|
$titles=~s/%([-+0-9.]*)([sfd])/%/g; |
2083
|
0
|
|
|
|
|
|
$titles=~s/%%/__PC__/g; |
2084
|
0
|
|
|
|
|
|
$titles=~s/%//; # just one |
2085
|
0
|
|
|
|
|
|
$titles=~s/£%([a-zA-Z]+)/%$1£/g; |
2086
|
0
|
|
|
|
|
|
my @titles=split(/%/,$titles); |
2087
|
0
|
|
|
|
|
|
map {s/[|,]//g } @titles; |
|
0
|
|
|
|
|
|
|
2088
|
0
|
|
|
|
|
|
map {s/ +//g } @titles; |
|
0
|
|
|
|
|
|
|
2089
|
0
|
|
|
|
|
|
map { s/__PC__//g; } @titles; |
|
0
|
|
|
|
|
|
|
2090
|
0
|
|
|
|
|
|
map { s/([\w']+)/\u\L$1/g; } @titles; |
|
0
|
|
|
|
|
|
|
2091
|
0
|
|
|
|
|
|
while ($format=~m/%[-+]?([0-9]+)\.([0-9]+)/) |
2092
|
|
|
|
|
|
|
{ |
2093
|
0
|
|
|
|
|
|
my $x; |
2094
|
|
|
|
|
|
|
#my $x=$1+$2; |
2095
|
0
|
|
|
|
|
|
$x=$1; |
2096
|
0
|
0
|
|
|
|
|
$2>$x and $x=$2; |
2097
|
0
|
|
|
|
|
|
$format=~s/%([-+]?)([0-9]+)\.([0-9]+)/%$1$x/; |
2098
|
|
|
|
|
|
|
} |
2099
|
0
|
|
|
|
|
|
$format=~s/(%[-+0-9.]*)[a-zA-Z]+/$1s/g; |
2100
|
|
|
|
|
|
|
#$format=~s/(%[-+0-9]+)\.[0-9]+/$1/g; |
2101
|
0
|
|
|
|
|
|
$format=~s/£//g; |
2102
|
|
|
|
|
|
|
#die "format=$format titles=@titles"; |
2103
|
0
|
|
|
|
|
|
$format=~s/[\x82\x83\xc3]+//g; # so we get some strange characters like ÃÂ occuring in pairs. Not sure why. This removes them. |
2104
|
0
|
0
|
0
|
|
|
|
print $out Bold if ($self->col and defined $INC{'Term/Chrome.pm'}); |
2105
|
0
|
|
|
|
|
|
printf $out $format, @titles; |
2106
|
0
|
0
|
0
|
|
|
|
print $out Reset if ($self->col and defined $INC{'Term/Chrome.pm'}); |
2107
|
0
|
|
|
|
|
|
return; |
2108
|
|
|
|
|
|
|
} |
2109
|
|
|
|
|
|
|
|
2110
|
|
|
|
|
|
|
|
2111
|
|
|
|
|
|
|
|
2112
|
|
|
|
|
|
|
# $p=$position->{position}; |
2113
|
|
|
|
|
|
|
# $m=$position->{market}; |
2114
|
|
|
|
|
|
|
|
2115
|
0
|
|
|
|
|
|
$format=~s/%%/##/g; |
2116
|
|
|
|
|
|
|
|
2117
|
|
|
|
|
|
|
|
2118
|
|
|
|
|
|
|
# while (($format=~s/%([-+0-9]+\.[0-9]+)([a-z][a-zA-Z0-9]*)/%$1__S__/) || ($format=~s/%([-+0-9]*)([a-z][a-zA-Z0-9]*)/%$1__F__/)) |
2119
|
|
|
|
|
|
|
# { |
2120
|
|
|
|
|
|
|
# my $s; |
2121
|
|
|
|
|
|
|
# $s=$activity->{$2}; |
2122
|
|
|
|
|
|
|
# my $pos=$1; |
2123
|
|
|
|
|
|
|
# $pos=~s/-//; |
2124
|
|
|
|
|
|
|
# $s=substr($s,0,$pos) if (defined(pos) and $pos ne '' and $pos<length($s)); |
2125
|
|
|
|
|
|
|
# push(@args,$s); |
2126
|
|
|
|
|
|
|
# } |
2127
|
|
|
|
|
|
|
|
2128
|
0
|
|
|
|
|
|
my $col=''; |
2129
|
0
|
|
|
|
|
|
while ($format=~s/%([-+0-9.]*[dsf])([a-zA-Z_][a-zA-Z0-9_]*)/%s/) |
2130
|
|
|
|
|
|
|
{ |
2131
|
0
|
|
|
|
|
|
my $s; |
2132
|
|
|
|
|
|
|
|
2133
|
0
|
|
|
|
|
|
my $item=$2; |
2134
|
0
|
|
0
|
|
|
|
my $len=$1//""; |
2135
|
|
|
|
|
|
|
# die "item is UNDEF" if ($item eq 'UNDEF'); |
2136
|
|
|
|
|
|
|
# die "len is UNDEF" if ($len eq 'UNDEF'); |
2137
|
|
|
|
|
|
|
# $len='' if ($len eq 'UNDEF'); |
2138
|
0
|
0
|
|
|
|
|
$len="%".$len if ($len); |
2139
|
0
|
0
|
0
|
|
|
|
if (defined $item and $item ne '' and exists $position->{$item} and defined $position->{$item}) |
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
2140
|
|
|
|
|
|
|
{ |
2141
|
0
|
|
|
|
|
|
$position->{$item}=~s/%//g; |
2142
|
|
|
|
|
|
|
#$position->{$item}='0' if ($position->{$item} eq 'UNDEF'); |
2143
|
0
|
|
|
|
|
|
$s=sprintf($len,$position->{$item}); |
2144
|
0
|
0
|
0
|
|
|
|
if ($item eq 'dbid' and exists $INC{'Term/Chrome.pm'} and $self->col) |
|
|
|
0
|
|
|
|
|
2145
|
|
|
|
|
|
|
{ |
2146
|
|
|
|
|
|
|
##my $v1=$position->{dbid}; |
2147
|
|
|
|
|
|
|
##$v1=~s/%//; |
2148
|
|
|
|
|
|
|
##$col=Green if (defined $up and $v1>$up); |
2149
|
|
|
|
|
|
|
##$col=Red if (defined $down and $v1<$down); |
2150
|
|
|
|
|
|
|
##$col=Green+Bold if (defined $up and $v1>$up*5); |
2151
|
|
|
|
|
|
|
##$col=Red+Bold if (defined $down and $v1<5*$down); |
2152
|
|
|
|
|
|
|
|
2153
|
|
|
|
|
|
|
# $col=&$colsub($position); |
2154
|
|
|
|
|
|
|
} |
2155
|
|
|
|
|
|
|
# $col=Yellow if (defined $up); |
2156
|
|
|
|
|
|
|
# $col=&$colsub($position); |
2157
|
|
|
|
|
|
|
} |
2158
|
|
|
|
|
|
|
elsif (defined $self->fetch($position,$item)) |
2159
|
|
|
|
|
|
|
{ |
2160
|
|
|
|
|
|
|
#$s=sprintf($len,$self->fetch($position,$2)//"UNDEF"); |
2161
|
0
|
|
0
|
|
|
|
$s=sprintf($len,$self->fetch($position,$item)//$self->uds); |
2162
|
0
|
0
|
0
|
|
|
|
if ($item eq 'dbid' and defined $INC{'Term/Chrome.pm'} and $self->col) |
|
|
|
0
|
|
|
|
|
2163
|
|
|
|
|
|
|
{ |
2164
|
|
|
|
|
|
|
#my $v1; |
2165
|
|
|
|
|
|
|
|
2166
|
|
|
|
|
|
|
#$v1=$self->fetch($position,'dbid'); |
2167
|
|
|
|
|
|
|
#$v1=~s/%//; |
2168
|
|
|
|
|
|
|
#$v1=100*$v1/$self->fetch($position,'bid'); |
2169
|
|
|
|
|
|
|
###$col=Green if (defined $up and $self->col and $self->fetch($position,'dbid')/$self->fetch($position,'bid')>$up/100); |
2170
|
|
|
|
|
|
|
###$col=Red if (defined $down and $self->col and $self->fetch($position,'dbid')/$self->fetch($position,'bid')<$down/100); |
2171
|
|
|
|
|
|
|
#$col=Green if (defined $up and $self->col and $v1>$up); |
2172
|
|
|
|
|
|
|
#$col=Red if (defined $down and $self->col and $v1<$down); |
2173
|
|
|
|
|
|
|
#$col=Green+Bold if (defined $up and $self->col and $v1>$up*5); |
2174
|
|
|
|
|
|
|
#$col=Red+Bold if (defined $down and $self->col and $v1<5*$down); |
2175
|
|
|
|
|
|
|
#$col=&$colsub($position); |
2176
|
|
|
|
|
|
|
} |
2177
|
|
|
|
|
|
|
#$col=&$colsub($position); |
2178
|
|
|
|
|
|
|
|
2179
|
|
|
|
|
|
|
} |
2180
|
|
|
|
|
|
|
else |
2181
|
|
|
|
|
|
|
{ |
2182
|
0
|
|
|
|
|
|
$len=~s/[df]/s/; |
2183
|
0
|
|
|
|
|
|
$len=~s/\.[0-9]+//; |
2184
|
|
|
|
|
|
|
#$s=sprintf($len,"UNDEF"); |
2185
|
0
|
|
|
|
|
|
$s=sprintf($len,$self->uds); |
2186
|
|
|
|
|
|
|
} |
2187
|
|
|
|
|
|
|
|
2188
|
0
|
|
|
|
|
|
$col=&$colsub($position); |
2189
|
0
|
|
|
|
|
|
$len=~s/[dsf]$//; |
2190
|
0
|
0
|
|
|
|
|
if ($len ne '') # len can be something like 0.2 |
2191
|
|
|
|
|
|
|
{ |
2192
|
0
|
|
|
|
|
|
$len=~s/%//; |
2193
|
0
|
0
|
|
|
|
|
$len=abs($len) if ($len ne ''); |
2194
|
0
|
0
|
0
|
|
|
|
$s=substr($s,0,$len) if ($len and $len<length($s) and $len>=1); |
|
|
|
0
|
|
|
|
|
2195
|
|
|
|
|
|
|
} |
2196
|
|
|
|
|
|
|
|
2197
|
0
|
|
|
|
|
|
$format=~s/%s/$s/; |
2198
|
|
|
|
|
|
|
} |
2199
|
|
|
|
|
|
|
|
2200
|
0
|
0
|
0
|
|
|
|
$col=&$colsub($position)//'' if ($self->col and defined $INC{'Term/Chrome.pm'}); |
|
|
|
0
|
|
|
|
|
2201
|
0
|
|
|
|
|
|
$format=~s/##/%/g; |
2202
|
0
|
|
|
|
|
|
$format=~s/£-/-£/g; |
2203
|
0
|
|
|
|
|
|
$format=~s/[\x82\x83\xc3]+//g; # so we get some strange characters like ÃÂ occuring in pairs. Not sure why. This removes them. |
2204
|
0
|
|
|
|
|
|
print $out $col, $format; |
2205
|
0
|
0
|
|
|
|
|
if (ref($col) ne '') |
2206
|
0
|
|
|
|
|
|
{ print $out Reset; |
2207
|
|
|
|
|
|
|
} |
2208
|
|
|
|
|
|
|
|
2209
|
|
|
|
|
|
|
} |
2210
|
|
|
|
|
|
|
|
2211
|
|
|
|
|
|
|
|
2212
|
|
|
|
|
|
|
|
2213
|
|
|
|
|
|
|
=head2 sortrange |
2214
|
|
|
|
|
|
|
|
2215
|
|
|
|
|
|
|
=head3 Parameters |
2216
|
|
|
|
|
|
|
|
2217
|
|
|
|
|
|
|
Ref to an array containing dates in printed ascii format. |
2218
|
|
|
|
|
|
|
|
2219
|
|
|
|
|
|
|
If there are no dates or an empty array, an empty string is returned. |
2220
|
|
|
|
|
|
|
|
2221
|
|
|
|
|
|
|
If there is one date, then that date is returned |
2222
|
|
|
|
|
|
|
|
2223
|
|
|
|
|
|
|
If there is more than one then the first and last after sorting is returned, with a dash between them. |
2224
|
|
|
|
|
|
|
|
2225
|
|
|
|
|
|
|
This is used in aggregation of positions and relates to creation dates with multiple positions |
2226
|
|
|
|
|
|
|
in the same security purchased at different times. |
2227
|
|
|
|
|
|
|
|
2228
|
|
|
|
|
|
|
=cut |
2229
|
|
|
|
|
|
|
|
2230
|
|
|
|
|
|
|
sub sortrange |
2231
|
|
|
|
|
|
|
{ |
2232
|
0
|
|
|
0
|
1
|
|
my ($self,$ar)=@_; |
2233
|
|
|
|
|
|
|
|
2234
|
0
|
|
|
|
|
|
my @dates=sort @$ar; |
2235
|
|
|
|
|
|
|
|
2236
|
0
|
0
|
|
|
|
|
return '' if (@dates==0); |
2237
|
0
|
0
|
|
|
|
|
return $dates[0] if (@dates==1); |
2238
|
0
|
|
|
|
|
|
return $dates[0] . "-".$dates[-1]; |
2239
|
|
|
|
|
|
|
} |
2240
|
|
|
|
|
|
|
|
2241
|
|
|
|
|
|
|
|
2242
|
|
|
|
|
|
|
|
2243
|
|
|
|
|
|
|
=head2 Red Blue Bold Reset Underline Green color |
2244
|
|
|
|
|
|
|
|
2245
|
|
|
|
|
|
|
=head3 Description |
2246
|
|
|
|
|
|
|
|
2247
|
|
|
|
|
|
|
The above parameterless functions are provided if Term::Chrome is not available. They are "do nothing" subs provided to satisfy references only. |
2248
|
|
|
|
|
|
|
|
2249
|
|
|
|
|
|
|
=head1 DEPENDENCIES |
2250
|
|
|
|
|
|
|
|
2251
|
|
|
|
|
|
|
Moose |
2252
|
|
|
|
|
|
|
Term::Chrom if available. |
2253
|
|
|
|
|
|
|
|
2254
|
|
|
|
|
|
|
=head1 UTILITIES |
2255
|
|
|
|
|
|
|
|
2256
|
|
|
|
|
|
|
A more complete position lister is given as igdisp.pl |
2257
|
|
|
|
|
|
|
|
2258
|
|
|
|
|
|
|
=head1 AUTHOR |
2259
|
|
|
|
|
|
|
|
2260
|
|
|
|
|
|
|
Mark Winder, C<< <markwin at cpan.org> >> |
2261
|
|
|
|
|
|
|
|
2262
|
|
|
|
|
|
|
=head1 BUGS |
2263
|
|
|
|
|
|
|
|
2264
|
|
|
|
|
|
|
Please report any bugs or feature requests to C<bug-finance-ig at rt.cpan.org>, or through |
2265
|
|
|
|
|
|
|
the web interface at L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Finance-IG>. I will be notified, and then you'll |
2266
|
|
|
|
|
|
|
automatically be notified of progress on your bug as I make changes. |
2267
|
|
|
|
|
|
|
|
2268
|
|
|
|
|
|
|
|
2269
|
|
|
|
|
|
|
|
2270
|
|
|
|
|
|
|
|
2271
|
|
|
|
|
|
|
=head1 SUPPORT |
2272
|
|
|
|
|
|
|
|
2273
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
2274
|
|
|
|
|
|
|
|
2275
|
|
|
|
|
|
|
perldoc Finance::IG |
2276
|
|
|
|
|
|
|
|
2277
|
|
|
|
|
|
|
|
2278
|
|
|
|
|
|
|
You can also look for information at: |
2279
|
|
|
|
|
|
|
|
2280
|
|
|
|
|
|
|
=over 4 |
2281
|
|
|
|
|
|
|
|
2282
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker (report bugs here) |
2283
|
|
|
|
|
|
|
|
2284
|
|
|
|
|
|
|
L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Finance-IG> |
2285
|
|
|
|
|
|
|
|
2286
|
|
|
|
|
|
|
=item * CPAN Ratings |
2287
|
|
|
|
|
|
|
|
2288
|
|
|
|
|
|
|
L<https://cpanratings.perl.org/d/Finance-IG> |
2289
|
|
|
|
|
|
|
|
2290
|
|
|
|
|
|
|
=item * Search CPAN |
2291
|
|
|
|
|
|
|
|
2292
|
|
|
|
|
|
|
L<https://metacpan.org/release/Finance-IG> |
2293
|
|
|
|
|
|
|
|
2294
|
|
|
|
|
|
|
=back |
2295
|
|
|
|
|
|
|
|
2296
|
|
|
|
|
|
|
|
2297
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
2298
|
|
|
|
|
|
|
|
2299
|
|
|
|
|
|
|
=head1 FURTHER READING |
2300
|
|
|
|
|
|
|
|
2301
|
|
|
|
|
|
|
IG REST API Reference https://labs.ig.com/rest-trading-api-reference |
2302
|
|
|
|
|
|
|
|
2303
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
2304
|
|
|
|
|
|
|
|
2305
|
|
|
|
|
|
|
This software is Copyright (c) 2020 by Mark Winder. |
2306
|
|
|
|
|
|
|
|
2307
|
|
|
|
|
|
|
This is free software, licensed under: |
2308
|
|
|
|
|
|
|
|
2309
|
|
|
|
|
|
|
The Artistic License 2.0 (GPL Compatible) |
2310
|
|
|
|
|
|
|
|
2311
|
|
|
|
|
|
|
|
2312
|
|
|
|
|
|
|
=cut |
2313
|
|
|
|
|
|
|
|
2314
|
|
|
|
|
|
|
1; # End of Finance::IG |