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