This repository has been archived on 2025-10-29. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
gekko/exchange/exchangeUtils.js
T
2021-06-06 20:25:14 +02:00

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
}