154 lines
3.5 KiB
JavaScript
154 lines
3.5 KiB
JavaScript
// generic low level reusuable utils for interacting with exchanges.
|
|
|
|
const retry = require('retry');
|
|
const errors = require('./exchangeErrors');
|
|
const _ = require('lodash');
|
|
|
|
const retryInstance = (options, checkFn, callback, e) => {
|
|
if(!options) {
|
|
options = {
|
|
retries: 100,
|
|
factor: 1.2,
|
|
minTimeout: 1 * 1000,
|
|
maxTimeout: 4 * 1000
|
|
};
|
|
}
|
|
|
|
let attempt = 0;
|
|
|
|
const operation = retry.operation(options);
|
|
operation.attempt(function(currentAttempt) {
|
|
checkFn((err, result) => {
|
|
|
|
if(!err) {
|
|
return callback(undefined, result);
|
|
}
|
|
|
|
console.log(new Date, err.message);
|
|
|
|
let maxAttempts = err.retry;
|
|
if(maxAttempts === true)
|
|
maxAttempts = 10;
|
|
|
|
if(err.retry && attempt++ < maxAttempts) {
|
|
return operation.retry(err);
|
|
}
|
|
|
|
if(err.notFatal) {
|
|
if(err.backoffDelay) {
|
|
return setTimeout(() => operation.retry(err), err.backoffDelay);
|
|
}
|
|
|
|
return operation.retry(err);
|
|
}
|
|
|
|
callback(err, result);
|
|
});
|
|
});
|
|
}
|
|
|
|
// es6 bind all: https://github.com/posrix/es6-class-bind-all/blob/master/lib/es6ClassBindAll.js
|
|
const allMethods = targetClass => {
|
|
const propertys = Object.getOwnPropertyNames(Object.getPrototypeOf(targetClass))
|
|
propertys.splice(propertys.indexOf('constructor'), 1)
|
|
return propertys
|
|
}
|
|
|
|
const bindAll = (targetClass, methodNames = []) => {
|
|
for (const name of !methodNames.length ? allMethods(targetClass) : methodNames) {
|
|
targetClass[name] = targetClass[name].bind(targetClass)
|
|
}
|
|
}
|
|
|
|
const isValidOrder = ({api, market, amount, price}) => {
|
|
let reason = false;
|
|
|
|
// Check amount
|
|
if(amount < market.minimalOrder.amount) {
|
|
reason = 'Amount is too small';
|
|
}
|
|
|
|
// Some exchanges have restrictions on prices
|
|
if(
|
|
_.isFunction(api.isValidPrice) &&
|
|
!api.isValidPrice(price)
|
|
) {
|
|
reason = 'Price is not valid';
|
|
}
|
|
|
|
if(
|
|
_.isFunction(api.isValidLot) &&
|
|
!api.isValidLot(price, amount)
|
|
) {
|
|
reason = 'Lot size is too small';
|
|
}
|
|
|
|
return {
|
|
reason,
|
|
valid: !reason
|
|
}
|
|
}
|
|
|
|
|
|
// https://gist.github.com/jiggzson/b5f489af9ad931e3d186
|
|
const scientificToDecimal = num => {
|
|
if(/\d+\.?\d*e[\+\-]*\d+/i.test(num)) {
|
|
const zero = '0';
|
|
const parts = String(num).toLowerCase().split('e'); // split into coeff and exponent
|
|
const e = parts.pop(); // store the exponential part
|
|
const l = Math.abs(e); // get the number of zeros
|
|
const sign = e/l;
|
|
const coeff_array = parts[0].split('.');
|
|
if(sign === -1) {
|
|
num = zero + '.' + new Array(l).join(zero) + coeff_array.join('');
|
|
} else {
|
|
const dec = coeff_array[1];
|
|
if(dec) {
|
|
l = l - dec.length;
|
|
}
|
|
num = coeff_array.join('') + new Array(l+1).join(zero);
|
|
}
|
|
} else {
|
|
// make sure we always cast to string
|
|
num = num + '';
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
// TEMP until we have proper scheduling
|
|
const cacheFn = (fn, timeout) => {
|
|
let nextCall = false;
|
|
let cache = false;
|
|
let inflight = false;
|
|
let callbackQueue = [];
|
|
|
|
return next => {
|
|
if(inflight) {
|
|
return callbackQueue.push(next);
|
|
}
|
|
|
|
const now = +new Date;
|
|
if(cache && now >= nextCall) {
|
|
return next(res.error, res.result);
|
|
}
|
|
|
|
inflight = true;
|
|
fn((error, result) => {
|
|
cache = {error, result};
|
|
nextCall = now + timeout;
|
|
next(error, result);
|
|
callbackQueue.forEach(cb => cb(error, result));
|
|
callbackQueue = [];
|
|
inflight = false;
|
|
})
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
retry: retryInstance,
|
|
bindAll,
|
|
isValidOrder,
|
|
scientificToDecimal,
|
|
cacheFn
|
|
} |