kraken-grid 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +94 -0
  3. package/index.js +795 -0
  4. package/package.json +30 -0
package/index.js ADDED
@@ -0,0 +1,795 @@
1
+ // COmment Line to see git diff...
2
+ const fs = require('fs');
3
+
4
+ if(!fs.existsSync('../keys.js')) {
5
+ console.log("Paste your key and secret between the single quotes and save this"
6
+ +" to the parent folder as keys.js:\nexports.key='';\nexports.secret='';");
7
+ process.exit(1);
8
+ }
9
+ const my = require('../keys.js');
10
+ const key = my.key; // API Key
11
+ const secret = my.secret; // API Private Key
12
+ const KrakenClient = require('kraken-api');
13
+ const kraken = new KrakenClient(key, secret);
14
+ function sleep(ms) {
15
+ return new Promise(resolve => setTimeout(resolve, ms));
16
+ }
17
+
18
+ async function kapi(arg,sd=5)
19
+ {
20
+ // await sleep(100);
21
+ let ret;
22
+ try { // Because failure is not an option here, sometimes.
23
+ if(Array.isArray(arg)) {
24
+ ret = await kraken.api(...arg);
25
+ } else {
26
+ ret = await kraken.api(arg);
27
+ }
28
+ } catch(err) {
29
+ if((!/AddOrder/.test(arg[0])&&/ETIMEDOUT|EAI_AGAIN/.test(err.code))
30
+ || /nonce/.test(err.message)
31
+ || /Response code 50/.test(err.message)) {
32
+ console.log(22,err.message+", so trying again in "+sd+"s...("+(new Date)+"):");
33
+ if(Array.isArray(arg)) {
34
+ delete arg[1].nonce;
35
+ console.log(...arg);
36
+ } else {
37
+ console.log(arg);
38
+ }
39
+ await sleep(sd*1000);
40
+ ret = await kapi(arg,sd>300?sd:2*sd);
41
+ } else if( /Unknown order/.test(err.message) && /CancelOrder/.test(arg[0])) {
42
+ console.log("Ignoring: ", ...arg);
43
+ ret = { result: { descr: "Ignored" }};
44
+ } else {
45
+ catcher(26,err);
46
+ ret = { result: { descr: err }};
47
+ }
48
+ }
49
+ if(verbose) console.log(ret);
50
+ return ret;
51
+ }
52
+
53
+ async function order(buysell, xmrbtc, price, amt, lev='none', uref=0, closeO=null) {
54
+ if(closeO) closeO.price = Number(closeO.price);
55
+ price = Number(price);
56
+ amt = Number(amt);
57
+ ret = '';
58
+ if( closeO && closeO.price == price ) closeO = null;
59
+ console.log(27,(safeMode ? '(Safe mode, so NOT) ' : '')
60
+ +buysell+"ing "+amt+" "+xmrbtc+" at "+price+" with leverage "+lev
61
+ +(!closeO || closeO.price == price ? "" : " to close at "+closeO.price));
62
+ // let ordered;
63
+ if(!safeMode) {
64
+ let response = await kapi(['AddOrder',
65
+ { pair: xmrbtc+'USD', // Just call it 'pair'! #USD Refactor
66
+ userref: uref,
67
+ type: buysell,
68
+ ordertype: 'limit',
69
+ price: price,
70
+ volume: amt,
71
+ leverage: lev,
72
+ close: closeO
73
+ }]);
74
+ console.log(40,response ? ((d = response.result)
75
+ ? (ret = d.txid,d.descr) : 'No result.descr from kapi') : "No kapi response.");
76
+ console.log(42,"Cooling it for a second...");
77
+ await sleep(1000);
78
+ }
79
+ return ret;
80
+ }
81
+
82
+ function gpToStr(gp) { return gp.userref+':'+gp.buy+'-'+gp.sell+' '+gp.bought+'/'+gp.sold; }
83
+
84
+ async function listOpens(portfolio = null, isFresh=false) {
85
+ let response = await kapi('OpenOrders'),
86
+ opens = response.result.open;
87
+ let opensA = [],
88
+ comps = [],
89
+ gPrices = [],
90
+ bSides = [],
91
+ ci,oo,od,rv,n=0,ur,op,cp,gpi,gp,ct,bs;
92
+ // Index for comps, n?, Closing Price, index to grid prices,
93
+ // and bs is "Both sides", holding an array of objects
94
+ // holding userref, and two bookeans, buy and sell.
95
+ if(portfolio&&portfolio['G']) gPrices = portfolio['G'];
96
+ if(gPrices.length == 0) {
97
+ let response = await kapi('ClosedOrders'),
98
+ r2 = await kapi(['ClosedOrders',{ofs:50}]),
99
+ r3 = await kapi(['ClosedOrders',{ofs:100}]),
100
+ closed = {...response.result.closed, ...r2.result.closed, ...r3.result.closed };
101
+ ts150 = closed[Object.keys(closed).pop()].closetm;
102
+ for(o in closed) {
103
+ let oo = closed[o],
104
+ od = oo.descr,
105
+ op = od.price,
106
+ rv = oo.vol-oo.vol_exec,
107
+ ur = oo.userref;
108
+ gp = gPrices.find(x => x.userref==ur);
109
+ if(ur>0) {
110
+ if(!gp) {
111
+ gp = {userref:ur,buy:'?',sell:'?', bought: 0, sold: 0};
112
+ gp[od.type] = op;
113
+ gp[(od.type=='buy') ? 'bought' : 'sold'] = Number(rv);
114
+ gPrices.push(gp);
115
+ if(verbose) console.log(gp.userref,'('+od.type+')','buy:',gp.buy,'sell:',gp.sell);
116
+ } else {
117
+ gp[(od.type=='buy') ? 'bought' : 'sold'] += Number(rv);
118
+ gp[od.type] = op;
119
+ }
120
+ }
121
+ }
122
+ }
123
+
124
+ // Save the old order array so we can see the diff
125
+ // -----------------------------------------------
126
+ let oldRefs = [];
127
+ if(portfolio && portfolio['O']) {
128
+ portfolio['O'].forEach((x) => { oldRefs.push(x[0]); });
129
+ }
130
+ for( o in opens ) {
131
+ oo = opens[o];
132
+ od = oo.descr;
133
+ op = od.price;
134
+ rv = oo.vol-oo.vol_exec;
135
+ ur = oo.userref;
136
+
137
+ if(ur > 0) {
138
+ // BothSides record for userref
139
+ // ----------------------------
140
+ bs = bSides.find(b => b.userref==ur);
141
+ if(!bs) {
142
+ bs = {userref:ur,buy:false,sell:false,trades:0};
143
+ bSides.push(bs);
144
+ }
145
+ bs[od.type]=true;
146
+ bs.trades++;
147
+
148
+ // BothSides record for grid extension
149
+ // -----------------------------------
150
+ bs = bSides.find(b => b.userref==od.pair);
151
+ if(!bs) {
152
+ bs = {
153
+ userref:od.pair,
154
+ price: op,
155
+ buy: od.type=='buy',
156
+ sell: od.type=='sell'
157
+ };
158
+ bSides.push(bs);
159
+ } else if(!bs[od.type]) {
160
+ bs[od.type] = true;
161
+ } else if(bs.buy != bs.sell) {
162
+ // Set bs.price to the lowest if there are only sells (bs.sell is true),
163
+ // or the highest if there are only buys (bs.buy is true).
164
+ // If both, it won't matter.
165
+ // --------------------------------------------------
166
+ if((bs.buy && Number(bs.price) < Number(op))
167
+ || (bs.sell && Number(bs.price) > Number(op))) bs.price = op;
168
+ }
169
+ }
170
+
171
+ // Record open trades
172
+ // ------------------
173
+ opensA.push([o,oo]);
174
+ ct = 'buy'==od.type?'sell':'buy';
175
+ cp = 0;
176
+ // Record the opening price for use in the closing
177
+ // order of the closing order into which we combine.
178
+ // -------------------------------------------------
179
+ if(od.close && ur>0) { // Externally added orders have userref=0
180
+ cp = /[0-9.]+$/.exec(od.close)[0];
181
+ gp = gPrices.find(gprice => gprice.userref==ur);
182
+ if(!gp) {
183
+ gp = {userref:ur,buy:'?',sell:'?', bought: 0, sold: 0};
184
+ gPrices.push(gp);
185
+ if(verbose) console.log(gp.userref,'('+od.type+')','buy:',gp.buy,'sell:',gp.sell);
186
+ }
187
+ gp[od.type] = op;
188
+ gp[ct] = cp;
189
+ }
190
+ gp = gPrices.find(gprice => gprice.userref==ur&&ur>0);
191
+ cp = gp ? gp[ct] : '?';
192
+ if(++n == 0) {
193
+ console.log(125, opens[o]);
194
+ }
195
+
196
+ ci = od.pair+od.price+od.type; // pair picks up externals
197
+ if(verbose) console.log("comps index: "+ci);
198
+ if(!comps[ci]) {
199
+ comps[ci]={
200
+ total: rv,
201
+ volume: Number(oo.vol),
202
+ type: od.type,
203
+ sym: /USD/.test(od.pair) ? /^([A-Z]+)USD/.exec(od.pair)[1] : od.pair,
204
+ // Just call it 'pair'! #USD Refactor
205
+ ctype: ct,
206
+ lev: od.leverage,
207
+ ids: [o],
208
+ userref: ur,
209
+ open: cp,
210
+ price: od.price,
211
+ hasClose: Boolean(od.close)
212
+ };
213
+ } else {
214
+ comps[ci].total+=rv; // Volume for combined order.
215
+ comps[ci].ids.push(o);
216
+ comps[ci].volume += Number(oo.vol); // Volume for extended order.
217
+ // If any of them are missing a close, combine them all
218
+ // ----------------------------------------------------
219
+ comps[ci].hasClose &&= Boolean(od.close);
220
+ // Fix a comp created from an external order.
221
+ // ------------------------------------------
222
+ if(0 == comps[ci].userref) comps[ci].userref = ur;
223
+ }
224
+ if(!Boolean(od.close)) {
225
+ console.log(154,od.order+" ("+ur+") had no close.");
226
+ cli.apl++;
227
+ }
228
+ let orid;
229
+ if((orid = oldRefs.indexOf(o)) > -1) {
230
+ oldRefs.splice(orid, 1);
231
+ } else {
232
+ console.log(159, "New: ",o,opensA.length, od.order, oo.userref, cp);
233
+ if(verbose) console.log(160,oo);
234
+ }
235
+
236
+ if(portfolio && isFresh && od.leverage == "none") {
237
+ if(od.type == "buy") {
238
+ if(/USD$/.test(od.pair)) { // Deplete our cash
239
+ portfolio['ZUSD'][2] -= od.price*opens[o].vol;
240
+ } else if(/XBT$/.test(od.pair)) { // Deplete our BTC
241
+ portfolio['XBT'][0] -= od.price*opens[o].vol;
242
+ }
243
+ } else {
244
+ // Deplete available crypto
245
+ // ------------------------
246
+ portfolio[od.pair.slice(0,-3)][0] -= opens[o].vol;
247
+ }
248
+ }
249
+ }
250
+ if(oldRefs.length > 0) {
251
+ console.log("Gone: "+oldRefs);
252
+ }
253
+
254
+ let nexes = 0, // Orders not requiring extension
255
+ dontask = false;
256
+ for( comp in comps ) if(/USD/.test(comp)) { // non-USD pairs break regex below... #USD Refactor
257
+ let c = comps[comp],
258
+ gp = gPrices.find(gprice => gprice.userref==c.userref);
259
+ bs = bSides.find(b => b.userref==c.userref);
260
+ if(!gp) {
261
+ gp = {userref:c.userref,buy:'?',sell:'?',bought:0,sold:0};
262
+ gPrices.push(gp);
263
+ console.log(gp.userref,'('+comp.slice(-4)+')','buy:',gp.buy,'sell:',gp.sell);
264
+ }
265
+ gp[c.ctype] = c.open;
266
+ gp[c.type] = c.price;
267
+ [,sym,price] = /([A-Z]+)USD([0-9.]+)/.exec(comp); //#USD Refactor
268
+ if(verbose) console.log("Checking: " + c.type + ' '
269
+ + sym + ' ' + price + ' ' + Math.round(c.total*10000)/10000
270
+ + (c.open ? ' to '+c.ctype+'-close @'+c.open : '') +' (' + c.userref + "):");
271
+ if(!isNaN(c.open)) {
272
+ if(!c.hasClose) { // If any doesn't have a close, combine them and add one.
273
+ console.log(Object.values(c.ids));
274
+ for(const id of c.ids) { await kill(id,null); }
275
+ await order(c.type,sym,price, Math.round(c.total*10000)/10000,
276
+ c.lev,c.userref,{ordertype:'limit',price:c.open});
277
+ c.hasClose = true;
278
+ // Store the trades in gp
279
+ // ----------------------
280
+ let traded = c.type=='buy' ? 'sold' : 'bought';
281
+ gp[traded]+=c.total;
282
+ }
283
+ // Do we need to extend the grid?
284
+ // If we don't have a buy and a sell, then yes.
285
+ // --------------------------------------------
286
+ bs = bSides.find(b => b.userref==c.sym+'USD'); // Just call it 'pair' #USD Refactor
287
+ if(bs.buy && bs.sell) {
288
+ // console.log("Still between buy and sell.");
289
+ ++nexes;
290
+ } else if(bs.price==c.price) { // The lowest sell or highest buy
291
+ // Calculate price for missing side
292
+ // --------------------------------
293
+ let dp = gp.buy.indexOf('.'),
294
+ decimals=Math.pow(10,dp > 0
295
+ ? gp.buy.length - dp - 1
296
+ : 0),
297
+ ngp,sp,bp;
298
+ if(bs.buy) { // Missing the sell
299
+ do {
300
+ sp = Math.round(decimals*gp.sell*gp.sell/gp.buy)/decimals;
301
+ c.userref -= 10000000;
302
+ // We may already have this grid price but the order
303
+ // was deleted, so search for it first.
304
+ ngp = gPrices.find(n => n.userref==c.userref);
305
+ if(!ngp) {
306
+ ngp = {userref:c.userref,
307
+ buy:gp.sell,
308
+ sell:String(sp),
309
+ bought: 0, sold: 0};
310
+ gPrices.push(ngp);
311
+ console.log(ngp.userref,'(sell)',
312
+ 'buy:',ngp.buy,'sell:',ngp.sell);
313
+ console.log(249,"sell "+c.sym+' '+sp+' '+c.volume
314
+ +" to close at "+gp.sell);
315
+ }
316
+ await order('sell',c.sym,sp,c.volume,
317
+ getLev(portfolio,'sell',sp,c.volume,c.sym,false),c.userref,
318
+ {ordertype:'limit',price:gp.sell});
319
+ gp = ngp;
320
+ } while(sp <= 1*portfolio[c.sym][1]);
321
+ } else {
322
+ do {
323
+ bp = Math.round(decimals*gp.buy*gp.buy/gp.sell)/decimals;
324
+ c.userref -= 1000000;
325
+ // We may already have this grid price but the order
326
+ // was deleted, so search for it first.
327
+ ngp = gPrices.find(n => n.userref==c.userref);
328
+ if(!ngp) {
329
+ ngp = {userref:c.userref,
330
+ buy:String(bp),
331
+ sell:gp.buy,
332
+ bought: 0, sold: 0};
333
+ gPrices.push(ngp);
334
+ console.log(ngp.userref,'( buy)',
335
+ 'buy:',ngp.buy,'sell:',ngp.sell);
336
+ console.log(264,"buy "+c.sym+" "+bp+' '+c.volume
337
+ +" to close at "+gp.buy);
338
+ }
339
+ await order('buy',c.sym,bp,c.volume,
340
+ getLev(portfolio,'buy',bp,c.volume,c.sym,false),c.userref,
341
+ {ordertype:'limit',price:gp.buy});
342
+ gp = ngp;
343
+ } while(bp >= 1*portfolio[c.sym][1])
344
+ }
345
+ }
346
+ }
347
+ }
348
+ // console.log(gPrices);
349
+ console.log(nexes,"orders didn't require extension.");
350
+ // console.log(comps);
351
+ if(portfolio){
352
+ portfolio['O'] = opensA;
353
+ portfolio['G'] = gPrices;
354
+ }
355
+ return opensA;
356
+ }
357
+
358
+ // getLev is NOT idempotent: It depletes availability.
359
+ // ---------------------------------------------------
360
+ function getLev(portfolio,buysell,price,amt,xmrbtc,posP) {
361
+ let lev = 'none';
362
+ if(buysell == 'buy') {
363
+ if(1*price > 1*portfolio[xmrbtc][1] && posP)
364
+ return "Buying "+xmrbtc+" @ "+price+" isn't a limit order.";
365
+ if(price*amt > 1*portfolio['ZUSD'][2]) { // #USD Refactor - This doesn't support leverage
366
+ lev = '2'; // on non-USD pairs.
367
+ } else {
368
+ portfolio['ZUSD'][2] -= price*amt;
369
+ }
370
+ } else {
371
+ if(price*1 < 1*portfolio[xmrbtc][1] && posP) return "Selling "+xmrbtc+" @ "+price+" isn't a limit order.";
372
+ //console.log("We have "+portfolio[xmrbtc][2]+" "+xmrbtc);
373
+ if(amt*1 > 1*portfolio[xmrbtc][2]) {
374
+ lev = '2';
375
+ } else {
376
+ portfolio[xmrbtc][2] -= amt;
377
+ }
378
+ //console.log("Now we have "+portfolio[xmrbtc][2]+" "+xmrbtc);
379
+ }
380
+ if(verbose) console.log("Leverage will be "+lev);
381
+ return lev;
382
+ }
383
+
384
+ async function kill(o,oa) {
385
+ if(0 == o) {
386
+ const killAll = prompt("Cancel ALL orders? [y/N]");
387
+ if(/^y/i.test(killAll)) {
388
+ let killed = await kapi('CancelAll');
389
+ console.log(314,killed);
390
+ } else { console.log("Maybe be more careful."); }
391
+ return;
392
+ } else if(safeMode) {
393
+ console.log("In Safemode, so NOT killing "+o);
394
+ return;
395
+ } else if('string'==typeof(o) && o.match(/-/)) {
396
+ console.log("Killing "+o+"...");
397
+ let killed = await kapi(['CancelOrder', {txid: o}]);
398
+ console.log(320,killed);
399
+ } else if(o < 100000) {
400
+ let idxo = oa[o-1];
401
+ console.log("Killing "+idxo[0]+"(described as "+idxo[1].descr.order+"...");
402
+ let killed = await kapi(['CancelOrder', {txid: idxo[0]}]);
403
+ console.log(325,killed);
404
+ idxo[0] = 'Killed: '+ idxo[0];
405
+ idxo[1].descr.order = 'Killed: '+idxo[1].descr.order;
406
+ } else {
407
+ console.log("Killing userref "+o+"...");
408
+ let killed = await kapi(['CancelOrder', {txid: o}]);
409
+ console.log(329,killed);
410
+ }
411
+ // console.log(331,"Waiting a second.");
412
+ // await sleep(1000);
413
+ }
414
+
415
+ async function handleArgs(portfolio, args, uref = 0) {
416
+ if(/buy|sell/.test(args[0])) {
417
+ [buysell,xmrbtc,price,amt,posP] = args;
418
+ if(!/XMR|XBT|ETH|LTC|DASH|EOS|BCH|USDT/.test(xmrbtc)) return xmrbtc+" is not yet supported.";
419
+ let total=price*amt;
420
+ if(total > 100000) return total+" is too much for code to "+buysell;
421
+
422
+ // console.log(buysell+"ing "+amt+xmrbtc+" for "+price+".");
423
+
424
+ // Do we need leverage?
425
+ // --------------------
426
+ let lev = getLev(portfolio,buysell,price,amt,xmrbtc,posP);
427
+ let cPrice = portfolio['G'][uref] ? portfolio['G'][uref][buysell=='buy'?'sell':'buy'] : 0;
428
+ // Without a record of a closing price, use the last one we found.
429
+ // ---------------------------------------------------------------
430
+ if(!cPrice) cPrice = portfolio[xmrbtc][1];
431
+ let closeO = posP ? { ordertype: 'limit', price: cPrice } : null;
432
+ let ret = await order(buysell,xmrbtc,price,amt,lev,uref,closeO);
433
+ console.log("New order: "+ret);
434
+ return;
435
+ } else if(args[0] == 'set') {
436
+ set(portfolio, args[1], args[2], args[3]);
437
+ } else if(args[0] == 'reset') {
438
+ portfolio['G'] = [];
439
+ await listOpens(portfolio);
440
+ } else if(args[0] == 'delev') {
441
+ await deleverage(portfolio['O'],args[1]-1);
442
+ } else if(args[0] == 'addlev') {
443
+ await deleverage(portfolio['O'],args[1]-1,true);
444
+ } else if(args[0] == 'refnum') {
445
+ await refnum(portfolio['O'],args[1]-1,args[2]);
446
+ } else if(args[0] == 'list') {
447
+ let sortedA = [], orders = portfolio['O'];
448
+ if(args[1] == 'C') {
449
+ let ur = args[2] ? args.pop() : false,
450
+ response = ur
451
+ ? await kapi(['ClosedOrders',{userref:ur}])
452
+ : await kapi('ClosedOrders');
453
+ orders = [];
454
+ for( o in response.result.closed) {
455
+ let oo = response.result.closed[o];
456
+ if(oo.status=='closed') orders.push([o,response.result.closed[o]]);
457
+ }
458
+ args.pop();
459
+ }
460
+ orders.forEach((x,i) => {
461
+ let ldo = x[1].descr.order;
462
+ if(args.length==1 || RegExp(args[1]).test(ldo))
463
+ console.log(i+1,ldo,x[1].userref,x[1].status=='closed'
464
+ ? new Date(1000*x[1].closetm)
465
+ : x[1].descr.close);
466
+ else if(x[1][args[1]]) sortedA[i+1]=x;
467
+ else if(x[1].descr[args[1]]) sortedA[i+1]=x;
468
+ });
469
+ if(sortedA.length > 0) {
470
+ sortedA.sort((a,b) => {
471
+ if(a[1].descr[args[1]]) {
472
+ a = a[1].descr[args[1]];
473
+ b = b[1].descr[args[1]];
474
+ } else {
475
+ a = a[1][args[1]];
476
+ b = b[1][args[1]];
477
+ }
478
+ return isNaN(a)
479
+ ? a.localeCompare(b)
480
+ : a - b;
481
+ });
482
+ sortedA.forEach((x,i) => {
483
+ let ldo = x[1].descr.order;
484
+ console.log(i+1, x[1].descr[args[1]]
485
+ ? x[1].descr[args[1]] : x[1][args[1]],
486
+ ldo,x[1].userref,x[1].descr.close);
487
+ });
488
+ };
489
+ } else if(args[0] == 'test') {
490
+ // Put some test code here if you want
491
+ // -----------------------------------
492
+ } else if(/^(y|Y)/.test(prompt("Try "+args[0]+" raw?"))) {
493
+ let raw = await kapi(args);
494
+ console.log(392,raw);
495
+ } else {
496
+ return args[0]+" is not yet implemented.";
497
+ }
498
+ }
499
+
500
+ async function refnum(opensA,oid,newRef) {
501
+ let o, oRef;
502
+ if(!opensA[oid]) {
503
+ console.log("Order "+oid+" not found.");
504
+ return;
505
+ } else {
506
+ [oRef,o] = opensA[oid];
507
+ }
508
+ if(!/USD$/.test(o.descr.pair)) {
509
+ console.log("Userref update to non-USD pairs is not yet supported.");
510
+ return;
511
+ }
512
+ if(o.userref == 0) {
513
+ let bs=o.descr.type,
514
+ sym=/^([A-Z]+)USD/.exec(o.descr.pair)[1],
515
+ p=o.descr.price,
516
+ amt=Math.round(10000*(Number(o.vol) - Number(o.vol_exec)))/10000,
517
+ lev=o.descr.leverage[0]=='n'?"none":'2';
518
+ console.log("Attempting "+bs+' '+sym+' '+p+' '+amt+' '+lev+' '+newRef+"...");
519
+ await kill(oid+1, opensA);
520
+ await order(bs,sym,p,amt,lev,newRef);
521
+ } else {
522
+ console.log(oRef+" already has userref "+o.userref);
523
+ }
524
+ }
525
+
526
+ async function deleverage(opensA,oid,undo=false) {
527
+ let o, oRef;
528
+ if(!opensA[oid]) {
529
+ console.log("Order "+oid+" not found.");
530
+ return;
531
+ } else {
532
+ [oRef,o] = opensA[oid];
533
+ }
534
+ if(!/USD$/.test(o.descr.pair)) {
535
+ console.log("Creating/deleveraging non-USD pairs is not yet supported.");
536
+ return;
537
+ }
538
+ if(undo ^ (o.descr.leverage == 'none')) {
539
+ console.log(oRef+" is "+ (undo ? "already leveraged" : "not leveraged."));
540
+ return;
541
+ }
542
+ if(!o.descr.close) {
543
+ await order(o.descr.type,/^([A-Z]+)USD/.exec(o.descr.pair)[1],
544
+ o.descr.price,Math.round(10000*(Number(o.vol) - Number(o.vol_exec)))/10000,
545
+ (undo ? '2' : 'none'),o.userref);
546
+ } else {
547
+ await order(o.descr.type,/^([A-Z]+)USD/.exec(o.descr.pair)[1],
548
+ o.descr.price,Math.round(10000*(Number(o.vol) - Number(o.vol_exec)))/10000,
549
+ (undo ? '2' : 'none'),o.userref,
550
+ { ordertype: 'limit', price: /[0-9.]+$/.exec(o.descr.close)[0] });
551
+ }
552
+ await kill(oid+1, opensA);
553
+ }
554
+
555
+
556
+ function set(p,ur,type,price) {
557
+ if(ur) {
558
+ let gp = p['G'].find(g => g.userref==ur);
559
+ if(!gp) {
560
+ gp = {userref:Number(ur),buy:'?',sell:'?'};
561
+ p['G'].push(gp);
562
+ }
563
+ console.log(405,gp);
564
+ gp[type] = price;
565
+ }
566
+ p['G'].sort((a,b) => a.userref-b.userref);
567
+ let profits = 0;
568
+ p['G'].forEach(x => {
569
+ let f = toDec(((x.sell-x.buy)*Math.min(x.bought,x.sold)),2);
570
+ console.log(x.userref+': '+x.buy+'-'+x.sell
571
+ + ((x.bought+x.sold)>0
572
+ ? (", bought "+toDec(x.bought,2)+" and sold "+toDec(x.sold,2)+' for ' + f)
573
+ : '' ));
574
+ if(!isNaN(f)) profits += f;
575
+ });
576
+ console.log("That's "+toDec(profits,2)+" since "+new Date(ts150*1000));
577
+ }
578
+
579
+ function toDec(n,places) {
580
+ let f = 10**places;
581
+ return Math.round(n*f)/f;
582
+ }
583
+ async function report(portfolio,showBalance=true) {
584
+ let dataPromise = [
585
+ 'Balance',
586
+ ['Ticker',{ pair : 'XBTUSD,XMRUSD,BCHUSD,DASHUSD,EOSUSD,ETHUSD,LTCUSD,USDTUSD' }],
587
+ 'TradeBalance'
588
+ ];
589
+ try {
590
+ dataPromise[0] = await kapi(dataPromise[0]);
591
+ dataPromise[1] = await kapi(dataPromise[1]);
592
+ dataPromise[2] = await kapi(dataPromise[2]);
593
+ } catch(err) {
594
+ catcher(411,err);
595
+ console.log(423,"Waiting a minute...");
596
+ await sleep(60000);
597
+ return;
598
+ }
599
+ let [bal,tik,trb] = dataPromise;
600
+ let mar = await marginReport(false);
601
+ portfolio['M'] = mar;
602
+ delete bal.result.KFEE;
603
+ delete bal.result.BSV;
604
+ delete bal.result.ADA;
605
+
606
+ let price;
607
+ for( const p in bal.result) {
608
+ let ts = p+'USD',
609
+ tsz = p+'ZUSD',
610
+ sym = /^X/.test(p) ? p.substr(1) : p,
611
+ amt = toDec(bal.result[p],4);
612
+ if(ts in tik.result) price = tik.result[ts].c[0];
613
+ else if(tsz in tik.result) price = tik.result[tsz].c[0];
614
+ price = toDec(price,(sym=='EOS'?4:2));
615
+ portfolio[sym]=[amt,price,amt]; // holdings w/reserves, price, holdings w/o reserves
616
+ if(mar[sym]) portfolio[sym][0] = toDec(portfolio[sym][0]+mar[sym].open,4);
617
+ if(showBalance) console.log(p+"\t"+w(portfolio[sym][0],16)+price);
618
+ }
619
+ if(showBalance) {
620
+ console.log("Cost\t"+trb.result['c']);
621
+ console.log("Value\t"+trb.result['v']);
622
+ console.log("P & L\t"+trb.result['n']);
623
+ for( const s in mar ) {
624
+ if(portfolio[s]) {
625
+ console.log(s+": "+portfolio[s][2]+" outright, and "+mar[s].open+" on margin.");
626
+ } else {
627
+ console.log("Did not find "+s+" in portfolio!");
628
+ }
629
+ }
630
+ }
631
+ //console.log(portfolio);
632
+ console.log(new Date);
633
+ await listOpens(portfolio,true);
634
+ process.stdout.write("\033[A".repeat(cli.apl));
635
+ cli.apl = 2;
636
+ }
637
+
638
+ function w(n,x) { let s = n.toString(); return s+' '.repeat(x-s.length); }
639
+
640
+ async function marginReport(show = true) {
641
+ let positions = await kapi(['OpenPositions',{consolidation:"market"}]);
642
+ let brief = [];
643
+ if(Object.keys(positions.result).length) {
644
+ positions.result.forEach( (pos) => {
645
+ let vol = (1*pos.vol-1*pos.vol_closed)*(pos.type=='sell' ? -1 : 1),
646
+ sym = /^X/.test(pos.pair) ? pos.pair.slice(1,4) : pos.pair.slice(0,-3);
647
+ vol = toDec(vol,8);
648
+ brief[sym] = {
649
+ open: vol,
650
+ sym: pos.pair,
651
+ margin: pos.margin };
652
+ });
653
+ if(show) console.log(475,brief);
654
+ }
655
+ return brief;
656
+ }
657
+
658
+ const prompt = require('prompt-sync')({sigint: true});
659
+ let stopNow = false,
660
+ portfolio = [],
661
+ histi = Math.floor(Date.now() / 1000),
662
+ ts150 = 0,
663
+ delay = 60,
664
+ auto = 0,
665
+ verbose = false,
666
+ risky = false,
667
+ cmdList = [],
668
+ safeMode = true,
669
+ cli = {'apl': 0};
670
+ async function runOnce(cmdList) {
671
+ //while(!stopNow) {
672
+ // if(cmd==null) cmd = prompt((auto>0 ? '('+delay+' min.)' : 'manual')+'>');
673
+ let cmds = cmdList.map((x) => { return x.trim(); }),
674
+ cdx = 0;
675
+
676
+ console.log("Got "+(cmds.length)+" commands...");
677
+ while(cdx < cmds.length) {
678
+ let args = cmds[cdx++].split(' ').map((x) => { return x.trim(); });
679
+ console.log("...("+cdx+")> "+args.join(' '));
680
+ try {
681
+ if(args[0] == 'kill') await kill(args[1],portfolio['O']);
682
+ else if(args[0] == "ws") {
683
+ if(kwsCheck) console.log("Kraken WebSocket heartbeat at "+kwsCheck);
684
+ if(!kwsCheck || (new Date()).valueOf() > 10000+kwsCheck.valueOf()) {
685
+ openSocket();
686
+ }
687
+ } else if(args[0] == "report" || args[0] == "") await report(portfolio);
688
+ else if(/^(manual)$/.test(args[0])) {
689
+ clearInterval(auto);
690
+ auto = 0;
691
+ } else if(args[0] == "auto") {
692
+ clearInterval(auto);
693
+ if(args[1]&&!isNaN(args[1])) delay = args[1];
694
+ let counter = delay;
695
+ auto = setInterval(() => {
696
+ if(0 == --counter) {
697
+ report(portfolio,false);
698
+ counter = delay;
699
+ }
700
+ },1000);
701
+ await report(portfolio);
702
+ } else if(args[0] == "risky") {
703
+ risky = !risky;
704
+ console.log("Risky Mode is "+(risky
705
+ ? 'on - Experimental additions will be tried' : 'off'));
706
+ } else if(args[0] == "safe") {
707
+ safeMode = !safeMode;
708
+ console.log("Safe Mode is "+(safeMode
709
+ ? 'on - Orders will be displayed butnot placed' : 'off'));
710
+ } else if(args[0] == "verbose") {
711
+ verbose = !verbose;
712
+ console.log("Verbose is "+(verbose ? 'on' : 'off'));
713
+ } else if(args[0] == 'margin') {
714
+ await marginReport();
715
+ } else await handleArgs(portfolio, args, ++histi).then(console.log);
716
+ } catch(err) {
717
+ catcher(468,err);
718
+ }
719
+ // Wait a sec for the nonce.
720
+ // -------------------------
721
+ await sleep(1000);
722
+ }
723
+ //console.log("Try CRTL-C while I sleep for a minute...");
724
+ //await sleep(1000);
725
+ cmd = null;
726
+ //}
727
+ }
728
+
729
+ process.stdin.on('readable', async () => {
730
+ // clearInterval(auto);
731
+ let cmd = '',
732
+ waiter = 0;
733
+ data = '';
734
+ while(null != (data = process.stdin.read())) cmd += data;
735
+ if(/^quit/.test(cmd)) {
736
+ console.log("Userref collisions possible with restart before "
737
+ + new Date(histi * 1000));
738
+ process.exit(0);
739
+ } else {
740
+ clearTimeout(waiter);
741
+ waiter = setTimeout(() => {
742
+ // Do we need to stop this listener from listening while runOnce runs?
743
+ if(cmdList.length > 0) runOnce(cmdList).catch((err) => { catcher(496,err); });
744
+ cmdList = [];
745
+ },100);
746
+ cmdList.push(cmd);
747
+ }
748
+ });
749
+
750
+ function catcher(line,err) {
751
+ if(/ETIMEDOUT/.test(err.code)) return; // We can ignore timeout errors.
752
+ console.log("Line "+line+";\n",err);
753
+ clearInterval(auto);
754
+ }
755
+
756
+ process.on('uncaughtException', function (err) {
757
+ catcher(0,err);
758
+ });
759
+
760
+ let kwsCheck;
761
+ function krakenSaid(obj) {
762
+ if(obj.event=='heartbeat') {
763
+ kwsCheck = new Date();
764
+ } else {
765
+ console.log(557,obj);
766
+ if(Array.isArray(obj)) {
767
+ runOnce(['report']).catch((err) => { catcher(543,err); });
768
+ }
769
+ }
770
+ }
771
+
772
+ // Subscribe to OwnOrders Websocket
773
+ // --------------------------------
774
+ const WebSocket = require('ws');
775
+ async function openSocket() {
776
+ let wsAuthToken = await kapi(['GetWebSocketsToken']);
777
+ while(!wsAuthToken) {
778
+ console.log(570,"Waiting a second...");
779
+ await sleep(1000);
780
+ wsAuthToken = await kapi(['GetWebSocketsToken']);
781
+ }
782
+ let myOrders = new WebSocket('wss://ws-auth.kraken.com');
783
+ myOrders.on('message', msg => krakenSaid(JSON.parse(msg)));
784
+ await new Promise(resolve => myOrders.once('open', resolve));
785
+ console.log(wsAuthToken);
786
+ myOrders.send(JSON.stringify({
787
+ event:'subscribe',
788
+ subscription: {
789
+ name:'ownTrades',
790
+ token:wsAuthToken.result.token
791
+ }}));
792
+ };
793
+
794
+ runOnce(['report']).catch((err) => { catcher(578,err); });
795
+ console.log("Safemode is on. `safe` toggles it.");