diff --git a/config.default.jsonc b/config.default.jsonc index 1d295cc3..8d29b138 100644 --- a/config.default.jsonc +++ b/config.default.jsonc @@ -43,7 +43,6 @@ /** List of supported exchanges **/ "exchanges": [ - "CoinDeal", "Resfinex", "P2PB2B" ], diff --git a/trade/api/coindeal_api.js b/trade/api/coindeal_api.js deleted file mode 100644 index ae4a99fd..00000000 --- a/trade/api/coindeal_api.js +++ /dev/null @@ -1,291 +0,0 @@ -const axios = require('axios'); -const FormData = require('form-data'); - -module.exports = function() { - const DEFAULT_HEADERS = { - 'Accept': 'application/json', - }; - - let WEB_BASE = ''; // API server like https://apigateway.coindeal.com - let config = { - 'apiKey': '', - 'secret_key': '', - 'tradePwd': '', - }; - let log = {}; - - function protectedRequest(path, data, type = 'get') { - let url = `${WEB_BASE}${path}`; - const params = []; - for (const key in data) { - const v = data[key]; - params.push(key + '=' + v); - } - const paramsString = params.join('&'); - if (paramsString && type !== 'post') { - url = url + '?' + paramsString; - } - let headersWithSign = Object.assign({ 'Authorization': setSign() }, DEFAULT_HEADERS); - - return new Promise((resolve, reject) => { - try { - - let httpOptions; - - if (type === 'get' || type === 'delete') { - - httpOptions = { - url: url, - method: type, - timeout: 10000, - headers: headersWithSign, - }; - - axios(httpOptions) - .then(function(response) { - const data = response.data; - resolve(data); - }) - .catch(function(error) { - // We can get 4xx with data - if (error.response && typeof error.response.data === 'object' && Object.keys(error.response.data).length !== 0) { - log.log(`${type.toUpperCase()}-request to ${url} with data ${JSON.stringify(data)} failed. ${error}. Reply data: ${JSON.stringify(error.response.data)}.`); - resolve(error.response.data); - } else if ( - type === 'delete' && - error.response && - error.response.status === 404 && - typeof error.response.data === 'string' && - error.response.data.includes('') && - error.response.data.includes('404 Not Found') - ) { - log.log(`${type.toUpperCase()}-request to ${url} with data ${JSON.stringify(data)} failed. ${error}. We assume that user doesn't have this order (but this may be a temporary server error, can't be sure).`); - if (error.response.status < 500) { - resolve(error.response.data); - } else { - reject(error.response.data); - } - } else { - log.log(`${type.toUpperCase()}-request to ${url} with data ${JSON.stringify(data)} failed. ${error}.`); - reject(error); - } - }); - } else { // post - const form = new FormData(); - Object.keys(data).forEach((key) => { - form.append(key, data[key]); - }); - - headersWithSign = Object.assign(headersWithSign, form.getHeaders()); - httpOptions = { - timeout: 10000, - headers: headersWithSign, - }; - - axios.post(url, form, httpOptions) - .then(function(response) { - const data = response.data; - resolve(data); - }) - .catch(function(error) { - // We can get 4xx with data - if (error.response && typeof error.response.data === 'object' && Object.keys(error.response.data).length !== 0) { - log.log(`${type.toUpperCase()}-request to ${url} with data ${JSON.stringify(data)} failed. ${error}. Reply data: ${JSON.stringify(error.response.data)}.`); - if (error.response.status < 500) { - resolve(error.response.data); - } else { - reject(error.response.data); - } - } else { - log.log(`${type.toUpperCase()}-request to ${url} with data ${JSON.stringify(data)} failed. ${error}.`); - reject(error); - } - }); - } - } catch (err) { - log.log(`Processing of ${type}-request to ${url} with data ${JSON.stringify(data)} failed. ${err}.`); - reject(null); - } - }); - } - - function publicRequest(url, data, type = 'get') { - return new Promise((resolve, reject) => { - try { - const httpOptions = { - url: url, - method: type, - timeout: 10000, - }; - axios(httpOptions) - .then(function(response) { - const data = response.data; - resolve(data); - }) - .catch(function(error) { - // We can get 4xx with data - if (error.response && typeof error.response.data === 'object' && Object.keys(error.response.data).length !== 0) { - log.log(`${type.toUpperCase()}-request to ${url} failed. ${error}. Reply data: ${JSON.stringify(error.response.data)}.`); - if (error.response.status < 500) { - resolve(error.response.data); - } else { - reject(error.response.data); - } - } else { - log.log(`${type.toUpperCase()}-request to ${url} failed. ${error}.`); - reject(error); - } - }); - - } catch (err) { - log.log(`Processing of ${type}-request to ${url} failed. ${err}.`); - reject(null); - } - }); - } - - function setSign() { - signString = 'Basic '; - signString += Buffer.from(config.apiKey + ':' + config.secret_key).toString('base64'); - return signString; - } - - const EXCHANGE_API = { - - setConfig: function(apiServer, apiKey, secretKey, tradePwd, logger, publicOnly = false) { - - if (apiServer) { - WEB_BASE = apiServer; - } - - if (logger) { - log = logger; - } - - if (!publicOnly) { - config = { - 'apiKey': apiKey, - 'secret_key': secretKey, - 'tradePwd': tradePwd || '', - }; - } - - }, - - /** - * ------------------------------------------------------------------ - * (Get user balances) - * ------------------------------------------------------------------ - */ - getUserAssets: function() { - return protectedRequest('/api/v1/trading/balance'); - }, - /** - * ------------------------------------------------------------------ - * (Get user open orders) - * ------------------------------------------------------------------ - */ - getUserNowEntrustSheet: function(coinFrom, coinTo) { - const data = {}; - data.symbol = coinFrom + coinTo; - // no limit/size parameter according to docs - // https://apigateway.coindeal.com/api/doc#operation/v1getOrder - return protectedRequest('/api/v1/order', data); - }, - /** - * ------------------------------------------------------------------ - * (Place a Limit order) - * @param symbol string "ADMBTC" - * @param amount float - * @param price float - * @param side string buy, sell - * ------------------------------------------------------------------ - */ - addEntrustSheet: function(symbol, amount, price, side) { - const data = {}; - data.symbol = symbol; - data.price = price; - data.quantity = amount; - data.side = side; - data.type = 'limit'; - return protectedRequest('/api/v1/order', data, 'post'); - }, - /** - * ------------------------------------------------------------------ - * (Cancel the order) - * @param entrustSheetId string - * ------------------------------------------------------------------ - */ - cancelEntrustSheet: function(entrustSheetId) { - const data = {}; - return protectedRequest(`/api/v1/order/${entrustSheetId}`, data, 'delete'); - }, - - /** - * ------------------------------------------------------------------ - * (Get the price data) - * @param symbol ADMBTC - * ------------------------------------------------------------------ - */ - orderBook: function(symbol, size) { - const data = {}; - // default limit/size is 100; - // no limit according to docs; 0 - full orderbook otherwise number of levels - // https://apigateway.coindeal.com/api/doc#operation/v1getPublicOrderbookCurrencyPair - if (size) { - data.limit = size; - } else { - data.limit = 0; - } - return publicRequest(`${WEB_BASE}/api/v1/public/orderbook/${symbol}`, data); - }, - - /** - * Get trades history - * @param pair Trading pair, like BTCUSDT - * @param {Number} limit Default: 100. — It is wrong, tested: max limit = 20 - * @param {String} sort Enum: "DESC", "ASC". Default: "DESC" - * @param {String} by Enum: "timestamp", "id". Default: "timestamp". Filter field: timestamp in milliseconds or id - * @param {String} from If filtered by timestamp, then timestamp in millisecond otherwise trade id. Example: from=1572356518965 - * @param {String} till If filtered by timestamp, then timestamp in millisecond otherwise trade id. Example: from=1572356518965 - * @param {Number} offset Example: offset=10 - * @return {Array of Object} Last trades - */ - getTradesHistory: function(symbol, limit, sort, by, from, till, offset) { - const data = {}; - data.symbol = symbol; - if (limit) data.limit = limit; - if (sort) data.sort = sort; - if (by) data.by = by; - if (from) data.from = from; - if (till) data.till = till; - if (offset) data.offset = offset; - return protectedRequest(`/api/v2/history/trades`, data, 'get'); - }, - - /** - * ------------------------------------------------------------------ - * (Get the deposit address) - * @param symbol ADM - * ------------------------------------------------------------------ - */ - getDepositAddress: function(symbol) { - const data = {}; - return protectedRequest(`/api/v1/deposits/${symbol}/addresses`, data); - }, - - /** - * ------------------------------------------------------------------ - * (Get stats) - * @param symbol eth_btc - * ------------------------------------------------------------------ - */ - stats: function(symbol) { - return publicRequest(`https://coinmarketcap.coindeal.com/api/v1/ticker`); - }, - - - }; - - return EXCHANGE_API; -}; diff --git a/trade/settings/tradeParams_coindeal.js b/trade/settings/tradeParams_coindeal.js deleted file mode 100644 index fe1cb32f..00000000 --- a/trade/settings/tradeParams_coindeal.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = { - 'mm_buyPercent': 0.67, - 'mm_minInterval': 60000, - 'mm_maxInterval': 360000, - 'mm_isActive': false, - 'mm_minAmount': 0.1, - 'mm_maxAmount': 202, - 'mm_Policy': 'optimal', - 'mm_isOrderBookActive': true, - 'mm_orderBookHeight': 10, - 'mm_orderBookOrdersCount': 10, - 'mm_orderBookMaxOrderPercent': 50, - 'mm_isLiquidityActive': false, - 'mm_liquiditySellAmount': 100, - 'mm_liquidityBuyQuoteAmount': 50, - 'mm_liquiditySpreadPercent': 2, - 'mm_liquidityTrend': 'middle', - 'mm_isPriceWatcherActive': false, - 'mm_priceWatcherLowPriceInSourceCoin': 0, - 'mm_priceWatcherMidPriceInSourceCoin': 0, - 'mm_priceWatcherHighPriceInSourceCoin': 0, - 'mm_priceWatcherDeviationPercent': 0, - 'mm_priceWatcherSource': '#', - 'mm_priceWatcherSourcePolicy': 'smart', - 'mm_priceWatcherAction': 'fill', -}; diff --git a/trade/trader_coindeal.js b/trade/trader_coindeal.js deleted file mode 100644 index 0bc544e6..00000000 --- a/trade/trader_coindeal.js +++ /dev/null @@ -1,432 +0,0 @@ -const Coindeal = require('./api/coindeal_api'); -const utils = require('../helpers/utils'); - -// API endpoints: -// https://apigateway.coindeal.com - -const apiServer = 'https://apigateway.coindeal.com'; -const exchangeName = 'CoinDeal'; - -module.exports = (apiKey, secretKey, pwd, log, publicOnly = false) => { - const CoindealClient = Coindeal(); - - CoindealClient.setConfig(apiServer, apiKey, secretKey, pwd, log, publicOnly); - - // CoinDeal API doesn't provide market info - const defaultMarketInfo = { - coin1Decimals: 8, - coin2Decimals: 8, - }; - - return { - get markets() { - return {}; - }, - marketInfo(pair) { - pair = pair.toUpperCase().trim(); - const [coin1, coin2] = pair.split('/'); - return { - ...defaultMarketInfo, - pairPlain: pair, - coin1, - coin2, - }; - }, - features() { - return { - getMarkets: false, - placeMarketOrder: false, - getDepositAddress: true, - getDepositAddressLimit: 'Only created on website', - createDepositAddressWithWebsiteOnly: true, - getFundHistory: false, - getFundHistoryImplemented: false, - allowAmountForMarketBuy: false, - amountForMarketOrderNecessary: false, - }; - }, - - getBalances(nonzero = true) { - return new Promise((resolve, reject) => { - CoindealClient.getUserAssets().then(function(data) { - try { - let assets = data; - if (!assets) { - assets = []; - } - let result = []; - assets.forEach((crypto) => { - result.push({ - code: crypto.symbol, - free: +crypto.available, - freezed: +crypto.reserved, - total: +crypto.available + +crypto.reserved, - btc: +crypto.estimatedBalanceBtc, - usd: +crypto.estimatedBalanceUsd, - pending: +crypto.pending, - }); - }); - if (nonzero) { - result = result.filter((crypto) => crypto.free || crypto.freezed); - } - resolve(result); - } catch (e) { - resolve(false); - log.warn('Error while processing getBalances() request: ' + e); - }; - }).catch((err) => { - log.warn(`API request getBalances(nonzero: ${nonzero}) of ${utils.getModuleName(module.id)} module failed. ${err}`); - resolve(undefined); - }); - }); - }, - getOpenOrders(pair) { - pair_ = formatPairName(pair); - return new Promise((resolve, reject) => { - CoindealClient.getUserNowEntrustSheet(pair_.coin1, pair_.coin2).then(function(data) { - try { - let openOrders = data; - if (!openOrders) { - openOrders = []; - } - - const result = []; - openOrders.forEach((order) => { - let orderStatus; - switch (order.status) { - case 'new': - orderStatus = 'new'; - break; - case 'canceled': - orderStatus = 'closed'; - break; - case 'filled': - orderStatus = 'filled'; - break; - case 'partiallyFilled': - orderStatus = 'part_filled'; - break; - case 'suspended': - break; - case 'expired': - break; - default: - break; - } - result.push({ - orderId: order.id?.toString(), - symbol: order.symbol, - price: +order.price, - side: order.side, // sell or buy - type: order.type, // limit or market, etc. - timestamp: order.createdAt, - amount: +order.cumQuantity, - amountExecuted: +order.cumQuantity - +order.quantity, - amountLeft: +order.quantity, - status: orderStatus, - uid: order.clientOrderId.toString(), - // coin2Amount: order.total, - coinFrom: order.baseCurrency, - coinTo: order.quoteCurrency, - }); - }); - - resolve(result); - - } catch (e) { - resolve(false); - log.warn('Error while processing getOpenOrders() request: ' + e); - }; - }).catch((err) => { - log.warn(`API request getOpenOrders(pair: ${pair}) of ${utils.getModuleName(module.id)} module failed. ${err}`); - resolve(undefined); - }); - }); - }, - cancelOrder(orderId) { - /* - Watch this: sometimes cancelled orders on Coindeal switched to "CANCELLING" state - Balances stay frozen. To fix them, you need to contact Coindeal support. - */ - return new Promise((resolve, reject) => { - CoindealClient.cancelEntrustSheet(orderId).then(function(data) { - try { - if (data.id) { - log.log(`Cancelling order ${orderId}…`); - resolve(true); - } else { - log.log(`Order ${orderId} not found. Unable to cancel it.`); - resolve(false); - } - } catch (e) { - if (e instanceof SyntaxError) { - /* - Watch this: Sometimes you'll get

The server returned a "404 Not Found".

instead of JSON. - This means this order does not exist. - */ - resolve(false); - log.warn(`Error while processing cancelOrder() request: ${e}. It seems the order ${orderId} does not exist.`); - } else { - resolve(false); - log.warn(`Error while processing cancelOrder() request: ${e}. Data object I've got: ${data}.`); - } - }; - }).catch((err) => { - log.warn(`API request cancelOrder(orderId: ${orderId}) of ${utils.getModuleName(module.id)} module failed. ${err}`); - resolve(undefined); - }); - }); - }, - getRates(pair) { - pair_ = formatPairName(pair); - return new Promise((resolve, reject) => { - CoindealClient.stats().then(function(data2) { - data2 = data2[pair_.coin1 + '_' + pair_.coin2]; - try { - if (data2) { - resolve({ - volume: +data2.baseVolume, - volumeInCoin2: +data2.quoteVolume, - high: +data2.high24hr, - low: +data2.low24hr, - ask: +data2.lowestAsk, - bid: +data2.highestBid, - }); - } else { - resolve(false); - } - } catch (e) { - resolve(false); - log.warn('Error while processing getRates() stats() request: ' + e); - }; - }).catch((err) => { - log.warn(`API request getRates(pair: ${pair}) of ${utils.getModuleName(module.id)} module failed. ${err}`); - resolve(undefined); - }); - }); - }, - - placeOrder(orderType, pair, price, coin1Amount, limit = 1, coin2Amount) { - - const pair_ = formatPairName(pair); - let output = ''; - let message; - const order = {}; - - const type = (orderType === 'sell') ? 'sell' : 'buy'; - - if (!this.marketInfo(pair)) { - log.warn(`Unable to place an order on ${exchangeName} exchange. I don't have info about market ${pair}.`); - return undefined; - } - - if (coin1Amount) { - coin1Amount = (+coin1Amount).toFixed(this.marketInfo(pair).coin1Decimals); - } - if (coin2Amount) { - coin2Amount = (+coin2Amount).toFixed(this.marketInfo(pair).coin2Decimals); - } - if (price) { - price = (+price).toFixed(this.marketInfo(pair).coin2Decimals); - } - - if (limit) { // Limit order - output = `${orderType} ${coin1Amount} ${pair_.coin1.toUpperCase()} at ${price} ${pair_.coin2.toUpperCase()}.`; - - return new Promise((resolve, reject) => { - CoindealClient.addEntrustSheet(pair_.pair, coin1Amount, price, type).then(function(data) { - try { - const result = data; - if (result && result.id) { - message = `Order placed to ${output} Order Id: ${result.id.toString()}.`; - log.info(message); - order.orderId = result.id.toString(); - order.message = message; - resolve(order); - } else { - message = `Unable to place order to ${output} Check parameters and balances. Description: ${result.message}`; - if (result.errors && result.errors.errors) { - message += `: ${result.errors.errors.join(', ')}`; - } - log.warn(message); - order.orderId = false; - order.message = message; - resolve(order); - } - } catch (e) { - message = 'Error while processing placeOrder() request: ' + e; - log.warn(message); - order.orderId = false; - order.message = message; - resolve(order); - }; - }).catch((err) => { - log.warn(`API request CoindealClient.addEntrustSheet-limit(pair: ${pair_.pair}, coin1Amount: ${coin1Amount}, price: ${price}, type: ${type}) of ${utils.getModuleName(module.id)} module failed. ${err}`); - resolve(undefined); - }); - }); - - } else { // Market order - let size = 0; - if (orderType === 'sell') { - if (coin1Amount) { - size = coin1Amount; - output = `${orderType} ${coin1Amount} ${pair_.coin1.toUpperCase()} at Market Price on ${pair} market.`; - } else { - message = `Unable to place order to ${orderType} ${pair_.coin1.toUpperCase()} at Market Price on ${pair} market. Set ${pair_.coin1.toUpperCase()} amount.`; - log.warn(message); - order.orderId = false; - order.message = message; - return order; - } - } else { // buy - if (coin2Amount) { - output = `${orderType} ${pair_.coin1} for ${coin2Amount} ${pair_.coin2.toUpperCase()} at Market Price on ${pair} market.`; - } else { - message = `Unable to place order to ${orderType} ${pair_.coin1.toUpperCase()} for ${pair_.coin2.toUpperCase()} at Market Price on ${pair} market. Set ${pair_.coin2.toUpperCase()} amount.`; - log.warn(message); - order.orderId = false; - order.message = message; - return order; - } - } - - message = `Unable to place order to ${output} CoinDeal doesn't support Market orders yet.`; - log.warn(message); - order.orderId = false; - order.message = message; - return order; - - } - }, - getOrderBook(pair) { - const pair_ = formatPairName(pair); - return new Promise((resolve, reject) => { - CoindealClient.orderBook(pair_.pair).then(function(data) { - try { - let book = data; - if (!book) { - book = []; - } - const result = { - bids: new Array(), - asks: new Array(), - }; - book.ask.forEach((crypto) => { - result.asks.push({ - amount: +crypto.amount, - price: +crypto.price, - count: 1, - type: 'ask-sell-right', - }); - }); - result.asks.sort(function(a, b) { - return parseFloat(a.price) - parseFloat(b.price); - }); - book.bid.forEach((crypto) => { - result.bids.push({ - amount: +crypto.amount, - price: +crypto.price, - count: 1, - type: 'bid-buy-left', - }); - }); - result.bids.sort(function(a, b) { - return parseFloat(b.price) - parseFloat(a.price); - }); - resolve(result); - } catch (e) { - resolve(false); - log.warn('Error while processing orderBook() request: ' + e); - }; - }).catch((err) => { - log.warn(`API request getOrderBook(pair: ${pair}) of ${utils.getModuleName(module.id)} module failed. ${err}`); - resolve(undefined); - }); - }); - }, - - getTradesHistory(pair, limit, sort, by, from, till, offset) { - const pair_ = formatPairName(pair); - return new Promise((resolve, reject) => { - CoindealClient.getTradesHistory(pair_.pair, limit, sort, by, from, till, offset).then(function(data) { - try { - let trades = data; - if (!trades) { - trades = []; - } - - const result = []; - trades.forEach((trade) => { - - result.push({ - coin1Amount: +trade.quantity, // amount in coin1 - price: +trade.price, // trade price - coin2Amount: +trade.quantity * +trade.price, // quote in coin2 - // trade.timestamp is like '2021-04-21 22:41:28' (ISO) - dateOri: trade.timestamp, - date: new Date(trade.timestamp + '+0000').getTime(), // must be as utils.unixTimeStampMs(): 1641121688194 - 1 641 121 688 194 - type: trade.side?.toLowerCase(), // 'buy' or 'sell' - tradeId: trade.id?.toString(), - // Additionally CoinDeal provides: clientOrderId, orderId, symbol, fee - }); - }); - - // We need ascending sort order - result.sort(function(a, b) { - return parseFloat(a.date) - parseFloat(b.date); - }); - resolve(result); - - } catch (e) { - resolve(false); - log.warn('Error while processing getTradesHistory() request: ' + e); - }; - }).catch((err) => { - log.warn(`API request getTradesHistory(pair: ${pair}) of ${utils.getModuleName(module.id)} module failed. ${err}`); - resolve(undefined); - }); - }); - }, - - getDepositAddress(coin) { - return new Promise((resolve) => { - CoindealClient.getDepositAddress(coin).then(function(data) { - try { - if (data?.items?.length) { - resolve(data.items.map(({ address }) => ({ network: null, address }))); - } else { - resolve(false); - } - } catch (e) { - resolve(false); - log.warn('Error while processing getDepositAddress() request: ' + e); - }; - }).catch((err) => { - log.warn(`API request getDepositAddress(coin: ${coin}) of ${utils.getModuleName(module.id)} module failed. ${err}`); - resolve(undefined); - }); - }); - }, - }; -}; - -function formatPairName(pair) { - let pair_; let coin1; let coin2; - if (pair.indexOf('-') > -1) { - pair_ = pair.replace('-', '').toUpperCase(); - [coin1, coin2] = pair.split('-'); - } else if (pair.indexOf('_') > -1) { - pair_ = pair.replace('_', '').toUpperCase(); - [coin1, coin2] = pair.split('_'); - } else { - pair_ = pair.replace('/', '').toUpperCase(); - [coin1, coin2] = pair.split('/'); - } - - return { - pair: pair_, - coin1: coin1.toUpperCase(), - coin2: coin2.toUpperCase(), - }; -}