UNPKG

ccxt-bybit

Version:

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

1,138 lines (1,114 loc) 83.2 kB
'use strict'; // --------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, DDoSProtection, InvalidNonce, AuthenticationError, InvalidAddress, RateLimitExceeded, PermissionDenied } = 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, 'pro': true, // new metainfo interface 'has': { 'fetchDepositAddress': true, 'CORS': false, 'fetchBidsAsks': true, 'fetchTickers': true, 'fetchTime': true, 'fetchOHLCV': true, 'fetchMyTrades': true, 'fetchOrder': true, 'fetchOrders': true, 'fetchOpenOrders': true, 'fetchClosedOrders': 'emulated', 'withdraw': true, 'fetchFundingFees': true, 'fetchDeposits': true, 'fetchWithdrawals': true, 'fetchTransactions': false, 'fetchTradingFee': true, 'fetchTradingFees': true, }, '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', 'sapi': 'https://api.binance.com/sapi/v1', 'fapiPublic': 'https://fapi.binance.com/fapi/v1', 'fapiPrivate': 'https://fapi.binance.com/fapi/v1', 'public': 'https://api.binance.com/api/v3', '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://binance-docs.github.io/apidocs/spot/en', ], 'api_management': 'https://www.binance.com/en/usercenter/settings/api-management', 'fees': 'https://www.binance.com/en/fee/schedule', }, 'api': { 'web': { 'get': [ 'exchange/public/product', 'assetWithdraw/getAllAsset.html', ], }, // the API structure below will need 3-layer apidefs 'sapi': { 'get': [ 'accountSnapshot', // these endpoints require this.apiKey 'margin/asset', 'margin/pair', 'margin/allAssets', 'margin/allPairs', 'margin/priceIndex', // these endpoints require this.apiKey + this.secret 'asset/assetDividend', 'margin/loan', 'margin/repay', 'margin/account', 'margin/transfer', 'margin/interestHistory', 'margin/forceLiquidationRec', 'margin/order', 'margin/openOrders', 'margin/allOrders', 'margin/myTrades', 'margin/maxBorrowable', 'margin/maxTransferable', 'futures/transfer', // https://binance-docs.github.io/apidocs/spot/en/#withdraw-sapi 'capital/config/getall', // get networks for withdrawing USDT ERC20 vs USDT Omni 'capital/deposit/address', 'capital/deposit/hisrec', 'capital/deposit/subAddress', 'capital/deposit/subHisrec', 'capital/withdraw/history', 'sub-account/futures/account', 'sub-account/futures/accountSummary', 'sub-account/futures/positionRisk', 'sub-account/margin/account', 'sub-account/margin/accountSummary', 'sub-account/status', // lending endpoints 'lending/daily/product/list', 'lending/daily/userLeftQuota', 'lending/daily/userRedemptionQuota', 'lending/daily/token/position', 'lending/union/account', 'lending/union/purchaseRecord', 'lending/union/redemptionRecord', 'lending/union/interestHistory', ], 'post': [ 'asset/dust', 'account/disableFastWithdrawSwitch', 'account/enableFastWithdrawSwitch', 'capital/withdraw/apply', 'margin/transfer', 'margin/loan', 'margin/repay', 'margin/order', 'sub-account/margin/enable', 'sub-account/margin/enable', 'sub-account/futures/enable', 'userDataStream', 'futures/transfer', // lending 'lending/daily/purchase', 'lending/daily/redeem', ], 'put': [ 'userDataStream', ], 'delete': [ 'margin/order', 'userDataStream', ], }, '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', ], }, 'fapiPublic': { 'get': [ 'ping', 'time', 'exchangeInfo', 'depth', 'trades', 'historicalTrades', 'aggTrades', 'klines', 'fundingRate', 'premiumIndex', 'ticker/24hr', 'ticker/price', 'ticker/bookTicker', 'leverageBracket', ], }, 'fapiPrivate': { 'get': [ 'allOrders', 'openOrder', 'openOrders', 'order', 'account', 'balance', 'positionMargin/history', 'positionRisk', 'userTrades', 'income', ], 'post': [ 'positionMargin', 'marginType', 'order', 'leverage', 'listenKey', ], 'put': [ 'listenKey', ], 'delete': [ 'order', 'allOpenOrders', 'listenKey', ], }, 'v3': { 'get': [ 'ticker/price', 'ticker/bookTicker', ], }, 'public': { 'get': [ 'ping', 'time', 'depth', 'trades', 'aggTrades', 'historicalTrades', 'klines', 'ticker/24hr', 'ticker/price', 'ticker/bookTicker', 'exchangeInfo', ], 'put': [ 'userDataStream' ], 'post': [ 'userDataStream' ], 'delete': [ 'userDataStream' ], }, 'private': { 'get': [ 'allOrderList', // oco 'openOrderList', // oco 'orderList', // oco 'order', 'openOrders', 'allOrders', 'account', 'myTrades', ], 'post': [ 'order/oco', 'order', 'order/test', ], 'delete': [ 'orderList', // oco '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', // publicGetTrades, publicGetHistoricalTrades 'fetchTickersMethod': 'publicGetTicker24hr', 'defaultTimeInForce': 'GTC', // 'GTC' = Good To Cancel (default), 'IOC' = Immediate Or Cancel 'defaultLimitOrderType': 'limit', // or 'limit_maker' 'defaultType': 'spot', // 'spot', 'future' '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, "You don't have permission.": PermissionDenied, // {"msg":"You don't have permission.","success":false} '-1000': ExchangeNotAvailable, // {"code":-1000,"msg":"An unknown error occured while processing the request."} '-1003': RateLimitExceeded, // {"code":-1003,"msg":"Too much request weight used, current limit is 1200 request weight per 1 MINUTE. Please use the websocket for live updates to avoid polling the API."} '-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 fetchTime (params = {}) { const type = this.safeString2 (this.options, 'fetchTime', 'defaultType', 'spot'); const method = (type === 'spot') ? 'publicGetTime' : 'fapiPublicGetTime'; const response = await this[method] (params); return this.safeInteger (response, 'serverTime'); } async loadTimeDifference () { const serverTime = await this.fetchTime (); const after = this.milliseconds (); this.options['timeDifference'] = after - serverTime; return this.options['timeDifference']; } async fetchMarkets (params = {}) { const defaultType = this.safeString2 (this.options, 'fetchMarkets', 'defaultType', 'spot'); const type = this.safeString (params, 'type', defaultType); const query = this.omit (params, 'type'); const method = (type === 'spot') ? 'publicGetExchangeInfo' : 'fapiPublicGetExchangeInfo'; const response = await this[method] (query); // // spot // // { // "timezone":"UTC", // "serverTime":1575416692969, // "rateLimits":[ // {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200}, // {"rateLimitType":"ORDERS","interval":"SECOND","intervalNum":10,"limit":100}, // {"rateLimitType":"ORDERS","interval":"DAY","intervalNum":1,"limit":200000} // ], // "exchangeFilters":[], // "symbols":[ // { // "symbol":"ETHBTC", // "status":"TRADING", // "baseAsset":"ETH", // "baseAssetPrecision":8, // "quoteAsset":"BTC", // "quotePrecision":8, // "baseCommissionPrecision":8, // "quoteCommissionPrecision":8, // "orderTypes":["LIMIT","LIMIT_MAKER","MARKET","STOP_LOSS_LIMIT","TAKE_PROFIT_LIMIT"], // "icebergAllowed":true, // "ocoAllowed":true, // "quoteOrderQtyMarketAllowed":true, // "isSpotTradingAllowed":true, // "isMarginTradingAllowed":true, // "filters":[ // {"filterType":"PRICE_FILTER","minPrice":"0.00000100","maxPrice":"100000.00000000","tickSize":"0.00000100"}, // {"filterType":"PERCENT_PRICE","multiplierUp":"5","multiplierDown":"0.2","avgPriceMins":5}, // {"filterType":"LOT_SIZE","minQty":"0.00100000","maxQty":"100000.00000000","stepSize":"0.00100000"}, // {"filterType":"MIN_NOTIONAL","minNotional":"0.00010000","applyToMarket":true,"avgPriceMins":5}, // {"filterType":"ICEBERG_PARTS","limit":10}, // {"filterType":"MARKET_LOT_SIZE","minQty":"0.00000000","maxQty":"63100.00000000","stepSize":"0.00000000"}, // {"filterType":"MAX_NUM_ALGO_ORDERS","maxNumAlgoOrders":5} // ] // }, // ], // } // // futures (fapi) // // { // "timezone":"UTC", // "serverTime":1575417244353, // "rateLimits":[ // {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200}, // {"rateLimitType":"ORDERS","interval":"MINUTE","intervalNum":1,"limit":1200} // ], // "exchangeFilters":[], // "symbols":[ // { // "symbol":"BTCUSDT", // "status":"TRADING", // "maintMarginPercent":"2.5000", // "requiredMarginPercent":"5.0000", // "baseAsset":"BTC", // "quoteAsset":"USDT", // "pricePrecision":2, // "quantityPrecision":3, // "baseAssetPrecision":8, // "quotePrecision":8, // "filters":[ // {"minPrice":"0.01","maxPrice":"100000","filterType":"PRICE_FILTER","tickSize":"0.01"}, // {"stepSize":"0.001","filterType":"LOT_SIZE","maxQty":"1000","minQty":"0.001"}, // {"stepSize":"0.001","filterType":"MARKET_LOT_SIZE","maxQty":"1000","minQty":"0.001"}, // {"limit":200,"filterType":"MAX_NUM_ORDERS"}, // {"multiplierDown":"0.8500","multiplierUp":"1.1500","multiplierDecimal":"4","filterType":"PERCENT_PRICE"} // ], // "orderTypes":["LIMIT","MARKET","STOP"], // "timeInForce":["GTC","IOC","FOK","GTX"] // } // ] // } // if (this.options['adjustForTimeDifference']) { await this.loadTimeDifference (); } const markets = this.safeValue (response, 'symbols'); const result = []; for (let i = 0; i < markets.length; i++) { const market = markets[i]; const future = ('maintMarginPercent' in market); const spot = !future; const marketType = spot ? 'spot' : 'future'; const id = this.safeString (market, 'symbol'); const lowercaseId = this.safeStringLower (market, 'symbol'); const baseId = market['baseAsset']; const quoteId = market['quoteAsset']; const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); const symbol = base + '/' + quote; const filters = this.indexBy (market['filters'], 'filterType'); const precision = { 'base': market['baseAssetPrecision'], 'quote': market['quotePrecision'], 'amount': market['baseAssetPrecision'], 'price': market['quotePrecision'], }; const status = this.safeString (market, 'status'); const active = (status === 'TRADING'); const entry = { 'id': id, 'lowercaseId': lowercaseId, 'symbol': symbol, 'base': base, 'quote': quote, 'baseId': baseId, 'quoteId': quoteId, 'info': market, 'type': marketType, 'spot': spot, 'future': future, '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) { const 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) { const filter = this.safeValue (filters, 'LOT_SIZE', {}); const stepSize = this.safeString (filter, 'stepSize'); entry['precision']['amount'] = this.precisionFromString (stepSize); entry['limits']['amount'] = { 'min': this.safeFloat (filter, 'minQty'), 'max': this.safeFloat (filter, 'maxQty'), }; } if ('MIN_NOTIONAL' in filters) { entry['limits']['cost']['min'] = this.safeFloat (filters['MIN_NOTIONAL'], 'minNotional'); } result.push (entry); } return result; } calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { const market = this.markets[symbol]; let key = 'quote'; const 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 (); const defaultType = this.safeString2 (this.options, 'fetchBalance', 'defaultType', 'spot'); const type = this.safeString (params, 'type', defaultType); const method = (type === 'spot') ? 'privateGetAccount' : 'fapiPrivateGetAccount'; const query = this.omit (params, 'type'); const response = await this[method] (query); // // spot // // { // makerCommission: 10, // takerCommission: 10, // buyerCommission: 0, // sellerCommission: 0, // canTrade: true, // canWithdraw: true, // canDeposit: true, // updateTime: 1575357359602, // accountType: "MARGIN", // balances: [ // { asset: "BTC", free: "0.00219821", locked: "0.00000000" }, // ] // } // // futures (fapi) // // { // "feeTier":0, // "canTrade":true, // "canDeposit":true, // "canWithdraw":true, // "updateTime":0, // "totalInitialMargin":"0.00000000", // "totalMaintMargin":"0.00000000", // "totalWalletBalance":"4.54000000", // "totalUnrealizedProfit":"0.00000000", // "totalMarginBalance":"4.54000000", // "totalPositionInitialMargin":"0.00000000", // "totalOpenOrderInitialMargin":"0.00000000", // "maxWithdrawAmount":"4.54000000", // "assets":[ // { // "asset":"USDT", // "walletBalance":"4.54000000", // "unrealizedProfit":"0.00000000", // "marginBalance":"4.54000000", // "maintMargin":"0.00000000", // "initialMargin":"0.00000000", // "positionInitialMargin":"0.00000000", // "openOrderInitialMargin":"0.00000000", // "maxWithdrawAmount":"4.54000000" // } // ], // "positions":[ // { // "symbol":"BTCUSDT", // "initialMargin":"0.00000", // "maintMargin":"0.00000", // "unrealizedProfit":"0.00000000", // "positionInitialMargin":"0.00000", // "openOrderInitialMargin":"0.00000" // } // ] // } // const result = { 'info': response }; if (type === 'spot') { const balances = this.safeValue (response, 'balances', []); for (let i = 0; i < balances.length; i++) { const balance = balances[i]; const currencyId = this.safeString (balance, 'asset'); const code = this.safeCurrencyCode (currencyId); const account = this.account (); account['free'] = this.safeFloat (balance, 'free'); account['used'] = this.safeFloat (balance, 'locked'); result[code] = account; } } else { const balances = this.safeValue (response, 'assets', []); for (let i = 0; i < balances.length; i++) { const balance = balances[i]; const currencyId = this.safeString (balance, 'asset'); const code = this.safeCurrencyCode (currencyId); const account = this.account (); account['used'] = this.safeFloat (balance, 'initialMargin'); account['total'] = this.safeFloat (balance, 'marginBalance'); result[code] = account; } } return this.parseBalance (result); } async fetchOrderBook (symbol, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], }; if (limit !== undefined) { request['limit'] = limit; // default 100, max 5000, see https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#order-book } const method = market['spot'] ? 'publicGetDepth' : 'fapiPublicGetDepth'; const response = await this[method] (this.extend (request, params)); const orderbook = this.parseOrderBook (response); orderbook['nonce'] = this.safeInteger (response, 'lastUpdateId'); return orderbook; } parseTicker (ticker, market = undefined) { const timestamp = this.safeInteger (ticker, 'closeTime'); let symbol = undefined; const marketId = this.safeString (ticker, 'symbol'); if (marketId in this.markets_by_id) { market = this.markets_by_id[marketId]; } if (market !== undefined) { symbol = market['symbol']; } 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 fetchStatus (params = {}) { const response = await this.wapiGetSystemStatus (); let status = this.safeValue (response, 'status'); if (status !== undefined) { status = (status === 0) ? 'ok' : 'maintenance'; this.status = this.extend (this.status, { 'status': status, 'updated': this.milliseconds (), }); } return this.status; } async fetchTicker (symbol, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], }; const method = market['spot'] ? 'publicGetTicker24hr' : 'fapiPublicGetTicker24hr'; const response = await this[method] (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 defaultType = this.safeString2 (this.options, 'fetchOpenOrders', 'defaultType', 'spot'); const type = this.safeString (params, 'type', defaultType); const query = this.omit (params, 'type'); const method = (type === 'spot') ? 'publicGetTickerBookTicker' : 'fapiPublicGetTickerBookTicker'; const response = await this[method] (query); 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 method = market['spot'] ? 'publicGetKlines' : 'fapiPublicGetKlines'; const response = await this[method] (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 // } // // futures trades // https://binance-docs.github.io/apidocs/futures/en/#account-trade-list-user_data // // { // "accountId": 20, // "buyer": False, // "commission": "-0.07819010", // "commissionAsset": "USDT", // "counterPartyId": 653, // "id": 698759, // "maker": False, // "orderId": 25851813, // "price": "7819.01", // "qty": "0.002", // "quoteQty": "0.01563", // "realizedPnl": "-0.91539999", // "side": "SELL", // "symbol": "BTCUSDT", // "time": 1569514978020 // } // 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 ('side' in trade) { side = this.safeStringLower (trade, 'side'); } 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.safeCurrencyCode (this.safeString (trade, 'commissionAsset')), }; } let takerOrMaker = undefined; if ('isMaker' in trade) { takerOrMaker = trade['isMaker'] ? 'maker' : 'taker'; } let symbol = undefined; if (market === undefined) { const 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 }; const defaultType = this.safeString2 (this.options, 'fetchTrades', 'defaultType', 'spot'); const type = this.safeString (params, 'type', defaultType); const query = this.omit (params, 'type'); const defaultMethod = (type === 'future') ? 'fapiPublicGetTrades' : 'publicGetTrades'; let method = this.safeString (this.options, 'fetchTradesMethod', defaultMethod); if (method === 'publicGetAggTrades') { if (since !== undefined) { request['startTime'] = since; // https://github.com/ccxt/ccxt/issues/6400 // https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list request['endTime'] = this.sum (since, 3600000); } if (type === 'future') { method = 'fapiPublicGetAggTrades'; } } else if ((method === 'publicGetHistoricalTrades') && (type === 'future')) { method = 'fapiPublicGetHistoricalTrades'; } 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 response = await this[method] (this.extend (request, query)); // // 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': 'canceled', }; return this.safeString (statuses, status, status); } parseOrder (order, market = undefined) { const status = this.parseOrderStatus (this.safeString (order, 'status')); let symbol = undefined; const marketId = this.safeString (order, 'symbol'); if (marketId in this.markets_by_id) { market = this.markets_by_id[marketId]; } if (market !== undefined) { symbol = market['symbol']; } 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; // - Spot/Margin market: cummulativeQuoteQty // - Futures market: cumQuote. // Note this is not the actual cost, since Binance futures uses leverage to calculate margins. let cost = this.safeFloat2 (order, 'cummulativeQuoteQty', 'cumQuote'); 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.safeStringLower (order, 'type'); if (type === 'market') { if (price === 0.0) { if ((cost !== undefined) && (filled !== undefined)) { if ((cost > 0) && (filled > 0)) { price = cost / filled; if (this.options['parseOrderToPrecision']) { price = parseFloat (this.priceToPrecision (symbol, price)); } } } } } else if (type === 'limit_maker') { type = 'limit'; } const side = this.safeStringLower (order, 'side'); let fee = undefined; let trades = undefined; const fills = this.safeValue (order, 'fills'); if (fills !== undefined) { trades = this.parseTrades (fills, market); const 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']) { average = parseFloat (this.priceToPrecision (symbol, average)); } } 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 = market['spot'] ? 'privatePostOrder' : 'fapiPrivatePostOrder'; if (market['spot']) { const test = this.safeValue (params, 'test', false); if (test) { method += 'Test'; params = this.omit (params, 'test'); } } const uppercaseType = type.toUpperCase (); const validOrderTypes = this.safeValue (market['info'], 'orderTypes'); if (!this.inArray (uppercaseType, validOrderTypes)) { throw new InvalidOrder (this.id + ' ' + type + ' is not a valid order type in ' + market['type'] + ' market ' + symbol); } const request = { 'symbol': market['id'], 'type': uppercaseType, 'side': side.toUpperCase (), }; if (uppercaseType === 'MARKET') { const quoteOrderQty = this.safeFloat (params, 'quoteOrderQty'); if (quoteOrderQty !== undefined) { request['quoteOrderQty'] = this.costToPrecision (symbol, quoteOrderQty); } else if (price !== undefined) { request['quoteOrderQty'] = this.costToPrecision (symbol, amount * price); } else { request['quantity'] = this.amountToPrecision (symbol, amount); } } else { request['quantity'] = this.amountToPrecision (symbol, amount); } if (market['spot']) { request['newOrderRespType'] = this.safeValue (this.options['newOrderRespType'], type, 'RESULT'); // '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; } else if (uppercaseType === 'STOP') { stopPriceIsRequired = true; 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 (