guyo13-ccxt
Version:
A ccxt fork with some corrections
2,160 lines • 92.1 kB
JavaScript
'use strict';
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, AccountSuspended, InvalidNonce, NotSupported, BadRequest, AuthenticationError, BadSymbol, RateLimitExceeded, PermissionDenied } = require ('./base/errors');
const Precise = require ('./base/Precise');
// ---------------------------------------------------------------------------
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,
'cancelAllOrders': true,
'cancelOrder': true,
'createDepositAddress': true,
'createOrder': true,
'fetchAccounts': true,
'fetchBalance': true,
'fetchClosedOrders': true,
'fetchCurrencies': true,
'fetchDepositAddress': true,
'fetchDeposits': true,
'fetchFundingFee': true,
'fetchLedger': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchStatus': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTime': true,
'fetchTrades': true,
'fetchWithdrawals': true,
'withdraw': true,
'transfer': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/51840849/87295558-132aaf80-c50e-11ea-9801-a2fb0c57c799.jpg',
'referral': 'https://www.kucoin.com/?rcode=E5wkqe',
'api': {
'public': 'https://openapi-v2.kucoin.com',
'private': 'https://openapi-v2.kucoin.com',
'futuresPrivate': 'https://api-futures.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',
'status',
'symbols',
'markets',
'market/allTickers',
'market/orderbook/level{level}_{limit}',
'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}',
'prices',
'mark-price/{symbol}/current',
'margin/config',
],
'post': [
'bullet-public',
],
},
'private': {
'get': [
'accounts',
'accounts/{accountId}',
'accounts/{accountId}/ledgers',
'accounts/{accountId}/holds',
'accounts/transferable',
'sub/user',
'sub-accounts',
'sub-accounts/{subUserId}',
'deposit-addresses',
'deposits',
'hist-deposits',
'hist-orders',
'hist-withdrawals',
'withdrawals',
'withdrawals/quotas',
'orders',
'order/client-order/{clientOid}',
'orders/{orderId}',
'limit/orders',
'fills',
'limit/fills',
'margin/account',
'margin/borrow',
'margin/borrow/outstanding',
'margin/borrow/borrow/repaid',
'margin/lend/active',
'margin/lend/done',
'margin/lend/trade/unsettled',
'margin/lend/trade/settled',
'margin/lend/assets',
'margin/market',
'margin/trade/last',
'stop-order/{orderId}',
'stop-order',
'stop-order/queryOrderByClientOid',
],
'post': [
'accounts',
'accounts/inner-transfer',
'accounts/sub-transfer',
'deposit-addresses',
'withdrawals',
'orders',
'orders/multi',
'margin/borrow',
'margin/order',
'margin/repay/all',
'margin/repay/single',
'margin/lend',
'margin/toggle-auto-lend',
'bullet-private',
'stop-order',
],
'delete': [
'withdrawals/{withdrawalId}',
'orders',
'orders/client-order/{clientOid}',
'orders/{orderId}',
'margin/lend/{orderId}',
'stop-order/cancelOrderByClientOid',
'stop-order/{orderId}',
'stop-order/cancel',
],
},
'futuresPrivate': {
'get': [
'account-overview',
'positions',
],
'post': [
'transfer-out',
],
},
},
'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."}
'Unsuccessful! Exceeded the max. funds out-transfer limit': InsufficientFunds, // {"code":"200000","msg":"Unsuccessful! Exceeded the max. funds out-transfer limit"}
'400': BadRequest,
'401': AuthenticationError,
'403': NotSupported,
'404': NotSupported,
'405': NotSupported,
'429': RateLimitExceeded,
'500': ExchangeNotAvailable, // Internal Server Error -- We had a problem with our server. Try again later.
'503': ExchangeNotAvailable,
'101030': PermissionDenied, // {"code":"101030","msg":"You haven't yet enabled the margin trading"}
'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,
'require more permission': PermissionDenied,
},
},
'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',
'TRY': 'Trias',
'VAI': 'VAIOT',
},
'options': {
'version': 'v1',
'symbolSeparator': '-',
'fetchMyTradesMethod': 'private_get_fills',
'fetchBalance': 'trade',
// endpoint versions
'versions': {
'public': {
'GET': {
'status': 'v1',
'market/orderbook/level2': 'v2',
'market/orderbook/level3': 'v2',
'market/orderbook/level2_20': 'v1',
'market/orderbook/level2_100': 'v1',
'market/orderbook/level{level}': 'v2',
'market/orderbook/level{level}_{limit}': 'v1',
},
},
'private': {
'POST': {
'accounts/inner-transfer': 'v2',
'accounts/sub-transfer': 'v2',
},
},
'futuresPrivate': {
'GET': {
'account-overview': 'v1',
'positions': 'v1',
},
'POST': {
'transfer-out': 'v2',
},
},
},
'accountsByType': {
'trade': 'trade',
'trading': 'trade',
'margin': 'margin',
'main': 'main',
'futures': 'contract',
'contract': 'contract',
'pool': 'pool',
'pool-x': 'pool',
},
},
});
}
nonce () {
return this.milliseconds ();
}
async loadTimeDifference (params = {}) {
const response = await this.publicGetTimestamp (params);
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 fetchStatus (params = {}) {
const response = await this.publicGetStatus (params);
//
// {
// "code":"200000",
// "data":{
// "msg":"",
// "status":"open"
// }
// }
//
const data = this.safeValue (response, 'data', {});
let status = this.safeValue (data, 'status');
if (status !== undefined) {
status = (status === 'open') ? 'ok' : 'maintenance';
this.status = this.extend (this.status, {
'status': status,
'updated': this.milliseconds (),
});
}
return this.status;
}
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.safeNumber (market, 'baseMaxSize');
const baseMinSizeString = this.safeString (market, 'baseMinSize');
const quoteMaxSizeString = this.safeString (market, 'quoteMaxSize');
const baseMinSize = this.parseNumber (baseMinSizeString);
const quoteMaxSize = this.parseNumber (quoteMaxSizeString);
const quoteMinSize = this.safeNumber (market, 'quoteMinSize');
// const quoteIncrement = this.safeNumber (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.safeNumber (market, 'priceIncrement'),
'max': this.parseNumber (Precise.stringDiv (quoteMaxSizeString, baseMinSizeString)),
},
'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);
//
// {
// "currency": "OMG",
// "name": "OMG",
// "fullName": "OmiseGO",
// "precision": 8,
// "confirms": 12,
// "withdrawalMinSize": "4",
// "withdrawalMinFee": "1.25",
// "isWithdrawEnabled": false,
// "isDepositEnabled": false,
// "isMarginEnabled": false,
// "isDebitEnabled": false
// }
//
const data = this.safeValue (response, 'data', []);
const result = {};
for (let i = 0; i < data.length; i++) {
const entry = data[i];
const id = this.safeString (entry, 'currency');
const name = this.safeString (entry, 'fullName');
const code = this.safeCurrencyCode (id);
const precision = this.safeInteger (entry, 'precision');
const isWithdrawEnabled = this.safeValue (entry, 'isWithdrawEnabled', false);
const isDepositEnabled = this.safeValue (entry, 'isDepositEnabled', false);
const fee = this.safeNumber (entry, 'withdrawalMinFee');
const active = (isWithdrawEnabled && isDepositEnabled);
result[code] = {
'id': id,
'name': name,
'code': code,
'precision': precision,
'info': entry,
'active': active,
'fee': fee,
'limits': this.limits,
};
}
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.safeNumber (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.safeNumber (ticker, 'changeRate');
if (percentage !== undefined) {
percentage = percentage * 100;
}
const last = this.safeNumber2 (ticker, 'last', 'lastTradedPrice');
const marketId = this.safeString (ticker, 'symbol');
const symbol = this.safeSymbol (marketId, market, '-');
const baseVolume = this.safeNumber (ticker, 'vol');
const quoteVolume = this.safeNumber (ticker, 'volValue');
const vwap = this.vwap (baseVolume, quoteVolume);
const timestamp = this.safeInteger2 (ticker, 'time', 'datetime');
return {
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'high': this.safeNumber (ticker, 'high'),
'low': this.safeNumber (ticker, 'low'),
'bid': this.safeNumber (ticker, 'buy'),
'bidVolume': undefined,
'ask': this.safeNumber (ticker, 'sell'),
'askVolume': undefined,
'vwap': vwap,
'open': this.safeNumber (ticker, 'open'),
'close': last,
'last': last,
'previousClose': undefined,
'change': this.safeNumber (ticker, 'changePrice'),
'percentage': percentage,
'average': this.safeNumber (ticker, 'averagePrice'),
'baseVolume': baseVolume,
'quoteVolume': quoteVolume,
'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 this.filterByArray (result, 'symbol', symbols);
}
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) {
//
// [
// "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 [
this.safeTimestamp (ohlcv, 0),
this.safeNumber (ohlcv, 1),
this.safeNumber (ohlcv, 3),
this.safeNumber (ohlcv, 4),
this.safeNumber (ohlcv, 2),
this.safeNumber (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));
//
// {
// "code":"200000",
// "data":[
// ["1591517700","0.025078","0.025069","0.025084","0.025064","18.9883256","0.4761861079404"],
// ["1591516800","0.025089","0.025079","0.025089","0.02506","99.4716622","2.494143499081"],
// ["1591515900","0.025079","0.02509","0.025091","0.025068","59.83701271","1.50060885172798"],
// ]
// }
//
const data = this.safeValue (response, 'data', []);
return this.parseOHLCVs (data, 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');
if (code !== 'NIM') {
// contains spaces
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', {});
const address = this.safeString (data, 'address');
const tag = this.safeString (data, 'memo');
if (code !== 'NIM') {
// contains spaces
this.checkAddress (address);
}
return {
'info': response,
'currency': code,
'address': address,
'tag': tag,
};
}
async fetchL3OrderBook (symbol, limit = undefined, params = {}) {
return await this.fetchOrderBook (symbol, limit, { 'level': 3 });
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
await this.loadMarkets ();
const marketId = this.marketId (symbol);
const level = this.safeInteger (params, 'level', 2);
const request = { 'symbol': marketId, 'level': level };
let method = 'publicGetMarketOrderbookLevelLevel';
if (level === 2) {
if (limit !== undefined) {
if ((limit === 20) || (limit === 100)) {
request['limit'] = limit;
method = 'publicGetMarketOrderbookLevelLevelLimit';
} else {
throw new ExchangeError (this.id + ' fetchOrderBook limit argument must be undefined, 20 or 100');
}
}
}
const response = await this[method] (this.extend (request, params));
//
// 'market/orderbook/level2'
// 'market/orderbook/level2_20'
// 'market/orderbook/level2_100'
//
// {
// "code":"200000",
// "data":{
// "sequence":"1583235112106",
// "asks":[
// // ...
// ["0.023197","12.5067468"],
// ["0.023194","1.8"],
// ["0.023191","8.1069672"]
// ],
// "bids":[
// ["0.02319","1.6000002"],
// ["0.023189","2.2842325"],
// ],
// "time":1586584067274
// }
// }
//
// 'market/orderbook/level3'
//
// {
// "code":"200000",
// "data":{
// "sequence":"1583731857120",
// "asks":[
// // id, price, size, timestamp in nanoseconds
// ["5e915f8acd26670009675300","6925.7","0.2","1586585482194286069"],
// ["5e915f8ace35a200090bba48","6925.7","0.001","1586585482229569826"],
// ["5e915f8a8857740009ca7d33","6926","0.00001819","1586585482149148621"],
// ],
// "bids":[
// ["5e915f8acca406000ac88194","6925.6","0.05","1586585482384384842"],
// ["5e915f93cd26670009676075","6925.6","0.08","1586585491334914600"],
// ["5e915f906aa6e200099b49f6","6925.4","0.2","1586585488941126340"],
// ],
// "time":1586585492487
// }
// }
//
const data = this.safeValue (response, 'data', {});
const timestamp = this.safeInteger (data, 'time');
const orderbook = this.parseOrderBook (data, symbol, timestamp, 'bids', 'asks', level - 2, level - 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 clientOrderId = this.safeString2 (params, 'clientOid', 'clientOrderId', this.uuid ());
params = this.omit (params, [ 'clientOid', 'clientOrderId' ]);
const request = {
'clientOid': clientOrderId,
'side': side,
'symbol': marketId,
'type': type, // limit or market
// 'remark': '', // optional remark for the order, length cannot exceed 100 utf8 characters
// 'stp': '', // self trade prevention, CN, CO, CB or DC
// To improve the system performance and to accelerate order placing and processing, KuCoin has added a new interface for margin orders
// The current one will no longer accept margin orders by May 1st, 2021 (UTC)
// At the time, KuCoin will notify users via the announcement, please pay attention to it
// 'tradeType': 'TRADE', // TRADE, MARGIN_TRADE // not used with margin orders
// limit orders ---------------------------------------------------
// 'timeInForce': 'GTC', // GTC, GTT, IOC, or FOK (default is GTC), limit orders only
// 'cancelAfter': long, // cancel after n seconds, requires timeInForce to be GTT
// 'postOnly': false, // Post only flag, invalid when timeInForce is IOC or FOK
// 'hidden': false, // Order will not be displayed in the order book
// 'iceberg': false, // Only a portion of the order is displayed in the order book
// 'visibleSize': this.amountToPrecision (symbol, visibleSize), // The maximum visible size of an iceberg order
// market orders --------------------------------------------------
// 'size': this.amountToPrecision (symbol, amount), // Amount in base currency
// 'funds': this.costToPrecision (symbol, cost), // Amount of quote currency to use
// stop orders ----------------------------------------------------
// 'stop': 'loss', // loss or entry, the default is loss, requires stopPrice
// 'stopPrice': this.priceToPrecision (symbol, amount), // need to be defined if stop is specified
// margin orders --------------------------------------------------
// 'marginMode': 'cross', // cross (cross mode) and isolated (isolated mode), set to cross by default, the isolated mode will be released soon, stay tuned
// 'autoBorrow': false, // The system will first borrow you funds at the optimal interest rate and then place an order for you
};
const quoteAmount = this.safeNumber2 (params, 'cost', 'funds');
if (type === 'market') {
if (quoteAmount !== undefined) {
params = this.omit (params, [ 'cost', 'funds' ]);
// kucoin uses base precision even for quote values
request['funds'] = this.amountToPrecision (symbol, quoteAmount);
} else {
request['size'] = this.amountToPrecision (symbol, amount);
}
} else {
request['price'] = this.priceToPrecision (symbol, price);
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 id = this.safeString (data, 'orderId');
const order = {
'id': id,
'clientOrderId': clientOrderId,
'info': data,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'lastTradeTimestamp': undefined,
'symbol': symbol,
'type': type,
'side': side,
'price': price,
'amount': undefined,
'cost': undefined,
'average': undefined,
'filled': undefined,
'remaining': undefined,
'status': undefined,
'fee': undefined,
'trades': undefined,
};
if (quoteAmount === undefined) {
order['amount'] = amount;
} else {
order['cost'] = quoteAmount;
}
return order;
}
async cancelOrder (id, symbol = undefined, params = {}) {
await this.loadMarkets ();
const request = {};
const clientOrderId = this.safeString2 (params, 'clientOid', 'clientOrderId');
let method = 'privateDeleteOrdersOrderId';
if (clientOrderId !== undefined) {
request['clientOid'] = clientOrderId;
method = 'privateDeleteOrdersClientOrderClientOid';
} else {
request['orderId'] = id;
}
params = this.omit (params, [ 'clientOid', 'clientOrderId' ]);
return await this[method] (this.extend (request, params));
}
async cancelAllOrders (symbol = undefined, params = {}) {
await this.loadMarkets ();
const request = {
// 'symbol': market['id'],
// 'tradeType': 'TRADE', // default is to cancel the spot trading order
};
let market = undefined;
if (symbol !== undefined) {
market = this.market (symbol);
request['symbol'] = market['id'];
}
return await this.privateDeleteOrders (this.extend (request, params));
}
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 = {};
const clientOrderId = this.safeString2 (params, 'clientOid', 'clientOrderId');
let method = 'privateGetOrdersOrderId';
if (clientOrderId !== undefined) {
request['clientOid'] = clientOrderId;
method = 'privateGetOrdersClientOrderClientOid';
} else {
// a special case for undefined ids
// otherwise a wrong endpoint for all orders will be triggered
// https://github.com/ccxt/ccxt/issues/7234
if (id === undefined) {
throw new InvalidOrder (this.id + ' fetchOrder() requires an order id');
}
request['orderId'] = id;
}
params = this.omit (params, [ 'clientOid', 'clientOrderId' ]);
const response = await this[method] (this.extend (request, params));
let market = undefined;
if (symbol !== undefined) {
market = this.market (symbol);
}
const responseData = this.safeValue (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
// }
//
const marketId = this.safeString (order, 'symbol');
const symbol = this.safeSymbol (marketId, market, '-');
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.safeNumber (order, 'price');
if (price === 0.0) {
// market orders
price = undefined;
}
const side = this.safeString (order, 'side');
const feeCurrencyId = this.safeString (order, 'feeCurrency');
const feeCurrency = this.safeCurrencyCode (feeCurrencyId);
const feeCost = this.safeNumber (order, 'fee');
const amount = this.safeNumber (order, 'size');
const filled = this.safeNumber (order, 'dealSize');
const cost = this.safeNumber (order, 'dealFunds');
// bool
const isActive = this.safeValue (order, 'isActive', false);
const cancelExist = this.safeValue (order, 'cancelExist', false);
let status = isActive ? 'open' : 'closed';
status = cancelExist ? 'canceled' : status;
const fee = {
'currency': feeCurrency,
'cost': feeCost,
};
const clientOrderId = this.safeString (order, 'clientOid');
const timeInForce = this.safeString (order, 'timeInForce');
const stopPrice = this.safeNumber (order, 'stopPrice');
const postOnly = this.safeValue (order, 'postOnly');
return this.safeOrder ({
'id': orderId,
'clientOrderId': clientOrderId,
'symbol': symbol,
'type': type,
'timeInForce': timeInForce,
'postOnly': postOnly,
'side': side,
'amount': amount,
'price': price,
'stopPrice': stopPrice,
'cost': cost,
'filled': filled,
'remaining': undefined,
'timestamp': timestamp,
'datetime': datetime,
'fee': fee,
'status': status,
'info': order,
'lastTradeTimestamp': undefined,
'average': undefined,
'trades': undefined,
});
}
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",
// }
//
const marketId = this.safeString (trade, 'symbol');
const symbol = this.safeSymbol (marketId, market, '-');
const id = this.safeString2 (trade, 'tradeId', 'id');
const orderId = this.safeString (trade, 'orderId');
const takerOrMaker = this.safeString (trade, 'liquidity');
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 priceString = this.safeString2 (trade, 'price', 'dealPrice');
const amountString = this.safeString2 (trade, 'size', 'amount');
const price = this.parseNumber (priceString);
const amount = this.parseNumber (amountString);
const side = this.safeString (trade, 'side');
let fee = undefined;
const feeCost = this.safeNumber (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.safeNumber (trade, 'feeRate'),
};
}
let type = this.safeString (trade, 'type');
if (type === 'match') {
type = undefined;
}
let cost = this.safeNumber2 (trade, 'funds', 'dealValue');
if (cost === undefined) {
cost = this.parseNumber (Precise.stringMul (priceString, amountString));
}
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',
'PROCESSING': 'ok',
'FAILURE': 'failed',
};
return this.safeString (statuses, status);
}
parseTransaction (transaction, currency = undefined) {
//
// fetchDeposits
//
// {
// "address": "0x5f047b29041bcfdbf0e4478cdfa753a336ba6989",
// "memo": "5c247c8a03aa677cea2a251d",
// "amount": 1,
// "fee": 0.0001,
// "currency": "KCS",
// "isInner": false,
// "walletTxId": "5bbb57386d99522d9f954c5a@test004",
// "status": "SUCCESS",
// "createdAt": 1544178843000,
// "updatedAt": 1544178891000
// "remark":"foobar"
// }
//
// fetchWithdrawals
//
// {
// "id": "5c2dc64e03aa675aa263f1ac",
// "address": "0x5bedb060b8eb8d823e2414d82acce78d38be7fe9",
// "memo": "",
// "currency": "ETH",
// "amount": 1.0000000,
// "fee": 0.0100000,
// "walletTxId": "3e2414d82acce78d38be7fe9",
// "isInner": false,
// "status": "FAILURE",
// "createdAt": 1546503758000,
// "updatedAt": 1546504603000
// "remark":"foobar"
// }
//
const currencyId = this.safeString (transaction, 'currency');
const code = this.safeCurrencyCode (currencyId, currency);
let address = this.safeString (transaction, 'address');
const amount = this.safeNumber (transaction, 'amount');
let txid = this.safeString (transaction, 'walletTxId');
if (txid !== undefined) {
const txidParts = txid.split ('@');
const numTxidParts = txidParts.length;
if (numTxidParts > 1) {
if (address === undefined) {
if (txidParts[1].length > 1) {
address = txidParts[1];
}
}
}
txid = txidParts[0];
}
let type = (txid === undefined) ? 'withdrawal' : 'deposit';
const rawStatus = this.safeString (transaction, 'status');
const status = this.parseTransactionStatus (rawStatus);
let fee = undefined;
const feeCost = this.safeNumber (transaction, 'fee');
if (feeCost !== undefined) {
let rate = undefined;
if (amount !== undefined) {
rate = feeCost / amount;
}
fee = {
'cost': feeCost,
'rate': rate,
'currency': code,
};
}
const tag = this.safeString (transaction, 'memo');
let timestamp = this.safeInteger2 (transaction, 'createdAt', 'createAt');
const id = this.safeString (transaction, 'id');
let updated = this.safeInteger (transaction, 'updatedAt');
const isV1 = !('createdAt' in transaction);
// if it's a v1 structure
if (isV1) {
type = ('address' in transaction) ? 'withdrawal' : 'deposit';
if (timestamp !== undefined) {
timestamp = timestamp * 1000;
}
if (updated !== undefined) {
updated = updated * 1000;
}
}
const comment = this.safeString (transaction, 'remark');
return {
'id': id,
'info': transaction,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'address': address,
'addressTo': address,
'addressFrom': undefined,
'tag': tag,
'tagTo': tag,
'tagFrom': undefined,
'currency': code,
'amount': amount,
'txid': txid,
'type': type,
'status': status,
'comment': comment,
'fee': fee,
'updated': updated,
};
}
async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const request = {};
let currency = undefined;
if (code !== undefined) {
currency = this.currency (code);
request['currency'] = currency['id'];
}
if (limit !== undefined) {
request['pageSize'] = limit;
}
let method = 'privateGetDeposits';
if (since !== undefined) {
// if since is earlier than 2019-02-18T00:00:00Z
if (since < 1550448000000) {
request['startAt'] = parseInt (since / 1000);
method = 'privateGetHistDeposits';
} else {
request['startAt'] = since;
}
}
const response = await this[method] (this.extend (request, params));
//
// {
// code: '200000',
// data: {
// "currentPage": 1,
// "pageSize": 5,
// "totalNum": 2,
// "totalPage": 1,
// "items": [
// //--------------------------------------------------
// // version 2 deposit response structure
// {
// "address": "0x5f047b29041bcfdbf0e4478cdfa753a336ba6989",
// "memo": "5c247c8a03aa677cea2a251d",
// "amount": 1,
// "fee": 0.0001,
// "currency": "KCS",
// "isInner": false,
// "walletTxId": "5bbb57386d99522d9f954c5a@test004",
// "status": "SUCCESS",
// "createdAt": 1544178843000,
// "updatedAt": 1544178891000
// "remark":"foobar"
// },
// //--------------------------------------------------
// // version 1 (historical) deposit response structure
// {
// "currency": "BTC",
// "createAt": 1528536998,
// "amount": "0.03266638",
// "walletTxId": "55c643bc2c68d6f17266383ac1be9e454038864b929ae7cee0bc408cc5c869e8@12ffGWmMMD1zA1WbFm7Ho3JZ1w6NYXjpFk@234",
// "isInner": false,
// "status": "SUCCESS",
// }
// ]
// }
// }
//
const responseData = response['data']['items'];
return this.parseTransactions (responseData, currency, since, limit, { 'type': 'deposit' });
}
async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const request = {};
let currency = undefined;
if (code !== undefined) {
currency = this.currency (code);
request['currency'] = currency['id'];
}
if (limit !== undefined) {
request['pageSize'] = limit;
}
let method = 'privateGetWithdrawals';
if (since !== undefined) {
// if since is earlier than 2019-02-18T00:00:00Z
if (since < 1550448000000) {
request['startAt'] = parseInt (since / 1000);
method = 'privateGetHistWithdrawals';
} else {
request['startAt'] = since;
}
}
const response = await this[method] (this.extend (request, params));
//
// {
// code: '200000',
// data: {
// "currentPage": 1,
// "pageSize": 5,
// "totalNum": 2,
// "totalPage": 1,
// "items": [
// //--------------------------------------------------
// // version 2 withdrawal response structure
// {
// "id": "5c2dc64e03aa675aa263f1ac",
// "address": "0x5bedb060b8eb8d823e2414d82acce78d38be7fe9",
// "memo": "",
// "currency": "ETH",
// "amount": 1.0000000,
// "fee": 0.0100000,
// "walletTxId": "3e2414d82acce78d38be7fe9",
// "isInner": false,
// "status": "FAILURE",
// "createdAt": 1546503758000,
// "updatedAt": 1546504603000
// },
// //--------------------------------------------------
// // version 1 (historical) withdrawal response structure
// {
// "currency": "BTC",
// "createAt": 1526723468,
// "amount": "0.534",
// "address": "33xW37ZSW4tQvg443Pc7NLCAs167Yc2XUV",
// "walletTxId": "aeacea864c020acf58e51606169240e96774838dcd4f7ce48acf38e3651323f4",
// "isInner": false,
// "status": "SUCCESS"
// }
// ]
// }
// }
//
const responseData = response['data']['items'];
return this.parseTransactions (responseData, currency, since, limit, { 'type': 'withdrawal' });
}
async fetchBalance (params = {}) {
await this.loadMarkets ();
const defaultType = this.safeString2 (this.options, 'fetchBalance', 'defaultType', 'trade');
const requestedType = this.safeString (params, 'type', defaultType);
const accountsByType = this.safeValue (this.options, 'accountsByType');
const type = this.safeString (accountsByType, requestedType);
if (type === undefined) {
const keys = Object.keys (accountsByType);
throw new ExchangeError (this.id + ' type must be one of ' + keys.join (', '));
}
params = this.omit (params, 'type');
if (type === 'contract') {
// futures api requires a futures apiKey
// only fetches one balance at a time
// by default it will only fetch the BTC balance of the futures account
// you can send 'currency' in params to fetch other currencies
// fetchBalance ({ 'type': 'futures', 'currency': 'USDT' })
const response = await this.futuresPrivateGetAccountOverview (params);
//
// {
// code: '200000',
// data: {
// accountEquity: 0.00005,
// unrealisedPNL: 0,
// marginBalance: 0.00005,
// positionMargin: 0,
// orderMargin: 0,
// frozenFunds: 0,
// availableBalance: 0.00005,
// currency: 'XBT'
// }
// }
//
const result = {
'info': response,
'timestamp': undefined,
'datetime': undefined,
};
const data = this.safeValue (response, 'data');
const currencyId = this.safeString (data, 'currency');
const code = this.safeCurrencyCode (currencyId);
const account = this.account ();
account['free'] = this.safeString (data, 'availableBalance');
account['total'] = this.safeString (data, 'accountEquity');
result[code] = account;
return this.parseBalance (result, false);
} else {
const request = {
'type': type,
};
const response = await this.privateGetAccounts (this.extend (request, params));
//
// {
// "code":"200000",
// "data":[
// {"balance":"0.00009788","available":"0.00009788","holds":"0","currency":"BTC","id":"5c6a4fd399a1d81c4f9cc4d0","type":"trade"},
// {"balance":"3.41060034","available":"3.41060034","holds":"0","currency":"SOUL","id":"5c6a4d5d99a1d8182d37046d","type":"trade"},
// {"balance":"0.01562641","available":"0.01562641","holds":"0","currency":"NEO","id":"5c6a4f1199a1d8165a99edb1","type":"trade"},
// ]
// }
//
const data = this.safeValue (response, 'data', []);
const result = {
'info': response,
'timestamp': undefined,
'datetime': undefined,
};
for (let i = 0; i < data.length; i++) {
const balance = data[i];
const balanceType = this.safeString (balance, 'type');
if (balanceType === type) {
const currencyId = this.safeString (balance, 'currency');
const code = this.safeCurrencyCode (currencyId);
const account = this.account ();
account['total'] = this.safeString (balance, 'balance');
account['free'] = this.safeString (balance, 'available');
account['used'] = this.safeString (balance, 'holds');
result[code] = account;
}
}
return this.parseBalance (result, false);
}
}
async transfer (code, amount, fromAccount, toAccount, params = {}) {
await this.loadMarkets ();
const currency = this.currency (code);
const requestedAmount = this.currencyToPrecision (code, amount);
const accountsById = this.safeValue (this.options, 'accountsByType', {});
const fromId = this.safeString (accountsById, fromAccount);
if (fromId === undefined) {
const keys = Object.keys (accountsById);
throw new ExchangeError (this.id + ' fromAccount must be one of ' + keys.join (', '));
}
const toId = this.safeString (accountsById, toAccount);
if (toId === undefined) {
const keys = Object.keys (accountsById);
throw new ExchangeError (this.id + ' toAccount must be one of ' + keys.join (', '));
}
if (fromId === 'contract') {
if (toId !== 'main') {
throw new ExchangeError (this.id + ' only supports transferring from futures account to main account');
}
const request = {
'currency': currency['id'],
'amount': requestedAmount,
};
if (!('bizNo' in params)) {
// it doesn't like more than 24 characters
request['bizNo'] = this.uuid22 ();
}
const response = await this.futuresPrivatePostTransferOut (this.extend (request, params));
//
// {
// code: '200000',
// data: {
// applyId: '605a87217dff1500063d485d',
// bizNo: 'bcd6e5e1291f4905af84dc',
// payAccountType: 'CONTRACT',
// payTag: 'DEFAULT',
// remark: '',
// recAccountType: 'MAIN',
// recTag: 'DEFAULT',
// recRemark: '',
// recSystem: 'KUCOIN',
// status: 'PROCESSING',
// currency: 'XBT',
// amount: '0.00001',
// fee: '0',
// sn: '573688685663948',
// reason: '',
// createdAt: 1616545569000,
// updatedAt: 1616545569000
// }
// }
//
const data = this.safeValue (response, 'data');
const timestamp = this.safeInteger (data, 'createdAt');
const id = this.safeString (data, 'applyId');
const currencyId = this.safeString (data, 'currency');
const code = this.safeCurrencyCode (currencyId);
const amount = this.safeNumber (data, 'amount');
const rawStatus = this.safeString (data, 'status');
let status = undefined;
if (rawStatus === 'PROCESSING') {
status = 'pending';
}
return {
'info': response,
'currency': code,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'amount': amount,
'fromAccount': fromId,
'toAccount': toId,
'id': id,
'status': status,
};
} else {
const request = {
'currency': currency['id'],
'from': fromId,
'to': toId,
'amount': requestedAmount,
};
if (!('clientOid' in params)) {
request['clientOid'] = this.uuid ();
}
const response = await this.privatePostAccountsInnerTransfer (this.extend (request, params));
// { code: '200000', data: { orderId: '605a6211e657f00006ad0ad6' } }
const data = this.safeValue (response, 'data');
const id = this.safeString (data, 'orderId');
return {
'info': response,
'id': id,
'timestamp': undefined,
'datetime': undefined,
'currency': code,
'amount': requestedAmount,
'fromAccount': fromId,
'toAccount': toId,
'status': undefined,
};
}
}
async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) {
if (code === undefined) {
throw new ArgumentsRequired (this.id + ' fetchLedger() requires a code param');
}
await this.loadMarkets ();
await this.loadAccounts ();
const currency = this.currency (code);
let accountId = this.safeString (params, 'accountId');
if (accountId === undefined) {
for (let i = 0; i < this.accounts.length; i++) {
const account = this.accounts[i];
if (account['currency'] === code && account['type'] === 'main') {
accountId = account['id'];
break;
}
}
}
if (accountId === undefined) {
throw new ExchangeError (this.id + ' ' + code + 'main account is not loaded in loadAccounts');
}
const request = {
'accountId': accountId,
};
if (since !== undefined) {
request['startAt'] = Math.floor (since / 1000);
}
const response = await this.privateGetAccountsAccountIdLedgers (this.extend (request, params));
//
// {
// code: '200000',
// data: {
// totalNum: 1,
// totalPage: 1,
// pageSize: 50,
// currentPage: 1,
// items: [
// {
// createdAt: 1561897880000,
// amount: '0.0111123',
// bizType: 'Exchange',
// balance: '0.13224427',
// fee: '0.0000111',
// context: '{"symbol":"KCS-ETH","orderId":"5d18ab98c788c6426188296f","tradeId":"5d18ab9818996813f539a806"}',
// currency: 'ETH',
// direction: 'out'
// }
// ]
// }
// }
//
const items = response['data']['items'];
return this.parseLedger (items, currency, since, limit);
}
parseLedgerEntry (item, currency = undefined) {
//
// trade
//
// {
// createdAt: 1561897880000,
// amount: '0.0111123',
// bizType: 'Exchange',
// balance: '0.13224427',
// fee: '0.0000111',
// context: '{"symbol":"KCS-ETH","orderId":"5d18ab98c788c6426188296f","tradeId":"5d18ab9818996813f539a806"}',
// currency: 'ETH',
// direction: 'out'
// }
//
// withdrawal
//
// {
// createdAt: 1561900264000,
// amount: '0.14333217',
// bizType: 'Withdrawal',
// balance: '0',
// fee: '0.01',
// context: '{"orderId":"5d18b4e687111437cf1c48b9","txId":"0x1d136ee065c5c4c5caa293faa90d43e213c953d7cdd575c89ed0b54eb87228b8"}',
// currency: 'ETH',
// direction: 'out'
// }
//
const currencyId = this.safeString (item, 'currency');
const code = this.safeCurrencyCode (currencyId, currency);
const fee = {
'cost': this.safeNumber (item, 'fee'),
'code': code,
};
const amount = this.safeNumber (item, 'amount');
const after = this.safeNumber (item, 'balance');
const direction = this.safeString (item, 'direction');
let before = undefined;
if (after !== undefined && amount !== undefined) {
const difference = (direction === 'out') ? amount : -amount;
before = this.sum (after, difference);
}
const timestamp = this.safeInteger (item, 'createdAt');
const type = this.parseLedgerEntryType (this.safeString (item, 'bizType'));
const contextString = this.safeString (item, 'context');
let id = undefined;
let referenceId = undefined;
if (this.isJsonEncodedObject (contextString)) {
const context = this.parseJson (contextString);
id = this.safeString (context, 'orderId');
if (type === 'trade') {
referenceId = this.safeString (context, 'tradeId');
} else if (type === 'transaction') {
referenceId = this.safeString (context, 'txId');
}
}
return {
'id': id,
'currency': code,
'account': undefined,
'referenceAccount': undefined,
'referenceId': referenceId,
'status': undefined,
'amount': amount,
'before': before,
'after': after,
'fee': fee,
'direction': direction,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'type': type,
'info': item,
};
}
parseLedgerEntryType (type) {
const types = {
'Exchange': 'trade',
'Withdrawal': 'transaction',
'Deposit': 'transaction',
'Transfer': 'transfer',
};
return this.safeString (types, type, type);
}
async fetchPositions (symbols = undefined, params = {}) {
const response = await this.futuresPrivateGetPositions (params);
//
// {
// code: '200000',
// data: [
// {
// id: '605a9772a229ab0006408258',
// symbol: 'XBTUSDTM',
// autoDeposit: false,
// maintMarginReq: 0.005,
// riskLimit: 200,
// realLeverage: 0,
// crossMode: false,
// delevPercentage: 0,
// currentTimestamp: 1616549746099,
// currentQty: 0,
// currentCost: 0,
// currentComm: 0,
// unrealisedCost: 0,
// realisedGrossCost: 0,
// realisedCost: 0,
// isOpen: false,
// markPrice: 54371.92,
// markValue: 0,
// posCost: 0,
// posCross: 0,
// posInit: 0,
// posComm: 0,
// posLoss: 0,
// posMargin: 0,
// posMaint: 0,
// maintMargin: 0,
// realisedGrossPnl: 0,
// realisedPnl: 0,
// unrealisedPnl: 0,
// unrealisedPnlPcnt: 0,
// unrealisedRoePcnt: 0,
// avgEntryPrice: 0,
// liquidationPrice: 0,
// bankruptPrice: 0,
// settleCurrency: 'USDT',
// isInverse: false
// },
// {
// id: '605a9772026ac900066550df',
// symbol: 'XBTUSDM',
// autoDeposit: false,
// maintMarginReq: 0.005,
// riskLimit: 200,
// realLeverage: 0,
// crossMode: false,
// delevPercentage: 0,
// currentTimestamp: 1616549746110,
// currentQty: 0,
// currentCost: 0,
// currentComm: 0,
// unrealisedCost: 0,
// realisedGrossCost: 0,
// realisedCost: 0,
// isOpen: false,
// markPrice: 54354.76,
// markValue: 0,
// posCost: 0,
// posCross: 0,
// posInit: 0,
// posComm: 0,
// posLoss: 0,
// posMargin: 0,
// posMaint: 0,
// maintMargin: 0,
// realisedGrossPnl: 0,
// realisedPnl: 0,
// unrealisedPnl: 0,
// unrealisedPnlPcnt: 0,
// unrealisedRoePcnt: 0,
// avgEntryPrice: 0,
// liquidationPrice: 0,
// bankruptPrice: 0,
// settleCurrency: 'XBT',
// isInverse: true
// }
// ]
// }
//
return this.safeValue (response, 'data', response);
}
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
//
// the v2 URL is https://openapi-v2.kucoin.com/api/v1/endpoint
// † ↑
//
const versions = this.safeValue (this.options, 'versions', {});
const apiVersions = this.safeValue (versions, api, {});
const methodVersions = this.safeValue (apiVersions, method, {});
const defaultVersion = this.safeString (methodVersions, path, this.options['version']);
const version = this.safeString (params, 'version', defaultVersion);
params = this.omit (params, 'version');
let endpoint = '/api/' + version + '/' + this.implodeParams (path, params);
const query = this.omit (params, this.extractParams (path));
let endpart = '';
headers = (headers !== undefined) ? headers : {};
if (Object.keys (query).length) {
if ((method === 'GET') || (method === 'DELETE')) {
endpoint += '?' + this.urlencode (query);
} else {
body = this.json (query);
endpart = body;
headers['Content-Type'] = 'application/json';
}
}
const url = this.urls['api'][api] + endpoint;
if ((api === 'private') || (api === 'futuresPrivate')) {
this.checkRequiredCredentials ();
const timestamp = this.nonce ().toString ();
headers = this.extend ({
'KC-API-KEY-VERSION': '2',
'KC-API-KEY': this.apiKey,
'KC-API-TIMESTAMP': timestamp,
}, headers);
const apiKeyVersion = this.safeString (headers, 'KC-API-KEY-VERSION');
if (apiKeyVersion === '2') {
const passphrase = this.hmac (this.encode (this.password), this.encode (this.secret), 'sha256', 'base64');
headers['KC-API-PASSPHRASE'] = passphrase;
} else {
headers['KC-API-PASSPHRASE'] = this.password;
}
const payload = timestamp + method + endpoint + endpart;
const signature = this.hmac (this.encode (payload), this.encode (this.secret), 'sha256', 'base64');
headers['KC-API-SIGN'] = signature;
const partner = this.safeValue (this.options, 'partner', {});
const partnerId = this.safeString (partner, 'id');
const partnerSecret = this.safeString (partner, 'secret');
if ((partnerId !== undefined) && (partnerSecret !== undefined)) {
const partnerPayload = timestamp + partnerId + this.apiKey;
const partnerSignature = this.hmac (this.encode (partnerPayload), this.encode (partnerSecret), 'sha256', 'base64');
headers['KC-API-PARTNER-SIGN'] = partnerSignature;
headers['KC-API-PARTNER'] = partnerId;
}
}
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
}
handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
if (!response) {
this.throwBroadlyMatchedException (this.exceptions['broad'], body, body);
return;
}
//
// bad
// { "code": "400100", "msg": "validation.createOrder.clientOidIsRequired" }
// good
// { code: '200000', data: { ... }}
//
const errorCode = this.safeString (response, 'code');
const message = this.safeString (response, 'msg', '');
this.throwExactlyMatchedException (this.exceptions['exact'], message, this.id + ' ' + message);
this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, this.id + ' ' + message);
}
};