UNPKG

bitprophet

Version:

Crypto trading platform for Binance that uses chat bots as its interface

308 lines (263 loc) 9.93 kB
module.exports = function() { "use strict" var vars = require(__dirname + "/vars.js") const exchUtils = require(__dirname + "/exchange_utils.js") const chatBot = require(__dirname + "/chat_bot.js") const utils = require(__dirname + "/utils.js") const pairGenerator = require(__dirname + "/pair_generator.js") var indicators = require(__dirname + "/indicators.js") var strategyManager = require(__dirname + "/strategy_manager.js") const binance = require("node-binance-api") const default_options = { pauseDangerBTC: true, mainLoopTimer: 1500, strategiesDir: __dirname + "/strategies", verbose: true } var options = default_options var btcIntervalsWatch = ["15m", "5m"] var userDataWebsocket = { listenKey: "", connectionTimestamp: 0, connecting: false } function initPairs() { for(var pairInfo of Object.values(vars.pairsInfo)) { var pair = new pairGenerator.create(pairInfo.tokenName, pairInfo.marketName) vars.pairs[pair.name()] = pair } } function initUserDataUpdates() { if(userDataWebsocket.connecting) return userDataWebsocket.connectionTimestamp = Date.now() userDataWebsocket.connecting = true binance.websockets.userData(balance_update, execution_update, function(listenKey) { userDataWebsocket.listenKey = listenKey userDataWebsocket.connecting = false }) } function balance_update(data) { for ( let obj of data.B ) { let { a:asset, f:available } = obj if(asset == "BNB" && parseFloat(available) < 0.05) { chatBot.sendMessage(":warning::fuelpump: Low BNB | " + available) break } } } function execution_update(data) { let { x:executionType, s:symbol, p:price, L:priceTraded, q:quantity, S:side, o:orderType, i:orderId, X:orderStatus, z:filledQuantity } = data var traded = executionType == "TRADE" var isNew = executionType == "NEW" var canceled = executionType == "CANCELED" var filled = parseFloat(filledQuantity) / parseFloat(quantity) if(traded) price = priceTraded var strategy = null var pair = null var order = null var strategiesList = strategyManager.listStrategies() for(var i = 0; i < strategiesList.length; ++i) { strategy = strategiesList[i] order = strategy.orderById(symbol, orderId) if(!order) continue pair = strategy.pairData(symbol) break } if(pair && order) { if(orderStatus == "FILLED") { if(side == "BUY") { pair.amountToSell += (1 - order.partFill) * parseFloat(quantity) } else { pair.amountToSell -= (1 - order.partFill) * parseFloat(quantity) } order.priceTraded = order.partFill * order.priceTraded + (1 - order.partFill) * parseFloat(price) order.partFill = 1 } else if(traded && filled < 1) { if(side == "BUY") { pair.amountToSell += (filled - order.partFill) * parseFloat(quantity) } else { pair.amountToSell -= (filled - order.partFill) * parseFloat(quantity) } order.priceTraded = (order.partFill * order.priceTraded + (filled - order.partFill) * parseFloat(price)) / filled order.partFill = filled } else if(canceled) { order.canceled = true order.waiting = false if(!order.automatedCancel) { chatBot.sendMessage(":raised_hand: Trading stopped for pair " + pair.chatName + " due to human intervention") strategy.resetPairData(pair.name) } } } var quantityFixed = quantity < 1 ? quantity : parseFloat(quantity).toFixed(2) var type, emojiStr if(traded) { type = "Traded" emojiStr = orderStatus == "FILLED" ? ":high_brightness:" : ":low_brightness:" } else if(isNew) { type = "Created" emojiStr = ":package:" } else { type = "Canceled" emojiStr = ":wastebasket:" } var oType = orderType == "STOP_LOSS_LIMIT" ? "SL " : "" var fill = traded ? " | " + parseFloat(filled * 100).toFixed(2) + "%" : "" chatBot.sendMessage(emojiStr + " " + oType + type + " - " + vars.pairs[symbol].chatName() + "\t" + side + " "+ quantityFixed + "@" + exchUtils.fixPrice(symbol, price) + fill) } function controlBitcoinRSI() { var btcPair = vars.pairs["BTCUSDT"] btcPair.ensureChartUpdates(btcIntervalsWatch) var chart15m = btcPair.chart(btcIntervalsWatch[0]).ticks var chart5m = btcPair.chart(btcIntervalsWatch[1]).ticks if(chart15m.length >= 500 && chart5m.length >= 500) { var lastClose = parseFloat(chart5m[chart5m.length - 1].close) var rsi15m = indicators.rsi(chart15m, 14, 100, 1) rsi15m = parseFloat(rsi15m[rsi15m.length - 1]).toFixed(2) var rsi5m = indicators.rsi(chart5m, 14, 100, 1) rsi5m = parseFloat(rsi5m[rsi5m.length - 1]).toFixed(2) vars.btcAnalysis.price = lastClose vars.btcAnalysis.rsi15m = rsi15m vars.btcAnalysis.rsi5m = rsi5m if(rsi15m > 70 && !vars.btcAnalysis.dangerZone) { chatBot.sendMessage(":triangular_flag_on_post: Danger: BTC is going up | " + lastClose + "$ | RSI 15m: " + rsi15m) vars.btcAnalysis.dangerZone = true } else if(rsi15m < 30 && !vars.btcAnalysis.dangerZone) { chatBot.sendMessage(":triangular_flag_on_post: Danger: BTC is going down | " + lastClose + "$ | RSI 15m: " + rsi15m) vars.btcAnalysis.dangerZone = true } else if(vars.btcAnalysis.dangerZone && rsi15m <= 65 && rsi15m >= 35) { chatBot.sendMessage(":waving_white_flag: Back to normal: BTC is stabilizing | " + lastClose + "$ | RSI 15m: " + rsi15m) vars.btcAnalysis.dangerZone = false } } setTimeout(controlBitcoinRSI, options.mainLoopTimer) } function processStrategies() { var dateStr = utils.formatDate(new Date(), true, true) if(!exchUtils.websocketActive(userDataWebsocket.listenKey)) { if(!userDataWebsocket.connecting && Date.now() - userDataWebsocket.connectionTimestamp > 10 * 1000) { console.log("Loading connection for user data updates: " + dateStr) initUserDataUpdates() } else { console.log("Waiting connection for user data updates: " + dateStr) } } else { var consoleMsg = "Processing: " + dateStr var activeStrategies = strategyManager.listStrategies(true) if(!activeStrategies.length) { consoleMsg += " | No active strategies" } else { for(var i = 0; i < activeStrategies.length; ++i) { var strategy = activeStrategies[i] var paperTradingStr = strategy.paperTrading() ? "[PT] " : "" consoleMsg += " | " + paperTradingStr + strategy.name() + " - " + strategy.validPairs().length + " " + strategy.tradingPairs().length + " " + strategy.profit().toFixed(2) + "%" } } console.log(consoleMsg) strategyManager.process() } setTimeout(processStrategies, options.mainLoopTimer) } function checkNextDay() { var currentDate = new Date() currentDate = currentDate.getTime() var currentSecond = currentDate / 1000 var currentDay = Math.floor(currentSecond / 86400) if(vars.currentDay == -1) { vars.currentDay = currentDay } else if(currentDay > vars.currentDay) { exchUtils.accountTotalBalance(function(error, balance) { if(error) { console.log("Error reading total account balance: " + error) return } vars.startBTCAmount = balance.btcTotal.toFixed(8) var strategiesList = strategyManager.listStrategies() var totalProfit = 0 var totalAccountProfit = 0 for(var i = 0; i < strategiesList.length; ++i) { var strategy = strategiesList[i] totalProfit += strategy.profit() totalAccountProfit += strategy.accountProfit() strategy.resetProfits() } vars.currentDay = currentDay chatBot.sendMessage(":clock12: New day: " + utils.formatDate(new Date(), true, true) + "\n" + "Profit from previous day: " + totalProfit.toFixed(2) + "% | " + totalAccountProfit.toFixed(2) + "%\n" + "Total: " + vars.startBTCAmount + "BTC | " + balance.usdtTotal.toFixed(2) + "$") }) } setTimeout(checkNextDay, 1000 * 60) } return { vars: vars, exchUtils: exchUtils, indicators: indicators, options: function(opt) { if(typeof opt.strategiesDir === "string") options.strategiesDir = opt.strategiesDir if(typeof opt.commandsCustomDir === "string") options.commandsCustomDir = opt.commandsCustomDir if(typeof opt.mainLoopTimer === "number") options.mainLoopTimer = opt.mainLoopTimer if(typeof opt.pauseDangerBTC === "boolean") options.pauseDangerBTC = opt.pauseDangerBTC if(typeof opt.verbose === "boolean") options.verbose = opt.verbose options.binance = opt.binance options.telegram = opt.telegram || {} options.discord = opt.discord || {} vars.options = options }, listenToChatId: function() { chatBot.init(true) }, start: function(next) { binance.options({ "APIKEY": vars.options.binance.key, "APISECRET": vars.options.binance.secret, useServerTime: true, reconnect: false }) var oldLog = console.log console.log = function() { if(vars.options.verbose) oldLog.apply(console, arguments) } exchUtils.initExchangeInfo(function(error) { if(error) { console.log("Error initializing exchange info.", error) if(next) next("Error initializing exchange info.") return } exchUtils.accountTotalBalance(function(error, balance) { if(error) { console.log("Error reading total account balance: " + error) if(next) next("Error reading total account balance.") return } vars.startBTCAmount = balance.btcTotal.toFixed(8) chatBot.init() initUserDataUpdates() initPairs() strategyManager.init() vars.pairs["BTCUSDT"].addWatcherChartUpdates(btcIntervalsWatch) chatBot.sendMessage(":traffic_light: BitProphet started: " + utils.formatDate(new Date(), true, true) + "\nTotal: " + vars.startBTCAmount + "BTC | " + balance.usdtTotal.toFixed(2) + "$") console.log("Initialization complete.") vars.initialized = true controlBitcoinRSI() processStrategies() checkNextDay() }) }) } } }()