diff --git a/README.md b/README.md index a3fc8ab08f..dea56488c8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![zenbot logo](https://rawgit.com/carlos8f/zenbot/master/assets/logo.png) +![zenbot logo](https://rawgit.com/deviavir/zenbot/master/assets/logo.png) > β€œTo follow the path, look to the master, follow the master, walk with the master, see through the master, become the master.” > – Zen Proverb @@ -50,13 +50,13 @@ Zenbot is a command-line cryptocurrency trading bot using Node.js and MongoDB. I Run in your console, ``` -git clone https://github.com/carlos8f/zenbot.git +git clone https://github.com/deviavir/zenbot.git ``` Or, without git, ``` -wget https://github.com/carlos8f/zenbot/archive/master.tar.gz +wget https://github.com/deviavir/zenbot/archive/master.tar.gz tar -xf zenbot-master.tar.gz mv zenbot-master zenbot ``` @@ -93,7 +93,7 @@ sudo apt-get install build-essential mongodb -y curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - sudo apt-get install -y nodejs -git clone https://github.com/carlos8f/zenbot.git +git clone https://github.com/deviavir/zenbot.git cd zenbot npm install @@ -476,7 +476,7 @@ In it's infancy, there are a few caveats with the current UI. ## Reading the console output -![console](https://rawgit.com/carlos8f/zenbot/master/assets/console.png) +![console](https://rawgit.com/deviavir/zenbot/master/assets/console.png) From left to right: @@ -674,7 +674,7 @@ zenbot sell gdax.BTC-USD --pct=10 ## Chat with other Zenbot users -[![zenbot logo](https://rawgit.com/carlos8f/zenbot/master/assets/discord.png)](https://discord.gg/ZdAd2gP) +[![zenbot logo](https://rawgit.com/deviavir/zenbot/master/assets/discord.png)](https://discord.gg/ZdAd2gP) Zenbot has a Discord chat! You can get in [through this invite link](https://discord.gg/ZdAd2gP). @@ -686,7 +686,7 @@ P.S., some have asked for how to donate to Zenbot development. I accept donation `187rmNSkSvehgcKpBunre6a5wA5hQQop6W` -![zenbot logo](https://rawgit.com/carlos8f/zenbot/master/assets/zenbot_square.png) +![zenbot logo](https://rawgit.com/deviavir/zenbot/master/assets/zenbot_square.png) Thanks! diff --git a/commands/backfill.js b/commands/backfill.js index 53c372c0f2..1d2a2ab0ef 100644 --- a/commands/backfill.js +++ b/commands/backfill.js @@ -5,6 +5,9 @@ var tb = require('timebucket') module.exports = function container (get, set, clear) { var c = get('conf') || {} + + var collectionService = get('lib.collection-service')(get, set, clear) + return function (program) { program .command('backfill [selector]') @@ -17,10 +20,10 @@ module.exports = function container (get, set, clear) { console.error('cannot backfill ' + selector.normalized + ': exchange not implemented') process.exit(1) } - var trades = get('db.trades') - get('db.mongo').collection('trades').ensureIndex({selector: 1, time: 1}) - var resume_markers = get('db.resume_markers') - get('db.mongo').collection('resume_markers').ensureIndex({selector: 1, to: -1}) + + var trades = collectionService.getTrades(); + var resume_markers = collectionService.getResumeMarkers(); + var marker = { id: crypto.randomBytes(4).toString('hex'), selector: selector.normalized, diff --git a/extensions/exchanges/binance/exchange.js b/extensions/exchanges/binance/exchange.js index e347c69fee..ac8f4f7c03 100644 --- a/extensions/exchanges/binance/exchange.js +++ b/extensions/exchanges/binance/exchange.js @@ -220,7 +220,7 @@ module.exports = function container (get, set, clear) { cb(null, order) }).catch(function (error) { console.error('An error occurred', error) - return retry('buy', func_args) + return retry('sell', func_args) }) }, diff --git a/extensions/exchanges/gdax/exchange.js b/extensions/exchanges/gdax/exchange.js index f27a9530ae..1dcab81925 100644 --- a/extensions/exchanges/gdax/exchange.js +++ b/extensions/exchanges/gdax/exchange.js @@ -1,20 +1,53 @@ var Gdax = require('gdax') - , path = require('path') - , colors = require('colors') - , numbro = require('numbro') module.exports = function container (get, set, clear) { var c = get('conf') - var public_client = {}, authed_client + var public_client = {}, authed_client, websocket_client = {}, websocket_cache = {} function publicClient (product_id) { if (!public_client[product_id]) { + websocketClient(product_id) public_client[product_id] = new Gdax.PublicClient(product_id, c.gdax.apiURI) } return public_client[product_id] } + function websocketClient (product_id) { + if (!websocket_client[product_id]) { + // OrderbookSync extends WebsocketClient and subscribes to the 'full' channel, so we can use it like one + var auth = null + try { + auth = authedClient() + } catch(e){} + websocket_client[product_id] = new Gdax.OrderbookSync([product_id], c.gdax.apiURI, c.gdax.websocketURI, auth) + // initialize a cache for the websocket connection + websocket_cache[product_id] = { + trades: [], + trade_ids: [] + } + websocket_client[product_id].on('open', () => { + console.log('websocket connection to '+product_id+' opened') + }) + websocket_client[product_id].on('message', (message) => { + switch (message.type) { + case 'match': + handleTrade(message, product_id) + break + default: + break + } + }) + websocket_client[product_id].on('error', (err) => { + console.log(err) + }) + websocket_client[product_id].on('close', () => { + console.error('websocket connection to '+product_id+' closed, attempting reconnect') + websocket_client[product_id].connect() + }) + } + } + function authedClient () { if (!authed_client) { if (!c.gdax || !c.gdax.key || c.gdax.key === 'YOUR-API-KEY') { @@ -45,6 +78,15 @@ module.exports = function container (get, set, clear) { }, 10000) } + function handleTrade(trade, product_id) { + var cache = websocket_cache[product_id] + cache.trades.push(trade) + cache.trade_ids.push(trade.trade_id) + } + + // TODO: this contains open orders and gets updated on buy/sell/getOrder + // should maintain a list of ID's and keep this up to date from the websocket feed's + // 'done'/'change'/'match' messages and use this as a cache for `getOrder` below var orders = {} var exchange = { @@ -69,6 +111,30 @@ module.exports = function container (get, set, clear) { // move cursor into the past args.after = opts.to } + // check for locally cached trades from the websocket feed + var cache = websocket_cache[opts.product_id] + var max_trade_id = cache.trade_ids.reduce(function(a, b) { + return Math.max(a, b) + }, -1) + if (opts.from && max_trade_id >= opts.from) { + var fromIndex = cache.trades.findIndex((value)=> {return value.trade_id == opts.from}) + var newTrades = cache.trades.slice(fromIndex + 1) + newTrades = newTrades.map(function (trade) { + return { + trade_id: trade.trade_id, + time: new Date(trade.time).getTime(), + size: Number(trade.size), + price: Number(trade.price), + side: trade.side + } + }) + newTrades.reverse() + cb(null, newTrades) + // trim cache + cache.trades = cache.trades.slice(fromIndex) + cache.trade_ids = cache.trade_ids.slice(fromIndex) + return + } client.getProductTrades(args, function (err, resp, body) { if (!err) err = statusErr(resp, body) if (err) return retry('getTrades', func_args, err) @@ -108,6 +174,22 @@ module.exports = function container (get, set, clear) { }, getQuote: function (opts, cb) { + // check websocket cache first + if(websocket_client[opts.product_id] && websocket_client[opts.product_id].books) { + var book = websocket_client[opts.product_id].books[opts.product_id] + var state = book.state() + var asks = state.asks + var bids = state.bids + if(bids.length && asks.length){ + // price is a `num` arbitrary precision number and needs to be toString()ed and parseFloat()ed + var best_bid = parseFloat(bids[0].price.toString()) + var best_ask = parseFloat(asks[0].price.toString()) + if(best_bid && best_ask){ + cb(null, {bid: best_bid, ask: best_ask}) + return + } + } + } var func_args = [].slice.call(arguments) var client = publicClient(opts.product_id) client.getProductTicker(function (err, resp, body) { diff --git a/extensions/exchanges/kraken/exchange.js b/extensions/exchanges/kraken/exchange.js index da83874c94..1f001f8622 100644 --- a/extensions/exchanges/kraken/exchange.js +++ b/extensions/exchanges/kraken/exchange.js @@ -48,7 +48,7 @@ module.exports = function container(get, set, clear) { if (assetsToFix.indexOf(asset) >= 0 && currency.length > 3) { currency = currency.substring(1) } - return `X${asset}X${currency}` + return asset + currency; } function retry(method, args, error) { diff --git a/lib/_codemap.js b/lib/_codemap.js index 3b0f3267af..00561ccb12 100644 --- a/lib/_codemap.js +++ b/lib/_codemap.js @@ -21,5 +21,6 @@ module.exports = { 'adx': require('./adx'), 'vwap': require('./vwap'), 'slow_stochastic': require('./slow_stochastic'), + 'collection-service': require('./services/collection-service'), 'cmf': require('./cmf') } diff --git a/lib/services/collection-service.js b/lib/services/collection-service.js new file mode 100644 index 0000000000..5372e340fe --- /dev/null +++ b/lib/services/collection-service.js @@ -0,0 +1,21 @@ +module.exports = (function (get, set, clear) { + + return { + + getTrades: () => { + var trades = get('db.trades') + get('db.mongo').collection('trades').ensureIndex({selector: 1, time: 1}) + + return trades + }, + + getResumeMarkers: () => { + var resume_markers = get('db.resume_markers') + get('db.mongo').collection('resume_markers').ensureIndex({selector: 1, to: -1}) + + return resume_markers; + } + } + +}) + diff --git a/package-lock.json b/package-lock.json index 7fb0c82d48..8b7d483450 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,9 +64,9 @@ } }, "JSONStream": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", - "integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", + "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", "requires": { "jsonparse": "1.3.1", "through": "2.3.8" @@ -121,9 +121,9 @@ } }, "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.1.tgz", + "integrity": "sha1-s4u4h22ehr7plJVqBOch6IskjrI=", "requires": { "co": "4.6.0", "fast-deep-equal": "1.0.0", @@ -293,7 +293,7 @@ "lodash": "4.17.4", "request": "2.76.0", "request-promise": "4.2.2", - "ws": "3.3.3" + "ws": "3.3.2" } }, "bitstamp": { @@ -393,15 +393,21 @@ "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", "dev": true }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, "caseless": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" }, "ccxt": { - "version": "1.10.429", - "resolved": "https://registry.npmjs.org/ccxt/-/ccxt-1.10.429.tgz", - "integrity": "sha512-GtN2bkzrUiQQyC+GCLzcQGiuSIYgeGXuYXC2mk2zm6yOJSYWSs8kC3tLJBsVbrL04L04JIz6mFNmAd8zMiDVNQ==", + "version": "1.10.272", + "resolved": "https://registry.npmjs.org/ccxt/-/ccxt-1.10.272.tgz", + "integrity": "sha512-okiyrZsczYykHUtFZ3A9F2Pc4vE4VwkVR9LplSr2GElSR2Q/3f5iSHodXui1pIyojtQbMyPOJhXrOicZ3Phs6A==", "requires": { "crypto-js": "3.1.9-1", "fetch-ponyfill": "4.1.0", @@ -421,7 +427,7 @@ "integrity": "sha512-vmv090Y7noSohend7XTBMGtXQcOIb5ouLKk/CYfnWr+4deiTv8ie7+sSgszvgGSE689iLDPSFIkrhzeL9CO5yg==", "requires": { "crypto": "1.0.1", - "moment": "2.20.1", + "moment": "2.19.3", "querystring": "0.2.0", "request": "2.83.0", "underscore": "1.8.3" @@ -483,7 +489,7 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "requires": { - "ajv": "5.5.2", + "ajv": "5.5.1", "har-schema": "2.0.0" } }, @@ -618,6 +624,23 @@ "restore-cursor": "2.0.0" } }, + "cli-table": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", + "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", + "dev": true, + "requires": { + "colors": "1.0.3" + }, + "dependencies": { + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true + } + } + }, "cli-width": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", @@ -672,6 +695,39 @@ } } }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, "clone": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", @@ -682,6 +738,12 @@ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, "codemap": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/codemap/-/codemap-1.3.1.tgz", @@ -837,10 +899,16 @@ "ms": "2.0.0" } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, "decimal.js": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-7.2.3.tgz", - "integrity": "sha512-AoFI37QS0S87Ft0r3Bdz4q9xSpm1Paa9lSeKLXgMPk/u/+QPIM5Gy4DHcZQS1seqPJH4gHLauPGn347z0HbsrA==" + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-9.0.1.tgz", + "integrity": "sha512-2h0iKbJwnImBk4TGk7CG1xadoA0g3LDPlQhQzbZ221zvG0p2YVUedbKIPsOZXKZGx6YmZMJKYOalpCMxSdDqTQ==" }, "deep-eql": { "version": "0.1.3", @@ -994,12 +1062,12 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.13.1.tgz", - "integrity": "sha512-UCJVV50RtLHYzBp1DZ8CMPtRSg4iVZvjgO9IJHIKyWU/AnJVjtdRikoUPLB29n5pzMB7TnsLQWf0V6VUJfoPfw==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.12.1.tgz", + "integrity": "sha512-28hOYej+NZ/R5H1yMvyKa1+bPlu+fnsIAQffK6hxXgvmXnImos2bA5XfCn5dYv2k2mrKj+/U/Z4L5ICWxC7TQw==", "dev": true, "requires": { - "ajv": "5.5.2", + "ajv": "5.5.1", "babel-code-frame": "6.26.0", "chalk": "2.3.0", "concat-stream": "1.6.0", @@ -1014,11 +1082,11 @@ "file-entry-cache": "2.0.0", "functional-red-black-tree": "1.0.1", "glob": "7.1.2", - "globals": "11.1.0", + "globals": "11.0.1", "ignore": "3.3.7", "imurmurhash": "0.1.4", "inquirer": "3.3.0", - "is-resolvable": "1.0.1", + "is-resolvable": "1.0.0", "js-yaml": "3.10.0", "json-stable-stringify-without-jsonify": "1.0.1", "levn": "0.3.0", @@ -1174,6 +1242,26 @@ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz", "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=" }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" + }, "express": { "version": "4.16.2", "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", @@ -1314,6 +1402,24 @@ "unpipe": "1.0.0" } }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + }, + "flat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.0.0.tgz", + "integrity": "sha512-ji/WMv2jdsE+LaznpkIF9Haax0sdpTBozrz/Dtg4qSRMfbs8oVg4ypJunIRYPiMLvH/ed6OflXbnbTIKJhtgeg==", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + }, "flat-cache": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", @@ -1376,9 +1482,9 @@ "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" }, "fraction.js": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.2.tgz", - "integrity": "sha512-OswcigOSil3vYXgrPSx4NCaSyPikXqVNYN/4CyhS0ucVOJ4GVYr6KQQLLcAudvS/4bBOzxqJ3XIsFaaMjl98ZQ==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.4.tgz", + "integrity": "sha512-aK/oGatyYLTtXRHjfEsytX5fieeR5H4s8sLorzcT12taFS+dbMZejnvm9gRa8mZAPwci24ucjq9epDyaq5u8Iw==" }, "fresh": { "version": "0.5.2", @@ -1462,7 +1568,7 @@ "requires": { "chalk": "1.1.3", "commander": "2.12.2", - "is-my-json-valid": "2.17.1", + "is-my-json-valid": "2.16.1", "pinkie-promise": "2.0.1" } }, @@ -1579,6 +1685,24 @@ "is-property": "1.0.2" } }, + "geneticalgorithm": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/geneticalgorithm/-/geneticalgorithm-0.3.4.tgz", + "integrity": "sha1-oVdal8Gwcy7yYW53k8bfU1bzA4E=", + "dev": true + }, + "get-caller-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -1608,9 +1732,9 @@ } }, "globals": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.1.0.tgz", - "integrity": "sha512-uEuWt9mqTlPDwSqi+sHjD4nWU/1N+q0fiWI9T1mZpD2UENqX20CFD5T/ziLZvztPaBKl7ZylUi1q6Qfm7E2CiQ==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.0.1.tgz", + "integrity": "sha1-Eqh7sBDlFUOWrMU14eQ/x1Ow5eg=", "dev": true }, "globby": { @@ -1643,7 +1767,7 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", "requires": { - "ajv": "5.5.2", + "ajv": "5.5.1", "har-schema": "2.0.0" } }, @@ -1849,6 +1973,18 @@ "resolved": "https://registry.npmjs.org/int/-/int-0.1.1.tgz", "integrity": "sha1-18efL4PP9QXTXoaYD4H6FPM4ekw=" }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -1874,16 +2010,34 @@ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "is-integer": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-integer/-/is-integer-1.0.7.tgz", + "integrity": "sha1-a96Bqs3feLZZtmKdYpytxRqIbVw=", + "dev": true, + "requires": { + "is-finite": "1.0.2" + } + }, "is-my-json-valid": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz", - "integrity": "sha512-Q2khNw+oBlWuaYvEEHtKSw/pCxD2L5Rc1C+UQme9X6JdRDh7m5D7HkozA0qa3DUkQ6VzCnEm8mVIQPyIRkI5sQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz", + "integrity": "sha512-ochPsqWS1WXj8ZnMIV0vnNXooaMhp7cyL4FMSIPKTtnV0Ha/T19G2b9kkhcNsabV9bxYkze7/aLZJb/bYuFduQ==", "requires": { "generate-function": "2.0.0", "generate-object-property": "1.2.0", @@ -1935,10 +2089,13 @@ } }, "is-resolvable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.1.tgz", - "integrity": "sha512-y5CXYbzvB3jTnWAZH1Nl7ykUWb6T3BcTs56HUruwBf8MhF56n1HWqhDWnVFo8GHrUPDgvUUNVhrc2U8W7iqz5g==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", + "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", + "dev": true, + "requires": { + "tryit": "1.0.3" + } }, "is-stream": { "version": "1.1.0", @@ -1976,6 +2133,21 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", + "requires": { + "exit": "0.1.2", + "glob": "7.1.2", + "jasmine-core": "2.8.0" + } + }, + "jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=" + }, "javascript-natural-sort": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", @@ -2024,6 +2196,35 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "json2csv": { + "version": "3.11.5", + "resolved": "https://registry.npmjs.org/json2csv/-/json2csv-3.11.5.tgz", + "integrity": "sha512-ORsw84BuRKMLxfI+HFZuvxRDnsJps53D5fIGr6tLn4ZY+ymcG8XU00E+JJ2wfAiHx5w2QRNmOLE8xHiGAeSfuQ==", + "dev": true, + "requires": { + "cli-table": "0.3.1", + "commander": "2.12.2", + "debug": "3.1.0", + "flat": "4.0.0", + "lodash.clonedeep": "4.5.0", + "lodash.flatten": "4.4.0", + "lodash.get": "4.4.2", + "lodash.set": "4.3.2", + "lodash.uniq": "4.5.0", + "path-is-absolute": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -2061,6 +2262,15 @@ "request": "2.76.0" } }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "1.0.0" + } + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -2071,6 +2281,16 @@ "type-check": "0.3.2" } }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + } + }, "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", @@ -2160,6 +2380,18 @@ "lodash._createassigner": "3.1.1" } }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", + "dev": true + }, "lodash.foreach": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-3.0.0.tgz", @@ -2171,6 +2403,12 @@ "lodash.isarray": "3.0.4" } }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -2206,6 +2444,18 @@ "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" }, + "lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, "lru-cache": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", @@ -2230,17 +2480,17 @@ "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=" }, "mathjs": { - "version": "3.16.5", - "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-3.16.5.tgz", - "integrity": "sha1-11pSZUNdKCSwZ7N6R4dx3uv2qsw=", + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-3.18.0.tgz", + "integrity": "sha512-k5ZNz2Ic86HYqPBj7ZnJHqmEI7s4f8y/ip3zJ3PnFulk05wOxXr3tuSoJJKUpCWMQFFVoXEhJwHbQmY+3Ah2ng==", "requires": { "complex.js": "2.0.4", - "decimal.js": "7.2.3", - "fraction.js": "4.0.2", + "decimal.js": "9.0.1", + "fraction.js": "4.0.4", "javascript-natural-sort": "0.7.1", "seed-random": "2.2.0", - "tiny-emitter": "2.0.0", - "typed-function": "0.10.5" + "tiny-emitter": "2.0.2", + "typed-function": "0.10.6" } }, "md5.js": { @@ -2257,6 +2507,15 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, + "requires": { + "mimic-fn": "1.1.0" + } + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -2325,9 +2584,9 @@ } }, "moment": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", - "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==" + "version": "2.19.3", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.19.3.tgz", + "integrity": "sha1-vbmdJw1tf9p4zA+6zoVeJ/59pp8=" }, "mongodb": { "version": "2.2.33", @@ -2524,7 +2783,7 @@ "resolved": "https://registry.npmjs.org/node.bittrex.api/-/node.bittrex.api-0.2.5.tgz", "integrity": "sha1-rtkKEr/U9kgYQbScXA+e7MC5RtE=", "requires": { - "JSONStream": "1.3.2", + "JSONStream": "1.3.1", "event-stream": "3.3.4", "request": "2.76.0" } @@ -2534,6 +2793,15 @@ "resolved": "https://registry.npmjs.org/nonce/-/nonce-1.0.4.tgz", "integrity": "sha1-7nMCrejBvvR28wG4yR9cxRpIdhI=" }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "2.0.1" + } + }, "num": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/num/-/num-0.2.1.tgz", @@ -2547,8 +2815,14 @@ "resolved": "https://registry.npmjs.org/number-abbreviate/-/number-abbreviate-2.0.0.tgz", "integrity": "sha1-6cstGNuADhU88HKClVPEr+DfeJg=" }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, "numbro": { - "version": "github:highvelocityspace/numbro#797eae5605bf217a016a112343e8dd9746b72e15" + "version": "github:highvelocityspace/numbro#e6e9a0d5f4c32939a7c19cf1546c7766b38cd31f" }, "oauth-sign": { "version": "0.8.2", @@ -2610,17 +2884,55 @@ "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" + } + }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz", + "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=", + "dev": true + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "1.1.0" + } + }, "parseurl": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -2632,6 +2944,18 @@ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", "dev": true }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -2958,7 +3282,7 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "requires": { - "ajv": "5.5.2", + "ajv": "5.5.1", "har-schema": "2.0.0" } }, @@ -3112,6 +3436,15 @@ "util-deprecate": "1.0.2" } }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "1.5.0" + } + }, "reconnect-core": { "version": "https://github.com/dodo/reconnect-core/tarball/merged", "integrity": "sha1-udryrcRbGabMX9LwSPjZQGzs5Jg=", @@ -3120,9 +3453,9 @@ } }, "regression": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regression/-/regression-2.0.1.tgz", - "integrity": "sha1-jSnD6CJKEIUMNeM36FqLL6w7DIc=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regression/-/regression-2.0.0.tgz", + "integrity": "sha1-DyPkAS8KTCte+Ci09LTjn3LfuWo=" }, "request": { "version": "2.76.0", @@ -3158,7 +3491,7 @@ "requires": { "chalk": "1.1.3", "commander": "2.12.2", - "is-my-json-valid": "2.17.1", + "is-my-json-valid": "2.16.1", "pinkie-promise": "2.0.1" } } @@ -3183,6 +3516,18 @@ "lodash": "4.17.4" } }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, "require-uncached": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", @@ -3210,6 +3555,15 @@ "semver": "5.4.1" } }, + "resolve": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", + "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } + }, "resolve-from": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", @@ -3239,6 +3593,16 @@ "glob": "7.1.2" } }, + "round-precision": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/round-precision/-/round-precision-1.0.0.tgz", + "integrity": "sha1-9aK+t+1Z5v6FMS+/VTdpEC6o9k8=", + "dev": true, + "requires": { + "is-finite": "1.0.2", + "is-integer": "1.0.7" + } + }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", @@ -3253,6 +3617,12 @@ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.6.tgz", "integrity": "sha1-KQA8miFj4B4tLfyQV18sbB1hoDk=" }, + "run-parallel-limit": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.0.3.tgz", + "integrity": "sha1-bDkwzHwLR9Na50IBCfZgqt4kAeM=", + "dev": true + }, "run-series": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.4.tgz", @@ -3331,6 +3701,12 @@ "send": "0.16.1" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", @@ -3351,6 +3727,17 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, + "shelljs": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", + "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "dev": true, + "requires": { + "glob": "7.1.2", + "interpret": "1.1.0", + "rechoir": "0.6.2" + } + }, "shortid": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.8.tgz", @@ -3516,6 +3903,12 @@ "ansi-regex": "2.1.1" } }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -3533,7 +3926,7 @@ "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", "dev": true, "requires": { - "ajv": "5.5.2", + "ajv": "5.5.1", "ajv-keywords": "2.1.1", "chalk": "2.3.0", "lodash": "4.17.4", @@ -3596,13 +3989,13 @@ "resolved": "https://registry.npmjs.org/timebucket/-/timebucket-0.4.0.tgz", "integrity": "sha1-2H9xqMhrjqq95zX3qcSzJc6ZhXw=", "requires": { - "moment": "2.20.1" + "moment": "2.19.3" } }, "tiny-emitter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.0.tgz", - "integrity": "sha1-utMnrbGAS0KiMa+nQVMr2ITNCa0=" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz", + "integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==" }, "tmp": { "version": "0.0.33", @@ -3626,6 +4019,12 @@ "resolved": "https://registry.npmjs.org/trend/-/trend-0.3.0.tgz", "integrity": "sha1-MA6kPSYYydVrA/JJcwn4VY63i4k=" }, + "tryit": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", + "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", + "dev": true + }, "tunnel-agent": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", @@ -3661,9 +4060,9 @@ } }, "typed-function": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-0.10.5.tgz", - "integrity": "sha1-Lg8Yq9BlIZ+raUpEamXG0ZgYMsA=" + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-0.10.6.tgz", + "integrity": "sha512-PYtsDjxyW3vq7Itn2RMz0cn6CrbybIY6XC2i9c1q1o/H94QW8B1Pf3wSsbBDOCMpN1i5jDRrlDsLXFaqXBpfHQ==" }, "typedarray": { "version": "0.0.6", @@ -3770,6 +4169,12 @@ "isexe": "2.0.0" } }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, "winston": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.0.tgz", @@ -3801,6 +4206,38 @@ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3816,9 +4253,9 @@ } }, "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.2.tgz", + "integrity": "sha512-t+WGpsNxhMR4v6EClXS8r8km5ZljKJzyGhJf7goJz9k5Ye3+b5Bvno5rjqPuIBn5mnn5GBb7o8IrIWHxX1qOLQ==", "requires": { "async-limiter": "1.0.0", "safe-buffer": "5.1.1", @@ -3850,6 +4287,12 @@ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, "yaeti": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", @@ -3861,6 +4304,35 @@ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true }, + "yargs": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-10.0.3.tgz", + "integrity": "sha512-DqBpQ8NAUX4GyPP/ijDGHsJya4tYqLQrjPr95HNsr1YwL3+daCfvBwg7+gIC6IdJhR2kATh3hb61vjzMWEtjdw==", + "dev": true, + "requires": { + "cliui": "3.2.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "8.1.0" + } + }, + "yargs-parser": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.1.0.tgz", + "integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==", + "dev": true, + "requires": { + "camelcase": "4.1.0" + } + }, "zero-fill": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/zero-fill/-/zero-fill-2.2.3.tgz", diff --git a/package.json b/package.json index ea706f7c05..a97332bb7b 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "zenbot": "./zenbot.sh" }, "scripts": { + "test": "jasmine test/**/**.test.js", "postinstall": "rm -rf node_modules/forex.analytics/.git && webpack -p" }, "dependencies": { @@ -25,6 +26,7 @@ "bitstamp": "^1.0.4", "bl": "^1.2.1", "bootstrap": "^4.0.0-beta.2", + "ccxt": "^1.10.171", "cexio-api-node": "^1.0.8", "cliff": "^0.1.10", "css-loader": "^0.28.7", @@ -32,7 +34,6 @@ "colors": "^1.1.2", "commander": "^2.9.0", "convnetjs": "0.3.0", - "ccxt": "^1.10.171", "counterup": "^1.0.2", "css-loader": "^0.28.7", "ejs": "^2.5.7", @@ -41,16 +42,17 @@ "expose-loader": "^0.7.4", "file-loader": "^1.1.6", "forex.analytics": "mkmarek/forex.analytics#7bc278987700d4204e959af17de61495941d1a14", - "gdax": "^0.4.2", + "gdax": "coinbase/gdax-node#v0.5.0", "gemini-api": "^2.0.4", "glob": "^7.1.1", "har-validator": "^5.0.3", "idgen": "^2.0.2", "imports-loader": "^0.7.1", "ip": "~1.1.5", + "jasmine": "^2.8.0", "jquery": "^3.2.1", "kraken-api": "^0.1.7", - "mathjs": "3.16.5", + "mathjs": "^3.17.0", "micro-request": "^666.0.10", "mime": "^1.4.0", "minimist": "^1.2.0", @@ -83,8 +85,8 @@ "stats-lite": "2.1.0", "style-loader": "^0.19.1", "talib": "^1.0.3", - "trend": "0.3.0", "timebucket": "^0.4.0", + "trend": "0.3.0", "url-loader": "^0.6.2", "waypoints": "^4.0.1", "webpack": "^3.10.0", diff --git a/scripts/auto_backtester/backtester.js b/scripts/auto_backtester/backtester.js index 7f6f48b31d..7d4b3bf914 100755 --- a/scripts/auto_backtester/backtester.js +++ b/scripts/auto_backtester/backtester.js @@ -189,7 +189,7 @@ let processOutput = output => { oversoldRsi: params.oversold_rsi, days: days, - period: params.periodLength, + periodLength: params.periodLength, min_periods: params.min_periods, roi: roi, wlRatio: losses > 0 ? roundp(wins / losses, 3) : 'Infinity', @@ -199,7 +199,7 @@ let processOutput = output => { let strategies = { cci_srsi: objectProduct({ - period: ['20m'], + periodLength: ['20m'], min_periods: [52, 200], rsi_periods: [14, 20], srsi_periods: [14, 20], @@ -212,7 +212,7 @@ let strategies = { constant: [0.015] }), srsi_macd: objectProduct({ - period: ['30m'], + periodLength: ['30m'], min_periods: [52, 200], rsi_periods: [14, 20], srsi_periods: [14, 20], @@ -227,7 +227,7 @@ let strategies = { down_trend_threshold: [0] }), macd: objectProduct({ - period: ['1h'], + periodLength: ['1h'], min_periods: [52], ema_short_period: range(10, 15), ema_long_period: range(20, 30), @@ -238,7 +238,7 @@ let strategies = { overbought_rsi: range(70, 70) }), rsi: objectProduct({ - period: ['2m'], + periodLength: ['2m'], min_periods: [52], rsi_periods: range(10, 30), oversold_rsi: range(20, 35), @@ -248,19 +248,19 @@ let strategies = { rsi_divisor: range(2, 2) }), sar: objectProduct({ - period: ['2m'], + periodLength: ['2m'], min_periods: [52], sar_af: range(0.01, 0.055, 0.005), sar_max_af: range(0.1, 0.55, 0.05) }), speed: objectProduct({ - period: ['1m'], + periodLength: ['1m'], min_periods: [52], baseline_periods: range(1000, 5000, 200), trigger_factor: range(1.0, 2.0, 0.1) }), trend_ema: objectProduct({ - period: ['2m'], + periodLength: ['2m'], min_periods: [52], trend_ema: range(TREND_EMA_MIN, TREND_EMA_MAX), neutral_rate: (NEUTRAL_RATE_AUTO ? new Array('auto') : []).concat(range(NEUTRAL_RATE_MIN, NEUTRAL_RATE_MAX).map(r => r / 100)), diff --git a/scripts/auto_backtester/backtester_trust_distrust.js b/scripts/auto_backtester/backtester_trust_distrust.js index 0f7b29220a..0c4f9f738b 100644 --- a/scripts/auto_backtester/backtester_trust_distrust.js +++ b/scripts/auto_backtester/backtester_trust_distrust.js @@ -8,7 +8,7 @@ * EMA Parameters: "trend_ema", "neutral_rate" * RSI Parameters: "oversold_rsi", "oversold_rsi_periods" * - * Example: ./backtester.js gdax.ETH-USD --days=10 --currency_capital=5 --period=1m + * Example: ./backtester.js gdax.ETH-USD --days=10 --currency_capital=5 --periodLength=1m */ let shell = require('shelljs'); @@ -114,7 +114,7 @@ let processOutput = output => { sellMin: params.sell_min, buyThreshold: params.buy_threshold, days: days, - period: params.periodLength, + periodLength: params.periodLength, roi: roi, wlRatio: losses > 0 ? roundp(wins / losses, 3) : 'Infinity', frequency: roundp((wins + losses) / days, 3) @@ -126,7 +126,7 @@ let strategies = objectProduct({ sell_threshold_max: range(SELL_THRESHOLD_MAX_MIN, SELL_THRESHOLD_MAX_MAX), sell_min: range(SELL_MIN_MIN, SELL_MIN_MAX), buy_threshold: range(BUY_THRESHOLD_MIN, BUY_THRESHOLD_MAX), - period: range(PERIOD_MIN, PERIOD_MAX) + periodLength: range(PERIOD_MIN, PERIOD_MAX) }); let tasks = strategies.map(strategy => { diff --git a/scripts/genetic_backtester/darwin.js b/scripts/genetic_backtester/darwin.js index d5f5ae7622..f99a7afc36 100755 --- a/scripts/genetic_backtester/darwin.js +++ b/scripts/genetic_backtester/darwin.js @@ -544,6 +544,42 @@ selectedStrategies.forEach(function(v) { } }); +var isUsefulKey = key => { + if(key == "filename" || key == "show_options" || key == "sim") return false; + return true; +} +var generateCommandParams = input => { + input = input.params.replace("module.exports =",""); + input = JSON.parse(input); + + var result = ""; + var keys = Object.keys(input); + for(i = 0;i < keys.length;i++){ + var key = keys[i]; + if(isUsefulKey(key)){ + // selector should be at start before keys + if(key == "selector"){ + result = input[key].normalized + result; + } + + else result += " --"+key+"="+input[key]; + } + + } + return result; +} +var saveGenerationData = function(csvFileName, jsonFileName, dataCSV, dataJSON, callback){ + fs.writeFile(csvFileName, dataCSV, err => { + if (err) throw err; + console.log("> Finished writing generation csv to " + csvFileName); + callback(1); + }); + fs.writeFile(jsonFileName, dataJSON, err => { + if (err) throw err; + console.log("> Finished writing generation json to " + jsonFileName); + callback(2); + }); +} let generationCount = 1; let simulateGeneration = () => { @@ -570,7 +606,7 @@ let simulateGeneration = () => { })).reduce((a, b) => a.concat(b)); parallel(tasks, PARALLEL_LIMIT, (err, results) => { - console.log("\Generation complete, saving results..."); + console.log("\n\Generation complete, saving results..."); results = results.filter(function(r) { return !!r; }); @@ -580,44 +616,52 @@ let simulateGeneration = () => { let fieldsGeneral = ['selector', 'fitness', 'vsBuyHold', 'wlRatio', 'frequency', 'strategy', 'order_type', 'endBalance', 'buyHold', 'wins', 'losses', 'periodLength', 'min_periods', 'days', 'params']; let fieldNamesGeneral = ['Selector', 'Fitness', 'VS Buy Hold (%)', 'Win/Loss Ratio', '# Trades/Day', 'Strategy', 'Order Type', 'Ending Balance ($)', 'Buy Hold ($)', '# Wins', '# Losses', 'Period', 'Min Periods', '# Days', 'Full Parameters']; - let csv = json2csv({ + let dataCSV = json2csv({ data: results, fields: fieldsGeneral, fieldNames: fieldNamesGeneral }); let fileDate = Math.round(+new Date() / 1000); - let fileName = `simulations/backtesting_${fileDate}.csv`; - fs.writeFile(fileName, csv, err => { - if (err) throw err; - }); - - // let fileNameJSON = `simulations/backtesting_${fileDate}.json`; - // fs.writeFile(fileNameJSON, JSON.stringify(results, null, 2), err => { - // if (err) throw err; - // }); - + let csvFileName = `simulations/backtesting_${fileDate}.csv`; + let poolData = {}; selectedStrategies.forEach(function(v) { poolData[v] = pools[v]['pool'].population(); }); - let poolFileName = `simulations/generation_data_${fileDate}_gen_${generationCount}.json`; - let poolDataJSON = JSON.stringify(poolData, null, 2); - fs.writeFile(poolFileName, poolDataJSON, err => { - if (err) throw err; - }); - - console.log(`\n\nGeneration's Best Results`); - - selectedStrategies.forEach(function(v) { - let best = pools[v]['pool'].best(); - console.log(`(${v}) VS Buy and Hold: ${best.sim.vsBuyHold} End Balance: ${best.sim.endBalance}`); - - let nextGen = pools[v]['pool'].evolve(); + let jsonFileName = `simulations/generation_data_${fileDate}_gen_${generationCount}.json`; + let dataJSON = JSON.stringify(poolData, null, 2); + var filesSaved = 0; + saveGenerationData(csvFileName, jsonFileName, dataCSV, dataJSON, (id)=>{ + filesSaved++; + if(filesSaved == 2){ + console.log(`\n\nGeneration's Best Results`); + selectedStrategies.forEach((v)=> { + let best = pools[v]['pool'].best(); + if(best.sim){ + console.log(`\t(${v}) Sim Fitness ${best.sim.fitness}, VS Buy and Hold: ${best.sim.vsBuyHold} End Balance: ${best.sim.endBalance}, Wins/Losses ${best.sim.wins}/${best.sim.losses}.`); + + } else { + console.log(`\t(${v}) Result Fitness ${results[0].fitness}, VS Buy and Hold: ${results[0].vsBuyHold}, End Balance: ${results[0].endBalance}, Wins/Losses ${results[0].wins}/${results[0].losses}.`); + } + + // prepare command snippet from top result for this strat + let prefix = './zenbot.sh sim '; + let bestCommand = generateCommandParams(results[0]); + + bestCommand = prefix + bestCommand; + bestCommand = bestCommand + ' --days=' + argv.days + ' --asset_capital=' + argv.asset_capital + ' --currency_capital=' + argv.currency_capital; + + console.log(bestCommand + '\n'); + + let nextGen = pools[v]['pool'].evolve(); + }); + + simulateGeneration(); + } }); - - simulateGeneration(); + }); }; diff --git a/test/lib/services/collections-service.test.js b/test/lib/services/collections-service.test.js new file mode 100644 index 0000000000..fb4263592d --- /dev/null +++ b/test/lib/services/collections-service.test.js @@ -0,0 +1,58 @@ + +var service = require('../../../lib/services/collection-service') + +describe('Collections Service', function() { + beforeEach(function() { + foo = { + get: function() { }, + set: function() { }, + clear: function() { } + } + }) + + describe(' trades ', function() { + beforeEach(function() { + spyOn(foo, 'get').and.returnValues([1, 2, 3], {collection: function() { return { ensureIndex: function() { }} } }); + }) + + it('is available', function() { + expect(service).not.toBe(undefined); + }), + + it('returns the expected objects', function() { + + var instance = service(foo.get, foo.set, foo.clear) + + var rtn = instance.getTrades() + + expect(rtn).toBeDefined(); + expect(rtn.length).toEqual(3); + expect(rtn[0]).toEqual(1); + expect(rtn[1]).toEqual(2); + expect(rtn[2]).toEqual(3); + }) + }), + + describe(' resume_markers ', function() { + beforeEach(function() { + spyOn(foo, 'get').and.returnValues([1, 2, 3], {collection: function() { return { ensureIndex: function() { }} } }); + }) + + it('is available', function() { + expect(service).not.toBe(undefined); + }), + + it('returns the expected objects', function() { + + var instance = service(foo.get, foo.set, foo.clear) + + var rtn = instance.getResumeMarkers() + + expect(rtn).toBeDefined(); + expect(rtn.length).toEqual(3); + expect(rtn[0]).toEqual(1); + expect(rtn[1]).toEqual(2); + expect(rtn[2]).toEqual(3); + }) + }) +});