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.
Files changed (3) hide show
  1. package/README.md +46 -31
  2. package/index.js +149 -130
  3. 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
- I would like `npm install kraken-grid` to work, and it may work simply because the code is here on Github. If not, perhaps someone who knows how to make it work will explain that to me. Assuming that doesn't work yet, place the file kraken-grid.js into a folder and then call `node kraken-grid.js` from a console using that folder as its working directory.
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
- Once the file (kraken-grid.js) is installed with the dependencies (NPM and/or Node figures this out), you can run it and it will tell you to save a file `keys.js` to the parent folder after adding your API keys from Kraken to it:
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|BTC|ETH|DASH|EOS|LTC|BCH] price amount makeClose`
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
- * `Amount` is the number of coins
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
- * The bracketed item will be missing for an order with default leverage or an order without a conditional close.
56
- * `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 order.
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. ***NOTE: See Internals below to understand how using `buy` or `sell` can block this repetition.***
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. Interestingly, I think `list` will still show it unless you run `report` first to update the internal record of open 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 shows 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. ***NOTE: The new order often (or always?) appears at the top of `list` after this, so the `Counter`s identifying other orders may change.
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
- ### manual
77
- This stops the automatic calling of `report`. The bot will do nothing until you give it a new command.
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
- When you place an order through trade.kraken.com or through kraken.com, it will have a `userref` of zero. It will be displayed but ignored for the purposes of the grid trading it does. When you place an order through the bot, it will have a userref and the bot will assume you have not yet decided at what price to close it. It will ask _every time_ `report` is called, for example, from `auto` and it will do nothing until you answer. It will ask for every order that doesn't have a conditional close, and fill out the internal record of prices using your answer. To skip the rest of its requests for prices, answer with a question mark. If you're still running `auto`, however, it's going to ask again in N seconds.
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
- ### Handling pasted commands
89
- I use Excel to calculate my grid prices so I like to copy the commands that Excel builds for me. I thought Node would accept multiline iput as several pieces of input but it doesn't. They are all combined into one large input string that I first broke up using CHR(13) (whatever that was in Javascript, I can't remember now), but process.stdin handles it a little differently. Search for `readable` to see how I handled it.
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('../keys.js')) {
6
- console.log("Paste your key and secret between the single quotes and save this"
7
- +" to the parent folder as keys.js:\nexports.key='';\nexports.secret='';");
8
- process.exit(1);
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
- const my = require('../keys.js');
11
- const key = my.key; // API Key
12
- const secret = my.secret; // API Private Key
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
- if(closeO) closeO.price = Number(closeO.price);
56
- price = Number(price);
57
- amt = Number(amt);
58
- ret = '';
59
- if( closeO && closeO.price == price ) closeO = null;
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 "+amt+" "+xmrbtc+" at "+price+" with leverage "+lev
62
- +(!closeO || closeO.price == price ? "" : " to close at "+closeO.price));
63
- // let ordered;
64
- if(!safeMode) {
65
- let response = await kapi(['AddOrder',
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: price,
71
- volume: amt,
81
+ price: p,
82
+ volume: a,
72
83
  leverage: lev,
73
- close: closeO
74
- }]);
75
- console.log(40,response ? ((d = response.result)
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 bookeans, buy and sell.
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,{ordertype:'limit',price:c.open});
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 -= 10000000;
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
- {ordertype:'limit',price:gp.sell});
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 -= 1000000;
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
- {ordertype:'limit',price:gp.buy});
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 didn't require extension.");
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 ? { ordertype: 'limit', price: cPrice } : null;
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
- { ordertype: 'limit', price: /[0-9.]+$/.exec(o.descr.close)[0] });
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 : 'XBTUSD,XMRUSD,BCHUSD,DASHUSD,EOSUSD,ETHUSD,LTCUSD,USDTUSD' }],
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
- //while(!stopNow) {
673
- // if(cmd==null) cmd = prompt((auto>0 ? '('+delay+' min.)' : 'manual')+'>');
674
- let cmds = cmdList.map((x) => { return x.trim(); }),
675
- cdx = 0;
676
-
677
- console.log("Got "+(cmds.length)+" commands...");
678
- while(cdx < cmds.length) {
679
- let args = cmds[cdx++].split(' ').map((x) => { return x.trim(); });
680
- console.log("...("+cdx+")> "+args.join(' '));
681
- try {
682
- if(args[0] == 'kill') await kill(args[1],portfolio['O']);
683
- else if(args[0] == "ws") {
684
- if(kwsCheck) console.log("Kraken WebSocket heartbeat at "+kwsCheck);
685
- if(!kwsCheck || (new Date()).valueOf() > 10000+kwsCheck.valueOf()) {
686
- openSocket();
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
- } else if(args[0] == "report" || args[0] == "") await report(portfolio);
689
- else if(/^(manual)$/.test(args[0])) {
690
- clearInterval(auto);
691
- auto = 0;
692
- } else if(args[0] == "auto") {
693
- clearInterval(auto);
694
- if(args[1]&&!isNaN(args[1])) delay = args[1];
695
- let counter = delay;
696
- auto = setInterval(() => {
697
- if(0 == --counter) {
698
- report(portfolio,false);
699
- counter = delay;
700
- }
701
- },1000);
702
- await report(portfolio);
703
- } else if(args[0] == "risky") {
704
- risky = !risky;
705
- console.log("Risky Mode is "+(risky
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
- //console.log("Try CRTL-C while I sleep for a minute...");
725
- //await sleep(1000);
726
- cmd = null;
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', async () => {
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kraken-grid",
3
- "version": "1.0.1",
3
+ "version": "1.0.4",
4
4
  "description": "Bot repeatedly buys & sells on kraken from a conditional close order.",
5
5
  "main": "index.js",
6
6
  "bin": "./index.js",