New Module to access the exchanges

This commit is contained in:
Luis Fernando Molina committed 2018-09-16 15:20:28 +02:00
1 parent 9f74ed7c59
commit 2dda1e3e37
12 files changed
+10170 -503

No files matched your search

+12 -2
View File
@@ -56,10 +56,17 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="CloudVM\PoloniexAPIClient.js" />
<Compile Include="Exchanges\PoloniexAPIClient.js" />
<Compile Include="Server\Exchange\ExchangeAPI.js" />
<Compile Include="Server\Exchange\exchangeUtils.js" />
<Compile Include="Server\Exchange\PoloniexAPIClient.js" />
<Compile Include="Globals.js">
<SubType>Code</SubType>
</Compile>
<Compile Include="Server\Exchange\Wrappers\beautifier.js" />
<Compile Include="Server\Exchange\Wrappers\binance.js" />
<Compile Include="Server\Exchange\Wrappers\binanceClient.js" />
<Compile Include="Server\Exchange\Wrappers\poloniex.js" />
<Compile Include="Server\Exchange\Wrappers\poloniexClient.js" />
<Compile Include="TopSpace\Login.js">
<SubType>Code</SubType>
</Compile>
@@ -216,17 +223,20 @@
<Compile Include="ViewPort.js" />
<Compile Include="VolumeBar.js" />
<Content Include="index.html" />
<Content Include="Server\Exchange\Wrappers\binanceProperties.json" />
<Content Include="Server\Exchange\Wrappers\poloniexProperties.json" />
<Content Include="this.server.config.json" />
</ItemGroup>
<ItemGroup>
<Folder Include="Azure\" />
<Folder Include="CloudVM\" />
<Folder Include="Exchanges\" />
<Folder Include="Server\Exchange\" />
<Folder Include="Files\" />
<Folder Include="FloatingSpace\" />
<Folder Include="AABrowserAPI\" />
<Folder Include="BottomSpace\" />
<Folder Include="Scales\" />
<Folder Include="Server\Exchange\Wrappers\" />
<Folder Include="TopSpace\" />
<Folder Include="Scripts\" />
<Folder Include="Server\" />
+247
View File
@@ -0,0 +1,247 @@
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;
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;
function initialize(callBackFunction) {
try {
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); }
let exchange = global.EXCHANGE_NAME.toLowerCase() + 'Client.js';
let api = require('./Wrappers/' + exchange);
if (!bot.instanceIndex)
bot.instanceIndex = 0;
apiClient = api.newAPIClient(global.EXCHANGE_KEYS[global.EXCHANGE_NAME][bot.instanceIndex].Key, global.EXCHANGE_KEYS[global.EXCHANGE_NAME][bot.instanceIndex].Secret, logger);
callBackFunction(global.DEFAULT_OK_RESPONSE);
} catch (err) {
logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.message);
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
}
}
/*
* 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);
}
}
};
-500
View File
@@ -1,500 +0,0 @@
exports.newPoloniexAPIClient = function newPoloniexAPIClient(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.
*/
let newFunction = new API(pKey, pSecret);
let thisObject = {
API: newFunction
};
return thisObject;
function API(key, secret) {
'use strict';
// Module dependencies
var crypto = require('crypto'),
request = require('request'),
nonce = require('nonce')();
// Constants
var version = '0.0.8',
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;
}
// Generate headers signed by thisObject user's key and secret.
// The secret is encapsulated and never exposed
function _getPrivateHeaders(parameters) {
var paramString, signature;
if (!key || !secret) {
throw 'Poloniex: Error. API key and secret required';
}
// Convert to `arg1=foo&arg2=bar`
paramString = Object.keys(parameters).map(function (param) {
return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]);
}).join('&');
signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex');
return {
Key: key,
Sign: signature
};
};
// Currently, thisObject fails with `Error: CERT_UNTRUSTED`
// Poloniex.STRICT_SSL can be set to `false` to avoid thisObject. Use with caution.
// Will be removed in future, once thisObject is resolved.
const STRICT_SSL = true;
// Prototype
let thisObject = {
// Make an API request
_request: function (options, callback) {
if (!('headers' in options)) {
options.headers = {};
}
options.json = true;
options.headers['User-Agent'] = USER_AGENT;
options.strictSSL = STRICT_SSL;
request(options, function (err, response, body) {
// Empty response
if (!err && (typeof body === 'undefined' || body === null)) {
err = 'Empty response';
}
callback(err, body);
});
return thisObject;
},
// 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 thisObject._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,
headers: _getPrivateHeaders(parameters)
};
return thisObject._request(options, callback);
},
/////
// PUBLIC METHODS
returnTicker: function (callback) {
return thisObject._public('returnTicker', callback);
},
return24hVolume: function (callback) {
return thisObject._public('return24hVolume', callback);
},
returnOrderBook: function (currencyA, currencyB, depth, callback) {
var parameters = {
currencyPair: joinCurrencies(currencyA, currencyB),
depth: depth
};
return thisObject._public('returnOrderBook', parameters, callback);
},
returnOrderBooks: function (depth, callback) {
var parameters = {
currencyPair: 'all',
depth: depth
};
return thisObject._public('returnOrderBook', parameters, callback);
},
returnPublicTradeHistory: function (currencyA, currencyB, start, end, callback) {
var parameters = {
currencyPair: joinCurrencies(currencyA, currencyB),
start: start,
end: end
};
return thisObject._public('returnTradeHistory', parameters, callback);
},
returnChartData: function (currencyA, currencyB, period, start, end, callback) {
var parameters = {
currencyPair: joinCurrencies(currencyA, currencyB),
period: period,
start: start,
end: end
};
return thisObject._public('returnChartData', parameters, callback);
},
returnCurrencies: function (callback) {
return thisObject._public('returnCurrencies', callback);
},
returnLoanOrders: function (currency, callback) {
return thisObject._public('returnLoanOrders', { currency: currency }, callback);
},
/////
// PRIVATE METHODS
returnBalances: function (callback) {
return thisObject._private('returnBalances', {}, callback);
},
returnCompleteBalances: function (callback) {
return thisObject._private('returnCompleteBalances', {}, callback);
},
returnDepositAddresses: function (callback) {
return thisObject._private('returnDepositAddresses', {}, callback);
},
generateNewAddress: function (currency, callback) {
return thisObject._private('returnDepositsWithdrawals', { currency: currency }, callback);
},
returnDepositsWithdrawals: function (start, end, callback) {
return thisObject._private('returnDepositsWithdrawals', { start: start, end: end }, callback);
},
returnOpenOrders: function (currencyA, currencyB, callback) {
var parameters = {
currencyPair: joinCurrencies(currencyA, currencyB)
};
return thisObject._private('returnOpenOrders', parameters, callback);
},
returnTradeHistory: function (currencyA, currencyB, callback) {
var parameters = {
currencyPair: joinCurrencies(currencyA, currencyB)
};
return thisObject._private('returnTradeHistory', parameters, callback);
},
returnOrderTrades: function (orderNumber, callback) {
var parameters = {
orderNumber: orderNumber
};
return thisObject._private('returnOrderTrades', parameters, callback);
},
buy: function (currencyA, currencyB, rate, amount, callback) {
var parameters = {
currencyPair: joinCurrencies(currencyA, currencyB),
rate: rate,
amount: amount
};
return thisObject._private('buy', parameters, callback);
},
sell: function (currencyA, currencyB, rate, amount, callback) {
var parameters = {
currencyPair: joinCurrencies(currencyA, currencyB),
rate: rate,
amount: amount
};
return thisObject._private('sell', parameters, callback);
},
cancelOrder: function (currencyA, currencyB, orderNumber, callback) {
var parameters = {
currencyPair: joinCurrencies(currencyA, currencyB),
orderNumber: orderNumber
};
return thisObject._private('cancelOrder', parameters, callback);
},
moveOrder: function (orderNumber, rate, amount, callback) {
var parameters = {
orderNumber: orderNumber,
rate: rate,
amount: amount ? amount : null
};
return thisObject._private('moveOrder', parameters, callback);
},
withdraw: function (currency, amount, address, callback) {
var parameters = {
currency: currency,
amount: amount,
address: address
};
return thisObject._private('withdraw', parameters, callback);
},
returnFeeInfo: function (callback) {
return thisObject._private('returnFeeInfo', {}, callback);
},
returnAvailableAccountBalances: function (account, callback) {
var options = {};
if (account) {
options.account = account;
}
return thisObject._private('returnAvailableAccountBalances', options, callback);
},
returnTradableBalances: function (callback) {
return thisObject._private('returnTradableBalances', {}, callback);
},
transferBalance: function (currency, amount, fromAccount, toAccount, callback) {
var parameters = {
currency: currency,
amount: amount,
fromAccount: fromAccount,
toAccount: toAccount
};
return thisObject._private('transferBalance', parameters, callback);
},
returnMarginAccountSummary: function (callback) {
return thisObject._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 thisObject._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 thisObject._private('marginSell', parameters, callback);
},
getMarginPosition: function (currencyA, currencyB, callback) {
var parameters = {
currencyPair: joinCurrencies(currencyA, currencyB)
};
return thisObject._private('getMarginPosition', parameters, callback);
},
closeMarginPosition: function (currencyA, currencyB, callback) {
var parameters = {
currencyPair: joinCurrencies(currencyA, currencyB)
};
return thisObject._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 thisObject._private('createLoanOffer', parameters, callback);
},
cancelLoanOffer: function (orderNumber, callback) {
var parameters = {
orderNumber: orderNumber
};
return thisObject._private('cancelLoanOffer', parameters, callback);
},
returnOpenLoanOffers: function (callback) {
return thisObject._private('returnOpenLoanOffers', {}, callback);
},
returnActiveLoans: function (callback) {
return thisObject._private('returnActiveLoans', {}, callback);
},
returnLendingHistory: function (start, end, limit, callback) {
var parameters = {
start: start,
end: end,
limit: limit
};
return thisObject._private('returnLendingHistory', parameters, callback);
},
toggleAutoRenew: function (orderNumber, callback) {
return thisObject._private('toggleAutoRenew', { orderNumber: orderNumber }, callback);
},
analizeResponse: function (logger, exchangeErr, exchangeResponse, notOkCallBack, okCallBack) {
const FULL_LOG = false;
const LOG_FILE_CONTENT = false;
const MODULE_NAME = "Poloniex API Client";
/* This function analizes the different situations we might encounter trying to access Poloniex and returns appropiate standard errors. */
try {
let stringExchangeResponse = JSON.stringify(exchangeResponse);
let stringExchangeErr = JSON.stringify(exchangeErr);
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] analizeResponse -> exchangeErr = " + stringExchangeErr); }
if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] analizeResponse -> exchangeResponse = " + stringExchangeResponse); }
if (stringExchangeResponse === undefined) {
logger.write(MODULE_NAME, "[WARN] analizeResponse -> No response received from the exchange. Retrying Later.");
notOkCallBack(global.DEFAULT_RETRY_RESPONSE);
return;
}
if (stringExchangeErr.indexOf("ETIMEDOUT") > 0 ||
stringExchangeErr.indexOf("ENOTFOUND") > 0 ||
stringExchangeErr.indexOf("ECONNREFUSED") > 0 ||
stringExchangeErr.indexOf("ESOCKETTIMEDOUT") > 0 ||
stringExchangeErr.indexOf("ECONNRESET") > 0 ||
(stringExchangeResponse !== undefined && (
stringExchangeResponse.indexOf("Connection timed out") > 0 ||
stringExchangeResponse.indexOf("Connection Error") > 0 ||
stringExchangeResponse.indexOf("Bad gateway") > 0 ||
stringExchangeResponse.indexOf("Nonce must be greater than") > 0 ||
stringExchangeResponse.indexOf("Internal error. Please try again") > 0))) {
logger.write(MODULE_NAME, "[ERROR] analizeResponse -> Somethng bad happened while trying to access the Exchange API. Requesting new execution later.");
logger.write(MODULE_NAME, "[ERROR] analizeResponse -> stringExchangeErr = " + stringExchangeErr);
notOkCallBack(global.DEFAULT_RETRY_RESPONSE);
return;
} else {
if (JSON.stringify(exchangeResponse).indexOf("error") > 0) {
logger.write(MODULE_NAME, "[ERROR] analizeResponse -> Unexpected response from the Exchange.");
logger.write(MODULE_NAME, "[ERROR] analizeResponse -> JSON.stringify(exchangeErr) = " + JSON.stringify(exchangeErr));
logger.write(MODULE_NAME, "[ERROR] analizeResponse -> exchangeErr = " + exchangeErr);
logger.write(MODULE_NAME, "[ERROR] analizeResponse -> exchangeResponse = " + exchangeResponse);
/* {"error":"Not enough USDT.","success":0}*/
if (JSON.stringify(exchangeResponse).indexOf("Not enough") > 0) {
let err = {
resutl: global.CUSTOM_FAIL_RESPONSE.result,
message: exchangeResponse.error
};
notOkCallBack(err);
} else {
notOkCallBack(global.DEFAULT_FAIL_RESPONSE);
}
return;
}
if (exchangeErr) {
logger.write(MODULE_NAME, "[ERROR] analizeResponse -> Unexpected error trying to contact the Exchange.");
logger.write(MODULE_NAME, "[ERROR] analizeResponse -> exchangeErr = " + exchangeErr);
notOkCallBack(global.DEFAULT_FAIL_RESPONSE);
return;
} else {
logger.write(MODULE_NAME, "[INFO] analizeResponse -> No problem found.");
okCallBack();
return;
}
}
} catch (err) {
logger.write(MODULE_NAME, "[ERROR] analizeResponse -> err.message = " + err.message);
notOkCallBack(global.DEFAULT_FAIL_RESPONSE);
return;
}
}
};
return thisObject;
}
}
+183
View File
@@ -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;
+368
View File
@@ -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;
+360
View File
@@ -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
+439
View File
@@ -0,0 +1,439 @@
// Source: https://github.com/premasagar/poloniex.js
// Documentation: https://poloniex.com/support/api/
module.exports = (function () {
'use strict';
// Module dependencies
var crypto = require('crypto'),
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(key, secret) {
// Generate headers signed by this user's key and secret.
// The secret is encapsulated and never exposed
this._getPrivateHeaders = function (parameters) {
var paramString, signature;
if (!key || !secret) {
throw 'Poloniex: Error. API key and secret required';
}
// Convert to `arg1=foo&arg2=bar`
paramString = Object.keys(parameters).map(function (param) {
return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]);
}).join('&');
signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex');
return {
Key: key,
Sign: signature
};
};
}
// 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,
headers: this._getPrivateHeaders(parameters)
};
return this._request(options, callback);
},
/////
// 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;
})();
+353
View File
@@ -0,0 +1,353 @@
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 = "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(pKey, pSecret);
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
+100
View File
@@ -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
}
+1 -1
View File
@@ -711,7 +711,7 @@ function onBrowserRequest(request, response) {
let exchangeKey = session.exchangeKeys[0].key; // 0 because we only deal with one exchange for now.
let exchangeSecret = session.exchangeKeys[0].secret;
const POLONIEX_CLIENT_MODULE = require('./Server/Exchange/' + 'PoloniexAPIClient');
const POLONIEX_CLIENT_MODULE = require('./Server/Exchange/' + 'ExchangeAPI');
let poloniexApiClient = POLONIEX_CLIENT_MODULE.newPoloniexAPIClient(exchangeKey, exchangeSecret);
switch (requestParameters[2]) {