kraken-grid 1.0.1 → 1.0.4
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 +46 -31
- package/index.js +149 -130
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,28 +4,21 @@ A bot that extends grid trading once you use it to create a grid using orders wi
|
|
|
4
4
|
This was developed with NodeJS running in the BASH shell provided by Windows 10. I believe it's using "Windows Subsystem for Linux" and that there are some oddities because of this. I don't see them as odd because I'm not familiar enough with Linux yet.
|
|
5
5
|
|
|
6
6
|
## Installation
|
|
7
|
-
|
|
7
|
+
1. Get your API key and secret from Kraken.
|
|
8
|
+
2. Install [NodeJS](https://nodejs.org/)
|
|
9
|
+
3. Run `npm install kraken-grid`
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
```
|
|
11
|
-
exports.key=' **your key goes here** ';
|
|
12
|
-
exports.secret=' **Your secret goes here** ';
|
|
13
|
-
|
|
14
|
-
// If you want private initialization code, (mine backs up the file)
|
|
15
|
-
// this is a good place to put it.
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Once you've done that, the first thing it will do is execute the `report` command, described below.
|
|
11
|
+
If you install kraken-grid globally (put `-g` after `install`), you can run it from anywhere, though you may have to reload your command line interpreter. Please note that it will save a file `keys.txt` to your home folder.
|
|
19
12
|
|
|
20
13
|
## Usage
|
|
21
14
|
At the prompt that kraken-grid presents (>), you can enter one of these commands:
|
|
22
15
|
|
|
23
16
|
### buy
|
|
24
|
-
`buy [XMR|
|
|
17
|
+
`buy [XMR|XBT|ETH|DASH|EOS|LTC|BCH] price amount makeClose`
|
|
25
18
|
|
|
26
|
-
makeClose is interpreted as a Boolean. I never tested `0` but I assume 0 means false (the default). If makeCLose evaluates to true, the code will create this buy with a conditional close at the last price it saw. If you don't want the code to place a trade with a conditional close, leave makeCLose off.
|
|
19
|
+
makeClose is interpreted as a Boolean. I never tested `0` but I assume 0 means false (the default). If makeCLose evaluates to true, the code will create this buy with a conditional close at the last price it saw. If you don't want the code to place a trade with a conditional close, leave makeCLose off or pass `false` for it.
|
|
27
20
|
|
|
28
|
-
If you want to use other cryptos, there is a line in `report()` and a line in `handleArgs()` which both need to be changed by adding the symbol. There's another line in report that assumes that the initial X in _every_ symbol is extraneous. This is true for the four I hold, XMR, ETH, XBT, and LTC, but check Kraken's AssetPairs to see if it's true for any you add.
|
|
21
|
+
If you want to use other cryptos, there is a line in `report()` and a line in `handleArgs()` which both need to be changed by adding the symbol. There's another line in report that assumes that the initial X in _every_ symbol is extraneous. This is true for the four I hold, XMR, ETH, XBT, and LTC, but check [Kraken's AssetPairs](https://api.kraken.com/0/public/Assets) to see if it's true for any you add.
|
|
29
22
|
|
|
30
23
|
### sell
|
|
31
24
|
The semantics are the same as for `buy`
|
|
@@ -40,55 +33,77 @@ XLTC ...
|
|
|
40
33
|
...
|
|
41
34
|
```
|
|
42
35
|
2. Retrieves the list of open orders, which is immediately processed to:
|
|
43
|
-
1. replace conditional closes resulting from partial executions with a single conditional close which, itself, has a conditional close to continue buying and selling between the two prices, but only up to the amount originally specified.
|
|
36
|
+
1. replace conditional closes resulting from partial executions with a single conditional close which, itself, has a conditional close to continue buying and selling between the two prices, but only up to the amount originally specified, and _only_ for orders with a User Reference Number (such as all orders placed through this program).
|
|
44
37
|
2. fill out the internal record of buy/sell prices using the open orders and their conditional closes (see `set` and `reset`).
|
|
45
38
|
3. extend the grid if there are only buys or only sells remaining for the crypto identified in each order.
|
|
46
39
|
4. identify any orders that are gone or new using Kraken's Order ID and for new orders, it also describes them.
|
|
47
40
|
|
|
48
|
-
### list
|
|
49
|
-
This simply prints out a list of all the orders the code last retrieved (it does NOT retrieve them again, so...) It may have orders in it that have already been executed. Each order is presented as:
|
|
50
|
-
`Counter amount pair @ limit price [with A:B leverage] userref [close position @ limit price]`
|
|
41
|
+
### list [SEARCH]
|
|
42
|
+
This simply prints out a list of all the open orders the code last retrieved (it does NOT retrieve them again, so...) It may have orders in it that have already been executed or which you canceled. Each order is presented as:
|
|
43
|
+
`Counter K trade amount pair @ limit price [with A:B leverage] userref [close position @ limit price]`
|
|
51
44
|
...where:
|
|
52
|
-
* `Counter` gives you a handle to use for other commands like delev and kill
|
|
53
|
-
* `
|
|
45
|
+
* `Counter` gives you a handle to use for other commands like delev and kill.
|
|
46
|
+
* `K` is `Killed` if you used the kill command to cancel an order and the bot hasn't yet updated the list. For existing orders, `K` is missing.
|
|
47
|
+
* `Trade` is either `buy` or `sell`.
|
|
48
|
+
* `Amount` is the number of coins.
|
|
54
49
|
* `Pair` is symbol (see `buy`) with the 'USD' suffix.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
50
|
+
* `Price` is the price for this trade.
|
|
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 derived from the UNIX TimeStamp when the order was placed. Extending the grid to higher sells uses a userref 10,000,000 less than the current highest sell's userref, and extending it to lower-priced buys uses a userref 1,000,000 less than the current lowest buy's userref. The last six digits of all userrefs are assumed to be different for every combination of price and symbol.
|
|
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 at one grid point. See `set` for a list of them userrefs for the grid points. Such orders also include the time at which the order filled completely.
|
|
57
55
|
|
|
58
56
|
### set
|
|
59
57
|
This lists the `userref`s and prices at which buys and sells have been (and will be) placed.
|
|
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`.
|
|
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`.
|
|
63
63
|
|
|
64
64
|
### auto
|
|
65
65
|
`auto N`
|
|
66
|
-
This automatically and repeatedly executes the second step of `report` and then waits N seconds. N defaults to 60 but when you call auto with a new value for it, it is updated.
|
|
66
|
+
This automatically and repeatedly executes the second step of `report` and then waits N seconds. N defaults to 60 but when you call auto with a new value for it, it is updated.
|
|
67
|
+
|
|
68
|
+
### manual
|
|
69
|
+
This stops the automatic calling of `report`. The bot will do nothing until you give it a new command.
|
|
70
|
+
|
|
71
|
+
### margin
|
|
72
|
+
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.
|
|
67
73
|
|
|
68
74
|
### kill
|
|
69
75
|
`kill X`
|
|
70
|
-
X can be an Order ID from Kraken (recognized by the presence of dashes), a userref (which often identifies more than one order, and, importantly, _both_ the initial buy or sell, _and_ the series of sells and buys resulting from partial executions), or a `Counter` as shown from `list`. This cancels the order or orders.
|
|
76
|
+
X can be an Order ID from Kraken (recognized by the presence of dashes), a userref (which often identifies more than one order, and, importantly, _both_ the initial buy or sell, _and_ the series of sells and buys resulting from partial executions), or a `Counter` as shown from `list`. This cancels the order or orders. `list` will still show such orders, prefixed with `Killed`, until `report` runs again to update the internal record of open orders.
|
|
71
77
|
|
|
72
78
|
### delev
|
|
73
79
|
`delev C`
|
|
74
|
-
C _must be_ a `Counter` as
|
|
80
|
+
C _must be_ a `Counter` as shown by executing `list`. If the identified order uses leverage, this command will first create an order without any leverage to replace it, and then kill the one identified. The order that was killed will still be in the list, prefixed with `Killed:` ***NOTE: The new order often (or always?) appears at the top of `list` after this, so the `Counter`s identifying other orders may change.
|
|
75
81
|
|
|
76
|
-
###
|
|
77
|
-
|
|
82
|
+
### addlev
|
|
83
|
+
`addlev C`
|
|
84
|
+
The semantics are the same as for `delev`.
|
|
85
|
+
|
|
86
|
+
### refnum
|
|
87
|
+
`refnum C R`
|
|
88
|
+
C _must be_ a `Counter` as shown by executing `list`, and it must be an order that was entered without a userref. It will cancel the existing order and create a new one with the specified userref `R`. All orders added by the bot (automatically and manually) have a userref. This function is to allow you to enter an order at trade.kraken.com using the same price and no conditional close so that the bot will include it into the existing grid point with the same userref (use `set` to make sure both the buy and sell prices are known for the userref) as R. If you use `refnum` to assign the reference number of an order that is at a different price, the behavior is undefined.
|
|
78
89
|
|
|
79
90
|
### verbose
|
|
80
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.
|
|
81
92
|
|
|
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 rish with a possible buggy bot.
|
|
95
|
+
|
|
82
96
|
### ws - EXPERIMENTAL
|
|
83
97
|
This connects to Kraken's WebSockets, which, I have to warn you, send you something about every second, and sometimes silently disconnects.
|
|
84
98
|
|
|
85
99
|
## Internals
|
|
86
|
-
|
|
100
|
+
### Userref
|
|
101
|
+
When you place an order through trade.kraken.com or through kraken.com, it will have a `userref` of zero. It will be ignored for the purposes of grid trading. When you place an order through the bot, it will have a userref.
|
|
87
102
|
|
|
88
|
-
###
|
|
89
|
-
|
|
103
|
+
### Partial Execution
|
|
104
|
+
Because grid orders have conditional closes (at a price I'll call C, for close, where I'll call the price on the opening order O), a new trade is created each time a partial execution occurs, but any such new trades do not have conditional closes (which would need to have C and O swapped). These conditional closes all have the same userref as the order that produced them. The bot detects this, sums the amount executed at price O, cancels the new orders created by the partial executions, and creates a new order for the sum at price C using the same userref and with a conditional close of its own that uses price O (see how C and O are now swapped?). Rarely, only part of an order will have executed (at price O) and the price will move back to C and cause the conditional close(s) to execute. If they were combined and thus already have their own conditional close (at O), new orders will appear at O, in addition to the original. At trade.kraken.com, this looks like it will be trading too much at O, but that is because the partial execution reduced the size of the original trade, and trade.kraken.com still shows the original trade amount. You can click the trade on trade.kraken.com to verify that the sum of the new order at O and the origianl one add to the right amount. You got a round trip on less volume than the bot was set to try, because the market didn't fully execute your original order. All is well.
|
|
90
105
|
|
|
91
106
|
## HELP!
|
|
92
107
|
This code is messy and monolithic. It works for me and I didn't want to keep waiting until I cleaned it up to publish it. I haven't yet put enough thought into how (and whether) I would break it up into smaller files with specific purposes, so I'd love to see proposals. One of the major motivations I have for publishing it is that as more people use a strategy like "grid trader" to balance their savings, the prices of the cryptos with which they do it will become more stable.
|
|
93
108
|
|
|
94
|
-
All calls to @nothingisdead's [Kraken-API](https://github.com/nothingisdead/npm-kraken-api) are made through a function I called `kapi` so that any other exchange could be used by updating that funtion to translate Kraken's APIs to those of other exchanges.
|
|
109
|
+
All calls to @nothingisdead's [Kraken-API](https://github.com/nothingisdead/npm-kraken-api) (which I have copied and renamed to kraka-djs to add CancelAll) are made through a function I called `kapi` so that any other exchange could be used by updating that funtion to translate Kraken's APIs to those of other exchanges.
|
package/index.js
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// COmment Line to see git diff...
|
|
3
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';
|
|
4
10
|
|
|
5
|
-
if(!fs.existsSync(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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);
|
|
9
15
|
}
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const secret
|
|
16
|
+
|
|
17
|
+
const myKeys = fs.readFileSync(keyFile,{encoding:'utf8', flag:'r'});
|
|
18
|
+
const [key,secret] = myKeys.split(' ');
|
|
13
19
|
const KrakenClient = require('kraken-api');
|
|
14
20
|
const kraken = new KrakenClient(key, secret);
|
|
15
21
|
function sleep(ms) {
|
|
@@ -27,9 +33,11 @@ async function kapi(arg,sd=5)
|
|
|
27
33
|
ret = await kraken.api(arg);
|
|
28
34
|
}
|
|
29
35
|
} catch(err) {
|
|
30
|
-
if((!/AddOrder/.test(arg[0])&&/ETIMEDOUT|EAI_AGAIN/.test(err.code))
|
|
36
|
+
if((!/AddOrder/.test(arg[0])&&/ETIMEDOUT|EAI_AGAIN/.test(err.code))
|
|
31
37
|
|| /nonce/.test(err.message)
|
|
32
|
-
|| /Response code 50/.test(err.message)
|
|
38
|
+
|| /Response code 50/.test(err.message)
|
|
39
|
+
|| (risky && /Internal error/.test(err.message))
|
|
40
|
+
|| /Unavailable/.test(err.message)) {
|
|
33
41
|
console.log(22,err.message+", so trying again in "+sd+"s...("+(new Date)+"):");
|
|
34
42
|
if(Array.isArray(arg)) {
|
|
35
43
|
delete arg[1].nonce;
|
|
@@ -40,39 +48,44 @@ async function kapi(arg,sd=5)
|
|
|
40
48
|
await sleep(sd*1000);
|
|
41
49
|
ret = await kapi(arg,sd>300?sd:2*sd);
|
|
42
50
|
} else if( /Unknown order/.test(err.message) && /CancelOrder/.test(arg[0])) {
|
|
43
|
-
console.log("Ignoring: ", ...arg);
|
|
51
|
+
console.log("Ignoring: ", err.message, ...arg);
|
|
44
52
|
ret = { result: { descr: "Ignored" }};
|
|
45
53
|
} else {
|
|
46
54
|
catcher(26,err);
|
|
47
55
|
ret = { result: { descr: err }};
|
|
48
|
-
}
|
|
56
|
+
}
|
|
49
57
|
}
|
|
50
58
|
if(verbose) console.log(ret);
|
|
51
59
|
return ret;
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
async function order(buysell, xmrbtc, price, amt, lev='none', uref=0, closeO=null) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
let cO = Number(closeO),
|
|
64
|
+
p = Number(price),
|
|
65
|
+
a = Number(amt),
|
|
66
|
+
ret = '';
|
|
67
|
+
|
|
68
|
+
if( cO == price ) cO = 0;
|
|
69
|
+
if(uref==0) uref = makeUserRef(buysell, xmrbtc, price);
|
|
70
|
+
|
|
60
71
|
console.log(27,(safeMode ? '(Safe mode, so NOT) ' : '')
|
|
61
|
-
+buysell+"ing "+
|
|
62
|
-
+(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
72
|
+
+buysell+"ing "+a+" "+xmrbtc+" at "+p+" with leverage "+lev
|
|
73
|
+
+(cO==0 ? "" : " to close at "+(isNaN(cO)?closeO+' is NaN!':cO)) +" as "+uref);
|
|
74
|
+
if( cO>0 && (buysell == 'buy' ? cO <= price : cO >= price) )
|
|
75
|
+
throw 'Close price, '+cO+' is on the wrong side of '+buysell+' at '+price+'!';
|
|
76
|
+
ret = ['AddOrder',
|
|
66
77
|
{ pair: xmrbtc+'USD', // Just call it 'pair'! #USD Refactor
|
|
67
78
|
userref: uref,
|
|
68
79
|
type: buysell,
|
|
69
80
|
ordertype: 'limit',
|
|
70
|
-
price:
|
|
71
|
-
volume:
|
|
81
|
+
price: p,
|
|
82
|
+
volume: a,
|
|
72
83
|
leverage: lev,
|
|
73
|
-
close:
|
|
74
|
-
}]
|
|
75
|
-
|
|
84
|
+
close: (cO>0 ? {ordertype:'limit',price:cO} : null)
|
|
85
|
+
}];
|
|
86
|
+
if(!safeMode) {
|
|
87
|
+
let response = await kapi(ret);
|
|
88
|
+
console.log(40,response ? ((d = response.result)
|
|
76
89
|
? (ret = d.txid,d.descr) : 'No result.descr from kapi') : "No kapi response.");
|
|
77
90
|
console.log(42,"Cooling it for a second...");
|
|
78
91
|
await sleep(1000);
|
|
@@ -82,6 +95,14 @@ async function order(buysell, xmrbtc, price, amt, lev='none', uref=0, closeO=nul
|
|
|
82
95
|
|
|
83
96
|
function gpToStr(gp) { return gp.userref+':'+gp.buy+'-'+gp.sell+' '+gp.bought+'/'+gp.sold; }
|
|
84
97
|
|
|
98
|
+
function makeUserRef(buysell, xmrbtc, price) {
|
|
99
|
+
let ret = Number((buysell=='buy'?'1':'2')
|
|
100
|
+
+ ('00'+USDPairs.indexOf(xmrbtc+'USD')).slice(-2)
|
|
101
|
+
+ String('000000'+price).replace('.','').slice(-7));
|
|
102
|
+
if(verbose) console.log("Created userref ",ret);
|
|
103
|
+
return ret;
|
|
104
|
+
}
|
|
105
|
+
|
|
85
106
|
async function listOpens(portfolio = null, isFresh=false) {
|
|
86
107
|
let response = await kapi('OpenOrders'),
|
|
87
108
|
opens = response.result.open;
|
|
@@ -92,7 +113,7 @@ async function listOpens(portfolio = null, isFresh=false) {
|
|
|
92
113
|
ci,oo,od,rv,n=0,ur,op,cp,gpi,gp,ct,bs;
|
|
93
114
|
// Index for comps, n?, Closing Price, index to grid prices,
|
|
94
115
|
// and bs is "Both sides", holding an array of objects
|
|
95
|
-
// holding userref, and two
|
|
116
|
+
// holding userref, and two booleans, buy and sell.
|
|
96
117
|
if(portfolio&&portfolio['G']) gPrices = portfolio['G'];
|
|
97
118
|
if(gPrices.length == 0) {
|
|
98
119
|
let response = await kapi('ClosedOrders'),
|
|
@@ -121,7 +142,7 @@ async function listOpens(portfolio = null, isFresh=false) {
|
|
|
121
142
|
}
|
|
122
143
|
}
|
|
123
144
|
}
|
|
124
|
-
|
|
145
|
+
|
|
125
146
|
// Save the old order array so we can see the diff
|
|
126
147
|
// -----------------------------------------------
|
|
127
148
|
let oldRefs = [];
|
|
@@ -134,7 +155,7 @@ async function listOpens(portfolio = null, isFresh=false) {
|
|
|
134
155
|
op = od.price;
|
|
135
156
|
rv = oo.vol-oo.vol_exec;
|
|
136
157
|
ur = oo.userref;
|
|
137
|
-
|
|
158
|
+
|
|
138
159
|
if(ur > 0) {
|
|
139
160
|
// BothSides record for userref
|
|
140
161
|
// ----------------------------
|
|
@@ -178,7 +199,7 @@ async function listOpens(portfolio = null, isFresh=false) {
|
|
|
178
199
|
// order of the closing order into which we combine.
|
|
179
200
|
// -------------------------------------------------
|
|
180
201
|
if(od.close && ur>0) { // Externally added orders have userref=0
|
|
181
|
-
cp = /[0-9.]+$/.exec(od.close)[0];
|
|
202
|
+
cp = /[0-9.]+$/.exec(od.close)[0];
|
|
182
203
|
gp = gPrices.find(gprice => gprice.userref==ur);
|
|
183
204
|
if(!gp) {
|
|
184
205
|
gp = {userref:ur,buy:'?',sell:'?', bought: 0, sold: 0};
|
|
@@ -211,7 +232,7 @@ async function listOpens(portfolio = null, isFresh=false) {
|
|
|
211
232
|
price: od.price,
|
|
212
233
|
hasClose: Boolean(od.close)
|
|
213
234
|
};
|
|
214
|
-
} else {
|
|
235
|
+
} else {
|
|
215
236
|
comps[ci].total+=rv; // Volume for combined order.
|
|
216
237
|
comps[ci].ids.push(o);
|
|
217
238
|
comps[ci].volume += Number(oo.vol); // Volume for extended order.
|
|
@@ -233,7 +254,7 @@ async function listOpens(portfolio = null, isFresh=false) {
|
|
|
233
254
|
console.log(159, "New: ",o,opensA.length, od.order, oo.userref, cp);
|
|
234
255
|
if(verbose) console.log(160,oo);
|
|
235
256
|
}
|
|
236
|
-
|
|
257
|
+
|
|
237
258
|
if(portfolio && isFresh && od.leverage == "none") {
|
|
238
259
|
if(od.type == "buy") {
|
|
239
260
|
if(/USD$/.test(od.pair)) { // Deplete our cash
|
|
@@ -254,7 +275,7 @@ async function listOpens(portfolio = null, isFresh=false) {
|
|
|
254
275
|
|
|
255
276
|
let nexes = 0, // Orders not requiring extension
|
|
256
277
|
dontask = false;
|
|
257
|
-
for( comp in comps ) if(/USD/.test(comp)) { // non-USD pairs break regex below... #USD Refactor
|
|
278
|
+
for( comp in comps ) if(/USD/.test(comp)) { // non-USD pairs break regex below... #USD Refactor
|
|
258
279
|
let c = comps[comp],
|
|
259
280
|
gp = gPrices.find(gprice => gprice.userref==c.userref);
|
|
260
281
|
bs = bSides.find(b => b.userref==c.userref);
|
|
@@ -266,7 +287,7 @@ async function listOpens(portfolio = null, isFresh=false) {
|
|
|
266
287
|
gp[c.ctype] = c.open;
|
|
267
288
|
gp[c.type] = c.price;
|
|
268
289
|
[,sym,price] = /([A-Z]+)USD([0-9.]+)/.exec(comp); //#USD Refactor
|
|
269
|
-
if(verbose) console.log("Checking: " + c.type + ' '
|
|
290
|
+
if(verbose) console.log("Checking: " + c.type + ' '
|
|
270
291
|
+ sym + ' ' + price + ' ' + Math.round(c.total*10000)/10000
|
|
271
292
|
+ (c.open ? ' to '+c.ctype+'-close @'+c.open : '') +' (' + c.userref + "):");
|
|
272
293
|
if(!isNaN(c.open)) {
|
|
@@ -274,13 +295,13 @@ async function listOpens(portfolio = null, isFresh=false) {
|
|
|
274
295
|
console.log(Object.values(c.ids));
|
|
275
296
|
for(const id of c.ids) { await kill(id,null); }
|
|
276
297
|
await order(c.type,sym,price, Math.round(c.total*10000)/10000,
|
|
277
|
-
c.lev,c.userref,
|
|
298
|
+
c.lev,c.userref,c.open);
|
|
278
299
|
c.hasClose = true;
|
|
279
300
|
// Store the trades in gp
|
|
280
301
|
// ----------------------
|
|
281
302
|
let traded = c.type=='buy' ? 'sold' : 'bought';
|
|
282
303
|
gp[traded]+=c.total;
|
|
283
|
-
}
|
|
304
|
+
}
|
|
284
305
|
// Do we need to extend the grid?
|
|
285
306
|
// If we don't have a buy and a sell, then yes.
|
|
286
307
|
// --------------------------------------------
|
|
@@ -299,8 +320,8 @@ async function listOpens(portfolio = null, isFresh=false) {
|
|
|
299
320
|
if(bs.buy) { // Missing the sell
|
|
300
321
|
do {
|
|
301
322
|
sp = Math.round(decimals*gp.sell*gp.sell/gp.buy)/decimals;
|
|
302
|
-
c.userref
|
|
303
|
-
// We may already have this grid price but the order
|
|
323
|
+
c.userref = makeUserRef('sell', c.sym, sp);
|
|
324
|
+
// We may already have this grid price but the order
|
|
304
325
|
// was deleted, so search for it first.
|
|
305
326
|
ngp = gPrices.find(n => n.userref==c.userref);
|
|
306
327
|
if(!ngp) {
|
|
@@ -316,14 +337,14 @@ async function listOpens(portfolio = null, isFresh=false) {
|
|
|
316
337
|
}
|
|
317
338
|
await order('sell',c.sym,sp,c.volume,
|
|
318
339
|
getLev(portfolio,'sell',sp,c.volume,c.sym,false),c.userref,
|
|
319
|
-
|
|
340
|
+
gp.sell);
|
|
320
341
|
gp = ngp;
|
|
321
|
-
} while(sp <= 1*portfolio[c.sym][1]);
|
|
342
|
+
} while(sp <= 1*portfolio[c.sym][1]);
|
|
322
343
|
} else {
|
|
323
344
|
do {
|
|
324
345
|
bp = Math.round(decimals*gp.buy*gp.buy/gp.sell)/decimals;
|
|
325
|
-
c.userref
|
|
326
|
-
// We may already have this grid price but the order
|
|
346
|
+
c.userref = makeUserRef('buy', c.sym, bp);
|
|
347
|
+
// We may already have this grid price but the order
|
|
327
348
|
// was deleted, so search for it first.
|
|
328
349
|
ngp = gPrices.find(n => n.userref==c.userref);
|
|
329
350
|
if(!ngp) {
|
|
@@ -339,7 +360,7 @@ async function listOpens(portfolio = null, isFresh=false) {
|
|
|
339
360
|
}
|
|
340
361
|
await order('buy',c.sym,bp,c.volume,
|
|
341
362
|
getLev(portfolio,'buy',bp,c.volume,c.sym,false),c.userref,
|
|
342
|
-
|
|
363
|
+
gp.buy);
|
|
343
364
|
gp = ngp;
|
|
344
365
|
} while(bp >= 1*portfolio[c.sym][1])
|
|
345
366
|
}
|
|
@@ -347,7 +368,7 @@ async function listOpens(portfolio = null, isFresh=false) {
|
|
|
347
368
|
}
|
|
348
369
|
}
|
|
349
370
|
// console.log(gPrices);
|
|
350
|
-
console.log(nexes,"orders
|
|
371
|
+
console.log(nexes,"orders did NOT require extension.");
|
|
351
372
|
// console.log(comps);
|
|
352
373
|
if(portfolio){
|
|
353
374
|
portfolio['O'] = opensA;
|
|
@@ -367,7 +388,7 @@ function getLev(portfolio,buysell,price,amt,xmrbtc,posP) {
|
|
|
367
388
|
lev = '2'; // on non-USD pairs.
|
|
368
389
|
} else {
|
|
369
390
|
portfolio['ZUSD'][2] -= price*amt;
|
|
370
|
-
}
|
|
391
|
+
}
|
|
371
392
|
} else {
|
|
372
393
|
if(price*1 < 1*portfolio[xmrbtc][1] && posP) return "Selling "+xmrbtc+" @ "+price+" isn't a limit order.";
|
|
373
394
|
//console.log("We have "+portfolio[xmrbtc][2]+" "+xmrbtc);
|
|
@@ -388,7 +409,7 @@ async function kill(o,oa) {
|
|
|
388
409
|
if(/^y/i.test(killAll)) {
|
|
389
410
|
let killed = await kapi('CancelAll');
|
|
390
411
|
console.log(314,killed);
|
|
391
|
-
} else { console.log("Maybe be more careful."); }
|
|
412
|
+
} else { console.log("Maybe be more careful."); }
|
|
392
413
|
return;
|
|
393
414
|
} else if(safeMode) {
|
|
394
415
|
console.log("In Safemode, so NOT killing "+o);
|
|
@@ -416,7 +437,7 @@ async function kill(o,oa) {
|
|
|
416
437
|
async function handleArgs(portfolio, args, uref = 0) {
|
|
417
438
|
if(/buy|sell/.test(args[0])) {
|
|
418
439
|
[buysell,xmrbtc,price,amt,posP] = args;
|
|
419
|
-
if(!/XMR|XBT|ETH|LTC|DASH|EOS|BCH|USDT/.test(xmrbtc)) return xmrbtc+" is not yet supported.";
|
|
440
|
+
if(!/XMR|XBT|ETH|LTC|DASH|EOS|BCH|USDT|UST|LUNA/.test(xmrbtc)) return xmrbtc+" is not yet supported.";
|
|
420
441
|
let total=price*amt;
|
|
421
442
|
if(total > 100000) return total+" is too much for code to "+buysell;
|
|
422
443
|
|
|
@@ -425,16 +446,16 @@ async function handleArgs(portfolio, args, uref = 0) {
|
|
|
425
446
|
// Do we need leverage?
|
|
426
447
|
// --------------------
|
|
427
448
|
let lev = getLev(portfolio,buysell,price,amt,xmrbtc,posP);
|
|
428
|
-
let cPrice = portfolio['G'][uref] ? portfolio['G'][uref][buysell=='buy'?'sell':'buy'] : 0;
|
|
449
|
+
let cPrice = !isNaN(portfolio['G'][uref]) ? portfolio['G'][uref][buysell=='buy'?'sell':'buy'] : 0;
|
|
429
450
|
// Without a record of a closing price, use the last one we found.
|
|
430
451
|
// ---------------------------------------------------------------
|
|
431
452
|
if(!cPrice) cPrice = portfolio[xmrbtc][1];
|
|
432
|
-
let closeO = posP ?
|
|
453
|
+
let closeO = posP ? cPrice : null;
|
|
433
454
|
let ret = await order(buysell,xmrbtc,price,amt,lev,uref,closeO);
|
|
434
455
|
console.log("New order: "+ret);
|
|
435
456
|
return;
|
|
436
457
|
} else if(args[0] == 'set') {
|
|
437
|
-
set(portfolio, args[1], args[2], args[3]);
|
|
458
|
+
await set(portfolio, args[1], args[2], args[3]);
|
|
438
459
|
} else if(args[0] == 'reset') {
|
|
439
460
|
portfolio['G'] = [];
|
|
440
461
|
await listOpens(portfolio);
|
|
@@ -454,7 +475,7 @@ async function handleArgs(portfolio, args, uref = 0) {
|
|
|
454
475
|
orders = [];
|
|
455
476
|
for( o in response.result.closed) {
|
|
456
477
|
let oo = response.result.closed[o];
|
|
457
|
-
if(oo.status=='closed') orders.push([o,response.result.closed[o]]);
|
|
478
|
+
if(oo.status=='closed') orders.push([o,response.result.closed[o]]);
|
|
458
479
|
}
|
|
459
480
|
args.pop();
|
|
460
481
|
}
|
|
@@ -475,7 +496,7 @@ async function handleArgs(portfolio, args, uref = 0) {
|
|
|
475
496
|
} else {
|
|
476
497
|
a = a[1][args[1]];
|
|
477
498
|
b = b[1][args[1]];
|
|
478
|
-
}
|
|
499
|
+
}
|
|
479
500
|
return isNaN(a)
|
|
480
501
|
? a.localeCompare(b)
|
|
481
502
|
: a - b;
|
|
@@ -485,7 +506,7 @@ async function handleArgs(portfolio, args, uref = 0) {
|
|
|
485
506
|
console.log(i+1, x[1].descr[args[1]]
|
|
486
507
|
? x[1].descr[args[1]] : x[1][args[1]],
|
|
487
508
|
ldo,x[1].userref,x[1].descr.close);
|
|
488
|
-
});
|
|
509
|
+
});
|
|
489
510
|
};
|
|
490
511
|
} else if(args[0] == 'test') {
|
|
491
512
|
// Put some test code here if you want
|
|
@@ -521,7 +542,7 @@ async function refnum(opensA,oid,newRef) {
|
|
|
521
542
|
await order(bs,sym,p,amt,lev,newRef);
|
|
522
543
|
} else {
|
|
523
544
|
console.log(oRef+" already has userref "+o.userref);
|
|
524
|
-
}
|
|
545
|
+
}
|
|
525
546
|
}
|
|
526
547
|
|
|
527
548
|
async function deleverage(opensA,oid,undo=false) {
|
|
@@ -544,11 +565,11 @@ async function deleverage(opensA,oid,undo=false) {
|
|
|
544
565
|
await order(o.descr.type,/^([A-Z]+)USD/.exec(o.descr.pair)[1],
|
|
545
566
|
o.descr.price,Math.round(10000*(Number(o.vol) - Number(o.vol_exec)))/10000,
|
|
546
567
|
(undo ? '2' : 'none'),o.userref);
|
|
547
|
-
} else {
|
|
568
|
+
} else {
|
|
548
569
|
await order(o.descr.type,/^([A-Z]+)USD/.exec(o.descr.pair)[1],
|
|
549
570
|
o.descr.price,Math.round(10000*(Number(o.vol) - Number(o.vol_exec)))/10000,
|
|
550
571
|
(undo ? '2' : 'none'),o.userref,
|
|
551
|
-
|
|
572
|
+
/[0-9.]+$/.exec(o.descr.close)[0] );
|
|
552
573
|
}
|
|
553
574
|
await kill(oid+1, opensA);
|
|
554
575
|
}
|
|
@@ -556,12 +577,12 @@ async function deleverage(opensA,oid,undo=false) {
|
|
|
556
577
|
|
|
557
578
|
function set(p,ur,type,price) {
|
|
558
579
|
if(ur) {
|
|
559
|
-
let gp = p['G'].find(g => g.userref==ur);
|
|
580
|
+
let gp = p['G'].find(g => g.userref==ur);
|
|
560
581
|
if(!gp) {
|
|
561
582
|
gp = {userref:Number(ur),buy:'?',sell:'?'};
|
|
562
583
|
p['G'].push(gp);
|
|
563
584
|
}
|
|
564
|
-
console.log(405,gp);
|
|
585
|
+
console.log(405,gp);
|
|
565
586
|
gp[type] = price;
|
|
566
587
|
}
|
|
567
588
|
p['G'].sort((a,b) => a.userref-b.userref);
|
|
@@ -569,33 +590,33 @@ function set(p,ur,type,price) {
|
|
|
569
590
|
p['G'].forEach(x => {
|
|
570
591
|
let f = toDec(((x.sell-x.buy)*Math.min(x.bought,x.sold)),2);
|
|
571
592
|
console.log(x.userref+': '+x.buy+'-'+x.sell
|
|
572
|
-
+ ((x.bought+x.sold)>0
|
|
593
|
+
+ ((x.bought+x.sold)>0
|
|
573
594
|
? (", bought "+toDec(x.bought,2)+" and sold "+toDec(x.sold,2)+' for ' + f)
|
|
574
595
|
: '' ));
|
|
575
596
|
if(!isNaN(f)) profits += f;
|
|
576
597
|
});
|
|
577
|
-
console.log("That's "+toDec(profits,2)+" since "+new Date(ts150*1000));
|
|
598
|
+
console.log("That's "+toDec(profits,2)+" since "+new Date(ts150*1000));
|
|
578
599
|
}
|
|
579
600
|
|
|
580
601
|
function toDec(n,places) {
|
|
581
602
|
let f = 10**places;
|
|
582
603
|
return Math.round(n*f)/f;
|
|
583
604
|
}
|
|
584
|
-
async function report(portfolio,showBalance=true) {
|
|
605
|
+
async function report(portfolio,showBalance=true) {
|
|
585
606
|
let dataPromise = [
|
|
586
607
|
'Balance',
|
|
587
|
-
['Ticker',{ pair :
|
|
608
|
+
['Ticker',{ pair : USDPairs.join() }],
|
|
588
609
|
'TradeBalance'
|
|
589
610
|
];
|
|
590
611
|
try {
|
|
591
612
|
dataPromise[0] = await kapi(dataPromise[0]);
|
|
592
613
|
dataPromise[1] = await kapi(dataPromise[1]);
|
|
593
614
|
dataPromise[2] = await kapi(dataPromise[2]);
|
|
594
|
-
} catch(err) {
|
|
615
|
+
} catch(err) {
|
|
595
616
|
catcher(411,err);
|
|
596
|
-
console.log(423,"Waiting a minute...");
|
|
617
|
+
console.log(423,"Waiting a minute...");
|
|
597
618
|
await sleep(60000);
|
|
598
|
-
return;
|
|
619
|
+
return;
|
|
599
620
|
}
|
|
600
621
|
let [bal,tik,trb] = dataPromise;
|
|
601
622
|
let mar = await marginReport(false);
|
|
@@ -614,7 +635,7 @@ async function report(portfolio,showBalance=true) {
|
|
|
614
635
|
else if(tsz in tik.result) price = tik.result[tsz].c[0];
|
|
615
636
|
price = toDec(price,(sym=='EOS'?4:2));
|
|
616
637
|
portfolio[sym]=[amt,price,amt]; // holdings w/reserves, price, holdings w/o reserves
|
|
617
|
-
if(mar[sym]) portfolio[sym][0] = toDec(portfolio[sym][0]+mar[sym].open,4);
|
|
638
|
+
if(mar[sym]) portfolio[sym][0] = toDec(portfolio[sym][0]+mar[sym].open,4);
|
|
618
639
|
if(showBalance) console.log(p+"\t"+w(portfolio[sym][0],16)+price);
|
|
619
640
|
}
|
|
620
641
|
if(showBalance) {
|
|
@@ -629,8 +650,8 @@ async function report(portfolio,showBalance=true) {
|
|
|
629
650
|
}
|
|
630
651
|
}
|
|
631
652
|
}
|
|
632
|
-
//console.log(portfolio);
|
|
633
|
-
console.log(new Date);
|
|
653
|
+
//console.log(portfolio);
|
|
654
|
+
console.log(new Date,' ',(auto>0?'A':'.')+(risky?'R':'.')+(safeMode?'S':'.'));
|
|
634
655
|
await listOpens(portfolio,true);
|
|
635
656
|
process.stdout.write("\033[A".repeat(cli.apl));
|
|
636
657
|
cli.apl = 2;
|
|
@@ -656,7 +677,6 @@ async function marginReport(show = true) {
|
|
|
656
677
|
return brief;
|
|
657
678
|
}
|
|
658
679
|
|
|
659
|
-
const prompt = require('prompt-sync')({sigint: true});
|
|
660
680
|
let stopNow = false,
|
|
661
681
|
portfolio = [],
|
|
662
682
|
histi = Math.floor(Date.now() / 1000),
|
|
@@ -667,75 +687,74 @@ let stopNow = false,
|
|
|
667
687
|
risky = false,
|
|
668
688
|
cmdList = [],
|
|
669
689
|
safeMode = true,
|
|
690
|
+
USDPairs = 'XBTUSD,XMRUSD,BCHUSD,DASHUSD,EOSUSD,ETHUSD,LTCUSD,USDTUSD,USTUSD,LUNAUSD'.split(',');
|
|
691
|
+
auto_on_hold = false;
|
|
670
692
|
cli = {'apl': 0};
|
|
671
693
|
async function runOnce(cmdList) {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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 && !auto_on_hold) {
|
|
719
|
+
await report(portfolio,false);
|
|
720
|
+
counter = delay;
|
|
687
721
|
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
? 'on - Experimental additions will be tried' : 'off'));
|
|
707
|
-
} else if(args[0] == "safe") {
|
|
708
|
-
safeMode = !safeMode;
|
|
709
|
-
console.log("Safe Mode is "+(safeMode
|
|
710
|
-
? 'on - Orders will be displayed butnot placed' : 'off'));
|
|
711
|
-
} else if(args[0] == "verbose") {
|
|
712
|
-
verbose = !verbose;
|
|
713
|
-
console.log("Verbose is "+(verbose ? 'on' : 'off'));
|
|
714
|
-
} else if(args[0] == 'margin') {
|
|
715
|
-
await marginReport();
|
|
716
|
-
} else await handleArgs(portfolio, args, ++histi).then(console.log);
|
|
717
|
-
} catch(err) {
|
|
718
|
-
catcher(468,err);
|
|
719
|
-
}
|
|
720
|
-
// Wait a sec for the nonce.
|
|
721
|
-
// -------------------------
|
|
722
|
-
await sleep(1000);
|
|
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, ++histi).then(console.log);
|
|
738
|
+
} catch(err) {
|
|
739
|
+
catcher(468,err);
|
|
723
740
|
}
|
|
724
|
-
//
|
|
725
|
-
//
|
|
726
|
-
|
|
727
|
-
|
|
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;
|
|
728
749
|
}
|
|
729
750
|
|
|
730
|
-
process.stdin.on('readable',
|
|
751
|
+
process.stdin.on('readable', () => {
|
|
731
752
|
// clearInterval(auto);
|
|
732
753
|
let cmd = '',
|
|
733
754
|
waiter = 0;
|
|
734
755
|
data = '';
|
|
735
756
|
while(null != (data = process.stdin.read())) cmd += data;
|
|
736
757
|
if(/^quit/.test(cmd)) {
|
|
737
|
-
console.log("Userref collisions possible with restart before "
|
|
738
|
-
+ new Date(histi * 1000));
|
|
739
758
|
process.exit(0);
|
|
740
759
|
} else {
|
|
741
760
|
clearTimeout(waiter);
|
|
@@ -743,7 +762,7 @@ process.stdin.on('readable', async () => {
|
|
|
743
762
|
// Do we need to stop this listener from listening while runOnce runs?
|
|
744
763
|
if(cmdList.length > 0) runOnce(cmdList).catch((err) => { catcher(496,err); });
|
|
745
764
|
cmdList = [];
|
|
746
|
-
},100);
|
|
765
|
+
},100);
|
|
747
766
|
cmdList.push(cmd);
|
|
748
767
|
}
|
|
749
768
|
});
|
|
@@ -759,13 +778,13 @@ process.on('uncaughtException', function (err) {
|
|
|
759
778
|
});
|
|
760
779
|
|
|
761
780
|
let kwsCheck;
|
|
762
|
-
function krakenSaid(obj) {
|
|
781
|
+
async function krakenSaid(obj) {
|
|
763
782
|
if(obj.event=='heartbeat') {
|
|
764
783
|
kwsCheck = new Date();
|
|
765
784
|
} else {
|
|
766
785
|
console.log(557,obj);
|
|
767
786
|
if(Array.isArray(obj)) {
|
|
768
|
-
runOnce(['report']).catch((err) => { catcher(543,err); });
|
|
787
|
+
await runOnce(['report']).catch((err) => { catcher(543,err); });
|
|
769
788
|
}
|
|
770
789
|
}
|
|
771
790
|
}
|