diff --git a/commands/trade.js b/commands/trade.js index b2aea91532..a24731570d 100644 --- a/commands/trade.js +++ b/commands/trade.js @@ -120,47 +120,44 @@ module.exports = function (program, conf) { keyMap.set('d', 'dump statistical output to HTML file'.grey) keyMap.set('D', 'toggle automatic HTML dump to file'.grey) + var pushStr = '' + function listKeys() { - console.log('\nAvailable command keys:') + printLog('Available command keys:', true) keyMap.forEach((value, key) => { - console.log(' ' + key + ' - ' + value) + printLog(' ' + key + ' - ' + value) }) } function listOptions () { - console.log() - console.log(s.exchange.name.toUpperCase() + ' exchange active trading options:'.grey) - console.log() - process.stdout.write(z(22, 'STRATEGY'.grey, ' ') + '\t' + so.strategy + '\t' + (require(`../extensions/strategies/${so.strategy}/strategy`).description).grey) - console.log('\n') - process.stdout.write([ + printLog(s.exchange.name.toUpperCase() + ' exchange active trading options:'.grey, true) + printLog(z(22, 'STRATEGY'.grey, ' ') + '\t' + so.strategy + '\t' + (require(`../extensions/strategies/${so.strategy}/strategy`).description).grey, true) + printLog([ z(24, (so.mode === 'paper' ? so.mode.toUpperCase() : so.mode.toUpperCase()) + ' MODE'.grey, ' '), z(26, 'PERIOD'.grey, ' '), z(30, 'ORDER TYPE'.grey, ' '), z(28, 'SLIPPAGE'.grey, ' '), z(33, 'EXCHANGE FEES'.grey, ' ') - ].join('') + '\n') - process.stdout.write([ + ].join(''), true) + printLog([ z(15, (so.mode === 'paper' ? ' ' : (so.mode === 'live' && (so.manual === false || typeof so.manual === 'undefined')) ? ' ' + 'AUTO'.black.bgRed + ' ' : ' ' + 'MANUAL'.black.bgGreen + ' '), ' '), z(13, so.period_length, ' '), z(29, (so.order_type === 'maker' ? so.order_type.toUpperCase().green : so.order_type.toUpperCase().red), ' '), z(31, (so.mode === 'paper' ? 'avg. '.grey + so.avg_slippage_pct + '%' : 'max '.grey + so.max_slippage_pct + '%'), ' '), z(20, (so.order_type === 'maker' ? so.order_type + ' ' + s.exchange.makerFee : so.order_type + ' ' + s.exchange.takerFee), ' ') - ].join('') + '\n') - process.stdout.write('') - process.stdout.write([ + ].join('')) + printLog([ z(19, 'BUY %'.grey, ' '), z(20, 'SELL %'.grey, ' '), z(35, 'TRAILING STOP %'.grey, ' '), z(33, 'TRAILING DISTANCE %'.grey, ' ') - ].join('') + '\n') - process.stdout.write([ + ].join('')) + printLog([ z(9, so.buy_pct + '%', ' '), z(9, so.sell_pct + '%', ' '), z(20, so.profit_stop_enable_pct + '%', ' '), z(20, so.profit_stop_pct + '%', ' ') - ].join('') + '\n') - process.stdout.write('') + ].join('')) } /* Implementing statistical Exit */ @@ -226,7 +223,7 @@ module.exports = function (program, conf) { } if (!statsonly) { output_lines.forEach(function (line) { - console.log(line) + printLog(line) }) } if (quit || dump) { @@ -275,9 +272,9 @@ module.exports = function (program, conf) { function toggleStats(){ shouldSaveStats = !shouldSaveStats if(shouldSaveStats) - console.log('Auto stats dump enabled') + printLog('Auto stats dump enabled') else - console.log('Auto stats dump disabled') + printLog('Auto stats dump disabled') } function saveStatsLoop(){ @@ -368,6 +365,79 @@ module.exports = function (program, conf) { } + function printLog(str, cr = false) { + if (str) { + console.log((cr?'\n':'') + str) + pushStr += str + '\n' + } + } + + function executeCommand(command) { + var info = { ctrl: false } + if (conf.debug) { + console.log('\nCommand received: ' + command) + } + executeKey(command, info) + } + + function executeKey(key, info) { + if (key === 'l') { + listKeys() + } else if (key === 'b' && !info.ctrl ) { + engine.executeSignal('buy') + printLog('manual'.grey + ' limit ' + 'BUY'.green + ' command executed'.grey, true) + } else if (key === 'B' && !info.ctrl) { + engine.executeSignal('buy', null, null, false, true) + printLog('manual'.grey + ' market ' + 'BUY'.green + ' command executed'.grey, true) + } else if (key === 's' && !info.ctrl) { + engine.executeSignal('sell') + printLog('manual'.grey + ' limit ' + 'SELL'.red + ' command executed'.grey, true) + } else if (key === 'S' && !info.ctrl) { + engine.executeSignal('sell', null, null, false, true) + printLog('manual'.grey + ' market ' + 'SELL'.red + ' command executed'.grey, true) + } else if ((key === 'c' || key === 'C') && !info.ctrl) { + delete s.buy_order + delete s.sell_order + printLog('manual'.grey + ' order cancel' + ' command executed'.grey, true) + } else if (key === 'm' && !info.ctrl && so.mode === 'live') { + so.manual = !so.manual + printLog('MANUAL trade in LIVE mode: ' + (so.manual ? 'ON'.green.inverse : 'OFF'.red.inverse), true) + } else if (key === 'T' && !info.ctrl) { + so.order_type = 'taker' + printLog('Taker fees activated'.bgRed, true) + } else if (key === 'M' && !info.ctrl) { + so.order_type = 'maker' + printLog('Maker fees activated'.black.bgGreen, true) + } else if (key === 'o' && !info.ctrl) { + listOptions() + } else if (key === 'O' && !info.ctrl) { + printLog(cliff.inspect(so), true) + } else if (key === 'P' && !info.ctrl) { + printLog('Writing statistics...'.grey, true) + printTrade(false) + } else if (key === 'X' && !info.ctrl) { + printLog('Exiting... ' + '\nWriting statistics...'.grey, true) + printTrade(true) + } else if (key === 'd' && !info.ctrl) { + printLog('Dumping statistics...'.grey, true) + printTrade(false, true) + } else if (key === 'D' && !info.ctrl) { + printLog('Dumping statistics...'.grey, true) + toggleStats() + } else if (key === 'L' && !info.ctrl) { + debug.flip() + printLog('DEBUG mode: ' + (debug.on ? 'ON'.green.inverse : 'OFF'.red.inverse), true) + } else if (info.name === 'c' && info.ctrl) { + // @todo: cancel open orders before exit + process.exit() + } + + if (pushStr) { + engine.pushMessage('Reply', colors.stripColors(pushStr)) + pushStr = '' + } + } + var order_types = ['maker', 'taker'] if (!order_types.includes(so.order_type)) { so.order_type = 'maker' @@ -483,62 +553,13 @@ module.exports = function (program, conf) { forwardScan() setInterval(forwardScan, so.poll_trades) + if (!so.non_interactive) { + engine.onMessage(executeCommand) + } readline.emitKeypressEvents(process.stdin) if (!so.non_interactive && process.stdin.setRawMode) { process.stdin.setRawMode(true) - process.stdin.on('keypress', function (key, info) { - if (key === 'l') { - listKeys() - } else if (key === 'b' && !info.ctrl ) { - engine.executeSignal('buy') - console.log('\nmanual'.grey + ' limit ' + 'BUY'.green + ' command executed'.grey) - } else if (key === 'B' && !info.ctrl) { - engine.executeSignal('buy', null, null, false, true) - console.log('\nmanual'.grey + ' market ' + 'BUY'.green + ' command executed'.grey) - } else if (key === 's' && !info.ctrl) { - engine.executeSignal('sell') - console.log('\nmanual'.grey + ' limit ' + 'SELL'.red + ' command executed'.grey) - } else if (key === 'S' && !info.ctrl) { - engine.executeSignal('sell', null, null, false, true) - console.log('\nmanual'.grey + ' market ' + 'SELL'.red + ' command executed'.grey) - } else if ((key === 'c' || key === 'C') && !info.ctrl) { - delete s.buy_order - delete s.sell_order - console.log('\nmanual'.grey + ' order cancel' + ' command executed'.grey) - } else if (key === 'm' && !info.ctrl && so.mode === 'live') { - so.manual = !so.manual - console.log('\nMANUAL trade in LIVE mode: ' + (so.manual ? 'ON'.green.inverse : 'OFF'.red.inverse)) - } else if (key === 'T' && !info.ctrl) { - so.order_type = 'taker' - console.log('\n' + 'Taker fees activated'.bgRed) - } else if (key === 'M' && !info.ctrl) { - so.order_type = 'maker' - console.log('\n' + 'Maker fees activated'.black.bgGreen) - } else if (key === 'o' && !info.ctrl) { - listOptions() - } else if (key === 'O' && !info.ctrl) { - console.log('\n' + cliff.inspect(so)) - } else if (key === 'P' && !info.ctrl) { - console.log('\nWriting statistics...'.grey) - printTrade(false) - } else if (key === 'X' && !info.ctrl) { - console.log('\nExiting... ' + '\nWriting statistics...'.grey) - printTrade(true) - } else if (key === 'd' && !info.ctrl) { - console.log('\nDumping statistics...'.grey) - printTrade(false, true) - } else if (key === 'D' && !info.ctrl) { - console.log('\nDumping statistics...'.grey) - toggleStats() - } else if (key === 'L' && !info.ctrl) { - debug.flip() - console.log('\nDEBUG mode: ' + (debug.on ? 'ON'.green.inverse : 'OFF'.red.inverse)) - } else if (info.name === 'c' && info.ctrl) { - // @todo: cancel open orders before exit - console.log() - process.exit() - } - }) + process.stdin.on('keypress', executeKey) } }) }) diff --git a/conf-sample.js b/conf-sample.js index e44f4ed04b..eaf9034fa6 100644 --- a/conf-sample.js +++ b/conf-sample.js @@ -262,6 +262,7 @@ c.notifiers.pushover.priority = '0' // choose a priority to send zenbot messages // telegram configs c.notifiers.telegram = {} c.notifiers.telegram.on = false // false telegram disabled; true telegram enabled (key should be correct) +c.notifiers.telegram.interactive = false // true telegram is interactive c.notifiers.telegram.bot_token = 'YOUR-BOT-TOKEN' c.notifiers.telegram.chat_id = 'YOUR-CHAT-ID' // the id of the chat the messages should be send in // end telegram configs diff --git a/extensions/notifiers/telegram.js b/extensions/notifiers/telegram.js index a37ed834a5..068e985d56 100644 --- a/extensions/notifiers/telegram.js +++ b/extensions/notifiers/telegram.js @@ -3,14 +3,34 @@ process.env['NTBA_FIX_319'] = 1 var TelegramBot = require('node-telegram-bot-api') module.exports = function telegram (config) { + var bot = new TelegramBot(config.bot_token, { polling: true }) + var wrapper = function(cb) { + return function(message) { + if (message.chat.id != config.chat_id) { + console.log('\nChat ID error: command coming from wrong chat: ' + message.chat.id) + return + } + cb(message.text) + } + } var telegram = { pushMessage: function(title, message) { - var bot = new TelegramBot(config.bot_token) - bot.sendMessage(config.chat_id, title + ': ' + message).catch(function (error) { console.error('\nerror: telegram notification') console.log(error.response.body) // => { ok: false, error_code: 400, description: 'Bad Request: chat not found' } }) + }, + onMessage: function (callback) { + bot.on('message', wrapper(callback)) + bot.on('webhook_error', (error) => { + console.log('\nwebhook error: telegram event ' + error.code) + }) + bot.on('polling_error', (error) => { + console.log('\npolling error: telegram event ' + error.code) + }) + bot.on('error', (error) => { + console.log('\nerror: telegram event ' + error.code) + }) } } return telegram diff --git a/lib/engine.js b/lib/engine.js index 85b6d6ccd2..1fc6376575 100644 --- a/lib/engine.js +++ b/lib/engine.js @@ -119,6 +119,12 @@ module.exports = function (s, conf) { } } + function onMessage(callback) { + if (so.mode === 'live' || so.mode === 'paper') { + notifier.onMessage(callback) + } + } + function isFiat() { return !s.currency.match(/^BTC|ETH|XMR|USDT$/) } @@ -996,6 +1002,8 @@ module.exports = function (s, conf) { executeSignal: executeSignal, writeReport: writeReport, syncBalance: syncBalance, + pushMessage: pushMessage, + onMessage: onMessage, } } diff --git a/lib/notify.js b/lib/notify.js index 1e1f81eab5..e0f0ce3140 100644 --- a/lib/notify.js +++ b/lib/notify.js @@ -1,8 +1,16 @@ module.exports = function notifier (conf) { var active_notifiers = [] + var interactive_notifiers = [] + for (var notifier in conf.notifiers) { if (conf.notifiers[notifier].on) { - active_notifiers.push(require(`../extensions/notifiers/${notifier}`)(conf.notifiers[notifier])) + var notif = require(`../extensions/notifiers/${notifier}`)(conf.notifiers[notifier]) + notif.notifier_name = notifier + + active_notifiers.push(notif) + if (conf.notifiers[notifier].interactive) { + interactive_notifiers.push(notif) + } } } @@ -14,10 +22,18 @@ module.exports = function notifier (conf) { active_notifiers.forEach((notifier) => { if (conf.debug) { - console.log(`Sending push message via ${notifier}`) + console.log(`Sending push message via ${notifier.notifier_name}`) } notifier.pushMessage(title, message) }) + }, + onMessage: function (callback) { + interactive_notifiers.forEach((notifier) => { + if (conf.debug) { + console.log(`Receiving message from ${notifier.notifier_name}`) + } + notifier.onMessage(callback) + }) } } }