diff --git a/extensions/kraken/_codemap.js b/extensions/kraken/_codemap.js new file mode 100644 index 0000000000..91fad4a3fc --- /dev/null +++ b/extensions/kraken/_codemap.js @@ -0,0 +1,6 @@ +module.exports = { + _ns: 'zenbot', + + 'exchanges.kraken': require('./exchange'), + 'exchanges.list[]': '#exchanges.kraken' +} \ No newline at end of file diff --git a/extensions/kraken/exchange.js b/extensions/kraken/exchange.js new file mode 100644 index 0000000000..fae61e5db2 --- /dev/null +++ b/extensions/kraken/exchange.js @@ -0,0 +1,229 @@ +var KrakenClient = require('kraken-api'), + path = require('path'), + moment = require('moment'), + n = require('numbro'), + colors = require('colors') + +module.exports = function container(get, set, clear) { + var c = get('conf') + + var public_client, authed_client + + function publicClient() { + if (!public_client) public_client = new KrakenClient() + return public_client; + } + + function authedClient() { + if (!authed_client) { + if (!c.kraken || !c.kraken.key || c.kraken.key === 'YOUR-API-KEY') { + throw new Error('please configure your Kraken credentials in conf.js') + } + authed_client = new KrakenClient(c.kraken.key, c.kraken.secret) + } + return authed_client; + } + + function joinProduct(product_id) { + return product_id.split('-')[0] + product_id.split('-')[1] + } + + function retry(method, args) { + if (method !== 'getTrades') { + console.error(('\nKraken API is down! unable to call ' + method + ', retrying in 10s').red) + } + setTimeout(function () { + exchange[method].apply(exchange, args) + }, 10000) + } + + var exchange = { + name: 'kraken', + historyScan: 'backward', + makerFee: 0.16, + + getProducts: function () { + return require('./products.json') + }, + + getTrades: function (opts, cb) { + var func_args = [].slice.call(arguments) + var client = publicClient(); + var args = { + pair: joinProduct(opts.product_id) + } + if (opts.from) { + args.since = parseFloat(opts.from) * 1000000000; + } + + client.api('Trades', args, function (error, data) { + if (error) { + return retry('getTrades', func_args) + } + if (data.error.length) { + return cb(err); + } + + var trades = []; + Object.keys(data.result[args.pair]).forEach(function (i) { + var trade = data.result[args.pair][i]; + if (!opts.to || (parseFloat(opts.to) >= parseFloat(trade[2]))) { + trades.push({ + trade_id: trade[2] + trade[1] + trade[0], + time: moment.utc(trade[2]).valueOf(), + size: parseFloat(trade[1]), + price: parseFloat(trade[0]), + side: trade[3] == 'b' ? 'buy' : 'sell' + }); + } + }); + cb(null, trades); + }); + }, + + getBalance: function (opts, cb) { + var args = [].slice.call(arguments) + var client = authedClient() + client.api('Balance', null, function (err, data) { + + var balance = { + asset: 0, + currency: 0 + }; + if (error || data.error.length) { + console.error('\ggetBalance error:') + console.error(data) + return retry('getBalance', args) + } + + if (data.result[opts.currency]) { + balance.currency = n(data.result[opts.currency]).format('0.00000000') + } + if (data.result[opts.asset]) { + balance.asset = n(data.result[opts.asset]).format('0.00000000') + } + + cb(null, balance); + + }); + }, + + getQuote: function (opts, cb) { + var args = [].slice.call(arguments); + var client = publicClient(); + + client.api('Ticker', { + pair: joinProduct(opts.product_id) + }, function (error, data) { + if (error || data.error.length) { + console.error('\ggetQuote error:') + console.error(data) + return retry('getQuote', args) + } + cb(null, { + bid: data.result[opts.pair].b[0], + ask: data.result[opts.pair].a[0], + }) + }); + }, + + cancelOrder: function (opts, cb) { + var args = [].slice.call(arguments) + var client = authedClient(); + client.api('CancelOrder', { + txid: opts.order_id + }, function (error, data) { + if (error) { + console.error('\cancelOrder error:') + console.error(data) + return retry('cancelOrder', args) + } + if (data.error.length) { + error = new Error('unable to cancel order') + error.body = data.error + } + cb(error) + }); + }, + + trade: function (type, opts, cb) { + var args = [].slice.call(arguments) + var client = authedClient() + var params = { + pair: joinProduct(opts.product_id), + type: type, + orderType: 'limit', + price: opts.price, + volume: opts.size, + oflags: opts.post_only === true ? 'post' : undefined + } + client.api('AddOrder', params, function (err, data) { + if (err) { + return retry('trade', args); + } + var order = { + id: data.result ? data.result.txid[0] : null, + status: 'open', + price: opts.price, + size: opts.volume, + post_only: !!opts.post_only, + created_at: new Date().getTime(), + filled_size: '0' + }; + if (data.error.length) { + order.status = 'rejected' + order.reject_reason = data.error.join(','); + return cb(null, order); + } + cb(null, order) + }) + }, + + buy: function (opts, cb) { + exchange.trade('buy', opts, cb) + }, + + sell: function (opts, cb) { + exchange.trade('sell', opts, cb) + }, + + getOrder: function (opts, cb) { + var args = [].slice.call(arguments) + var client = authedClient() + var params = { + txid: opts.order_id + } + client.api('QueryOrders', params, function (error, data) { + if (error) { + return retry('getOrder', args); + } + if (data.error.length) { + return cb('\nQueryOrders result:'); + } + var orderData = data.result[params.txid]; + + if(!orderData) { + return cb('Order not found'); + } + + var order = { + id: orderData.refid, + status: orderData.status, + price: orderData.price, + size: orderData.vol, + post_only: orderData.oflags.match(/post/), + created_at: orderData.opentm, + filled_size: parseFloat(orderData.vol) - parseFloat(orderData.vol_exec) + }; + + cb(null, order); + }) + }, + + // return the property used for range querying. + getCursor: function (trade) { + return Math.floor((trade.time || trade) / 1000) + } + } + return exchange +} \ No newline at end of file diff --git a/extensions/kraken/products.json b/extensions/kraken/products.json new file mode 100644 index 0000000000..04999c9e9a --- /dev/null +++ b/extensions/kraken/products.json @@ -0,0 +1,482 @@ +[ + { + "asset": "DASH", + "currency": "ZEUR", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "DASH/EUR" + }, + { + "asset": "DASH", + "currency": "ZUSD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "DASH/USD" + }, + { + "asset": "DASH", + "currency": "XXBT", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "DASH/XBT" + }, + { + "asset": "GNO", + "currency": "XETH", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "GNO/ETH" + }, + { + "asset": "GNO", + "currency": "ZEUR", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "GNO/EUR" + }, + { + "asset": "GNO", + "currency": "ZUSD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "GNO/USD" + }, + { + "asset": "GNO", + "currency": "XXBT", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "GNO/XBT" + }, + { + "asset": "USDT", + "currency": "ZUSD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "USDT/USD" + }, + { + "asset": "XETC", + "currency": "XETH", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ETC/ETH" + }, + { + "asset": "XETC", + "currency": "XXBT", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ETC/XBT" + }, + { + "asset": "XETC", + "currency": "ZEUR", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ETC/EUR" + }, + { + "asset": "XETC", + "currency": "ZUSD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ETC/USD" + }, + { + "asset": "XETH", + "currency": "XXBT", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ETH/XBT" + }, + { + "asset": "XETH", + "currency": "XXBT", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ETH/XBT" + }, + { + "asset": "XETH", + "currency": "ZCAD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ETH/CAD" + }, + { + "asset": "XETH", + "currency": "ZCAD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ETH/CAD" + }, + { + "asset": "XETH", + "currency": "ZEUR", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ETH/EUR" + }, + { + "asset": "XETH", + "currency": "ZEUR", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ETH/EUR" + }, + { + "asset": "XETH", + "currency": "ZGBP", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ETH/GBP" + }, + { + "asset": "XETH", + "currency": "ZGBP", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ETH/GBP" + }, + { + "asset": "XETH", + "currency": "ZJPY", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ETH/JPY" + }, + { + "asset": "XETH", + "currency": "ZJPY", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ETH/JPY" + }, + { + "asset": "XETH", + "currency": "ZUSD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ETH/USD" + }, + { + "asset": "XETH", + "currency": "ZUSD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ETH/USD" + }, + { + "asset": "XICN", + "currency": "XETH", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ICN/ETH" + }, + { + "asset": "XICN", + "currency": "XXBT", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ICN/XBT" + }, + { + "asset": "XLTC", + "currency": "XXBT", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "LTC/XBT" + }, + { + "asset": "XLTC", + "currency": "ZEUR", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "LTC/EUR" + }, + { + "asset": "XLTC", + "currency": "ZUSD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "LTC/USD" + }, + { + "asset": "XMLN", + "currency": "XETH", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "MLN/ETH" + }, + { + "asset": "XMLN", + "currency": "XXBT", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "MLN/XBT" + }, + { + "asset": "XREP", + "currency": "XETH", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "REP/ETH" + }, + { + "asset": "XREP", + "currency": "XXBT", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "REP/XBT" + }, + { + "asset": "XREP", + "currency": "ZEUR", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "REP/EUR" + }, + { + "asset": "XREP", + "currency": "ZUSD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "REP/USD" + }, + { + "asset": "XXBT", + "currency": "ZCAD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XBT/CAD" + }, + { + "asset": "XXBT", + "currency": "ZCAD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XBT/CAD" + }, + { + "asset": "XXBT", + "currency": "ZEUR", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XBT/EUR" + }, + { + "asset": "XXBT", + "currency": "ZEUR", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XBT/EUR" + }, + { + "asset": "XXBT", + "currency": "ZGBP", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XBT/GBP" + }, + { + "asset": "XXBT", + "currency": "ZGBP", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XBT/GBP" + }, + { + "asset": "XXBT", + "currency": "ZJPY", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XBT/JPY" + }, + { + "asset": "XXBT", + "currency": "ZJPY", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XBT/JPY" + }, + { + "asset": "XXBT", + "currency": "ZUSD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XBT/USD" + }, + { + "asset": "XXBT", + "currency": "ZUSD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XBT/USD" + }, + { + "asset": "XXDG", + "currency": "XXBT", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XDG/XBT" + }, + { + "asset": "XXLM", + "currency": "XXBT", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XLM/XBT" + }, + { + "asset": "XXLM", + "currency": "ZEUR", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XLM/EUR" + }, + { + "asset": "XXLM", + "currency": "ZUSD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XLM/USD" + }, + { + "asset": "XXMR", + "currency": "XXBT", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XMR/XBT" + }, + { + "asset": "XXMR", + "currency": "ZEUR", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XMR/EUR" + }, + { + "asset": "XXMR", + "currency": "ZUSD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XMR/USD" + }, + { + "asset": "XXRP", + "currency": "XXBT", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XRP/XBT" + }, + { + "asset": "XXRP", + "currency": "ZCAD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XRP/CAD" + }, + { + "asset": "XXRP", + "currency": "ZEUR", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XRP/EUR" + }, + { + "asset": "XXRP", + "currency": "ZJPY", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XRP/JPY" + }, + { + "asset": "XXRP", + "currency": "ZUSD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "XRP/USD" + }, + { + "asset": "XZEC", + "currency": "XXBT", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ZEC/XBT" + }, + { + "asset": "XZEC", + "currency": "ZEUR", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ZEC/EUR" + }, + { + "asset": "XZEC", + "currency": "ZUSD", + "min_total": "0.0001", + "max_size": null, + "increment": "0.00000001", + "label": "ZEC/USD" + } +] \ No newline at end of file diff --git a/extensions/kraken/update-products.sh b/extensions/kraken/update-products.sh new file mode 100755 index 0000000000..09454706c9 --- /dev/null +++ b/extensions/kraken/update-products.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env node + +var KrakenClient = require('kraken-api'); +var kraken = new KrakenClient(); + +var mapping; +var products = []; + +function addProduct(base, quote, altname) { + products.push({ + asset: base, + currency: quote, + min_total: '0.0001', + max_size: null, + increment: '0.00000001', + label: getPair(base) + '/' + getPair(quote) + }) +} + +function getPair(name) { + return mapping[name].altname; +}; + +kraken.api('Assets', null, function (error, data) { + if (error) { + console.log(error); + process.exit(1); + } else { + + mapping = data.result; + + kraken.api('AssetPairs', null, function (error, data) { + if (error) { + console.log(error); + process.exit(1); + } else { + Object.keys(data.result).forEach(function (result) { + addProduct(data.result[result].base, data.result[result].quote, data.result[result].altname); + }); + var target = require('path').resolve(__dirname, 'products.json') + require('fs').writeFileSync(target, JSON.stringify(products, null, 2)) + console.log('wrote', target) + process.exit() + } + }); + } +}); \ No newline at end of file diff --git a/package.json b/package.json index 22aabd5c1f..99b41a6729 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "gdax": "^0.4.2", "glob": "^7.1.1", "idgen": "^2.0.2", + "kraken-api": "^0.1.7", "micro-request": "^666.0.10", "minimist": "^1.2.0", "moment": "^2.18.1",