/* ****************************************************************************************************************** UKION TRIANGULAR ARBITRAGE ver 1.01 ALPHA (Binance only) author: Igor Ustijanoski Instructions: ------------ 1. Install NODE.JS 2. Import CCXT JavaScript library 3. Set INVESTMENT_AMOUNT_DOLLARS parameter - initial investment in USDT (minimum investment in Binance SPOT wallet: 150 USDT) (Due to exchange limitation, recommended value in application is 100) 4. Set MIN_PROFIT_DOLLARS - minimum profit for triangular pair (recommended value is 1 or 1.5) 5. Set BROKERAGE_PER_TRANSACTION_PERCENT - fees are taken from exchange (however there are some dast which always stay in wallet so recommended value for additional fee is between 0.01 and 0.04 (must check first time if there are profit or lose) 6. Add apiKey and secret 7. Comment out the line: exchange.set_sandbox_mode(true); // using binance testnet 8. Watch the console for any errors. If errors increase, please check wallet to transfer stuck funds to USDT 9. Watch console for any errors. If error increase please check wallet to transfer stucked funds to USDT in SPOT wallet 10. Report error to: [email protected] GOOD LUCK with TRADING!!! !!! Please be aware that this is not finansial advice. There can be lose. So please watch closly !!! ****************************************************************************************************************** */ // binance // api c2TmoYy8eZCbeKIK71vJtPcvjKPqEzjh8UUZVkl506JikkGVojIQYXfSej2dfNnr // secret tZGQSh5r4lYJ400oJl0Rjux6TL8nJSsTljsvSHJlzshv6N7cDvv0nkRouQnZLuUe // binance testnet // V9gGChVv5aRW8oFDApiOgTJmXPXfrSBkvdEQZJZ0Wwn3hjVAS0nYCokaxstu7SlJ // 8rAOB4OQcVuq73XpRgSnEg5bIjNhJKCYofdEIcCCYEuoOZqzP83OfP6PfQlZIN7J // Import the ccxt library /* const CONFIG = require('../../config/config'); const logger = require('./Loggers'); const Util = require('./Util'); const Binance = require('node-binance-api'); const binance = new Binance().options(Object.assign({ APIKEY: CONFIG.KEYS.API, APISECRET: CONFIG.KEYS.SECRET, test: !CONFIG.EXECUTION.ENABLED, log: (...args) => logger.binance.info(args.length > 1 ? args : args[0]), verbose: true }, CONFIG.BINANCE_OPTIONS)); */ const ccxt = require('ccxt'); const CONFIG = require('./config/config'); const colors = require('colors/safe'); colors.enable(); let continue1 = false; if (process.argv[2] && process.argv[3] && process.argv[4]) { continue1 = true; } if (!continue1) { throw new Error("All 3 parameters must be filled: \nAPP.js_param1:exchangeName_param2:fee(-1 for default)_param3:investment\n"); } // true za test server, false za staging server let testingServer = false; let exchangeName = process.argv[2]; if (process.argv[2] === 'test') { exchangeName = 'binance'; testingServer = true; } let apikey, secret, password,uid; let timeInForce = 'Normal'; let waitingSecondsBetweenTrades = 2; let imatPari = false; let fees = process.argv[3]; if (testingServer) { // testing server Binance apikey = 'V9gGChVv5aRW8oFDApiOgTJmXPXfrSBkvdEQZJZ0Wwn3hjVAS0nYCokaxstu7SlJ'; secret = '8rAOB4OQcVuq73XpRgSnEg5bIjNhJKCYofdEIcCCYEuoOZqzP83OfP6PfQlZIN7J'; waitingSecondsBetweenTrades = 0; imatPari = true; // testing server - Kucoin //apikey = 'b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c'; //secret = ''; } else { // staging servers // crypto.com -0.0750% if (exchangeName === 'poloniex') { // o.2 fee, ne najde parovi, nekolku pati pusteno apikey = 'GZS2NK59-1GTS4YJV-HEKQNR3T-U9BF8FJB'; secret = 'cb6441b08a6a852404377505cd711ef915d39046c4ba2490e14a82583df9b00fad5c523389a6198b8cf1bdb08ec6728f8c6c4e70f2f4fe1a591fd9b749505174'; } if (exchangeName === 'bitmex') { // bitmex - 0.025% fee apikey = 'pba5eGL7PkB8H7jFGrBilL5a'; secret = 'BtbNgqpgqoTPMsAFDt2A6XWo8dyRZgnIAFtY2-x9KXq0kI56'; } if (exchangeName === 'mexc') { // mexc - naogja tek tuk parovi, slab profit, so 0% fee e probano, denski 0.05 usd profit apikey = 'mx0vglDbQjBY9iHe3J'; secret = '931c1bcdb92c427e98c901a92aebf176'; waitingSecondsBetweenTrades = 1.5; imatPari = true; } if (exchangeName === 'bybit') { // bybit - Buy Limit Order is forbidden to trade, so market order ne naogja parovi! // 0.1% fee spored oficijalniot sajt apikey = 'AUFsjrOQUznt5IfrcI'; secret = '5TbKzzvOJV5gZQ8PmXGeUPpWLI7eW87eBAV4'; } if (exchangeName === 'gate') { // Gate, treba da se proveri ubavo, so 0.2 fee nema parovi apikey = '63090e1b65e0bd540c2091782dfb8d10'; secret = 'ad609a563ffddcac9026bd478b16911ab3d7418c8f68d71308b121b75e4a1c60'; } if (exchangeName === 'binance') { // binance, ne najde nieden par dosega so 0.075 fee apikey = 'c2TmoYy8eZCbeKIK71vJtPcvjKPqEzjh8UUZVkl506JikkGVojIQYXfSej2dfNnr'; secret = 'tZGQSh5r4lYJ400oJl0Rjux6TL8nJSsTljsvSHJlzshv6N7cDvv0nkRouQnZLuUe'; waitingSecondsBetweenTrades = 0.5; } if (exchangeName === 'woo') { // woo - nesho ima problem so fetchOrder, treba da se analizira kodot i da se zameni samo so woo so druga funkcija apikey = 'ysSRS/ePjPWDNwlM4bYLZQ=='; secret = 'DVTWR2QHWBVXUXZL2LVIMEQTZE5R'; } if (exchangeName === 'okx') { // OKX, 0.08% fee, slabo parovi apikey = '71956a5c-879f-4dc0-8197-84c4ac10971f'; secret = 'BB7259C0BD56949D1E828E9C51E24CB9'; password = '&yEDYxC7!f$m+KU'; } if (exchangeName === 'bitmart') { //bitmart - naogja parovi so 0 fee nekojpat uid = '1234567890'; apikey = '09f509e9de7b3ae3766d5c3211c596954824084b'; secret = '4df6acc79c2d64861663965aa3028cfa713d6aa387c619e3bb47841c0e817516'; } if (exchangeName === 'kucoin') { // fees 0.1 - 0.3 apikey = '656a248edae7240001a955f9'; secret = 'ed405c5d-e4a2-436f-906f-4261e5116ba7'; password = '123456789'; } } const exchange = new ccxt[exchangeName]( { 'maxCapacity': 2000, // sendbox binance server parametar 'enableRateLimit': true, //'rateLimit': 100, 'uid': uid, 'apiKey': apikey, 'secret': secret, 'password': password, 'options': { 'defaultType': 'spot', 'adjustForTimeDifference': true } }); // sandbox binance server if (testingServer) { // testing server exchange.set_sandbox_mode(true); // using binance testnet } // CONFIG VARIABLES let INVESTMENT_AMOUNT_DOLLARS = parseFloat(process.argv[4]); let MIN_PROFIT_DOLLARS = 0.0001; // expected profit let BROKERAGE_PER_TRANSACTION_PERCENT = fees; console.log('exchange:',colors.blue(exchangeName),'fee:',colors.yellow(BROKERAGE_PER_TRANSACTION_PERCENT), 'investment-USDT:', colors.yellow(INVESTMENT_AMOUNT_DOLLARS), 'min-profit:', colors.yellow(MIN_PROFIT_DOLLARS),'timeInForce:', colors.blue(timeInForce)); let BAD_TOKEN_MAX_LOSE = MIN_PROFIT_DOLLARS*2; // Maximum lose for Loosing -> BAD_TOKEN let MASTER_CURRENCY = 'USDT'; let ORDER_TYPE = 'limit'; // APP VARIABLES let PROFIT = 0; let BALANCESTART; let PRETHODEN_PROFIT = 0; let TOTALPROFIT =0; let balance, balance1,balanceBTC; let count = 0; let badTokensInfo = []; let startTime = new Date().getTime(); let nowTime; // Apprication start main(); async function main() { let _symbols, markets; // clear screen //console.clear(); //const markets = await exchange.fetchMarketData(undefined, 'future'); //let markets = await exchange.loadMarkets(); //let markets = await exchange.fetchMarkets(); //console.log(markets); //exchange.verbose =true; //let //let _symbols = await filterOnlyValidTokens_2(markets); if (exchangeName === 'mexc') { _symbols = await getAllMexcTokens(); //MEXC exchange } else if (exchangeName === 'woo') { markets = await exchange.fetchMarkets(); _symbols = await market_symbols(markets); } else if (exchangeName === 'bitmart') { _symbols = await getAllBitMartTokens(); // bitmart exchange - fee = 0 tokens } else if (exchangeName === 'bitmex') { _symbols = await getAllBitMexTokens(); // bitmex } else { _symbols = await filterOnlyValidTokens(); } console.log(_symbols); balance = await getAllBalancesInUSDT(exchange); console.log('CURRENT BALANCE ALL:',balance); BALANCESTART = await getBalance('USDT'); console.log('CURRENT BALANCE USDT: ',BALANCESTART); PROFIT = BALANCESTART; PRETHODEN_PROFIT = PROFIT; console.log(colors.blue('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')); //console.log(_symbols); console.log(`No. of market symbols: ${_symbols.length}`); let wx_combinations_usdt = await get_crypto_combinations(_symbols, MASTER_CURRENCY); console.log(`No. of triangular combinations: ${wx_combinations_usdt.length*2}`); // console.table(wx_combinations_usdt); console.log(colors.blue('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<')); // throw new Error("STOP Execution!"); let recursion, feeCheck; console.time(); while(true) { for (var combination, _pj_c = 0, _pj_a = wx_combinations_usdt, _pj_b = _pj_a.length; _pj_c < _pj_b; _pj_c += 1) { combination = _pj_a[_pj_c]; //if (!testingServer) { base = combination["base"]; intermediate = combination["intermediate"]; ticker = combination["ticker"]; //} else { // base = 'USDT'; // intermediate = 'BTC'; // ticker = 'WTC'; //} s1 = `${intermediate}/${base}`; s2 = `${ticker}/${intermediate}`; s3 = `${ticker}/${base}`; try { recursion = true; feeCheck = await findRealFee('BUY_BUY_SELL', s1,s2, s3); if (feeCheck === true) { while (recursion === true) { recursion = await perform_triangular_arbitrage(s1, s2, s3, "BUY_BUY_SELL", INVESTMENT_AMOUNT_DOLLARS, BROKERAGE_PER_TRANSACTION_PERCENT, MIN_PROFIT_DOLLARS); } } //setTimeout(() => {}, "1000"); recursion = true; feeCheck = await findRealFee('BUY_SELL_SELL', s3,s2,s1); if (feeCheck === true) { while (recursion === true) { recursion = await perform_triangular_arbitrage(s3,s2,s1,"BUY_SELL_SELL",INVESTMENT_AMOUNT_DOLLARS, BROKERAGE_PER_TRANSACTION_PERCENT, MIN_PROFIT_DOLLARS); } } //setTimeout(() => {}, "1000"); } catch (e) { throw e; } } //balance1 = PROFIT; //await getBalance('USDT'); PROFIT = await getBalance('USDT'); TOTALPROFIT = PROFIT - BALANCESTART; nowTime = new Date().getTime(); let time = Math.floor(((nowTime-startTime) % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))+'h '+ Math.floor(((nowTime-startTime) % (1000 * 60 * 60)) / (1000 * 60)) +'m '+Math.floor(((nowTime-startTime) % (1000 * 60)) / 1000) + 's'; //PROFIT = balance1; console.log(colors.blue('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')); console.log('START USDT balance:',BALANCESTART,'NEW USDT Balance:', PROFIT,'TOTAL PROFIT:', TOTALPROFIT, 'Execution Time:', time); console.log(colors.blue('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')); count = 0; } // while end } async function checkIfTokenIsBad(badTokens, token1, token2, token3) { // Loop through the array of tokens for (let token of badTokens) { // If any of the tokens match any of the strings, return true if (token === token1 || token === token2 || token === token3) { return true; } } // If none of the tokens match any of the strings, return false return false; } async function market_symbols(markets) { var _pj_a = [], _pj_b = markets; for (var _pj_c = 0, _pj_d = _pj_b.length; _pj_c < _pj_d; _pj_c += 1) { var market = _pj_b[_pj_c]; _pj_a.push(market["symbol"]); } return _pj_a; } // Create all triangular arbitrage pairs of available symbols async function get_crypto_combinations(_symbols, base) { var combination, combinations, sym1_token1, sym1_token2, sym2_token1, sym2_token2, sym3_token1, sym3_token2; combinations = []; for (let i = 0; i < _symbols.length; i++) { let [sym1_token1, sym1_token2] = _symbols[i].split('/'); if (sym1_token2 === base) { //&& sym1_token1 === 'BTC') { for (let j = 0; j < _symbols.length; j++) { let [sym2_token1, sym2_token2] = _symbols[j].split('/'); if (sym1_token1 === sym2_token2 && sym2_token1 !== 'IOTX' && sym2_token1 !== 'COS') { for (let k = 0; k < _symbols.length; k++) { let [sym3_token1, sym3_token2] = _symbols[k].split('/'); if (sym2_token1 === sym3_token1 && sym3_token2 === sym1_token2) { combination = { "base": sym1_token2, "intermediate": sym1_token1, "ticker": sym2_token1 }; combinations.push(combination); } } } } } } return combinations; } async function filterOnlyValidTokens() { let tickers = await exchange.fetchTickers(); //console.log(tickers); // Initialize an empty array to store the symbols let result1 = []; // Loop through the entries of the tickers object for (let [symbol, ticker] of Object.entries(tickers)) { // Check if the ticker has ask, bid or close values if (ticker.ask !== undefined) { if (ticker.bid !== undefined) { //if (ticker.close !== undefined) { // if (ticker.symbol.split("/")[0] !== 'BUSD') { // if (ticker.symbol.split("/")[1] !== 'BUSD') { result1.push(symbol); // } // } //} } } } return result1; } async function filterOnlyValidTokens_2(markets) { let symbols = []; // Loop through the list of markets and append the symbol name to the array for (let market of markets) { symbols.push(market['symbol']); } // Return the array of symbols return symbols; } // Define an async function that takes an exchange as input and returns an object with all balances in USDT async function getAllBalancesInUSDT(exchange) { let brojacUSDT = 0; try { // Fetch the balance from the exchange let balance = await exchange.fetchBalance({type: 'spot'}); //console.log(balance); // Fetch the prices from the exchange let prices = await exchange.fetchTickers(); // all exchanges // let prices = await exchange.fetchMarketData(); // woo exchange //console.log(prices); // Initialize an empty object to store the balances in USDT let balancesInUSDT = {}; // Loop through the coins in the balance object for (let coin in balance.free) { // Get the amount of the coin let amount = balance.free[coin]; // If the amount is positive, convert it to USDT if (amount > 0) { // Check if the coin is USDT itself if (coin === 'USDT') { // Set the balance in USDT to the amount balancesInUSDT[coin] = amount; brojacUSDT = amount; } else { // Try to find the price of the coin in USDT let symbol = coin + '/USDT'; let price = prices[symbol] ? prices[symbol].last : undefined; // If the price is defined, multiply it by the amount and set the balance in USDT if (price) { /* // sell all tokens to USDT try { console.log(coin,amount); await sellToken(symbol,await getBalance(coin)); } catch (e) { //throw e; } */ //balancesInUSDT[coin] = amount * price; brojacUSDT = brojacUSDT + (amount * price); } } } } // Return the balances in USDT object // throw new Error("STOP EXECUTION!!!"); return brojacUSDT; } catch (e) { throw e; } } // x=1 BuyBuySell, x=2 BuySellSell async function fetchLimitPrice(symbol,askbid) { // askbid for fetchTicker - ask/bid, for fetchOrderBook - asks/bids let price; const ticker = await exchange.fetchOrderBook(symbol,1); //await exchange.fetchTicker(symbol); price = ticker[askbid][0][0]; //ticker[askbid]; if (price === undefined) { return 0;} return price; } // Get market price of symbol async function fetch_current_ticker_price(ticker) { // use the await keyword to fetch the ticker details let current_ticker_details = await exchange.fetchTicker(ticker); // use the ternary operator to assign the ticker price or null let ticker_price = current_ticker_details ? current_ticker_details["close"] : undefined; // "last" bese "close", probvam so last dali e ok. //console.log(ticker_price); return ticker_price; } // use the Math object instead of the math module for mathematical operations function check_if_float_zero(value) { return Math.abs(value - 0.0) <= 1e-3; }; // round number on X decimals function roundNumber(number, X) { // Declare a constant variable to store the result const ret = Math.round(number * Math.pow (10, X)) / Math.pow (10, X); // Return the result return ret; } function checkPriceOk(price) { return price === undefined ? 0 : price; } async function calculateTriangularTrade(type, symbol1, symbol2, symbol3, orderBook1, orderBook2, orderBook3, initialamount, fee) { let _finalPrice, _limit_qty, _limit_prices, _amountCheck,_limitCheck,_fee1; let orderPrice1, orderPrice2, orderPrice3, limitPrice1, limitPrice2, limitPrice3; _finalPrice = 0; _limit_qty = {}; _limit_prices = {}; // BUY_BUY_SELL PREDICTION if (type === 'bbs') { // buy base - with input quote -> ex: BTC/USDT, BTC=base, USDT=quote _amountCheck = initialamount; orderPrice1 = checkPriceOk(orderBook1['asks'][0][0]); //orderPrice1 = (orderBook1['asks'][0][0]+ orderBook1['bids'][0][0])/2; orderAmount1 = orderBook1['asks'][0][1]; _limitCheck = orderAmount1*orderPrice1; if (_limitCheck-_amountCheck >= 0) { limitOrder1 = _amountCheck/orderPrice1; _fee1 = fee === '-1' ? await getOrderFee(symbol1, ORDER_TYPE, 'buy', limitOrder1, orderPrice1) : fee/100; //console.log(_fee1); limitOrder1 = limitOrder1 * (1-_fee1); limitOrder1 = await setSymbolPrecision(symbol1,limitOrder1); limitPrice1 = orderPrice1; //console.log('\tbuy',symbol1, limitOrder1, limitPrice1); } else { return [0,0,0]; } //buy base - with input quote _amountCheck = limitOrder1; orderPrice2 = checkPriceOk(orderBook2['asks'][0][0]); //orderPrice2 = (orderBook2['asks'][0][0]+ orderBook2['bids'][0][0])/2; orderAmount2 = orderBook2['asks'][0][1]; _limitCheck = orderAmount2*orderPrice2; if (_limitCheck-_amountCheck >= 0) { limitOrder2 = _amountCheck/orderPrice2; _fee1 = fee === '-1' ? await getOrderFee(symbol2, ORDER_TYPE, 'buy', limitOrder2, orderPrice2) : fee/100; limitOrder2 = limitOrder2 * (1-_fee1); limitOrder2 = await setSymbolPrecision(symbol2,limitOrder2); limitPrice2 = orderPrice2; //console.log('\tbuy',symbol2, limitOrder2, limitPrice2); } else { return [0,0,0]; } //sell base - wuth input value base _amountCheck = limitOrder2; orderPrice3 = checkPriceOk(orderBook3['bids'][0][0]); //orderPrice3 = (orderBook3['asks'][0][0]+ orderBook3['bids'][0][0])/2; orderAmount3 = orderBook3['bids'][0][1]; _limitCheck = orderAmount3; if (_limitCheck-_amountCheck >= 0) { limitOrder3 = _amountCheck; limitOrder3 = await setSymbolPrecision(symbol3,limitOrder3) limitPrice3 = orderPrice3; //console.log('\tsell',symbol3, limitOrder3, limitPrice3); _fee1 = fee === '-1' ? await getOrderFee(symbol3, ORDER_TYPE, 'sell', limitOrder3, orderPrice3) : fee/100; _finalPrice = roundNumber(limitOrder3*limitPrice3 * (1-_fee1),5); } else { return [0,0,0]; } } // BUY_SELL_SELL PREDICTION if (type === 'bss') { // buy base - with input quote -> ex: BTC/USDT, BTC=base, USDT=quote _amountCheck = initialamount; orderPrice1 = checkPriceOk(orderBook1['asks'][0][0]); // orderPrice1 = (orderBook1['asks'][0][0]+ orderBook1['bids'][0][0])/2; orderAmount1 = orderBook1['asks'][0][1]; _limitCheck = orderAmount1*orderPrice1; if (_limitCheck-_amountCheck >= 0) { limitOrder1 = _amountCheck/orderPrice1; _fee1 = fee === '-1' ? await getOrderFee(symbol1, ORDER_TYPE, 'buy', limitOrder1, orderPrice1) : fee/100; limitOrder1 = limitOrder1 * (1-_fee1); limitOrder1 = await setSymbolPrecision(symbol1,limitOrder1); limitPrice1 = orderPrice1; // console.log('\tbuy',symbol1, limitOrder1, limitPrice1); } else { return [0,0,0]; } //sell base - with input base _amountCheck = limitOrder1; orderPrice2 = checkPriceOk(orderBook2['bids'][0][0]); // orderPrice2 = (orderBook2['bids'][0][0]+ orderBook2['bids'][0][0])/2; orderAmount2 = orderBook2['bids'][0][1]; _limitCheck = orderAmount2; if (_limitCheck-_amountCheck >= 0) { limitOrder2 = _amountCheck; // console.log('limitOrder2_1',limitOrder2) limitOrder2 = await setSymbolPrecision(symbol2,limitOrder2); // console.log('limitOrder2_2',limitOrder2) limitPrice2 = orderPrice2; // console.log('\tsell',symbol2, limitOrder2, limitPrice2); } else { return [0,0,0]; } //sell base - wuth input base _fee1 = fee === '-1' ? await getOrderFee(symbol2, ORDER_TYPE, 'sell', limitOrder2, orderPrice2) : fee/100; _amountCheck = limitOrder2*limitPrice2*(1-_fee1); orderPrice3 = checkPriceOk(orderBook3['bids'][0][0]); // orderPrice3 = (orderBook3['bids'][0][0]+ orderBook3['bids'][0][0])/2; orderAmount3 = orderBook3['bids'][0][1]; _limitCheck = orderAmount3; if (_limitCheck-_amountCheck >= 0) { limitOrder3 = _amountCheck; // console.log('limitOrder3_1',limitOrder3) limitOrder3 = await setSymbolPrecision(symbol3,limitOrder3) // console.log('limitOrder3_2',limitOrder3) limitPrice3 = orderPrice3; // console.log('\tsell',symbol3, limitOrder3, limitPrice3); _fee1 = fee === '-1' ? await getOrderFee(symbol3, ORDER_TYPE, 'sell', limitOrder3, orderPrice3): fee/100; _finalPrice = roundNumber(limitOrder3*limitPrice3 * (1-_fee1),5); } else { return [0,0,0]; } } _limit_qty = { [symbol1]: limitOrder1, [symbol2]: limitOrder2, [symbol3]: limitOrder3 }; _limit_prices = { [symbol1]: limitPrice1, [symbol2]: limitPrice2, [symbol3]: limitPrice3 }; return [_finalPrice, _limit_qty, _limit_prices] } async function getOrderFee(symbol, type, side, amount, price) { let fee; try { fee = await exchange.calculateFee(symbol, type, side, amount, price); //console.log('rate',fee.rate, 'cost',fee.cost); //console.log(fee); return fee.rate; } catch (e) { throw e; } } // find all current prices for 1 triangular combination for buy_buy_sell async function check_buy_buy_sell(scrip1, scrip2, scrip3, initial_investment) { let final_price, scrip_qty, scrip_prices, orderBook; final_price = 0; scrip_prices = {}; scrip_qty = {}; orderBook = await Promise.all([exchange.fetchOrderBook(scrip1), exchange.fetchOrderBook(scrip2), exchange.fetchOrderBook(scrip3)]); [final_price,scrip_qty, scrip_prices] = await calculateTriangularTrade('bbs', scrip1, scrip2, scrip3, orderBook[0], orderBook[1], orderBook[2], initial_investment, BROKERAGE_PER_TRANSACTION_PERCENT); return [final_price,scrip_qty, scrip_prices]; } // find all current prices for 1 triangular combination for buy_sell_sell async function check_buy_sell_sell(scrip1, scrip2, scrip3, initial_investment) { let final_price, scrip_qty, scrip_prices, orderBook; final_price = 0; scrip_prices = {}; scrip_qty = {}; // original bellow, for polonex argument 2 is removed //orderBook = await Promise.all([exchange.fetchOrderBook(scrip1,1), exchange.fetchOrderBook(scrip2,1), exchange.fetchOrderBook(scrip3,1 )]); orderBook = await Promise.all([exchange.fetchOrderBook(scrip1), exchange.fetchOrderBook(scrip2), exchange.fetchOrderBook(scrip3)]); [final_price,scrip_qty, scrip_prices] = await calculateTriangularTrade('bss', scrip1, scrip2, scrip3, orderBook[0], orderBook[1], orderBook[2], initial_investment, BROKERAGE_PER_TRANSACTION_PERCENT); return [final_price,scrip_qty, scrip_prices]; } // check profit of 1 triangular combination async function check_profit_loss(total_price_after_sell, initial_investment, transaction_brokerage, min_profit) { var apprx_brokerage, min_profitable_price, profit_loss; apprx_brokerage = 0; //transaction_brokerage * initial_investment / 100 * 3; min_profitable_price = initial_investment + apprx_brokerage + min_profit; profit_loss = Math.round((total_price_after_sell - min_profitable_price) * 1e7) / 1e7; //profit_loss = total_price_after_sell - min_profitable_price; return profit_loss; } // TUTORIAL /* RequestTimeout This exception is raised when the connection with the exchange fails or data is not fully received in a specified amount of time. This is controlled by the timeout option. When a RequestTimeout is raised, the user doesn't know the outcome of a request (whether it was accepted by the exchange server or not). Thus it's advised to handle this type of exception in the following manner: for fetching requests it is safe to retry the call for a request to cancelOrder() a user is required to retry the same call the second time. A subsequent retry to cancelOrder() will return one of the following possible results: a request is completed successfully, meaning the order has been properly canceled now an OrderNotFound exception is raised, which means the order was either already canceled on the first attempt or has been executed (filled and closed) in the meantime between the two attempts. if a request to createOrder() fails with a RequestTimeout the user should: call fetchOrders(), fetchOpenOrders(), fetchClosedOrders() to check if the request to place the order has succeeded and the order is now open if the order is not 'open' the user should fetchBalance() to check if the balance has changed since the order was created on the first run and then was filled and closed by the time of the second check. */ async function fetchOrderInternal(orderID, symbol, count) { let order; if (!count) {count = 1;}; try { order = await exchange.fetchOrder(orderID, symbol); //waiting(1) } catch (e) { console.log('fetch',e.message); //waiting(1); if (count < 9) { await fetchOrderInternal(orderID, symbol,count+1); } else { return [true, 'closed']; } } return [true, order.status]; } async function cancelOrderInternal(orderID, symbol,count) { let order; try { order = await exchange.cancelOrder(orderID, symbol); waiting(1); } catch (e) { console.log('cancel', e.message); //waiting(1); if (count < 2) { await cancelOrderInternal(orderID, symbol,2); } else { return [false,'canceled']; } } return [false,order.status]; } //limit buy order async function place_buy_order_limit(scrip, quantity,price,x) { let order,qty, price1, orderID, order_c, orderTime; let check = false; let z = 1; let buffer = '>>>>>>'; try { while(!check) { price1 = await fetchLimitPrice(scrip,'asks'); if (x === 1) { qty = quantity; } else { qty = await getBalance(scrip.split('/')[1])/price1; qty = await setSymbolPrecision(scrip,qty); } if (z>1) {buffer = colors.blue(buffer);} console.log ('\t',buffer,z,'. try - Creating',ORDER_TYPE,'BUY order: ', scrip, qty , price1); order = await exchange.createOrder (scrip, ORDER_TYPE, 'buy', qty ,price1); orderID = order.id; orderTime = order.timestamp; [check,order.status] = await fetchOrderInternal(orderID, scrip); console.log('1',colors.yellow(orderID),order.status); while (order.status === 'open') { waiting(0.5); try { [check,order.status] = await fetchOrderInternal(orderID, scrip); console.log('1.1.',colors.yellow(orderID),order.status); if ((Date.now() - orderTime) > 3000) { if (order.status === 'open') { [check,order.status] = await cancelOrderInternal(orderID, scrip); console.log('1.2.',colors.yellow(orderID),order.status); break; } } } catch (e) { console.log('ovde buy', e); //check = true; //console.log('-closed'); //break; } } if (order.status === 'canceled') { check = false; console.log('2',order.status);} if (order.status === 'closed' || order.status === 'filled' || order.status === undefined) { waiting(waitingSecondsBetweenTrades); check = true; console.log('3',order.status); break; } z++; } } catch (e) { throw e; } console.log('\t>>> BUY order created!') } //limit sell order async function place_sell_order_limit(scrip, quantity,price,x) { let order,qty, orderID, price1, order_c, orderTime; let check = false; let z = 1; let buffer = '>>>>>>'; try { while(!check) { price1 = await fetchLimitPrice(scrip,'bids'); qty = Math.min(await getBalance(scrip.split('/')[0]),quantity); qty = await setSymbolPrecision(scrip,qty); if (z>1) {buffer = colors.blue(buffer);} console.log ('\t',buffer,z,'. try - Creating ',ORDER_TYPE,' SELL order: ', scrip, qty, price1); order = await exchange.createOrder (scrip, ORDER_TYPE, 'sell', qty ,price1); orderID = order.id; orderTime = order.timestamp; [check,order.status] = await fetchOrderInternal(orderID, scrip); console.log('1',colors.yellow(orderID),order.status); while (order.status === 'open') { waiting(0.5); try { [check,order.status] = await fetchOrderInternal(orderID, scrip); console.log('1.1.',colors.yellow(orderID),order.status); if ((Date.now() - orderTime) > 3000) { if (order.status === 'open') { [check,order.status] = await cancelOrderInternal(orderID, scrip); console.log('1.2.',colors.yellow(orderID),order.status); break; } } } catch (e) { console.log('ovde buy', e); //check = true; //console.log('-closed'); //break; } } if (order.status === 'canceled') { check = false; console.log('2',order.status);} if (order.status === 'closed' || order.status === 'filled' || order.status === undefined) { waiting(waitingSecondsBetweenTrades); check = true; console.log('3',order.status); break; } z++; } } catch (e) { throw e; } console.log('\t>>> SELL order created!') } async function qtyToPrecision(scrip, quantity) { return Math.round((quantity) * 1e8) / 1e8; } async function quantity_To_Precision(scrip, quantity) { try { let formatedqty = await exchange.amountToPrecision (scrip, quantity); return formatedqty; } catch (e) { //throw e; return 0; } } async function place_order(type,scrip,qty1,price,x) { let order; try { if (type === 'BUY') { order = await place_buy_order_limit(scrip, qty1,price,x); } else { if (type === 'SELL') { order = await place_sell_order_limit(scrip, qty1, price,x); } } } catch (e) { throw e; } } async function place_trade_orders(type, scrip1, scrip2, scrip3, initial_amount, scrip_prices, scrip_qty) { var order; let problem = 0; console.log(colors.blue('************************************************************************************************************')); try { if (type === "BUY_BUY_SELL") { try { order = await place_order('BUY',scrip1, scrip_qty[scrip1],scrip_prices[scrip1],1); } catch (e) {problem = 1; throw e;} try { order =await place_order('BUY',scrip2,scrip_qty[scrip2],scrip_prices[scrip2],2); } catch (e) {problem = 2; throw e;} try { order =await place_order('SELL',scrip3,scrip_qty[scrip3],scrip_prices[scrip3],3); } catch (e) {problem = 3; throw e;} } else { if (type === "BUY_SELL_SELL") { try { order = await place_order('BUY',scrip1,scrip_qty[scrip1],scrip_prices[scrip1],1); } catch (e) {problem = 4; throw e;} try { order = await place_order('SELL',scrip2,scrip_qty[scrip2],scrip_prices[scrip2],2); } catch (e) {problem = 5; throw e;} try { order = await place_order('SELL',scrip3,scrip_qty[scrip3],scrip_prices[scrip3],3); } catch (e) {problem = 6; throw e;} } } console.log(colors.blue('************************************************************************************************************')); //return true; } catch (e) { console.log('Problem: ', problem, e.message); //return 0; throw e; } } async function perform_triangular_arbitrage(scrip1, scrip2, scrip3, arbitrage_type, initial_investment, transaction_brokerage, min_profit) { var final_price, profit_loss, scrip_prices, scrip_qty; let bal, balBTC, balBNB, balETH; let trueCheck = false; let checkNear = false; let textcolor = []; final_price = 0.0; profit_loss = 0; if (arbitrage_type === "BUY_BUY_SELL") { [final_price, scrip_qty, scrip_prices] = await check_buy_buy_sell(scrip1, scrip2, scrip3, initial_investment); } else { if (arbitrage_type === "BUY_SELL_SELL") { [final_price, scrip_qty, scrip_prices] = await check_buy_sell_sell(scrip1, scrip2, scrip3, initial_investment); } } profit_loss = await check_profit_loss((final_price), initial_investment, transaction_brokerage, min_profit); textcolor[1] = arbitrage_type+' '+scrip1+' '+scrip2+' '+scrip3; textcolor[4] = 'Final Price: '+final_price; textcolor[5] = 'P/L Market: '+profit_loss; if (final_price >=initial_investment*0.995) { textcolor[4] = colors.brightBlue.bold('Final Price:',final_price); } if (final_price >=initial_investment*0.9985) { textcolor[4] = colors.yellow.bold('Final Price:',final_price); } if (final_price >=initial_investment*0.999) { textcolor[4] = colors.magenta.bold('Final Price:',final_price); checkNear = false; // dali da povtori proverka na ovoj triangl pak, true e DA } if (profit_loss > 0) { textcolor[5] = colors.green.bold('P/L Market:',profit_loss); textcolor[4] = colors.green.bold('Final Price:',final_price); checkNear = false; } count = count+1; console.log(count,exchangeName,':',roundNumber(PROFIT-BALANCESTART,4),'\t',textcolor[1],textcolor[5],textcolor[4]); if (checkNear) {count--; return true;} if ( profit_loss > 0) { // && (final_price-final_price_AB) <= initial_investment/100*1/3) { console.log(colors.green('>>>EXECUTE TRANSACTIONS:'),arbitrage_type, scrip1,scrip2,scrip3, 'P/L:', profit_loss); count = count -1; try { // if (testingServer) { // test server trading checker = await place_trade_orders(arbitrage_type, scrip1, scrip2, scrip3, initial_investment, scrip_prices, scrip_qty); } else { // staging server real trading (uncomment for real trade) if (imatPari) { checker = await place_trade_orders(arbitrage_type, scrip1, scrip2, scrip3, initial_investment, scrip_prices, scrip_qty); } } //setTimeout(() => {}, "1000"); bal = await getBalance('USDT')// so trading //balBTC = await getBalance('BTC'); //balBNB = await getBalance('BNB'); //balETH = await getBalance('ETH'); if (testingServer) { PROFIT = (bal); // calculation for real trading on test server }else { // calculation for real trading on staging server (uncomment for real data, comment calculation below) if (imatPari) { PROFIT = (bal); } else { // calculation for testing without trading PROFIT = PROFIT + (final_price-initial_investment); //bez trading presmetka } } if (PROFIT >= PRETHODEN_PROFIT) { // ako najde triangular t.e. ako vleze tuka -> povtorno se izvrsuva ovaa funkcija so isti vrednosti PRETHODEN_PROFIT = PROFIT; trueCheck = true; } else { //console.log(''); //await addBadToken(arbitrage_type, scrip1, scrip2,scrip3,PROFIT, PRETHODEN_PROFIT); //console.log(badTokensInfo); console.log(colors.red('>>> Profit pomal od prethodna triangular kombinacija, baram druga triangular kombinacija!')); console.log(''); PRETHODEN_PROFIT = PROFIT; trueCheck = false; } let balanceUSD; if (trueCheck) { console.log('USDT :', bal, 'BTC:',balBTC, 'ETH:',balBTC, 'BNB:',balBNB); } else { console.log('USDT :', bal, 'BTC:',balBTC, 'ETH:',balBTC, 'BNB:',balBNB); //throw new Error("NO PROFIT! STOP Execution!"); } console.log(''); /* if (bal <= INVESTMENT_AMOUNT_DOLLARS) { throw new Error("LOOSER!!! STOP Execution!"); } */ return trueCheck; } catch(error) { console.log('new order problem:', error); throw error; } } return false; } async function getBalance(token) { let balanceToken; try { let balanceAll = await exchange.fetchBalance(); balanceToken = balanceAll.free[token]; if (balanceToken === undefined) { balanceToken = 0; } return balanceToken; } catch (e) { throw e; } } // KUPI PRODAJ TOKEN samo vo USDT, USDTamount = za kolku USDT kupuvame base token async function buyToken(symbol,USDTamount) { let order; try { order = await exchange.createOrder (symbol, 'market', 'buy', null, null, {'quoteOrderQty': USDTamount}); while (order.status !== "closed") { order = await exchange.fetchOrder(order.id, order.symbol); } if (order.status === "closed") { console.log('Buy ',symbol.split('/')[0],' for ', USDTamount, symbol.split('/')[1]); console.log(colors.blue('Transaction closed')); } return true; } catch (e) { throw e; } } // KUPI PRODAJ TOKEN za site, amount = kolicina na kupuvanje za base token async function buyTokenAll(symbol,amount) { let order; try { order = await exchange.createOrder (symbol, 'market', 'buy', amount); while (order.status !== "closed") { order = await exchange.fetchOrder(order.id, order.symbol); } if (order.status === "closed") { console.log('Buy ',amount,symbol.split('/')[0]); //console.log('Transaction closed'); } return true; } catch (e) { throw e; } } // PRODAJ TOKEN - ZA TEST, amount = kolicina na prodavaje na base token async function sellToken(ticker,amount) { let price,order; try { price = await exchange.fetchTicker(ticker); //formattedAmount = await exchange.amountToPrecision(ticker, qty); order = await exchange.createOrder(ticker, 'market', 'sell', amount); while (order.status !== "closed") { try { order = await exchange.fetchOrder(order.id, order.symbol); if (order.status === "expired") {break;}; } catch (e) { throw e; } } console.log('================> PRODADENO:',amount,ticker.split('/')[0],'na',ticker.split('/')[1],'!'); } catch (e) { console.log('INFO:',ticker,amount, e.message); throw e; } finally {} } async function addBadToken(arbitrage_type, symbol1, symbol2,symbol3, profit, prethoden_profit) { let badCombination; //,realfee; //realfee = 100 - ((profit-prethoden_profit+initial_investment)/initial_investment*100); // console.log(realfee); if ((profit - prethoden_profit) < (-BAD_TOKEN_MAX_LOSE)) { badCombination = { "arbitrage_type": arbitrage_type, "symbol1": symbol1, "symbol2": symbol2, "symbol3": symbol3 }; badTokensInfo.push(badCombination); console.log('--> Added to bad tokens list!!!'); } } // Define a function that takes four input parameters async function findRealFee(arbitrage_type, symbol1, symbol2, symbol3) { // Loop through the array below for (let item of badTokensInfo) { // Check if the input parameters match the item in all four properties if ( arbitrage_type === item.arbitrage_type && symbol1 === item.symbol1 && symbol2 === item.symbol2 && symbol3 === item.symbol3 ) { // Return the realfee value of the item //console.log('realfee',item.realfee); console.log('--> not real fee data!!! Go to next combination.'); return false; } } // If no match is found, return 0 return true; } async function consolePrint() { process.stdout.write('.'); } async function marketNotional_ (symbol) { let minNotional = await exchange.market(symbol); // console.log (symbol, minNotional['limits']['amount']['min']); return minNotional['limits']['amount']['min']; } async function marketPrecision_ (symbol) { let precision1 = await exchange.market(symbol); return precision1['precision']['amount']; } function countDecimals (number) { if (Math.floor (number) === number) { // Return 0 as the number of decimals return 0; } // Convert the number to a string in scientific notation let parts = number.toExponential ().split ('e'); // Return the absolute value of the exponent as the number of decimals //console.log('countdecimals',number, 'return', Math.abs (parts[1])); return Math.abs (parts[1]); /* console.log('countdecimals',number); // Check if the number is an integer, meaning it has no decimals // Convert the number to a string and split it by the decimal point let parts = number.toString ().split ("."); // Return the length of the second part, which is the number of decimals return parts[1].length; */ } // Define a function that returns the dust limit for the coin function getDustLimit(coin) { // Get the market symbol for the coin and USDT pair let symbol = coin + '/USDT'; // Get the market information for the symbol from the markets dictionary let market = markets[symbol]; // Get the dust limit from the market information let dustLimit = market['limits']['amount']['min']; // Return the dust limit return dustLimit; } // Define a function that returns the number of decimal places for the coin async function getDecimals(symbol) { // Get the market information for the symbol from the markets dictionary let market = await exchange.market(symbol); // Get the number of decimal places from the market information let decimals = market['precision']['amount']; // Return the number of decimal places return decimals; } async function setSymbolPrecision(symbol, qty) { let precision1, precision2; let qtyNumberPrecision; let market = await exchange.market(symbol); precision1 = market['precision']['amount']; //console.log('precision',precision1 ) if (Number.isInteger(precision1)) { precision2 = precision1; } else { precision2 = countDecimals(precision1); } let factor = Math.pow(10, precision2); if (factor !== 0) { qtyNumberPrecision = Math.trunc(qty*factor)/factor; } else { qtyNumberPrecision = Math.trunc(qty); } return qtyNumberPrecision; } function waiting(sec) { let time1 = Date.now(); while (true) { if ((Date.now() - time1) > sec*1000) { break; } } } async function getAllMexcTokens() { let niza = ['USDC/USDT', 'XWG/USDC','XWG/USDT','HOP/USDC','HOP/USDT','USDD/USDC','USDD/USDT','PAXG/USDC','PAXG/USDT','CGPT/USDC','CGPT/USDT','ENS/USDC','ENS/USDT','WBTC/USDC','WBTC/USDT','STETH/USDC','STETH/USDT','AZERO/USDC','AZERO/USDT','EUL/USDC','EUL/USDT','MX/USDC','MX/USDT','GAL/USDC','GAL/USDT','FITFI/USDC','FITFI/USDT','RAY/USDC','RAY/USDT','*****/USDC','*****/USDT','CEL/USDC','CEL/USDT']; return niza; } async function getAllBitMartTokens() { let niza = ['1INCH/USDT','AAVE/USDT','ACH/USDT','ADA/USDC','ADA/USDT','AGB/USDT','AGI/USDT','AGIX/USDT','AIDOGE/USDT','ALGO/USDC','ALGO/USDT','AMP/USDT','ANKR/UST','APE/USDC','APE/USDT','APT/USDT','ARB/USDT','ARKM/USDT','ARPA/USDT','ASTR/USDT','ATOM/BTC','ATOM/USDT','AUDIO/USDT','AVAX/USDT','AXS/USDT','BAT/USD','BCH/BTC','BCH/USDT','BEN/USDT','BLUR/USDT','BNB/BTC','BNB/USDT','BOB/USDT','BONE/USDT','BSV/USDT','BTC/DAI','BTC/TUSD','BTC/USDC','BTC/USDT','BTT/UDT','CAKE/USDT','CANDY/USDT','CAPO/USDT','CETUS/USDT','CFX/USDT','CGPT/USDT','CHZ/USDT','COMBO/USDT','CRO/USDT','CRV/USDT','CVX/USDT','CYBER/USDT','DAH/BTC','DASH/USDT','DOGE/USDC','DOGE/USDT','DOT/USDT','DYDX/USDT','DZOO/USDT','EDU/USDT','EGLD/USDT','ELF/USDT','ENJ/USDT','EOS/USDC','EOS/USDT','ETC/SDT','ETH/BTC','ETH/DAI','ETH/TUSD','ETH/USDC','ETH/USDT','EURT/USDT','FERC/USDT','FET/USDT','FIL/USDT','FLOKI/DOGE','FLOKI/USDT','FLOW/USDT','FLR/USD','FTM/USDT','FTT/USDT','FUN/USDT','FXS/USDC','GALA/USDC','GALA/USDT','GLMR/USDT','GMX/USDT','GRT/USDT','HBAR/USDT','HNT/USDT','HOOK/USDT','HT/USDT','CP/USDT','ID/USDT','IMX/USDT','IRON/USDT','JOE/USDT','KAS/USDT','KEY/USDT','KLAY/USDT','KNC/USDT','LADYS/USDT','LBR/USDT','LDO/USDC','LDO/USDT','LINK/TC','LINK/ETH','LINK/USDT','LMWR/USDT','LQTY/USDT','LRC/USDT','LTC/BTC','LTC/ETH','LTC/USDC','LTC/USDT','LUNC/USDT','MAGIC/USDT','MANA/USDT','MATIC/BT','MATIC/USDC','MATIC/USDT','MAV/USDT','MDT/USDT','MEME/USDT','MEMEBRC/USDT','MINA/USDT','MKR/BTC','MNT/USDT','MONG/USDT','MYRIA/USDT','NEAR/USDT','NE/ETH','NEO/USDT','NTRN/USDT','OBI/USDT','OKB/USDT','OP/USDC','OP/USDT','ORDI/USDT','OSMO/USDT','PAW/USDT','PENDLE/USDT','PEPE/USDT','PEPEBRC/USDT','PIABRC/USDT','PLAY/USDT','PVFYBO/USDT','QNT/USDT','RFD/USDT','RPL/USDT','RUNE/USDT','SAMO/USDT','SAND/USDC','SAND/USDT','SATS/USDT','SEI/USDT','SHIB/USD','SHIB/USDT','SHIBAI/USDT','SHRAP/USDT','SNX/USDT','SOL/USDC','SOL/USDT','SPONGE/USDT','SSWP/USDT','STPT/USDT','STX/USDT','SUI/USDT','SYS/USDT','THET/USDT','TIA/USDT','TON/USDT','TRB/USDT','TRX/BTC','TRX/ETH','TRX/USDC','TRX/USDT','TWT/USDT','UNI/USDT','VELA/USDT','VELO/USDT','VET/BTC','VET/ETH','VT/USDT','VMPX/USDT','WBTC/USDT','WEMIX/USDT','WLD/USDT','WOJAK/USDT','WSB/USDT','XAUT/USDT','XLM/BTC','XLM/ETH','XLM/USDC','XLM/USDT','XMR/BTC','XMR/UDT','XRD/USDT','XRP/USDT','XTZ/USDT','YFI/USDT','ZEC/USDT','ZIL/BTC','ZZZ/USDT','$LINA/USDT','$PRIME/USDT','$TRAC/USDT','$TURBO/USDT']; return niza; } async function getAllBitMexTokens() { let niza = ['BTC/USDT','BMEX/USDT','SOL/USDT','ETH/BTC','ETH/USDT','TRX/USDT','UNI/USDT','LINK/USDT','APE/USDT','AXS/USDT','MATIC/USDT']; return niza; }
Write, Run & Share NodeJS code online using OneCompiler's NodeJS online compiler for free. It's one of the robust, feature-rich online compilers for NodeJS language,running on the latest LTS version NodeJS 16.14.2. Getting started with the OneCompiler's NodeJS editor is easy and fast. The editor shows sample boilerplate code when you choose language as NodeJS and start coding. You can provide the dependencies in package.json
.
Node.js is a free and open-source server environment. Node.js is very popular in recent times and a large number of companies like Microsoft, Paypal, Uber, Yahoo, General Electric and many others are using Node.js.
Google chrome's javascript engine V8
and is pretty fast.Asynchronous
, event-driven
and works on single-thread model
thus eliminating the dis-advantages of multi-thread model.Express is one of the most popular web application framework in the NodeJS echosystem.
let moment = require('moment');
console.log(moment().format('MMMM Do YYYY, h:mm:ss a'));
const _ = require("lodash");
let colors = ['blue', 'green', 'yellow', 'red'];
let firstElement = _.first(colors);
let lastElement = _.last(colors);
console.log(`First element: ${firstElement}`);
console.log(`Last element: ${lastElement}`);
Following are the libraries supported by OneCompiler's NodeJS compiler.