IPC removed, replaced by WS
This commit is contained in:
1 parent
ba4217fb24
commit
656c193511
503 files changed
+28128
-34498
No files matched your search
+270
-103
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user