node-huobi-api
Version:
Node huobi API is an asynchronous node.js library for the huobi API designed to be easy to use.
1,875 lines • 78.6 kB
JavaScript
/**
* cover from Node Binance API
* @module jaggedsoft/node-binance-api
* @return {object} instance to class object
*/
let api = function Huobi(){
let Huobi = this;
'use strict';
const WebSocket = require('ws');
const request = require('request');
const crypto = require('crypto');
const file = require('fs');
const url = require('url');
const pako = require('pako');
const HttpsProxyAgent = require('https-proxy-agent');
const SocksProxyAgent = require('socks-proxy-agent');
//const stringHash = require('string-hash');
//const async = require('async');
const site = 'api.huobi.pro';
const base = 'https://'+site;
const wapi = 'https://api.binance.com/wapi/';
const stream = 'wss://'+site+'/ws/';
//const combineStream = 'wss://api.huobi.pro/ws/';
const userAgent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36';//'Mozilla/4.0 (compatible; Node Huobi API)';
const contentType = 'application/x-www-form-urlencoded';
Huobi.subscriptions = {};
Huobi.depthCache = {};
Huobi.depthCacheContext = {};
Huobi.ohlcLatest = {};
Huobi.klineQueue = {};
Huobi.ohlc = {};
const default_options = {
recvWindow: 5000,
useServerTime: false,
reconnect: true,
verbose: false,
test: false,
log: function (...args) {
console.log(Array.prototype.slice.call(args));
}
};
Huobi.options = default_options;
Huobi.info = { timeOffset: 0 };
Huobi.socketHeartbeatInterval = null;
/**
* Replaces socks connection uri hostname with IP address
* @param {string} connString - socks connection string
* @return {string} modified string with ip address
*/
const proxyReplacewithIp = function (connString) {
return connString;
}
/**
* Returns an array in the form of [host, port]
* @param {string} connString - connection string
* @return {array} array of host and port
*/
const parseProxy = function (connString) {
let arr = connString.split('/');
let host = arr[2].split(':')[0];
let port = arr[2].split(':')[1];
return [arr[0], host, port];
}
/**
* Checks to see of the object is iterable
* @param {object} obj - The object check
* @return {boolean} true or false is iterable
*/
const isIterable = function (obj) {
// checks for null and undefined
if (obj === null) {
return false;
}
return typeof obj[Symbol.iterator] === 'function';
}
const addProxy = opt => {
let socksproxy = process.env.socks_proxy || false;
if (socksproxy === false) return opt;
socksproxy = proxyReplacewithIp(socksproxy);
if (Huobi.options.verbose) Huobi.options.log('using socks proxy server ' + socksproxy);
opt.agentClass = SocksProxyAgent;
opt.agentOptions = {
protocol: parseProxy(socksproxy)[0],
host: parseProxy(socksproxy)[1],
port: parseProxy(socksproxy)[2]
}
return opt;
}
const reqHandler = cb => (error, response, body) => {
if (!cb) return;
if (error) return cb(error, {});
if (response && response.statusCode !== 200) return cb(response, {});
return cb(null, JSON.parse(body));
}
const proxyRequest = (opt, cb) => request(addProxy(opt), reqHandler(cb));
const reqObj = (url, data = {}, method = 'GET', key) => ({
url: url,
qs: data,
method: method,
timeout: Huobi.options.recvWindow,
headers: {
'User-Agent': userAgent,
'Content-type': contentType,
'X-MBX-APIKEY': key || ''
}
})
const reqObjPOST = (url, data = {}, method = 'POST', key) => ({
url: url,
form: data,
method: method,
timeout: Huobi.options.recvWindow,
headers: {
'User-Agent': userAgent,
'Content-type': contentType,
'X-MBX-APIKEY': key || ''
}
})
/**
* Create a http request to the public API
* @param {string} url - The http endpoint
* @param {object} data - The data to send
* @param {function} callback - The callback method to call
* @param {string} method - the http method
* @return {undefined}
*/
const publicRequest = function (url, data = {}, callback, method = 'GET') {
let opt = reqObj(url, data, method);
proxyRequest(opt, callback);
};
/**
* Create a http request to the public API
* @param {string} url - The http endpoint
* @param {object} data - The data to send
* @param {function} callback - The callback method to call
* @param {string} method - the http method
* @return {undefined}
*/
const apiRequest = function (url, data = {}, callback, method = 'GET') {
if (!Huobi.options.APIKEY) throw Error('apiRequest: Invalid API Key');
let opt = reqObj(
url,
data,
method,
Huobi.options.APIKEY
);
proxyRequest(opt, callback);
};
/**
* Used to subscribe to a single websocket endpoint
* @param {string} endpoint - endpoint to connect to
* @param {function} callback - the function to called when information is received
* @param {boolean} reconnect - whether to reconnect on disconnect
* @param {object} opened_callback - the function to called when opened
* @return {WebSocket} - websocket reference
*/
const subscribe = function (symbols, callback, reconnect = false, opened_callback = false) {
let httpsproxy = process.env.https_proxy || false;
let socksproxy = process.env.socks_proxy || false;
let ws = false;
if (socksproxy !== false) {
socksproxy = proxyReplacewithIp(socksproxy);
if (Huobi.options.verbose) Huobi.options.log('using socks proxy server ' + socksproxy);
let agent = new SocksProxyAgent({
protocol: parseProxy(socksproxy)[0],
host: parseProxy(socksproxy)[1],
port: parseProxy(socksproxy)[2]
});
//ws = new WebSocket(stream + endpoint, { agent: agent });
ws = new WebSocket(stream , { agent: agent });
} else if (httpsproxy !== false) {
if (Huobi.options.verbose) Huobi.options.log('using proxy server ' + agent);
let config = url.parse(httpsproxy);
let agent = new HttpsProxyAgent(config);
//ws = new WebSocket(stream + endpoint, { agent: agent });
ws = new WebSocket(stream , { agent: agent });
} else {
//ws = new WebSocket(stream + endpoint);
ws = new WebSocket(stream);
}
let doSymbolsToWebsocket = function () {
if (Array.isArray(symbols)) {
symbols.forEach(s => {
let channel = {};
channel.sub = "market." + s + ".depth.step0";
channel.id = new Date().getTime()+s;
// Huobi.options.log(channel.sub);
ws.send(JSON.stringify(channel));
});
}else{
let channel = {};
channel.sub = "market." + symbols + ".depth.step0";
channel.id = new Date().getTime()+symbols;
ws.send(JSON.stringify(channel));
}
};
if (Huobi.options.verbose) Huobi.options.log('Subscribed to ' + stream);
ws.reconnect = Huobi.options.reconnect;
ws.endpoint = new Date().getTime()+"depth";
ws.isAlive = false;
ws.on('open', handleSocketOpen.bind(ws, opened_callback ? doSymbolsToWebsocket : opened_callback));
ws.on('pong', handleSocketHeartbeat);
ws.on('error', handleSocketError);
ws.on('close', handleSocketClose.bind(ws, reconnect));
ws.on('message', function (data) {
data = pako.inflate(data,{ to: 'string' });
//Huobi.options.log('ws data: ' + data);
try {
let msg = JSON.parse(data);
if (msg.ping) {
ws.send(JSON.stringify({ pong: msg.ping }));
// Huobi.options.log('ping: '+msg.ping );
} else if (msg.subbed) {
//Huobi.options.log('subbed: '+msg.id +" status: "+msg.status );
//options.log('subbed: '+msg.id +" status: "+msg.status );
} else {
if (msg.status && msg.status == 'error') {
ws.send(JSON.stringify({ pong: msg.ping }));
// Huobi.options.log('ping: '+msg.ping );
throw new Error(msg)
}
callback( JSON.parse(data) );
}
} catch (error) {
//options.log('CombinedStream: Parse error: '+error.message +'-> '+ JSON.stringify(data) );
Huobi.options.log('Parse error: ' + error.message);
}
// try {
// callback(JSON.parse(data));
// } catch (error) {
// Huobi.options.log('Parse error: ' + error.message);
// }
});
return ws;
};
/**
* Make market request
* @param {string} url - The http endpoint
* @param {object} data - The data to send
* @param {function} callback - The callback method to call
* @param {string} method - the http method
* @return {undefined}
*/
const marketRequest = function (url, data = {}, callback, method = 'GET') {
if (!Huobi.options.APIKEY) throw Error('apiRequest: Invalid API Key');
let query = Object.keys(data).reduce(function (a, k) {
a.push(k + '=' + encodeURIComponent(data[k]));
return a;
}, []).join('&');
let opt = reqObj(
url + (query ? '?' + query : ''),
data,
method,
Huobi.options.APIKEY
);
proxyRequest(opt, callback);
};
/**
* Create a signed http request to the signed API
* @param {string} url - The http endpoint
* @param {object} data - The data to send
* @param {function} callback - The callback method to call
* @param {string} method - the http method
* @return {undefined}
*/
const signedRequest = function (url, data = {}, callback, method = 'GET') {
if (!Huobi.options.APIKEY) throw Error('apiRequest: Invalid API Key');
if (!Huobi.options.APISECRET) throw Error('signedRequest: Invalid API Secret');
//if (typeof data.recvWindow === 'undefined') data.recvWindow = Huobi.options.recvWindow;
data.Timestamp = new Date().toISOString().replace(/\..+/, '');//.getTime()+ Huobi.info.timeOffset;
data.SignatureMethod='HmacSHA256';
data.SignatureVersion=2;
data.AccessKeyId=Huobi.options.APIKEY;
//console.log(data.Timestamp);
let query = Object.keys(data)
.sort( (a,b)=> (a > b) ? 1 : -1 )
.reduce(function (a, k) {
a.push(k + '=' + encodeURIComponent(data[k]));
return a;
}, []).join('&');
//console.log("query %s",query);
let source = method+'\n' + site+'\n'+url.replace(base,'')+'\n'+query;
//console.log("source %s",source);
let signature = crypto.createHmac('sha256', Huobi.options.APISECRET).update(source).digest('base64');//digest('hex'); // set the HMAC hash header
signature = encodeURIComponent(signature);
//console.log("Signature %s",signature);
if (method === 'POST') {
let opt = reqObjPOST(
url + '?Signature=' + signature,
data,
method,
Huobi.options.APIKEY
);
proxyRequest(opt, callback);
} else {
let opt = reqObj(
url + '?' + query + '&Signature=' + signature,
data,
method,
Huobi.options.APIKEY
);
proxyRequest(opt, callback);
}
};
const parseSymbol = function (depth) {
//Huobi.options.log("parseSymbol = ",depth)
return depth.ch.split('.')[1];
};
/**
* Used for /depth endpoint
* @param {object} depth - information
* @return {undefined}
*/
const depthHandler = function (depth) {
let symbol = parseSymbol(depth), obj;
let context = Huobi.depthCacheContext[symbol];
//Huobi.options.log(depth);
//Huobi.options.log("context is" + (context != null));
let updateDepthCache = function () {
Huobi.depthCache[symbol].eventTime = depth.ts;
for (obj of depth.tick.bids) { //bids
Huobi.depthCache[symbol].bids[obj[0]] = parseFloat(obj[1]);
if (obj[1] === '0.00000000') {
delete Huobi.depthCache[symbol].bids[obj[0]];
}
}
for (obj of depth.tick.asks) { //asks
Huobi.depthCache[symbol].asks[obj[0]] = parseFloat(obj[1]);
if (obj[1] === '0.00000000') {
delete Huobi.depthCache[symbol].asks[obj[0]];
}
}
context.skipCount = 0;
context.lastEventUpdateId = depth.ts;
context.lastEventUpdateTime = depth.ts;
};
// This is our first legal update from the stream data
updateDepthCache();
};
/**
* Create a signed http request to the signed API
* @param {string} side - BUY or SELL
* @param {string} symbol - The symbol to buy or sell
* @param {string} quantity - The quantity to buy or sell
* @param {string} price - The price per unit to transact each unit at
* @param {object} flags - additional order settings
* @param {function} callback - the callback function
* @return {undefined}
*/
const order = function (side, symbol, quantity, price, flags = {}, callback = false) {
let endpoint = 'v3/order';
if (Huobi.options.test) endpoint += '/test';
let opt = {
symbol: symbol,
side: side,
type: 'LIMIT',
quantity: quantity
};
if (typeof flags.type !== 'undefined') opt.type = flags.type;
if (opt.type.includes('LIMIT')) {
opt.price = price;
opt.timeInForce = 'GTC';
}
if (typeof flags.timeInForce !== 'undefined') opt.timeInForce = flags.timeInForce;
if (typeof flags.newOrderRespType !== 'undefined') opt.newOrderRespType = flags.newOrderRespType;
if (typeof flags.newClientOrderId !== 'undefined') opt.newClientOrderId = flags.newClientOrderId;
/*
* STOP_LOSS
* STOP_LOSS_LIMIT
* TAKE_PROFIT
* TAKE_PROFIT_LIMIT
* LIMIT_MAKER
*/
if (typeof flags.icebergQty !== 'undefined') opt.icebergQty = flags.icebergQty;
if (typeof flags.stopPrice !== 'undefined') {
opt.stopPrice = flags.stopPrice;
if (opt.type === 'LIMIT') throw Error('stopPrice: Must set "type" to one of the following: STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, TAKE_PROFIT_LIMIT');
}
signedRequest(base + endpoint, opt, function (error, response) {
if (!response) {
if (callback) callback(error, response);
else Huobi.options.log('Order() error:', error);
return;
}
if (typeof response.msg !== 'undefined' && response.msg === 'Filter failure: MIN_NOTIONAL') {
Huobi.options.log('Order quantity too small. See exchangeInfo() for minimum amounts');
}
if (callback) callback(error, response);
else Huobi.options.log(side + '(' + symbol + ',' + quantity + ',' + price + ') ', response);
}, 'POST');
};
/**
* No-operation function
* @return {undefined}
*/
const noop = function () {
// do nothing
};
/**
* Gets depth cache for given symbol
* @param {string} symbol - the symbol to fetch
* @return {object} - the depth cache object
*/
const getDepthCache = function (symbol) {
if (typeof Huobi.depthCache[symbol] === 'undefined') return { bids: {}, asks: {} };
return Huobi.depthCache[symbol];
};
/**
* Calculate Buy/Sell volume from DepthCache
* @param {string} symbol - the symbol to fetch
* @return {object} - the depth volume cache object
*/
const depthVolume = function (symbol) {
let cache = getDepthCache(symbol), quantity, price;
let bidbase = 0, askbase = 0, bidqty = 0, askqty = 0;
for (price in cache.bids) {
quantity = cache.bids[price];
bidbase += parseFloat((quantity * parseFloat(price)).toFixed(8));
bidqty += quantity;
}
for (price in cache.asks) {
quantity = cache.asks[price];
askbase += parseFloat((quantity * parseFloat(price)).toFixed(8));
askqty += quantity;
}
return { bids: bidbase, asks: askbase, bidQty: bidqty, askQty: askqty };
};
/**
* Reworked Tuitio's heartbeat code into a shared single interval tick
* @return {undefined}
*/
const socketHeartbeat = function () {
/* sockets removed from `subscriptions` during a manual terminate()
will no longer be at risk of having functions called on them */
for (let endpointId in Huobi.subscriptions) {
const ws = Huobi.subscriptions[endpointId];
if (ws.isAlive) {
ws.isAlive = false;
if (ws.readyState === WebSocket.OPEN) ws.ping(noop);
} else {
if (Huobi.options.verbose) Huobi.options.log('Terminating inactive/broken WebSocket: ' + ws.endpoint);
if (ws.readyState === WebSocket.OPEN) ws.terminate();
}
}
};
/**
* Called when socket is opened, subscriptions are registered for later reference
* @param {function} opened_callback - a callback function
* @return {undefined}
*/
const handleSocketOpen = function (opened_callback) {
this.isAlive = true;
if (Object.keys(Huobi.subscriptions).length === 0) {
Huobi.socketHeartbeatInterval = setInterval(socketHeartbeat, 30000);
}
Huobi.subscriptions[this.endpoint] = this;
if (typeof opened_callback === 'function') opened_callback(this.endpoint);
};
/**
* Called when socket is closed, subscriptions are de-registered for later reference
* @param {boolean} reconnect - true or false to reconnect the socket
* @param {string} code - code associated with the socket
* @param {string} reason - string with the response
* @return {undefined}
*/
const handleSocketClose = function (reconnect, code, reason) {
delete Huobi.subscriptions[this.endpoint];
if (Huobi.subscriptions && Object.keys(Huobi.subscriptions).length === 0) {
clearInterval(Huobi.socketHeartbeatInterval);
}
Huobi.options.log('WebSocket closed: ' + this.endpoint +
(code ? ' (' + code + ')' : '') +
(reason ? ' ' + reason : ''));
if (Huobi.options.reconnect && this.reconnect && reconnect) {
if (this.endpoint && parseInt(this.endpoint.length, 10) === 60) Huobi.options.log('Account data WebSocket reconnecting...');
else Huobi.options.log('WebSocket reconnecting: ' + this.endpoint + '...');
try {
reconnect();
} catch (error) {
Huobi.options.log('WebSocket reconnect error: ' + error.message);
}
}
};
/**
* Used by balance to get the balance data
* @param {array} data - account info object
* @return {object} - balances hel with available, onorder amounts
*/
const balanceData = function (data) {
let balances = {};
if (typeof data === 'undefined') return {};
if (typeof data.data === 'undefined') {
Huobi.options.log('balanceData error', data);
return {};
}
for (let obj of data.data.list) {
//balances[obj.asset] = { available: obj.free, onOrder: obj.locked };
if (balances[obj.currency] == null){
balances[obj.currency] = {};
}
if(obj.type === 'trade'){
balances[obj.currency].available = obj.balance;
}else if(obj.type === 'frozen'){
balances[obj.currency].frozen = obj.balance;
}
}
return balances;
};
/**
* Called when socket errors
* @param {object} error - error object message
* @return {undefined}
*/
const handleSocketError = function (error) {
/* Errors ultimately result in a `close` event.
see: https://github.com/websockets/ws/blob/828194044bf247af852b31c49e2800d557fedeff/lib/websocket.js#L126 */
Huobi.options.log('WebSocket error: ' + this.endpoint +
(error.code ? ' (' + error.code + ')' : '') +
(error.message ? ' ' + error.message : ''));
};
/**
* Called on each socket heartbeat
* @return {undefined}
*/
const handleSocketHeartbeat = function () {
this.isAlive = true;
};
/**
* Used to terminate a web socket
* @param {string} endpoint - endpoint identifier associated with the web socket
* @param {boolean} reconnect - auto reconnect after termination
* @return {undefined}
*/
const terminate = function (endpoint, reconnect = false) {
let ws = Huobi.subscriptions[endpoint];
if (!ws) return;
ws.removeAllListeners('message');
ws.reconnect = reconnect;
ws.terminate();
}
/**
* Checks whether or not an array contains any duplicate elements
* Note(keith1024): at the moment this only works for primitive types,
* will require modification to work with objects
* @param {array} array - the array to check
* @return {boolean} - true or false
*/
const isArrayUnique = function (array) {
let s = new Set(array);
return s.size === array.length;
};
return {
/**
* Gets depth cache for given symbol
* @param {symbol} symbol - get depch cache for this symbol
* @return {object} - object
*/
depthCache: function (symbol) {
return getDepthCache(symbol);
},
/**
* Gets depth volume for given symbol
* @param {symbol} symbol - get depch volume for this symbol
* @return {object} - object
*/
depthVolume: function (symbol) {
return depthVolume(symbol);
},
//
// /**
// * Count decimal places
// * @param {float} float - get the price precision point
// * @return {int} - number of place
// */
// getPrecision: function (float) {
// if ( !float || Number.isInteger( float ) ) return 0;
// return float.toString().split('.')[1].length || 0;
// },
//
// /**
// * rounds number with given step
// * @param {float} qty - quantity to round
// * @param {float} stepSize - stepSize as specified by exchangeInfo
// * @return {float} - number
// */
// roundStep: function (qty, stepSize) {
// // Integers do not require rounding
// if (Number.isInteger(qty)) return qty;
// const qtyString = qty.toFixed(16);
// const desiredDecimals = Math.max(stepSize.indexOf('1') - 1, 0);
// const decimalIndex = qtyString.indexOf('.');
// return parseFloat(qtyString.slice(0, decimalIndex + desiredDecimals + 1));
// },
//
// /**
// * rounds price to required precision
// * @param {float} price - price to round
// * @param {float} tickSize - tickSize as specified by exchangeInfo
// * @return {float} - number
// */
// roundTicks: function (price, tickSize) {
// const formatter = new Intl.NumberFormat('en-US', { style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: 8 });
// const precision = formatter.format(tickSize).split('.')[1].length || 0;
// if (typeof price === 'string') price = parseFloat(price);
// return price.toFixed(precision);
// },
//
// /**
// * Gets percentage of given numbers
// * @param {float} min - the smaller number
// * @param {float} max - the bigger number
// * @param {int} width - percentage width
// * @return {float} - percentage
// */
// percent: function (min, max, width = 100) {
// return (min * 0.01) / (max * 0.01) * width;
// },
//
// /**
// * Gets the sum of an array of numbers
// * @param {array} array - the number to add
// * @return {float} - sum
// */
// sum: function (array) {
// return array.reduce((a, b) => a + b, 0);
// },
//
// /**
// * Reverses the keys of an object
// * @param {object} object - the object
// * @return {object} - the object
// */
// reverse: function (object) {
// let range = Object.keys(object).reverse(), output = {};
// for (let price of range) {
// output[price] = object[price];
// }
// return output;
// },
//
// /**
// * Converts an object to an array
// * @param {object} obj - the object
// * @return {array} - the array
// */
// array: function (obj) {
// return Object.keys(obj).map(function (key) {
// return [Number(key), obj[key]];
// });
// },
//
/**
* Sorts bids
* @param {string} symbol - the object
* @param {int} max - the max number of bids
* @param {string} baseValue - the object
* @return {object} - the object
*/
sortBids: function (symbol, max = Infinity, baseValue = false) {
let object = {}, count = 0, cache;
if (typeof symbol === 'object') cache = symbol;
else cache = getDepthCache(symbol).bids;
let sorted = Object.keys(cache).sort(function (a, b) {
return parseFloat(b) - parseFloat(a)
});
let cumulative = 0;
for (let price of sorted) {
if (baseValue === 'cumulative') {
cumulative += parseFloat(cache[price]);
object[price] = cumulative;
} else if (!baseValue) object[price] = parseFloat(cache[price]);
else object[price] = parseFloat((cache[price] * parseFloat(price)).toFixed(8));
if (++count >= max) break;
}
return object;
},
/**
* Sorts asks
* @param {string} symbol - the object
* @param {int} max - the max number of bids
* @param {string} baseValue - the object
* @return {object} - the object
*/
sortAsks: function (symbol, max = Infinity, baseValue = false) {
let object = {}, count = 0, cache;
if (typeof symbol === 'object') cache = symbol;
else cache = getDepthCache(symbol).asks;
let sorted = Object.keys(cache).sort(function (a, b) {
return parseFloat(a) - parseFloat(b);
});
let cumulative = 0;
for (let price of sorted) {
if (baseValue === 'cumulative') {
cumulative += parseFloat(cache[price]);
object[price] = cumulative;
} else if (!baseValue) object[price] = parseFloat(cache[price]);
else object[price] = parseFloat((cache[price] * parseFloat(price)).toFixed(8));
if (++count >= max) break;
}
return object;
},
//
// /**
// * Returns the first property of an object
// * @param {object} object - the object to get the first member
// * @return {string} - the object key
// */
// first: function (object) {
// return Object.keys(object).shift();
// },
//
// /**
// * Returns the last property of an object
// * @param {object} object - the object to get the first member
// * @return {string} - the object key
// */
// last: function (object) {
// return Object.keys(object).pop();
// },
//
// /**
// * Returns an array of properties starting at start
// * @param {object} object - the object to get the properties form
// * @param {int} start - the starting index
// * @return {array} - the array of entires
// */
// slice: function (object, start = 0) {
// return Object.entries(object).slice(start).map(entry => entry[0]);
// },
//
// /**
// * Gets the minimum key form object
// * @param {object} object - the object to get the properties form
// * @return {string} - the minimum key
// */
// min: function (object) {
// return Math.min.apply(Math, Object.keys(object));
// },
//
// /**
// * Gets the maximum key form object
// * @param {object} object - the object to get the properties form
// * @return {string} - the minimum key
// */
// max: function (object) {
// return Math.max.apply(Math, Object.keys(object));
// },
//
// /**
// * Sets an option given a key and value
// * @param {string} key - the key to set
// * @param {object} value - the value of the key
// * @return {undefined}
// */
// setOption: function (key, value) {
// Binance.options[key] = value;
// },
//
// /**
// * Gets an option given a key
// * @param {string} key - the key to set
// * @return {undefined}
// */
// getOption: function (key) {
// return Binance.options[key];
// },
//
// /**
// * Returns the entire info object
// * @return {object} - the info object
// */
// getInfo: function () {
// return Binance.info;
// },
/**
* Returns the entire options object
* @return {object} - the options object
*/
getOptions: function () {
return Binance.options;
},
/**
* Gets an option given a key
* @param {object} opt - the object with the class configuration
* @param {function} callback - the callback function
* @return {undefined}
*/
options: function (opt, callback = false) {
if (typeof opt === 'string') { // Pass json config filename
Huobi.options = JSON.parse(file.readFileSync(opt));
} else Huobi.options = opt;
if (typeof Huobi.options.recvWindow === 'undefined') Huobi.options.recvWindow = default_options.recvWindow;
if (typeof Huobi.options.useServerTime === 'undefined') Huobi.options.useServerTime = default_options.useServerTime;
if (typeof Huobi.options.reconnect === 'undefined') Huobi.options.reconnect = default_options.reconnect;
if (typeof Huobi.options.test === 'undefined') Huobi.options.test = default_options.test;
if (typeof Huobi.options.log === 'undefined') Huobi.options.log = default_options.log;
if (typeof Huobi.options.verbose === 'undefined') Huobi.options.verbose = default_options.verbose;
if (Huobi.options.useServerTime) {
apiRequest(base + 'v1/time', {}, function (error, response) {
Huobi.info.timeOffset = response.serverTime - new Date().getTime();
//Binance.options.log("server time set: ", response.serverTime, Binance.info.timeOffset);
if (callback) callback();
});
} else if (callback) callback();
return this;
},
/**
* Creates an order
* @param {string} side - BUY or SELL
* @param {string} symbol - the symbol to buy
* @param {numeric} quantity - the quantity required
* @param {numeric} price - the price to pay for each unit
* @param {object} flags - aadditionalbuy order flags
* @param {function} callback - the callback function
* @return {undefined}
*/
order: function (side, symbol, quantity, price, flags = {}, callback = false) {
order(side, symbol, quantity, price, flags, callback);
},
/**
* Creates a buy order
* @param {string} symbol - the symbol to buy
* @param {numeric} quantity - the quantity required
* @param {numeric} price - the price to pay for each unit
* @param {object} flags - additional buy order flags
* @param {function} callback - the callback function
* @return {undefined}
*/
buy: function (symbol, quantity, price, flags = {}, callback = false) {
order('BUY', symbol, quantity, price, flags, callback);
},
/**
* Creates a sell order
* @param {string} symbol - the symbol to sell
* @param {numeric} quantity - the quantity required
* @param {numeric} price - the price to sell each unit for
* @param {object} flags - additional order flags
* @param {function} callback - the callback function
* @return {undefined}
*/
sell: function (symbol, quantity, price, flags = {}, callback = false) {
order('SELL', symbol, quantity, price, flags, callback);
},
/**
* Creates a market buy order
* @param {string} symbol - the symbol to buy
* @param {numeric} quantity - the quantity required
* @param {object} flags - additional buy order flags
* @param {function} callback - the callback function
* @return {undefined}
*/
marketBuy: function (symbol, quantity, flags = { type: 'MARKET' }, callback = false) {
if (typeof flags === 'function') { // Accept callback as third parameter
callback = flags;
flags = { type: 'MARKET' };
}
if (typeof flags.type === 'undefined') flags.type = 'MARKET';
order('BUY', symbol, quantity, 0, flags, callback);
},
/**
* Creates a market sell order
* @param {string} symbol - the symbol to sell
* @param {numeric} quantity - the quantity required
* @param {object} flags - additional sell order flags
* @param {function} callback - the callback function
* @return {undefined}
*/
marketSell: function (symbol, quantity, flags = { type: 'MARKET' }, callback = false) {
if (typeof flags === 'function') { // Accept callback as third parameter
callback = flags;
flags = { type: 'MARKET' };
}
if (typeof flags.type === 'undefined') flags.type = 'MARKET';
order('SELL', symbol, quantity, 0, flags, callback);
},
/**
* Cancels an order
* @param {string} symbol - the symbol to cancel
* @param {string} orderid - the orderid to cancel
* @param {function} callback - the callback function
* @return {undefined}
*/
cancel: function (symbol, orderid, callback = false) {
signedRequest(base + 'v3/order', { symbol: symbol, orderId: orderid }, function (error, data) {
if (callback) return callback.call(this, error, data, symbol);
}, 'DELETE');
},
/**
* Gets the status of an order
* @param {string} symbol - the symbol to check
* @param {string} orderid - the orderid to check
* @param {function} callback - the callback function
* @param {object} flags - any additional flags
* @return {undefined}
*/
orderStatus: function (symbol, orderid, callback, flags = {}) {
let parameters = Object.assign({ symbol: symbol, orderId: orderid }, flags);
signedRequest(base + 'v3/order', parameters, function (error, data) {
if (callback) return callback.call(this, error, data, symbol);
});
},
/**
* Gets open orders
* @param {string} symbol - the symbol to get
* @param {function} callback - the callback function
* @return {undefined}
*/
openOrders: function (symbol, callback) {
let parameters = symbol ? { symbol: symbol } : {};
signedRequest(base + 'v3/openOrders', parameters, function (error, data) {
return callback.call(this, error, data, symbol);
});
},
/**
* Cancels all order of a given symbol
* @param {string} symbol - the symbol to cancel all orders for
* @param {function} callback - the callback function
* @return {undefined}
*/
cancelOrders: function (symbol, callback = false) {
signedRequest(base + 'v3/openOrders', { symbol: symbol }, function (error, json) {
if (json.length === 0) {
if (callback) return callback.call(this, 'No orders present for this symbol', {}, symbol);
}
for (let obj of json) {
let quantity = obj.origQty - obj.executedQty;
Binance.options.log('cancel order: ' + obj.side + ' ' + symbol + ' ' + quantity + ' @ ' + obj.price + ' #' + obj.orderId);
signedRequest(base + 'v3/order', { symbol: symbol, orderId: obj.orderId }, function (error, data) {
if (callback) return callback.call(this, error, data, symbol);
}, 'DELETE');
}
});
},
/**
* Gets all order of a given symbol
* @param {string} symbol - the symbol
* @param {function} callback - the callback function
* @param {object} options - additional options
* @return {undefined}
*/
allOrders: function (symbol, callback, options = {}) {
let parameters = Object.assign({ symbol: symbol }, options);
signedRequest(base + 'v3/allOrders', parameters, function (error, data) {
if (callback) return callback.call(this, error, data, symbol);
});
},
/**
* Gets the depth information for a given symbol
* @param {string} symbol - the symbol
* @param {function} callback - the callback function
* @param {int} limit - limit the number of returned orders
* @return {undefined}
*/
depth: function (symbol, callback, limit = 100) {
publicRequest(base + 'v1/depth', { symbol: symbol, limit: limit }, function (error, data) {
return callback.call(this, error, depthData(data), symbol);
});
},
/**
* Gets the average prices of a given symbol
* @param {string} symbol - the symbol
* @param {function} callback - the callback function
* @return {undefined}
*/
avgPrice: function (symbol, callback = false) {
let socksproxy = process.env.socks_proxy || false;
let opt = {
url: base + 'v3/avgPrice?symbol=' + symbol,
timeout: Binance.options.recvWindow
};
if (socksproxy !== false) {
socksproxy = proxyReplacewithIp(socksproxy);
if (Binance.options.verbose) Binance.options.log('using socks proxy server ' + socksproxy);
opt.agentClass = SocksProxyAgent;
opt.agentOptions = {
protocol: parseProxy(socksproxy)[0],
host: parseProxy(socksproxy)[1],
port: parseProxy(socksproxy)[2]
}
}
request(opt, function (error, response, body) {
if (!callback) return;
if (error) return callback(error);
if (response && response.statusCode !== 200) return callback(response);
if (callback) return callback(null, priceData(JSON.parse(body)));
});
},
/**
* Gets the prices of a given symbol(s)
* @param {string} symbol - the symbol
* @param {function} callback - the callback function
* @return {undefined}
*/
prices: function (symbol, callback = false) {
const params = typeof symbol === 'string' ? '?symbol=' + symbol : '';
if (typeof symbol === 'function') callback = symbol; // backwards compatibility
let socksproxy = process.env.socks_proxy || false;
let opt = {
url: base + 'v3/ticker/price' + params,
timeout: Binance.options.recvWindow
};
if (socksproxy !== false) {
socksproxy = proxyReplacewithIp(socksproxy);
if (Binance.options.verbose) Binance.options.log('using socks proxy server ' + socksproxy);
opt.agentClass = SocksProxyAgent;
opt.agentOptions = {
protocol: parseProxy(socksproxy)[0],
host: parseProxy(socksproxy)[1],
port: parseProxy(socksproxy)[2]
}
}
request(opt, function (error, response, body) {
if (!callback) return;
if (error) return callback(error);
if (response && response.statusCode !== 200) return callback(response);
if (callback) return callback(null, priceData(JSON.parse(body)));
});
},
/**
* Gets the book tickers of given symbol(s)
* @param {string} symbol - the symbol
* @param {function} callback - the callback function
* @return {undefined}
*/
bookTickers: function (symbol, callback) {
const params = typeof symbol === 'string' ? '?symbol=' + symbol : '';
if (typeof symbol === 'function') callback = symbol; // backwards compatibility
let socksproxy = process.env.socks_proxy || false;
let opt = {
url: base + 'v3/ticker/bookTicker' + params,
timeout: Binance.options.recvWindow
};
if (socksproxy !== false) {
socksproxy = proxyReplacewithIp(socksproxy);
if (Binance.options.verbose) Binance.options.log('using socks proxy server ' + socksproxy);
opt.agentClass = SocksProxyAgent;
opt.agentOptions = {
protocol: parseProxy(socksproxy)[0],
host: parseProxy(socksproxy)[1],
port: parseProxy(socksproxy)[2]
}
}
request(opt, function (error, response, body) {
if (!callback) return;
if (error) return callback(error);
if (response && response.statusCode !== 200) return callback(response);
if (callback) {
const result = symbol ? JSON.parse(body) : bookPriceData(JSON.parse(body));
return callback(null, result);
}
});
},
/**
* Gets the prevday percentage change
* @param {string} symbol - the symbol or symbols
* @param {function} callback - the callback function
* @return {undefined}
*/
prevDay: function (symbol, callback) {
let input = symbol ? { symbol: symbol } : {};
publicRequest(base + 'v1/ticker/24hr', input, function (error, data) {
if (callback) return callback.call(this, error, data, symbol);
});
},
/**
* Gets the the exchange info
* @param {function} callback - the callback function
* @return {undefined}
*/
exchangeInfo: function (callback) {
publicRequest(base + '/v1/common/symbols', {}, callback);
},
/**
* Gets the dust log for user
* @param {function} callback - the callback function
* @return {undefined}
*/
dustLog: function (callback) {
signedRequest(wapi + '/v3/userAssetDribbletLog.html', {}, callback);
},
/**
* Gets the the system status
* @param {function} callback - the callback function
* @return {undefined}
*/
systemStatus: function (callback) {
publicRequest(wapi + 'v3/systemStatus.html', {}, callback);
},
/**
* Withdraws asset to given wallet id
* @param {string} asset - the asset symbol
* @param {string} address - the wallet to transfer it to
* @param {number} amount - the amount to transfer
* @param {string} addressTag - and addtional address tag
* @param {function} callback - the callback function
* @return {undefined}
*/
withdraw: function (asset, address, amount, addressTag = false, callback = false) {
let params = { asset, address, amount };
params.name = 'API Withdraw';
if (addressTag) params.addressTag = addressTag;
signedRequest(wapi + 'v3/withdraw.html', params, callback, 'POST');
},
/**
* Get the Withdraws history for a given asset
* @param {function} callback - the callback function
* @param {object} params - supports limit and fromId parameters
* @return {undefined}
*/
withdrawHistory: function (callback, params = {}) {
if (typeof params === 'string') params = { asset: params };
signedRequest(wapi + 'v3/withdrawHistory.html', params, callback);
},
/**
* Get the deposit history
* @param {function} callback - the callback function
* @param {object} params - additional params
* @return {undefined}
*/
depositHistory: function (callback, params = {}) {
if (typeof params === 'string') params = { asset: params }; // Support 'asset' (string) or optional parameters (object)
signedRequest(wapi + 'v3/depositHistory.html', params, callback);
},
/**
* Get the deposit history for given asset
* @param {string} asset - the asset
* @param {function} callback - the callback function
* @return {undefined}
*/
depositAddress: function (asset, callback) {
signedRequest(wapi + 'v3/depositAddress.html', { asset: asset }, callback);
},
/**
* Get the account status
* @param {function} callback - the callback function
* @return {undefined}
*/
accountStatus: function (callback) {
signedRequest(wapi + 'v3/accountStatus.html', {}, callback);
},
/**
* Get the trade fee
* @param {function} callback - the callback function
* @param {string} symbol (optional)
* @return {undefined}
*/
tradeFee: function (callback, symbol = false) {
let params = symbol ? { symbol: symbol } : {};
signedRequest(wapi + 'v3/tradeFee.html', params, callback);
},
/**
* Fetch asset detail (minWithdrawAmount, depositStatus, withdrawFee, withdrawStatus, depositTip)
* @param {function} callback - the callback function
* @return {undefined}
*/
assetDetail: function (callback) {
signedRequest(wapi + 'v3/assetDetail.html', {}, callback);
},
/**
* Get the account
* @param {function} callback - the callback function
* @return {undefined}
*/
account: function (callback) {
signedRequest(base + '/v1/account/accounts', {}, callback);
},
/**
* Get the balance data
* @param {string} accountId - the account
* @param {function} callback - the callback function
* @return {undefined}
*/
balance: function (accountId,callback) {
signedRequest(base + '/v1/account/accounts/'+accountId+'/balance', {}, function (error, data) {
if (callback) callback(error, balanceData(data));
});
},
/**
* Get trades for a given symbol
* @param {string} symbol - the symbol
* @param {function} callback - the callback function
* @param {object} options - additional options
* @return {undefined}
*/
trades: function (symbol, callback, options = {}) {
let parameters = Object.assign({ symbol: symbol }, options);
signedRequest(base + 'v3/myTrades', parameters, function (error, data) {
if (callback) return callback.call(this, error, data, symbol);
});
},
/**
* Tell api to use the server time to offset time indexes
* @param {function} callback - the callback function
* @return {undefined}
*/
useServerTime: function (callback = false) {
apiRequest(base + 'v1/time', {}, function (error, response) {
Huobi.info.timeOffset = response.serverTime - new Date().getTime();
//Binance.options.log("server time set: ", response.serverTime, Binance.info.timeOffset);
if (callback) callback();
});
},
/**
* Gets the time
* @param {function} callback - the callback function
* @return {undefined}
*/
time: function (callback) {
apiRequest(base + '/v1/common/timestamp', {}, callback);
},
/**
* Get agg trades for given symbol
* @param {string} symbol - the symbol
* @param {object} options - addtional optoins
* @param {function} callback - the callback function
* @return {undefined}
*/
aggTrades: function (symbol, options = {}, callback = false) { //fromId startTime endTime limit
let parameters = Object.assign({ symbol }, options);
marketRequest(base + 'v1/aggTrades', parameters, callback);
},
/**
* Get the recent trades
* @param {string} symbol - the symbol
* @param {function} callback - the callback function
* @param {int} limit - limit the number of items returned
* @return {undefined}
*/
recentTrades: function (symbol, callback, limit = 500) {
marketRequest(base + 'v1/trades', { symbol: symbol, limit: limit }, callback);
},
/**
* Get the historical trade info
* @param {string} symbol - the symbol
* @param {function} callback - the callback function
* @param {int} limit - limit the number of items returned
* @param {int} fromId - from this id
* @return {undefined}
*/
historicalTrades: function (symbol, callback, limit = 500, fromId = false) {
let parameters = { symbol: symbol, limit: limit };
if (fromId) parameters.fromId = fromId;
marketRequest(base + 'v1/historicalTrades', parameters, callback);
},
/**
* Convert chart data to highstock array [timestamp,open,high,low,close]
* @param {object} chart - the chart
* @param {boolean} include_volume - to include the volume or not
* @return {array} - an array
*/
highstock: function (chart, include_volume = false) {
let array = [];
for (let timestamp in chart) {
let obj = chart[timestamp];
let line = [
Number(timestamp),
parseFloat(obj.open),
parseFloat(obj.high),
parseFloat(obj.low),
parseFloat(obj.close)
];
if (include_volume) line.push(parseFloat(obj.volume));
array.push(line);
}
return array;
},
/**
* Populates hte OHLC information
* @param {object} chart - the chart
* @return {object} - object with candle information
*/
ohlc: function (chart) {
let open = [], high = [], low = [], close = [], volume = [];
for (let timestamp in chart) { //Binance.ohlc[symbol][interval]
let obj = chart[timestamp];
open.push(parseFloat(obj.open));
high.push(parseFloat(obj.high));
low.push(parseFloat(obj.low));
close.push(parseFloat(obj.close));
volume.push(parseFloat(obj.volume));
}
return { open: open, high: high, low: low, close: close, volume: volume };
},
/**
* Gets the candles information for a given symbol
* intervals: 1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,1M
* @param {string} symbol - the symbol
* @param {function} interval - the callback function
* @param {function} callback - the callback function
* @param {object} options - additional options
* @return {undefined}
*/
candlesticks: function (symbol, interval = '5m', callback = false, options = { limit: 500 }) {
if (!callback) return;
let params = Object.assign({ symbol: symbol, interval: interval }, options);
publicRequest(base + 'v1/klines', params, function (error, data) {
return callback.call(this, error, data, symbol);
});
},
/**
* Queries the public api
* @param {string} url - the public api endpoint
* @param {object} data - the data to send
* @param {function} callback - the callback function
* @param {string} method - the http method
* @return {undefined}
*/
publicRequest: function (url, data, callback, method = 'GET') {
publicRequest(url, data, callback, method)
},
/**
* Queries the signed api
* @param {string} url - the signed api endpoint
* @param {object} data - the data to send
* @param {function} callback - the callback function
* @param {string} method - the http method
* @return {undefined}
*/
signedRequest: function (url, data, callback, method = 'GET') {
signedRequest(url, data, callback, method);
},
/**
* Gets the market asset of given symbol
* @param {string} symbol - the public api endpoint
* @return {undefined}
*/
getMarket: function (symbol) {
const substring = symbol.substr(-3);
if (substring === 'BTC') return 'BTC';
else if (substring === 'ETH') return 'ETH';
else if (substring === 'BNB') return 'BNB';
else if (symbol.substr(-4) === 'USDT') return 'USDT';
},
websockets: {
/**
* Userdata websockets function
* @param {function} callback - the callback function
* @param {function} execution_callback - optional execution callback
* @param {function} subscribed_callback - subscription callback
* @return {undefined}
*/
userData: function userData(callback, execution_callback = false, subscribed_callback = false) {
let reconnect = function () {
if (Huobi.options.reconnect) userData(callback, execution_callback, subscribed_callback);
};
apiRequest(base + 'v1/userDataStream', {}, function (error, response) {
Huobi.options.listenKey = response.listenKey;
setTimeout(function userDataKeepAlive() { // keepalive
try {
apiRequest(base + 'v1/userDataStream?listenKey=' + Huobi.options.listenKey, {}, function (err) {
if (err) setTimeout(userDataKeepAlive, 60000); // retry in 1 minute
else setTimeout(userDataKeepAlive, 60 * 30 * 1000); // 30 minute keepalive
}, 'PUT');
} catch (error) {
setTimeout(userDataKeepAlive, 60000); // retry in 1 minute
}
}, 60 * 30 * 1000); // 30 minute keepalive
Huobi.options.balance_callback = callback;
Huobi.options.execution_callback = execution_callback;
const subscription = subscribe(Huobi.options.listenKey, userDataHandler, reconnect);
if (subscribed_callback) subscribed_callback(subscription.endpoint);
}, 'POST');
},
/**
* Subscribe to a generic websocket
* @param {string} url - the websocket endpoint
* @param {function} callback - optional execution callback
* @param {boolean} reconnect - subscription callback
* @return {WebSocket} the websocket reference
*/
subscribe: function (url, callback, reconnect = false) {
return subscribe(url, callback, reconnect);
},
/**
* Subscribe to a generic combined websocket
* @param {string} url - the websocket endpoint
* @param {function} callback - optional execution callback
* @param {boolean} reconnect - subscription callback
* @return {WebSocket} the websocket reference
*/
subscribeCombined: function (url, callback, reconnect = false) {
return subscribeCombined(url, callback, reconnect);
},
/**
* Returns the known websockets subscriptions
* @return {array} array of web socket subscriptions
*/
subscriptions: function () {
return Huobi.subscriptions;
},
/**
* Terminates a web socket
* @param {string} endpoint - the string associated with the endpoint
* @return {undefined}
*/
terminate: function (endpoint) {
if (Huobi.options.verbose) Huobi.options.log('WebSocket terminating:', endpoint);
return terminate(endpoint);
},
/**
* Websocket depth chart
* @param {array/string} symbols - an array or string of symbols to query
* @param {function} callback - callback function
* @return {string} the websocket endpoint
*/
depth: function depth(symbols, callback) {
let reconnect = function () {
if (Huobi.options.reconnect) depth(symbols, callback);
};
let subscription;
if (Array.isArray(symbols)) {
// if (!isArrayUnique(symbols)) throw Error('depth: "symbols" cannot contain duplicate elements.');
// let streams = symbols.map(function (symbol) {
// return symbol.toLowerCase() + '@depth';
// });
// subscription = subscribeCombined(streams, callback, reconnect);
} else {
let symbol = symbols;
subscription = subscribe(symbol, callback, reconnect);
}
return subscription.endpoint;
},
/**
* Websocket depth cache
* @param {array/string} symbols - an array or string of symbols to query
* @param {function} callback - callback function
* @param {int} limit - the number of entries
* @return {string} the websocket endpoint
*/
depthCache: function depthCacheFunction(symbols, callback, limit = 500) {
let reconnect = function () {
if (Huobi.options.reconnect) depthCacheFunction(symbols, callback, limit);
};
let symbolDepthInit = function (symbol) {
//Huobi.options.log("symbolDepthInit ====="+symbol);
if (typeof Huobi.depthCacheContext[symbol] === 'undefined') Huobi.depthCacheContext[symbol] = {};
let context = Huobi.depthCacheContext[symbol];
context.snapshotUpdateId = null;
context.lastEventUpdateId = null;
context.messageQueue = [];
Huobi.depthCache[symbol] = { bids: {}, asks: {} };
};
let assignEndpointIdToContext = function (symbol, endpointId) {
if (Huobi.depthCacheContext[symbol]) {
let context = Huobi.depthCacheContext[symbol];
context.endpointId = endpointId;
//Huobi.options.log("symbol "+symbol+" endpointId "+endpointId);
}
};
let handleDepthStreamData = function (depth) {
let symbol = parseSymbol(depth);
let context = Huobi.depthCacheContext[symbol];
// try {
depthHandler(depth);
// } catch (err) {
// return terminate(context.endpointId, true);
// }
if (callback) callback(symbol, Huobi.depthCache[symbol], context);
};
// let getSymbolDepthSnapshot = function (symbol, cb) {
//
// publicRequest(base + 'v1/depth', { symbol: symbol, limit: limit }, function (error, json) {
// if (error) {
// return cb(error, null);
// }
// // Store symbol next use
// json.symb = symbol;
// cb(null, json)
// });
// };
let updateSymbolDepthCache = function (json) {
// Get previous store symbol
let symbol = json.symb;
// Initialize depth cache from snapshot
Huobi.depthCache[symbol] = depthData(json);
// Prepare depth cache context
let context = Huobi.depthCacheContext[symbol];
context.snapshotUpdateId = json.lastUpdateId;
context.messageQueue = context.messageQueue.filter(depth => depth.u > context.snapshotUpdateId);
// Process any pending depth messages
for (let depth of context.messageQueue) {
/* Although sync errors shouldn't ever happen here, we catch and swallow them anyway
just in case. The stream handler function above will deal with broken caches. */
try {
depthHandler(depth);
} catch (err) {
// do nothing
}
}
delete context.messageQueue;
if (callback) callback(symbol, Huobi.depthCache[symbol]);
};
/* If an array of symbols are sent we use a combined stream connection rather.
This is transparent to the developer, and results in a single socket connection.
This essentially eliminates "unexpected response" errors when subscribing to a lot of data. */
let subscription;
if (Array.isArray(symbols)) {
if (!isArrayUnique(symbols)) throw Error('depthCache: "symbols" cannot contain duplicate elements.');
symbols.forEach(symbolDepthInit);
let streams = symbols.map(function (symbol) {
return symbol.toLowerCase();
});
subscription = subscribe(streams, handleDepthStreamData, reconnect, function () {
});
symbols.forEach(s => assignEndpointIdToContext(s, subscription.endpoint));
} else {
let symbol = symbols;
symbolDepthInit(symbol);
subscription = subscribe(symbol, handleDepthStreamData, reconnect, function () {
});
assignEndpointIdToContext(symbol, subscription.endpoint);
}
return subscription.endpoint;
},
/**
* Websocket staggered depth cache
* @param {array/string} symbols - an array of symbols to query
* @param {function} callback - callback function
* @param {int} limit - the number of entries
* @param {int} stagger - ms between each depth cache
* @return {Promise} the websocket endpoint
*/
depthCacheStaggered: function (symbols, callback, limit = 100, stagger = 200) {
if (!Array.isArray(symbols)) symbols = [symbols];
let chain = null;
// symbols.forEach(symbol => {
// let promise = () => new Promise(resolve => {
// this.depthCache(symbol, callback, limit);
// setTimeout(resolve, stagger);
// });
// chain = chain ? chain.then(promise) : promise();
// });
let promise = () => new Promise(resolve => {
this.depthCache(symbols, callback, limit);
setTimeout(resolve, stagger);
});
chain = chain ? chain.then(promise) : promise();
return chain;
},
/**
* Websocket aggregated trades
* @param {array/string} symbols - an array or string of symbols to query
* @param {function} callback - callback function
* @return {string} the websocket endpoint
*/
aggTrades: function trades(symbols, callback) {
let reconnect = function () {
if (Huobi.options.reconnect) trades(symbols, callback);
};
let subscription;
if (Array.isArray(symbols)) {
if (!isArrayUnique(symbols)) throw Error('trades: "symbols" cannot contain duplicate elements.');
let streams = symbols.map(function (symbol) {
return symbol.toLowerCase() + '@aggTrade';
});
subscription = subscribeCombined(streams, callback, reconnect);
} else {
let symbol = symbols;
subscription = subscribe(symbol.toLowerCase() + '@aggTrade', callback, reconnect);
}
return subscription.endpoint;
},
/**
* Websocket raw trades
* @param {array/string} symbols - an array or string of symbols to query
* @param {function} callback - callback function
* @return {string} the websocket endpoint
*/
trades: function trades(symbols, callback) {
let reconnect = function () {
if (Huobi.options.reconnect) trades(symbols, callback);
};
let subscription;
if (Array.isArray(symbols)) {
if (!isArrayUnique(symbols)) throw Error('trades: "symbols" cannot contain duplicate elements.');
let streams = symbols.map(function (symbol) {
return symbol.toLowerCase() + '@trade';
});
subscription = subscribeCombined(streams, callback, reconnect);
} else {
let symbol = symbols;
subscription = subscribe(symbol.toLowerCase() + '@trade', callback, reconnect);
}
return subscription.endpoint;
},
/**
* Websocket klines
* @param {array/string} symbols - an array or string of symbols to query
* @param {string} interval - the time interval
* @param {function} callback - callback function
* @param {int} limit - maximum results, no more than 1000
* @return {string} the websocket endpoint
*/
chart: function chart(symbols, interval, callback, limit = 500) {
let reconnect = function () {
if (Huobi.options.reconnect) chart(symbols, interval, callback, limit);
};
let symbolChartInit = function (symbol) {
if (typeof Huobi.info[symbol] === 'undefined') Huobi.info[symbol] = {};
if (typeof Huobi.info[symbol][interval] === 'undefined') Huobi.info[symbol][interval] = {};
if (typeof Huobi.ohlc[symbol] === 'undefined') Huobi.ohlc[symbol] = {};
if (typeof Huobi.ohlc[symbol][interval] === 'undefined') Huobi.ohlc[symbol][interval] = {};
if (typeof Huobi.ohlcLatest[symbol] === 'undefined') Huobi.ohlcLatest[symbol] = {};
if (typeof Huobi.ohlcLatest[symbol][interval] === 'undefined') Huobi.ohlcLatest[symbol][interval] = {};
if (typeof Huobi.klineQueue[symbol] === 'undefined') Huobi.klineQueue[symbol] = {};
if (typeof Huobi.klineQueue[symbol][interval] === 'undefined') Huobi.klineQueue[symbol][interval] = [];
Huobi.info[symbol][interval].timestamp = 0;
}
let handleKlineStreamData = function (kline) {
let symbol = kline.s;
if (!Huobi.info[symbol][interval].timestamp) {
if (typeof (Huobi.klineQueue[symbol][interval]) !== 'undefined' && kline !== null) {
Huobi.klineQueue[symbol][interval].push(kline);
}
} else {
//Binance.options.log('@klines at ' + kline.k.t);
klineHandler(symbol, kline);
if (callback) callback(symbol, interval, klineConcat(symbol, interval));
}
};
let getSymbolKlineSnapshot = function (symbol, limit = 500) {
publicRequest(base + 'v1/klines', { symbol: symbol, interval: interval, limit: limit }, function (error, data) {
klineData(symbol, interval, data);
//Binance.options.log('/klines at ' + Binance.info[symbol][interval].timestamp);
if (typeof Huobi.klineQueue[symbol][interval] !== 'undefined') {
for (let kline of Huobi.klineQueue[symbol][interval]) klineHandler(symbol, kline, Huobi.info[symbol][interval].timestamp);
delete Huobi.klineQueue[symbol][interval];
}
if (callback) callback(symbol, interval, klineConcat(symbol, interval));
});
};
let subscription;
if (Array.isArray(symbols)) {
if (!isArrayUnique(symbols)) throw Error('chart: "symbols" cannot contain duplicate elements.');
symbols.forEach(symbolChartInit);
let streams = symbols.map(function (symbol) {
return symbol.toLowerCase() + '@kline_' + interval;
});
subscription = subscribeCombined(streams, handleKlineStreamData, reconnect);
symbols.forEach(element => getSymbolKlineSnapshot(element, limit));
} else {
let symbol = symbols;
symbolChartInit(symbol);
subscription = subscribe(symbol.toLowerCase() + '@kline_' + interval, handleKlineStreamData, reconnect);
getSymbolKlineSnapshot(symbol, limit);
}
return subscription.endpoint;
},
/**
* Websocket candle sticks
* @param {array/string} symbols - an array or string of symbols to query
* @param {string} interval - the time interval
* @param {function} callback - callback function
* @return {string} the websocket endpoint
*/
candlesticks: function candlesticks(symbols, interval, callback) {
let reconnect = function () {
if (Huobi.options.reconnect) candlesticks(symbols, interval, callback);
};
/* If an array of symbols are sent we use a combined stream connection rather.
This is transparent to the developer, and results in a single socket connection.
This essentially eliminates "unexpected response" errors when subscribing to a lot of data. */
let subscription;
if (Array.isArray(symbols)) {
if (!isArrayUnique(symbols)) throw Error('candlesticks: "symbols" cannot contain duplicate elements.');
let streams = symbols.map(function (symbol) {
return symbol.toLowerCase() + '@kline_' + interval;
});
subscription = subscribeCombined(streams, callback, reconnect);
} else {
let symbol = symbols.toLowerCase();
subscription = subscribe(symbol + '@kline_' + interval, callback, reconnect);
}
return subscription.endpoint;
},
/**
* Websocket mini ticker
* @param {function} callback - callback function
* @return {string} the websocket endpoint
*/
miniTicker: function miniTicker(callback) {
let reconnect = function () {
if (Huobi.options.reconnect) miniTicker(callback);
};
let subscription = subscribe('!miniTicker@arr', function (data) {
let markets = {};
for (let obj of data) {
markets[obj.s] = {
close: obj.c,
open: obj.o,
high: obj.h,
low: obj.l,
volume: obj.v,
quoteVolume: obj.q,
eventTime: obj.E
};
}
callback(markets);
}, reconnect);
return subscription.endpoint;
},
/**
* Websocket prevday percentage
* @param {array/string} symbols - an array or string of symbols to query
* @param {function} callback - callback function
* @return {string} the websocket endpoint
*/
prevDay: function prevDay(symbols, callback) {
let reconnect = function () {
if (Huobi.options.reconnect) prevDay(symbols, callback);
};
let subscription;
// Combine stream for array of symbols
if (Array.isArray(symbols)) {
if (!isArrayUnique(symbols)) throw Error('prevDay: "symbols" cannot contain duplicate elements.');
let streams = symbols.map(function (symbol) {
return symbol.toLowerCase() + '@ticker';
});
subscription = subscribeCombined(streams, function (data) {
prevDayStreamHandler(data, callback);
}, reconnect);
// Raw stream for a single symbol
} else if (symbols) {
let symbol = symbols;
subscription = subscribe(symbol.toLowerCase() + '@ticker', function (data) {
prevDayStreamHandler(data, callback);
}, reconnect);
// Raw stream of all listed symbols
} else {
subscription = subscribe('!ticker@arr', function (data) {
for (let line of data) {
prevDayStreamHandler(line, callback);
}
}, reconnect);
}
return subscription.endpoint;
}
}
};
};
module.exports = api;