These functions work with the node-binance-api for tracking prices and buying crypto with Binance.
import Binance from 'node-binance-api';
const isTesting = process.env.NODE_ENV === 'test';
export const QUOTE = isTesting ? 'USDT' : 'USD';
const options = {
APIKEY: process.env.BINANCE_API_KEY,
APISECRET: process.env.BINANCE_API_SECRET,
useServerTime: true,
urls: {
base: isTesting ? 'https://testnet.binance.vision/api/' : 'https://api.binance.us/api/',
},
};
const binance = new Binance().options(options);
export const lastPriceAsync = async (symbol) =>
parseFloat((await binance.prices(symbol + QUOTE))[symbol + QUOTE]);
export const lastPrice = (symbol, cb) => {
binance.prices(symbol + QUOTE, function (err, data) {
if (err) console.error(err.body);
cb(parseFloat(data[symbol + QUOTE]));
});
};
export const getAvailableSymbols = async (quote) => {
let tradeableMarkets = [];
const data = await binance.exchangeInfo();
for (let obj of data.symbols) {
if (marketIsValid(obj, quote)) {
tradeableMarkets.push(obj.baseAsset);
}
}
console.log(tradeableMarkets.length + ' tradeable markets');
return tradeableMarkets;
};
export function getPriceAtTime(symbol, timestamp, callback) {
const ts = new Date(timestamp).valueOf();
binance.aggTrades(
symbol + QUOTE,
{ startTime: ts, endTime: ts + 10000 },
(error, response) => {
if (response.length) {
callback(response.length ? parseFloat(response[0].p) : undefined);
}
}
);
}
// For backtesting
// Returns the min/max prices for the symbol at 'buyTimestamp'
function getMinMaxes(symbol, buyTimestamp, cb) {
let startDate = new Date(buyTimestamp);
let min5m, max5m, min1h, max1h;
binance.candlesticks(
symbol + QUOTE,
'1m',
(error, ticks) => {
ticks.forEach((tick) => {
let [time, open, high, low, close, volume, closeTime] = tick;
if (time < startDate.valueOf() + 5 * 60 * 1000) {
min5m = !min5m ? low : Math.min(min5m, low);
max5m = !max5m ? high : Math.max(max5m, high);
}
min1h = !min1h ? low : Math.min(min1h, low);
max1h = !max1h ? high : Math.max(max1h, high);
});
console.log('5m', min5m, max5m);
console.log('1h', min1h, max1h);
cb(min5m, max5m, min1h, max1h);
},
{
startTime: startDate.valueOf(),
limit: startDate.getSeconds() > 45 ? 60 : 61,
}
);
}
// ================ BUYING =================== //
const qtyToBuy = (symbol) => {
const tickerInfo = global.tickerInfo[symbol];
if (!tickerInfo) return 0;
const stepSize = tickerInfo.stepSize;
const minQty = tickerInfo.minNotional + 0.01;
const amtAvailable = global.balances[QUOTE] || 100;
const curPrice = global.prices[symbol];
let amount = (amtAvailable * (BUY_PERCENT / 100)) / curPrice;
amount = binance.roundStep(amount, stepSize);
if (amount < tickerInfo.minQty) amount = minQty;
if (curPrice * amount < minQty) {
amount = binance.roundStep(minQty / curPrice, stepSize);
if (curPrice * amount < minQty) {
amount = amount + binance.roundStep(parseFloat(stepSize), stepSize);
}
}
return binance.roundStep(amount, stepSize);
};
export const buySymbol = async (symbol) => {
const market = symbol + QUOTE;
const flags = { type: 'MARKET', newOrderRespType: 'FULL' };
const bResponse = await binance.marketBuy(market, qtyToBuy(symbol), flags);
if (bResponse.status !== 'FILLED' || !bResponse.fills.length) {
console.error("Buy order wasn't executed: ", bResponse.body ?? bResponse);
return;
}
const qty = parseFloat(bResponse.executedQty);
const buyOrderId = bResponse.orderId;
const buyTime = new Date(bResponse.transactTime).toISOString();
const filledOrder = bResponse.fills[0];
const fee =
parseFloat(filledOrder.commission) * global.prices[filledOrder.commissionAsset];
const price = parseFloat(filledOrder.price);
console.log(`Bought ${qty} ${symbol} at $${price}`);
return { price, qty };
};
export const marketIsValid = (obj, quoteAsset) => {
let quote = quoteAsset || QUOTE;
return obj.isSpotTradingAllowed && obj.quoteAsset === quote;
};
js
balance.js
This file runs in the background and saves the prices and the user's current balance, saving them as global variables.
import { binance, marketIsValid, QUOTE } from "./binance.js";
global.prices = {};
global.tickerInfo = {};
global.balances = {};
const SYMBOLS = ["AAVE", "ADA", "ALGO", ...]
// Get exchangeInfo on startup
//minNotional = minimum order value (price * quantity)
export function fetchTickerInfo() {
binance.exchangeInfo((error, data) => {
if (error) console.error(error);
let tickerInfo = {};
for (let obj of data.symbols) {
if (marketIsValid(obj)) {
let filters = { status: obj.status };
for (let filter of obj.filters) {
if (filter.filterType == "MIN_NOTIONAL") {
filters.minNotional = parseFloat(filter.minNotional);
} else if (filter.filterType == "PRICE_FILTER") {
filters.tickSize = parseFloat(filter.tickSize);
} else if (filter.filterType == "LOT_SIZE") {
filters.stepSize = filter.stepSize;
filters.minQty = parseFloat(filter.minQty);
}
}
tickerInfo[obj.baseAsset] = filters;
}
}
global.tickerInfo = tickerInfo;
console.info("set info for " + Object.keys(tickerInfo).length + " symbols");
// Get balance every 10 mins
setInterval(function () {
updateBalance();
}, 1000 * 60 * 10);
// Fetch prices every minute
setInterval(function () {
updatePrices();
}, 1000 * 60);
updateBalance();
updatePrices();
});
}
// Update global.balances
function updateBalance() {
binance.balance((error, balances) => {
if (error) console.error(error);
for (let asset in balances) {
const available = parseFloat(balances[asset].available);
if (!available) continue;
global.balances[asset] = available;
}
});
}
// Update global.prices
function updatePrices() {
binance.prices((error, tickers) => {
if (error) console.error(error);
for (let symbol in tickers) {
const baseAsset = symbol.replace(QUOTE, "");
if (symbol.endsWith(QUOTE) && SYMBOLS.includes(baseAsset)) {
global.prices[baseAsset] = parseFloat(tickers[symbol]);
}
}
});
}
js