bitdo2
Version:
A cryptocurrency order-rule executor
240 lines (211 loc) • 5.67 kB
JavaScript
#!/usr/bin/env node
const blessed = require('blessed');
const contrib = require('blessed-contrib');
const chalk = require('chalk');
const moment = require('moment');
const _ = require('lodash');
const Exchange = require('./exchanges');
const config = require('./config');
const log = require('./log');
const format = require('./lib/format');
const MethodTransport = require('./lib/methodtransport');
const Promise = require('bluebird');
const HoldingsService = require('./services/holdings');
log.overrideConsole();
/* eslint arrow-body-style: off */
const screen = blessed.Screen({
smartCSR: true,
});
const header = blessed.Text({
top: 0,
left: '70%+1',
width: '40%-2',
height: 1,
content: 'BitDo2',
});
screen.append(header);
const clock = blessed.Text({
left: '100%-14',
width: 14,
height: 1,
});
screen.append(clock);
const holdingTable = contrib.table({
keys: true,
width: '70%',
height: '50%',
interactive: true,
label: 'Holdings',
columnWidth: [10, 10, 6, 14, 12, 12, 12, 12, 12, 12, 8],
columnSpacing: 4,
border: {
type: 'line',
},
fg: 'white',
});
screen.append(holdingTable);
const orderTable = blessed.ListTable({
width: '70%',
height: '50%',
top: '50%',
border: {
type: 'line',
},
style: {
header: {
bold: true,
bg: 'blue',
},
},
});
screen.append(orderTable);
const logpanel = blessed.Log({
width: '30%',
height: '100%-1',
left: '70%',
top: 1,
border: {
type: 'line',
},
style: {
border: {
fg: '#f0f0f0',
},
},
tags: true,
scrollback: 10000,
label: 'Log Messages',
});
screen.append(logpanel);
// Scren methods and control
// Clock ticker
setInterval(() => {
clock.content = chalk.cyan(moment().format('LTS'));
screen.render();
}, 1000);
screen.key(['C-c'], () => {
process.exit(0);
});
log.add(MethodTransport, {
cb: msg => {
logpanel.log(msg);
screen.render();
},
});
// Processing and display
function directionalColor(val) {
if (val > 0.0)
return chalk.greenBright;
if (val < 0.0)
return chalk.redBright;
return chalk.yellow;
}
// Set up exchanges
const exchanges = Exchange.createFromConfig(config.exchanges);
const holdingsService = new HoldingsService(exchanges);
const HOLDING_DELTA_HISTORY = {};
function updateHoldings() {
log.info('Updating holdings...');
return holdingsService.getHoldings()
.then(holdings => _.orderBy(holdings, h => h.conversions.USD, 'desc'))
.then(holdings => {
const sums = { BTC: 0, USD: 0 };
const data = _.map(holdings, v => {
const key = `${v.exchange.name}:${v.currency}`;
const lastHistoryPrice = _.get(HOLDING_DELTA_HISTORY, key, v.conversions.USD);
const delta = v.conversions.USD - lastHistoryPrice;
HOLDING_DELTA_HISTORY[key] = (lastHistoryPrice * 3 + v.conversions.USD) / 4.0;
sums.BTC += v.conversions.BTC;
sums.USD += v.conversions.USD;
return [
moment(v.updatedAt).format('Do hA'),
v.exchange.name,
v.currency,
v.detail || '',
v.ticker.BTC ? format.number(v.ticker.BTC) : '',
chalk.yellow(format.number(v.ticker.USD)),
chalk.bold.blueBright(format.number(v.balance)),
format.number(v.hold),
format.number(v.conversions.BTC),
chalk.blue(format.number(v.conversions.USD)),
directionalColor(delta)(format.number(delta)),
];
});
data.unshift([]);
data.unshift(['', 'Total', '', '', '', '', '', '', format.number(sums.BTC), format.number(sums.USD)]);
holdingTable.setData({
headers: ['Updated', 'Exch', 'Sym', 'Detail', 'BTC', 'Last USD', 'Owned', 'Hold', 'BTC', 'USD', 'EMA-D'],
data,
});
screen.render();
})
.catch(err => {
log.error(err.message);
});
}
function updateOrders() {
log.info('Updating orders...');
return Promise.map(exchanges, exchange => exchange.getOrders())
.then(_.flatten)
.then(orders => {
const rows = _.flatten([
_.map(_.orderBy(_.filter(orders, x => x.status === 'O'), x => x.date, 'desc'), order => {
return [
order.status,
moment(order.date).format('M/D H:mm'),
order.exchange.name,
order.product,
order.side,
order.type,
format.number(order.size),
format.number(order.price),
'N/A',
];
}),
[[]],
_.map(_.orderBy(_.filter(orders, x => x.status !== 'O'), x => x.date, 'desc'), order => {
return [
order.status,
moment(order.date).format('M/D H:mm'),
order.exchange.name,
order.product,
order.side,
order.type,
format.number(order.size),
format.number(order.price),
format.number(order.fee),
];
}),
]);
rows.unshift(['', 'Created', 'Exchange', 'Product', 'Side', 'Type', 'Size', 'Exec Price', 'Fee']);
orderTable.setData(rows);
screen.render();
})
.catch(err => {
log.error(err.message);
});
}
// Update
let isUpdating = false;
function update() {
if (isUpdating) {
log.warn('Already updating, will not update again');
return;
}
isUpdating = true;
Promise.all([
updateHoldings(),
updateOrders(),
]).then(() => {
log.info('Update complete');
}).catch(err => {
log.warn(`Error updating: ${err.message}`);
}).finally(() => {
isUpdating = false;
});
}
setInterval(() => {
update();
}, 60000);
update();
screen.key(['f5', 'r'], update);