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