Files
Superalgos/Server/Exchange/Wrappers/binanceClient.js
T

361 lines
12 KiB
JavaScript

exports.newAPIClient = function newAPIClient(pKey, pSecret) {
/*
/* 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) { console.log("[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) { console.log("[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 (CONSOLE_LOG === true) { console.log("[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 (CONSOLE_LOG === true) { console.log("[INFO] analizeResponse -> exchangeErr = " + stringExchangeErr); }
if (CONSOLE_LOG === true) { console.log("[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) {
console.log("[ERROR] analizeResponse -> err.message = " + err.message);
return callBack(global.DEFAULT_FAIL_RESPONSE, exchangeResponse);
}
}
}
}