Skip to content

Instantly share code, notes, and snippets.

@cue232s
Created January 5, 2021 21:12
Show Gist options
  • Save cue232s/c23f049f002ac237cb45f0be1e99354c to your computer and use it in GitHub Desktop.
Save cue232s/c23f049f002ac237cb45f0be1e99354c to your computer and use it in GitHub Desktop.
// "use strict";
const binance = require('node-binance-api');
const chalk = require('chalk');
const log = console.log;
const moment = require('moment');
// percentage of gains targeted
const MIN_PROFIT_TARGET = 0.1;
const MAX_PROFIT_TARGET = 5.0;
const NUMBER_OF_TRADES_OPPORTUNITIES = 1;
let ARBITRAGE_OPP_COUNT = 0;
let ARB_GAIN_ACCUM = 0;
let ACT_BAL = 0;
let EXCHANGE_INFO;
const ACT_PERC_PER_TRADE = 0.6;
// tradding fee as a percentage
const TRADING_FEE = 0.05 / 100;
const SLIPPAGE_RANGE = 0.15;
const SLIPPAGE_ALLOWED = SLIPPAGE_RANGE / 100;
let tradeCandidates = [];
let iSRunning;
const config = () => {
binance.options({
APIKEY: 'aeU3hOzGOn5ueapCbfJCUg1yqzuoa8B5BeSS6EaiwibKh8mE7uHtfZ4H1Yp3ZTId',
APISECRET:
'TSkA5FEmInjr6P0JrwdSYK27CQJppHfbeAJX1Ox9j7Vzp5FOfJXa5DTdrJtmqCZu'
});
};
let execPause = 0;
const runArbitrage = () => {
try {
if (iSRunning) throw 'Script already in process.. chill!';
} catch (error) {
log(error);
}
iSRunning = true;
ARB_GAIN_ACCUM = 0;
ARBITRAGE_OPP_COUNT = 0;
const tickers = new Promise((resolve, reject) => {
binance.prices(function(ticker) {
// console.log("prices()", ticker);
binance.balance((balances, error) => {
ACT_BAL = balances.BTC.available;
binance.exchangeInfo(x => {
EXCHANGE_INFO = x;
resolve(ticker);
});
});
});
});
// at the moment BTC is quoting them all
tickers
.then(currencies => {
// console.log(currencies)
console.log('******STARTING BTC BALANCE: ' + ACT_BAL + '**********');
let initialCurrency = 'BTC';
let holdingCurrency = initialCurrency;
let startingPairs = getAllCurrencyPairsForCrypto(
holdingCurrency,
currencies
);
// startingPairs.splice(1, startingPairs.length)
let amountOf1stCrypto = ACT_BAL * 1.5;
// let amountOf1stCrypto = 4;
startingPairs.map(startingCrypto => {
let firstTransaction = buildTransaction(
amountOf1stCrypto,
holdingCurrency,
startingCrypto,
currencies
);
console.group();
let secondHoldingCurrency = firstTransaction.buy.currency;
let amountOf2ndCrypto = firstTransaction.buy.volume;
// get all the currency pairs for the new currency you are holding
let crossPairs = getAllCurrencyPairsForCrypto(
secondHoldingCurrency,
currencies
);
crossPairs.splice(crossPairs.indexOf(startingCrypto), 1);
crossPairs.map(secondPair => {
let transactionPath = new Array();
transactionPath.push(firstTransaction);
if (startingCrypto === secondPair) {
throw 'Error redundant Pair';
}
secondTransaction = buildTransaction(
amountOf2ndCrypto,
secondHoldingCurrency,
secondPair,
currencies
);
console.group();
let thirdHoldingCurrency = secondTransaction.buy.currency;
let thirdAmountOfThirdCurrency = secondTransaction.buy.volume;
finalPairs = getAllCurrencyPairsForCrypto(
thirdHoldingCurrency,
currencies
);
transactionPath.push(secondTransaction);
finalPairs.splice(finalPairs.indexOf(secondPair), 1);
let finalPair = finalPairs.filter(x => {
return x.includes(initialCurrency);
})[0];
finalTransaction = buildTransaction(
thirdAmountOfThirdCurrency,
thirdHoldingCurrency,
finalPair,
currencies
);
transactionPath.push(finalTransaction);
// console.log(Math.abs(finalTransaction.buyAmount - amountOf1stCrypto));
try {
let trueParity = finalTransaction.buy.volume - amountOf1stCrypto;
let potentialGain = trueParity / amountOf1stCrypto * 100;
// currently only trading parities in one direction (positive parity).
// Where we end up with more of the Initial coin then we began with.
// need to find negative parity and reverse order of transaction
if (
potentialGain > MIN_PROFIT_TARGET &&
potentialGain < MAX_PROFIT_TARGET
) {
console.log(
chalk.yellow(
'potential arbitrage for ' + potentialGain + '% profit'
)
);
tradeCandidates.push({
trade: executeAribtrage(transactionPath, execPause * 2),
profitScore: potentialGain
});
execPause++;
// executeAribtrage(transactionPath)()
transactionPath.forEach(x => {
log(chalk.cyan(x.description));
});
ARBITRAGE_OPP_COUNT++;
ARB_GAIN_ACCUM += potentialGain;
}
} catch (error) {
console.warn(error);
}
console.groupEnd();
});
console.groupEnd();
});
log(
chalk.blue(
'There are a total of ' + ARBITRAGE_OPP_COUNT + ' opportunities'
)
);
log(
chalk.blue(
'There average opportunity shows gains of ' +
(ARB_GAIN_ACCUM / ARBITRAGE_OPP_COUNT).toFixed(4) +
'%'
)
);
return runHighestRankingTrades(
NUMBER_OF_TRADES_OPPORTUNITIES,
tradeCandidates
);
})
.catch(x => {
console.warn(chalk.bgYellow(x));
})
.then(x => {
// toggle the iRunning attribute to let the program kno
iSRunning = false;
});
};
runHighestRankingTrades = (numberOfTrades, listOfTrades) => {
let sortedList = listOfTrades.sort(function(a, b) {
var profitA = a.profitScore; // ignore upper and lowercase
var profitB = b.profitScore; // ignore upper and lowercase
if (profitA < profitB) {
return -1;
}
if (profitA > profitB) {
return 1;
}
// names must be equal
return 0;
});
const executeTrades = sortedList.slice(0, numberOfTrades);
executeTrades.map(tradeGroup => {
setTimeout(() => {
tradeGroup.trade().then(x => console.log('trade executed :', x));
}, 20);
});
};
executeAribtrage = (trnPath, executionPause) => {
let index = 0;
return function() {
return Promise.resolve(
executeTrade(trnPath[index], executionPause)
.then(order => {
index++;
if (order.status === 'FILLED') {
return executeTrade(trnPath[index]);
}
})
.then(order => {
index++;
if (order.status === 'FILLED') {
return executeTrade(trnPath[index]);
}
})
.then(order => {
index++;
if (order.status === 'FILLED') {
return executeTrade(trnPath[index]);
}
})
.then(order => {
log(chalk.bgRed('Final order succeded!'));
})
.catch(x => {
log(trnPath[index].pair + ' :index of trade: ', index);
log(trnPath[index].pair + ' :trade attempt : ', trnPath[index]);
log(trnPath[index].pair + ' :trade error: ', x);
log('end: ', chalk.red(moment().valueOf()));
return x;
})
);
};
};
// return order2 = executeTrade(trnPath[index]).then((x) => {
// index++;
// let finalOrder = executeTrade(trnPath[index]).then((x) => {
// // log('finished the arbitrage')
// });
// });
// }).catch(x => console.log(x));
// let order = executeTrade(trnPath[index]).then((x) => {
// index++;
// let order2 = executeTrade(trnPath[index]).then((x) => {
// index++;
// let finalOrder = executeTrade(trnPath[index]).then((x) => {
// log('finished the arbitrage')
// });
// });
// }).catch(x => console.log(x));
getRuleCompliantNumber = (currencyPair, number, ruleType) => {
let compliantNumber;
// number = number.toFixed(10);
if (ruleType === 'LOT_SIZE') {
// never round up on lot.
let lotFilter = EXCHANGE_INFO.symbols.find(x => {
return x.symbol === currencyPair;
}).filters[1].stepSize;
// compliantNumber = number - (number % lotFilter);
// compliantNumber = Math.round(number / lotFilter) * lotFilter
compliantNumber = getVolume(number, lotFilter);
log('volume modified: ', parseFloat(compliantNumber));
return parseFloat(compliantNumber);
} else {
let priceFilter = EXCHANGE_INFO.symbols.find(x => {
return x.symbol === currencyPair;
}).filters[0].minPrice;
// compliantNumber = number - (number % priceFilter).toFixed(10);
compliantNumber = getPrice(number, priceFilter);
// compliantNumber = number.toFixed(getPrecision(parseFloat(EXCHANGE_INFO.symbols.find((x) => {
// return x.symbol === currencyPair
// }).filters[0].minPrice)));
return parseFloat(compliantNumber);
}
};
function getPrecision(number) {
var n = parseFloat(number)
.toString()
.split('.');
return n.length > 1 ? n[1].length : 0;
}
function getPricePrecision(number) {
const realNumber = parseFloat(number);
if (realNumber.toString().indexOf('-') >= 0) {
const precision = realNumber.toString().slice(3);
return precision;
} else {
var n = realNumber.toString().split('.');
if (!n[1]) {
n = number.toString().split('.');
}
return n.length > 1 ? n[1].length : 0;
}
}
getPrice = (price, stepSize) => {
const precision = getPricePrecision(stepSize);
log('precison of price is: ', precision);
// realPrice = parseFloat(price);
// if(realPrice.toString().indexOf('-') >= 0) {
// const
// } else {
// }
n = parseFloat(price)
.toString()
.split('.');
preDecimal = n[0];
postDecimal = n[1];
try {
return preDecimal + '.' + postDecimal.slice(0, precision);
} catch (error) {
return error;
}
};
getVolume = (desiredVol, stepSize) => {
const precision = getPrecision(stepSize);
n = parseFloat(desiredVol)
.toString()
.split('.');
preDecimal = n[0];
postDecimal = n[1];
try {
return preDecimal + '.' + postDecimal.slice(0, precision);
} catch (error) {
return error;
}
};
// given an orderId continually check for if it has been filled
checkOrderStatus = (orderId, orderPair, checkCount = null) => {
checkCount = checkCount || 0;
return new Promise(resolve => {
binance.orderStatus(orderPair, orderId, (orderStatus, symbol) => {
resolve(orderStatus);
});
}).then(orderStatus => {
if (orderStatus.status === 'FILLED') {
console.log(orderId + ' order status:', orderStatus.status);
return orderStatus;
} else if (orderStatus.status === 'CANCELED') {
console.log(orderId + ' order status:', orderStatus.status);
return orderStatus;
} else if (!orderStatus.status) {
log(chalk.yellow(orderId + ' bad order status:', orderStatus));
return orderStatus;
} else {
console.log(
orderId + ' order status:',
chalk.red(moment().valueOf()) + '--' + orderStatus.status
);
return checkOrderStatus(orderId, orderPair, checkCount + 1);
}
});
};
executeTrade = (trx, timeoutX = 0) => {
return new Promise((r, j) => {
setTimeout(() => {
log(chalk.red(moment().valueOf()));
try {
if (trx.dealType === 'BUY') {
binance.buy(
trx.pair,
trx.buy.volume,
trx.limitPairPrice,
{
type: 'LIMIT'
},
(resp, err) => {
if (!resp.orderId) {
log(
chalk.bgYellow(
trx.pair + 'trade exited immediately: ',
resp.checkOrderStatus
)
);
return j(resp.msg);
}
return r(
checkOrderStatus(resp.orderId, trx.pair).catch(x => log)
);
}
);
} else {
binance.sell(
trx.pair,
trx.buy.volume,
trx.limitPairPrice,
{
type: 'LIMIT'
},
(resp, err) => {
if (!resp.orderId) {
log(
chalk.bgYellow(
trx.pair + 'trade exited immediately: ',
resp.checkOrderStatus
)
);
return j(resp.msg);
}
return r(
checkOrderStatus(resp.orderId, trx.pair).catch(x => log)
);
}
);
}
} catch (error) {
j(error);
}
}, timeoutX * 100);
});
// orderStr = 'binance.' + trx.dealType.toLowerCase() + '("' + trx.pair + '",' + trx.buy.volume + ',' + trx.limitPairPrice + ', type:"LIMIT"}, (resp) => { })';
// let order = eval(orderStr);
// return checkOrderStatus(order.orderId);
};
transactionDetail = (currencyPair, heldCurrency) => {
let buying;
let selling;
let currentPos;
if (currencyPair.indexOf(heldCurrency) === 0) {
currentPos = 'BASE';
newCurrency = getCryptoQuoteCurrency(currencyPair);
} else {
currentPos = 'QUOTE';
newCurrency = getCryptoBaseCurrency(currencyPair);
}
let deal = {
buy: newCurrency,
sell: heldCurrency,
sellPostion: currentPos
};
return deal;
};
// this fuction is supplied the type of currency we are currently holding
// how much of said currency we are holding
// and the currecy pair that we are transacting with
// the result of this function is an array of arbitrage opportunities
buildTransaction = (amountHeld, holdingCurency, newPair, currencies) => {
let buyAmount;
let sellAmount = amountHeld;
let adjustedCurrencyPrice;
let dealAction;
try {
let deal = transactionDetail(newPair, holdingCurency);
let buyCurrency = deal.buy;
let slippageFee = Number(currencies[newPair] * SLIPPAGE_ALLOWED);
log(chalk.bgRed('currency price: ' + currencies[newPair]));
log(chalk.bgRed('cost of slippage: ' + slippageFee));
if (deal.sellPostion === 'QUOTE') {
dealAction = 'BUY';
adjustedCurrencyPrice = getRuleCompliantNumber(
newPair,
currencies[newPair] * 1 + slippageFee,
'PRICE'
);
log(chalk.bgRed('adjusted price: ' + adjustedCurrencyPrice));
// if currency in hand is the quote currency of the new pair in thransaction
// (pair being - the curency you hold and the currency you are buying)
// divide amount of currency you hold (selling) by the price of the pair
buyAmount = amountHeld / adjustedCurrencyPrice;
} else {
dealAction = 'SELL';
adjustedCurrencyPrice = getRuleCompliantNumber(
newPair,
currencies[newPair] * 1 - slippageFee,
'PRICE'
);
log(chalk.bgRed('adjusted price: ' + adjustedCurrencyPrice));
// if currency in hand is the base currency of the new pair in thransaction
// (pair being - the curency you hold and the currency you are buying)
// multiply amount of currency you hold (selling) by the price of the pair
buyAmount = amountHeld * adjustedCurrencyPrice;
}
log(chalk.bgRed('pre-fee amount bought: ' + buyAmount));
const tradingFee = buyAmount * TRADING_FEE;
const volume = getRuleCompliantNumber(
newPair,
buyAmount - tradingFee,
'LOT_SIZE'
);
let exchange = {
currentPairPrice: currencies[newPair],
limitPairPrice: adjustedCurrencyPrice,
pair: newPair,
fee: tradingFee,
dealType: dealAction,
buy: {
volume: volume,
currency: buyCurrency
},
sell: {
volume: sellAmount,
currency: holdingCurency
},
description:
' Sell ' +
sellAmount +
' of ' +
holdingCurency +
' for ' +
buyAmount +
' of ' +
buyCurrency
};
console.log(
newPair +
': going from ' +
chalk.red(exchange.sell.volume + ' ' + exchange.sell.currency) +
' => ' +
chalk.green(exchange.buy.volume + ' ' + exchange.buy.currency)
);
return exchange;
} catch (error) {
console.warn(error);
}
};
getCryptoQuoteCurrency = currency => {
let quoteCurr;
if (currency.slice(currency.length - 4) === 'USDT') {
quoteCurr = 'USDT';
} else {
quoteCurr = currency.slice(currency.length - 3);
}
return quoteCurr;
};
getCryptoBaseCurrency = currency => {
let baseCurr;
if (currency.slice(currency.length - 4) === 'USDT') {
baseCurr = currency.slice(0, currency.length - 4);
} else {
baseCurr = currency.slice(0, currency.length - 3);
}
return baseCurr;
};
getAllBaseCryptos = (BaseCrypto, cryptos) => {
return Object.keys(cryptos).filter(x => {
return x.startsWith(BaseCrypto);
});
};
getAllCurrencyPairsForCrypto = (currency, cryptos) => {
return Object.keys(cryptos).filter(x => {
return x.includes(currency) && !x.includes('BNB');
});
};
getAllQuoteCryptos = (QuoteCrypto, cryptos) => {
return Object.keys(cryptos).filter(x => {
return x.endsWith(QuoteCrypto);
});
};
module.exports = {
aribitrageChecker: runArbitrage,
buildTransaction: buildTransaction,
checkOrderStatus: checkOrderStatus,
config: config,
executeAribtrage: executeAribtrage,
executeTrade: executeTrade,
getAllBaseCryptos: getAllBaseCryptos,
getAllQuoteCryptos: getAllQuoteCryptos,
getAllCurrencyPairsForCrypto: getAllCurrencyPairsForCrypto,
getCryptoQuoteCurrency: getCryptoQuoteCurrency,
getCryptoBaseCurrency: getCryptoBaseCurrency,
getPrecision: getPrecision,
getRuleCompliantNumber: getRuleCompliantNumber,
transactionDetail: transactionDetail
};
module.exports.config();
setInterval(() => {
module.exports.aribitrageChecker();
}, 500);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment