profitability data collection from Poloniex

This commit is contained in:
Jerry Brady 2014-04-13 16:44:12 +00:00
parent da22dad482
commit 0af27316d3
5 changed files with 482 additions and 11 deletions

View File

@ -68,10 +68,16 @@
}
},
"profitSwitch": {
"enabled": false,
"updateInterval": 60,
"depth": 0.80
},
"redisBlockNotifyListener": {
"enabled": false,
"redisPort": 6379,
"redisHost": "hostname",
"psubscribeKey": "newblocks:*"
}
}
}

40
init.js
View File

@ -12,6 +12,7 @@ var WorkerListener = require('./libs/workerListener.js');
var PoolWorker = require('./libs/poolWorker.js');
var PaymentProcessor = require('./libs/paymentProcessor.js');
var Website = require('./libs/website.js');
var ProfitSwitch = require('./libs/profitSwitch.js');
var algos = require('stratum-pool/lib/algoProperties.js');
@ -58,7 +59,7 @@ catch(e){
if (cluster.isWorker){
switch(process.env.workerType){
case 'pool':
new PoolWorker(logger);
@ -69,6 +70,9 @@ if (cluster.isWorker){
case 'website':
new Website(logger);
break;
case 'profitSwitch':
new ProfitSwitch(logger);
break;
}
return;
@ -203,23 +207,19 @@ var startCoinswitchListener = function(portalConfig){
logger.debug('Master', 'Coinswitch', text);
});
listener.on('switchcoin', function(message){
var ipcMessage = {type:'blocknotify', coin: message.coin, hash: message.hash};
Object.keys(cluster.workers).forEach(function(id) {
cluster.workers[id].send(ipcMessage);
});
var ipcMessage = {
type:'switch',
coin: message.coin
};
type:'switch',
coin: message.coin
};
Object.keys(cluster.workers).forEach(function(id) {
cluster.workers[id].send(ipcMessage);
});
});
listener.start();
};
var startRedisBlockListener = function(portalConfig){
@ -287,6 +287,28 @@ var startWebsite = function(portalConfig, poolConfigs){
};
var startProfitSwitch = function(portalConfig, poolConfigs){
if (!portalConfig.profitSwitch.enabled){
logger.error('Master', 'Profit', 'Profit auto switching disabled');
return;
}
var worker = cluster.fork({
workerType: 'profitSwitch',
pools: JSON.stringify(poolConfigs),
portalConfig: JSON.stringify(portalConfig)
});
worker.on('exit', function(code, signal){
logger.error('Master', 'Profit', 'Profit switching process died, spawning replacement...');
setTimeout(function(){
startWebsite(portalConfig, poolConfigs);
}, 2000);
});
};
(function init(){
var poolConfigs = buildPoolConfigs();
@ -305,4 +327,6 @@ var startWebsite = function(portalConfig, poolConfigs){
startWebsite(portalConfig, poolConfigs);
startProfitSwitch(portalConfig, poolConfigs);
})();

212
libs/apiPoloniex.js Normal file
View File

@ -0,0 +1,212 @@
var request = require('request');
var nonce = require('nonce');
module.exports = function() {
'use strict';
// Module dependencies
// Constants
var version = '0.1.0',
PUBLIC_API_URL = 'http://poloniex.com/public',
PRIVATE_API_URL = 'https://poloniex.com/tradingApi',
USER_AGENT = 'npm-crypto-apis/' + version
// 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';
}
// Sort parameters alphabetically and convert to `arg1=foo&arg2=bar`
paramString = Object.keys(parameters).sort().map(function(param){
return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]);
}).join('&');
signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex');
return {
Key: key,
Sign: signature
};
};
}
// If a site uses non-trusted SSL certificates, set this value to false
Poloniex.STRICT_SSL = true;
// Helper methods
function joinCurrencies(currencyA, currencyB){
return currencyA + '_' + currencyB;
}
// Prototype
Poloniex.prototype = {
constructor: Poloniex,
// Make an API request
_request: function(options, callback){
if (!('headers' in options)){
options.headers = {};
}
options.headers['User-Agent'] = USER_AGENT;
options.json = true;
options.strictSSL = Poloniex.STRICT_SSL;
request(options, function(err, response, body) {
callback(err, body);
});
return this;
},
// Make a public API request
_public: function(parameters, callback){
var options = {
method: 'GET',
url: PUBLIC_API_URL,
qs: parameters
};
return this._request(options, callback);
},
// Make a private API request
_private: function(parameters, callback){
var options;
parameters.nonce = nonce();
options = {
method: 'POST',
url: PRIVATE_API_URL,
form: parameters,
headers: this._getPrivateHeaders(parameters)
};
return this._request(options, callback);
},
/////
// PUBLIC METHODS
getTicker: function(callback){
var parameters = {
command: 'returnTicker'
};
return this._public(parameters, callback);
},
get24hVolume: function(callback){
var parameters = {
command: 'return24hVolume'
};
return this._public(parameters, callback);
},
getOrderBook: function(currencyA, currencyB, callback){
var parameters = {
command: 'returnOrderBook',
currencyPair: joinCurrencies(currencyA, currencyB)
};
return this._public(parameters, callback);
},
getTradeHistory: function(currencyA, currencyB, callback){
var parameters = {
command: 'returnTradeHistory',
currencyPair: joinCurrencies(currencyA, currencyB)
};
return this._public(parameters, callback);
},
/////
// PRIVATE METHODS
myBalances: function(callback){
var parameters = {
command: 'returnBalances'
};
return this._private(parameters, callback);
},
myOpenOrders: function(currencyA, currencyB, callback){
var parameters = {
command: 'returnOpenOrders',
currencyPair: joinCurrencies(currencyA, currencyB)
};
return this._private(parameters, callback);
},
myTradeHistory: function(currencyA, currencyB, callback){
var parameters = {
command: 'returnTradeHistory',
currencyPair: joinCurrencies(currencyA, currencyB)
};
return this._private(parameters, callback);
},
buy: function(currencyA, currencyB, rate, amount, callback){
var parameters = {
command: 'buy',
currencyPair: joinCurrencies(currencyA, currencyB),
rate: rate,
amount: amount
};
return this._private(parameters, callback);
},
sell: function(currencyA, currencyB, rate, amount, callback){
var parameters = {
command: 'sell',
currencyPair: joinCurrencies(currencyA, currencyB),
rate: rate,
amount: amount
};
return this._private(parameters, callback);
},
cancelOrder: function(currencyA, currencyB, orderNumber, callback){
var parameters = {
command: 'cancelOrder',
currencyPair: joinCurrencies(currencyA, currencyB),
orderNumber: orderNumber
};
return this._private(parameters, callback);
},
withdraw: function(currency, amount, address, callback){
var parameters = {
command: 'withdraw',
currency: currency,
amount: amount,
address: address
};
return this._private(parameters, callback);
}
};
return Poloniex;
}();

227
libs/profitSwitch.js Normal file
View File

@ -0,0 +1,227 @@
var async = require('async');
var Poloniex = require('./apiPoloniex.js');
module.exports = function(logger){
var _this = this;
var portalConfig = JSON.parse(process.env.portalConfig);
var poolConfigs = JSON.parse(process.env.pools);
var logSystem = 'Profit';
//
// build status tracker for collecting coin market information
//
var profitStatus = {};
var profitSymbols = {};
Object.keys(poolConfigs).forEach(function(coin){
var poolConfig = poolConfigs[coin];
var algo = poolConfig.coin.algorithm;
if (!profitStatus.hasOwnProperty(algo)) {
profitStatus[algo] = {};
}
var coinStatus = {
name: poolConfig.coin.name,
symbol: poolConfig.coin.symbol,
difficulty: 0,
reward: 0,
avgPrice: { BTC: 0, LTC: 0 },
avgDepth: { BTC: 0, LTC: 0 },
avgVolume: { BTC: 0, LTC: 0 },
prices: {},
depths: {},
volumes: {},
};
profitStatus[algo][poolConfig.coin.symbol] = coinStatus;
profitSymbols[poolConfig.coin.symbol] = algo;
});
//
// ensure we have something to switch
//
var isMoreThanOneCoin = false;
Object.keys(profitStatus).forEach(function(algo){
if (Object.keys(profitStatus[algo]).length > 1) {
isMoreThanOneCoin = true;
}
});
if (!isMoreThanOneCoin){
logger.debug(logSystem, 'Config', 'No alternative coins to switch to in current config, switching disabled.');
return;
}
logger.debug(logSystem, 'profitStatus', JSON.stringify(profitStatus));
logger.debug(logSystem, 'profitStatus', JSON.stringify(profitSymbols));
//
// setup APIs
//
var poloApi = new Poloniex(
// 'API_KEY',
// 'API_SECRET'
);
//
// market data collection from Poloniex
//
this.getProfitDataPoloniex = function(callback){
async.series([
function(taskCallback){
poloApi.getTicker(function(err, data){
if (err){
taskCallback(err);
return;
}
Object.keys(profitSymbols).forEach(function(symbol){
var btcPrice = new Number(0);
var ltcPrice = new Number(0);
if (data.hasOwnProperty('BTC_' + symbol)) {
btcPrice = new Number(data['BTC_' + symbol]);
}
if (data.hasOwnProperty('LTC_' + symbol)) {
ltcPrice = new Number(data['LTC_' + symbol]);
}
if (btcPrice > 0 || ltcPrice > 0) {
var prices = {
BTC: btcPrice,
LTC: ltcPrice
};
profitStatus[profitSymbols[symbol]][symbol].prices['Poloniex'] = prices;
}
});
taskCallback();
});
},
function(taskCallback){
poloApi.get24hVolume(function(err, data){
if (err){
taskCallback(err);
return;
}
Object.keys(profitSymbols).forEach(function(symbol){
var btcVolume = new Number(0);
var ltcVolume = new Number(0);
if (data.hasOwnProperty('BTC_' + symbol)) {
btcVolume = new Number(data['BTC_' + symbol].BTC);
}
if (data.hasOwnProperty('LTC_' + symbol)) {
ltcVolume = new Number(data['LTC_' + symbol].LTC);
}
if (btcVolume > 0 || ltcVolume > 0) {
var volumes = {
BTC: btcVolume,
LTC: ltcVolume
};
profitStatus[profitSymbols[symbol]][symbol].volumes['Poloniex'] = volumes;
}
});
taskCallback();
});
},
function(taskCallback){
var depthTasks = [];
Object.keys(profitSymbols).forEach(function(symbol){
var coinVolumes = profitStatus[profitSymbols[symbol]][symbol].volumes;
var coinPrices = profitStatus[profitSymbols[symbol]][symbol].prices;
if (coinVolumes.hasOwnProperty('Poloniex') && coinPrices.hasOwnProperty('Poloniex')){
var btcDepth = new Number(0);
var ltcDepth = new Number(0);
if (coinVolumes['Poloniex']['BTC'] > 0 && coinPrices['Poloniex']['BTC'] > 0){
var coinPrice = new Number(coinPrices['Poloniex']['BTC']);
depthTasks.push(function(callback){
_this.getMarketDepthFromPoloniex('BTC', symbol, coinPrice, callback)
});
}
if (coinVolumes['Poloniex']['LTC'] > 0 && coinPrices['Poloniex']['LTC'] > 0){
var coinPrice = new Number(coinPrices['Poloniex']['LTC']);
depthTasks.push(function(callback){
_this.getMarketDepthFromPoloniex('LTC', symbol, coinPrice, callback)
});
}
}
});
if (depthTasks.length == 0){
taskCallback;
return;
}
async.parallel(depthTasks, function(err){
if (err){
logger.error(logSystem, 'Check', 'Error while checking profitability: ' + err);
return;
}
taskCallback();
});
}
], function(err){
if (err){
callback(err);
return;
}
callback(null);
});
};
this.getMarketDepthFromPoloniex = function(symbolA, symbolB, coinPrice, callback){
poloApi.getOrderBook(symbolA, symbolB, function(err, data){
if (err){
callback(err);
return;
}
var depth = new Number(0);
if (data.hasOwnProperty('bids')){
data['bids'].forEach(function(order){
var price = new Number(order[0]);
var qty = new Number(order[1]);
// only measure the depth down to configured depth
if (price >= coinPrice * portalConfig.profitSwitch.depth){
depth += (qty * price);
}
});
}
if (!profitStatus[profitSymbols[symbolB]][symbolB].depths.hasOwnProperty('Poloniex')){
profitStatus[profitSymbols[symbolB]][symbolB].depths['Poloniex'] = {
BTC: 0,
LTC: 0
};
}
profitStatus[profitSymbols[symbolB]][symbolB].depths['Poloniex'][symbolA] = depth;
callback();
});
};
// TODO
this.getProfitDataCryptsy = function(callback){
callback(null);
};
var checkProfitability = function(){
logger.debug(logSystem, 'Check', 'Running profitability checks.');
async.parallel([
_this.getProfitDataPoloniex,
_this.getProfitDataCryptsy
], function(err){
if (err){
logger.error(logSystem, 'Check', 'Error while checking profitability: ' + err);
return;
}
logger.debug(logSystem, 'Check', JSON.stringify(profitStatus));
});
};
setInterval(checkProfitability, portalConfig.profitSwitch.updateInterval * 1000);
};

View File

@ -42,9 +42,11 @@
"compression": "*",
"dot": "*",
"colors": "*",
"node-watch": "*"
"node-watch": "*",
"request": "*",
"nonce": "*"
},
"engines": {
"node": ">=0.10"
}
}
}