ccxt-compiled
Version:
A JavaScript / Python / PHP cryptocurrency trading library with support for 90+ exchanges
385 lines (296 loc) • 11.9 kB
JavaScript
//-----------------------------------------------------------------------------
var _stringify = require('babel-runtime/core-js/json/stringify');
var _stringify2 = _interopRequireDefault(_stringify);
var _values = require('babel-runtime/core-js/object/values');
var _values2 = _interopRequireDefault(_values);
var _assign = require('babel-runtime/core-js/object/assign');
var _assign2 = _interopRequireDefault(_assign);
var _keys = require('babel-runtime/core-js/object/keys');
var _keys2 = _interopRequireDefault(_keys);
var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
var _promise = require('babel-runtime/core-js/promise');
var _promise2 = _interopRequireDefault(_promise);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const CryptoJS = require('crypto-js'),
qs = require('qs'); // querystring
//-----------------------------------------------------------------------------
const { RequestTimeout } = require('./errors');
//-----------------------------------------------------------------------------
// utility helpers
const setTimeout_original = setTimeout;
// setTimeout can fire earlier than specified, so we need to ensure it does not happen...
const setTimeout_safe = (done, ms, setTimeout = setTimeout_original /* overrideable for mocking purposes */, targetTime = Date.now() + ms) => {
let clearInnerTimeout = () => {};
let active = true;
let id = setTimeout(() => {
active = true;
const rest = targetTime - Date.now();
if (rest > 0) {
clearInnerTimeout = setTimeout_safe(done, rest, setTimeout, targetTime); // try sleep more
} else {
done();
}
}, ms);
return function clear() {
if (active) {
active = false; // dunno if IDs are unique on various platforms, so it's better to rely on this flag to exclude the possible cancellation of the wrong timer (if called after completion)
clearTimeout(id);
}
clearInnerTimeout();
};
};
const sleep = ms => new _promise2.default(resolve => setTimeout_safe(resolve, ms));
const decimal = float => parseFloat(float).toString();
const timeout = (() => {
var _ref = (0, _asyncToGenerator3.default)(function* (ms, promise) {
let clear = function () {};
const timeout = new _promise2.default(function (resolve) {
return clear = setTimeout_safe(resolve, ms);
});
try {
return yield _promise2.default.race([promise, timeout.then(function () {
throw new RequestTimeout('request timed out');
})]);
} finally {
clear(); // fixes https://github.com/ccxt/ccxt/issues/749
}
});
return function timeout(_x, _x2) {
return _ref.apply(this, arguments);
};
})();
const capitalize = string => string.length ? string.charAt(0).toUpperCase() + string.slice(1) : string;
const keysort = object => {
const result = {};
(0, _keys2.default)(object).sort().forEach(key => result[key] = object[key]);
return result;
};
const extend = (...args) => (0, _assign2.default)({}, ...args);
const deepExtend = function (...args) {
// if (args.length < 1)
// return args
// else if (args.length < 2)
// return args[0]
let result = undefined;
for (const arg of args) {
if (arg && typeof arg == 'object' && (arg.constructor === Object || !('constructor' in arg))) {
if (typeof result != 'object') {
result = {};
}
for (const key in arg) {
result[key] = deepExtend(result[key], arg[key]);
}
} else {
result = arg;
}
}
return result;
};
const omit = (object, ...args) => {
const result = extend(object);
for (const x of args) {
if (typeof x === 'string') {
delete result[x];
} else if (Array.isArray(x)) {
for (const k of x) delete result[k];
}
}
return result;
};
const groupBy = (array, key) => {
const result = {};
(0, _values2.default)(array).filter(entry => entry[key] != 'undefined').forEach(entry => {
if (typeof result[entry[key]] == 'undefined') result[entry[key]] = [];
result[entry[key]].push(entry);
});
return result;
};
const filterBy = (array, key, value = undefined) => {
if (value) {
let grouped = groupBy(array, key);
if (value in grouped) return grouped[value];
return [];
}
return array;
};
const indexBy = (array, key) => {
const result = {};
(0, _values2.default)(array).filter(entry => entry[key] != 'undefined').forEach(entry => {
result[entry[key]] = entry;
});
return result;
};
const sortBy = (array, key, descending = false) => {
descending = descending ? -1 : 1;
return array.sort((a, b) => a[key] < b[key] ? -descending : a[key] > b[key] ? descending : 0);
};
const flatten = (array, result = []) => {
for (let i = 0, length = array.length; i < length; i++) {
const value = array[i];
if (Array.isArray(value)) {
flatten(value, result);
} else {
result.push(value);
}
}
return result;
};
const unique = array => array.filter((value, index, self) => self.indexOf(value) == index);
const pluck = (array, key) => array.filter(element => typeof element[key] != 'undefined').map(element => element[key]);
const urlencode = object => qs.stringify(object);
const rawencode = object => qs.stringify(object, { encode: false });
const sum = (...args) => {
const result = args.filter(arg => typeof arg != 'undefined');
return result.length > 0 ? result.reduce((sum, value) => sum + value, 0) : undefined;
};
const safeFloat = (object, key, defaultValue = undefined) => {
if (key in object) {
if (typeof object[key] == 'number') return object[key];else if (typeof object[key] == 'string' && object[key]) return parseFloat(object[key]);
}
return defaultValue;
};
const safeString = (object, key, defaultValue = undefined) => {
return object && key in object && object[key] ? object[key].toString() : defaultValue;
};
const safeInteger = (object, key, defaultValue = undefined) => {
return key in object && object[key] ? parseInt(object[key]) : defaultValue;
};
const safeValue = (object, key, defaultValue = undefined) => {
return key in object && object[key] ? object[key] : defaultValue;
};
const uuid = a => a ? (a ^ Math.random() * 16 >> a / 4).toString(16) : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, uuid);
// See https://stackoverflow.com/questions/1685680/how-to-avoid-scientific-notation-for-large-numbers-in-javascript for discussion
function toFixed(x) {
// avoid scientific notation for too large and too small numbers
if (Math.abs(x) < 1.0) {
const e = parseInt(x.toString().split('e-')[1]);
if (e) {
x *= Math.pow(10, e - 1);
x = '0.' + new Array(e).join('0') + x.toString().substring(2);
}
} else {
let e = parseInt(x.toString().split('+')[1]);
if (e > 20) {
e -= 20;
x /= Math.pow(10, e);
x += new Array(e + 1).join('0');
}
}
return x;
}
// See https://stackoverflow.com/questions/4912788/truncate-not-round-off-decimal-numbers-in-javascript for discussion
// > So, after all it turned out, rounding bugs will always haunt you, no matter how hard you try to compensate them.
// > Hence the problem should be attacked by representing numbers exactly in decimal notation.
const truncate_regExpCache = [],
truncate = (num, precision = 0) => {
num = toFixed(num);
const re = truncate_regExpCache[precision] || (truncate_regExpCache[precision] = new RegExp("([-]*\\d+\\.\\d{" + precision + "})(\\d)"));
const [, result] = num.toString().match(re) || [null, num];
return parseFloat(result);
};
const precisionFromString = string => {
const split = string.replace(/0+$/g, '').split('.');
return split.length > 1 ? split[1].length : 0;
};
const ordered = x => x; // a stub to keep assoc keys in order, in JS it does nothing, it's mostly for Python
const aggregate = function (bidasks) {
let result = {};
bidasks.forEach(([price, volume]) => {
if (volume > 0) result[price] = (result[price] || 0) + volume;
});
return (0, _keys2.default)(result).map(price => [parseFloat(price), parseFloat(result[price])]);
};
//-----------------------------------------------------------------------------
// string ←→ binary ←→ base64 conversion routines
const stringToBinary = str => {
const arr = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++) {
arr[i] = str.charCodeAt(i);
}
return CryptoJS.lib.WordArray.create(arr);
};
const stringToBase64 = string => CryptoJS.enc.Latin1.parse(string).toString(CryptoJS.enc.Base64),
utf16ToBase64 = string => CryptoJS.enc.Utf16.parse(string).toString(CryptoJS.enc.Base64),
base64ToBinary = string => CryptoJS.enc.Base64.parse(string),
base64ToString = string => CryptoJS.enc.Base64.parse(string).toString(CryptoJS.enc.Utf8),
binaryToString = string => string;
const binaryConcat = (...args) => args.reduce((a, b) => a.concat(b));
// url-safe-base64 without equals signs, with + replaced by - and slashes replaced by underscores
const urlencodeBase64 = base64string => base64string.replace(/[=]+$/, '').replace(/\+/g, '-').replace(/\//g, '_');
//-----------------------------------------------------------------------------
// cryptography
const hash = (request, hash = 'md5', digest = 'hex') => {
const result = CryptoJS[hash.toUpperCase()](request);
return digest == 'binary' ? result : result.toString(CryptoJS.enc[capitalize(digest)]);
};
const hmac = (request, secret, hash = 'sha256', digest = 'hex') => {
const encoding = digest == 'binary' ? 'Latin1' : capitalize(digest);
return CryptoJS['Hmac' + hash.toUpperCase()](request, secret).toString(CryptoJS.enc[capitalize(encoding)]);
};
//-----------------------------------------------------------------------------
// a JSON Web Token authentication method
const jwt = (request, secret, alg = 'HS256', hash = 'sha256') => {
const encodedHeader = urlencodeBase64(stringToBase64((0, _stringify2.default)({ 'alg': alg, 'typ': 'JWT' }))),
encodedData = urlencodeBase64(stringToBase64((0, _stringify2.default)(request))),
token = [encodedHeader, encodedData].join('.'),
signature = urlencodeBase64(utf16ToBase64(hmac(token, secret, hash, 'utf16')));
return [token, signature].join('.');
};
//-----------------------------------------------------------------------------
module.exports = {
setTimeout_safe,
// common utility functions
sleep,
timeout,
capitalize,
keysort,
extend,
deepExtend,
omit,
groupBy,
indexBy,
sortBy,
filterBy,
flatten,
unique,
pluck,
urlencode,
rawencode,
sum,
decimal,
safeFloat,
safeString,
safeInteger,
safeValue,
ordered,
aggregate,
truncate,
uuid,
precisionFromString,
// underscore aliases
index_by: indexBy,
sort_by: sortBy,
group_by: groupBy,
filter_by: filterBy,
safe_float: safeFloat,
safe_string: safeString,
safe_integer: safeInteger,
safe_value: safeValue,
// crypto functions
binaryConcat,
stringToBinary,
binaryToString,
stringToBase64,
utf16ToBase64,
base64ToBinary,
base64ToString,
urlencodeBase64,
hash,
hmac,
jwt,
// json
json: _stringify2.default,
unjson: JSON.parse
};
;