IPC removed, replaced by WS

This commit is contained in:
Luis Fernando Molina committed 2020-04-06 08:33:07 +02:00
1 parent ba4217fb24
commit 656c193511
503 files changed
+28128 -34498

No files matched your search

+270 -103
View File
@@ -3,7 +3,7 @@
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { AuthenticationError, ExchangeError, ExchangeNotAvailable, InvalidOrder, OrderNotFound, InsufficientFunds, ArgumentsRequired, BadSymbol, BadRequest, RequestTimeout } = require ('./base/errors');
const { AuthenticationError, ExchangeError, PermissionDenied, ExchangeNotAvailable, InvalidOrder, OrderNotFound, InsufficientFunds, ArgumentsRequired, BadSymbol, BadRequest, RequestTimeout } = require ('./base/errors');
// ---------------------------------------------------------------------------
@@ -18,11 +18,12 @@ module.exports = class huobipro extends Exchange {
'version': 'v1',
'accounts': undefined,
'accountsById': undefined,
'hostname': 'api.huobi.pro',
'hostname': 'api.huobi.pro', // api.testnet.huobi.pro
'pro': true,
'has': {
'CORS': false,
'fetchTickers': true,
'fetchDepositAddress': false,
'fetchDepositAddress': true,
'fetchOHLCV': true,
'fetchOrder': true,
'fetchOrders': true,
@@ -48,12 +49,18 @@ module.exports = class huobipro extends Exchange {
'1y': '1year',
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/27766569-15aa7b9a-5edd-11e7-9e7f-44791f4ee49c.jpg',
'test': {
'market': 'https://api.testnet.huobi.pro',
'public': 'https://api.testnet.huobi.pro',
'private': 'https://api.testnet.huobi.pro',
},
'logo': 'https://user-images.githubusercontent.com/1294454/76137448-22748a80-604e-11ea-8069-6e389271911d.jpg',
'api': {
'market': 'https://{hostname}',
'public': 'https://{hostname}',
'private': 'https://{hostname}',
'zendesk': 'https://huobiglobal.zendesk.com/hc/en-us/articles',
'v2Public': 'https://{hostname}',
'v2Private': 'https://{hostname}',
},
'www': 'https://www.huobi.pro',
'referral': 'https://www.huobi.co/en-us/topic/invited/?invite_code=rwrd3',
@@ -61,9 +68,19 @@ module.exports = class huobipro extends Exchange {
'fees': 'https://www.huobi.pro/about/fee/',
},
'api': {
'zendesk': {
'v2Public': {
'get': [
'360000400491-Trade-Limits',
'reference/currencies',
],
},
'v2Private': {
'get': [
'account/withdraw/quota',
'account/deposit/address',
'reference/transact-fee-rate',
],
'post': [
'sub-user/management',
],
},
'market': {
@@ -87,10 +104,6 @@ module.exports = class huobipro extends Exchange {
],
},
'private': {
// todo add v2 endpoints
// 'GET /v2/account/withdraw/quota
// 'GET /v2/reference/currencies
// 'GET /v2/account/deposit/address
'get': [
'account/accounts', // 查询当前用户的所有账户(即account-id)
'account/accounts/{id}/balance', // 查询指定账户的余额
@@ -115,8 +128,6 @@ module.exports = class huobipro extends Exchange {
'stable-coin/exchange_rate',
'stable-coin/quote',
],
// todo add v2 endpoints
// POST /v2/sub-user/management
'post': [
'futures/transfer',
'order/batch-orders',
@@ -152,6 +163,7 @@ module.exports = class huobipro extends Exchange {
'exceptions': {
'exact': {
// err-code
'api-not-support-temp-addr': PermissionDenied, // {"status":"error","err-code":"api-not-support-temp-addr","err-msg":"API withdrawal does not support temporary addresses","data":null}
'timeout': RequestTimeout, // {"ts":1571653730865,"status":"error","err-code":"timeout","err-msg":"Request Timeout"}
'gateway-internal-error': ExchangeNotAvailable, // {"status":"error","err-code":"gateway-internal-error","err-msg":"Failed to load data. Try again later.","data":null}
'account-frozen-balance-insufficient-error': InsufficientFunds, // {"status":"error","err-code":"account-frozen-balance-insufficient-error","err-msg":"trade account balance is not enough, left: `0.0027`","data":null}
@@ -319,6 +331,21 @@ module.exports = class huobipro extends Exchange {
}
parseTicker (ticker, market = undefined) {
//
// {
// "amount": 26228.672978342216,
// "open": 9078.95,
// "close": 9146.86,
// "high": 9155.41,
// "id": 209988544334,
// "count": 265846,
// "low": 8988.0,
// "version": 209988544334,
// "ask": [ 9146.87, 0.156134 ],
// "vol": 2.3822168242201668E8,
// "bid": [ 9146.86, 0.080758 ],
// }
//
let symbol = undefined;
if (market !== undefined) {
symbol = market['symbol'];
@@ -390,13 +417,35 @@ module.exports = class huobipro extends Exchange {
'type': 'step0',
};
const response = await this.marketGetDepth (this.extend (request, params));
//
// {
// "status": "ok",
// "ch": "market.btcusdt.depth.step0",
// "ts": 1583474832790,
// "tick": {
// "bids": [
// [ 9100.290000000000000000, 0.200000000000000000 ],
// [ 9099.820000000000000000, 0.200000000000000000 ],
// [ 9099.610000000000000000, 0.205000000000000000 ],
// ],
// "asks": [
// [ 9100.640000000000000000, 0.005904000000000000 ],
// [ 9101.010000000000000000, 0.287311000000000000 ],
// [ 9101.030000000000000000, 0.012121000000000000 ],
// ],
// "ts":1583474832008,
// "version":104999698780
// }
// }
//
if ('tick' in response) {
if (!response['tick']) {
throw new ExchangeError (this.id + ' fetchOrderBook() returned empty response: ' + this.json (response));
}
const orderbook = this.safeValue (response, 'tick');
const result = this.parseOrderBook (orderbook, orderbook['ts']);
result['nonce'] = orderbook['version'];
const tick = this.safeValue (response, 'tick');
const timestamp = this.safeInteger (tick, 'ts', this.safeInteger (response, 'ts'));
const result = this.parseOrderBook (tick, timestamp);
result['nonce'] = this.safeInteger (tick, 'version');
return result;
}
throw new ExchangeError (this.id + ' fetchOrderBook() returned unrecognized response: ' + this.json (response));
@@ -409,7 +458,31 @@ module.exports = class huobipro extends Exchange {
'symbol': market['id'],
};
const response = await this.marketGetDetailMerged (this.extend (request, params));
return this.parseTicker (response['tick'], market);
//
// {
// "status": "ok",
// "ch": "market.btcusdt.detail.merged",
// "ts": 1583494336669,
// "tick": {
// "amount": 26228.672978342216,
// "open": 9078.95,
// "close": 9146.86,
// "high": 9155.41,
// "id": 209988544334,
// "count": 265846,
// "low": 8988.0,
// "version": 209988544334,
// "ask": [ 9146.87, 0.156134 ],
// "vol": 2.3822168242201668E8,
// "bid": [ 9146.86, 0.080758 ],
// }
// }
//
const ticker = this.parseTicker (response['tick'], market);
const timestamp = this.safeValue (response, 'ts');
ticker['timestamp'] = timestamp;
ticker['datetime'] = this.iso8601 (timestamp);
return ticker;
}
async fetchTickers (symbols = undefined, params = {}) {
@@ -430,10 +503,24 @@ module.exports = class huobipro extends Exchange {
result[symbol] = ticker;
}
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
parseTrade (trade, market = undefined) {
//
// fetchTrades (public)
//
// {
// "amount": 0.010411000000000000,
// "trade-id": 102090736910,
// "ts": 1583497692182,
// "id": 10500517034273194594947,
// "price": 9096.050000000000000000,
// "direction": "sell"
// }
//
// fetchMyTrades (private)
//
let symbol = undefined;
if (market === undefined) {
const marketId = this.safeString (trade, 'symbol');
@@ -481,7 +568,8 @@ module.exports = class huobipro extends Exchange {
'currency': feeCurrency,
};
}
const id = this.safeString (trade, 'id');
const tradeId = this.safeString2 (trade, 'trade-id', 'tradeId');
const id = this.safeString (trade, 'id', tradeId);
return {
'id': id,
'info': trade,
@@ -528,6 +616,30 @@ module.exports = class huobipro extends Exchange {
request['size'] = limit;
}
const response = await this.marketGetHistoryTrade (this.extend (request, params));
//
// {
// "status": "ok",
// "ch": "market.btcusdt.trade.detail",
// "ts": 1583497692365,
// "data": [
// {
// "id": 105005170342,
// "ts": 1583497692182,
// "data": [
// {
// "amount": 0.010411000000000000,
// "trade-id": 102090736910,
// "ts": 1583497692182,
// "id": 10500517034273194594947,
// "price": 9096.050000000000000000,
// "direction": "sell"
// }
// ]
// },
// // ...
// ]
// }
//
const data = this.safeValue (response, 'data');
let result = [];
for (let i = 0; i < data.length; i++) {
@@ -729,6 +841,9 @@ module.exports = class huobipro extends Exchange {
}
async fetchOpenOrdersV1 (symbol = undefined, since = undefined, limit = undefined, params = {}) {
if (symbol === undefined) {
throw new ArgumentsRequired (this.id + ' fetchOpenOrdersV1 requires a symbol argument');
}
return await this.fetchOrdersByStates ('pre-submitted,submitted,partial-filled', symbol, since, limit, params);
}
@@ -892,6 +1007,7 @@ module.exports = class huobipro extends Exchange {
return {
'info': order,
'id': id,
'clientOrderId': undefined,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'lastTradeTimestamp': undefined,
@@ -906,6 +1022,7 @@ module.exports = class huobipro extends Exchange {
'remaining': remaining,
'status': status,
'fee': fee,
'trades': undefined,
};
}
@@ -960,6 +1077,8 @@ module.exports = class huobipro extends Exchange {
'cost': undefined,
'trades': undefined,
'fee': undefined,
'clientOrderId': undefined,
'average': undefined,
};
}
@@ -999,94 +1118,50 @@ module.exports = class huobipro extends Exchange {
};
}
async withdraw (code, amount, address, tag = undefined, params = {}) {
await this.loadMarkets ();
parseDepositAddress (depositAddress, currency = undefined) {
//
// {
// currency: "eth",
// address: "0xf7292eb9ba7bc50358e27f0e025a4d225a64127b",
// addressTag: "",
// chain: "eth"
// }
//
const address = this.safeString (depositAddress, 'address');
const tag = this.safeString (depositAddress, 'addressTag');
const currencyId = this.safeString (depositAddress, 'currency');
const code = this.safeCurrencyCode (currencyId);
this.checkAddress (address);
return {
'currency': code,
'address': address,
'tag': tag,
'info': depositAddress,
};
}
async fetchDepositAddress (code, params = {}) {
await this.loadMarkets ();
const currency = this.currency (code);
const request = {
'address': address, // only supports existing addresses in your withdraw address list
'amount': amount,
'currency': currency['id'].toLowerCase (),
'currency': currency['id'],
};
if (tag !== undefined) {
request['addr-tag'] = tag; // only for XRP?
}
const response = await this.privatePostDwWithdrawApiCreate (this.extend (request, params));
const id = this.safeString (response, 'data');
return {
'info': response,
'id': id,
};
}
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
let url = '/';
if (api === 'market') {
url += api;
} else if ((api === 'public') || (api === 'private')) {
url += this.version;
}
url += '/' + this.implodeParams (path, params);
const query = this.omit (params, this.extractParams (path));
if (api === 'private') {
this.checkRequiredCredentials ();
const timestamp = this.ymdhms (this.milliseconds (), 'T');
let request = {
'SignatureMethod': 'HmacSHA256',
'SignatureVersion': '2',
'AccessKeyId': this.apiKey,
'Timestamp': timestamp,
};
if (method !== 'POST') {
request = this.extend (request, query);
}
request = this.keysort (request);
let auth = this.urlencode (request);
// unfortunately, PHP demands double quotes for the escaped newline symbol
// eslint-disable-next-line quotes
const payload = [ method, this.hostname, url, auth ].join ("\n");
const signature = this.hmac (this.encode (payload), this.encode (this.secret), 'sha256', 'base64');
auth += '&' + this.urlencode ({ 'Signature': signature });
url += '?' + auth;
if (method === 'POST') {
body = this.json (query);
headers = {
'Content-Type': 'application/json',
};
} else {
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
}
} else {
if (Object.keys (params).length) {
url += '?' + this.urlencode (params);
}
}
url = this.implodeParams (this.urls['api'][api], {
'hostname': this.hostname,
}) + url;
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
}
handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) {
if (response === undefined) {
return; // fallback to default error handler
}
if ('status' in response) {
//
// {"status":"error","err-code":"order-limitorder-amount-min-error","err-msg":"limit order amount error, min: `0.001`","data":null}
//
const status = this.safeString (response, 'status');
if (status === 'error') {
const code = this.safeString (response, 'err-code');
const feedback = this.id + ' ' + body;
this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback);
const message = this.safeString (response, 'err-msg');
this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback);
throw new ExchangeError (feedback);
}
}
const response = await this.v2PrivateGetAccountDepositAddress (this.extend (request, params));
//
// {
// code: 200,
// data: [
// {
// currency: "eth",
// address: "0xf7292eb9ba7bc50358e27f0e025a4d225a64127b",
// addressTag: "",
// chain: "eth"
// }
// ]
// }
//
const data = this.safeValue (response, 'data', []);
return this.parseDepositAddress (this.safeValue (data, 0, {}), currency);
}
async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) {
@@ -1230,4 +1305,96 @@ module.exports = class huobipro extends Exchange {
};
return this.safeString (statuses, status, status);
}
async withdraw (code, amount, address, tag = undefined, params = {}) {
await this.loadMarkets ();
this.checkAddress (address);
const currency = this.currency (code);
const request = {
'address': address, // only supports existing addresses in your withdraw address list
'amount': amount,
'currency': currency['id'].toLowerCase (),
};
if (tag !== undefined) {
request['addr-tag'] = tag; // only for XRP?
}
const response = await this.privatePostDwWithdrawApiCreate (this.extend (request, params));
const id = this.safeString (response, 'data');
return {
'info': response,
'id': id,
};
}
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
let url = '/';
if (api === 'market') {
url += api;
} else if ((api === 'public') || (api === 'private')) {
url += this.version;
} else if ((api === 'v2Public') || (api === 'v2Private')) {
url += 'v2';
}
url += '/' + this.implodeParams (path, params);
const query = this.omit (params, this.extractParams (path));
if (api === 'private' || api === 'v2Private') {
this.checkRequiredCredentials ();
const timestamp = this.ymdhms (this.milliseconds (), 'T');
let request = {
'SignatureMethod': 'HmacSHA256',
'SignatureVersion': '2',
'AccessKeyId': this.apiKey,
'Timestamp': timestamp,
};
if (method !== 'POST') {
request = this.extend (request, query);
}
request = this.keysort (request);
let auth = this.urlencode (request);
// unfortunately, PHP demands double quotes for the escaped newline symbol
// eslint-disable-next-line quotes
const payload = [ method, this.hostname, url, auth ].join ("\n");
const signature = this.hmac (this.encode (payload), this.encode (this.secret), 'sha256', 'base64');
auth += '&' + this.urlencode ({ 'Signature': signature });
url += '?' + auth;
if (method === 'POST') {
body = this.json (query);
headers = {
'Content-Type': 'application/json',
};
} else {
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
}
} else {
if (Object.keys (params).length) {
url += '?' + this.urlencode (params);
}
}
url = this.implodeParams (this.urls['api'][api], {
'hostname': this.hostname,
}) + url;
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
}
handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) {
if (response === undefined) {
return; // fallback to default error handler
}
if ('status' in response) {
//
// {"status":"error","err-code":"order-limitorder-amount-min-error","err-msg":"limit order amount error, min: `0.001`","data":null}
//
const status = this.safeString (response, 'status');
if (status === 'error') {
const code = this.safeString (response, 'err-code');
const feedback = this.id + ' ' + body;
this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback);
const message = this.safeString (response, 'err-msg');
this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback);
throw new ExchangeError (feedback);
}
}
}
};