kraken-grid 1.0.4 → 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.
- package/README.md +5 -5
- package/index.js +169 -99
- package/index.js2 +816 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -49,14 +49,14 @@ This simply prints out a list of all the open orders the code last retrieved (it
|
|
|
49
49
|
* `Pair` is symbol (see `buy`) with the 'USD' suffix.
|
|
50
50
|
* `Price` is the price for this trade.
|
|
51
51
|
* The corresponding bracketed items will be missing for an order with no leverage or without a conditional close.
|
|
52
|
-
* `userref` is a user-reference number
|
|
52
|
+
* `userref` is a user-reference number created when you use the `buy` or `sell` command. It starts with 1 for buys and 2 for sells, followed by two digits that identify the cryptocurrency pair, and then the price without the decimal point and with leading zeroes. Note that this causes collisions in very rare cases like a price of $35.01 and another price for the same crypto of $350.10. I expect this to be too rare to fix at this time.
|
|
53
53
|
|
|
54
|
-
If you enter anything for [SEARCH], the list will only display lines that contain what you entered, except in one case, `C`. If it's just the `C`, it will retrieve the last 50 orders that are no longer open (Filled, Cancelled, or Expired), but only list those that actually executed (Filled). If you add the userref after `C`, then it will fetch only orders with that userref, which means the buys and sells
|
|
54
|
+
If you enter anything for [SEARCH], the list will only display lines that contain what you entered, except in one case, `C`. If it's just the `C`, it will retrieve the last 50 orders that are no longer open (Filled, Cancelled, or Expired), but only list those that actually executed (Filled). If you add the userref after `C`, then it will fetch only orders with that userref, which means the buys and sells between one set of prices. Use `set` for a list of the userrefs for the grid points. Such orders also include the time at which the order filled completely.
|
|
55
55
|
|
|
56
56
|
### set
|
|
57
57
|
This lists the `userref`s and prices at which buys and sells have been (and will be) placed.
|
|
58
58
|
`set R S P`
|
|
59
|
-
R _must be_ a userref, S _must be_ either `buy` or `sell`, and P is the price you want to add for that grid point. If the bot fails to determine either the buy price or the sell price, it displays a ?, and this will prevent the creation of a new order as described under `refnum`. This command allows you to fix that so that Step 2.1 under `report` will work properly as described under `refnum`.
|
|
59
|
+
R _must be_ a userref, S _must be_ either `buy` or `sell`, and P is the price you want to add or replace) for that grid point. If the bot fails to determine either the buy price or the sell price, it displays a ?, and this will prevent the creation of a new order as described under `refnum`. This command allows you to fix that so that Step 2.1 under `report` will work properly as described under `refnum`.
|
|
60
60
|
|
|
61
61
|
### reset
|
|
62
62
|
This erases the list of `userref`s and prices at which buys and sells will be placed, but that list gets immediately rebuilt because it performs the second step in `report`.
|
|
@@ -69,7 +69,7 @@ This automatically and repeatedly executes the second step of `report` and then
|
|
|
69
69
|
This stops the automatic calling of `report`. The bot will do nothing until you give it a new command.
|
|
70
70
|
|
|
71
71
|
### margin
|
|
72
|
-
|
|
72
|
+
Whether or not you use this command, the bot will try to use leverage when there isn't enough USD or crypto. Whether or not it succeeds, it will still be able to report how much you are long or short for each supported crypto. Reporting that is all this command does.
|
|
73
73
|
|
|
74
74
|
### kill
|
|
75
75
|
`kill X`
|
|
@@ -91,7 +91,7 @@ C _must be_ a `Counter` as shown by executing `list`, and it must be an order th
|
|
|
91
91
|
There is a little bit of logic in the code to spit out a lot more information when verbose is on. It's off by default and this command just toggles it.
|
|
92
92
|
|
|
93
93
|
### safe
|
|
94
|
-
When the bot starts, it is in "safe" mode, which means that it will not __actually__ add or cancel any orders. The idea is that it won't do anything, but instead just show you what it would do if __safe__ were off. Your have to enter `safe` to turn this off so that the bot will actually do things. It allows for startup with a lot less
|
|
94
|
+
When the bot starts, it is in "safe" mode, which means that it will not __actually__ add or cancel any orders. The idea is that it won't do anything, but instead just show you what it would do if __safe__ were off. Your have to enter `safe` to turn this off so that the bot will actually do things. It allows for startup with a lot less risk with a possibly buggy bot.
|
|
95
95
|
|
|
96
96
|
### ws - EXPERIMENTAL
|
|
97
97
|
This connects to Kraken's WebSockets, which, I have to warn you, send you something about every second, and sometimes silently disconnects.
|
package/index.js
CHANGED
|
@@ -16,7 +16,7 @@ if(!fs.existsSync(keyFile)) {
|
|
|
16
16
|
|
|
17
17
|
const myKeys = fs.readFileSync(keyFile,{encoding:'utf8', flag:'r'});
|
|
18
18
|
const [key,secret] = myKeys.split(' ');
|
|
19
|
-
const KrakenClient = require('
|
|
19
|
+
const KrakenClient = require('kraka-djs');
|
|
20
20
|
const kraken = new KrakenClient(key, secret);
|
|
21
21
|
function sleep(ms) {
|
|
22
22
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
@@ -37,7 +37,8 @@ async function kapi(arg,sd=5)
|
|
|
37
37
|
|| /nonce/.test(err.message)
|
|
38
38
|
|| /Response code 50/.test(err.message)
|
|
39
39
|
|| (risky && /Internal error/.test(err.message))
|
|
40
|
-
|| /Unavailable/.test(err.message)
|
|
40
|
+
|| /Unavailable/.test(err.message)
|
|
41
|
+
|| /Rate limit|Throttled/.test(err.message)) {
|
|
41
42
|
console.log(22,err.message+", so trying again in "+sd+"s...("+(new Date)+"):");
|
|
42
43
|
if(Array.isArray(arg)) {
|
|
43
44
|
delete arg[1].nonce;
|
|
@@ -65,6 +66,7 @@ async function order(buysell, xmrbtc, price, amt, lev='none', uref=0, closeO=nul
|
|
|
65
66
|
a = Number(amt),
|
|
66
67
|
ret = '';
|
|
67
68
|
|
|
69
|
+
if(!RegExp('^('+USDPairs.join('|')+')$').test(xmrbtc+'USD')) return xmrbtc+" is not yet supported.";
|
|
68
70
|
if( cO == price ) cO = 0;
|
|
69
71
|
if(uref==0) uref = makeUserRef(buysell, xmrbtc, price);
|
|
70
72
|
|
|
@@ -74,7 +76,7 @@ async function order(buysell, xmrbtc, price, amt, lev='none', uref=0, closeO=nul
|
|
|
74
76
|
if( cO>0 && (buysell == 'buy' ? cO <= price : cO >= price) )
|
|
75
77
|
throw 'Close price, '+cO+' is on the wrong side of '+buysell+' at '+price+'!';
|
|
76
78
|
ret = ['AddOrder',
|
|
77
|
-
{ pair: xmrbtc+'USD', // Just call it 'pair'! #USD Refactor
|
|
79
|
+
{ pair: xmrbtc+'USD', // Just call it 'pair' so it already has the USD at the end! #USD Refactor
|
|
78
80
|
userref: uref,
|
|
79
81
|
type: buysell,
|
|
80
82
|
ordertype: 'limit',
|
|
@@ -120,24 +122,26 @@ async function listOpens(portfolio = null, isFresh=false) {
|
|
|
120
122
|
r2 = await kapi(['ClosedOrders',{ofs:50}]),
|
|
121
123
|
r3 = await kapi(['ClosedOrders',{ofs:100}]),
|
|
122
124
|
closed = {...response.result.closed, ...r2.result.closed, ...r3.result.closed };
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if(
|
|
133
|
-
gp
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
+
}
|
|
141
145
|
}
|
|
142
146
|
}
|
|
143
147
|
}
|
|
@@ -223,7 +227,7 @@ async function listOpens(portfolio = null, isFresh=false) {
|
|
|
223
227
|
volume: Number(oo.vol),
|
|
224
228
|
type: od.type,
|
|
225
229
|
sym: /USD/.test(od.pair) ? /^([A-Z]+)USD/.exec(od.pair)[1] : od.pair,
|
|
226
|
-
// Just call it 'pair'! #USD Refactor
|
|
230
|
+
// Just call it 'pair' instad of sym and use od.pair! #USD Refactor
|
|
227
231
|
ctype: ct,
|
|
228
232
|
lev: od.leverage,
|
|
229
233
|
ids: [o],
|
|
@@ -258,7 +262,7 @@ async function listOpens(portfolio = null, isFresh=false) {
|
|
|
258
262
|
if(portfolio && isFresh && od.leverage == "none") {
|
|
259
263
|
if(od.type == "buy") {
|
|
260
264
|
if(/USD$/.test(od.pair)) { // Deplete our cash
|
|
261
|
-
portfolio['ZUSD'][2] -= od.price*opens[o].vol;
|
|
265
|
+
portfolio['ZUSD'][2] -= od.price*opens[o].vol; // #USD Refactor and basePair()
|
|
262
266
|
} else if(/XBT$/.test(od.pair)) { // Deplete our BTC
|
|
263
267
|
portfolio['XBT'][0] -= od.price*opens[o].vol;
|
|
264
268
|
}
|
|
@@ -286,7 +290,7 @@ async function listOpens(portfolio = null, isFresh=false) {
|
|
|
286
290
|
}
|
|
287
291
|
gp[c.ctype] = c.open;
|
|
288
292
|
gp[c.type] = c.price;
|
|
289
|
-
[,sym,price] = /([A-Z]+)USD([0-9.]+)/.exec(comp);
|
|
293
|
+
[,sym,price] = /([A-Z]+)USD([0-9.]+)/.exec(comp); //remove USD #USD Refactor
|
|
290
294
|
if(verbose) console.log("Checking: " + c.type + ' '
|
|
291
295
|
+ sym + ' ' + price + ' ' + Math.round(c.total*10000)/10000
|
|
292
296
|
+ (c.open ? ' to '+c.ctype+'-close @'+c.open : '') +' (' + c.userref + "):");
|
|
@@ -385,9 +389,9 @@ function getLev(portfolio,buysell,price,amt,xmrbtc,posP) {
|
|
|
385
389
|
if(1*price > 1*portfolio[xmrbtc][1] && posP)
|
|
386
390
|
return "Buying "+xmrbtc+" @ "+price+" isn't a limit order.";
|
|
387
391
|
if(price*amt > 1*portfolio['ZUSD'][2]) { // #USD Refactor - This doesn't support leverage
|
|
388
|
-
lev = '2'; // on non-USD pairs.
|
|
392
|
+
lev = '2'; // on non-USD pairs. Hunt ZUSD and add basePair(pair) to get base.
|
|
389
393
|
} else {
|
|
390
|
-
portfolio['ZUSD'][2] -= price*amt;
|
|
394
|
+
portfolio['ZUSD'][2] -= price*amt; // #USD Refactor and basePair()
|
|
391
395
|
}
|
|
392
396
|
} else {
|
|
393
397
|
if(price*1 < 1*portfolio[xmrbtc][1] && posP) return "Selling "+xmrbtc+" @ "+price+" isn't a limit order.";
|
|
@@ -434,10 +438,13 @@ async function kill(o,oa) {
|
|
|
434
438
|
// await sleep(1000);
|
|
435
439
|
}
|
|
436
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
|
+
*/
|
|
437
445
|
async function handleArgs(portfolio, args, uref = 0) {
|
|
438
446
|
if(/buy|sell/.test(args[0])) {
|
|
439
447
|
[buysell,xmrbtc,price,amt,posP] = args;
|
|
440
|
-
if(!/XMR|XBT|ETH|LTC|DASH|EOS|BCH|USDT|UST|LUNA/.test(xmrbtc)) return xmrbtc+" is not yet supported.";
|
|
441
448
|
let total=price*amt;
|
|
442
449
|
if(total > 100000) return total+" is too much for code to "+buysell;
|
|
443
450
|
|
|
@@ -450,7 +457,11 @@ async function handleArgs(portfolio, args, uref = 0) {
|
|
|
450
457
|
// Without a record of a closing price, use the last one we found.
|
|
451
458
|
// ---------------------------------------------------------------
|
|
452
459
|
if(!cPrice) cPrice = portfolio[xmrbtc][1];
|
|
453
|
-
|
|
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.
|
|
454
465
|
let ret = await order(buysell,xmrbtc,price,amt,lev,uref,closeO);
|
|
455
466
|
console.log("New order: "+ret);
|
|
456
467
|
return;
|
|
@@ -466,48 +477,9 @@ async function handleArgs(portfolio, args, uref = 0) {
|
|
|
466
477
|
} else if(args[0] == 'refnum') {
|
|
467
478
|
await refnum(portfolio['O'],args[1]-1,args[2]);
|
|
468
479
|
} else if(args[0] == 'list') {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
response = ur
|
|
473
|
-
? await kapi(['ClosedOrders',{userref:ur}])
|
|
474
|
-
: await kapi('ClosedOrders');
|
|
475
|
-
orders = [];
|
|
476
|
-
for( o in response.result.closed) {
|
|
477
|
-
let oo = response.result.closed[o];
|
|
478
|
-
if(oo.status=='closed') orders.push([o,response.result.closed[o]]);
|
|
479
|
-
}
|
|
480
|
-
args.pop();
|
|
481
|
-
}
|
|
482
|
-
orders.forEach((x,i) => {
|
|
483
|
-
let ldo = x[1].descr.order;
|
|
484
|
-
if(args.length==1 || RegExp(args[1]).test(ldo))
|
|
485
|
-
console.log(i+1,ldo,x[1].userref,x[1].status=='closed'
|
|
486
|
-
? new Date(1000*x[1].closetm)
|
|
487
|
-
: x[1].descr.close);
|
|
488
|
-
else if(x[1][args[1]]) sortedA[i+1]=x;
|
|
489
|
-
else if(x[1].descr[args[1]]) sortedA[i+1]=x;
|
|
490
|
-
});
|
|
491
|
-
if(sortedA.length > 0) {
|
|
492
|
-
sortedA.sort((a,b) => {
|
|
493
|
-
if(a[1].descr[args[1]]) {
|
|
494
|
-
a = a[1].descr[args[1]];
|
|
495
|
-
b = b[1].descr[args[1]];
|
|
496
|
-
} else {
|
|
497
|
-
a = a[1][args[1]];
|
|
498
|
-
b = b[1][args[1]];
|
|
499
|
-
}
|
|
500
|
-
return isNaN(a)
|
|
501
|
-
? a.localeCompare(b)
|
|
502
|
-
: a - b;
|
|
503
|
-
});
|
|
504
|
-
sortedA.forEach((x,i) => {
|
|
505
|
-
let ldo = x[1].descr.order;
|
|
506
|
-
console.log(i+1, x[1].descr[args[1]]
|
|
507
|
-
? x[1].descr[args[1]] : x[1][args[1]],
|
|
508
|
-
ldo,x[1].userref,x[1].descr.close);
|
|
509
|
-
});
|
|
510
|
-
};
|
|
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]);
|
|
511
483
|
} else if(args[0] == 'test') {
|
|
512
484
|
// Put some test code here if you want
|
|
513
485
|
// -----------------------------------
|
|
@@ -519,6 +491,101 @@ async function handleArgs(portfolio, args, uref = 0) {
|
|
|
519
491
|
}
|
|
520
492
|
}
|
|
521
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
|
+
|
|
522
589
|
async function refnum(opensA,oid,newRef) {
|
|
523
590
|
let o, oRef;
|
|
524
591
|
if(!opensA[oid]) {
|
|
@@ -546,7 +613,7 @@ async function refnum(opensA,oid,newRef) {
|
|
|
546
613
|
}
|
|
547
614
|
|
|
548
615
|
async function deleverage(opensA,oid,undo=false) {
|
|
549
|
-
let o, oRef;
|
|
616
|
+
let o, oRef, placed;
|
|
550
617
|
if(!opensA[oid]) {
|
|
551
618
|
console.log("Order "+oid+" not found.");
|
|
552
619
|
return;
|
|
@@ -562,16 +629,18 @@ async function deleverage(opensA,oid,undo=false) {
|
|
|
562
629
|
return;
|
|
563
630
|
}
|
|
564
631
|
if(!o.descr.close) {
|
|
565
|
-
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],
|
|
566
633
|
o.descr.price,Math.round(10000*(Number(o.vol) - Number(o.vol_exec)))/10000,
|
|
567
634
|
(undo ? '2' : 'none'),o.userref);
|
|
568
635
|
} else {
|
|
569
|
-
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],
|
|
570
637
|
o.descr.price,Math.round(10000*(Number(o.vol) - Number(o.vol_exec)))/10000,
|
|
571
638
|
(undo ? '2' : 'none'),o.userref,
|
|
572
639
|
/[0-9.]+$/.exec(o.descr.close)[0] );
|
|
573
640
|
}
|
|
574
|
-
|
|
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
|
+
}
|
|
575
644
|
}
|
|
576
645
|
|
|
577
646
|
|
|
@@ -627,8 +696,8 @@ async function report(portfolio,showBalance=true) {
|
|
|
627
696
|
|
|
628
697
|
let price;
|
|
629
698
|
for( const p in bal.result) {
|
|
630
|
-
let ts = p+'USD',
|
|
631
|
-
tsz = p+'ZUSD',
|
|
699
|
+
let ts = p+'USD', // #USD Refactor and basePair()
|
|
700
|
+
tsz = p+'ZUSD', // #USD Refactor and basePair()
|
|
632
701
|
sym = /^X/.test(p) ? p.substr(1) : p,
|
|
633
702
|
amt = toDec(bal.result[p],4);
|
|
634
703
|
if(ts in tik.result) price = tik.result[ts].c[0];
|
|
@@ -679,7 +748,6 @@ async function marginReport(show = true) {
|
|
|
679
748
|
|
|
680
749
|
let stopNow = false,
|
|
681
750
|
portfolio = [],
|
|
682
|
-
histi = Math.floor(Date.now() / 1000),
|
|
683
751
|
ts150 = 0,
|
|
684
752
|
delay = 60,
|
|
685
753
|
auto = 0,
|
|
@@ -715,8 +783,8 @@ async function runOnce(cmdList) {
|
|
|
715
783
|
if(args[1]&&!isNaN(args[1])) delay = args[1];
|
|
716
784
|
let counter = delay;
|
|
717
785
|
auto = setInterval(async function() {
|
|
718
|
-
if(0 == --counter
|
|
719
|
-
await report(portfolio,false);
|
|
786
|
+
if(0 == --counter) {
|
|
787
|
+
if(!auto_on_hold) await report(portfolio,false);
|
|
720
788
|
counter = delay;
|
|
721
789
|
}
|
|
722
790
|
},1000);
|
|
@@ -734,7 +802,7 @@ async function runOnce(cmdList) {
|
|
|
734
802
|
console.log("Verbose is "+(verbose ? 'on' : 'off'));
|
|
735
803
|
} else if(args[0] == 'margin') {
|
|
736
804
|
await marginReport();
|
|
737
|
-
} else await handleArgs(portfolio, args,
|
|
805
|
+
} else await handleArgs(portfolio, args, 0).then(console.log);
|
|
738
806
|
} catch(err) {
|
|
739
807
|
catcher(468,err);
|
|
740
808
|
}
|
|
@@ -748,29 +816,11 @@ 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);
|
|
773
822
|
clearInterval(auto);
|
|
823
|
+
auto = 0;
|
|
774
824
|
}
|
|
775
825
|
|
|
776
826
|
process.on('uncaughtException', function (err) {
|
|
@@ -813,3 +863,23 @@ async function openSocket() {
|
|
|
813
863
|
|
|
814
864
|
runOnce(['report']).catch((err) => { catcher(578,err); });
|
|
815
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
|
|
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",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"homepage": "https://github.com/dscotese/kraken-grid#readme",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"
|
|
27
|
+
"kraka-djs": "^1.0.2",
|
|
28
28
|
"prompt-sync": "^4.2.0",
|
|
29
29
|
"ws": "^8.4.0"
|
|
30
30
|
}
|