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