Skip to content

Commit 854388d

Browse files
authored
Merge pull request #50 from dscotese/1.2.14Taxes
1.2.14 taxes
2 parents c6cf763 + d2be2c5 commit 854388d

File tree

7 files changed

+427
-196
lines changed

7 files changed

+427
-196
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ XLTC ...
206206
...
207207
```
208208
2. Retrieves the list of open orders, which is immediately processed to:
209-
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).
209+
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). Note: If you place a conditional close order with a price that matches one side of an existing grid point and it executes, creating the conditional close, that new order will be added in with the others, increasing the size of the trade around that grid point.
210210
2. fill out the internal record of buy/sell prices using the open orders and their conditional closes (see [set](#set) and [reset](#reset)).
211211
3. extend the grid if there are only buys or only sells remaining for the crypto identified in each order.
212212
4. identify any orders that are gone or new using Kraken's Order ID and for new orders, it also describes them.

balancer.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ module.exports = function Balancer (target) {
126126
bot.getLev(bot.portfolio,'sell',sP,sAmt,po.base,false),
127127
p.type=='buy'?Ordered.uref:0,p.price);
128128
}
129-
console.log({high,low,cryptp,qf,atargU,atargD,curVal,curP,toSell,toBuy,sAmt,bAmt,sP,bP,Ordered});
129+
if(bot.FLAGS.verbose)
130+
console.log({high,low,cryptp,qf,atargU,atargD,curVal,curP,toSell,toBuy,sAmt,bAmt,sP,bP,Ordered});
130131
}
131132

132133
return {setTrades};

bot.js

Lines changed: 265 additions & 158 deletions
Large diffs are not rendered by default.

init.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
#!/usr/bin/env node
22
process.TESTING = process.TESTING || !(/init.js$|kraken-grid$/.test(process.argv[1]));
3-
console.log("filename is ",process.argv[1],"in "+(process.TESTING?'test':'production')+" mode.");
3+
if(process.argv.length > 2) process.TESTING = process.argv[2];
4+
console.log("filename is ",process.argv[1],"in "+(process.TESTING
5+
? 'test ('+process.TESTING+")"
6+
:'production')+" mode.");
47
const Manager = require('./manager.js');
58
const Bot = require('./bot.js');
69
const Web = require('./web.js');
710
let bot = Bot();
811
let man = Manager(bot);
912
//let web = Web(manager);
10-
if(!process.TESTING) man.init();
13+
man.init();
1114
// else manager.init();
1215
// For testing purposes, we provide these two items
1316
// They will not be ready for use until manager.init is done,

manager.js

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function Manager(b) {
4040
savings = require('./savings.js');
4141
/*if(!process.TESTING)*/ await bot.report(true);
4242
savings().setTickers(Bot.tickers);
43-
if(!process.TESTING) listen();
43+
if(!process.TESTING || process.argv.length > 2) listen();
4444
else {
4545
console.log("42:",portfolio);
4646
this.already = true;
@@ -95,19 +95,19 @@ function Manager(b) {
9595
Note that handleArgs handles string arguments as collected from process.stdin.
9696
This means that true and 1, as args, are strings, not a boolean and a number.
9797
*/
98-
async function handleArgs(bot, portfolio, args, uref = 0) {
98+
async function handleArgs(bot, args, uref = 0, p = portfolio) {
9999
if(['args',whoami()].includes(process.TESTING))
100100
console.log(whoami(),"called with ",arguments);
101101
let buysell,xmrbtc,price,amt,posP;
102102
if(/^(buy|sell)$/.test(args[0])) {
103103
let pair;
104104
[buysell,xmrbtc,price,amt,posP] = args;
105-
pair = Bot.findPair(xmrbtc, portfolio.Numeraire, -1);
105+
pair = Bot.findPair(xmrbtc, p.Numeraire, -1);
106106
if(pair) xmrbtc = pair[1].base;
107-
if( 'undefined' == typeof(portfolio[xmrbtc]) ) {
107+
if( 'undefined' == typeof(p[xmrbtc]) ) {
108108
// Try the asset's altname
109109
// -----------------------
110-
if('undefined' == typeof(portfolio[Bot.alts[xmrbtc]]))
110+
if('undefined' == typeof(p[Bot.alts[xmrbtc]]))
111111
throw new Error(xmrbtc+" is not a recognized symbol. Try 'asset' command.");
112112
console.log("Using",Bot.alts[xmrbtc],"instead of",xmrbtc);
113113
xmrbtc = Bot.alts[xmrbtc];
@@ -119,12 +119,12 @@ function Manager(b) {
119119

120120
// Do we need leverage?
121121
// --------------------
122-
let lev = bot.getLev(portfolio,buysell,price,amt,xmrbtc,posP);
123-
let cPrice = !isNaN(portfolio['G'][uref])
124-
? portfolio['G'][uref][buysell=='buy'?'sell':'buy'] : 0;
122+
let lev = bot.getLev(p,buysell,price,amt,xmrbtc,posP);
123+
let cPrice = !isNaN(p['G'][uref])
124+
? p['G'][uref][buysell=='buy'?'sell':'buy'] : 0;
125125
// Without a record of a closing price, use the last one we found.
126126
// ---------------------------------------------------------------
127-
if(!cPrice) cPrice = portfolio[xmrbtc][1];
127+
if(!cPrice) cPrice = p[xmrbtc][1];
128128
// When passing 1 as close, it will mean close at 1 (if Risky)
129129
// or at current price (without Risky)
130130
// -----------------------------------------------------------
@@ -135,16 +135,16 @@ function Manager(b) {
135135
console.log("New order: "+ret);
136136
return;
137137
} else if(args[0] == 'set') {
138-
await bot.set(portfolio, args[1], args[2], args[3]);
138+
await bot.set(p, args[1], args[2], args[3]);
139139
} else if(args[0] == 'reset') {
140-
portfolio['G'] = [];
141-
await bot.listOpens(portfolio);
140+
p['G'] = [];
141+
await bot.listOpens();
142142
} else if(args[0] == 'delev') {
143-
await bot.deleverage(portfolio['O'],args[1]-1);
143+
await bot.deleverage(p['O'],args[1]-1);
144144
} else if(args[0] == 'addlev') {
145-
await bot.deleverage(portfolio['O'],args[1]-1,true);
145+
await bot.deleverage(p['O'],args[1]-1,true);
146146
} else if(args[0] == 'refnum') {
147-
await bot.refnum(portfolio['O'],args[1]-1,args[2]);
147+
await bot.refnum(p['O'],args[1]-1,args[2]);
148148
} else if(args[0] == 'list') {
149149
await bot.list(args);
150150
} else if(/^(less|more)$/.test(args[0])) {
@@ -159,7 +159,7 @@ function Manager(b) {
159159
}
160160
let label = args[3] ? args[3] : "default",
161161
tkr = 'REMOVE' == args[1] ? args[2] : args[1],
162-
account = portfolio.Savings.find(a => a.label == label);
162+
account = p.Savings.find(a => a.label == label);
163163
if(Bot.alts[tkr]) tkr = Bot.alts[tkr];
164164
if(!account) {
165165
if('REMOVE' == args[1]) {
@@ -175,15 +175,15 @@ function Manager(b) {
175175
assets:[{ticker:tkr,amount:Number(args[2])}]
176176
});
177177
console.log("Created new account, ", label);
178-
portfolio.Savings.push(JSON.parse(account.save()));
178+
p.Savings.push(JSON.parse(account.save()));
179179
if(args[3]) account.labelMe(args[3]);
180180
} else {
181181
account = savings(account);
182182
}
183183
if('REMOVE' == args[1]) {
184184
if('ACCOUNT' == args[2].toUpperCase()) {
185-
let smaller = portfolio.Savings.filter(x => x.label != args[3]);
186-
portfolio.Savings = smaller;
185+
let smaller = p.Savings.filter(x => x.label != args[3]);
186+
p.Savings = smaller;
187187
bot.save();
188188
console.log("Account",label,"has been removed.");
189189
} else {
@@ -200,10 +200,10 @@ function Manager(b) {
200200
bot.save();
201201
}
202202
} else if(args[0] == 'assets') {
203-
let sav,pnum = portfolio.Numeraire;
204-
ret = portfolio.Savings.length + ': ';
205-
for(h=0;h < portfolio.Savings.length; ++h) {
206-
sav = savings(portfolio.Savings[h]);
203+
let sav,pnum = p.Numeraire;
204+
ret = p.Savings.length + ': ';
205+
for(h=0;h < p.Savings.length; ++h) {
206+
sav = savings(p.Savings[h]);
207207
if(!(args[1]) || sav.label == args[1])
208208
console.log(h, sav.list());
209209
else if(args[1]) {
@@ -214,7 +214,7 @@ function Manager(b) {
214214
// Include the assets on the Exchange
215215
// ----------------------------------
216216
sav = savings({ label:'OnExchange', assets:
217-
[{ticker:pnum, amount:portfolio[pnum][0]}]});
217+
[{ticker:pnum, amount:p[pnum][0]}]});
218218
return ret;
219219
} else if(args[0] == 'allocation') {
220220
let d = await getAllocation(true),c; // Desired
@@ -285,8 +285,8 @@ function Manager(b) {
285285
if('undefined' == typeof(alloc)
286286
|| isNaN(p) || isNaN(a)) {
287287
console.log(usage);
288-
} else if(a<0||p<0||a>100*alloc.target||p>100) {
289-
console.log("apct must be between 0 and",100*alloc.target,
288+
} else if(a<0||p<0||a>100*(1-alloc.target)||p>100) {
289+
console.log("apct must be between 0 and",100-100*alloc.target,
290290
"and ppct must be between zero and 100.");
291291
} else {
292292
t = alloc.ticker;
@@ -297,14 +297,14 @@ function Manager(b) {
297297
if(args.length < 3 || isNaN(args[1]) || isNaN(args[2])) {
298298
console.log("Usage: limits AtLeast AtMost\n" +
299299
"The allocation command will make trades only if the\n" +
300-
"amount in "+portfolio.Numeraire+" is at least the \n" +
300+
"amount in "+p.Numeraire+" is at least the \n" +
301301
"AtLeast amount and no more than the AtMost amount.\n" +
302302
"If AtMost is -1, there is no upper limit (dangerous!).");
303303
} else if(Number(args[1]) > Number(args[2]) && -1 != Number(args[2])) {
304304
console.log("Doing nothing becuase you seem to have\n" +
305305
"switched the arguments.");
306306
} else {
307-
portfolio.limits = [Number(args[1]),Number(args[2])];
307+
p.limits = [Number(args[1]),Number(args[2])];
308308
bot.save();
309309
}
310310
} else if(args[0] == 'test') {
@@ -336,7 +336,7 @@ function Manager(b) {
336336
let args = cmds[cdx++].split(' ').map((x) => { return x.trim(); });
337337
console.log("...("+cdx+")> "+args.join(' '));
338338
//try {
339-
if(args[0] == 'kill') await bot.kill(args[1],bot.portfolio['O']);
339+
if(args[0] == 'kill') await bot.kill(args[1],portfolio['O']);
340340
else if(args[0] == "keys") { await bot.keys(); }
341341
else if(args[0] == "ws") {
342342
if(kwsCheck) console.log("Kraken WebSocket heartbeat at "+kwsCheck);
@@ -379,9 +379,9 @@ function Manager(b) {
379379
} else if(args[0] == 'margin') {
380380
await bot.marginReport();
381381
} else if(args[0] == 'show') {
382-
if(args[2]) console.log(args[2],{value:bot.portfolio[args[1]][args[2]]});
383-
if(args[1]) console.log(args[1],{value:bot.portfolio[args[1]]});
384-
else console.log({Portfolio:bot.portfolio,Bot});
382+
if(args[2]) console.log(args[2],{value:portfolio[args[1]][args[2]]});
383+
else if(args[1]) console.log(args[1],{value:portfolio[args[1]]});
384+
else console.log({Portfolio:portfolio,Bot});
385385
} else if(args[0] == 'web') {
386386
if(args[1]) {
387387
if(args[1].toUpperCase() == 'ON')
@@ -392,7 +392,7 @@ function Manager(b) {
392392
+ "This starts the web interface or stops it.");
393393
}
394394
} else {
395-
ret = await handleArgs(bot, bot.portfolio, args, 0);
395+
ret = await handleArgs(bot, args, 0);
396396
if(bot.FLAGS.verbose) console.log(ret);
397397
}
398398
//}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "kraken-grid",
3-
"version": "1.2.13",
3+
"version": "1.2.14",
44
"description": "Bot repeatedly buys & sells on kraken from a conditional close order.",
55
"main": "init.js",
66
"bin": "./init.js",

reports.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
function Reports(bot) {
2+
let offset = 0,
3+
closed = {offset:0, forward:false, orders:{}};
4+
const TxIDPattern = /[0-9A-Z]{6}-[0-9A-Z]{5}-[0-9A-Z]{6}/;
5+
6+
// This function will collect count executed orders in reverse chronological order,
7+
// first the most recent (ofs=0) and then earlier history (ofs from known).
8+
async function getExecuted(count, known = {}) {
9+
if(!known.hasOwnProperty('orders')) { // Is old format or not collected yet
10+
known = {orders:{}};
11+
console.log("known passed has no 'orders' property.");
12+
}
13+
console.log("Known:",Object.keys(known.orders).length,
14+
"Closed:",Object.keys(closed.orders).length, [known.offset,closed.offset]);
15+
if(Object.keys(known.orders).length > Object.keys(closed.orders).length)
16+
Object.assign(closed,known);
17+
while(count > 0 && offset > -1) {
18+
let mixed = await bot.kapi(['ClosedOrders',{ofs:offset, closetime:'close'}]),
19+
total = mixed.result.count;
20+
if(mixed.error.length > 0) {
21+
console.log("Errors:\n",mixed.error.join("\n"));
22+
}
23+
console.log("At",offset,"of",total,"results.");
24+
// If more orders closed since our previous call, they will
25+
// push everything further down the list and cause some old
26+
// orders to be reported again. These duplicates do not
27+
// cause a problem when the list of closed orders is an object
28+
// because the later assignments to the TxID (key) overwrite
29+
// the earlier ones.
30+
// ClosedOrders might return pending, open canceled, and expired
31+
// orders too. Remove them.
32+
// ------------------------------------------------------
33+
let executed = Object.entries(mixed.result.closed).filter((e) =>
34+
(e[1].status == 'closed')),
35+
rCount = Object.keys(mixed.result.closed).length,
36+
elen = executed.length;
37+
offset += rCount;
38+
// If offset >= total, we have collected the earliest order
39+
// so we can collect only new ones (returned first) from now
40+
// on. This is what it means when offset on disk is -1.
41+
// If known.offset is -1 then the only orders left to collect
42+
// are the newest ones, and if the first one we get is already
43+
// in known.orders, then there are no new ones and we have everything.
44+
// -------------------------------------------------------------------
45+
closed.offset = ((offset >= total
46+
|| (known.orders.hasOwnProperty(executed[0][0]) && known.offset == -1))
47+
? -1 : offset);
48+
count -= elen;
49+
console.log("Retrieved",elen,"executed orders.");
50+
const KRAKEN_GCO_MAX = 50;
51+
Object.assign(closed.orders, Object.fromEntries(executed));
52+
if(rCount < KRAKEN_GCO_MAX) { // We must have reached the earliest order.
53+
if(closed.offset > -1) // Should be impossible, so...
54+
throw(offset+" still < "+total+" API returns < 50");
55+
console.log("Total Executed orders collected: "
56+
+(Object.keys(closed.orders).length));
57+
count = 0;
58+
} else if(Object.keys(known.orders || {}).includes(executed[executed.length-1][0])) {
59+
// Last order retrieved already on disk
60+
console.log("Jumping to the end... ("+known.offset+")");
61+
offset = known.offset; // so jump to the end.
62+
closed.offset = offset;
63+
}
64+
}
65+
closed = keyOrder(closed);
66+
// We set offset to 0 when done because we must ask for more
67+
// orders since some may have executed since the previous call.
68+
offset = offset == -1 ? 0 : offset;
69+
return closed;
70+
}
71+
72+
// This function adds an iterator and the "forward" key to iterate
73+
// through the orders property backwards or forwards.
74+
// ---------------------------------------------------------------
75+
function keyOrder(keyed, pattern = TxIDPattern) {
76+
keyed['forward'] = true;
77+
let keys = Object.keys(keyed.orders),
78+
K = keys.filter((x) => (pattern.test(x)));
79+
if( keys.length > K.length ) { // Keys that don't match.
80+
console.log("Ignoring",(keys.length - K.length,"non-matching keys:"));
81+
console.log(keys.filter((x) => (!pattern.test(x))));
82+
}
83+
84+
K.sort((a,b) => (keyed.orders[a].closetm - keyed.orders[b].closetm));
85+
let Kr = K.toReversed();
86+
87+
keyed.orders[Symbol.iterator] = function* () {
88+
for( const key of (keyed.forward ? K : Kr) ) yield key;
89+
}
90+
return keyed;
91+
}
92+
93+
function yearStart() {
94+
const now = new Date();
95+
const yearStart = new Date(now.getFullYear(), 0, 1);
96+
return yearStart.getTime();
97+
}
98+
99+
// This ensures all orders have been retrieved
100+
// and provides whatever information it can about the process.
101+
async function capGains() {
102+
let started = Date.now();
103+
let notBefore = yearStart();
104+
// Let's not go back before the beginning of the year
105+
while(closed[closed.length].opentm > notBefore/1000) {
106+
await getExecuted();
107+
if( Date.now() - started > 5 ) {
108+
console.log("I'm stopping after five seconds.");
109+
return;
110+
}
111+
}
112+
console.log("I've collected",closed.length,
113+
" orders, and that goes back to ",
114+
Date(closed[closed.length].opentm));
115+
}
116+
117+
return {getExecuted, capGains};
118+
}
119+
120+
module.exports = Reports;

0 commit comments

Comments
 (0)