From bdf4ed903da818f22f3b031f5f0691aedad85845 Mon Sep 17 00:00:00 2001 From: crubb Date: Mon, 17 Jul 2017 09:37:42 +0200 Subject: [PATCH 1/9] Only do last sell in simExit when there have been trades (#383) --- commands/sim.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/commands/sim.js b/commands/sim.js index 42a2f8c9ea..0de558004a 100644 --- a/commands/sim.js +++ b/commands/sim.js @@ -95,12 +95,14 @@ module.exports = function container (get, set, clear) { }) var options_json = JSON.stringify(options, null, 2) output_lines.push(options_json) - s.my_trades.push({ - price: s.period.close, - size: s.balance.asset, - type: 'sell', - time: s.period.time - }) + if (s.my_trades.length) { + s.my_trades.push({ + price: s.period.close, + size: s.balance.asset, + type: 'sell', + time: s.period.time + }) + } s.balance.currency = n(s.balance.currency).add(n(s.period.close).multiply(s.balance.asset)).format('0.00000000') s.balance.asset = 0 s.lookback.unshift(s.period) From 21e2133d61b84a499a240324def24ee48d4d672e Mon Sep 17 00:00:00 2001 From: Clifford Roche Date: Tue, 18 Jul 2017 06:02:51 -0400 Subject: [PATCH 2/9] Fixes for Quadriga CX trading (#386) * Quadriga CX Fixes * Add amount for takers fee * Minor update to backfill warning text * Fix variable shadowing error * Trades with the same timestamp are now ordered oldest to newest when r eturned from getTrades * Trade volume now returned as number and not a string, prevents NaN on volume report * Filter results on timestamp start to prevent re-reporting the same trades in the results * Sometime opts.from is undefined, and we should return all results. * Formatting --- extensions/exchanges/quadriga/exchange.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/extensions/exchanges/quadriga/exchange.js b/extensions/exchanges/quadriga/exchange.js index cb90f1afd5..1a514b4b8f 100644 --- a/extensions/exchanges/quadriga/exchange.js +++ b/extensions/exchanges/quadriga/exchange.js @@ -50,6 +50,7 @@ module.exports = function container(get, set, clear) { name: 'quadriga', historyScan: 'backward', makerFee: 0.5, + takerFee: 0.5, getProducts: function() { return require('./products.json') @@ -63,21 +64,23 @@ module.exports = function container(get, set, clear) { } var client = publicClient() - client.api('transactions', args, function(err, trades) { + client.api('transactions', args, function(err, body) { if (!shownWarnings) { - console.log('please note: the quadriga api does not support backfilling (trade/paper only).') - console.log('please note: make sure to set the period to 1h') + console.log('please note: the quadriga api does not support backfilling.') + console.log('please note: periods should be set to 1h or less.'); shownWarnings = true; } if (err) return retry('getTrades', func_args, err) - if (trades.error) return retry('getTrades', func_args, trades.error) + if (body.error) return retry('getTrades', func_args, trades.error) - var trades = trades.map(function(trade) { + var trades = body.filter(t => { + return (typeof opts.from === 'undefined') ? true : (moment.unix(t.date).valueOf() > opts.from) + }).reverse().map(function(trade) { return { trade_id: trade.tid, time: moment.unix(trade.date).valueOf(), - size: trade.amount, + size: Number(trade.amount), price: trade.price, side: trade.side } From d8f21fcecf252aba1bd9b1a6831ca47fd525d0ad Mon Sep 17 00:00:00 2001 From: Pierre Dulac Date: Tue, 18 Jul 2017 12:07:12 +0200 Subject: [PATCH 3/9] setRawMode is not supported on some remote consoles (no tty) (#380) --- commands/trade.js | 58 ++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/commands/trade.js b/commands/trade.js index 4d5cc5db4d..f011161a35 100644 --- a/commands/trade.js +++ b/commands/trade.js @@ -154,34 +154,36 @@ module.exports = function container (get, set, clear) { forwardScan() setInterval(forwardScan, c.poll_trades) readline.emitKeypressEvents(process.stdin) - process.stdin.setRawMode(true) - process.stdin.on('keypress', function (key, info) { - if (key === 'b' && !info.ctrl ) { - engine.executeSignal('buy') - } - else if (key === 'B' && !info.ctrl) { - engine.executeSignal('buy', null, null, false, true) - } - else if (key === 's' && !info.ctrl) { - engine.executeSignal('sell') - } - else if (key === 'S' && !info.ctrl) { - engine.executeSignal('sell', null, null, false, true) - } - else if ((key === 'c' || key === 'C') && !info.ctrl) { - delete s.buy_order - delete s.sell_order - } - else if ((key === 'm' || key === 'M') && !info.ctrl) { - so.manual = !so.manual - console.log('\nmanual mode: ' + (so.manual ? 'ON' : 'OFF') + '\n') - } - else if (info.name === 'c' && info.ctrl) { - // @todo: cancel open orders before exit - console.log() - process.exit() - } - }) + if (process.stdin.setRawMode) { + process.stdin.setRawMode(true) + process.stdin.on('keypress', function (key, info) { + if (key === 'b' && !info.ctrl ) { + engine.executeSignal('buy') + } + else if (key === 'B' && !info.ctrl) { + engine.executeSignal('buy', null, null, false, true) + } + else if (key === 's' && !info.ctrl) { + engine.executeSignal('sell') + } + else if (key === 'S' && !info.ctrl) { + engine.executeSignal('sell', null, null, false, true) + } + else if ((key === 'c' || key === 'C') && !info.ctrl) { + delete s.buy_order + delete s.sell_order + } + else if ((key === 'm' || key === 'M') && !info.ctrl) { + so.manual = !so.manual + console.log('\nmanual mode: ' + (so.manual ? 'ON' : 'OFF') + '\n') + } + else if (info.name === 'c' && info.ctrl) { + // @todo: cancel open orders before exit + console.log() + process.exit() + } + }) + } }) }) return From e48dff4826c795f0581a346a1f8e85ac93478a9d Mon Sep 17 00:00:00 2001 From: Clifford Roche Date: Tue, 18 Jul 2017 06:23:03 -0400 Subject: [PATCH 4/9] Windows support (#387) * Windows support * Fixed spawn command when trading * Fixed backtester runCommand * Added docker-compose-windows.yml which addresses DB volume exposing issues on Windows * Update readme.md * Untabify file... --- README.md | 8 +++++++- commands/trade.js | 3 ++- docker-compose-windows.yml | 21 +++++++++++++++++++++ scripts/auto_backtester/backtester.js | 3 ++- zenbot.bat | 1 + 5 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 docker-compose-windows.yml create mode 100644 zenbot.bat diff --git a/README.md b/README.md index f3fe634c90..0059566831 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Zenbot is a command-line cryptocurrency trading bot using Node.js and MongoDB. I ## Quick-start -### 1. Requirements: Linux or OSX or Docker, [Node.js](https://nodejs.org/) and [MongoDB](https://www.mongodb.com/). +### 1. Requirements: Windows, Linux or OSX or Docker, [Node.js](https://nodejs.org/) and [MongoDB](https://www.mongodb.com/). ### 2. Install zenbot 4: @@ -95,6 +95,12 @@ docker-compose build docker-compose up (-d if you don't want to see the log) ``` +If you are running windows use the following command + +``` +docker-compose --file=docker-compose-windows.yml up +``` + If you wish to run commands (e.g. backfills, list-selectors), you can run this separate command after a successful `docker-compose up -d`: ``` diff --git a/commands/trade.js b/commands/trade.js index f011161a35..d4bb9069c6 100644 --- a/commands/trade.js +++ b/commands/trade.js @@ -97,7 +97,8 @@ module.exports = function container (get, set, clear) { var periods = get('db.periods') console.log('fetching pre-roll data:') - var backfiller = spawn(path.resolve(__dirname, '..', 'zenbot.sh'), ['backfill', so.selector, '--days', days]) + var zenbot_cmd = process.platform === 'win32' ? 'zenbot.bat' : 'zenbot.sh'; // Use 'win32' for 64 bit windows too + var backfiller = spawn(path.resolve(__dirname, '..', zenbot_cmd), ['backfill', so.selector, '--days', days]) backfiller.stdout.pipe(process.stdout) backfiller.stderr.pipe(process.stderr) backfiller.on('exit', function (code) { diff --git a/docker-compose-windows.yml b/docker-compose-windows.yml new file mode 100644 index 0000000000..db5c62c486 --- /dev/null +++ b/docker-compose-windows.yml @@ -0,0 +1,21 @@ +server: + build: . + volumes: + - .:/app + - /app/node_modules + links: + - mongodb + command: ./zenbot.sh trade --paper + restart: always + +mongodb: + image: mongo:latest + volumes_from: + - mongodb-data + command: mongod --smallfiles + +mongodb-data: + image: mongo:latest + volumes: + - /data/db + command: "true" diff --git a/scripts/auto_backtester/backtester.js b/scripts/auto_backtester/backtester.js index b8b114014e..6713a6119d 100755 --- a/scripts/auto_backtester/backtester.js +++ b/scripts/auto_backtester/backtester.js @@ -80,7 +80,8 @@ let runCommand = (strategy, cb) => { speed: `--baseline_periods=${strategy.baseline_periods} --trigger_factor=${strategy.trigger_factor}`, trend_ema: `--trend_ema=${strategy.trend_ema} --oversold_rsi=${strategy.oversold_rsi} --oversold_rsi_periods=${strategy.oversold_rsi_periods} --neutral_rate=${strategy.neutral_rate}` }; - let command = `zenbot sim ${simArgs} ${strategyArgs[strategyName]} --period=${strategy.period} --min_periods=${strategy.min_periods}`; + let zenbot_cmd = process.platform === 'win32' ? 'zenbot.bat' : 'zenbot.sh'; // Use 'win32' for 64 bit windows too + let command = `${zenbot_cmd} sim ${simArgs} ${strategyArgs[strategyName]} --period=${strategy.period} --min_periods=${strategy.min_periods}`; console.log(`[ ${countArr.length}/${strategies[strategyName].length} ] ${command}`); shell.exec(command, {silent:true, async:true}, (code, stdout, stderr) => { diff --git a/zenbot.bat b/zenbot.bat new file mode 100644 index 0000000000..990a084240 --- /dev/null +++ b/zenbot.bat @@ -0,0 +1 @@ +node zenbot.sh %* \ No newline at end of file From 1d8735a579bdb21592275d0d1ab3bb0c9c8944c6 Mon Sep 17 00:00:00 2001 From: crubb Date: Thu, 20 Jul 2017 00:38:18 +0200 Subject: [PATCH 5/9] - Bitfinex and Kraken API throw the "post only" error only on checkOrder(), check for it in engine.js (#391) - Sometimes I would get "0% funds on hold (0.000000)". New check if balance on hold is > 0 --- lib/engine.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/engine.js b/lib/engine.js index 1ac7b3587c..7156770ea2 100644 --- a/lib/engine.js +++ b/lib/engine.js @@ -286,7 +286,7 @@ module.exports = function container (get, set, clear) { cb(null, order) }) } - if (order.status === 'rejected' && order.reject_reason === 'post only') { + if (order.status === 'rejected' && (order.reject_reason === 'post only' || api_order.reject_reason === 'post only')) { msg('post-only ' + type + ' failed, re-ordering') return cancelOrder(true) } @@ -416,7 +416,7 @@ module.exports = function container (get, set, clear) { return cb(err) } } - if (n(s.balance.currency).subtract(s.balance.currency_hold || 0).value() < n(price).multiply(size).value()) { + if (n(s.balance.currency).subtract(s.balance.currency_hold || 0).value() < n(price).multiply(size).value() && s.balance.currency_hold > 0) { msg('buy delayed: ' + pct(n(s.balance.currency_hold || 0).divide(s.balance.currency).value()) + ' of funds (' + fc(s.balance.currency_hold) + ') on hold') return setTimeout(function () { if (s.last_signal === signal) { From cd2cbf238ccb5570b00e815c53f56b39137d9594 Mon Sep 17 00:00:00 2001 From: Clifford Roche Date: Thu, 20 Jul 2017 04:52:17 -0400 Subject: [PATCH 6/9] Quadriga CX now supports LTC trading, adding to products (#392) * Quadriga CX Fixes * Add amount for takers fee * Minor update to backfill warning text * Fix variable shadowing error * Trades with the same timestamp are now ordered oldest to newest when r eturned from getTrades * Trade volume now returned as number and not a string, prevents NaN on volume report * Filter results on timestamp start to prevent re-reporting the same trades in the results * Sometime opts.from is undefined, and we should return all results. * Formatting * Quadriga now supports trading LTC * Fixing QCX API handling of numbers Some argument about the lack of type safety in javascript. * QCX fix incorrect error reporting --- extensions/exchanges/quadriga/exchange.js | 59 ++++++++++++--------- extensions/exchanges/quadriga/products.json | 12 ++++- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/extensions/exchanges/quadriga/exchange.js b/extensions/exchanges/quadriga/exchange.js index 1a514b4b8f..79485639c8 100644 --- a/extensions/exchanges/quadriga/exchange.js +++ b/extensions/exchanges/quadriga/exchange.js @@ -81,7 +81,7 @@ module.exports = function container(get, set, clear) { trade_id: trade.tid, time: moment.unix(trade.date).valueOf(), size: Number(trade.amount), - price: trade.price, + price: Number(trade.price), side: trade.side } }) @@ -104,11 +104,11 @@ module.exports = function container(get, set, clear) { currency: 0 } - balance.currency = wallet[currency + '_balance']; - balance.asset = wallet[asset + '_balance']; + balance.currency = Number(wallet[currency + '_balance']); + balance.asset = Number(wallet[asset + '_balance']); - balance.currency_hold = wallet[currency + '_reserved'] - balance.asset_hold = wallet[asset + '_reserved'] + balance.currency_hold = Number(wallet[currency + '_reserved']) + balance.asset_hold = Number(wallet[asset + '_reserved']) cb(null, balance) }) }, @@ -126,8 +126,8 @@ module.exports = function container(get, set, clear) { if (quote.error) return retry('getQuote', func_args, quote.error) var r = { - bid: quote.bid, - ask: quote.ask + bid: Number(quote.bid), + ask: Number(quote.ask) } cb(null, r) @@ -163,15 +163,18 @@ module.exports = function container(get, set, clear) { var order = { id: null, status: 'open', - price: opts.price, - size: opts.size, + price: Number(opts.price), + size: Number(opts.size), created_at: new Date().getTime(), - filled_size: '0', + filled_size: 0, ordertype: opts.order_type } if (err) return cb(err) - if (body.error) return cb(body.error.message) + if (body.error) { + //console.log(`API Error: ${body.error.message}`); + return cb(body.error) + } if (opts.order_type === 'taker') { order.status = 'done' @@ -182,15 +185,15 @@ module.exports = function container(get, set, clear) { var price_total = 0.0 var order_count = body.orders_matched.length for (var idx = 0; idx < order_count; idx++) { - asset_total = asset_total + body.orders_matched[idx].amount - price_total = price_total + (body.orders_matched[idx].amount * body.orders_matched[idx].price) + asset_total = asset_total + Number(body.orders_matched[idx].amount) + price_total = price_total + (Number(body.orders_matched[idx].amount) * Number(body.orders_matched[idx].price)) } order.price = price_total / asset_total order.size = asset_total } else { - order.price = body.price - order.size = body.amount + order.price = Number(body.price) + order.size = Number(body.amount) } } @@ -215,15 +218,18 @@ module.exports = function container(get, set, clear) { var order = { id: null, status: 'open', - price: opts.price, - size: opts.size, + price: Number(opts.price), + size: Number(opts.size), created_at: new Date().getTime(), - filled_size: '0', + filled_size: 0, ordertype: opts.order_type } if (err) return cb(err) - if (body.error) return cb(body.error.message) + if (body.error) { + //console.log(`API Error: ${body.error.message}`); + return cb(body.error) + } if (opts.order_type === 'taker') { order.status = 'done' @@ -234,15 +240,15 @@ module.exports = function container(get, set, clear) { var price_total = 0.0 var order_count = body.orders_matched.length for (var idx = 0; idx < order_count; idx++) { - asset_total = asset_total + body.orders_matched[idx].amount - price_total = price_total + (body.orders_matched[idx].amount * body.orders_matched[idx].price) + asset_total = asset_total + Number(body.orders_matched[idx].amount) + price_total = price_total + (Number(body.orders_matched[idx].amount) * body.orders_matched[idx].price) } order.price = price_total / asset_total order.size = asset_total } else { - order.price = body.price - order.size = body.amount + order.price = Number(body.price) + order.size = Number(body.amount) } } @@ -261,12 +267,15 @@ module.exports = function container(get, set, clear) { var client = authedClient() client.api('lookup_order', params, function(err, body) { if (err) return cb(err) - if (body.error) return cb(body.error.message) + if (body.error) { + //console.log(`API Error: ${body.error.message}`); + return cb(body.error) + } if (body.status === 2) { order.status = 'done' order.done_at = new Date().getTime() - order.filled_size = body.amount + order.filled_size = Number(body.amount) return cb(null, order) } cb(null, order) diff --git a/extensions/exchanges/quadriga/products.json b/extensions/exchanges/quadriga/products.json index 298c236fa8..331a2d886d 100644 --- a/extensions/exchanges/quadriga/products.json +++ b/extensions/exchanges/quadriga/products.json @@ -1,5 +1,4 @@ -[ - { +[{ "id": "BTCUSD", "asset": "BTC", "currency": "USD", @@ -34,5 +33,14 @@ "max_size": "1000000", "increment": "0.00001", "label": "ETH/BTC" + }, + { + "id": "LTCCAD", + "asset": "LTC", + "currency": "CAD", + "min_size": "0.00001", + "max_size": "10000", + "increment": "0.00001", + "label": "LTC/CAD" } ] From 7b6eeaa8aa3beac3575a8adc320d7814506b0b9b Mon Sep 17 00:00:00 2001 From: Nate Fanaro Date: Tue, 25 Jul 2017 01:27:14 -0400 Subject: [PATCH 7/9] Updated name of renamed srsi_macd strategy (#400) --- extensions/strategies/srsi_macd/strategy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/strategies/srsi_macd/strategy.js b/extensions/strategies/srsi_macd/strategy.js index ba087e562d..562db9bc23 100644 --- a/extensions/strategies/srsi_macd/strategy.js +++ b/extensions/strategies/srsi_macd/strategy.js @@ -3,7 +3,7 @@ let z = require('zero-fill') module.exports = function container (get, set, clear) { return { - name: 'rsi_macd', + name: 'srsi_macd', description: 'Stochastic MACD Strategy', getOptions: function () { From 5f35192eb88443e3683230efad4836742fc836fd Mon Sep 17 00:00:00 2001 From: Rohit Dhingra Date: Tue, 25 Jul 2017 01:27:59 -0400 Subject: [PATCH 8/9] Removed trailing comma from dependencies. (#398) --- extensions/exchanges/quadriga/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/exchanges/quadriga/package.json b/extensions/exchanges/quadriga/package.json index 8912c8c82e..e5506a9f49 100644 --- a/extensions/exchanges/quadriga/package.json +++ b/extensions/exchanges/quadriga/package.json @@ -3,6 +3,6 @@ "version": "0.0.1", "description": "Zenbot supporting code for QuadrigaCX", "dependencies": { - "quadrigacx": "^0.0.7", + "quadrigacx": "^0.0.7" } } From 1b2879197ce22721da7aeac7ffc6d554dc374d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20B?= Date: Tue, 25 Jul 2017 07:29:14 +0200 Subject: [PATCH 9/9] Add extension point in dev doc (#395) --- docs/developers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developers.md b/docs/developers.md index f9132463c9..5b023d26cb 100644 --- a/docs/developers.md +++ b/docs/developers.md @@ -153,7 +153,7 @@ getQuote: function (opts, cb) ``` Called from: - https://github.com/carlos8f/zenbot/blob/master/lib/engine.js -- https://github.com/carlos8f/zenbot/blob/master/commands/buyjs +- https://github.com/carlos8f/zenbot/blob/master/commands/buy.js - https://github.com/carlos8f/zenbot/blob/master/commands/sell.js Input: