Binance NodeJS Client

NodeJS client for trading with the Binance API

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