Merge pull request #67 from bluecircle/master
Feature: Added automagical coin switching based on profitability
This commit is contained in:
commit
cf74872863
|
@ -34,6 +34,7 @@
|
|||
|
||||
"coinSwitchListener": {
|
||||
"enabled": false,
|
||||
"host": "127.0.0.1",
|
||||
"port": 8118,
|
||||
"password": "test"
|
||||
},
|
||||
|
@ -69,10 +70,19 @@
|
|||
}
|
||||
},
|
||||
|
||||
"profitSwitch": {
|
||||
"enabled": false,
|
||||
"updateInterval": 600,
|
||||
"depth": 0.90,
|
||||
"usePoloniex": true,
|
||||
"useCryptsy": true,
|
||||
"useMintpal": true
|
||||
},
|
||||
|
||||
"redisBlockNotifyListener": {
|
||||
"enabled": false,
|
||||
"redisPort": 6379,
|
||||
"redisHost": "hostname",
|
||||
"psubscribeKey": "newblocks:*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
40
init.js
40
init.js
|
@ -11,6 +11,7 @@ var RedisBlocknotifyListener = require('./libs/redisblocknotifyListener.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');
|
||||
|
||||
|
@ -57,7 +58,7 @@ catch(e){
|
|||
|
||||
|
||||
if (cluster.isWorker){
|
||||
|
||||
|
||||
switch(process.env.workerType){
|
||||
case 'pool':
|
||||
new PoolWorker(logger);
|
||||
|
@ -68,6 +69,9 @@ if (cluster.isWorker){
|
|||
case 'website':
|
||||
new Website(logger);
|
||||
break;
|
||||
case 'profitSwitch':
|
||||
new ProfitSwitch(logger);
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -210,23 +214,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){
|
||||
|
@ -294,6 +294,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();
|
||||
|
@ -310,4 +332,6 @@ var startWebsite = function(portalConfig, poolConfigs){
|
|||
|
||||
startWebsite(portalConfig, poolConfigs);
|
||||
|
||||
startProfitSwitch(portalConfig, poolConfigs);
|
||||
|
||||
})();
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
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://pubapi.cryptsy.com/api.php',
|
||||
PRIVATE_API_URL = 'https://api.cryptsy.com/api',
|
||||
USER_AGENT = 'nomp/node-open-mining-portal'
|
||||
|
||||
// Constructor
|
||||
function Cryptsy(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 'Cryptsy: 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
|
||||
Cryptsy.STRICT_SSL = true;
|
||||
|
||||
// Helper methods
|
||||
function joinCurrencies(currencyA, currencyB){
|
||||
return currencyA + '_' + currencyB;
|
||||
}
|
||||
|
||||
// Prototype
|
||||
Cryptsy.prototype = {
|
||||
constructor: Cryptsy,
|
||||
|
||||
// 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 = Cryptsy.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 = {
|
||||
method: 'marketdatav2'
|
||||
};
|
||||
|
||||
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 Cryptsy;
|
||||
}();
|
|
@ -0,0 +1,216 @@
|
|||
var request = require('request');
|
||||
var nonce = require('nonce');
|
||||
|
||||
module.exports = function() {
|
||||
'use strict';
|
||||
|
||||
// Module dependencies
|
||||
|
||||
// Constants
|
||||
var version = '0.1.0',
|
||||
PUBLIC_API_URL = 'https://api.mintpal.com/v2/market',
|
||||
PRIVATE_API_URL = 'https://api.mintpal.com/v2/market',
|
||||
USER_AGENT = 'nomp/node-open-mining-portal'
|
||||
|
||||
// Constructor
|
||||
function Mintpal(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 'Mintpal: 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
|
||||
Mintpal.STRICT_SSL = true;
|
||||
|
||||
// Helper methods
|
||||
function joinCurrencies(currencyA, currencyB){
|
||||
return currencyA + '_' + currencyB;
|
||||
}
|
||||
|
||||
// Prototype
|
||||
Mintpal.prototype = {
|
||||
constructor: Mintpal,
|
||||
|
||||
// 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 = Mintpal.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 options = {
|
||||
method: 'GET',
|
||||
url: PUBLIC_API_URL + '/summary',
|
||||
qs: null
|
||||
};
|
||||
|
||||
return this._request(options, callback);
|
||||
},
|
||||
|
||||
getBuyOrderBook: function(currencyA, currencyB, callback){
|
||||
var options = {
|
||||
method: 'GET',
|
||||
url: PUBLIC_API_URL + '/orders/' + currencyB + '/' + currencyA + '/BUY',
|
||||
qs: null
|
||||
};
|
||||
|
||||
return this._request(options, 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 Mintpal;
|
||||
}();
|
|
@ -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 = 'https://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;
|
||||
}();
|
|
@ -63,10 +63,10 @@ module.exports = function(logger){
|
|||
var oldPool = pools[oldCoin];
|
||||
var proxyPort = proxySwitch[algo].port;
|
||||
|
||||
if (newCoin == oldCoin) {
|
||||
if (newCoin == oldCoin) {
|
||||
logger.debug(logSystem, logComponent, logSubCat, 'Switch message would have no effect - ignoring ' + newCoin);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
logger.debug(logSystem, logComponent, logSubCat, 'Proxy message for ' + algo + ' from ' + oldCoin + ' to ' + newCoin);
|
||||
|
||||
|
@ -91,8 +91,8 @@ module.exports = function(logger){
|
|||
else {
|
||||
logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state saved to redis for ' + algo);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -230,9 +230,9 @@ module.exports = function(logger){
|
|||
// Setup proxySwitch object to control proxy operations from configuration and any restored
|
||||
// state. Each algorithm has a listening port, current coin name, and an active pool to
|
||||
// which traffic is directed when activated in the config.
|
||||
//
|
||||
// In addition, the proxy config also takes diff and varDiff parmeters the override the
|
||||
// defaults for the standard config of the coin.
|
||||
//
|
||||
// In addition, the proxy config also takes diff and varDiff parmeters the override the
|
||||
// defaults for the standard config of the coin.
|
||||
//
|
||||
Object.keys(portalConfig.proxy).forEach(function(algorithm) {
|
||||
|
||||
|
@ -245,21 +245,21 @@ module.exports = function(logger){
|
|||
};
|
||||
|
||||
|
||||
// Copy diff and vardiff configuation into pools that match our algorithm so the stratum server can pick them up
|
||||
//
|
||||
// Note: This seems a bit wonky and brittle - better if proxy just used the diff config of the port it was
|
||||
// routed into instead.
|
||||
//
|
||||
if (portalConfig.proxy[algorithm].hasOwnProperty('varDiff')) {
|
||||
// Copy diff and vardiff configuation into pools that match our algorithm so the stratum server can pick them up
|
||||
//
|
||||
// Note: This seems a bit wonky and brittle - better if proxy just used the diff config of the port it was
|
||||
// routed into instead.
|
||||
//
|
||||
if (portalConfig.proxy[algorithm].hasOwnProperty('varDiff')) {
|
||||
proxySwitch[algorithm].varDiff = new Stratum.varDiff(proxySwitch[algorithm].port, portalConfig.proxy[algorithm].varDiff);
|
||||
proxySwitch[algorithm].diff = portalConfig.proxy[algorithm].diff;
|
||||
}
|
||||
}
|
||||
Object.keys(pools).forEach(function (coinName) {
|
||||
var a = poolConfigs[coinName].coin.algorithm;
|
||||
var p = pools[coinName];
|
||||
if (a === algorithm) {
|
||||
if (a === algorithm) {
|
||||
p.setVarDiff(proxySwitch[algorithm].port, proxySwitch[algorithm].varDiff);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
proxySwitch[algorithm].proxy = net.createServer(function(socket) {
|
||||
|
@ -267,12 +267,12 @@ module.exports = function(logger){
|
|||
var logSubCat = 'Thread ' + (parseInt(forkId) + 1);
|
||||
|
||||
logger.debug(logSystem, 'Connect', logSubCat, 'Proxy connect from ' + socket.remoteAddress + ' on ' + proxySwitch[algorithm].port
|
||||
+ ' routing to ' + currentPool);
|
||||
+ ' routing to ' + currentPool);
|
||||
pools[currentPool].getStratumServer().handleNewClient(socket);
|
||||
|
||||
}).listen(parseInt(proxySwitch[algorithm].port), function() {
|
||||
logger.debug(logSystem, logComponent, logSubCat, 'Proxy listening for ' + algorithm + ' on port ' + proxySwitch[algorithm].port
|
||||
+ ' into ' + proxySwitch[algorithm].currentPool);
|
||||
+ ' into ' + proxySwitch[algorithm].currentPool);
|
||||
});
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -0,0 +1,542 @@
|
|||
var async = require('async');
|
||||
var net = require('net');
|
||||
var bignum = require('bignum');
|
||||
var algos = require('stratum-pool/lib/algoProperties.js');
|
||||
var util = require('stratum-pool/lib/util.js');
|
||||
|
||||
var Cryptsy = require('./apiCryptsy.js');
|
||||
var Poloniex = require('./apiPoloniex.js');
|
||||
var Mintpal = require('./apiMintpal.js');
|
||||
var Stratum = require('stratum-pool');
|
||||
|
||||
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 symbolToAlgorithmMap = {};
|
||||
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,
|
||||
exchangeInfo: {}
|
||||
};
|
||||
profitStatus[algo][poolConfig.coin.symbol] = coinStatus;
|
||||
symbolToAlgorithmMap[poolConfig.coin.symbol] = algo;
|
||||
});
|
||||
|
||||
|
||||
//
|
||||
// ensure we have something to switch
|
||||
//
|
||||
Object.keys(profitStatus).forEach(function(algo){
|
||||
if (Object.keys(profitStatus[algo]).length <= 1) {
|
||||
delete profitStatus[algo];
|
||||
Object.keys(symbolToAlgorithmMap).forEach(function(symbol){
|
||||
if (symbolToAlgorithmMap[symbol] === algo)
|
||||
delete symbolToAlgorithmMap[symbol];
|
||||
});
|
||||
}
|
||||
});
|
||||
if (Object.keys(profitStatus).length == 0){
|
||||
logger.debug(logSystem, 'Config', 'No alternative coins to switch to in current config, switching disabled.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// setup APIs
|
||||
//
|
||||
var poloApi = new Poloniex(
|
||||
// 'API_KEY',
|
||||
// 'API_SECRET'
|
||||
);
|
||||
var cryptsyApi = new Cryptsy(
|
||||
// 'API_KEY',
|
||||
// 'API_SECRET'
|
||||
);
|
||||
var mintpalApi = new Mintpal(
|
||||
// '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(symbolToAlgorithmMap).forEach(function(symbol){
|
||||
var exchangeInfo = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo;
|
||||
if (!exchangeInfo.hasOwnProperty('Poloniex'))
|
||||
exchangeInfo['Poloniex'] = {};
|
||||
var marketData = exchangeInfo['Poloniex'];
|
||||
|
||||
if (data.hasOwnProperty('BTC_' + symbol)) {
|
||||
if (!marketData.hasOwnProperty('BTC'))
|
||||
marketData['BTC'] = {};
|
||||
|
||||
var btcData = data['BTC_' + symbol];
|
||||
marketData['BTC'].ask = new Number(btcData.lowestAsk);
|
||||
marketData['BTC'].bid = new Number(btcData.highestBid);
|
||||
marketData['BTC'].last = new Number(btcData.last);
|
||||
marketData['BTC'].baseVolume = new Number(btcData.baseVolume);
|
||||
marketData['BTC'].quoteVolume = new Number(btcData.quoteVolume);
|
||||
}
|
||||
if (data.hasOwnProperty('LTC_' + symbol)) {
|
||||
if (!marketData.hasOwnProperty('LTC'))
|
||||
marketData['LTC'] = {};
|
||||
|
||||
var ltcData = data['LTC_' + symbol];
|
||||
marketData['LTC'].ask = new Number(ltcData.lowestAsk);
|
||||
marketData['LTC'].bid = new Number(ltcData.highestBid);
|
||||
marketData['LTC'].last = new Number(ltcData.last);
|
||||
marketData['LTC'].baseVolume = new Number(ltcData.baseVolume);
|
||||
marketData['LTC'].quoteVolume = new Number(ltcData.quoteVolume);
|
||||
}
|
||||
// save LTC to BTC exchange rate
|
||||
if (marketData.hasOwnProperty('LTC') && data.hasOwnProperty('BTC_LTC')) {
|
||||
var btcLtc = data['BTC_LTC'];
|
||||
marketData['LTC'].ltcToBtc = new Number(btcLtc.highestBid);
|
||||
}
|
||||
});
|
||||
|
||||
taskCallback();
|
||||
});
|
||||
},
|
||||
function(taskCallback){
|
||||
var depthTasks = [];
|
||||
Object.keys(symbolToAlgorithmMap).forEach(function(symbol){
|
||||
var marketData = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo['Poloniex'];
|
||||
if (marketData.hasOwnProperty('BTC') && marketData['BTC'].bid > 0){
|
||||
depthTasks.push(function(callback){
|
||||
_this.getMarketDepthFromPoloniex('BTC', symbol, marketData['BTC'].bid, callback)
|
||||
});
|
||||
}
|
||||
if (marketData.hasOwnProperty('LTC') && marketData['LTC'].bid > 0){
|
||||
depthTasks.push(function(callback){
|
||||
_this.getMarketDepthFromPoloniex('LTC', symbol, marketData['LTC'].bid, callback)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (!depthTasks.length){
|
||||
taskCallback();
|
||||
return;
|
||||
}
|
||||
async.series(depthTasks, function(err){
|
||||
if (err){
|
||||
taskCallback(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);
|
||||
var totalQty = new Number(0);
|
||||
if (data.hasOwnProperty('bids')){
|
||||
data['bids'].forEach(function(order){
|
||||
var price = new Number(order[0]);
|
||||
var limit = new Number(coinPrice * portalConfig.profitSwitch.depth);
|
||||
var qty = new Number(order[1]);
|
||||
// only measure the depth down to configured depth
|
||||
if (price >= limit){
|
||||
depth += (qty * price);
|
||||
totalQty += qty;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var marketData = profitStatus[symbolToAlgorithmMap[symbolB]][symbolB].exchangeInfo['Poloniex'];
|
||||
marketData[symbolA].depth = depth;
|
||||
if (totalQty > 0)
|
||||
marketData[symbolA].weightedBid = new Number(depth / totalQty);
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
this.getProfitDataCryptsy = function(callback){
|
||||
async.series([
|
||||
function(taskCallback){
|
||||
cryptsyApi.getTicker(function(err, data){
|
||||
if (err || data.success != 1){
|
||||
taskCallback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(symbolToAlgorithmMap).forEach(function(symbol){
|
||||
var exchangeInfo = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo;
|
||||
if (!exchangeInfo.hasOwnProperty('Cryptsy'))
|
||||
exchangeInfo['Cryptsy'] = {};
|
||||
|
||||
var marketData = exchangeInfo['Cryptsy'];
|
||||
var results = data.return.markets;
|
||||
|
||||
if (results && results.hasOwnProperty(symbol + '/BTC')) {
|
||||
if (!marketData.hasOwnProperty('BTC'))
|
||||
marketData['BTC'] = {};
|
||||
|
||||
var btcData = results[symbol + '/BTC'];
|
||||
marketData['BTC'].last = new Number(btcData.lasttradeprice);
|
||||
marketData['BTC'].baseVolume = new Number(marketData['BTC'].last / btcData.volume);
|
||||
marketData['BTC'].quoteVolume = new Number(btcData.volume);
|
||||
if (btcData.sellorders != null)
|
||||
marketData['BTC'].ask = new Number(btcData.sellorders[0].price);
|
||||
if (btcData.buyorders != null) {
|
||||
marketData['BTC'].bid = new Number(btcData.buyorders[0].price);
|
||||
var limit = new Number(marketData['BTC'].bid * portalConfig.profitSwitch.depth);
|
||||
var depth = new Number(0);
|
||||
var totalQty = new Number(0);
|
||||
btcData['buyorders'].forEach(function(order){
|
||||
var price = new Number(order.price);
|
||||
var qty = new Number(order.quantity);
|
||||
if (price >= limit){
|
||||
depth += (qty * price);
|
||||
totalQty += qty;
|
||||
}
|
||||
});
|
||||
marketData['BTC'].depth = depth;
|
||||
if (totalQty > 0)
|
||||
marketData['BTC'].weightedBid = new Number(depth / totalQty);
|
||||
}
|
||||
}
|
||||
|
||||
if (results && results.hasOwnProperty(symbol + '/LTC')) {
|
||||
if (!marketData.hasOwnProperty('LTC'))
|
||||
marketData['LTC'] = {};
|
||||
|
||||
var ltcData = results[symbol + '/LTC'];
|
||||
marketData['LTC'].last = new Number(ltcData.lasttradeprice);
|
||||
marketData['LTC'].baseVolume = new Number(marketData['LTC'].last / ltcData.volume);
|
||||
marketData['LTC'].quoteVolume = new Number(ltcData.volume);
|
||||
if (ltcData.sellorders != null)
|
||||
marketData['LTC'].ask = new Number(ltcData.sellorders[0].price);
|
||||
if (ltcData.buyorders != null) {
|
||||
marketData['LTC'].bid = new Number(ltcData.buyorders[0].price);
|
||||
var limit = new Number(marketData['LTC'].bid * portalConfig.profitSwitch.depth);
|
||||
var depth = new Number(0);
|
||||
var totalQty = new Number(0);
|
||||
ltcData['buyorders'].forEach(function(order){
|
||||
var price = new Number(order.price);
|
||||
var qty = new Number(order.quantity);
|
||||
if (price >= limit){
|
||||
depth += (qty * price);
|
||||
totalQty += qty;
|
||||
}
|
||||
});
|
||||
marketData['LTC'].depth = depth;
|
||||
if (totalQty > 0)
|
||||
marketData['LTC'].weightedBid = new Number(depth / totalQty);
|
||||
}
|
||||
}
|
||||
});
|
||||
taskCallback();
|
||||
});
|
||||
}
|
||||
], function(err){
|
||||
if (err){
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
callback(null);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
this.getProfitDataMintpal = function(callback){
|
||||
async.series([
|
||||
function(taskCallback){
|
||||
mintpalApi.getTicker(function(err, response){
|
||||
if (err || !response.data){
|
||||
taskCallback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(symbolToAlgorithmMap).forEach(function(symbol){
|
||||
response.data.forEach(function(market){
|
||||
var exchangeInfo = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo;
|
||||
if (!exchangeInfo.hasOwnProperty('Mintpal'))
|
||||
exchangeInfo['Mintpal'] = {};
|
||||
|
||||
var marketData = exchangeInfo['Mintpal'];
|
||||
|
||||
if (market.exchange == 'BTC' && market.code == symbol) {
|
||||
if (!marketData.hasOwnProperty('BTC'))
|
||||
marketData['BTC'] = {};
|
||||
|
||||
marketData['BTC'].last = new Number(market.last_price);
|
||||
marketData['BTC'].baseVolume = new Number(market['24hvol']);
|
||||
marketData['BTC'].quoteVolume = new Number(market['24hvol'] / market.last_price);
|
||||
marketData['BTC'].ask = new Number(market.top_ask);
|
||||
marketData['BTC'].bid = new Number(market.top_bid);
|
||||
}
|
||||
|
||||
if (market.exchange == 'LTC' && market.code == symbol) {
|
||||
if (!marketData.hasOwnProperty('LTC'))
|
||||
marketData['LTC'] = {};
|
||||
|
||||
marketData['LTC'].last = new Number(market.last_price);
|
||||
marketData['LTC'].baseVolume = new Number(market['24hvol']);
|
||||
marketData['LTC'].quoteVolume = new Number(market['24hvol'] / market.last_price);
|
||||
marketData['LTC'].ask = new Number(market.top_ask);
|
||||
marketData['LTC'].bid = new Number(market.top_bid);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
taskCallback();
|
||||
});
|
||||
},
|
||||
function(taskCallback){
|
||||
var depthTasks = [];
|
||||
Object.keys(symbolToAlgorithmMap).forEach(function(symbol){
|
||||
var marketData = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo['Mintpal'];
|
||||
if (marketData.hasOwnProperty('BTC') && marketData['BTC'].bid > 0){
|
||||
depthTasks.push(function(callback){
|
||||
_this.getMarketDepthFromMintpal('BTC', symbol, marketData['BTC'].bid, callback)
|
||||
});
|
||||
}
|
||||
if (marketData.hasOwnProperty('LTC') && marketData['LTC'].bid > 0){
|
||||
depthTasks.push(function(callback){
|
||||
_this.getMarketDepthFromMintpal('LTC', symbol, marketData['LTC'].bid, callback)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (!depthTasks.length){
|
||||
taskCallback();
|
||||
return;
|
||||
}
|
||||
async.series(depthTasks, function(err){
|
||||
if (err){
|
||||
taskCallback(err);
|
||||
return;
|
||||
}
|
||||
taskCallback();
|
||||
});
|
||||
}
|
||||
], function(err){
|
||||
if (err){
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
this.getMarketDepthFromMintpal = function(symbolA, symbolB, coinPrice, callback){
|
||||
mintpalApi.getBuyOrderBook(symbolA, symbolB, function(err, response){
|
||||
if (err){
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
var depth = new Number(0);
|
||||
if (response.hasOwnProperty('data')){
|
||||
var totalQty = new Number(0);
|
||||
response['data'].forEach(function(order){
|
||||
var price = new Number(order.price);
|
||||
var limit = new Number(coinPrice * portalConfig.profitSwitch.depth);
|
||||
var qty = new Number(order.amount);
|
||||
// only measure the depth down to configured depth
|
||||
if (price >= limit){
|
||||
depth += (qty * price);
|
||||
totalQty += qty;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var marketData = profitStatus[symbolToAlgorithmMap[symbolB]][symbolB].exchangeInfo['Mintpal'];
|
||||
marketData[symbolA].depth = depth;
|
||||
if (totalQty > 0)
|
||||
marketData[symbolA].weightedBid = new Number(depth / totalQty);
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
this.getCoindDaemonInfo = function(callback){
|
||||
var daemonTasks = [];
|
||||
Object.keys(profitStatus).forEach(function(algo){
|
||||
Object.keys(profitStatus[algo]).forEach(function(symbol){
|
||||
var coinName = profitStatus[algo][symbol].name;
|
||||
var poolConfig = poolConfigs[coinName];
|
||||
var daemonConfig = poolConfig.shareProcessing.internal.daemon;
|
||||
daemonTasks.push(function(callback){
|
||||
_this.getDaemonInfoForCoin(symbol, daemonConfig, callback)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (daemonTasks.length == 0){
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
async.series(daemonTasks, function(err){
|
||||
if (err){
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
this.getDaemonInfoForCoin = function(symbol, cfg, callback){
|
||||
var daemon = new Stratum.daemon.interface([cfg]);
|
||||
daemon.once('online', function(){
|
||||
daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], function(result){
|
||||
if (result[0].error != null){
|
||||
logger.error(logSystem, symbol, 'Error while reading daemon info: ' + JSON.stringify(result[0]));
|
||||
callback(null); // fail gracefully for each coin
|
||||
return;
|
||||
}
|
||||
var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol];
|
||||
var response = result[0].response;
|
||||
|
||||
// some shitcoins dont provide target, only bits, so we need to deal with both
|
||||
var target = response.target ? bignum(response.target, 16) : util.bignumFromBitsHex(response.bits);
|
||||
coinStatus.difficulty = parseFloat((diff1.toNumber() / target.toNumber()).toFixed(9));
|
||||
logger.debug(logSystem, symbol, 'difficulty is ' + coinStatus.difficulty);
|
||||
|
||||
coinStatus.reward = new Number(response.coinbasevalue / 100000000);
|
||||
callback(null);
|
||||
});
|
||||
}).once('connectionFailed', function(error){
|
||||
logger.error(logSystem, symbol, JSON.stringify(error));
|
||||
callback(null); // fail gracefully for each coin
|
||||
}).on('error', function(error){
|
||||
logger.error(logSystem, symbol, JSON.stringify(error));
|
||||
callback(null); // fail gracefully for each coin
|
||||
}).init();
|
||||
};
|
||||
|
||||
|
||||
this.getMiningRate = function(callback){
|
||||
var daemonTasks = [];
|
||||
Object.keys(profitStatus).forEach(function(algo){
|
||||
Object.keys(profitStatus[algo]).forEach(function(symbol){
|
||||
var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol];
|
||||
coinStatus.blocksPerMhPerHour = new Number(86400 / ((coinStatus.difficulty * Math.pow(2,32)) / (1 * 1000 * 1000)));
|
||||
coinStatus.coinsPerMhPerHour = new Number(coinStatus.reward * coinStatus.blocksPerMhPerHour);
|
||||
});
|
||||
});
|
||||
callback(null);
|
||||
};
|
||||
|
||||
|
||||
this.switchToMostProfitableCoins = function() {
|
||||
Object.keys(profitStatus).forEach(function(algo) {
|
||||
var algoStatus = profitStatus[algo];
|
||||
|
||||
var bestExchange;
|
||||
var bestCoin;
|
||||
var bestBtcPerMhPerHour = new Number(0);
|
||||
|
||||
Object.keys(profitStatus[algo]).forEach(function(symbol) {
|
||||
var coinStatus = profitStatus[algo][symbol];
|
||||
|
||||
Object.keys(coinStatus.exchangeInfo).forEach(function(exchange){
|
||||
var exchangeData = coinStatus.exchangeInfo[exchange];
|
||||
if (exchangeData.hasOwnProperty('BTC') && exchangeData['BTC'].hasOwnProperty('weightedBid')){
|
||||
var btcPerMhPerHour = new Number(exchangeData['BTC'].weightedBid * coinStatus.coinsPerMhPerHour);
|
||||
if (btcPerMhPerHour > bestBtcPerMhPerHour){
|
||||
bestBtcPerMhPerHour = btcPerMhPerHour;
|
||||
bestExchange = exchange;
|
||||
bestCoin = profitStatus[algo][symbol].name;
|
||||
}
|
||||
coinStatus.btcPerMhPerHour = btcPerMhPerHour;
|
||||
logger.debug(logSystem, 'CALC', 'BTC/' + symbol + ' on ' + exchange + ' with ' + coinStatus.btcPerMhPerHour.toFixed(8) + ' BTC/day per Mh/s');
|
||||
}
|
||||
if (exchangeData.hasOwnProperty('LTC') && exchangeData['LTC'].hasOwnProperty('weightedBid')){
|
||||
var btcPerMhPerHour = new Number((exchangeData['LTC'].weightedBid * coinStatus.coinsPerMhPerHour) * exchangeData['LTC'].ltcToBtc);
|
||||
if (btcPerMhPerHour > bestBtcPerMhPerHour){
|
||||
bestBtcPerMhPerHour = btcPerMhPerHour;
|
||||
bestExchange = exchange;
|
||||
bestCoin = profitStatus[algo][symbol].name;
|
||||
}
|
||||
coinStatus.btcPerMhPerHour = btcPerMhPerHour;
|
||||
logger.debug(logSystem, 'CALC', 'LTC/' + symbol + ' on ' + exchange + ' with ' + coinStatus.btcPerMhPerHour.toFixed(8) + ' BTC/day per Mh/s');
|
||||
}
|
||||
});
|
||||
});
|
||||
logger.debug(logSystem, 'RESULT', 'Best coin for ' + algo + ' is ' + bestCoin + ' on ' + bestExchange + ' with ' + bestBtcPerMhPerHour.toFixed(8) + ' BTC/day per Mh/s');
|
||||
if (portalConfig.coinSwitchListener.enabled){
|
||||
var client = net.connect(portalConfig.coinSwitchListener.port, portalConfig.coinSwitchListener.host, function () {
|
||||
client.write(JSON.stringify({
|
||||
password: portalConfig.coinSwitchListener.password,
|
||||
coin: bestCoin
|
||||
}) + '\n');
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
var checkProfitability = function(){
|
||||
logger.debug(logSystem, 'Check', 'Collecting profitability data.');
|
||||
|
||||
profitabilityTasks = [];
|
||||
if (portalConfig.profitSwitch.usePoloniex)
|
||||
profitabilityTasks.push(_this.getProfitDataPoloniex);
|
||||
|
||||
if (portalConfig.profitSwitch.useCryptsy)
|
||||
profitabilityTasks.push(_this.getProfitDataCryptsy);
|
||||
|
||||
if (portalConfig.profitSwitch.useMintpal)
|
||||
profitabilityTasks.push(_this.getProfitDataMintpal);
|
||||
|
||||
profitabilityTasks.push(_this.getCoindDaemonInfo);
|
||||
profitabilityTasks.push(_this.getMiningRate);
|
||||
|
||||
// has to be series
|
||||
async.series(profitabilityTasks, function(err){
|
||||
if (err){
|
||||
logger.error(logSystem, 'Check', 'Error while checking profitability: ' + err);
|
||||
return;
|
||||
}
|
||||
//
|
||||
// TODO offer support for a userConfigurable function for deciding on coin to override the default
|
||||
//
|
||||
_this.switchToMostProfitableCoins();
|
||||
});
|
||||
};
|
||||
setInterval(checkProfitability, portalConfig.profitSwitch.updateInterval * 1000);
|
||||
|
||||
};
|
|
@ -42,9 +42,12 @@
|
|||
"compression": "*",
|
||||
"dot": "*",
|
||||
"colors": "*",
|
||||
"node-watch": "*"
|
||||
"node-watch": "*",
|
||||
"request": "*",
|
||||
"nonce": "*",
|
||||
"bignum": "*"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue