Upercase Exchange Folder
This commit is contained in:
1 parent
b3da8aba41
commit
fd006adc67
9 files changed
+10212
No files matched your search
@@ -0,0 +1,301 @@
|
||||
exports.newExchangeAPI = function newExchangeAPI(bot, logger) {
|
||||
|
||||
/*
|
||||
|
||||
This module allows trading bots to connect to the exchange and do trading operations on it. So far it can only work with Poloniex.
|
||||
|
||||
*/
|
||||
const _ = require('lodash')
|
||||
const isValidOrder = require('./exchangeUtils').isValidOrder
|
||||
const graphqlClient = require('graphql-client')
|
||||
const appRoot = require('app-root-path')
|
||||
const auth = require(`${appRoot}/utils/auth`)
|
||||
|
||||
let MODULE_NAME = "Exchange API";
|
||||
|
||||
let thisObject = {
|
||||
initialize: initialize,
|
||||
getTicker: getTicker,
|
||||
getOpenPositions: getOpenPositions,
|
||||
getExecutedTrades: getExecutedTrades,
|
||||
putPosition: putPosition,
|
||||
movePosition: movePosition,
|
||||
getPublicTradeHistory: getPublicTradeHistory,
|
||||
getExchangeProperties: getExchangeProperties,
|
||||
getMaxDecimalPositions: getMaxDecimalPositions
|
||||
};
|
||||
|
||||
let apiClient;
|
||||
|
||||
return thisObject;
|
||||
|
||||
async function initialize(callBackFunction) {
|
||||
try {
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); }
|
||||
|
||||
// TODO integrate with financial beings module to get the exchange
|
||||
let botExchange = 'Poloniex';
|
||||
let exchange = botExchange.toLowerCase() + 'Client.js';
|
||||
let api = require('./wrappers/' + exchange);
|
||||
|
||||
const accessToken = await auth.authenticate()
|
||||
|
||||
let keyVaultAPI = createKeyVaultAPIClient(accessToken)
|
||||
apiClient = api.newAPIClient(keyVaultAPI, logger);
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function createKeyVaultAPIClient(authToken) {
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createKeyVaultAPIClient -> Entering function."); }
|
||||
|
||||
const keyVaultAPI = graphqlClient({
|
||||
url: global.GATEWAY_ENDPOINT,
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + authToken
|
||||
}
|
||||
});
|
||||
|
||||
keyVaultAPI.signTransaction = function (transaction, next) {
|
||||
let variables = {
|
||||
botId: bot.codeName,
|
||||
transaction: transaction
|
||||
}
|
||||
|
||||
keyVaultAPI.query(`
|
||||
mutation($botId: String, $transaction: String!){
|
||||
keyVault_SignTransaction(botId: $botId, transaction: $transaction){
|
||||
key,
|
||||
signature,
|
||||
date
|
||||
}
|
||||
}`, variables, function (req, res) {
|
||||
if (res.status === 401) {
|
||||
next(undefined, 'Error from graphql: Not authorized');
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.errors) {
|
||||
next(undefined, 'Error from graphql: ' + res.errors);
|
||||
} else {
|
||||
let signature = {
|
||||
Key: res.data.keyVault_SignTransaction.key,
|
||||
Sign: res.data.keyVault_SignTransaction.signature
|
||||
}
|
||||
next(signature)
|
||||
}
|
||||
}).catch(error => {
|
||||
next(undefined, 'Error signing the message on the key vault: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createKeyVaultAPIClient -> Returning graphql client."); }
|
||||
|
||||
return keyVaultAPI;
|
||||
}
|
||||
|
||||
/*
|
||||
* Position Object = {
|
||||
* id, String
|
||||
* type, String
|
||||
* rate, Number
|
||||
* amountA, Number
|
||||
* amountB, Number
|
||||
* fee, Number
|
||||
* datetime Date
|
||||
* };
|
||||
*/
|
||||
function truncDecimals(pFloatValue) {
|
||||
let decimals = getMaxDecimalPositions();
|
||||
return parseFloat(parseFloat(pFloatValue).toFixed(decimals));
|
||||
}
|
||||
|
||||
/*
|
||||
* Return number of decimals for the current market
|
||||
*/
|
||||
function getMaxDecimalPositions() {
|
||||
return getMarketConfig().maxDecimals;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return number of decimals for the current market
|
||||
*/
|
||||
function getMarketConfig() {
|
||||
return _.find(getExchangeProperties().markets, (p) => {
|
||||
return _.first(p.pair) === global.MARKET.assetA.toUpperCase() &&
|
||||
_.last(p.pair) === global.MARKET.assetB.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Return number of decimals for the current market
|
||||
*/
|
||||
function getExchangeProperties() {
|
||||
try {
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getExchangeProperties -> Entering function."); }
|
||||
|
||||
return apiClient.getExchangeProperties();
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] getExchangeProperties -> err = " + err.message);
|
||||
callBack(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the price for a given pair of assets.
|
||||
* The object returned is an array of trades:
|
||||
* ticker = {
|
||||
* bid, Number
|
||||
* ask, Number
|
||||
* last Number
|
||||
* };
|
||||
*/
|
||||
function getTicker(pMarket, callBack) {
|
||||
try {
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getTicker -> Entering function."); }
|
||||
|
||||
apiClient.getTicker(pMarket, callBack);
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] getTicker -> err = " + err.message);
|
||||
callBack(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the open positions ath the exchange for a given market and user account.
|
||||
* The object returned is an array of positions
|
||||
*
|
||||
*/
|
||||
function getOpenPositions(pMarket, callBack) {
|
||||
try {
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getOpenPositions -> Entering function."); }
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getOpenPositions -> pMarket = " + JSON.stringify(pMarket)); }
|
||||
|
||||
apiClient.getOpenPositions(pMarket, callBack);
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] getOpenPositions -> Error = " + err.message);
|
||||
callBack(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the trades for a given order number.
|
||||
* The object returned is an array of positions
|
||||
*/
|
||||
function getExecutedTrades(pPositionId, callBack) {
|
||||
try {
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getExecutedTrades -> Entering function."); }
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getExecutedTrades -> pPositionId = " + pPositionId); }
|
||||
|
||||
apiClient.getExecutedTrades(pPositionId, callBack);
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] getExecutedTrades -> Error = " + err.message);
|
||||
callBack(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates a new buy or sell order.
|
||||
* The orderNumber is returned. String
|
||||
*/
|
||||
function putPosition(pMarket, pType, pRate, pAmountA, pAmountB, callBack) {
|
||||
try {
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> Entering function."); }
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> pMarket = " + JSON.stringify(pMarket)); }
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> pType = " + pType); }
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> pRate = " + truncDecimals(pRate)); }
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> pAmountA = " + truncDecimals(pAmountA)); }
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> pAmountB = " + truncDecimals(pAmountB)); }
|
||||
|
||||
let check = isValidOrder({
|
||||
market: getMarketConfig(),
|
||||
api: apiClient,
|
||||
amount: truncDecimals(pAmountB),
|
||||
price: truncDecimals(pRate)
|
||||
});
|
||||
|
||||
if (check.valid) {
|
||||
if (pType === "buy") {
|
||||
apiClient.buy(pMarket.assetA, pMarket.assetB, truncDecimals(pRate), truncDecimals(pAmountB), callBack);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pType === "sell") {
|
||||
apiClient.sell(pMarket.assetA, pMarket.assetB, truncDecimals(pRate), truncDecimals(pAmountB), callBack);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.write(MODULE_NAME, "[ERROR] putPosition -> pType must be either 'buy' or 'sell'.");
|
||||
callBack(global.DEFAULT_FAIL_RESPONSE);
|
||||
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] putPosition -> The order is invalid: " + check.reason);
|
||||
callBack(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] putPosition -> err = " + err.message);
|
||||
callBack(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Move an existing position to the new rate.
|
||||
* The new orderNumber is returned.
|
||||
*/
|
||||
function movePosition(pPosition, pNewRate, pNewAmountB, callBack) {
|
||||
try {
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] movePosition -> Entering function."); }
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] movePosition -> pPosition = " + JSON.stringify(pPosition)); }
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] movePosition -> pNewRate = " + truncDecimals(pNewRate)); }
|
||||
|
||||
apiClient.movePosition(pPosition, truncDecimals(pNewRate), truncDecimals(pNewAmountB), callBack);
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] movePosition -> err = " + err.message);
|
||||
callBack(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns all the trade history from the Exchange since startTime to endTime orderd by tradeId.
|
||||
* It's possible that the exchange doesn't support this method.
|
||||
* The object returned is an array of trades:
|
||||
* trade = {
|
||||
* tradeID, String
|
||||
* globalTradeID, String
|
||||
* type, String
|
||||
* rate, Number
|
||||
* amountA, Number
|
||||
* amountB, Number
|
||||
* date Date
|
||||
* };
|
||||
*/
|
||||
function getPublicTradeHistory(assetA, assetB, startTime, endTime, callBack) {
|
||||
try {
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getTradeHistory -> Entering function."); }
|
||||
|
||||
apiClient.getPublicTradeHistory(assetA, assetB, startTime, endTime, callBack);
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] getTradeHistory -> err = " + err.message);
|
||||
callBack(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
// generic low level reusuable utils for interacting with exchanges.
|
||||
|
||||
const retry = require('retry');
|
||||
const _ = require('lodash');
|
||||
|
||||
const retryInstance = (options, checkFn, callback) => {
|
||||
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(global.DEFAULT_OK_RESPONSE, result);
|
||||
} else {
|
||||
if (err.result === global.CUSTOM_OK_RESPONSE.result) {
|
||||
return callback(err, result);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
const includes = function(str, list) {
|
||||
if (!_.isString(str))
|
||||
return false;
|
||||
|
||||
return _.some(list, item => str.includes(item));
|
||||
}
|
||||
|
||||
|
||||
const getMarketConfig = function(exchangeProperties) {
|
||||
return _.find(exchangeProperties.markets, (p) => {
|
||||
return _.first(p.pair) === global.MARKET.assetA.toUpperCase() &&
|
||||
_.last(p.pair) === global.MARKET.assetB.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
retry: retryInstance,
|
||||
includes: includes,
|
||||
isValidOrder,
|
||||
getMarketConfig
|
||||
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
const _ = require('underscore');
|
||||
|
||||
class Beautifier {
|
||||
|
||||
constructor() {
|
||||
this._beautifications = {
|
||||
aggTrades: {
|
||||
a: 'aggTradeId',
|
||||
p: 'price',
|
||||
q: 'quantity',
|
||||
f: 'firstTradeId',
|
||||
l: 'lastTradeId',
|
||||
T: 'timestamp',
|
||||
m: 'maker',
|
||||
M: 'bestPriceMatch'
|
||||
},
|
||||
klines: {
|
||||
0: 'openTime',
|
||||
1: 'open',
|
||||
2: 'high',
|
||||
3: 'low',
|
||||
4: 'close',
|
||||
5: 'volume',
|
||||
6: 'closeTime',
|
||||
7: 'quoteAssetVolume',
|
||||
8: 'trades',
|
||||
9: 'takerBaseAssetVolume',
|
||||
10: 'takerQuoteAssetVolume',
|
||||
11: 'ignored'
|
||||
},
|
||||
bids: [
|
||||
{
|
||||
0: 'price',
|
||||
1: 'quantity',
|
||||
2: 'ignored'
|
||||
}
|
||||
],
|
||||
asks: [
|
||||
{
|
||||
0: 'price',
|
||||
1: 'quantity',
|
||||
2: 'ignored'
|
||||
}
|
||||
],
|
||||
depthUpdateEvent: {
|
||||
e: 'eventType',
|
||||
E: 'eventTime',
|
||||
s: 'symbol',
|
||||
u: 'updateId',
|
||||
b: 'bidDepthDelta',
|
||||
a: 'askDepthDelta'
|
||||
},
|
||||
bidDepthDelta: [
|
||||
{
|
||||
0: 'price',
|
||||
1: 'quantity',
|
||||
2: 'ignored'
|
||||
}
|
||||
],
|
||||
askDepthDelta: [
|
||||
{
|
||||
0: 'price',
|
||||
1: 'quantity',
|
||||
2: 'ignored'
|
||||
}
|
||||
],
|
||||
klineEvent: {
|
||||
e: 'eventType',
|
||||
E: 'eventTime',
|
||||
s: 'symbol',
|
||||
k: 'kline'
|
||||
},
|
||||
kline: {
|
||||
t: 'startTime',
|
||||
T: 'endTime',
|
||||
s: 'symbol',
|
||||
i: 'interval',
|
||||
f: 'firstTradeId',
|
||||
L: 'lastTradeId',
|
||||
o: 'open',
|
||||
c: 'close',
|
||||
h: 'high',
|
||||
l: 'low',
|
||||
v: 'volume',
|
||||
n: 'trades',
|
||||
x: 'final',
|
||||
q: 'quoteVolume',
|
||||
V: 'volumeActive',
|
||||
Q: 'quoteVolumeActive',
|
||||
B: 'ignored'
|
||||
},
|
||||
aggTradeEvent: {
|
||||
e: 'eventType',
|
||||
E: 'eventTime',
|
||||
s: 'symbol',
|
||||
a: 'tradeId',
|
||||
p: 'price',
|
||||
q: 'quantity',
|
||||
f: 'firstTradeId',
|
||||
l: 'lastTradeId',
|
||||
T: 'time',
|
||||
m: 'maker',
|
||||
M: 'ignored'
|
||||
},
|
||||
outboundAccountInfoEvent: {
|
||||
e: 'eventType',
|
||||
E: 'eventTime',
|
||||
m: 'makerCommission',
|
||||
t: 'takerCommission',
|
||||
b: 'buyerCommission',
|
||||
s: 'sellerCommission',
|
||||
T: 'canTrade',
|
||||
W: 'canWithdraw',
|
||||
D: 'canDeposit',
|
||||
B: 'balances'
|
||||
},
|
||||
balances: [
|
||||
{
|
||||
a: 'asset',
|
||||
f: 'availableBalance',
|
||||
l: 'onOrderBalance'
|
||||
}
|
||||
],
|
||||
executionReportEvent: {
|
||||
e: 'eventType',
|
||||
E: 'eventTime',
|
||||
s: 'symbol',
|
||||
c: 'newClientOrderId',
|
||||
S: 'side',
|
||||
o: 'orderType',
|
||||
f: 'cancelType', // GTC 'Good till Cancel', IOC: 'Immediate or Cancel'
|
||||
q: 'quantity',
|
||||
p: 'price',
|
||||
x: 'executionType',
|
||||
X: 'orderStatus',
|
||||
r: 'rejectReason',
|
||||
i: 'orderId',
|
||||
l: 'lastTradeQuantity',
|
||||
z: 'accumulatedQuantity',
|
||||
L: 'lastTradePrice',
|
||||
n: 'commission',
|
||||
N: 'commissionAsset',
|
||||
m: 'maker',
|
||||
T: 'tradeTime',
|
||||
t: 'tradeId'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beautify(object, type) {
|
||||
const beautifications = _.isObject(type) ? type : this._beautifications[type];
|
||||
if (!beautifications) {
|
||||
return object;
|
||||
}
|
||||
let newItem = {};
|
||||
_.each(object, (value, key) => {
|
||||
if (_.isArray(value)) {
|
||||
const newArray = [];
|
||||
if (_.isArray(this._beautifications[beautifications[key]])) {
|
||||
_.each(value, arrayValue => {
|
||||
newArray.push(this.beautify(arrayValue, this._beautifications[beautifications[key]][0]));
|
||||
});
|
||||
} else {
|
||||
_.each(value, (value, key) => {
|
||||
newItem.push(this.beautify(value, beautifications[key]))
|
||||
});
|
||||
}
|
||||
const newKey = beautifications[key] || key;
|
||||
newItem[newKey] = newArray;
|
||||
} else if (_.isObject(value)) {
|
||||
const newKey = beautifications[key] || key;
|
||||
newItem[newKey] = this.beautify(value, beautifications[key]);
|
||||
} else {
|
||||
const newKey = beautifications[key] || key;
|
||||
newItem[newKey] = value;
|
||||
}
|
||||
});
|
||||
return newItem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Beautifier;
|
||||
@@ -0,0 +1,368 @@
|
||||
// Source: https://github.com/zoeyg/binance/blob/master/lib/rest.js
|
||||
|
||||
const request = require('request');
|
||||
const qs = require('querystring');
|
||||
const _ = require('underscore');
|
||||
const crypto = require('crypto');
|
||||
const Beautifier = require('./beautifier.js');
|
||||
const assert = require('assert');
|
||||
|
||||
class BinanceRest {
|
||||
|
||||
constructor({
|
||||
key,
|
||||
secret,
|
||||
recvWindow = false,
|
||||
timeout = 15000,
|
||||
disableBeautification = false,
|
||||
handleDrift = false
|
||||
}) {
|
||||
this.key = key;
|
||||
this.secret = secret;
|
||||
this.recvWindow = recvWindow;
|
||||
this.timeout = timeout;
|
||||
this.disableBeautification = disableBeautification;
|
||||
this.handleDrift = handleDrift;
|
||||
|
||||
this._beautifier = new Beautifier();
|
||||
this._baseUrl = 'https://api.binance.com/';
|
||||
this._drift = 0;
|
||||
this._syncInterval = 0;
|
||||
}
|
||||
|
||||
_makeRequest(query, callback, route, security, method, attempt = 0) {
|
||||
assert(_.isUndefined(callback) || _.isFunction(callback), 'callback must be a function or undefined');
|
||||
assert(_.isObject(query), 'query must be an object');
|
||||
|
||||
let queryString;
|
||||
const type = _.last(route.split('/')),
|
||||
options = {
|
||||
url: `${this._baseUrl}${route}`,
|
||||
timeout: this.timeout
|
||||
};
|
||||
|
||||
if (security === 'SIGNED') {
|
||||
if (this.recvWindow) {
|
||||
query.recvWindow = this.recvWindow;
|
||||
}
|
||||
queryString = qs.stringify(query);
|
||||
options.url += '?' + queryString;
|
||||
if (options.url.substr(options.url.length - 1) !== '?') {
|
||||
options.url += '&';
|
||||
}
|
||||
options.url += `signature=${this._sign(queryString)}`;
|
||||
} else {
|
||||
queryString = qs.stringify(query);
|
||||
if (queryString) {
|
||||
options.url += '?' + queryString;
|
||||
}
|
||||
}
|
||||
if (security === 'API-KEY' || security === 'SIGNED') {
|
||||
options.headers = { 'X-MBX-APIKEY': this.key };
|
||||
}
|
||||
if (method) {
|
||||
options.method = method;
|
||||
}
|
||||
|
||||
const action = cb => {
|
||||
request(options, (err, response, body) => {
|
||||
let payload;
|
||||
try {
|
||||
payload = JSON.parse(body);
|
||||
} catch (e) {
|
||||
payload = body;
|
||||
}
|
||||
if (err) {
|
||||
cb(err, payload);
|
||||
} else if (response.statusCode < 200 || response.statusCode > 299) {
|
||||
/*
|
||||
* If we get a response that the timestamp is ahead of the server,
|
||||
* calculate the drift and then attempt the request again
|
||||
*/
|
||||
if (response.statusCode === 400 && payload.code === -1021 &&
|
||||
this.handleDrift && attempt === 0) {
|
||||
|
||||
this.calculateDrift()
|
||||
.then(() => {
|
||||
query.timestamp = this._getTime() + this._drift;
|
||||
return this._makeRequest(query, cb, route, security, method,
|
||||
++attempt);
|
||||
});
|
||||
} else {
|
||||
cb(new Error(`Response code ${response.statusCode}`), payload);
|
||||
}
|
||||
} else {
|
||||
if (_.isArray(payload)) {
|
||||
payload = _.map(payload, (item) => {
|
||||
return this._doBeautifications(item, type);
|
||||
});
|
||||
} else {
|
||||
payload = this._doBeautifications(payload);
|
||||
}
|
||||
cb(err, payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
if (callback) {
|
||||
action(callback);
|
||||
} else {
|
||||
return new Promise((resolve, reject) => action((err, payload) => {
|
||||
if (err) {
|
||||
if (payload === undefined) {
|
||||
reject(err);
|
||||
} else {
|
||||
reject(payload);
|
||||
}
|
||||
} else {
|
||||
resolve(payload);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
_doBeautifications(response, route) {
|
||||
if (this.disableBeautification) {
|
||||
return response;
|
||||
}
|
||||
return this._beautifier.beautify(response, route);
|
||||
}
|
||||
|
||||
_sign(queryString) {
|
||||
return crypto.createHmac('sha256', this.secret)
|
||||
.update(queryString)
|
||||
.digest('hex');
|
||||
}
|
||||
|
||||
_setTimestamp(query) {
|
||||
if (!query.timestamp) {
|
||||
query.timestamp = this._getTime() + this._drift;
|
||||
}
|
||||
}
|
||||
|
||||
_getTime() {
|
||||
return new Date().getTime();
|
||||
}
|
||||
|
||||
calculateDrift() {
|
||||
const systemTime = this._getTime();
|
||||
return this.time()
|
||||
.then((response) => {
|
||||
// Calculate the approximate trip time from here to binance
|
||||
const transitTime = parseInt((this._getTime() - systemTime) / 2);
|
||||
this._drift = response.serverTime - (systemTime + transitTime);
|
||||
});
|
||||
}
|
||||
|
||||
startTimeSync(interval = 300000, onRecalculateDriftCb) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// If there's already an interval running, clear it and reset values
|
||||
if (this._syncInterval !== 0) {
|
||||
this.endTimeSync();
|
||||
return resolve();
|
||||
}
|
||||
|
||||
// Calculate initial drift value and setup interval to periodically update it
|
||||
this.calculateDrift()
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
|
||||
this._syncInterval = setInterval(() => {
|
||||
const promise = this.calculateDrift();
|
||||
|
||||
if (_.isFunction(onRecalculateDriftCb)) {
|
||||
onRecalculateDriftCb(promise);
|
||||
}
|
||||
}, interval);
|
||||
});
|
||||
}
|
||||
|
||||
endTimeSync() {
|
||||
clearInterval(this._syncInterval);
|
||||
this._drift = 0;
|
||||
this._syncInterval = 0;
|
||||
}
|
||||
|
||||
// Public APIs
|
||||
ping(callback) {
|
||||
return this._makeRequest({}, callback, 'api/v1/ping');
|
||||
}
|
||||
|
||||
time(callback) {
|
||||
return this._makeRequest({}, callback, 'api/v1/time');
|
||||
}
|
||||
|
||||
depth(query = {}, callback) {
|
||||
if (_.isString(query)) {
|
||||
query = { symbol: query };
|
||||
}
|
||||
|
||||
return this._makeRequest(query, callback, 'api/v1/depth');
|
||||
}
|
||||
|
||||
trades(query = {}, callback) {
|
||||
if (_.isString(query)) {
|
||||
query = { symbol: query };
|
||||
}
|
||||
|
||||
return this._makeRequest(query, callback, 'api/v1/trades');
|
||||
}
|
||||
|
||||
historicalTrades(query = {}, callback) {
|
||||
if (_.isString(query)) {
|
||||
query = { symbol: query };
|
||||
}
|
||||
|
||||
return this._makeRequest(query, callback, 'api/v1/historicalTrades', 'API-KEY');
|
||||
}
|
||||
|
||||
aggTrades(query = {}, callback) {
|
||||
if (_.isString(query)) {
|
||||
query = { symbol: query };
|
||||
}
|
||||
|
||||
return this._makeRequest(query, callback, 'api/v1/aggTrades');
|
||||
}
|
||||
|
||||
exchangeInfo(callback) {
|
||||
return this._makeRequest({}, callback, 'api/v1/exchangeInfo');
|
||||
}
|
||||
|
||||
klines(query = {}, callback) {
|
||||
return this._makeRequest(query, callback, 'api/v1/klines');
|
||||
}
|
||||
|
||||
ticker24hr(query = {}, callback) {
|
||||
if (_.isString(query)) {
|
||||
query = { symbol: query };
|
||||
}
|
||||
|
||||
return this._makeRequest(query, callback, 'api/v1/ticker/24hr');
|
||||
}
|
||||
|
||||
tickerPrice(query = {}, callback) {
|
||||
if (_.isString(query)) {
|
||||
query = { symbol: query };
|
||||
}
|
||||
|
||||
return this._makeRequest(query, callback, 'api/v3/ticker/price');
|
||||
}
|
||||
|
||||
bookTicker(query = {}, callback) {
|
||||
if (_.isString(query)) {
|
||||
query = { symbol: query };
|
||||
}
|
||||
|
||||
return this._makeRequest(query, callback, 'api/v3/ticker/bookTicker');
|
||||
}
|
||||
|
||||
allBookTickers(callback) {
|
||||
return this._makeRequest({}, callback, 'api/v1/ticker/allBookTickers');
|
||||
}
|
||||
|
||||
allPrices(callback) {
|
||||
return this._makeRequest({}, callback, 'api/v1/ticker/allPrices');
|
||||
}
|
||||
|
||||
// Private APIs
|
||||
newOrder(query = {}, callback) {
|
||||
this._setTimestamp(query);
|
||||
return this._makeRequest(query, callback, 'api/v3/order', 'SIGNED', 'POST');
|
||||
}
|
||||
|
||||
testOrder(query = {}, callback) {
|
||||
this._setTimestamp(query);
|
||||
return this._makeRequest(query, callback, 'api/v3/order/test', 'SIGNED', 'POST');
|
||||
}
|
||||
|
||||
queryOrder(query = {}, callback) {
|
||||
this._setTimestamp(query);
|
||||
return this._makeRequest(query, callback, 'api/v3/order', 'SIGNED');
|
||||
}
|
||||
|
||||
cancelOrder(query = {}, callback) {
|
||||
this._setTimestamp(query);
|
||||
return this._makeRequest(query, callback, 'api/v3/order', 'SIGNED', 'DELETE');
|
||||
}
|
||||
|
||||
openOrders(query = {}, callback) {
|
||||
if (_.isString(query)) {
|
||||
query = { symbol: query };
|
||||
}
|
||||
this._setTimestamp(query);
|
||||
return this._makeRequest(query, callback, 'api/v3/openOrders', 'SIGNED');
|
||||
}
|
||||
|
||||
allOrders(query = {}, callback) {
|
||||
if (_.isString(query)) {
|
||||
query = { symbol: query };
|
||||
}
|
||||
this._setTimestamp(query);
|
||||
return this._makeRequest(query, callback, 'api/v3/allOrders', 'SIGNED');
|
||||
}
|
||||
|
||||
account(query = {}, callback) {
|
||||
if (_.isFunction(query)) {
|
||||
callback = query;
|
||||
query = {};
|
||||
}
|
||||
this._setTimestamp(query);
|
||||
return this._makeRequest(query, callback, 'api/v3/account', 'SIGNED');
|
||||
}
|
||||
|
||||
myTrades(query = {}, callback) {
|
||||
if (_.isString(query)) {
|
||||
query = { symbol: query };
|
||||
}
|
||||
this._setTimestamp(query);
|
||||
return this._makeRequest(query, callback, 'api/v3/myTrades', 'SIGNED');
|
||||
}
|
||||
|
||||
withdraw(query = {}, callback) {
|
||||
this._setTimestamp(query);
|
||||
return this._makeRequest(query, callback, 'wapi/v3/withdraw.html', 'SIGNED', 'POST');
|
||||
}
|
||||
|
||||
depositHistory(query = {}, callback) {
|
||||
if (_.isString(query)) {
|
||||
query = { asset: query };
|
||||
}
|
||||
this._setTimestamp(query);
|
||||
return this._makeRequest(query, callback, 'wapi/v3/depositHistory.html', 'SIGNED');
|
||||
}
|
||||
|
||||
withdrawHistory(query = {}, callback) {
|
||||
if (_.isString(query)) {
|
||||
query = { asset: query };
|
||||
}
|
||||
this._setTimestamp(query);
|
||||
return this._makeRequest(query, callback, 'wapi/v3/withdrawHistory.html', 'SIGNED');
|
||||
}
|
||||
|
||||
depositAddress(query = {}, callback) {
|
||||
if (_.isString(query)) {
|
||||
query = { asset: query };
|
||||
}
|
||||
this._setTimestamp(query);
|
||||
return this._makeRequest(query, callback, 'wapi/v3/depositAddress.html', 'SIGNED');
|
||||
}
|
||||
|
||||
accountStatus(callback) {
|
||||
const query = {};
|
||||
this._setTimestamp(query);
|
||||
return this._makeRequest(query, callback, 'wapi/v3/accountStatus.html', 'SIGNED');
|
||||
}
|
||||
|
||||
startUserDataStream(callback) {
|
||||
return this._makeRequest({}, callback, 'api/v1/userDataStream', 'API-KEY', 'POST');
|
||||
}
|
||||
|
||||
keepAliveUserDataStream(query = {}, callback) {
|
||||
return this._makeRequest(query, callback, 'api/v1/userDataStream', 'API-KEY', 'PUT');
|
||||
}
|
||||
|
||||
closeUserDataStream(query = {}, callback) {
|
||||
return this._makeRequest(query, callback, 'api/v1/userDataStream', 'API-KEY', 'DELETE');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BinanceRest;
|
||||
@@ -0,0 +1,360 @@
|
||||
exports.newAPIClient = function newAPIClient(pKey, pSecret, logger) {
|
||||
|
||||
/*
|
||||
/* ATENTION: This Library is used both a the cloud (AACloud) and also at the AAWeb on the server side without any modifications.
|
||||
/* There is a second version, stripped of all the inner functionality that runs at AAWeb browser side, which channels the requests to the server side one.
|
||||
*/
|
||||
|
||||
const FULL_LOG = true;
|
||||
const LOG_FILE_CONTENT = true;
|
||||
const MODULE_NAME = "binanceClient";
|
||||
|
||||
const retry = require('../exchangeUtils').retry;
|
||||
const includes = require('../exchangeUtils').includes;
|
||||
const _ = require('lodash');
|
||||
const Binance = require("./binance");
|
||||
const exchangeProperties = require('./poloniexProperties.json');
|
||||
|
||||
const recoverableErrors = [
|
||||
'SOCKETTIMEDOUT',
|
||||
'TIMEDOUT',
|
||||
'CONNRESET',
|
||||
'CONNREFUSED',
|
||||
'NOTFOUND',
|
||||
'Error -1021',
|
||||
'Response code 429',
|
||||
'Response code 5',
|
||||
'ETIMEDOUT'
|
||||
];
|
||||
|
||||
let API = new Binance({
|
||||
key: pKey,
|
||||
secret: pSecret,
|
||||
timeout: 15000,
|
||||
recvWindow: 60000,
|
||||
disableBeautification: false,
|
||||
handleDrift: true,
|
||||
});
|
||||
|
||||
let thisObject = {
|
||||
getTicker: getTicker,
|
||||
getOpenPositions: getOpenPositions,
|
||||
getExecutedTrades: getExecutedTrades,
|
||||
buy: buy,
|
||||
sell: sell,
|
||||
movePosition: movePosition,
|
||||
getPublicTradeHistory: getPublicTradeHistory,
|
||||
getExchangeProperties: getExchangeProperties,
|
||||
isValidPrice: isValidPrice,
|
||||
isValidLot: isValidLot
|
||||
};
|
||||
|
||||
return thisObject;
|
||||
|
||||
function joinCurrencies(currencyA, currencyB) {
|
||||
// If only one arg, then return the first
|
||||
if (typeof currencyB !== 'string') {
|
||||
return currencyA;
|
||||
}
|
||||
|
||||
return currencyB + '' + currencyA;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the properties for this exchange
|
||||
*/
|
||||
function getExchangeProperties() {
|
||||
return exchangeProperties;
|
||||
}
|
||||
|
||||
function truncDecimals(pFloatValue) {
|
||||
|
||||
let decimals = 6;
|
||||
|
||||
return parseFloat(parseFloat(pFloatValue).toFixed(decimals));
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the price for a given pair of assets.
|
||||
* The object returned is an array of trades:
|
||||
* ticker = {
|
||||
* bid, Number
|
||||
* ask, Number
|
||||
* last Number
|
||||
* };
|
||||
*/
|
||||
function getTicker(pMarket, callBack) {
|
||||
|
||||
let market = joinCurrencies(pMarket.assetA, pMarket.assetB);
|
||||
const handle = (err, response) => {
|
||||
let ticker;
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
ticker = {
|
||||
bid: Number(response.bidPrice),
|
||||
ask: Number(response.askPrice),
|
||||
last: Number(response.lastPrice)
|
||||
};
|
||||
}
|
||||
callBack(err, ticker);
|
||||
};
|
||||
|
||||
const fetch = next => API.ticker24hr(market, analizeResponse(next));
|
||||
|
||||
retry(null, fetch, handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the open positions ath the exchange for a given market and user account.
|
||||
* The object returned is an array of positions:
|
||||
* position = {
|
||||
* id,
|
||||
* type,
|
||||
* rate, Number
|
||||
* amountA, Number
|
||||
* amountB, Number
|
||||
* fee, (not required) Number
|
||||
* datetime
|
||||
* };
|
||||
*/
|
||||
function getOpenPositions(pMarket, callBack) {
|
||||
|
||||
let market = joinCurrencies(pMarket.assetA, pMarket.assetB);
|
||||
const handle = (err, response) => {
|
||||
let exchangePositions = [];
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
let openPosition = {
|
||||
id: response[i].orderId,
|
||||
type: response[i].side.toLowerCase(),
|
||||
rate: Number(response[i].price),
|
||||
amountA: truncDecimals(response[i].origQty * response[i].price),
|
||||
amountB: Number(response[i].origQty),
|
||||
date: (new Date(response[i].time)).valueOf()
|
||||
};
|
||||
exchangePositions.push(openPosition);
|
||||
}
|
||||
}
|
||||
callBack(err, exchangePositions);
|
||||
};
|
||||
|
||||
const fetch = next => API.openOrders(market, analizeResponse(next));
|
||||
|
||||
retry(null, fetch, handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the trades for a given order number.
|
||||
* The object returned is an array of positions:
|
||||
* position = {
|
||||
* id,
|
||||
* type,
|
||||
* rate, Number
|
||||
* amountA, Number
|
||||
* amountB, Number
|
||||
* fee, Number
|
||||
* datetime
|
||||
* };
|
||||
*/
|
||||
function getExecutedTrades(pPositionId, callBack) {
|
||||
|
||||
let market = joinCurrencies(global.MARKET.assetA, global.MARKET.assetB);
|
||||
const handle = (err, response) => {
|
||||
let exchangeTrades = [];
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
if (response[i].orderId === pPositionId) {
|
||||
let type = "sell"
|
||||
if (response[i].isBuyer === true) {
|
||||
type = "buy";
|
||||
}
|
||||
let trade = {
|
||||
id: response[i].orderId,
|
||||
type: type,
|
||||
rate: Number(response[i].price),
|
||||
amountA: truncDecimals(response[i].qty * response[i].price),
|
||||
amountB: Number(response[i].qty),
|
||||
fee: Number(response[i].commission),
|
||||
date: (new Date(response[i].time)).valueOf()
|
||||
};
|
||||
exchangeTrades.push(trade);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
callBack(err, exchangeTrades);
|
||||
};
|
||||
|
||||
const fetch = next => API.myTrades(market, analizeResponse(next));
|
||||
|
||||
retry(null, fetch, handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates a new buy order.
|
||||
* The orderNumber is returned.
|
||||
*/
|
||||
function buy(pCurrencyA, pCurrencyB, pRate, pAmount, callBack) {
|
||||
|
||||
var parameters = {
|
||||
symbol: joinCurrencies(pCurrencyA, pCurrencyB),
|
||||
side: 'BUY',
|
||||
type: 'LIMIT',
|
||||
quantity: pAmount,
|
||||
price: pRate,
|
||||
timeInForce: 'GTC'
|
||||
};
|
||||
const handle = (err, response) => {
|
||||
let orderNumber;
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
orderNumber = response.orderId;
|
||||
}
|
||||
callBack(err, orderNumber);
|
||||
};
|
||||
|
||||
const fetch = next => API.newOrder(parameters, analizeResponse(next));
|
||||
|
||||
retry(null, fetch, handle);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates a new sell order.
|
||||
* The orderNumber is returned. String
|
||||
*/
|
||||
function sell(pCurrencyA, pCurrencyB, pRate, pAmount, callBack) {
|
||||
|
||||
var parameters = {
|
||||
symbol: joinCurrencies(pCurrencyA, pCurrencyB),
|
||||
side: 'SELL',
|
||||
type: 'LIMIT',
|
||||
quantity: pAmount,
|
||||
price: pRate,
|
||||
timeInForce: 'GTC'
|
||||
};
|
||||
const handle = (err, response) => {
|
||||
let orderNumber;
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
orderNumber = response.orderId;
|
||||
}
|
||||
callBack(err, orderNumber);
|
||||
};
|
||||
|
||||
const fetch = next => API.newOrder(parameters, analizeResponse(next));
|
||||
|
||||
retry(null, fetch, handle);
|
||||
}
|
||||
|
||||
/* //TODO Complete partial orders
|
||||
* Move an existing position to the new rate.
|
||||
* The new orderNumber is returned. String
|
||||
*/
|
||||
function movePosition(pPosition, pNewRate, pNewAmountB, callBack) {
|
||||
|
||||
var parameters = {
|
||||
symbol: joinCurrencies(global.MARKET.assetA, global.MARKET.assetB),
|
||||
orderId: pPosition.id
|
||||
};
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] moveOrder -> Step1 - Cancelling Position: " + JSON.stringify(pPosition)); }
|
||||
|
||||
const handle = (err, response) => {
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] moveOrder -> Step1 Complete - Position Cancelled. Proceeding to put the new position."); }
|
||||
|
||||
if (position.type === "buy")
|
||||
thisObject.buy(global.MARKET.assetA, global.MARKET.assetB, pNewRate, pNewAmountB, callBack);
|
||||
else
|
||||
thisObject.sell(global.MARKET.assetA, global.MARKET.assetB, pNewRate, pNewAmountB, callBack);
|
||||
} else {
|
||||
callBack(err);
|
||||
}
|
||||
};
|
||||
|
||||
const fetch = next => API.cancelOrder(parameters, analizeResponse(next));
|
||||
|
||||
retry(null, fetch, handle);
|
||||
}
|
||||
|
||||
/* //TODO Pending Review
|
||||
* Returns all the trade history from the Exchange since startTime to endTime orderd by tradeId.
|
||||
* It's possible that the exchange doesn't support this method.
|
||||
* The object returned is an array of trades:
|
||||
* trade = {
|
||||
* tradeIdAtExchange,
|
||||
* marketIdAtExchange,
|
||||
* type,
|
||||
* rate, Number
|
||||
* amountA, Number
|
||||
* amountB, Number
|
||||
* datetime
|
||||
* };
|
||||
*/
|
||||
function getPublicTradeHistory(assetA, assetB, startTime, endTime, callBack) {
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] getPublicTradeHistory -> Not implemented yet."); }
|
||||
callBack(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
|
||||
function isValidPrice(price) {
|
||||
return price >= market.minimalOrder.price;
|
||||
}
|
||||
|
||||
function isValidLot(price, amount) {
|
||||
return amount * price >= market.minimalOrder.order;
|
||||
}
|
||||
|
||||
function analizeResponse(callBack) {
|
||||
|
||||
return (exchangeErr, exchangeResponse) => {
|
||||
let error;
|
||||
|
||||
/* This function analizes the different situations we might encounter trying to access Poloniex and returns appropiate standard errors. */
|
||||
|
||||
let stringExchangeErr = JSON.stringify(exchangeErr);
|
||||
let stringExchangeResponse = JSON.stringify(exchangeResponse);
|
||||
|
||||
try {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] analizeResponse -> exchangeErr = " + stringExchangeErr); }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] analizeResponse -> exchangeResponse = " + stringExchangeResponse); }
|
||||
|
||||
if (exchangeErr) {
|
||||
error = global.DEFAULT_FAIL_RESPONSE;
|
||||
} else if (!exchangeResponse) {
|
||||
error = {
|
||||
result: global.DEFAULT_FAIL_RESPONSE.result,
|
||||
message: 'Empty response'
|
||||
};
|
||||
} else if (includes(exchangeResponse.error, ['<!DOCTYPE html>'])) {
|
||||
error = {
|
||||
result: global.DEFAULT_FAIL_RESPONSE.result,
|
||||
message: exchangeResponse
|
||||
};
|
||||
} else if (includes(exchangeResponse.error, ['Order does not exist.'])) {
|
||||
error = {
|
||||
result: global.CUSTOM_OK_RESPONSE.result,
|
||||
message: exchangeResponse.error
|
||||
};
|
||||
} else if (exchangeResponse.error) {
|
||||
error = {
|
||||
result: global.DEFAULT_FAIL_RESPONSE.result,
|
||||
message: exchangeResponse.error
|
||||
};
|
||||
}
|
||||
|
||||
if (error) {
|
||||
if (includes(error.message, recoverableErrors)) {
|
||||
error.notFatal = true;
|
||||
}
|
||||
}
|
||||
|
||||
return callBack(error, exchangeResponse);
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] analizeResponse -> err.message = " + err.message);
|
||||
return callBack(global.DEFAULT_FAIL_RESPONSE, exchangeResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,440 @@
|
||||
// Source: https://github.com/premasagar/poloniex.js
|
||||
// Documentation: https://poloniex.com/support/api/
|
||||
|
||||
module.exports = (function () {
|
||||
'use strict';
|
||||
|
||||
// Module dependencies
|
||||
var request = require('request'),
|
||||
nonce = require('nonce')();
|
||||
|
||||
// Constants
|
||||
var version = '0.0.9',
|
||||
PUBLIC_API_URL = 'https://poloniex.com/public',
|
||||
PRIVATE_API_URL = 'https://poloniex.com/tradingApi',
|
||||
USER_AGENT = 'poloniex.js ' + version;
|
||||
//USER_AGENT = 'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:26.0) Gecko/20100101 Firefox/26.0'
|
||||
|
||||
// Helper methods
|
||||
function joinCurrencies(currencyA, currencyB) {
|
||||
// If only one arg, then return the first
|
||||
if (typeof currencyB !== 'string') {
|
||||
return currencyA;
|
||||
}
|
||||
|
||||
return currencyA + '_' + currencyB;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
function Poloniex(keyVaultAPI) {
|
||||
// Generate headers signed by this user's key and secret.
|
||||
// The secret is encapsulated and never exposed
|
||||
this._getPrivateHeaders = function (parameters, next) {
|
||||
var paramString, signature;
|
||||
|
||||
// Convert to `arg1=foo&arg2=bar`
|
||||
paramString = Object.keys(parameters).map(function (param) {
|
||||
return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]);
|
||||
}).join('&');
|
||||
|
||||
keyVaultAPI.signTransaction(paramString, next);
|
||||
};
|
||||
}
|
||||
|
||||
// Currently, this fails with `Error: CERT_UNTRUSTED`
|
||||
// Poloniex.STRICT_SSL can be set to `false` to avoid this. Use with caution.
|
||||
// Will be removed in future, once this is resolved.
|
||||
Poloniex.STRICT_SSL = true;
|
||||
|
||||
// Customisable user agent string
|
||||
Poloniex.USER_AGENT = USER_AGENT;
|
||||
|
||||
// Prototype
|
||||
Poloniex.prototype = {
|
||||
constructor: Poloniex,
|
||||
|
||||
// Make an API request
|
||||
_request: function (options, callback) {
|
||||
if (!('headers' in options)) {
|
||||
options.headers = {};
|
||||
}
|
||||
|
||||
options.json = true;
|
||||
options.headers['User-Agent'] = Poloniex.USER_AGENT;
|
||||
options.strictSSL = Poloniex.STRICT_SSL;
|
||||
options.timeout = 60 * 1000;
|
||||
|
||||
request(options, function (err, response, body) {
|
||||
// Empty response
|
||||
if (!err && (typeof body === 'undefined' || body === null)) {
|
||||
err = 'Empty response';
|
||||
}
|
||||
|
||||
callback(err, body);
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
// Make a public API request
|
||||
_public: function (command, parameters, callback) {
|
||||
var options;
|
||||
|
||||
if (typeof parameters === 'function') {
|
||||
callback = parameters;
|
||||
parameters = {};
|
||||
}
|
||||
|
||||
parameters || (parameters = {});
|
||||
parameters.command = command;
|
||||
options = {
|
||||
method: 'GET',
|
||||
url: PUBLIC_API_URL,
|
||||
qs: parameters
|
||||
};
|
||||
|
||||
options.qs.command = command;
|
||||
return this._request(options, callback);
|
||||
},
|
||||
|
||||
// Make a private API request
|
||||
_private: function (command, parameters, callback) {
|
||||
var options;
|
||||
|
||||
if (typeof parameters === 'function') {
|
||||
callback = parameters;
|
||||
parameters = {};
|
||||
}
|
||||
|
||||
parameters || (parameters = {});
|
||||
parameters.command = command;
|
||||
parameters.nonce = nonce();
|
||||
|
||||
options = {
|
||||
method: 'POST',
|
||||
url: PRIVATE_API_URL,
|
||||
form: parameters
|
||||
};
|
||||
|
||||
let headers = this._getPrivateHeaders(parameters, next)
|
||||
|
||||
let thisObject = this
|
||||
|
||||
function next(pHeaders, error) {
|
||||
if (!error) {
|
||||
options.headers = pHeaders;
|
||||
return thisObject._request(options, callback);
|
||||
} else {
|
||||
callback(error);
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
/////
|
||||
// PUBLIC METHODS
|
||||
|
||||
returnTicker: function (callback) {
|
||||
return this._public('returnTicker', callback);
|
||||
},
|
||||
|
||||
return24hVolume: function (callback) {
|
||||
return this._public('return24hVolume', callback);
|
||||
},
|
||||
|
||||
returnOrderBook: function (currencyA, currencyB, callback) {
|
||||
var currencyPair;
|
||||
|
||||
if (typeof currencyB === 'function') {
|
||||
currencyPair = currencyA;
|
||||
callback = currencyB;
|
||||
currencyB = null;
|
||||
}
|
||||
|
||||
else {
|
||||
currencyPair = joinCurrencies(currencyA, currencyB);
|
||||
}
|
||||
|
||||
var parameters = {
|
||||
currencyPair: currencyPair
|
||||
};
|
||||
|
||||
return this._public('returnOrderBook', parameters, callback);
|
||||
},
|
||||
|
||||
returnChartData: function (currencyA, currencyB, period, start, end, callback) {
|
||||
var parameters = {
|
||||
currencyPair: joinCurrencies(currencyA, currencyB),
|
||||
period: period,
|
||||
start: start,
|
||||
end: end
|
||||
};
|
||||
|
||||
return this._public('returnChartData', parameters, callback);
|
||||
},
|
||||
|
||||
returnCurrencies: function (callback) {
|
||||
return this._public('returnCurrencies', callback);
|
||||
},
|
||||
|
||||
returnLoanOrders: function (currency, callback) {
|
||||
return this._public('returnLoanOrders', { currency: currency }, callback);
|
||||
},
|
||||
|
||||
returnTradeHistory: function (currencyA, currencyB, start, end, callback) {
|
||||
if (arguments.length < 5) {
|
||||
callback = start;
|
||||
start = Date.now() / 1000 - 60 * 60;
|
||||
end = Date.now() / 1000 + 60 * 60; // Some buffer in case of client/server time out of sync.
|
||||
}
|
||||
|
||||
var parameters = {
|
||||
currencyPair: joinCurrencies(currencyA, currencyB),
|
||||
start: start,
|
||||
end: end
|
||||
};
|
||||
|
||||
return this._public('returnTradeHistory', parameters, callback);
|
||||
},
|
||||
|
||||
/////
|
||||
// PRIVATE METHODS
|
||||
|
||||
returnBalances: function (callback) {
|
||||
return this._private('returnBalances', {}, callback);
|
||||
},
|
||||
|
||||
returnCompleteBalances: function (account, callback) {
|
||||
var parameters = {};
|
||||
|
||||
if (typeof account === 'function') {
|
||||
callback = account;
|
||||
}
|
||||
|
||||
else if (typeof account === 'string' && !!account) {
|
||||
parameters.account = account;
|
||||
}
|
||||
|
||||
return this._private('returnCompleteBalances', parameters, callback);
|
||||
},
|
||||
|
||||
returnDepositAddresses: function (callback) {
|
||||
return this._private('returnDepositAddresses', {}, callback);
|
||||
},
|
||||
|
||||
generateNewAddress: function (currency, callback) {
|
||||
return this._private('generateNewAddress', { currency: currency }, callback);
|
||||
},
|
||||
|
||||
returnDepositsWithdrawals: function (start, end, callback) {
|
||||
return this._private('returnDepositsWithdrawals', { start: start, end: end }, callback);
|
||||
},
|
||||
|
||||
// can be called with `returnOpenOrders('all', callback)`
|
||||
returnOpenOrders: function (currencyA, currencyB, callback) {
|
||||
var currencyPair;
|
||||
|
||||
if (typeof currencyB === 'function') {
|
||||
currencyPair = currencyA;
|
||||
callback = currencyB;
|
||||
currencyB = null;
|
||||
}
|
||||
|
||||
else {
|
||||
currencyPair = joinCurrencies(currencyA, currencyB);
|
||||
}
|
||||
|
||||
var parameters = {
|
||||
currencyPair: currencyPair
|
||||
};
|
||||
|
||||
return this._private('returnOpenOrders', parameters, callback);
|
||||
},
|
||||
|
||||
returnOrderTrades: function (orderNumber, callback) {
|
||||
var parameters = {
|
||||
orderNumber: orderNumber
|
||||
};
|
||||
|
||||
return this._private('returnOrderTrades', parameters, callback);
|
||||
},
|
||||
|
||||
buy: function (currencyA, currencyB, rate, amount, callback) {
|
||||
var parameters = {
|
||||
currencyPair: joinCurrencies(currencyA, currencyB),
|
||||
rate: rate,
|
||||
amount: amount
|
||||
};
|
||||
|
||||
return this._private('buy', parameters, callback);
|
||||
},
|
||||
|
||||
sell: function (currencyA, currencyB, rate, amount, callback) {
|
||||
var parameters = {
|
||||
currencyPair: joinCurrencies(currencyA, currencyB),
|
||||
rate: rate,
|
||||
amount: amount
|
||||
};
|
||||
|
||||
return this._private('sell', parameters, callback);
|
||||
},
|
||||
|
||||
cancelOrder: function (currencyA, currencyB, orderNumber, callback) {
|
||||
var parameters = {
|
||||
currencyPair: joinCurrencies(currencyA, currencyB),
|
||||
orderNumber: orderNumber
|
||||
};
|
||||
|
||||
return this._private('cancelOrder', parameters, callback);
|
||||
},
|
||||
|
||||
moveOrder: function (orderNumber, rate, amount, callback) {
|
||||
var parameters = {
|
||||
orderNumber: orderNumber,
|
||||
rate: rate,
|
||||
amount: amount ? amount : null
|
||||
};
|
||||
|
||||
return this._private('moveOrder', parameters, callback);
|
||||
},
|
||||
|
||||
withdraw: function (currency, amount, address, callback) {
|
||||
var parameters = {
|
||||
currency: currency,
|
||||
amount: amount,
|
||||
address: address
|
||||
};
|
||||
|
||||
return this._private('withdraw', parameters, callback);
|
||||
},
|
||||
|
||||
returnFeeInfo: function (callback) {
|
||||
return this._private('returnFeeInfo', {}, callback);
|
||||
},
|
||||
|
||||
returnAvailableAccountBalances: function (account, callback) {
|
||||
var parameters = {};
|
||||
|
||||
if (typeof account === 'function') {
|
||||
callback = account;
|
||||
}
|
||||
|
||||
else if (typeof account === 'string' && !!account) {
|
||||
parameters.account = account;
|
||||
}
|
||||
|
||||
return this._private('returnAvailableAccountBalances', parameters, callback);
|
||||
},
|
||||
|
||||
returnTradableBalances: function (callback) {
|
||||
return this._private('returnTradableBalances', {}, callback);
|
||||
},
|
||||
|
||||
transferBalance: function (currency, amount, fromAccount, toAccount, callback) {
|
||||
var parameters = {
|
||||
currency: currency,
|
||||
amount: amount,
|
||||
fromAccount: fromAccount,
|
||||
toAccount: toAccount
|
||||
};
|
||||
|
||||
return this._private('transferBalance', parameters, callback);
|
||||
},
|
||||
|
||||
returnMarginAccountSummary: function (callback) {
|
||||
return this._private('returnMarginAccountSummary', {}, callback);
|
||||
},
|
||||
|
||||
marginBuy: function (currencyA, currencyB, rate, amount, lendingRate, callback) {
|
||||
var parameters = {
|
||||
currencyPair: joinCurrencies(currencyA, currencyB),
|
||||
rate: rate,
|
||||
amount: amount,
|
||||
lendingRate: lendingRate ? lendingRate : null
|
||||
};
|
||||
|
||||
return this._private('marginBuy', parameters, callback);
|
||||
},
|
||||
|
||||
marginSell: function (currencyA, currencyB, rate, amount, lendingRate, callback) {
|
||||
var parameters = {
|
||||
currencyPair: joinCurrencies(currencyA, currencyB),
|
||||
rate: rate,
|
||||
amount: amount,
|
||||
lendingRate: lendingRate ? lendingRate : null
|
||||
};
|
||||
|
||||
return this._private('marginSell', parameters, callback);
|
||||
},
|
||||
|
||||
getMarginPosition: function (currencyA, currencyB, callback) {
|
||||
var parameters = {
|
||||
currencyPair: joinCurrencies(currencyA, currencyB)
|
||||
};
|
||||
|
||||
return this._private('getMarginPosition', parameters, callback);
|
||||
},
|
||||
|
||||
closeMarginPosition: function (currencyA, currencyB, callback) {
|
||||
var parameters = {
|
||||
currencyPair: joinCurrencies(currencyA, currencyB)
|
||||
};
|
||||
|
||||
return this._private('closeMarginPosition', parameters, callback);
|
||||
},
|
||||
|
||||
createLoanOffer: function (currency, amount, duration, autoRenew, lendingRate, callback) {
|
||||
var parameters = {
|
||||
currency: currency,
|
||||
amount: amount,
|
||||
duration: duration,
|
||||
autoRenew: autoRenew,
|
||||
lendingRate: lendingRate
|
||||
};
|
||||
|
||||
return this._private('createLoanOffer', parameters, callback);
|
||||
},
|
||||
|
||||
cancelLoanOffer: function (orderNumber, callback) {
|
||||
var parameters = {
|
||||
orderNumber: orderNumber
|
||||
};
|
||||
|
||||
return this._private('cancelLoanOffer', parameters, callback);
|
||||
},
|
||||
|
||||
returnOpenLoanOffers: function (callback) {
|
||||
return this._private('returnOpenLoanOffers', {}, callback);
|
||||
},
|
||||
|
||||
returnActiveLoans: function (callback) {
|
||||
return this._private('returnActiveLoans', {}, callback);
|
||||
},
|
||||
|
||||
returnLendingHistory: function (start, end, limit, callback) {
|
||||
var parameters = {
|
||||
start: start,
|
||||
end: end,
|
||||
limit: limit
|
||||
};
|
||||
|
||||
return this._private('returnLendingHistory', parameters, callback);
|
||||
},
|
||||
|
||||
toggleAutoRenew: function (orderNumber, callback) {
|
||||
return this._private('toggleAutoRenew', { orderNumber: orderNumber }, callback);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Backwards Compatibility
|
||||
Poloniex.prototype.getTicker = Poloniex.prototype.returnTicker;
|
||||
Poloniex.prototype.get24hVolume = Poloniex.prototype.return24hVolume;
|
||||
Poloniex.prototype.getOrderBook = Poloniex.prototype.returnOrderBook;
|
||||
Poloniex.prototype.getTradeHistory = Poloniex.prototype.returnChartData;
|
||||
Poloniex.prototype.myBalances = Poloniex.prototype.returnBalances;
|
||||
Poloniex.prototype.myOpenOrders = Poloniex.prototype.returnOpenOrders;
|
||||
Poloniex.prototype.myTradeHistory = Poloniex.prototype.returnTradeHistory;
|
||||
|
||||
return Poloniex;
|
||||
})();
|
||||
@@ -0,0 +1,353 @@
|
||||
exports.newAPIClient = function newAPIClient(keyVaultAPI, logger) {
|
||||
|
||||
/*
|
||||
/* ATENTION: This Library is used both a the cloud (AACloud) and also at the AAWeb on the server side without any modifications.
|
||||
/* There is a second version, stripped of all the inner functionality that runs at AAWeb browser side, which channels the requests to the server side one.
|
||||
*/
|
||||
|
||||
const FULL_LOG = true;
|
||||
const LOG_FILE_CONTENT = true;
|
||||
const MODULE_NAME = "poloniexClient";
|
||||
|
||||
const retry = require('../exchangeUtils').retry;
|
||||
const includes = require('../exchangeUtils').includes;
|
||||
const _ = require('lodash');
|
||||
const Poloniex = require("./poloniex");
|
||||
const exchangeProperties = require('./poloniexProperties.json');
|
||||
|
||||
const recoverableErrors = [
|
||||
'SOCKETTIMEDOUT',
|
||||
'TIMEDOUT',
|
||||
'CONNRESET',
|
||||
'CONNREFUSED',
|
||||
'NOTFOUND',
|
||||
'429',
|
||||
'522',
|
||||
'504',
|
||||
'503',
|
||||
'500',
|
||||
'502',
|
||||
'Empty response',
|
||||
'Please try again in a few minutes.',
|
||||
'Nonce must be greater than',
|
||||
'Internal error. Please try again.',
|
||||
'Connection timed out. Please try again.',
|
||||
'ENOTFOUND',
|
||||
'ECONNREFUSED',
|
||||
'CONNREFUSED',
|
||||
'NOTFOUND',
|
||||
'ESOCKETTIMEDOUT',
|
||||
'ECONNRESET',
|
||||
'Connection timed out',
|
||||
'Connection Error',
|
||||
'Order not found, or you are not the person who placed it.',
|
||||
'ETIMEDOUT'
|
||||
];
|
||||
|
||||
let API = new Poloniex(keyVaultAPI);
|
||||
|
||||
let thisObject = {
|
||||
getTicker: getTicker,
|
||||
getOpenPositions: getOpenPositions,
|
||||
getExecutedTrades: getExecutedTrades,
|
||||
buy: buy,
|
||||
sell: sell,
|
||||
movePosition: movePosition,
|
||||
getPublicTradeHistory: getPublicTradeHistory,
|
||||
getExchangeProperties: getExchangeProperties,
|
||||
isValidLot: isValidLot
|
||||
};
|
||||
|
||||
return thisObject;
|
||||
|
||||
function joinCurrencies(currencyA, currencyB) {
|
||||
// If only one arg, then return the first
|
||||
if (typeof currencyB !== 'string') {
|
||||
return currencyA;
|
||||
}
|
||||
|
||||
return currencyA + '_' + currencyB;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Returns the properties for this exchange
|
||||
*/
|
||||
function getExchangeProperties() {
|
||||
return exchangeProperties;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the price for a given pair of assets.
|
||||
* The object returned is an array of trades:
|
||||
* ticker = {
|
||||
* bid, Number
|
||||
* ask, Number
|
||||
* last Number
|
||||
* };
|
||||
*/
|
||||
function getTicker(pMarket, callBack) {
|
||||
|
||||
const handle = (err, response) => {
|
||||
let ticker;
|
||||
let market = joinCurrencies(pMarket.assetA, pMarket.assetB);
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
ticker = {
|
||||
bid: Number(response[market].highestBid),
|
||||
ask: Number(response[market].lowestAsk),
|
||||
last: Number(response[market].last)
|
||||
};
|
||||
}
|
||||
callBack(err, ticker);
|
||||
};
|
||||
|
||||
const fetch = next => API.getTicker(analizeResponse(next));
|
||||
|
||||
retry(null, fetch, handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the open positions ath the exchange for a given market and user account.
|
||||
* The object returned is an array of positions:
|
||||
* position = {
|
||||
* id,
|
||||
* type,
|
||||
* rate, Number
|
||||
* amountA, Number
|
||||
* amountB, Number
|
||||
* fee, (not required) Number
|
||||
* datetime
|
||||
* };
|
||||
*/
|
||||
function getOpenPositions(pMarket, callBack) {
|
||||
|
||||
const handle = (err, response) => {
|
||||
let exchangePositions = [];
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
let openPosition = {
|
||||
id: response[i].orderNumber,
|
||||
type: response[i].type,
|
||||
rate: Number(response[i].rate),
|
||||
amountA: Number(response[i].total),
|
||||
amountB: Number(response[i].amount),
|
||||
date: (new Date(response[i].time)).valueOf()
|
||||
};
|
||||
exchangePositions.push(openPosition);
|
||||
}
|
||||
}
|
||||
callBack(err, exchangePositions);
|
||||
};
|
||||
|
||||
const fetch = next => API.returnOpenOrders(pMarket.assetA, pMarket.assetB, analizeResponse(next));
|
||||
|
||||
retry(null, fetch, handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the trades for a given order number.
|
||||
* The object returned is an array of positions:
|
||||
* position = {
|
||||
* id,
|
||||
* type,
|
||||
* rate, Number
|
||||
* amountA, Number
|
||||
* amountB, Number
|
||||
* fee, Number
|
||||
* datetime
|
||||
* };
|
||||
*/
|
||||
function getExecutedTrades(pPositionId, callBack) {
|
||||
|
||||
const handle = (err, response) => {
|
||||
let trades = [];
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
let trade = {
|
||||
id: response[i].tradeID,
|
||||
type: response[i].type,
|
||||
rate: Number(response[i].rate),
|
||||
amountA: Number(response[i].total),
|
||||
amountB: Number(response[i].amount),
|
||||
fee: Number(response[i].fee),
|
||||
date: (new Date(response[i].date)).valueOf()
|
||||
}
|
||||
trades.push(trade);
|
||||
}
|
||||
} else if (err.result === global.CUSTOM_OK_RESPONSE.result) {
|
||||
// No trades found at the exchange for the position, but returning empy trades object.
|
||||
err = global.DEFAULT_OK_RESPONSE;
|
||||
}
|
||||
callBack(err, trades);
|
||||
};
|
||||
|
||||
const fetch = next => API.returnOrderTrades(pPositionId, analizeResponse(next));
|
||||
|
||||
retry(null, fetch, handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates a new buy order.
|
||||
* The orderNumber is returned. String
|
||||
*/
|
||||
function buy(pCurrencyA, pCurrencyB, pRate, pAmount, callBack) {
|
||||
|
||||
const handle = (err, response) => {
|
||||
let orderNumber;
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
orderNumber = response.orderNumber;
|
||||
}
|
||||
callBack(err, orderNumber);
|
||||
};
|
||||
|
||||
const fetch = next => API.buy(pCurrencyA, pCurrencyB, pRate, pAmount, analizeResponse(next));
|
||||
|
||||
retry(null, fetch, handle);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates a new sell order.
|
||||
* The orderNumber is returned. String
|
||||
*/
|
||||
function sell(pCurrencyA, pCurrencyB, pRate, pAmount, callBack) {
|
||||
|
||||
const handle = (err, response) => {
|
||||
let orderNumber;
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
orderNumber = response.orderNumber;
|
||||
}
|
||||
callBack(err, orderNumber);
|
||||
};
|
||||
|
||||
const fetch = next => API.sell(pCurrencyA, pCurrencyB, pRate, pAmount, analizeResponse(next));
|
||||
|
||||
retry(null, fetch, handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Move an existing position to the new rate.
|
||||
* The orderNumber is returned. String
|
||||
*/
|
||||
function movePosition(pPosition, pNewRate, pNewAmountB, callBack) {
|
||||
|
||||
const handle = (err, response) => {
|
||||
let orderNumber;
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
orderNumber = response.orderNumber;
|
||||
}
|
||||
callBack(err, orderNumber);
|
||||
};
|
||||
|
||||
const fetch = next => API.moveOrder(pPosition.id, pNewRate, pNewAmountB, analizeResponse(next));
|
||||
|
||||
retry(null, fetch, handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns all the trade history from the Exchange since startTime to endTime orderd by tradeId.
|
||||
* It's possible that the exchange doesn't support this method.
|
||||
* The object returned is an array of trades:
|
||||
* trade = {
|
||||
* tradeID, String
|
||||
* globalTradeID, String
|
||||
* type, String
|
||||
* rate, Number
|
||||
* amountA, Number
|
||||
* amountB, Number
|
||||
* date Date
|
||||
* };
|
||||
*/
|
||||
function getPublicTradeHistory(assetA, assetB, startTime, endTime, callBack) {
|
||||
|
||||
const handle = (err, response) => {
|
||||
let trades = [];
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
let trade = {
|
||||
tradeID: response[i].tradeID,
|
||||
globalTradeID: response[i].globalTradeID,
|
||||
type: response[i].type,
|
||||
rate: Number(response[i].rate),
|
||||
total: Number(response[i].total),
|
||||
amount: Number(response[i].amount),
|
||||
date: new Date(response[i].date)
|
||||
}
|
||||
trades.push(trade);
|
||||
}
|
||||
}
|
||||
callBack(err, trades);
|
||||
};
|
||||
|
||||
const fetch = next => API.returnTradeHistory(assetA, assetB, startTime, endTime, analizeResponse(next));
|
||||
|
||||
retry(null, fetch, handle);
|
||||
}
|
||||
|
||||
function isValidLot(price, amount) {
|
||||
return amount * price >= 0.0001;
|
||||
}
|
||||
|
||||
function analizeResponse(callBack) {
|
||||
|
||||
return (exchangeErr, exchangeResponse) => {
|
||||
let error;
|
||||
|
||||
/* This function analizes the different situations we might encounter trying to access Poloniex and returns appropiate standard errors. */
|
||||
|
||||
let stringExchangeErr = JSON.stringify(exchangeErr);
|
||||
let stringExchangeResponse = JSON.stringify(exchangeResponse);
|
||||
|
||||
try {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] analizeResponse -> exchangeErr = " + stringExchangeErr); }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] analizeResponse -> exchangeResponse = " + stringExchangeResponse); }
|
||||
|
||||
if (exchangeErr) {
|
||||
error = global.DEFAULT_FAIL_RESPONSE;
|
||||
|
||||
if (includes(exchangeErr.code, recoverableErrors)) {
|
||||
error.notFatal = true;
|
||||
}
|
||||
} else if (!exchangeResponse || exchangeResponse === undefined) {
|
||||
error = {
|
||||
result: global.DEFAULT_FAIL_RESPONSE.result,
|
||||
message: 'Empty response',
|
||||
notFatal: true
|
||||
};
|
||||
} else if (includes(exchangeResponse.error, ['Please complete the security check to proceed.'])) {
|
||||
error = {
|
||||
result: global.DEFAULT_FAIL_RESPONSE.result,
|
||||
message: 'The IP has been flagged by CloudFlare.'
|
||||
};
|
||||
} else if (includes(exchangeResponse.error, ['<!DOCTYPE html>'])) {
|
||||
error = {
|
||||
result: global.DEFAULT_FAIL_RESPONSE.result,
|
||||
message: exchangeResponse
|
||||
};
|
||||
} else if (includes(exchangeResponse.error, ['Not enough'])) {
|
||||
error = {
|
||||
result: global.DEFAULT_RETRY_RESPONSE.result,
|
||||
message: exchangeResponse.error
|
||||
};
|
||||
} else if (includes(exchangeResponse.error, ['Order not found, or you are not the person who placed it.'])) {
|
||||
error = {
|
||||
result: global.CUSTOM_OK_RESPONSE.result,
|
||||
message: exchangeResponse.error
|
||||
};
|
||||
} else if (exchangeResponse.error) {
|
||||
error = {
|
||||
result: global.DEFAULT_FAIL_RESPONSE.result,
|
||||
message: exchangeResponse.error
|
||||
};
|
||||
}
|
||||
|
||||
return callBack(error, exchangeResponse);
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] analizeResponse -> err.message = " + err.message);
|
||||
return callBack(global.DEFAULT_FAIL_RESPONSE, exchangeResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user