diff --git a/conf-sample.js b/conf-sample.js index 532402849d..402d9c0a0c 100644 --- a/conf-sample.js +++ b/conf-sample.js @@ -1,57 +1,83 @@ var c = module.exports = {} -// COMMONLY TWEAKED VARIABLES: +// mongo configuration +c.mongo = {} +c.mongo.host = 'localhost' +c.mongo.port = 27017 +c.mongo.db = 'zenbot4' +c.mongo.username = null +c.mongo.password = null // default selector. only used if omitting [selector] argument from a command. c.selector = 'gdax.BTC-USD' // name of default trade strategy c.strategy = 'trend_ema' + +// Exchange API keys: + +// to enable GDAX trading, enter your API credentials: +c.gdax = {} +c.gdax.key = 'YOUR-API-KEY' +c.gdax.b64secret = 'YOUR-BASE64-SECRET' +c.gdax.passphrase = 'YOUR-PASSPHRASE' + +// to enable Poloniex trading, enter your API credentials: +c.poloniex = {} +c.poloniex.key = 'YOUR-API-KEY' +c.poloniex.secret = 'YOUR-SECRET' + +// to enable Bitfinex trading, enter your API credentials: +c.bitfinex = {} +c.bitfinex.key = 'YOUR-API-KEY' +c.bitfinex.secret = 'YOUR-SECRET' +// May use 'exchange' or 'trading' wallet balances. However margin trading may not work...read the API documentation. +c.bitfinex.wallet = 'exchange' + +// Optional stop-order triggers: + // sell if price drops below this % of bought price (0 to disable) c.sell_stop_pct = 0 // buy if price surges above this % of sold price (0 to disable) c.buy_stop_pct = 0 -// enable trailing sell stop when reaching this % profit (0 to disable. note: in extreme bull markets, turn this off for max profit!) -c.profit_stop_enable_pct = 10 +// enable trailing sell stop when reaching this % profit (0 to disable) +c.profit_stop_enable_pct = 0 // maintain a trailing stop this % below the high-water mark of profit c.profit_stop_pct = 1 -// avoid selling at a loss below this pct -c.max_sell_loss_pct = 25 + +// Order execution rules: + // avoid trading at a slippage above this pct -c.max_slippage_pct = 2 +c.max_slippage_pct = 5 // buy with this % of currency balance c.buy_pct = 99 // sell with this % of asset balance c.sell_pct = 99 +// ms to adjust non-filled order after +c.order_adjust_time = 30000 +// avoid selling at a loss below this pct +c.max_sell_loss_pct = 25 +// ms to poll order status +c.order_poll_time = 5000 +// ms to wait for settlement (after an order cancel) +c.wait_for_settlement = 5000 +// ms to wait for settlement (after a funds on hold error) +c.wait_more_for_settlement = 60000 // % to mark up or down price for orders c.markup_pct = 0 -// LESS-COMMONLY TWEAKED VARAIBLES: +// Misc options: -// mongo configuration -c.mongo_host = 'localhost' -c.mongo_port = 27017 -c.mongo_db = 'zenbot4' -c.mongo_username = null -c.mongo_password = null // default # days for backfill and sim commands c.days = 90 -// fee assessed for market-type orders. (note: zenbot normally attempts to use limit-type orders to avoid fees) -c.fee_pct = 0.25 // ms to poll new trades at c.poll_trades = 30000 // amount of currency to start simulations with c.currency_capital = 1000 // amount of asset to start simulations with c.asset_capital = 0 -// ms to poll order status -c.order_poll_time = 5000 -// ms to adjust non-filled order after -c.order_adjust_time = 30000 // for sim, reverse time at the end of the graph, normalizing buy/hold to 0 c.symmetrical = false // number of periods to calculate RSI at c.rsi_periods = 14 -// ms to wait for settlement (after an order cancel) when funds are still on hold -c.wait_for_settlement = 5000 // period to record balances for stats -c.balance_snapshot_period = '1h' +c.balance_snapshot_period = '15m' diff --git a/extensions/bitfinex/_codemap.js b/extensions/bitfinex/_codemap.js new file mode 100644 index 0000000000..cb747b9bb2 --- /dev/null +++ b/extensions/bitfinex/_codemap.js @@ -0,0 +1,6 @@ +module.exports = { + _ns: 'zenbot', + + 'exchanges.bitfinex': require('./exchange'), + 'exchanges.list[]': '#exchanges.bitfinex' +} diff --git a/extensions/bitfinex/exchange.js b/extensions/bitfinex/exchange.js new file mode 100644 index 0000000000..9342cde95b --- /dev/null +++ b/extensions/bitfinex/exchange.js @@ -0,0 +1,135 @@ +var BFX = require('bitfinex-api-node') + , _ = require('lodash') + , path = require('path') + , n = require('numbro') + +module.exports = function container (get, set, clear) { + var c = get('conf') + + var public_client, authed_client + + function publicClient () { + if (!public_client) public_client = new BFX.APIRest() + return public_client + } + + function authedClient () { + if (!authed_client) { + if (!c.bitfinex.key || c.bitfinex.key === 'YOUR-API-KEY') { + throw new Error('please configure your Bitfinex credentials in ' + path.resolve(__dirname, 'conf.js')) + } + authed_client = new BFX.APIRest(c.bitfinex.key, c.bitfinex.secret) + } + return authed_client + } + + function joinProduct (product_id) { + return product_id.split('-')[0] + '' + product_id.split('-')[1] + } + + return { + name: 'bitfinex', +// historyScan: 'backward', + makerFee: 0.1, + + getProducts: function () { + return require('./products.json') + }, + + getTrades: function (opts, cb) { + var client = publicClient() + var args = joinProduct(opts.product_id) +// var path = args.pair; +// if(opts.from) +// path += '?limit_trades=49999'; + client.trades(args, function (err, body) { + if (err) return cb(err) + var trades = _.map(body, function(trade) { + return { + trade_id: trade.tid, + time: n(trade.timestamp).multiply(1000).value(), + size: Number(trade.amount), + price: Number(trade.price), + side: trade.type + } + }) + cb(null, trades) + }) + }, + + getBalance: function (opts, cb) { + var client = authedClient() + client.wallet_balances(function (err, body) { + if (err) return cb(err) + var balance = {asset: 0, currency: 0} + var accounts = _(body).filter(function (body) { return body.type === c.bitfinex.wallet }).forEach(function (account) { + if (account.currency.toUpperCase() === opts.currency) { + balance.currency = account.amount + balance.currency_hold = (account.amount - account.available) + } + else if (account.currency === opts.asset) { + balance.asset = account.amount + balance.asset_hold = (account.amount - account.available) + } + }) + cb(null, balance) + }) + }, + + getQuote: function (opts, cb) { + var client = publicClient() + var pair = joinProduct(opts.product_id) + client.ticker(pair, function (err, body) { + if (err) return cb(err) + cb(null, {bid: body.bid, ask: body.ask}) + }) + }, + + cancelOrder: function (opts, cb) { + var client = authedClient() + client.cancel_order(opts.order_id, function (err, body) { + if (err) return cb(err) + cb() + }) + }, + + buy: function (opts, cb) { + var client = authedClient() + if (typeof opts.type === 'undefined') { + opts.type = 'exchange limit' + } + var pair = joinProduct(opts.product_id) + client.new_order(pair, opts.size, opts.price, 'bitfinex', 'buy', opts.type, false, function (err, body) { + if (err) return cb(err) + cb(null, body) + }) +// console.log(pair, opts) + }, + + sell: function (opts, cb) { + var client = authedClient() + if (typeof opts.type === 'undefined') { + opts.type = 'exchange limit' + } + var pair = joinProduct(opts.product_id) + client.new_order(pair, opts.size, opts.price, 'bitfinex', 'sell', opts.type, false, function (err, body) { + if (err) return cb(err) + cb(null, body) + }) +// console.log(pair, opts) + }, + + getOrder: function (opts, cb) { + var client = authedClient() + client.order_status(opts.order_id, function (err, body) { + if (err) return cb(err) + cb(null, body) + }) + }, + + // return the property used for range querying. + getCursor: function (trade) { + return trade.trade_id + } + } +} diff --git a/extensions/bitfinex/products.json b/extensions/bitfinex/products.json new file mode 100644 index 0000000000..043fcea1a4 --- /dev/null +++ b/extensions/bitfinex/products.json @@ -0,0 +1,170 @@ +[ + { + "asset": "BTC", + "currency": "USD", + "min_size": 0.01, + "max_size": 2000, + "increment": 0.01, + "label": "BTC/USD" + }, + { + "asset": "LTC", + "currency": "USD", + "min_size": 0.1, + "max_size": 5000, + "increment": 0.1, + "label": "LTC/USD" + }, + { + "asset": "LTC", + "currency": "BTC", + "min_size": 0.1, + "max_size": 5000, + "increment": 0.1, + "label": "LTC/BTC" + }, + { + "asset": "ETH", + "currency": "USD", + "min_size": 0.1, + "max_size": 5000, + "increment": 0.1, + "label": "ETH/USD" + }, + { + "asset": "ETH", + "currency": "BTC", + "min_size": 0.1, + "max_size": 5000, + "increment": 0.1, + "label": "ETH/BTC" + }, + { + "asset": "ETC", + "currency": "BTC", + "min_size": 0.1, + "max_size": 100000, + "increment": 0.1, + "label": "ETC/BTC" + }, + { + "asset": "ETC", + "currency": "USD", + "min_size": 0.1, + "max_size": 100000, + "increment": 0.1, + "label": "ETC/USD" + }, + { + "asset": "RRT", + "currency": "USD", + "min_size": 0.1, + "max_size": 100000, + "increment": 0.1, + "label": "RRT/USD" + }, + { + "asset": "RRT", + "currency": "BTC", + "min_size": 0.1, + "max_size": 100000, + "increment": 0.1, + "label": "RRT/BTC" + }, + { + "asset": "ZEC", + "currency": "USD", + "min_size": 0.0001, + "max_size": 20000, + "increment": 0.0001, + "label": "ZEC/USD" + }, + { + "asset": "ZEC", + "currency": "BTC", + "min_size": 0.0001, + "max_size": 20000, + "increment": 0.0001, + "label": "ZEC/BTC" + }, + { + "asset": "XMR", + "currency": "USD", + "min_size": 0.1, + "max_size": 5000, + "increment": 0.1, + "label": "XMR/USD" + }, + { + "asset": "XMR", + "currency": "BTC", + "min_size": 0.1, + "max_size": 5000, + "increment": 0.1, + "label": "XMR/BTC" + }, + { + "asset": "DSH", + "currency": "USD", + "min_size": 0.01, + "max_size": 5000, + "increment": 0.01, + "label": "DSH/USD" + }, + { + "asset": "DSH", + "currency": "BTC", + "min_size": 0.01, + "max_size": 5000, + "increment": 0.01, + "label": "DSH/BTC" + }, + { + "asset": "BCC", + "currency": "BTC", + "min_size": 0.01, + "max_size": 2000, + "increment": 0.01, + "label": "BCC/BTC" + }, + { + "asset": "BCU", + "currency": "BTC", + "min_size": 0.01, + "max_size": 2000, + "increment": 0.01, + "label": "BCU/BTC" + }, + { + "asset": "BCC", + "currency": "USD", + "min_size": 0.01, + "max_size": 2000, + "increment": 0.01, + "label": "BCC/USD" + }, + { + "asset": "BCU", + "currency": "USD", + "min_size": 0.01, + "max_size": 2000, + "increment": 0.01, + "label": "BCU/USD" + }, + { + "asset": "XRP", + "currency": "USD", + "min_size": 0.1, + "max_size": 200000, + "increment": 0.1, + "label": "XRP/USD" + }, + { + "asset": "XRP", + "currency": "BTC", + "min_size": 0.1, + "max_size": 200000, + "increment": 0.1, + "label": "XRP/BTC" + } +] diff --git a/extensions/bitfinex/update-products.sh b/extensions/bitfinex/update-products.sh new file mode 100644 index 0000000000..445a58b7c9 --- /dev/null +++ b/extensions/bitfinex/update-products.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env node +var request = require('micro-request') +request('https://api.bitfinex.com/v1/symbols_details', {headers: {'User-Agent': 'zenbot/4'}}, function (err, resp, body) { + if (err) throw err + if (resp.statusCode !== 200) { + var err = new Error('non-200 status: ' + resp.statusCode) + err.code = 'HTTP_STATUS' + err.body = body + console.error(err) + process.exit(1) + } + var products = [] + body.forEach(function (product) { + products.push({ +// id: product.pair, + asset: product.pair.substring(0, 3).toUpperCase(), + currency: product.pair.substring(3, 6).toUpperCase(), + min_size: Number(product.minimum_order_size), + max_size: Number(product.maximum_order_size), + increment: Number(product.minimum_order_size), + label: product.pair.substring(0, 3).toUpperCase() + '/' + product.pair.substring(3, 6).toUpperCase() + }) + }) + var target = require('path').resolve(__dirname, 'products.json') + require('fs').writeFileSync(target, JSON.stringify(products, null, 2)) + console.log('wrote', target) + process.exit() +}) diff --git a/package.json b/package.json index 1c0ba4a216..15a45afb2f 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "colors": "^1.1.2", "commander": "^2.9.0", "gdax": "^0.4.2", + "bitfinex-api-node": "^0.2.9", "glob": "^7.1.1", "idgen": "^2.0.2", "micro-request": "^666.0.10",