UNPKG

kamiswiss-ccxt

Version:

A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges

1,177 lines (1,141 loc) 57.5 kB
'use strict'; // --------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, DDoSProtection, InvalidNonce, AuthenticationError, InvalidAddress } = require ('./base/errors'); const { ROUND } = require ('./base/functions/number'); // --------------------------------------------------------------------------- module.exports = class binance extends Exchange { describe () { return this.deepExtend (super.describe (), { 'id': 'binance', 'name': 'Binance', 'countries': [ 'JP', 'MT' ], // Japan, Malta 'rateLimit': 500, 'certified': true, // new metainfo interface 'has': { 'fetchDepositAddress': true, 'CORS': false, 'fetchBidsAsks': true, 'fetchTickers': true, 'fetchOHLCV': true, 'fetchMyTrades': true, 'fetchOrder': true, 'fetchOrders': true, 'fetchOpenOrders': true, 'fetchClosedOrders': true, 'withdraw': true, 'fetchFundingFees': true, 'fetchDeposits': true, 'fetchWithdrawals': true, 'fetchTransactions': false, }, 'timeframes': { '1m': '1m', '3m': '3m', '5m': '5m', '15m': '15m', '30m': '30m', '1h': '1h', '2h': '2h', '4h': '4h', '6h': '6h', '8h': '8h', '12h': '12h', '1d': '1d', '3d': '3d', '1w': '1w', '1M': '1M', }, 'urls': { 'logo': 'https://user-images.githubusercontent.com/1294454/29604020-d5483cdc-87ee-11e7-94c7-d1a8d9169293.jpg', 'api': { 'web': 'https://www.binance.com', 'wapi': 'https://api.binance.com/wapi/v3', 'public': 'https://api.binance.com/api/v1', 'private': 'https://api.binance.com/api/v3', 'v3': 'https://api.binance.com/api/v3', 'v1': 'https://api.binance.com/api/v1', }, 'www': 'https://www.binance.com', 'referral': 'https://www.binance.com/?ref=10205187', 'doc': [ 'https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md', 'https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md', ], 'fees': 'https://www.binance.com/en/fee/schedule', }, 'api': { 'web': { 'get': [ 'exchange/public/product', 'assetWithdraw/getAllAsset.html', ], }, 'wapi': { 'post': [ 'withdraw', 'sub-account/transfer', ], 'get': [ 'depositHistory', 'withdrawHistory', 'depositAddress', 'accountStatus', 'systemStatus', 'apiTradingStatus', 'userAssetDribbletLog', 'tradeFee', 'assetDetail', 'sub-account/list', 'sub-account/transfer/history', 'sub-account/assets', ], }, 'v3': { 'get': [ 'ticker/price', 'ticker/bookTicker', ], }, 'public': { 'get': [ 'ping', 'time', 'depth', 'trades', 'aggTrades', 'historicalTrades', 'klines', 'ticker/24hr', 'ticker/allPrices', 'ticker/allBookTickers', 'ticker/price', 'ticker/bookTicker', 'exchangeInfo', ], 'put': [ 'userDataStream' ], 'post': [ 'userDataStream' ], 'delete': [ 'userDataStream' ], }, 'private': { 'get': [ 'order', 'openOrders', 'allOrders', 'account', 'myTrades', ], 'post': [ 'order', 'order/test', ], 'delete': [ 'order', ], }, }, 'fees': { 'trading': { 'tierBased': false, 'percentage': true, 'taker': 0.001, 'maker': 0.001, }, }, 'commonCurrencies': { 'BCC': 'BCC', // kept for backward-compatibility https://github.com/ccxt/ccxt/issues/4848 'YOYO': 'YOYOW', }, // exchange-specific options 'options': { 'fetchTradesMethod': 'publicGetAggTrades', 'fetchTickersMethod': 'publicGetTicker24hr', 'defaultTimeInForce': 'GTC', // 'GTC' = Good To Cancel (default), 'IOC' = Immediate Or Cancel 'defaultLimitOrderType': 'limit', // or 'limit_maker' 'hasAlreadyAuthenticatedSuccessfully': false, 'warnOnFetchOpenOrdersWithoutSymbol': true, 'recvWindow': 5 * 1000, // 5 sec, binance default 'timeDifference': 0, // the difference between system clock and Binance clock 'adjustForTimeDifference': false, // controls the adjustment logic upon instantiation 'parseOrderToPrecision': false, // force amounts and costs in parseOrder to precision 'newOrderRespType': { 'market': 'FULL', // 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills 'limit': 'RESULT', // we change it from 'ACK' by default to 'RESULT' }, }, 'exceptions': { 'API key does not exist': AuthenticationError, 'Order would trigger immediately.': InvalidOrder, 'Account has insufficient balance for requested action.': InsufficientFunds, 'Rest API trading is not enabled.': ExchangeNotAvailable, '-1000': ExchangeNotAvailable, // {"code":-1000,"msg":"An unknown error occured while processing the request."} '-1013': InvalidOrder, // createOrder -> 'invalid quantity'/'invalid price'/MIN_NOTIONAL '-1021': InvalidNonce, // 'your time is ahead of server' '-1022': AuthenticationError, // {"code":-1022,"msg":"Signature for this request is not valid."} '-1100': InvalidOrder, // createOrder(symbol, 1, asdf) -> 'Illegal characters found in parameter 'price' '-1104': ExchangeError, // Not all sent parameters were read, read 8 parameters but was sent 9 '-1128': ExchangeError, // {"code":-1128,"msg":"Combination of optional parameters invalid."} '-2010': ExchangeError, // generic error code for createOrder -> 'Account has insufficient balance for requested action.', {"code":-2010,"msg":"Rest API trading is not enabled."}, etc... '-2011': OrderNotFound, // cancelOrder(1, 'BTC/USDT') -> 'UNKNOWN_ORDER' '-2013': OrderNotFound, // fetchOrder (1, 'BTC/USDT') -> 'Order does not exist' '-2014': AuthenticationError, // { "code":-2014, "msg": "API-key format invalid." } '-2015': AuthenticationError, // "Invalid API-key, IP, or permissions for action." }, }); } nonce () { return this.milliseconds () - this.options['timeDifference']; } async loadTimeDifference () { const response = await this.publicGetTime (); const after = this.milliseconds (); this.options['timeDifference'] = parseInt (after - response['serverTime']); return this.options['timeDifference']; } async fetchMarkets (params = {}) { const response = await this.publicGetExchangeInfo (params); if (this.options['adjustForTimeDifference']) { await this.loadTimeDifference (); } let markets = response['symbols']; let result = []; for (let i = 0; i < markets.length; i++) { let market = markets[i]; let id = market['symbol']; // "123456" is a "test symbol/market" if (id === '123456') continue; let baseId = market['baseAsset']; let quoteId = market['quoteAsset']; let base = this.commonCurrencyCode (baseId); let quote = this.commonCurrencyCode (quoteId); let symbol = base + '/' + quote; let filters = this.indexBy (market['filters'], 'filterType'); let precision = { 'base': market['baseAssetPrecision'], 'quote': market['quotePrecision'], 'amount': market['baseAssetPrecision'], 'price': market['quotePrecision'], }; let active = (market['status'] === 'TRADING'); let entry = { 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'baseId': baseId, 'quoteId': quoteId, 'info': market, 'active': active, 'precision': precision, 'limits': { 'amount': { 'min': Math.pow (10, -precision['amount']), 'max': undefined, }, 'price': { 'min': undefined, 'max': undefined, }, 'cost': { 'min': -1 * Math.log10 (precision['amount']), 'max': undefined, }, }, }; if ('PRICE_FILTER' in filters) { let filter = filters['PRICE_FILTER']; // PRICE_FILTER reports zero values for maxPrice // since they updated filter types in November 2018 // https://github.com/ccxt/ccxt/issues/4286 // therefore limits['price']['max'] doesn't have any meaningful value except undefined entry['limits']['price'] = { 'min': this.safeFloat (filter, 'minPrice'), 'max': undefined, }; const maxPrice = this.safeFloat (filter, 'maxPrice'); if ((maxPrice !== undefined) && (maxPrice > 0)) { entry['limits']['price']['max'] = maxPrice; } entry['precision']['price'] = this.precisionFromString (filter['tickSize']); } if ('LOT_SIZE' in filters) { let filter = filters['LOT_SIZE']; entry['precision']['amount'] = this.precisionFromString (filter['stepSize']); entry['limits']['amount'] = { 'min': this.safeFloat (filter, 'minQty'), 'max': this.safeFloat (filter, 'maxQty'), }; } if ('MIN_NOTIONAL' in filters) { entry['limits']['cost']['min'] = parseFloat (filters['MIN_NOTIONAL']['minNotional']); } result.push (entry); } return result; } calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { let market = this.markets[symbol]; let key = 'quote'; let rate = market[takerOrMaker]; let cost = amount * rate; let precision = market['precision']['price']; if (side === 'sell') { cost *= price; } else { key = 'base'; precision = market['precision']['amount']; } cost = this.decimalToPrecision (cost, ROUND, precision, this.precisionMode); return { 'type': takerOrMaker, 'currency': market[key], 'rate': rate, 'cost': parseFloat (cost), }; } async fetchBalance (params = {}) { await this.loadMarkets (); let response = await this.privateGetAccount (params); let result = { 'info': response }; let balances = response['balances']; for (let i = 0; i < balances.length; i++) { let balance = balances[i]; let currency = balance['asset']; if (currency in this.currencies_by_id) currency = this.currencies_by_id[currency]['code']; let account = { 'free': parseFloat (balance['free']), 'used': parseFloat (balance['locked']), 'total': 0.0, }; account['total'] = this.sum (account['free'], account['used']); result[currency] = account; } return this.parseBalance (result); } async fetchOrderBook (symbol, limit = undefined, params = {}) { await this.loadMarkets (); let market = this.market (symbol); let request = { 'symbol': market['id'], }; if (limit !== undefined) { request['limit'] = limit; // default = maximum = 100 } let response = await this.publicGetDepth (this.extend (request, params)); let orderbook = this.parseOrderBook (response); orderbook['nonce'] = this.safeInteger (response, 'lastUpdateId'); return orderbook; } parseTicker (ticker, market = undefined) { const timestamp = this.safeInteger (ticker, 'closeTime'); const symbol = this.findSymbol (this.safeString (ticker, 'symbol'), market); const last = this.safeFloat (ticker, 'lastPrice'); return { 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'high': this.safeFloat (ticker, 'highPrice'), 'low': this.safeFloat (ticker, 'lowPrice'), 'bid': this.safeFloat (ticker, 'bidPrice'), 'bidVolume': this.safeFloat (ticker, 'bidQty'), 'ask': this.safeFloat (ticker, 'askPrice'), 'askVolume': this.safeFloat (ticker, 'askQty'), 'vwap': this.safeFloat (ticker, 'weightedAvgPrice'), 'open': this.safeFloat (ticker, 'openPrice'), 'close': last, 'last': last, 'previousClose': this.safeFloat (ticker, 'prevClosePrice'), // previous day close 'change': this.safeFloat (ticker, 'priceChange'), 'percentage': this.safeFloat (ticker, 'priceChangePercent'), 'average': undefined, 'baseVolume': this.safeFloat (ticker, 'volume'), 'quoteVolume': this.safeFloat (ticker, 'quoteVolume'), 'info': ticker, }; } async fetchTicker (symbol, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], }; const response = await this.publicGetTicker24hr (this.extend (request, params)); return this.parseTicker (response, market); } parseTickers (rawTickers, symbols = undefined) { const tickers = []; for (let i = 0; i < rawTickers.length; i++) { tickers.push (this.parseTicker (rawTickers[i])); } return this.filterByArray (tickers, 'symbol', symbols); } async fetchBidsAsks (symbols = undefined, params = {}) { await this.loadMarkets (); const response = await this.publicGetTickerBookTicker (params); return this.parseTickers (response, symbols); } async fetchTickers (symbols = undefined, params = {}) { await this.loadMarkets (); const method = this.options['fetchTickersMethod']; const response = await this[method] (params); return this.parseTickers (response, symbols); } parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) { return [ ohlcv[0], parseFloat (ohlcv[1]), parseFloat (ohlcv[2]), parseFloat (ohlcv[3]), parseFloat (ohlcv[4]), parseFloat (ohlcv[5]), ]; } async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], 'interval': this.timeframes[timeframe], }; if (since !== undefined) { request['startTime'] = since; } if (limit !== undefined) { request['limit'] = limit; // default == max == 500 } const response = await this.publicGetKlines (this.extend (request, params)); return this.parseOHLCVs (response, market, timeframe, since, limit); } parseTrade (trade, market = undefined) { if ('isDustTrade' in trade) { return this.parseDustTrade (trade, market); } // // aggregate trades // https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list // // { // "a": 26129, // Aggregate tradeId // "p": "0.01633102", // Price // "q": "4.70443515", // Quantity // "f": 27781, // First tradeId // "l": 27781, // Last tradeId // "T": 1498793709153, // Timestamp // "m": true, // Was the buyer the maker? // "M": true // Was the trade the best price match? // } // // recent public trades and old public trades // https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#recent-trades-list // https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#old-trade-lookup-market_data // // { // "id": 28457, // "price": "4.00000100", // "qty": "12.00000000", // "time": 1499865549590, // "isBuyerMaker": true, // "isBestMatch": true // } // // private trades // https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#account-trade-list-user_data // // { // "symbol": "BNBBTC", // "id": 28457, // "orderId": 100234, // "price": "4.00000100", // "qty": "12.00000000", // "commission": "10.10000000", // "commissionAsset": "BNB", // "time": 1499865549590, // "isBuyer": true, // "isMaker": false, // "isBestMatch": true // } // const timestamp = this.safeInteger2 (trade, 'T', 'time'); const price = this.safeFloat2 (trade, 'p', 'price'); const amount = this.safeFloat2 (trade, 'q', 'qty'); const id = this.safeString2 (trade, 'a', 'id'); let side = undefined; const orderId = this.safeString (trade, 'orderId'); if ('m' in trade) { side = trade['m'] ? 'sell' : 'buy'; // this is reversed intentionally } else if ('isBuyerMaker' in trade) { side = trade['isBuyerMaker'] ? 'sell' : 'buy'; } else { if ('isBuyer' in trade) { side = (trade['isBuyer']) ? 'buy' : 'sell'; // this is a true side } } let fee = undefined; if ('commission' in trade) { fee = { 'cost': this.safeFloat (trade, 'commission'), 'currency': this.commonCurrencyCode (trade['commissionAsset']), }; } let takerOrMaker = undefined; if ('isMaker' in trade) { takerOrMaker = trade['isMaker'] ? 'maker' : 'taker'; } let symbol = undefined; if (market === undefined) { let marketId = this.safeString (trade, 'symbol'); market = this.safeValue (this.markets_by_id, marketId); } if (market !== undefined) { symbol = market['symbol']; } return { 'info': trade, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'symbol': symbol, 'id': id, 'order': orderId, 'type': undefined, 'takerOrMaker': takerOrMaker, 'side': side, 'price': price, 'amount': amount, 'cost': price * amount, 'fee': fee, }; } async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], // 'fromId': 123, // ID to get aggregate trades from INCLUSIVE. // 'startTime': 456, // Timestamp in ms to get aggregate trades from INCLUSIVE. // 'endTime': 789, // Timestamp in ms to get aggregate trades until INCLUSIVE. // 'limit': 500, // default = 500, maximum = 1000 }; if (this.options['fetchTradesMethod'] === 'publicGetAggTrades') { if (since !== undefined) { request['startTime'] = since; request['endTime'] = this.sum (since, 3600000); } } if (limit !== undefined) { request['limit'] = limit; // default = 500, maximum = 1000 } // // Caveats: // - default limit (500) applies only if no other parameters set, trades up // to the maximum limit may be returned to satisfy other parameters // - if both limit and time window is set and time window contains more // trades than the limit then the last trades from the window are returned // - 'tradeId' accepted and returned by this method is "aggregate" trade id // which is different from actual trade id // - setting both fromId and time window results in error const method = this.safeValue (this.options, 'fetchTradesMethod', 'publicGetTrades'); const response = await this[method] (this.extend (request, params)); // // aggregate trades // // [ // { // "a": 26129, // Aggregate tradeId // "p": "0.01633102", // Price // "q": "4.70443515", // Quantity // "f": 27781, // First tradeId // "l": 27781, // Last tradeId // "T": 1498793709153, // Timestamp // "m": true, // Was the buyer the maker? // "M": true // Was the trade the best price match? // } // ] // // recent public trades and historical public trades // // [ // { // "id": 28457, // "price": "4.00000100", // "qty": "12.00000000", // "time": 1499865549590, // "isBuyerMaker": true, // "isBestMatch": true // } // ] // return this.parseTrades (response, market, since, limit); } parseOrderStatus (status) { const statuses = { 'NEW': 'open', 'PARTIALLY_FILLED': 'open', 'FILLED': 'closed', 'CANCELED': 'canceled', 'PENDING_CANCEL': 'canceling', // currently unused 'REJECTED': 'rejected', 'EXPIRED': 'expired', }; return this.safeString (statuses, status, status); } parseOrder (order, market = undefined) { const status = this.parseOrderStatus (this.safeString (order, 'status')); const symbol = this.findSymbol (this.safeString (order, 'symbol'), market); let timestamp = undefined; if ('time' in order) { timestamp = this.safeInteger (order, 'time'); } else if ('transactTime' in order) { timestamp = this.safeInteger (order, 'transactTime'); } let price = this.safeFloat (order, 'price'); const amount = this.safeFloat (order, 'origQty'); const filled = this.safeFloat (order, 'executedQty'); let remaining = undefined; let cost = this.safeFloat (order, 'cummulativeQuoteQty'); if (filled !== undefined) { if (amount !== undefined) { remaining = amount - filled; if (this.options['parseOrderToPrecision']) { remaining = parseFloat (this.amountToPrecision (symbol, remaining)); } remaining = Math.max (remaining, 0.0); } if (price !== undefined) { if (cost === undefined) { cost = price * filled; } } } const id = this.safeString (order, 'orderId'); let type = this.safeString (order, 'type'); if (type !== undefined) { type = type.toLowerCase (); if (type === 'market') { if (price === 0.0) { if ((cost !== undefined) && (filled !== undefined)) { if ((cost > 0) && (filled > 0)) { price = cost / filled; } } } } } let side = this.safeString (order, 'side'); if (side !== undefined) { side = side.toLowerCase (); } let fee = undefined; let trades = undefined; const fills = this.safeValue (order, 'fills'); if (fills !== undefined) { trades = this.parseTrades (fills, market); let numTrades = trades.length; if (numTrades > 0) { cost = trades[0]['cost']; fee = { 'cost': trades[0]['fee']['cost'], 'currency': trades[0]['fee']['currency'], }; for (let i = 1; i < trades.length; i++) { cost = this.sum (cost, trades[i]['cost']); fee['cost'] = this.sum (fee['cost'], trades[i]['fee']['cost']); } } } let average = undefined; if (cost !== undefined) { if (filled) { average = cost / filled; } if (this.options['parseOrderToPrecision']) { cost = parseFloat (this.costToPrecision (symbol, cost)); } } return { 'info': order, 'id': id, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'lastTradeTimestamp': undefined, 'symbol': symbol, 'type': type, 'side': side, 'price': price, 'amount': amount, 'cost': cost, 'average': average, 'filled': filled, 'remaining': remaining, 'status': status, 'fee': fee, 'trades': trades, }; } async createOrder (symbol, type, side, amount, price = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); // the next 5 lines are added to support for testing orders let method = 'privatePostOrder'; const test = this.safeValue (params, 'test', false); if (test) { method += 'Test'; params = this.omit (params, 'test'); } const uppercaseType = type.toUpperCase (); const newOrderRespType = this.safeValue (this.options['newOrderRespType'], type, 'RESULT'); const request = { 'symbol': market['id'], 'quantity': this.amountToPrecision (symbol, amount), 'type': uppercaseType, 'side': side.toUpperCase (), 'newOrderRespType': newOrderRespType, // 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills }; let timeInForceIsRequired = false; let priceIsRequired = false; let stopPriceIsRequired = false; if (uppercaseType === 'LIMIT') { priceIsRequired = true; timeInForceIsRequired = true; } else if ((uppercaseType === 'STOP_LOSS') || (uppercaseType === 'TAKE_PROFIT')) { stopPriceIsRequired = true; } else if ((uppercaseType === 'STOP_LOSS_LIMIT') || (uppercaseType === 'TAKE_PROFIT_LIMIT')) { stopPriceIsRequired = true; priceIsRequired = true; timeInForceIsRequired = true; } else if (uppercaseType === 'LIMIT_MAKER') { priceIsRequired = true; } if (priceIsRequired) { if (price === undefined) { throw new InvalidOrder (this.id + ' createOrder method requires a price argument for a ' + type + ' order'); } request['price'] = this.priceToPrecision (symbol, price); } if (timeInForceIsRequired) { request['timeInForce'] = this.options['defaultTimeInForce']; // 'GTC' = Good To Cancel (default), 'IOC' = Immediate Or Cancel } if (stopPriceIsRequired) { const stopPrice = this.safeFloat (params, 'stopPrice'); if (stopPrice === undefined) { throw new InvalidOrder (this.id + ' createOrder method requires a stopPrice extra param for a ' + type + ' order'); } else { params = this.omit (params, 'stopPrice'); request['stopPrice'] = this.priceToPrecision (symbol, stopPrice); } } const response = await this[method] (this.extend (request, params)); return this.parseOrder (response, market); } async fetchOrder (id, symbol = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); } await this.loadMarkets (); const market = this.market (symbol); const origClientOrderId = this.safeValue (params, 'origClientOrderId'); const request = { 'symbol': market['id'], }; if (origClientOrderId !== undefined) { request['origClientOrderId'] = origClientOrderId; } else { request['orderId'] = parseInt (id); } const response = await this.privateGetOrder (this.extend (request, params)); return this.parseOrder (response, market); } async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchOrders requires a symbol argument'); } await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], }; if (since !== undefined) { request['startTime'] = since; } if (limit !== undefined) { request['limit'] = limit; } const response = await this.privateGetAllOrders (this.extend (request, params)); // // [ // { // "symbol": "LTCBTC", // "orderId": 1, // "clientOrderId": "myOrder1", // "price": "0.1", // "origQty": "1.0", // "executedQty": "0.0", // "cummulativeQuoteQty": "0.0", // "status": "NEW", // "timeInForce": "GTC", // "type": "LIMIT", // "side": "BUY", // "stopPrice": "0.0", // "icebergQty": "0.0", // "time": 1499827319559, // "updateTime": 1499827319559, // "isWorking": true // } // ] // return this.parseOrders (response, market, since, limit); } async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); let market = undefined; const request = {}; if (symbol !== undefined) { market = this.market (symbol); request['symbol'] = market['id']; } else if (this.options['warnOnFetchOpenOrdersWithoutSymbol']) { const symbols = this.symbols; const numSymbols = symbols.length; const fetchOpenOrdersRateLimit = parseInt (numSymbols / 2); throw new ExchangeError (this.id + ' fetchOpenOrders WARNING: fetching open orders without specifying a symbol is rate-limited to one call per ' + fetchOpenOrdersRateLimit.toString () + ' seconds. Do not call this method frequently to avoid ban. Set ' + this.id + '.options["warnOnFetchOpenOrdersWithoutSymbol"] = false to suppress this warning message.'); } const response = await this.privateGetOpenOrders (this.extend (request, params)); return this.parseOrders (response, market, since, limit); } async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { const orders = await this.fetchOrders (symbol, since, limit, params); return this.filterBy (orders, 'status', 'closed'); } async cancelOrder (id, symbol = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); } await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], 'orderId': parseInt (id), // 'origClientOrderId': id, }; const response = await this.privateDeleteOrder (this.extend (request, params)); return this.parseOrder (response); } async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); } await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], }; if (limit !== undefined) { request['limit'] = limit; } const response = await this.privateGetMyTrades (this.extend (request, params)); // // [ // { // "symbol": "BNBBTC", // "id": 28457, // "orderId": 100234, // "price": "4.00000100", // "qty": "12.00000000", // "commission": "10.10000000", // "commissionAsset": "BNB", // "time": 1499865549590, // "isBuyer": true, // "isMaker": false, // "isBestMatch": true // } // ] // return this.parseTrades (response, market, since, limit); } async fetchMyDustTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { // // Bianance provides an opportunity to trade insignificant (i.e. non-tradable and non-withdrawable) // token leftovers (of any asset) into `BNB` coin which in turn can be used to pay trading fees with it. // The corresponding trades history is called the `Dust Log` and can be requested via the following end-point: // https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#dustlog-user_data // await this.loadMarkets (); const response = await this.wapiGetUserAssetDribbletLog (params); // { success: true, // results: { total: 1, // rows: [ { transfered_total: "1.06468458", // service_charge_total: "0.02172826", // tran_id: 2701371634, // logs: [ { tranId: 2701371634, // serviceChargeAmount: "0.00012819", // uid: "35103861", // amount: "0.8012", // operateTime: "2018-10-07 17:56:07", // transferedAmount: "0.00628141", // fromAsset: "ADA" } ], // operate_time: "2018-10-07 17:56:06" } ] } } const results = this.safeValue (response, 'results', {}); const rows = this.safeValue (results, 'rows', []); const data = []; for (let i = 0; i < rows.length; i++) { const logs = rows[i]['logs']; for (let j = 0; j < logs.length; j++) { logs[j]['isDustTrade'] = true; data.push (logs[j]); } } const trades = this.parseTrades (data, undefined, since, limit); return this.filterBySinceLimit (trades, since, limit); } parseDustTrade (trade, market = undefined) { // { tranId: 2701371634, // serviceChargeAmount: "0.00012819", // uid: "35103861", // amount: "0.8012", // operateTime: "2018-10-07 17:56:07", // transferedAmount: "0.00628141", // fromAsset: "ADA" }, let order = this.safeString (trade, 'tranId'); let time = this.safeString (trade, 'operateTime'); let timestamp = this.parse8601 (time); let datetime = this.iso8601 (timestamp); let tradedCurrency = this.safeCurrencyCode (trade, 'fromAsset'); let earnedCurrency = this.currency ('BNB')['code']; let applicantSymbol = earnedCurrency + '/' + tradedCurrency; let tradedCurrencyIsQuote = false; if (applicantSymbol in this.markets) { tradedCurrencyIsQuote = true; } // // Warning // Binance dust trade `fee` is already excluded from the `BNB` earning reported in the `Dust Log`. // So the parser should either set the `fee.cost` to `0` or add it on top of the earned // BNB `amount` (or `cost` depending on the trade `side`). The second of the above options // is much more illustrative and therefore preferable. // let fee = { 'currency': earnedCurrency, 'cost': this.safeFloat (trade, 'serviceChargeAmount'), }; let symbol = undefined; let amount = undefined; let cost = undefined; let side = undefined; if (tradedCurrencyIsQuote) { symbol = applicantSymbol; amount = this.sum (this.safeFloat (trade, 'transferedAmount'), fee['cost']); cost = this.safeFloat (trade, 'amount'); side = 'buy'; } else { symbol = tradedCurrency + '/' + earnedCurrency; amount = this.safeFloat (trade, 'amount'); cost = this.sum (this.safeFloat (trade, 'transferedAmount'), fee['cost']); side = 'sell'; } let price = cost / amount; let id = undefined; let type = undefined; let takerOrMaker = undefined; return { 'id': id, 'timestamp': timestamp, 'datetime': datetime, 'symbol': symbol, 'order': order, 'type': type, 'takerOrMaker': takerOrMaker, 'side': side, 'amount': amount, 'price': price, 'cost': cost, 'fee': fee, 'info': trade, }; } async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); let currency = undefined; const request = {}; if (code !== undefined) { currency = this.currency (code); request['asset'] = currency['id']; } if (since !== undefined) { request['startTime'] = since; } const response = await this.wapiGetDepositHistory (this.extend (request, params)); // // { success: true, // depositList: [ { insertTime: 1517425007000, // amount: 0.3, // address: "0x0123456789abcdef", // addressTag: "", // txId: "0x0123456789abcdef", // asset: "ETH", // status: 1 } ] } // return this.parseTransactions (response['depositList'], currency, since, limit); } async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); let currency = undefined; const request = {}; if (code !== undefined) { currency = this.currency (code); request['asset'] = currency['id']; } if (since !== undefined) { request['startTime'] = since; } const response = await this.wapiGetWithdrawHistory (this.extend (request, params)); // // { withdrawList: [ { amount: 14, // address: "0x0123456789abcdef...", // successTime: 1514489710000, // addressTag: "", // txId: "0x0123456789abcdef...", // id: "0123456789abcdef...", // asset: "ETH", // applyTime: 1514488724000, // status: 6 }, // { amount: 7600, // address: "0x0123456789abcdef...", // successTime: 1515323226000, // addressTag: "", // txId: "0x0123456789abcdef...", // id: "0123456789abcdef...", // asset: "ICN", // applyTime: 1515322539000, // status: 6 } ], // success: true } // return this.parseTransactions (response['withdrawList'], currency, since, limit); } parseTransactionStatusByType (status, type = undefined) { if (type === undefined) { return status; } const statuses = { 'deposit': { '0': 'pending', '1': 'ok', }, 'withdrawal': { '0': 'pending', // Email Sent '1': 'canceled', // Cancelled (different from 1 = ok in deposits) '2': 'pending', // Awaiting Approval '3': 'failed', // Rejected '4': 'pending', // Processing '5': 'failed', // Failure '6': 'ok', // Completed }, }; return (status in statuses[type]) ? statuses[type][status] : status; } parseTransaction (transaction, currency = undefined) { // // fetchDeposits // { insertTime: 1517425007000, // amount: 0.3, // address: "0x0123456789abcdef", // addressTag: "", // txId: "0x0123456789abcdef", // asset: "ETH", // status: 1 } // // fetchWithdrawals // // { amount: 14, // address: "0x0123456789abcdef...", // successTime: 1514489710000, // addressTag: "", // txId: "0x0123456789abcdef...", // id: "0123456789abcdef...", // asset: "ETH", // applyTime: 1514488724000, // status: 6 } // const id = this.safeString (transaction, 'id'); const address = this.safeString (transaction, 'address'); let tag = this.safeString (transaction, 'addressTag'); // set but unused if (tag !== undefined) { if (tag.length < 1) { tag = undefined; } } const txid = this.safeValue (transaction, 'txId'); let code = undefined; const currencyId = this.safeString (transaction, 'asset'); if (currencyId in this.currencies_by_id) { currency = this.currencies_by_id[currencyId]; } else { code = this.commonCurrencyCode (currencyId); } if (currency !== undefined) { code = currency['code']; } let timestamp = undefined; const insertTime = this.safeInteger (transaction, 'insertTime'); let applyTime = this.safeInteger (transaction, 'applyTime'); let type = this.safeString (transaction, 'type'); if (type === undefined) { if ((insertTime !== undefined) && (applyTime === undefined)) { type = 'deposit'; timestamp = insertTime; } else if ((insertTime === undefined) && (applyTime !== undefined)) { type = 'withdrawal'; timestamp = applyTime; } } const status = this.parseTransactionStatusByType (this.safeString (transaction, 'status'), type); const amount = this.safeFloat (transaction, 'amount'); return { 'info': transaction, 'id': id, 'txid': txid, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'address': address, 'tag': tag, 'type': type, 'amount': amount, 'currency': code, 'status': status, 'updated': undefined, 'fee': undefined, }; } async fetchDepositAddress (code, params = {}) { await this.loadMarkets (); const currency = this.currency (code); const request = { 'asset': currency['id'], }; const response = await this.wapiGetDepositAddress (this.extend (request, params)); const success = this.safeValue (response, 'success'); if ((success === undefined) || !success) { throw new InvalidAddress (this.id + ' fetchDepositAddress returned an empty response – create the deposit address in the user settings first.'); } const address = this.safeString (response, 'address'); const tag = this.safeString (response, 'addressTag'); this.checkAddress (address); return { 'currency': code, 'address': this.checkAddress (address), 'tag': tag, 'info': response, }; } async fetchFundingFees (codes = undefined, params = {}) { const response = await this.wapiGetAssetDetail (params); // // { // "success": true, // "assetDetail": { // "CTR": { // "minWithdrawAmount": "70.00000000", //min withdraw amount // "depositStatus": false,//deposit status // "withdrawFee": 35, // withdraw fee // "withdrawStatus": true, //withdraw status //