2034 lines
87 KiB
JavaScript
2034 lines
87 KiB
JavaScript
'use strict';
|
||
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const Exchange = require ('./base/Exchange');
|
||
const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, DDoSProtection, InvalidNonce, AuthenticationError, InvalidAddress, RateLimitExceeded, PermissionDenied, NotSupported } = require ('./base/errors');
|
||
const { ROUND, TRUNCATE } = 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,
|
||
'cancelAllOrders': 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',
|
||
'test': {
|
||
'fapiPublic': 'https://testnet.binancefuture.com/fapi/v1',
|
||
'fapiPrivate': 'https://testnet.binancefuture.com/fapi/v1',
|
||
},
|
||
'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',
|
||
'lending/project/list',
|
||
'lending/project/position/list',
|
||
],
|
||
'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/customizedFixed/purchase',
|
||
'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',
|
||
'allForceOrders',
|
||
'openInterest',
|
||
'leverageBracket',
|
||
],
|
||
},
|
||
'fapiPrivate': {
|
||
'get': [
|
||
'allOrders',
|
||
'openOrder',
|
||
'openOrders',
|
||
'order',
|
||
'account',
|
||
'balance',
|
||
'positionMargin/history',
|
||
'positionRisk',
|
||
'userTrades',
|
||
'income',
|
||
],
|
||
'post': [
|
||
'batchOrders',
|
||
'positionSide/dual',
|
||
'positionMargin',
|
||
'marginType',
|
||
'order',
|
||
'leverage',
|
||
'listenKey',
|
||
],
|
||
'put': [
|
||
'listenKey',
|
||
],
|
||
'delete': [
|
||
'batchOrders',
|
||
'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': [
|
||
'openOrders', // added on 2020-04-25 for canceling all open orders per symbol
|
||
'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
|
||
'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}
|
||
'Market is closed.': ExchangeNotAvailable, // {"code":-1013,"msg":"Market is closed."}
|
||
'-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."
|
||
},
|
||
});
|
||
}
|
||
|
||
setSandboxMode (enabled) {
|
||
if (enabled) { // eslint-disable-line no-extra-boolean-cast
|
||
if ('test' in this.urls) {
|
||
const type = this.safeString (this.options, 'defaultType', 'spot');
|
||
if (type !== 'future') {
|
||
throw new NotSupported (this.id + ' does not have a sandbox URL for ' + type + " markets, set exchange.options['defaultType'] = 'future' or don't use the sandbox for " + this.id);
|
||
}
|
||
if (typeof this.urls['api'] === 'string') {
|
||
this.urls['api_backup'] = this.urls['api'];
|
||
this.urls['api'] = this.urls['test'];
|
||
} else {
|
||
this.urls['api_backup'] = this.extend ({}, this.urls['api']);
|
||
this.urls['api'] = this.extend ({}, this.urls['test']);
|
||
}
|
||
} else {
|
||
throw new NotSupported (this.id + ' does not have a sandbox URL');
|
||
}
|
||
} else if ('api_backup' in this.urls) {
|
||
if (typeof this.urls['api'] === 'string') {
|
||
this.urls['api'] = this.urls['api_backup'];
|
||
} else {
|
||
this.urls['api'] = this.extend ({}, this.urls['api_backup']);
|
||
}
|
||
}
|
||
}
|
||
|
||
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': undefined,
|
||
'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 ('MARKET_LOT_SIZE' in filters) {
|
||
const filter = this.safeValue (filters, 'MARKET_LOT_SIZE', {});
|
||
entry['limits']['market'] = {
|
||
'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 (params);
|
||
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, 'fetchBidsAsks', '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 [
|
||
this.safeInteger (ohlcv, 0),
|
||
this.safeFloat (ohlcv, 1),
|
||
this.safeFloat (ohlcv, 2),
|
||
this.safeFloat (ohlcv, 3),
|
||
this.safeFloat (ohlcv, 4),
|
||
this.safeFloat (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'];
|
||
}
|
||
let cost = undefined;
|
||
if ((price !== undefined) && (amount !== undefined)) {
|
||
cost = price * amount;
|
||
}
|
||
return {
|
||
'info': trade,
|
||
'timestamp': timestamp,
|
||
'datetime': this.iso8601 (timestamp),
|
||
'symbol': symbol,
|
||
'id': id,
|
||
'order': orderId,
|
||
'type': undefined,
|
||
'side': side,
|
||
'takerOrMaker': takerOrMaker,
|
||
'price': price,
|
||
'amount': amount,
|
||
'cost': cost,
|
||
'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) {
|
||
//
|
||
// spot
|
||
//
|
||
// {
|
||
// "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
|
||
// }
|
||
//
|
||
// futures
|
||
//
|
||
// {
|
||
// "symbol": "BTCUSDT",
|
||
// "orderId": 1,
|
||
// "clientOrderId": "myOrder1",
|
||
// "price": "0.1",
|
||
// "origQty": "1.0",
|
||
// "executedQty": "1.0",
|
||
// "cumQuote": "10.0",
|
||
// "status": "NEW",
|
||
// "timeInForce": "GTC",
|
||
// "type": "LIMIT",
|
||
// "side": "BUY",
|
||
// "stopPrice": "0.0",
|
||
// "updateTime": 1499827319559
|
||
// }
|
||
//
|
||
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));
|
||
}
|
||
}
|
||
const clientOrderId = this.safeString (order, 'clientOrderId');
|
||
return {
|
||
'info': order,
|
||
'id': id,
|
||
'clientOrderId': clientOrderId,
|
||
'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');
|
||
const precision = market['precision']['price'];
|
||
if (quoteOrderQty !== undefined) {
|
||
request['quoteOrderQty'] = this.decimalToPrecision (quoteOrderQty, TRUNCATE, precision, this.precisionMode);
|
||
params = this.omit (params, 'quoteOrderQty');
|
||
} else if (price !== undefined) {
|
||
request['quoteOrderQty'] = this.decimalToPrecision (amount * price, TRUNCATE, precision, this.precisionMode);
|
||
} 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;
|
||
if (market['future']) {
|
||
priceIsRequired = 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;
|
||
} else if (uppercaseType === 'STOP_MARKET') {
|
||
stopPriceIsRequired = 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 method = market['spot'] ? 'privateGetOrder' : 'fapiPrivateGetOrder';
|
||
const request = {
|
||
'symbol': market['id'],
|
||
};
|
||
const origClientOrderId = this.safeValue (params, 'origClientOrderId');
|
||
if (origClientOrderId !== undefined) {
|
||
request['origClientOrderId'] = origClientOrderId;
|
||
} else {
|
||
request['orderId'] = parseInt (id);
|
||
}
|
||
const response = await this[method] (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 method = market['spot'] ? 'privateGetAllOrders' : 'fapiPrivateGetAllOrders';
|
||
const response = await this[method] (this.extend (request, params));
|
||
//
|
||
// Spot:
|
||
// [
|
||
// {
|
||
// "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
|
||
// }
|
||
// ]
|
||
//
|
||
// Futures:
|
||
// [
|
||
// {
|
||
// "symbol": "BTCUSDT",
|
||
// "orderId": 1,
|
||
// "clientOrderId": "myOrder1",
|
||
// "price": "0.1",
|
||
// "origQty": "1.0",
|
||
// "executedQty": "1.0",
|
||
// "cumQuote": "10.0",
|
||
// "status": "NEW",
|
||
// "timeInForce": "GTC",
|
||
// "type": "LIMIT",
|
||
// "side": "BUY",
|
||
// "stopPrice": "0.0",
|
||
// "updateTime": 1499827319559
|
||
// }
|
||
// ]
|
||
//
|
||
return this.parseOrders (response, market, since, limit);
|
||
}
|
||
|
||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||
await this.loadMarkets ();
|
||
let market = undefined;
|
||
let query = undefined;
|
||
let type = undefined;
|
||
const request = {};
|
||
if (symbol !== undefined) {
|
||
market = this.market (symbol);
|
||
request['symbol'] = market['id'];
|
||
type = market['type'];
|
||
query = params;
|
||
} 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.');
|
||
} else {
|
||
const defaultType = this.safeString2 (this.options, 'fetchOpenOrders', 'defaultType', 'spot');
|
||
type = this.safeString (params, 'type', defaultType);
|
||
query = this.omit (params, 'type');
|
||
}
|
||
const method = (type === 'spot') ? 'privateGetOpenOrders' : 'fapiPrivateGetOpenOrders';
|
||
const response = await this[method] (this.extend (request, query));
|
||
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);
|
||
// https://github.com/ccxt/ccxt/issues/6507
|
||
const origClientOrderId = this.safeValue (params, 'origClientOrderId');
|
||
const request = {
|
||
'symbol': market['id'],
|
||
// 'orderId': parseInt (id),
|
||
// 'origClientOrderId': id,
|
||
};
|
||
if (origClientOrderId === undefined) {
|
||
request['orderId'] = parseInt (id);
|
||
} else {
|
||
request['origClientOrderId'] = origClientOrderId;
|
||
}
|
||
const method = market['spot'] ? 'privateDeleteOrder' : 'fapiPrivateDeleteOrder';
|
||
const response = await this[method] (this.extend (request, params));
|
||
return this.parseOrder (response);
|
||
}
|
||
|
||
async cancelAllOrders (symbol = undefined, params = {}) {
|
||
if (symbol === undefined) {
|
||
throw new ArgumentsRequired (this.id + ' cancelAllOrders requires a symbol argument');
|
||
}
|
||
await this.loadMarkets ();
|
||
const market = this.market (symbol);
|
||
const request = {
|
||
'symbol': market['id'],
|
||
};
|
||
const defaultType = this.safeString2 (this.options, 'cancelAllOrders', 'defaultType', 'spot');
|
||
const type = this.safeString (params, 'type', defaultType);
|
||
const query = this.omit (params, 'type');
|
||
const method = (type === 'spot') ? 'privateDeleteOpenOrders' : 'fapiPrivateDeleteAllOpenOrders';
|
||
const response = await this[method] (this.extend (request, query));
|
||
if (Array.isArray (response)) {
|
||
return this.parseOrders (response, market);
|
||
} else {
|
||
return 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 method = market['spot'] ? 'privateGetMyTrades' : 'fapiPrivateGetUserTrades';
|
||
const request = {
|
||
'symbol': market['id'],
|
||
};
|
||
if (since !== undefined) {
|
||
request['startTime'] = since;
|
||
}
|
||
if (limit !== undefined) {
|
||
request['limit'] = limit;
|
||
}
|
||
const response = await this[method] (this.extend (request, params));
|
||
//
|
||
// spot trade
|
||
// [
|
||
// {
|
||
// "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 trade
|
||
//
|
||
// [
|
||
// {
|
||
// "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
|
||
// }
|
||
// ]
|
||
return this.parseTrades (response, market, since, limit);
|
||
}
|
||
|
||
async fetchMyDustTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||
//
|
||
// Binance 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" },
|
||
const orderId = this.safeString (trade, 'tranId');
|
||
const timestamp = this.parse8601 (this.safeString (trade, 'operateTime'));
|
||
const tradedCurrency = this.safeCurrencyCode (this.safeString (trade, 'fromAsset'));
|
||
const earnedCurrency = this.currency ('BNB')['code'];
|
||
const 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.
|
||
//
|
||
const 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 = undefined;
|
||
if (cost !== undefined) {
|
||
if (amount) {
|
||
price = cost / amount;
|
||
}
|
||
}
|
||
const id = undefined;
|
||
const type = undefined;
|
||
const takerOrMaker = undefined;
|
||
return {
|
||
'id': id,
|
||
'timestamp': timestamp,
|
||
'datetime': this.iso8601 (timestamp),
|
||
'symbol': symbol,
|
||
'order': orderId,
|
||
'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;
|
||
// max 3 months range https://github.com/ccxt/ccxt/issues/6495
|
||
request['endTime'] = this.sum (since, 7776000000);
|
||
}
|
||
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;
|
||
// max 3 months range https://github.com/ccxt/ccxt/issues/6495
|
||
request['endTime'] = this.sum (since, 7776000000);
|
||
}
|
||
const response = await this.wapiGetWithdrawHistory (this.extend (request, params));
|
||
//
|
||
// { withdrawList: [ { amount: 14,
|
||
// address: "0x0123456789abcdef...",
|
||
// successTime: 1514489710000,
|
||
// transactionFee: 0.01,
|
||
// addressTag: "",
|
||
// txId: "0x0123456789abcdef...",
|
||
// id: "0123456789abcdef...",
|
||
// asset: "ETH",
|
||
// applyTime: 1514488724000,
|
||
// status: 6 },
|
||
// { amount: 7600,
|
||
// address: "0x0123456789abcdef...",
|
||
// successTime: 1515323226000,
|
||
// transactionFee: 0.01,
|
||
// 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) {
|
||
const statusesByType = {
|
||
'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
|
||
},
|
||
};
|
||
const statuses = this.safeValue (statusesByType, type, {});
|
||
return this.safeString (statuses, 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,
|
||
// transactionFee: 0.01,
|
||
// 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.safeString (transaction, 'txId');
|
||
const currencyId = this.safeString (transaction, 'asset');
|
||
const code = this.safeCurrencyCode (currencyId, currency);
|
||
let timestamp = undefined;
|
||
const insertTime = this.safeInteger (transaction, 'insertTime');
|
||
const 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');
|
||
const feeCost = this.safeFloat (transaction, 'transactionFee');
|
||
let fee = undefined;
|
||
if (feeCost !== undefined) {
|
||
fee = { 'currency': code, 'cost': feeCost };
|
||
}
|
||
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': fee,
|
||
};
|
||
}
|
||
|
||
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
|
||
// "depositTip": "Delisted, Deposit Suspended" //reason
|
||
// },
|
||
// "SKY": {
|
||
// "minWithdrawAmount": "0.02000000",
|
||
// "depositStatus": true,
|
||
// "withdrawFee": 0.01,
|
||
// "withdrawStatus": true
|
||
// }
|
||
// }
|
||
// }
|
||
//
|
||
const detail = this.safeValue (response, 'assetDetail', {});
|
||
const ids = Object.keys (detail);
|
||
const withdrawFees = {};
|
||
for (let i = 0; i < ids.length; i++) {
|
||
const id = ids[i];
|
||
const code = this.safeCurrencyCode (id);
|
||
withdrawFees[code] = this.safeFloat (detail[id], 'withdrawFee');
|
||
}
|
||
return {
|
||
'withdraw': withdrawFees,
|
||
'deposit': {},
|
||
'info': response,
|
||
};
|
||
}
|
||
|
||
async withdraw (code, amount, address, tag = undefined, params = {}) {
|
||
this.checkAddress (address);
|
||
await this.loadMarkets ();
|
||
const currency = this.currency (code);
|
||
// name is optional, can be overrided via params
|
||
const name = address.slice (0, 20);
|
||
const request = {
|
||
'asset': currency['id'],
|
||
'address': address,
|
||
'amount': parseFloat (amount),
|
||
'name': name, // name is optional, can be overrided via params
|
||
// https://binance-docs.github.io/apidocs/spot/en/#withdraw-sapi
|
||
// issue sapiGetCapitalConfigGetall () to get networks for withdrawing USDT ERC20 vs USDT Omni
|
||
// 'network': 'ETH', // 'BTC', 'TRX', etc, optional
|
||
};
|
||
if (tag !== undefined) {
|
||
request['addressTag'] = tag;
|
||
}
|
||
const response = await this.wapiPostWithdraw (this.extend (request, params));
|
||
return {
|
||
'info': response,
|
||
'id': this.safeString (response, 'id'),
|
||
};
|
||
}
|
||
|
||
parseTradingFee (fee, market = undefined) {
|
||
//
|
||
// {
|
||
// "symbol": "ADABNB",
|
||
// "maker": 0.9000,
|
||
// "taker": 1.0000
|
||
// }
|
||
//
|
||
const marketId = this.safeString (fee, 'symbol');
|
||
let symbol = marketId;
|
||
if (marketId in this.markets_by_id) {
|
||
const market = this.markets_by_id[marketId];
|
||
symbol = market['symbol'];
|
||
}
|
||
return {
|
||
'info': fee,
|
||
'symbol': symbol,
|
||
'maker': this.safeFloat (fee, 'maker'),
|
||
'taker': this.safeFloat (fee, 'taker'),
|
||
};
|
||
}
|
||
|
||
async fetchTradingFee (symbol, params = {}) {
|
||
await this.loadMarkets ();
|
||
const market = this.market (symbol);
|
||
const request = {
|
||
'symbol': market['id'],
|
||
};
|
||
const response = await this.wapiGetTradeFee (this.extend (request, params));
|
||
//
|
||
// {
|
||
// "tradeFee": [
|
||
// {
|
||
// "symbol": "ADABNB",
|
||
// "maker": 0.9000,
|
||
// "taker": 1.0000
|
||
// }
|
||
// ],
|
||
// "success": true
|
||
// }
|
||
//
|
||
const tradeFee = this.safeValue (response, 'tradeFee', []);
|
||
const first = this.safeValue (tradeFee, 0, {});
|
||
return this.parseTradingFee (first);
|
||
}
|
||
|
||
async fetchTradingFees (params = {}) {
|
||
await this.loadMarkets ();
|
||
const response = await this.wapiGetTradeFee (params);
|
||
//
|
||
// {
|
||
// "tradeFee": [
|
||
// {
|
||
// "symbol": "ADABNB",
|
||
// "maker": 0.9000,
|
||
// "taker": 1.0000
|
||
// }
|
||
// ],
|
||
// "success": true
|
||
// }
|
||
//
|
||
const tradeFee = this.safeValue (response, 'tradeFee', []);
|
||
const result = {};
|
||
for (let i = 0; i < tradeFee.length; i++) {
|
||
const fee = this.parseTradingFee (tradeFee[i]);
|
||
const symbol = fee['symbol'];
|
||
result[symbol] = fee;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||
let url = this.urls['api'][api];
|
||
url += '/' + path;
|
||
if (api === 'wapi') {
|
||
url += '.html';
|
||
}
|
||
const userDataStream = (path === 'userDataStream') || (path === 'listenKey');
|
||
if (path === 'historicalTrades') {
|
||
if (this.apiKey) {
|
||
headers = {
|
||
'X-MBX-APIKEY': this.apiKey,
|
||
};
|
||
} else {
|
||
throw new AuthenticationError (this.id + ' historicalTrades endpoint requires `apiKey` credential');
|
||
}
|
||
} else if (userDataStream) {
|
||
if (this.apiKey) {
|
||
// v1 special case for userDataStream
|
||
body = this.urlencode (params);
|
||
headers = {
|
||
'X-MBX-APIKEY': this.apiKey,
|
||
'Content-Type': 'application/x-www-form-urlencoded',
|
||
};
|
||
} else {
|
||
throw new AuthenticationError (this.id + ' userDataStream endpoint requires `apiKey` credential');
|
||
}
|
||
}
|
||
if ((api === 'private') || (api === 'sapi') || (api === 'wapi' && path !== 'systemStatus') || (api === 'fapiPrivate')) {
|
||
this.checkRequiredCredentials ();
|
||
let query = undefined;
|
||
if ((api === 'sapi') && (path === 'asset/dust')) {
|
||
query = this.urlencodeWithArrayRepeat (this.extend ({
|
||
'timestamp': this.nonce (),
|
||
'recvWindow': this.options['recvWindow'],
|
||
}, params));
|
||
} else if (path === 'batchOrders') {
|
||
query = this.rawencode (this.extend ({
|
||
'timestamp': this.nonce (),
|
||
'recvWindow': this.options['recvWindow'],
|
||
}, params));
|
||
} else {
|
||
query = this.urlencode (this.extend ({
|
||
'timestamp': this.nonce (),
|
||
'recvWindow': this.options['recvWindow'],
|
||
}, params));
|
||
}
|
||
const signature = this.hmac (this.encode (query), this.encode (this.secret));
|
||
query += '&' + 'signature=' + signature;
|
||
headers = {
|
||
'X-MBX-APIKEY': this.apiKey,
|
||
};
|
||
if ((method === 'GET') || (method === 'DELETE') || (api === 'wapi')) {
|
||
url += '?' + query;
|
||
} else {
|
||
body = query;
|
||
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||
}
|
||
} else {
|
||
// userDataStream endpoints are public, but POST, PUT, DELETE
|
||
// therefore they don't accept URL query arguments
|
||
// https://github.com/ccxt/ccxt/issues/5224
|
||
if (!userDataStream) {
|
||
if (Object.keys (params).length) {
|
||
url += '?' + this.urlencode (params);
|
||
}
|
||
}
|
||
}
|
||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||
}
|
||
|
||
handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
|
||
if ((code === 418) || (code === 429)) {
|
||
throw new DDoSProtection (this.id + ' ' + code.toString () + ' ' + reason + ' ' + body);
|
||
}
|
||
// error response in a form: { "code": -1013, "msg": "Invalid quantity." }
|
||
// following block cointains legacy checks against message patterns in "msg" property
|
||
// will switch "code" checks eventually, when we know all of them
|
||
if (code >= 400) {
|
||
if (body.indexOf ('Price * QTY is zero or less') >= 0) {
|
||
throw new InvalidOrder (this.id + ' order cost = amount * price is zero or less ' + body);
|
||
}
|
||
if (body.indexOf ('LOT_SIZE') >= 0) {
|
||
throw new InvalidOrder (this.id + ' order amount should be evenly divisible by lot size ' + body);
|
||
}
|
||
if (body.indexOf ('PRICE_FILTER') >= 0) {
|
||
throw new InvalidOrder (this.id + ' order price is invalid, i.e. exceeds allowed price precision, exceeds min price or max price limits or is invalid float value in general, use this.priceToPrecision (symbol, amount) ' + body);
|
||
}
|
||
}
|
||
if (body.length > 0) {
|
||
if (body[0] === '{') {
|
||
// check success value for wapi endpoints
|
||
// response in format {'msg': 'The coin does not exist.', 'success': true/false}
|
||
const success = this.safeValue (response, 'success', true);
|
||
if (!success) {
|
||
const message = this.safeString (response, 'msg');
|
||
let parsedMessage = undefined;
|
||
if (message !== undefined) {
|
||
try {
|
||
parsedMessage = JSON.parse (message);
|
||
} catch (e) {
|
||
// do nothing
|
||
parsedMessage = undefined;
|
||
}
|
||
if (parsedMessage !== undefined) {
|
||
response = parsedMessage;
|
||
}
|
||
}
|
||
}
|
||
const message = this.safeString (response, 'msg');
|
||
if (message !== undefined) {
|
||
this.throwExactlyMatchedException (this.exceptions, message, this.id + ' ' + message);
|
||
}
|
||
// checks against error codes
|
||
const error = this.safeString (response, 'code');
|
||
if (error !== undefined) {
|
||
// https://github.com/ccxt/ccxt/issues/6501
|
||
if (error === '200') {
|
||
return;
|
||
}
|
||
// a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
|
||
// despite that their message is very confusing, it is raised by Binance
|
||
// on a temporary ban, the API key is valid, but disabled for a while
|
||
if ((error === '-2015') && this.options['hasAlreadyAuthenticatedSuccessfully']) {
|
||
throw new DDoSProtection (this.id + ' temporary banned: ' + body);
|
||
}
|
||
const feedback = this.id + ' ' + body;
|
||
this.throwExactlyMatchedException (this.exceptions, error, feedback);
|
||
throw new ExchangeError (feedback);
|
||
}
|
||
if (!success) {
|
||
throw new ExchangeError (this.id + ' ' + body);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||
const response = await this.fetch2 (path, api, method, params, headers, body);
|
||
// a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
|
||
if ((api === 'private') || (api === 'wapi')) {
|
||
this.options['hasAlreadyAuthenticatedSuccessfully'] = true;
|
||
}
|
||
return response;
|
||
}
|
||
};
|