diff --git a/config_example.json b/config_example.json index 9083c59..a801998 100644 --- a/config_example.json +++ b/config_example.json @@ -107,6 +107,7 @@ "depth": 0.90, "usePoloniex": true, "useCryptsy": true, - "useMintpal": true + "useMintpal": true, + "useBittrex": true } } diff --git a/libs/apiBittrex.js b/libs/apiBittrex.js new file mode 100644 index 0000000..9a46d58 --- /dev/null +++ b/libs/apiBittrex.js @@ -0,0 +1,222 @@ +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://bittrex.com/api/v1/public', + PRIVATE_API_URL = 'https://bittrex.com/api/v1/market', + USER_AGENT = 'nomp/node-open-mining-portal' + + // Constructor + function Bittrex(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 'Bittrex: 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 + Bittrex.STRICT_SSL = true; + + // Helper methods + function joinCurrencies(currencyA, currencyB){ + return currencyA + '-' + currencyB; + } + + // Prototype + Bittrex.prototype = { + constructor: Bittrex, + + // 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 = Bittrex.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 + '/getmarketsummaries', + 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 = { + market: joinCurrencies(currencyA, currencyB), + type: 'buy', + depth: '50' + } + var options = { + method: 'GET', + url: PUBLIC_API_URL + '/getorderbook', + qs: parameters + } + + return this._request(options, 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 Bittrex; +}(); diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index 5650ffa..990314e 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -7,6 +7,7 @@ var util = require('stratum-pool/lib/util.js'); var Cryptsy = require('./apiCryptsy.js'); var Poloniex = require('./apiPoloniex.js'); var Mintpal = require('./apiMintpal.js'); +var Bittrex = require('./apiBittrex.js'); var Stratum = require('stratum-pool'); module.exports = function(logger){ @@ -77,6 +78,11 @@ module.exports = function(logger){ // 'API_SECRET' ); + var bittrexApi = new Bittrex( + // 'API_KEY', + // 'API_SECRET' + ); + // // market data collection from Poloniex // @@ -393,6 +399,118 @@ module.exports = function(logger){ }; + this.getProfitDataBittrex = function(callback){ + async.series([ + function(taskCallback){ + bittrexApi.getTicker(function(err, response){ + if (err || !response.result){ + taskCallback(err); + return; + } + + Object.keys(symbolToAlgorithmMap).forEach(function(symbol){ + response.result.forEach(function(market){ + var exchangeInfo = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo; + if (!exchangeInfo.hasOwnProperty('Bittrex')) + exchangeInfo['Bittrex'] = {}; + + var marketData = exchangeInfo['Bittrex']; + var marketPair = market.MarketName.match(/([\w]+)-([\w-_]+)/) + market.exchange = marketPair[1] + market.code = marketPair[2] + if (market.exchange == 'BTC' && market.code == symbol) { + if (!marketData.hasOwnProperty('BTC')) + marketData['BTC'] = {}; + + marketData['BTC'].last = new Number(market.Last); + marketData['BTC'].baseVolume = new Number(market.BaseVolume); + marketData['BTC'].quoteVolume = new Number(market.BaseVolume / market.Last); + marketData['BTC'].ask = new Number(market.Ask); + marketData['BTC'].bid = new Number(market.Bid); + } + + if (market.exchange == 'LTC' && market.code == symbol) { + if (!marketData.hasOwnProperty('LTC')) + marketData['LTC'] = {}; + + marketData['LTC'].last = new Number(market.Last); + marketData['LTC'].baseVolume = new Number(market.BaseVolume); + marketData['LTC'].quoteVolume = new Number(market.BaseVolume / market.Last); + marketData['LTC'].ask = new Number(market.Ask); + marketData['LTC'].bid = new Number(market.Bid); + } + + }); + }); + taskCallback(); + }); + }, + function(taskCallback){ + var depthTasks = []; + Object.keys(symbolToAlgorithmMap).forEach(function(symbol){ + var marketData = profitStatus[symbolToAlgorithmMap[symbol]][symbol].exchangeInfo['Bittrex']; + if (marketData.hasOwnProperty('BTC') && marketData['BTC'].bid > 0){ + depthTasks.push(function(callback){ + _this.getMarketDepthFromBittrex('BTC', symbol, marketData['BTC'].bid, callback) + }); + } + if (marketData.hasOwnProperty('LTC') && marketData['LTC'].bid > 0){ + depthTasks.push(function(callback){ + _this.getMarketDepthFromBittrex('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.getMarketDepthFromBittrex = function(symbolA, symbolB, coinPrice, callback){ + bittrexApi.getOrderBook(symbolA, symbolB, function(err, response){ + if (err){ + callback(err); + return; + } + var depth = new Number(0); + if (response.hasOwnProperty('result')){ + var totalQty = new Number(0); + response['result'].forEach(function(order){ + var price = new Number(order.Rate); + var limit = new Number(coinPrice * portalConfig.profitSwitch.depth); + var qty = new Number(order.Quantity); + // only measure the depth down to configured depth + if (price >= limit){ + depth += (qty * price); + totalQty += qty; + } + }); + } + + var marketData = profitStatus[symbolToAlgorithmMap[symbolB]][symbolB].exchangeInfo['Bittrex']; + 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){ @@ -525,6 +643,9 @@ module.exports = function(logger){ if (portalConfig.profitSwitch.useMintpal) profitabilityTasks.push(_this.getProfitDataMintpal); + if (portalConfig.profitSwitch.useBittrex) + profitabilityTasks.push(_this.getProfitDataBittrex); + profitabilityTasks.push(_this.getCoindDaemonInfo); profitabilityTasks.push(_this.getMiningRate);