bitprophet
Version:
Crypto trading platform for Binance that uses chat bots as its interface
648 lines (546 loc) • 18.7 kB
JavaScript
var vars = require(__dirname + "/vars.js")
var exchUtils = require(__dirname + "/exchange_utils.js")
const chatBot = require(__dirname + "/chat_bot.js")
const path = require("path")
const shortid = require("shortid")
module.exports = {
create: function(strategyId, strategyName) {
var _id = strategyId
var _name = strategyName
var _active = false
var _warningSilent = false
var _targetMarket = ""
var _targetTokens = []
var _excludeTokens = []
var _paperTrading = false
var _buyAmountMarket = 0
var _buyPercentageAccount = 0
var _profitTarget = 0
var _maxLoss = 0
var _maxTradingPairs = 1
var _source = require(path.resolve(vars.options.strategiesDir, _id + ".js"))
var _pairsData = {}
var _profit = 0
var _accountProfit = 0
this.id = function() {
return _id
}
this.name = function() {
return _name
}
this.active = function() {
return _active
}
this.setActive = function(active) {
_active = active
}
this.setTargetMarket = function(targetMarket) {
if(!targetMarket) _targetMarket = ""
else _targetMarket = targetMarket
}
this.setTargetTokens = function(targetTokens) {
if(!targetTokens) _targetTokens = []
else _targetTokens = targetTokens
}
this.setExcludeTokens = function(excludeTokens) {
if(!excludeTokens) _excludeTokens = []
else _excludeTokens = excludeTokens
}
this.paperTrading = function() {
return _paperTrading
}
this.setPaperTrading = function(paperTrading) {
_paperTrading = paperTrading
}
this.buyAmountMarket = function() {
if(_buyPercentageAccount > 0) return vars.startBTCAmount * _buyPercentageAccount
else return _buyAmountMarket
}
this.setBuyAmountMarket = function(amount) {
_buyAmountMarket = amount
}
this.setBuyPercentageAccount = function(percentageAccount) {
_buyPercentageAccount = percentageAccount
}
this.profitTarget = function() {
return _profitTarget
}
this.setProfitTarget = function(profitTarget) {
_profitTarget = profitTarget * 0.01
}
this.maxLoss = function() {
return _maxLoss
}
this.setMaxLoss = function(maxLoss) {
_maxLoss = maxLoss * 0.01
}
this.maxTradingPairs = function() {
return _maxTradingPairs
}
this.setMaxTradingPairs = function(maxTradingPairs) {
_maxTradingPairs = maxTradingPairs
}
this.pairs = function() {
var pairsArray = []
for(var pairName of Object.keys(_pairsData)) {
var pairData = this.pairData(pairName)
pairsArray.push(pairData)
}
return pairsArray
}
this.validPairs = function() {
var validPairsArray = []
for(var pairName of Object.keys(_pairsData)) {
var pairData = this.pairData(pairName)
if(pairData.status == -1) continue
validPairsArray.push(pairData)
}
return validPairsArray
}
this.tradingPairs = function() {
var tradingPairsArray = []
for(var pairName of Object.keys(_pairsData)) {
var pairData = this.pairData(pairName)
if(pairData.status < 1 && !pairData.processing) continue
tradingPairsArray.push(pairData)
}
return tradingPairsArray
}
this.process = function() {
var currentTime = Date.now()
for(var pair of Object.values(vars.pairs)) {
if(_targetMarket.length && pair.marketName().toLowerCase() != _targetMarket.toLowerCase()) continue
if((_targetTokens.length && _targetTokens.indexOf(pair.tokenName()) == -1) ||
_excludeTokens.indexOf(pair.tokenName()) != -1) continue
var pairData = this.pairData(pair.name())
var blackFlagTime = currentTime - pairData.blackFlagTime
if(pairData.processing || (pairData.status <= 0 && (vars.paused || (vars.btcAnalysis.dangerZone && vars.options.pauseDangerBTC) ||
this.tradingPairs().length >= _maxTradingPairs || blackFlagTime < 15 * 60 * 1000))) continue
if(pairData.status <= 0) {
if(pairData.status == -1) {
try {
_source.resetPairCustomData(pairData)
}
catch(error) {
if(!_warningSilent) {
chatBot.sendMessage(":warning: " + _name + " is crashing. Check the console logs for more info.")
_warningSilent = true
}
console.log(error)
continue
}
}
var needsCheck = currentTime - pairData.lastValidCheck >= 10 * 60 * 1000
if(needsCheck) {
try {
_source.checkValidWorkingPair(this, pairData)
}
catch(error) {
if(!_warningSilent) {
chatBot.sendMessage(":warning: " + _name + " is crashing. Check the console logs for more info.")
_warningSilent = true
}
console.log(error)
}
}
}
if(pairData.status == -1) continue
if(_paperTrading && pairData.status > 0) {
var orders = this.openOrders(pairData.name)
var lastPrice = parseFloat(pairData.functions.lastPrice())
for(var i = 0; i < orders.length; ++i) {
var order = orders[i]
var side = order.side
var price = order.price
if(side == "BUY") {
if(lastPrice < price) {
order.partFill = 1
order.priceTraded = parseFloat(price)
pairData.amountToSell += order.amount
let quantity = order.amount
let quantityFixed = quantity < 1 ? quantity : parseFloat(quantity).toFixed(2)
chatBot.sendMessage(":high_brightness: [PT] Traded - " + pairData.chatName + " BUY " +
quantityFixed + "@" + exchUtils.fixPrice(pairData.name, order.price) + " | 100.00%")
}
}
else {
if(lastPrice > price) {
order.partFill = 1
order.priceTraded = parseFloat(price)
pairData.amountToSell -= order.amount
let quantity = order.amount
let quantityFixed = quantity < 1 ? quantity : parseFloat(quantity).toFixed(2)
chatBot.sendMessage(":high_brightness: [PT] Traded - " + pairData.chatName + " SELL " +
quantityFixed + "@" + exchUtils.fixPrice(pairData.name, order.price) + " | 100.00%")
}
}
}
}
try {
_source.process(this, pairData)
}
catch(error) {
if(!_warningSilent) {
chatBot.sendMessage(":warning: " + _name + " is crashing. Check the console logs for more info.")
_warningSilent = true
}
console.log(error)
}
}
}
this.manageStopLoss = function(pairData, lastClose, trailingType = 2, trailingPercentage = 0.006, sellPriceDistance = 0.001) {
if(lastClose < pairData.stopLoss.sellPrice && lastClose < pairData.stopLoss.stopPrice) {
if(!pairData.dangerSilent) {
this.sendMessage(pairData, "@" + lastClose + " crossed stop loss price of " + pairData.stopLoss.sellPrice + "!", "warning")
pairData.dangerSilent = true
}
}
if(lastClose <= pairData.stopLoss.stopPrice && pairData.sellTarget > pairData.stopLoss.sellPrice) {
pairData.sellTarget = pairData.stopLoss.sellPrice
return true
}
switch(trailingType) {
case 0:
default:
break
case 1:
if(lastClose / pairData.entryPrice >= (1 + trailingPercentage) && pairData.stopLoss.sellPrice < pairData.entryPrice) {
pairData.stopLoss.stopPrice = pairData.entryPrice * (1 + sellPriceDistance)
pairData.stopLoss.sellPrice = pairData.entryPrice
this.sendMessage(pairData, "stop loss adjusted to 0.0%", "point_up")
}
break
case 2:
if(pairData.entryPrice < lastClose / (1 + trailingPercentage) && pairData.stopLoss.stopPrice < lastClose / (1 + trailingPercentage)) {
pairData.stopLoss.stopPrice = lastClose / (1 + trailingPercentage)
pairData.stopLoss.sellPrice = lastClose / (1 + trailingPercentage + sellPriceDistance)
var percentage = (pairData.stopLoss.stopPrice / pairData.entryPrice - 1) * 100
this.sendMessage(pairData, "stop loss adjusted to " + percentage.toFixed(2) + "%", "point_up")
}
break
}
return false
}
this.tradeFinished = function(pairData) {
var buyInfo = this.buyTradedInfo(pairData)
var sellInfo = this.sellTradedInfo(pairData)
var totalBought = buyInfo.amountMarketPrice
var totalSold = sellInfo.amountMarketPrice
var finalProfit = (totalSold - totalBought) / totalBought * 100
if(finalProfit <= 0) pairData.blackFlagTime = Date.now()
pairData.profit += finalProfit
vars.pairs[pairData.name].addProfit(finalProfit)
this.addProfit(finalProfit)
var accountProfit = 0
if(!_paperTrading) {
var fees = (totalSold + totalBought) * vars.tradingFees
accountProfit = (totalSold - totalBought - fees) / vars.startBTCAmount * 100
pairData.accountProfit += accountProfit
vars.pairs[pairData.name].addAccountProfit(accountProfit)
this.addAccountProfit(accountProfit)
}
var emojiCode = finalProfit > 0 ? "golf::tada" : "golf::cold_sweat"
this.sendMessage(pairData, "trading #finished!\nProfit: " + finalProfit.toFixed(2) + "%\nAccount profit: " +
accountProfit.toFixed(2) + "%\nAvg Bought @" + buyInfo.avgPrice.toFixed(8) + " & Avg Sold @" + sellInfo.avgPrice.toFixed(8), emojiCode)
this.resetPairData(pairData.name)
}
this.pairData = function(pairName) {
if(!_pairsData[pairName]) {
var data = {}
data.name = pairName
data.token = vars.pairs[pairName].tokenName()
data.market = vars.pairs[pairName].marketName()
data.chatName = vars.pairs[pairName].chatName()
data.functions = vars.pairs[pairName]
data.processing = false
data.status = -1
data.entryPrice = 0
data.sellTarget = 0
data.amountToSell = 0
data.stopLoss = {}
data.stopLoss.stopPrice = 0
data.stopLoss.sellPrice = 0
data.orders = []
data.forceSell = false
data.lastValidCheck = -1
data.profit = 0
data.accountProfit = 0
_pairsData[pairName] = data
}
return _pairsData[pairName]
}
this.resetPairData = function(pairName) {
var data = this.pairData(pairName)
if(!data) return
data.status = -1
data.processing = false
data.entryPrice = 0
data.sellTarget = 0
data.amountToSell = 0
data.stopLoss.stopPrice = 0
data.stopLoss.sellPrice = 0
data.forceSell = false
var orders = this.openOrders(pairName)
for(var i = 0; i < orders.length; ++i) {
var order = orders[i]
this.cancelOrder(data, order.id, function(error) {
if(error) {
console.log("Error canceling order", error)
}
})
}
data.orders = []
try {
_source.resetPairCustomData(data)
}
catch(error) {
if(!_warningSilent) {
chatBot.sendMessage(":warning: " + _name + " is crashing. Check the console logs for more info.")
_warningSilent = true
}
console.log(error)
}
}
this.buy = function(pair, price, amountMarket, next) {
if(_paperTrading) {
amountMarket = parseFloat(amountMarket)
price = parseFloat(price)
var quantity = parseFloat(exchUtils.normalizeAmount(pair.name, amountMarket / price, price))
var order = this.createOrder(pair.name, shortid.generate(), "BUY", price, quantity)
setTimeout(function() {
var quantityFixed = quantity < 1 ? quantity : quantity.toFixed(2)
chatBot.sendMessage(":package: [PT] Created - " + pair.chatName + " BUY " + quantityFixed + "@" + exchUtils.fixPrice(pair.name, price))
}, 1000)
next(null, order)
return
}
var that = this
pair.processing = true
exchUtils.accountBalance(pair.market, function(error, balance) {
if(error) {
pair.processing = false
if(next) next("Error reading " + pair.market + " balance: " + error)
return
}
if(balance.available > amountMarket) {
exchUtils.createLimitOrder(pair.name, true, price, parseFloat(amountMarket / price), function(error, orderId, quantity, filled) {
pair.processing = false
var order = null
if(!error) {
order = that.createOrder(pair.name, orderId, "BUY", parseFloat(price), parseFloat(quantity))
if(filled) {
order.partFill = 1
order.priceTraded = parseFloat(price)
order.waiting = false
pair.amountToSell += order.amount
}
}
if(next) next(error, order)
})
}
else {
pair.processing = false
if(next) next("Not enough " + pair.market + " available: " + balance.available)
}
})
}
this.sell = function(pair, price, quantity, next) {
if(_paperTrading) {
price = parseFloat(price)
quantity = parseFloat(exchUtils.normalizeAmount(pair.name, quantity, price))
var order = this.createOrder(pair.name, shortid.generate(), "SELL", price, quantity)
setTimeout(function() {
var quantityFixed = quantity < 1 ? quantity : quantity.toFixed(2)
chatBot.sendMessage(":package: [PT] Created - " + pair.chatName + " SELL " + quantityFixed + "@" + exchUtils.fixPrice(pair.name, price))
}, 1000)
next(null, order)
return
}
var that = this
pair.processing = true
exchUtils.createLimitOrder(pair.name, false, price, quantity, function(error, orderId, quantity, filled) {
pair.processing = false
var order = null
if(!error) {
order = that.createOrder(pair.name, orderId, "SELL", parseFloat(price), parseFloat(quantity))
if(filled) {
order.partFill = 1
order.priceTraded = parseFloat(price)
order.waiting = false
pair.amountToSell -= order.amount
}
}
if(next) next(error, order)
})
}
/*this.stopLoss = function(pair, price, stopPrice, quantity, next) {
//TODO paper trading
pair.processing = true
exchUtils.createStopLimitOrder(pair.name, false, price, stopPrice, quantity, function(error, orderId, quantity) {
pair.processing = false
if(next) next(error, orderId, quantity)
})
}*/
this.cancelOrder = function(pair, orderId, next) {
var targetOrder = this.orderById(pair.name, orderId)
if(!targetOrder) {
if(next) next("Error canceling order. Order with id " + orderId + " not found for " + pair.name)
return
}
if(targetOrder.canceled) {
if(next) next(null)
return
}
if(_paperTrading) {
targetOrder.canceled = true
targetOrder.waiting = false
var quantity = parseFloat(exchUtils.normalizeAmount(pair.name, targetOrder.amount, targetOrder.price))
var quantityFixed = quantity < 1 ? quantity : parseFloat(quantity).toFixed(2)
setTimeout(function() {
chatBot.sendMessage(":wastebasket: [PT] Canceled - " + pair.chatName + " " + targetOrder.side + " " + quantityFixed + "@" + exchUtils.fixPrice(pair.name, targetOrder.price))
}, 1000)
next(null)
return
}
targetOrder.automatedCancel = true
pair.processing = true
exchUtils.cancelOrder(pair.name, orderId, function(error) {
pair.processing = false
if(error) targetOrder.automatedCancel = false
else {
targetOrder.canceled = true
targetOrder.waiting = false
}
next(error)
})
}
this.createOrder = function(pairName, orderId, side, price, amount, stopLoss = false) {
var order = {}
order.id = orderId
order.side = side
order.price = price
order.priceTraded = price
order.amount = amount
order.stopLoss = stopLoss
order.timestamp = Date.now()
order.partFill = 0
order.waiting = true
order.canceled = false
this.pairData(pairName).orders.push(order)
return order
}
this.removeOrder = function(pairName, orderId) {
var orders = this.pairData(pairName).orders
var orderIndex = -1
for(var i = 0; i < orders.length; ++i)
{
var order = orders[i]
if(order.id == orderId) {
orderIndex = i
break
}
}
if(orderIndex >= 0) orders.splice(orders.indexOf(orderIndex), 1)
return orderIndex >= 0
}
this.openOrders = function(pairName) {
var orders = this.pairData(pairName).orders
var openOrdersArray = []
for(var i = 0; i < orders.length; ++i)
{
var order = orders[i]
if(order.waiting && !order.canceled && order.partFill < 1) openOrdersArray.push(order)
}
return openOrdersArray
}
this.order = function(pairName, side, waiting = true) {
var orders = this.pairData(pairName).orders
for(var i = 0; i < orders.length; ++i)
{
var order = orders[i]
if(order.side == side && order.waiting == waiting) {
return order
}
}
return null
}
this.orderById = function(pairName, orderId) {
var orders = this.pairData(pairName).orders
for(var i = 0; i < orders.length; ++i) {
var order = orders[i]
if(order.id == orderId) {
return order
}
}
return null
}
this.buyTradedInfo = function(pairData) {
var avgBoughtPrice = -1
var boughtAmount = 0
for(var i = 0; i < pairData.orders.length; ++i)
{
var order = pairData.orders[i]
if(order.side != "BUY") continue
if(order.partFill) {
if(avgBoughtPrice == -1) {
avgBoughtPrice = order.priceTraded
boughtAmount = order.partFill * order.amount
}
else {
var amount = (order.partFill * order.amount)
avgBoughtPrice = (boughtAmount * avgBoughtPrice + amount * order.priceTraded) / (boughtAmount + amount)
boughtAmount += order.partFill * order.amount
}
}
}
return {avgPrice: avgBoughtPrice, amount: boughtAmount, amountMarketPrice: avgBoughtPrice * boughtAmount}
}
this.sellTradedInfo = function(pairData) {
var avgSoldPrice = -1
var soldAmount = 0
for(var i = 0; i < pairData.orders.length; ++i)
{
var order = pairData.orders[i]
if(order.side != "SELL") continue
if(order.partFill) {
if(avgSoldPrice == -1) {
avgSoldPrice = order.priceTraded
soldAmount = order.partFill * order.amount
}
else {
var amount = (order.partFill * order.amount)
avgSoldPrice = (soldAmount * avgSoldPrice + amount * order.priceTraded) / (soldAmount + amount)
soldAmount += order.partFill * order.amount
}
}
}
return {avgPrice: avgSoldPrice, amount: soldAmount, amountMarketPrice: avgSoldPrice * soldAmount}
}
this.profit = function() {
return _profit
}
this.addProfit = function(profit) {
_profit += profit
}
this.accountProfit = function() {
return _accountProfit
}
this.addAccountProfit = function(profit) {
_accountProfit += profit
}
this.resetProfits = function() {
_profit = 0
_accountProfit = 0
for(var pairName of Object.keys(_pairsData)) {
var pairData = this.pairData(pairName)
pairData.profit = 0
pairData.accountProfit = 0
}
}
this.sendMessage = function(pairData, message, emojiCode) {
var paperTradingStr = _paperTrading ? "[PT] " : ""
var emojiString = emojiCode ? ":" + emojiCode + ": " : ""
chatBot.sendMessage(emojiString + paperTradingStr + _name + ": " + pairData.chatName + " " + message)
}
}
}