ccxt-bybit
Version:
A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges
1,220 lines (1,188 loc) • 69.6 kB
JavaScript
'use strict';
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, AccountSuspended, InvalidNonce, NotSupported, BadRequest, AuthenticationError, BadSymbol, RateLimitExceeded } = require ('./base/errors');
// ---------------------------------------------------------------------------
module.exports = class kucoin extends Exchange {
describe () {
return this.deepExtend (super.describe (), {
'id': 'kucoin',
'name': 'KuCoin',
'countries': [ 'SC' ],
'rateLimit': 334,
'version': 'v2',
'certified': false,
'pro': true,
'comment': 'Platform 2.0',
'has': {
'CORS': false,
'fetchTime': true,
'fetchMarkets': true,
'fetchCurrencies': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchOrderBook': true,
'fetchOrder': true,
'fetchClosedOrders': true,
'fetchOpenOrders': true,
'fetchDepositAddress': true,
'createDepositAddress': true,
'withdraw': true,
'fetchDeposits': true,
'fetchWithdrawals': true,
'fetchBalance': true,
'fetchTrades': true,
'fetchMyTrades': true,
'createOrder': true,
'cancelOrder': true,
'fetchAccounts': true,
'fetchFundingFee': true,
'fetchOHLCV': true,
'fetchLedger': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/57369448-3cc3aa80-7196-11e9-883e-5ebeb35e4f57.jpg',
'referral': 'https://www.kucoin.com/?rcode=E5wkqe',
'api': {
'public': 'https://openapi-v2.kucoin.com',
'private': 'https://openapi-v2.kucoin.com',
},
'test': {
'public': 'https://openapi-sandbox.kucoin.com',
'private': 'https://openapi-sandbox.kucoin.com',
},
'www': 'https://www.kucoin.com',
'doc': [
'https://docs.kucoin.com',
],
},
'requiredCredentials': {
'apiKey': true,
'secret': true,
'password': true,
},
'api': {
'public': {
'get': [
'timestamp',
'symbols',
'market/allTickers',
'market/orderbook/level{level}',
'market/orderbook/level2',
'market/orderbook/level2_20',
'market/orderbook/level2_100',
'market/orderbook/level3',
'market/histories',
'market/candles',
'market/stats',
'currencies',
'currencies/{currency}',
],
'post': [
'bullet-public',
],
},
'private': {
'get': [
'accounts',
'accounts/{accountId}',
'accounts/{accountId}/ledgers',
'accounts/{accountId}/holds',
'deposit-addresses',
'deposits',
'hist-deposits',
'hist-orders',
'hist-withdrawals',
'withdrawals',
'withdrawals/quotas',
'orders',
'orders/{orderId}',
'fills',
'limit/fills',
],
'post': [
'accounts',
'accounts/inner-transfer',
'accounts/sub-transfer',
'deposit-addresses',
'withdrawals',
'orders',
'bullet-private',
],
'delete': [
'withdrawals/{withdrawalId}',
'orders/{orderId}',
],
},
},
'timeframes': {
'1m': '1min',
'3m': '3min',
'5m': '5min',
'15m': '15min',
'30m': '30min',
'1h': '1hour',
'2h': '2hour',
'4h': '4hour',
'6h': '6hour',
'8h': '8hour',
'12h': '12hour',
'1d': '1day',
'1w': '1week',
},
'exceptions': {
'exact': {
'order not exist': OrderNotFound,
'order not exist.': OrderNotFound, // duplicated error temporarily
'order_not_exist': OrderNotFound, // {"code":"order_not_exist","msg":"order_not_exist"} ¯\_(ツ)_/¯
'order_not_exist_or_not_allow_to_cancel': InvalidOrder, // {"code":"400100","msg":"order_not_exist_or_not_allow_to_cancel"}
'Order size below the minimum requirement.': InvalidOrder, // {"code":"400100","msg":"Order size below the minimum requirement."}
'The withdrawal amount is below the minimum requirement.': ExchangeError, // {"code":"400100","msg":"The withdrawal amount is below the minimum requirement."}
'400': BadRequest,
'401': AuthenticationError,
'403': NotSupported,
'404': NotSupported,
'405': NotSupported,
'429': RateLimitExceeded,
'500': ExchangeError,
'503': ExchangeNotAvailable,
'200004': InsufficientFunds,
'230003': InsufficientFunds, // {"code":"230003","msg":"Balance insufficient!"}
'260100': InsufficientFunds, // {"code":"260100","msg":"account.noBalance"}
'300000': InvalidOrder,
'400000': BadSymbol,
'400001': AuthenticationError,
'400002': InvalidNonce,
'400003': AuthenticationError,
'400004': AuthenticationError,
'400005': AuthenticationError,
'400006': AuthenticationError,
'400007': AuthenticationError,
'400008': NotSupported,
'400100': BadRequest,
'411100': AccountSuspended,
'415000': BadRequest, // {"code":"415000","msg":"Unsupported Media Type"}
'500000': ExchangeError,
},
'broad': {
'Exceeded the access frequency': RateLimitExceeded,
},
},
'fees': {
'trading': {
'tierBased': false,
'percentage': true,
'taker': 0.001,
'maker': 0.001,
},
'funding': {
'tierBased': false,
'percentage': false,
'withdraw': {},
'deposit': {},
},
},
'commonCurrencies': {
'HOT': 'HOTNOW',
'EDGE': 'DADI', // https://github.com/ccxt/ccxt/issues/5756
'WAX': 'WAXP',
},
'options': {
'version': 'v1',
'symbolSeparator': '-',
'fetchMyTradesMethod': 'private_get_fills',
'fetchBalance': {
'type': 'trade', // or 'main'
},
// endpoint versions
'versions': {
'public': {
'GET': {
'market/orderbook/level{level}': 'v1',
'market/orderbook/level2': 'v2',
'market/orderbook/level2_20': 'v1',
'market/orderbook/level2_100': 'v1',
},
},
'private': {
'POST': {
'accounts/inner-transfer': 'v2',
'accounts/sub-transfer': 'v2',
},
},
},
},
});
}
nonce () {
return this.milliseconds ();
}
async loadTimeDifference () {
const response = await this.publicGetTimestamp ();
const after = this.milliseconds ();
const kucoinTime = this.safeInteger (response, 'data');
this.options['timeDifference'] = parseInt (after - kucoinTime);
return this.options['timeDifference'];
}
async fetchTime (params = {}) {
const response = await this.publicGetTimestamp (params);
//
// {
// "code":"200000",
// "msg":"success",
// "data":1546837113087
// }
//
return this.safeInteger (response, 'data');
}
async fetchMarkets (params = {}) {
const response = await this.publicGetSymbols (params);
//
// { quoteCurrency: 'BTC',
// symbol: 'KCS-BTC',
// quoteMaxSize: '9999999',
// quoteIncrement: '0.000001',
// baseMinSize: '0.01',
// quoteMinSize: '0.00001',
// enableTrading: true,
// priceIncrement: '0.00000001',
// name: 'KCS-BTC',
// baseIncrement: '0.01',
// baseMaxSize: '9999999',
// baseCurrency: 'KCS' }
//
const data = response['data'];
const result = [];
for (let i = 0; i < data.length; i++) {
const market = data[i];
const id = this.safeString (market, 'symbol');
const [ baseId, quoteId ] = id.split ('-');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
const symbol = base + '/' + quote;
const active = this.safeValue (market, 'enableTrading');
const baseMaxSize = this.safeFloat (market, 'baseMaxSize');
const baseMinSize = this.safeFloat (market, 'baseMinSize');
const quoteMaxSize = this.safeFloat (market, 'quoteMaxSize');
const quoteMinSize = this.safeFloat (market, 'quoteMinSize');
// const quoteIncrement = this.safeFloat (market, 'quoteIncrement');
const precision = {
'amount': this.precisionFromString (this.safeString (market, 'baseIncrement')),
'price': this.precisionFromString (this.safeString (market, 'priceIncrement')),
};
const limits = {
'amount': {
'min': baseMinSize,
'max': baseMaxSize,
},
'price': {
'min': this.safeFloat (market, 'priceIncrement'),
'max': quoteMaxSize / baseMinSize,
},
'cost': {
'min': quoteMinSize,
'max': quoteMaxSize,
},
};
result.push ({
'id': id,
'symbol': symbol,
'baseId': baseId,
'quoteId': quoteId,
'base': base,
'quote': quote,
'active': active,
'precision': precision,
'limits': limits,
'info': market,
});
}
return result;
}
async fetchCurrencies (params = {}) {
const response = await this.publicGetCurrencies (params);
//
// {
// precision: 10,
// name: 'KCS',
// fullName: 'KCS shares',
// currency: 'KCS'
// }
//
const responseData = response['data'];
const result = {};
for (let i = 0; i < responseData.length; i++) {
const entry = responseData[i];
const id = this.safeString (entry, 'currency');
const name = this.safeString (entry, 'fullName');
const code = this.safeCurrencyCode (id);
const precision = this.safeInteger (entry, 'precision');
result[code] = {
'id': id,
'name': name,
'code': code,
'precision': precision,
'info': entry,
};
}
return result;
}
async fetchAccounts (params = {}) {
const response = await this.privateGetAccounts (params);
//
// { code: "200000",
// data: [ { balance: "0.00009788",
// available: "0.00009788",
// holds: "0",
// currency: "BTC",
// id: "5c6a4fd399a1d81c4f9cc4d0",
// type: "trade" },
// ...,
// { balance: "0.00000001",
// available: "0.00000001",
// holds: "0",
// currency: "ETH",
// id: "5c6a49ec99a1d819392e8e9f",
// type: "trade" } ] }
//
const data = this.safeValue (response, 'data');
const result = [];
for (let i = 0; i < data.length; i++) {
const account = data[i];
const accountId = this.safeString (account, 'id');
const currencyId = this.safeString (account, 'currency');
const code = this.safeCurrencyCode (currencyId);
const type = this.safeString (account, 'type'); // main or trade
result.push ({
'id': accountId,
'type': type,
'currency': code,
'info': account,
});
}
return result;
}
async fetchFundingFee (code, params = {}) {
const currencyId = this.currencyId (code);
const request = {
'currency': currencyId,
};
const response = await this.privateGetWithdrawalsQuotas (this.extend (request, params));
const data = response['data'];
const withdrawFees = {};
withdrawFees[code] = this.safeFloat (data, 'withdrawMinFee');
return {
'info': response,
'withdraw': withdrawFees,
'deposit': {},
};
}
parseTicker (ticker, market = undefined) {
//
// {
// symbol: "ETH-BTC",
// high: "0.019518",
// vol: "7997.82836194",
// last: "0.019329",
// low: "0.019",
// buy: "0.019329",
// sell: "0.01933",
// changePrice: "-0.000139",
// time: 1580553706304,
// averagePrice: "0.01926386",
// changeRate: "-0.0071",
// volValue: "154.40791568183474"
// }
//
// {
// "trading": true,
// "symbol": "KCS-BTC",
// "buy": 0.00011,
// "sell": 0.00012,
// "sort": 100,
// "volValue": 3.13851792584, //total
// "baseCurrency": "KCS",
// "market": "BTC",
// "quoteCurrency": "BTC",
// "symbolCode": "KCS-BTC",
// "datetime": 1548388122031,
// "high": 0.00013,
// "vol": 27514.34842,
// "low": 0.0001,
// "changePrice": -1.0e-5,
// "changeRate": -0.0769,
// "lastTradedPrice": 0.00012,
// "board": 0,
// "mark": 0
// }
//
let percentage = this.safeFloat (ticker, 'changeRate');
if (percentage !== undefined) {
percentage = percentage * 100;
}
const last = this.safeFloat2 (ticker, 'last', 'lastTradedPrice');
let symbol = undefined;
const marketId = this.safeString (ticker, 'symbol');
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
symbol = market['symbol'];
} else {
const [ baseId, quoteId ] = marketId.split ('-');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
}
if (symbol === undefined) {
if (market !== undefined) {
symbol = market['symbol'];
}
}
const timestamp = this.safeInteger2 (ticker, 'time', 'datetime');
return {
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'high': this.safeFloat (ticker, 'high'),
'low': this.safeFloat (ticker, 'low'),
'bid': this.safeFloat (ticker, 'buy'),
'bidVolume': undefined,
'ask': this.safeFloat (ticker, 'sell'),
'askVolume': undefined,
'vwap': undefined,
'open': this.safeFloat (ticker, 'open'),
'close': last,
'last': last,
'previousClose': undefined,
'change': this.safeFloat (ticker, 'changePrice'),
'percentage': percentage,
'average': this.safeFloat (ticker, 'averagePrice'),
'baseVolume': this.safeFloat (ticker, 'vol'),
'quoteVolume': this.safeFloat (ticker, 'volValue'),
'info': ticker,
};
}
async fetchTickers (symbols = undefined, params = {}) {
await this.loadMarkets ();
const response = await this.publicGetMarketAllTickers (params);
//
// {
// "code": "200000",
// "data": {
// "date": 1550661940645,
// "ticker": [
// 'buy': '0.00001168',
// 'changePrice': '-0.00000018',
// 'changeRate': '-0.0151',
// 'datetime': 1550661146316,
// 'high': '0.0000123',
// 'last': '0.00001169',
// 'low': '0.00001159',
// 'sell': '0.00001182',
// 'symbol': 'LOOM-BTC',
// 'vol': '44399.5669'
// },
// ]
// }
//
const data = this.safeValue (response, 'data', {});
const tickers = this.safeValue (data, 'ticker', []);
const result = {};
for (let i = 0; i < tickers.length; i++) {
const ticker = this.parseTicker (tickers[i]);
const symbol = this.safeString (ticker, 'symbol');
if (symbol !== undefined) {
result[symbol] = ticker;
}
}
return result;
}
async fetchTicker (symbol, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
'symbol': market['id'],
};
const response = await this.publicGetMarketStats (this.extend (request, params));
//
// {
// "code": "200000",
// "data": {
// 'buy': '0.00001168',
// 'changePrice': '-0.00000018',
// 'changeRate': '-0.0151',
// 'datetime': 1550661146316,
// 'high': '0.0000123',
// 'last': '0.00001169',
// 'low': '0.00001159',
// 'sell': '0.00001182',
// 'symbol': 'LOOM-BTC',
// 'vol': '44399.5669'
// },
// }
//
return this.parseTicker (response['data'], market);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
//
// [
// "1545904980", // Start time of the candle cycle
// "0.058", // opening price
// "0.049", // closing price
// "0.058", // highest price
// "0.049", // lowest price
// "0.018", // base volume
// "0.000945", // quote volume
// ]
//
return [
parseInt (ohlcv[0]) * 1000,
parseFloat (ohlcv[1]),
parseFloat (ohlcv[3]),
parseFloat (ohlcv[4]),
parseFloat (ohlcv[2]),
parseFloat (ohlcv[5]),
];
}
async fetchOHLCV (symbol, timeframe = '15m', since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
const marketId = market['id'];
const request = {
'symbol': marketId,
'type': this.timeframes[timeframe],
};
const duration = this.parseTimeframe (timeframe) * 1000;
let endAt = this.milliseconds (); // required param
if (since !== undefined) {
request['startAt'] = parseInt (Math.floor (since / 1000));
if (limit === undefined) {
// https://docs.kucoin.com/#get-klines
// https://docs.kucoin.com/#details
// For each query, the system would return at most 1500 pieces of data.
// To obtain more data, please page the data by time.
limit = this.safeInteger (this.options, 'fetchOHLCVLimit', 1500);
}
endAt = this.sum (since, limit * duration);
} else if (limit !== undefined) {
since = endAt - limit * duration;
request['startAt'] = parseInt (Math.floor (since / 1000));
}
request['endAt'] = parseInt (Math.floor (endAt / 1000));
const response = await this.publicGetMarketCandles (this.extend (request, params));
const responseData = this.safeValue (response, 'data', []);
return this.parseOHLCVs (responseData, market, timeframe, since, limit);
}
async createDepositAddress (code, params = {}) {
await this.loadMarkets ();
const currencyId = this.currencyId (code);
const request = { 'currency': currencyId };
const response = await this.privatePostDepositAddresses (this.extend (request, params));
// BCH {"code":"200000","data":{"address":"bitcoincash:qza3m4nj9rx7l9r0cdadfqxts6f92shvhvr5ls4q7z","memo":""}}
// BTC {"code":"200000","data":{"address":"36SjucKqQpQSvsak9A7h6qzFjrVXpRNZhE","memo":""}}
const data = this.safeValue (response, 'data', {});
let address = this.safeString (data, 'address');
// BCH/BSV is returned with a "bitcoincash:" prefix, which we cut off here and only keep the address
if (address !== undefined) {
address = address.replace ('bitcoincash:', '');
}
const tag = this.safeString (data, 'memo');
this.checkAddress (address);
return {
'info': response,
'currency': code,
'address': address,
'tag': tag,
};
}
async fetchDepositAddress (code, params = {}) {
await this.loadMarkets ();
const currencyId = this.currencyId (code);
const request = { 'currency': currencyId };
const response = await this.privateGetDepositAddresses (this.extend (request, params));
// BCH {"code":"200000","data":{"address":"bitcoincash:qza3m4nj9rx7l9r0cdadfqxts6f92shvhvr5ls4q7z","memo":""}}
// BTC {"code":"200000","data":{"address":"36SjucKqQpQSvsak9A7h6qzFjrVXpRNZhE","memo":""}}
const data = this.safeValue (response, 'data', {});
let address = this.safeString (data, 'address');
// BCH/BSV is returned with a "bitcoincash:" prefix, which we cut off here and only keep the address
if (address !== undefined) {
address = address.replace ('bitcoincash:', '');
}
const tag = this.safeString (data, 'memo');
this.checkAddress (address);
return {
'info': response,
'currency': code,
'address': address,
'tag': tag,
};
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
let level = '2';
if (limit !== undefined) {
if ((limit !== 20) && (limit !== 100)) {
throw new ExchangeError (this.id + ' fetchOrderBook limit argument must be undefined, 20 or 100');
}
level += '_' + limit.toString ();
}
await this.loadMarkets ();
const marketId = this.marketId (symbol);
const request = this.extend ({ 'symbol': marketId, 'level': level }, params);
const response = await this.publicGetMarketOrderbookLevelLevel (request);
//
// { sequence: '1547731421688',
// asks: [ [ '5c419328ef83c75456bd615c', '0.9', '0.09' ], ... ],
// bids: [ [ '5c419328ef83c75456bd615c', '0.9', '0.09' ], ... ], }
//
const data = response['data'];
const timestamp = this.safeInteger (data, 'time');
// level can be a string such as 2_20 or 2_100
const levelString = this.safeString (request, 'level');
const levelParts = levelString.split ('_');
const offset = parseInt (levelParts[0]);
const orderbook = this.parseOrderBook (data, timestamp, 'bids', 'asks', offset - 2, offset - 1);
orderbook['nonce'] = this.safeInteger (data, 'sequence');
return orderbook;
}
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
await this.loadMarkets ();
const marketId = this.marketId (symbol);
// required param, cannot be used twice
const clientOid = this.uuid ();
const request = {
'clientOid': clientOid,
'side': side,
'symbol': marketId,
'type': type,
};
if (type !== 'market') {
request['price'] = this.priceToPrecision (symbol, price);
request['size'] = this.amountToPrecision (symbol, amount);
} else {
if (this.safeValue (params, 'quoteAmount')) {
// used to create market order by quote amount - https://github.com/ccxt/ccxt/issues/4876
request['funds'] = this.amountToPrecision (symbol, amount);
} else {
request['size'] = this.amountToPrecision (symbol, amount);
}
}
const response = await this.privatePostOrders (this.extend (request, params));
//
// {
// code: '200000',
// data: {
// "orderId": "5bd6e9286d99522a52e458de"
// }
// }
//
const data = this.safeValue (response, 'data', {});
const timestamp = this.milliseconds ();
const order = {
'id': this.safeString (data, 'orderId'),
'symbol': symbol,
'type': type,
'side': side,
'price': price,
'cost': undefined,
'filled': undefined,
'remaining': undefined,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'fee': undefined,
'status': 'open',
'clientOid': clientOid,
'info': data,
};
if (!this.safeValue (params, 'quoteAmount')) {
order['amount'] = amount;
}
return order;
}
async cancelOrder (id, symbol = undefined, params = {}) {
const request = { 'orderId': id };
const response = await this.privateDeleteOrdersOrderId (this.extend (request, params));
return response;
}
async fetchOrdersByStatus (status, symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const request = {
'status': status,
};
let market = undefined;
if (symbol !== undefined) {
market = this.market (symbol);
request['symbol'] = market['id'];
}
if (since !== undefined) {
request['startAt'] = since;
}
if (limit !== undefined) {
request['pageSize'] = limit;
}
const response = await this.privateGetOrders (this.extend (request, params));
//
// {
// code: '200000',
// data: {
// "currentPage": 1,
// "pageSize": 1,
// "totalNum": 153408,
// "totalPage": 153408,
// "items": [
// {
// "id": "5c35c02703aa673ceec2a168", //orderid
// "symbol": "BTC-USDT", //symbol
// "opType": "DEAL", // operation type,deal is pending order,cancel is cancel order
// "type": "limit", // order type,e.g. limit,markrt,stop_limit.
// "side": "buy", // transaction direction,include buy and sell
// "price": "10", // order price
// "size": "2", // order quantity
// "funds": "0", // order funds
// "dealFunds": "0.166", // deal funds
// "dealSize": "2", // deal quantity
// "fee": "0", // fee
// "feeCurrency": "USDT", // charge fee currency
// "stp": "", // self trade prevention,include CN,CO,DC,CB
// "stop": "", // stop type
// "stopTriggered": false, // stop order is triggered
// "stopPrice": "0", // stop price
// "timeInForce": "GTC", // time InForce,include GTC,GTT,IOC,FOK
// "postOnly": false, // postOnly
// "hidden": false, // hidden order
// "iceberg": false, // iceberg order
// "visibleSize": "0", // display quantity for iceberg order
// "cancelAfter": 0, // cancel orders time,requires timeInForce to be GTT
// "channel": "IOS", // order source
// "clientOid": "", // user-entered order unique mark
// "remark": "", // remark
// "tags": "", // tag order source
// "isActive": false, // status before unfilled or uncancelled
// "cancelExist": false, // order cancellation transaction record
// "createdAt": 1547026471000 // time
// },
// ]
// }
// }
const responseData = this.safeValue (response, 'data', {});
const orders = this.safeValue (responseData, 'items', []);
return this.parseOrders (orders, market, since, limit);
}
async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
return await this.fetchOrdersByStatus ('done', symbol, since, limit, params);
}
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
return await this.fetchOrdersByStatus ('active', symbol, since, limit, params);
}
async fetchOrder (id, symbol = undefined, params = {}) {
await this.loadMarkets ();
const request = {
'orderId': id,
};
let market = undefined;
if (symbol !== undefined) {
market = this.market (symbol);
}
const response = await this.privateGetOrdersOrderId (this.extend (request, params));
const responseData = response['data'];
return this.parseOrder (responseData, market);
}
parseOrder (order, market = undefined) {
//
// fetchOpenOrders, fetchClosedOrders
//
// {
// "id": "5c35c02703aa673ceec2a168", //orderid
// "symbol": "BTC-USDT", //symbol
// "opType": "DEAL", // operation type,deal is pending order,cancel is cancel order
// "type": "limit", // order type,e.g. limit,markrt,stop_limit.
// "side": "buy", // transaction direction,include buy and sell
// "price": "10", // order price
// "size": "2", // order quantity
// "funds": "0", // order funds
// "dealFunds": "0.166", // deal funds
// "dealSize": "2", // deal quantity
// "fee": "0", // fee
// "feeCurrency": "USDT", // charge fee currency
// "stp": "", // self trade prevention,include CN,CO,DC,CB
// "stop": "", // stop type
// "stopTriggered": false, // stop order is triggered
// "stopPrice": "0", // stop price
// "timeInForce": "GTC", // time InForce,include GTC,GTT,IOC,FOK
// "postOnly": false, // postOnly
// "hidden": false, // hidden order
// "iceberg": false, // iceberg order
// "visibleSize": "0", // display quantity for iceberg order
// "cancelAfter": 0, // cancel orders time,requires timeInForce to be GTT
// "channel": "IOS", // order source
// "clientOid": "", // user-entered order unique mark
// "remark": "", // remark
// "tags": "", // tag order source
// "isActive": false, // status before unfilled or uncancelled
// "cancelExist": false, // order cancellation transaction record
// "createdAt": 1547026471000 // time
// }
//
let symbol = undefined;
const marketId = this.safeString (order, 'symbol');
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
symbol = market['symbol'];
} else {
const [ baseId, quoteId ] = marketId.split ('-');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
market = this.safeValue (this.markets_by_id, marketId);
}
if (symbol === undefined) {
if (market !== undefined) {
symbol = market['symbol'];
}
}
const orderId = this.safeString (order, 'id');
const type = this.safeString (order, 'type');
const timestamp = this.safeInteger (order, 'createdAt');
const datetime = this.iso8601 (timestamp);
let price = this.safeFloat (order, 'price');
const side = this.safeString (order, 'side');
const feeCurrencyId = this.safeString (order, 'feeCurrency');
const feeCurrency = this.safeCurrencyCode (feeCurrencyId);
const feeCost = this.safeFloat (order, 'fee');
const amount = this.safeFloat (order, 'size');
const filled = this.safeFloat (order, 'dealSize');
const cost = this.safeFloat (order, 'dealFunds');
const remaining = amount - filled;
// bool
let status = order['isActive'] ? 'open' : 'closed';
status = order['cancelExist'] ? 'canceled' : status;
const fee = {
'currency': feeCurrency,
'cost': feeCost,
};
if (type === 'market') {
if (price === 0.0) {
if ((cost !== undefined) && (filled !== undefined)) {
if ((cost > 0) && (filled > 0)) {
price = cost / filled;
}
}
}
}
return {
'id': orderId,
'symbol': symbol,
'type': type,
'side': side,
'amount': amount,
'price': price,
'cost': cost,
'filled': filled,
'remaining': remaining,
'timestamp': timestamp,
'datetime': datetime,
'fee': fee,
'status': status,
'info': order,
};
}
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const request = {};
let market = undefined;
if (symbol !== undefined) {
market = this.market (symbol);
request['symbol'] = market['id'];
}
if (limit !== undefined) {
request['pageSize'] = limit;
}
const method = this.options['fetchMyTradesMethod'];
let parseResponseData = false;
if (method === 'private_get_fills') {
// does not return trades earlier than 2019-02-18T00:00:00Z
if (since !== undefined) {
// only returns trades up to one week after the since param
request['startAt'] = since;
}
} else if (method === 'private_get_limit_fills') {
// does not return trades earlier than 2019-02-18T00:00:00Z
// takes no params
// only returns first 1000 trades (not only "in the last 24 hours" as stated in the docs)
parseResponseData = true;
} else if (method === 'private_get_hist_orders') {
// despite that this endpoint is called `HistOrders`
// it returns historical trades instead of orders
// returns trades earlier than 2019-02-18T00:00:00Z only
if (since !== undefined) {
request['startAt'] = parseInt (since / 1000);
}
} else {
throw new ExchangeError (this.id + ' invalid fetchClosedOrder method');
}
const response = await this[method] (this.extend (request, params));
//
// {
// "currentPage": 1,
// "pageSize": 50,
// "totalNum": 1,
// "totalPage": 1,
// "items": [
// {
// "symbol":"BTC-USDT", // symbol
// "tradeId":"5c35c02709e4f67d5266954e", // trade id
// "orderId":"5c35c02703aa673ceec2a168", // order id
// "counterOrderId":"5c1ab46003aa676e487fa8e3", // counter order id
// "side":"buy", // transaction direction,include buy and sell
// "liquidity":"taker", // include taker and maker
// "forceTaker":true, // forced to become taker
// "price":"0.083", // order price
// "size":"0.8424304", // order quantity
// "funds":"0.0699217232", // order funds
// "fee":"0", // fee
// "feeRate":"0", // fee rate
// "feeCurrency":"USDT", // charge fee currency
// "stop":"", // stop type
// "type":"limit", // order type, e.g. limit, market, stop_limit.
// "createdAt":1547026472000 // time
// },
// //------------------------------------------------------
// // v1 (historical) trade response structure
// {
// "symbol": "SNOV-ETH",
// "dealPrice": "0.0000246",
// "dealValue": "0.018942",
// "amount": "770",
// "fee": "0.00001137",
// "side": "sell",
// "createdAt": 1540080199
// "id":"5c4d389e4c8c60413f78e2e5",
// }
// ]
// }
//
const data = this.safeValue (response, 'data', {});
let trades = undefined;
if (parseResponseData) {
trades = data;
} else {
trades = this.safeValue (data, 'items', []);
}
return this.parseTrades (trades, market, since, limit);
}
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
'symbol': market['id'],
};
if (since !== undefined) {
request['startAt'] = Math.floor (since / 1000);
}
if (limit !== undefined) {
request['pageSize'] = limit;
}
const response = await this.publicGetMarketHistories (this.extend (request, params));
//
// {
// "code": "200000",
// "data": [
// {
// "sequence": "1548764654235",
// "side": "sell",
// "size":"0.6841354",
// "price":"0.03202",
// "time":1548848575203567174
// }
// ]
// }
//
const trades = this.safeValue (response, 'data', []);
return this.parseTrades (trades, market, since, limit);
}
parseTrade (trade, market = undefined) {
//
// fetchTrades (public)
//
// {
// "sequence": "1548764654235",
// "side": "sell",
// "size":"0.6841354",
// "price":"0.03202",
// "time":1548848575203567174
// }
//
// {
// sequence: '1568787654360',
// symbol: 'BTC-USDT',
// side: 'buy',
// size: '0.00536577',
// price: '9345',
// takerOrderId: '5e356c4a9f1a790008f8d921',
// time: '1580559434436443257',
// type: 'match',
// makerOrderId: '5e356bffedf0010008fa5d7f',
// tradeId: '5e356c4aeefabd62c62a1ece'
// }
//
// fetchMyTrades (private) v2
//
// {
// "symbol":"BTC-USDT",
// "tradeId":"5c35c02709e4f67d5266954e",
// "orderId":"5c35c02703aa673ceec2a168",
// "counterOrderId":"5c1ab46003aa676e487fa8e3",
// "side":"buy",
// "liquidity":"taker",
// "forceTaker":true,
// "price":"0.083",
// "size":"0.8424304",
// "funds":"0.0699217232",
// "fee":"0",
// "feeRate":"0",
// "feeCurrency":"USDT",
// "stop":"",
// "type":"limit",
// "createdAt":1547026472000
// }
//
// fetchMyTrades v2 alternative format since 2019-05-21 https://github.com/ccxt/ccxt/pull/5162
//
// {
// symbol: "OPEN-BTC",
// forceTaker: false,
// orderId: "5ce36420054b4663b1fff2c9",
// fee: "0",
// feeCurrency: "",
// type: "",
// feeRate: "0",
// createdAt: 1558417615000,
// size: "12.8206",
// stop: "",
// price: "0",
// funds: "0",
// tradeId: "5ce390cf6e0db23b861c6e80"
// }
//
// fetchMyTrades (private) v1 (historical)
//
// {
// "symbol": "SNOV-ETH",
// "dealPrice": "0.0000246",
// "dealValue": "0.018942",
// "amount": "770",
// "fee": "0.00001137",
// "side": "sell",
// "createdAt": 1540080199
// "id":"5c4d389e4c8c60413f78e2e5",
// }
//
let symbol = undefined;
const marketId = this.safeString (trade, 'symbol');
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
symbol = market['symbol'];
} else {
const [ baseId, quoteId ] = marketId.split ('-');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
}
if (symbol === undefined) {
if (market !== undefined) {
symbol = market['symbol'];
}
}
const id = this.safeString2 (trade, 'tradeId', 'id');
const orderId = this.safeString (trade, 'orderId');
const takerOrMaker = this.safeString (trade, 'liquidity');
const amount = this.safeFloat2 (trade, 'size', 'amount');
let timestamp = this.safeInteger (trade, 'time');
if (timestamp !== undefined) {
timestamp = parseInt (timestamp / 1000000);
} else {
timestamp = this.safeInteger (trade, 'createdAt');
// if it's a historical v1 trade, the exchange returns timestamp in seconds
if (('dealValue' in trade) && (timestamp !== undefined)) {
timestamp = timestamp * 1000;
}
}
const price = this.safeFloat2 (trade, 'price', 'dealPrice');
const side = this.safeString (trade, 'side');
let fee = undefined;
const feeCost = this.safeFloat (trade, 'fee');
if (feeCost !== undefined) {
const feeCurrencyId = this.safeString (trade, 'feeCurrency');
let feeCurrency = this.safeCurrencyCode (feeCurrencyId);
if (feeCurrency === undefined) {
if (market !== undefined) {
feeCurrency = (side === 'sell') ? market['quote'] : market['base'];
}
}
fee = {
'cost': feeCost,
'currency': feeCurrency,
'rate': this.safeFloat (trade, 'feeRate'),
};
}
const type = this.safeString (trade, 'type');
let cost = this.safeFloat2 (trade, 'funds', 'dealValue');
if (cost === undefined) {
if (amount !== undefined) {
if (price !== undefined) {
cost = amount * price;
}
}
}
return {
'info': trade,
'id': id,
'order': orderId,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'symbol': symbol,
'type': type,
'takerOrMaker': takerOrMaker,
'side': side,
'price': price,
'amount': amount,
'cost': cost,
'fee': fee,
};
}
async withdraw (code, amount, address, tag = undefined, params = {}) {
await this.loadMarkets ();
this.checkAddress (address);
const currency = this.currencyId (code);
const request = {
'currency': currency,
'address': address,
'amount': amount,
};
if (tag !== undefined) {
request['memo'] = tag;
}
const response = await this.privatePostWithdrawals (this.extend (request, params));
//
// https://github.com/ccxt/ccxt/issues/5558
//
// {
// "code": 200000,
// "data": {
// "withdrawalId": "abcdefghijklmnopqrstuvwxyz"
// }
// }
//
const data = this.safeValue (response, 'data', {});
return {
'id': this.safeString (data, 'withdrawalId'),
'info': response,
};
}
parseTransactionStatus (status) {
const statuses = {
'SUCCESS': 'ok',