diff --git a/core/error.js b/core/error.js deleted file mode 100644 index aafdec49c..000000000 --- a/core/error.js +++ /dev/null @@ -1,25 +0,0 @@ -const _ = require('lodash'); - -let RetryError = function(message) { - _.bindAll(this); - - this.name = "RetryError"; - this.message = message; -} - -RetryError.prototype = new Error(); - -let AbortError = function(message) { - _.bindAll(this); - - this.name = "AbortError"; - this.message = message; -} - -AbortError.prototype = new Error(); - -module.exports = { - 'RetryError': RetryError, - 'AbortError': AbortError -}; - diff --git a/core/util.js b/core/util.js index 41a918088..39cd4ab1f 100644 --- a/core/util.js +++ b/core/util.js @@ -4,8 +4,6 @@ var path = require('path'); var fs = require('fs'); var semver = require('semver'); var program = require('commander'); -var retry = require('retry'); -var Errors = require('./error'); var startTime = moment(); @@ -17,19 +15,6 @@ var _gekkoEnv = false; var _args = false; -var retryHelper = function(fn, options, callback) { - var operation = retry.operation(options); - operation.attempt(function(currentAttempt) { - fn(function(err, result) { - if (!(err instanceof Errors.AbortError) && operation.retry(err)) { - return; - } - - callback(err ? err.message : null, result); - }); - }); -} - // helper functions var util = { getConfig: function() { @@ -183,19 +168,6 @@ var util = { getStartTime: function() { return startTime; }, - retry: function(fn, callback) { - var options = { - retries: 5, - factor: 1.2, - minTimeout: 1 * 1000, - maxTimeout: 3 * 1000 - }; - - retryHelper(fn, options, callback); - }, - retryCustom: function(options, fn, callback) { - retryHelper(fn, options, callback); - }, } // NOTE: those options are only used diff --git a/docs/extending/add_an_exchange.md b/docs/extending/add_an_exchange.md index d11754cb8..23508309f 100644 --- a/docs/extending/add_an_exchange.md +++ b/docs/extending/add_an_exchange.md @@ -67,19 +67,23 @@ This should create a buy / sell order at the exchange for [amount] of [asset] at this.exchange.getOrder(order, callback); -The order will be something that the manager previously received via the `sell` or `buy` methods. The callback should have the parameters `err` and `order`. Order is an object with properties `price`, `amount` and `date`. Price is the (volume weighted) average price of all trades necesarry to execute the order. Amount is the amount of currency traded and Date is a moment object of the last trade part of this order. +Will only be called on orders that have been completed. The order will be something that the manager previously received via the `sell` or `buy` methods. The callback should have the parameters `err` and `order`. Order is an object with properties `price`, `amount` and `date`. Price is the (volume weighted) average price of all trades necesarry to execute the order. Amount is the amount of currency traded and Date is a moment object of the last trade part of this order. ### checkOrder this.exchange.checkOrder(order, callback); -The order will be something that the manager previously received via the `sell` or `buy` methods. The callback should have the parameters `err` and `filled`. Filled is a boolean that is true when the order is already filled and false when it is not. Currently partially filled orders should be treated as not filled. +The order will be something that the manager previously received via the `sell` or `buy` methods. The callback should have the parameters `err` and a `result` object. The result object will have two or three properties: + +- `open`: whether this order is currently in the orderbook. +- `completed`: whether this order has executed (filled completely). +- `filledAmount`: the amount of the order that has been filled. This property is only needed when both `open` is true and `completed` is false. ### cancelOrder this.exchange.cancelOrder(order, callback); -The order will be something that the manager previously received via the `sell` or `buy` methods. The callback should have the parameterer `err`. +The order will be something that the manager previously received via the `sell` or `buy` methods. The callback should have the parameterers `err` and `filled`, `filled` last one should be true if the order was filled before it could be cancelled. ## Trading method's expectations @@ -89,20 +93,26 @@ The trading method analyzes exchange data to determine what to do. The trading m this.watcher.getTrades(since, callback, descending); - If since is truthy, Gekko requests as much trades as the exchange can give (up to ~10,000 trades, if the exchange supports more you can [create an importer](../features/importing.md)). The callback expects an error and a `trades` object. Trades is an array of trade objects in chronological order (0 is older trade, 1 is newer trade). Each trade object needs to have: - a `date` property (unix timestamp in either string or int) -- a `price` property (float) which represents the price in [currency] per 1 [asset]. ` +- a `price` property (float) which represents the price in [currency] per 1 [asset]. - an `amount` proprty (float) which represent the amount of [asset]. - a `tid` property (float) which represents the tradeID. +## Error handling + +It is the responsibility of the wrapper to handle errors and retry the call in case of a temporary error. Gekko exposes a retry helper you can use to implement an exponential backoff retry strategy. Your wrapper does need pass a proper error object explaining whether the call can be retried or not. If the error is fatal (for example private API calls with invalid API keys) the wrapper is expected to upstream this error. If the error is retryable (exchange is overloaded or a network error) an error should be passed with the property `notFatal` set to true. If the exchange replied with another error that might be temporary (for example an `Insufficiant funds` erorr right after Gekko canceled an order, which might be caused by the exchange not having updated the balance yet) the error object can be extended with an `retry` property indicating that the call can be retried for n times but after that the error should be considered fatal. -### Recompiling Gekko UI +For implementation refer to the bitfinex implementation, in a gist this is what a common flow looks like: -Once you added your exchange you can use it with Gekko! However if you want the new exchange to show up in the web interface you need to recompile the frontend (so your updated `exchanges.js` file is used by the webapp). [Read here](https://gekko.wizb.it/docs/internals/gekko_ui.html#Developing-for-the-Gekko-UI-frontend) how to do that. +- (async call) `exchange.buy`, then +- handle the response and normalize the error so the retry helper understands it, then +- the retry helper will determine whether the call needs to be retried, then + - based on the error it will retry (nonFatal or retry) + - if no error it will pass it to your handle function that normalizes the output ## Capabilities diff --git a/docs/gekko-broker/introduction.md b/docs/gekko-broker/introduction.md new file mode 100644 index 000000000..00222b64e --- /dev/null +++ b/docs/gekko-broker/introduction.md @@ -0,0 +1,65 @@ +# Gekko Broker + +Order execution library for bitcoin and crypto exchanges. This library is Gekko's execution engine for all live orders (simulated orders go through the paper trader, which is a separate module). This library is intended for developers of trading systems in need for advanced order types on multiple exchanges over a unified API. + +## Introduction + +This library makes it easy to do (advanced) orders all crypto exchanges where Gekko can live trade at. See the complete list here: https://gekko.wizb.it/docs/introduction/supported_exchanges.html + +This library allows you to: + +- Get basic market data + - ticker (BBO) + - orderbook (TODO) + - historical trades +- Get portfolio data +- Do an (advanced) order: + - Submit the order to the exchange + - Receive events about the status of the order: + - submitted + - open (accepted) + - partially filled + - rejected + - completed + - Mutate the order + - cancel + - amend + - move + - move limit +- Tracks orders submitted through the library. + +## Status + +Early WIP. All communication is via the REST APIs of exhanges. Not all exchanges are supported. + +Currently supported exchanges: + +- Binance +- GDAX +- Poloniex +- Coinfalcon + +## Order types + +This library aims to offer advanced order types, even on exchanges that do not natively support them by tracking the market and supplimenting native order support on specific exchanges. + +WIP: + +- Base orders + - Limit Order + - [Sticky Order](./sticky_order.md) + +TODO: + +- Base orders: + - Market Order +- Triggers: + - Stop + - If Touched (stop but opposite direction) + +### TODO + +- finish all exchange integrations that gekko supports +- finish all order types and triggers (under todo) +- implement websocket support (per exchange) +- use native move API calls wherever possible (poloniex) diff --git a/docs/gekko-broker/sticky_order.md b/docs/gekko-broker/sticky_order.md new file mode 100644 index 000000000..aeff789e6 --- /dev/null +++ b/docs/gekko-broker/sticky_order.md @@ -0,0 +1,45 @@ +# Sticky Order + +An advanced order that stays at the top of the book (until the optional limit). The order will automatically stick to the best BBO until the complete amount has been filled. + +TODO: + +- implement "overtake" +- implement fallback this order is alone at the top, some spread before everyone else +- finalize API +- add more events / ways to debug +- pull ticker data out of this order market data should flow from the broker (so we can easier move to at least public websocket streams). + +## Example usage + + const Broker = require('gekko-broker'); + + const gdax = new Broker({ + currency: 'EUR', + asset: 'BTC', + + exchange: 'gdax', + + // Enables access to private endpoints. + // Needed to create orders and fetch portfolio + private: true, + + key: 'x', + secret: 'y', + passphrase: 'z' + }); + + gdax.portfolio.setBalances(console.log); + + const type = 'sticky'; + const amount = 0.5; + const side = 'buy'; + const limit = 6555; + + const order = gdax.createOrder(type, side, amount, { limit }); + order.on('statusChange', status => console.log(status)); + order.on('filled', result => console.log(result)); + order.on('completed', result => console.log(result)); + + order.moveAmount(1); + order.moveLimit(6666); \ No newline at end of file diff --git a/docs/introduction/supported_exchanges.md b/docs/introduction/supported_exchanges.md index e2d4012ab..b49b37418 100644 --- a/docs/introduction/supported_exchanges.md +++ b/docs/introduction/supported_exchanges.md @@ -55,6 +55,6 @@ Gekko is able to directly communicate with the APIs of a number of exchanges. Ho [22]: https://vip.bitcoin.co.id/ [23]: https://quadrigacx.com/ [24]: https://www.binance.com/?ref=11236330 -[25]: https://coinfalcon.com/?ref=CFJSTEXJQFFE +[25]: https://coinfalcon.com/?ref=CFJSQBMXZZDS diff --git a/exchange/README.md b/exchange/README.md new file mode 100644 index 000000000..c9efdae3e --- /dev/null +++ b/exchange/README.md @@ -0,0 +1,3 @@ +# Gekko Broker + +see [the docs](../docs/gekko-broker/introduction.md). \ No newline at end of file diff --git a/core/exchangeChecker.js b/exchange/exchangeChecker.js similarity index 82% rename from core/exchangeChecker.js rename to exchange/exchangeChecker.js index a4542b11e..578b404ab 100644 --- a/core/exchangeChecker.js +++ b/exchange/exchangeChecker.js @@ -1,31 +1,17 @@ -var _ = require('lodash'); -var fs = require('fs'); -var util = require('./util'); -var config = util.getConfig(); -var dirs = util.dirs(); -var moment = require('moment'); - -var Checker = function() { - _.bindAll(this); -} +const _ = require('lodash'); +const fs = require('fs'); +const moment = require('moment'); +const errors = require('./exchangeErrors'); -Checker.prototype.notValid = function(conf) { - if(conf.tradingEnabled) - return this.cantTrade(conf); - else - return this.cantMonitor(conf); +const Checker = function() { + _.bindAll(this); } Checker.prototype.getExchangeCapabilities = function(slug) { - var capabilities; - - if(!fs.existsSync(dirs.exchanges + slug + '.js')) - util.die(`Gekko does not know exchange "${slug}"`); - - var Trader = require(dirs.exchanges + slug); - capabilities = Trader.getCapabilities(); + if(!fs.existsSync('./wrappers/' + slug + '.js')) + throw new errors.ExchangeError(`Gekko does not know exchange "${slug}"`); - return capabilities; + return require('./wrappers/' + slug).getCapabilities(); } // check if the exchange is configured correctly for monitoring @@ -56,7 +42,7 @@ Checker.prototype.cantMonitor = function(conf) { if(!pair) return 'Gekko does not support this currency/assets pair at ' + name; - // everyting okay + // everything is okay return false; } diff --git a/exchange/exchangeErrors.js b/exchange/exchangeErrors.js new file mode 100644 index 000000000..4dca77cf5 --- /dev/null +++ b/exchange/exchangeErrors.js @@ -0,0 +1,42 @@ +const _ = require('lodash'); + +const ExchangeError = function(message) { + _.bindAll(this); + + this.name = "ExchangeError"; + this.message = message; +} +ExchangeError.prototype = new Error(); + +const ExchangeAuthenticationError = function(message) { + _.bindAll(this); + + this.name = "ExchangeAuthenticationError"; + this.message = message; +} +ExchangeAuthenticationError.prototype = new Error(); + +const RetryError = function(message) { + _.bindAll(this); + + this.name = "RetryError"; + this.retry = 5; + this.message = message; +} +RetryError.prototype = new Error(); + +const AbortError = function(message) { + _.bindAll(this); + + this.name = "AbortError"; + this.message = message; +} +AbortError.prototype = new Error(); + +module.exports = { + ExchangeError, + ExchangeAuthenticationError, + RetryError, + AbortError +}; + diff --git a/exchange/exchangeUtils.js b/exchange/exchangeUtils.js new file mode 100644 index 000000000..f97964cd4 --- /dev/null +++ b/exchange/exchangeUtils.js @@ -0,0 +1,58 @@ +// generic low level reusuable utils for interacting with exchanges. + +const retry = require('retry'); +const errors = require('./exchangeErrors'); + +const retryInstance = (options, checkFn, callback) => { + if(!options) { + options = { + retries: 100, + factor: 1.2, + minTimeout: 1 * 1000, + maxTimeout: 4 * 1000 + }; + } + + let attempt = 0; + + const operation = retry.operation(options); + operation.attempt(function(currentAttempt) { + checkFn((err, result) => { + if(!err) { + return callback(undefined, result); + } + + let maxAttempts = err.retry; + if(maxAttempts === true) + maxAttempts = 10; + + if(err.retry && attempt++ < maxAttempts) { + return operation.retry(err); + } + + if(err.notFatal) { + return operation.retry(err); + } + + callback(err, result); + }); + }); +} + +// es6 bind all: https://github.com/posrix/es6-class-bind-all/blob/master/lib/es6ClassBindAll.js +const allMethods = targetClass => { + const propertys = Object.getOwnPropertyNames(Object.getPrototypeOf(targetClass)) + propertys.splice(propertys.indexOf('constructor'), 1) + return propertys +} + +const bindAll = (targetClass, methodNames = []) => { + for (const name of !methodNames.length ? allMethods(targetClass) : methodNames) { + targetClass[name] = targetClass[name].bind(targetClass) + } +} + +module.exports = { + retry: retryInstance, + bindAll +} \ No newline at end of file diff --git a/exchange/gekkoBroker.js b/exchange/gekkoBroker.js new file mode 100644 index 000000000..bf423d13b --- /dev/null +++ b/exchange/gekkoBroker.js @@ -0,0 +1,134 @@ +/* + The broker manages all communicatinn with the exchange, delegating: + + - the management of the portfolio to the portfolioManager + - the management of actual trades to orders. +*/ + +const _ = require('lodash'); +const async = require('async'); +const events = require('events'); +const moment = require('moment'); +const checker = require('./exchangeChecker'); +const errors = require('./exchangeErrors'); +const Portfolio = require('./portfolioManager'); +// const Market = require('./market'); +const orders = require('./orders'); +const bindAll = require('./exchangeUtils').bindAll; + +class Broker { + constructor(config) { + this.config = config; + + this.orders = { + // contains current open orders + open: [], + // contains all closed orders + closed: [] + } + + const slug = config.exchange.toLowerCase(); + + const API = require('./wrappers/' + slug); + + this.api = new API(config); + + this.marketConfig = _.find(API.getCapabilities().markets, (p) => { + return _.first(p.pair) === config.currency.toUpperCase() && + _.last(p.pair) === config.asset.toUpperCase(); + }); + + this.interval = this.api.interval || 1500; + +// this.market = new Market(config); + + this.market = config.currency.toUpperCase() + config.asset.toUpperCase(); + + if(config.private) + this.portfolio = new Portfolio(config, this.api); + + bindAll(this); + } + + cantTrade() { + return checker.cantTrade(this.config); + } + + sync(callback) { + if(!this.private) { + this.setTicker(); + return; + } + + if(this.cantTrade()) + throw new errors.ExchangeError(this.cantTrade()); + + this.syncPrivateData(); + } + + syncPrivateData(callback) { + async.series([ + this.setTicker, + next => setTimeout(next, this.interval), + this.portfolio.setFee.bind(this.portfolio), + next => setTimeout(next, this.interval), + this.portfolio.setBalances.bind(this.portfolio), + next => setTimeout(next, this.interval) + ], callback); + } + + setTicker(callback) { + this.api.getTicker((err, ticker) => { + + if(err) { + if(err.message) { + console.log(err.message); + throw err; + } else { + console.log('err not wrapped in error:', err); + throw new errors.ExchangeError(err); + } + } + + this.ticker = ticker; + + if(_.isFunction(callback)) + callback(); + }); + } + + createOrder(type, side, amount, parameters, handler) { + if(!this.config.private) + throw new Error('Client not authenticated'); + + if(side !== 'buy' && side !== 'sell') + throw new Error('Unknown side ' + side); + + if(!orders[type]) + throw new Error('Unknown order type'); + + const order = new orders[type](this.api); + + this.orders.open.push(order); + + // todo: figure out a smarter generic way + this.syncPrivateData(() => { + order.setData({ + balances: this.portfolio.balances, + ticker: this.ticker, + market: this.marketConfig + }); + + order.create(side, amount, parameters); + }); + + order.on('completed', summary => { + _.remove(this.orders.open, order); + this.orders.closed.push(summary); + }); + + return order; + } +} + +module.exports = Broker; \ No newline at end of file diff --git a/exchange/orders/index.js b/exchange/orders/index.js new file mode 100644 index 000000000..6b28034f5 --- /dev/null +++ b/exchange/orders/index.js @@ -0,0 +1,7 @@ +const sticky = require('./sticky'); +const limit = require('./limit'); + +module.exports = { + sticky, + limit +} \ No newline at end of file diff --git a/exchange/orders/limit.js b/exchange/orders/limit.js new file mode 100644 index 000000000..b229caa5e --- /dev/null +++ b/exchange/orders/limit.js @@ -0,0 +1,239 @@ +/* + The limit order is a simple order: + - It is created at the specified price + - If it were to cross it will throw instead (only if postOnly is specified) + - It can be moved + +*/ + +const _ = require('lodash'); +const async = require('async'); +const events = require('events'); +const moment = require('moment'); +const errors = require('../exchangeErrors'); +const BaseOrder = require('./order'); +const states = require('./states'); + +class LimitOrder extends BaseOrder { + constructor(api) { + super(api); + } + + create(side, amount, params) { + this.side = side; + + this.postOnly = params.postOnly; + + this.status = states.SUBMITTED; + this.emitStatus(); + + this.createOrder(price, amount); + } + + createOrder(price, amount) { + this.amount = this.api.roundAmount(amount); + this.price = this.api.roundPrice(price); + + // note: this assumes ticker data to be up to date + if(this.postOnly) { + if(side === 'buy' && this.price > this.data.ticker.ask) + throw new Error('Order crosses the book'); + else if(side === 'sell' && this.price < this.data.ticker.bid) + throw new Error('Order crosses the book'); + } + + this.submit({ + side: this.side, + amount: this.api.roundAmount(this.amount - alreadyFilled), + price: this.price, + alreadyFilled: this.filled + }); + } + + handleCreate(err, id) { + if(err) + throw err; + + this.status = states.OPEN; + this.emitStatus(); + + this.id = id; + + if(this.cancelling) + return this.cancel(); + + if(this.movingAmount) + return this.moveAmount(); + + if(this.movingPrice) + return this.movePrice(); + + this.timeout = setTimeout(this.checkOrder, this.checkInterval) + } + + checkOrder() { + this.checking = true; + this.api.checkOrder(this.id, this.handleCheck); + } + + handleCheck(err, result) { + if(this.cancelling || this.status === states.CANCELLED) + return; + + this.checking = false; + + if(err) + throw err; + + if(result.open) { + if(result.filledAmount !== this.filledAmount) { + this.filledAmount = result.filledAmount; + + // note: doc event API + this.emit('fill', this.filledAmount); + } + + if(this.cancelling) + return this.cancel(); + + if(this.movingAmount) + return this.moveAmount(); + + if(this.movingPrice) + return this.movePrice(); + + this.timeout = setTimeout(this.checkOrder, this.checkInterval); + return; + } + + if(!result.executed) { + // not open and not executed means it never hit the book + this.rejected(); + return; + } + + this.filled(this.price); + } + + movePrice(price) { + if(this.completed) + return; + + if(!price) + price = this.movePriceTo; + + if(this.price === this.api.roundPrice(price)) + // effectively nothing changed + return; + + if( + this.status === states.SUBMITTED || + this.status === states.MOVING || + this.checking + ) { + this.movePriceTo = price; + this.movingPrice = true; + return; + } + + this.movingPrice = false; + + this.price = this.api.roundPrice(price); + + clearTimeout(this.timeout); + + this.status = states.MOVING; + + this.api.cancelOrder(this.id, (err, filled) => { + if(err) + throw err; + + if(filled) + return this.filled(this.price); + + this.submit({ + side: this.side, + amount: this.amount, + price: this.price, + alreadyFilled: this.filled + }); + }); + } + + moveAmount(amount) { + if(this.completed) + return; + + if(!amount) + amount = this.moveAmountTo; + + if(this.amount === this.api.roundAmount(amount)) + // effectively nothing changed + return; + + if( + this.status === states.SUBMITTED || + this.status === states.MOVING || + this.checking + ) { + this.moveAmountTo = amount; + this.movingAmount = true; + return; + } + + this.movingAmount = false; + this.amount = this.api.roundAmount(amount); + + clearTimeout(this.timeout); + + this.status = states.MOVING; + this.emitStatus(); + + this.api.cancelOrder(this.id, (err, filled) => { + if(err) + throw err; + + if(filled) + return this.filled(this.price); + + this.submit({ + side: this.side, + amount: this.amount, + price: this.price, + alreadyFilled: this.filled + }); + }); + } + + cancel() { + if(this.completed) + return; + + if( + this.status === states.SUBMITTED || + this.status === states.MOVING || + this.checking + ) { + this.cancelling = true; + return; + } + + clearTimeout(this.timeout); + + this.api.cancelOrder(this.id, (err, filled) => { + if(err) + throw err; + + this.cancelling = false; + + if(filled) + return this.filled(this.price); + + this.status = states.CANCELLED; + this.emitStatus(); + this.finish(false); + }); + } +} + +module.exports = LimitOrder; \ No newline at end of file diff --git a/exchange/orders/order.js b/exchange/orders/order.js new file mode 100644 index 000000000..b7e044a83 --- /dev/null +++ b/exchange/orders/order.js @@ -0,0 +1,113 @@ +const EventEmitter = require('events'); +const _ = require('lodash'); + +const bindAll = require('../exchangeUtils').bindAll; +const states = require('./states'); + +// base order + +class BaseOrder extends EventEmitter { + constructor(api) { + super(); + + this.api = api; + + this.checkInterval = api.interval || 1500; + + this.status = states.INITIALIZING; + + this.completed = false; + this.completing = false; + + bindAll(this); + } + + submit({side, amount, price, alreadyFilled}) { + + // Check amount + if(amount < this.data.market.minimalOrder.amount) { + if(alreadyFilled) { + // partially filled, but the remainder is too + // small. + return this.filled(); + } + + // We are not partially filled, meaning the + // amount passed was too small to even start. + throw new Error('Amount is too small'); + } + + // Some exchanges have restrictions on prices + if( + _.isFunction(this.api.isValidPrice) && + !this.api.isValidPrice(price) + ) { + if(alreadyFilled) { + // partially filled, but the remainder is too + // small. + return this.filled(); + } + + // We are not partially filled, meaning the + // amount passed was too small to even start. + throw new Error('Price is not valid'); + } + + // Some exchanges have restrictions on lot sizes + if( + _.isFunction(this.api.isValidLot) && + !this.api.isValidLot(this.price, amount) + ) { + if(alreadyFilled) { + // partially filled, but the remainder is too + // small. + return this.filled(); + } + + // We are not partially filled, meaning the + // amount passed was too small to even start. + throw new Error('Lot size is too small'); + } + + this.api[this.side](amount, this.price, this.handleCreate); + } + + setData(data) { + this.data = data; + } + + emitStatus() { + this.emit('statusChange', this.status); + } + + cancelled() { + this.status = states.CANCELLED; + this.completed = true; + this.finish(); + } + + rejected(reason) { + this.rejectedReason = reason; + this.status = states.REJECTED; + this.finish(); + } + + filled(price) { + this.status = states.FILLED; + this.emitStatus(); + + this.completed = true; + + this.finish(true); + } + + finish(filled) { + this.completed = true; + this.emit('completed', { + status: this.status, + filled + }) + } +} + +module.exports = BaseOrder; \ No newline at end of file diff --git a/exchange/orders/states.js b/exchange/orders/states.js new file mode 100644 index 000000000..ac285c6ed --- /dev/null +++ b/exchange/orders/states.js @@ -0,0 +1,28 @@ +const states = { + // Not created + INITIALIZING: 'INITIALIZING', + + // Created and send to the exchange, but no acknowledgement received yet + SUBMITTED: 'SUBMITTED', + + // In the process of moving the order + MOVING: 'MOVING', + + // Order is open on the exchange + OPEN: 'OPEN', + + + // the orders below indicate a fully completed order + + + // Order is completely filled + FILLED: 'FILLED', + + // Order was succesfully cancelled + CANCELLED: 'CANCELLED', + + // Order was rejected by the exchange + REJECTED: 'REJECTED' +} + +module.exports = states; \ No newline at end of file diff --git a/exchange/orders/sticky.js b/exchange/orders/sticky.js new file mode 100644 index 000000000..3ef75411b --- /dev/null +++ b/exchange/orders/sticky.js @@ -0,0 +1,430 @@ +/* + The sticky order is an advanced order: + - It is created at max price X + - if limit is not specified always at bbo. + - if limit is specified the price is either limit or the bbo (whichever is comes first) + - it will readjust the order: + - if overtake is true it will overbid the current bbo <- TODO + - if overtake is false it will stick to current bbo when this moves + - If the price moves away from the order it will "stick to" the top + + TODO: + - specify move behaviour (create new one first and cancel old order later?) + - native move + - if overtake is true it will overbid the current bbo +*/ + +const _ = require('lodash'); +const async = require('async'); +const events = require('events'); +const moment = require('moment'); +const errors = require('../exchangeErrors'); +const BaseOrder = require('./order'); +const states = require('./states'); + +class StickyOrder extends BaseOrder { + constructor(api) { + super(api); + + // global async lock + this.sticking = false; + } + + createSummary(next) { + if(!this.completed) + console.log(new Date, 'createSummary BUT ORDER NOT COMPLETED!'); + + if(!next) + next = _.noop; + + const checkOrders = _.keys(this.orders) + .map(id => next => { + + if(!this.orders[id].filled) { + return next(); + } + + setTimeout(() => this.api.getOrder(id, next), this.timeout); + }); + + async.series(checkOrders, (err, trades) => { + if(err) { + return next(err); + } + + let price = 0; + let amount = 0; + let date = moment(0); + + _.each(trades, trade => { + // last fill counts + date = moment(trade.date); + price = ((price * amount) + (+trade.price * trade.amount)) / (+trade.amount + amount); + amount += +trade.amount; + }); + + const summary = { + price, + amount, + date, + side: this.side, + orders: trades.length + } + + if(_.first(trades) && _.first(trades).fees) { + _.each(trades, trade => { + summary.fees = {}; + _.each(trade.fees, (amount, currency) => { + if(!_.isNumber(summary.fees[currency])) { + summary.fees[currency] = amount; + } else { + summary.fees[currency] += amount; + } + }); + }); + } + + this.emit('summary', summary); + next(undefined, summary); + }); + } + + create(side, rawAmount, params = {}) { + if(this.completed || this.completing) { + return false; + } + + this.side = side; + + this.amount = this.api.roundAmount(rawAmount); + + if(side === 'buy') { + if(params.limit) + this.limit = this.api.roundPrice(params.limit); + else + this.limit = Infinity; + } else { + if(params.limit) + this.limit = this.api.roundPrice(params.limit); + else + this.limit = -Infinity; + } + + this.status = states.SUBMITTED; + this.emitStatus(); + + this.orders = {}; + + // note: currently always sticks to max BBO, does not overtake + if(side === 'buy') + this.price = Math.min(this.data.ticker.bid, this.limit); + else + this.price = Math.max(this.data.ticker.ask, this.limit); + + this.createOrder(); + + return this; + } + + createOrder() { + if(this.completed || this.completing) { + return false; + } + + const alreadyFilled = this.calculateFilled(); + + // console.log(new Date, `creating [${this.api.name}] ${this.side} ${this.api.roundAmount(this.amount - alreadyFilled)} at ${this.price}`); + this.submit({ + side: this.side, + amount: this.api.roundAmount(this.amount - alreadyFilled), + price: this.price, + alreadyFilled + }); + } + + handleCreate(err, id) { + if(err) { + console.log('handleCreate', err.message); + throw err; + } + + if(!id) + console.log('BLUP! no id...'); + + // potentailly clean up old order + if( + this.id && + this.orders[this.id] && + this.orders[this.id].filled === 0 + ) + delete this.orders[this.id]; + + // register new order + this.id = id; + this.orders[id] = { + price: this.price, + filled: 0 + } + + this.emit('new order', this.id); + + this.status = states.OPEN; + this.emitStatus(); + + // remove lock + this.sticking = false; + + // check whether we had an action pending + if(this.cancelling) + return this.cancel(); + + if(this.movingLimit) + return this.moveLimit(); + + if(this.movingAmount) + return this.moveAmount(); + + // register check + this.timeout = setTimeout(this.checkOrder, this.checkInterval); + } + + checkOrder() { + if(this.completed || this.completing) { + return console.log(new Date, 'checkOrder called on completed/completing order..', this.completed, this.completing); + } + + this.sticking = true; + + this.api.checkOrder(this.id, (err, result) => { + if(err) { + console.log(new Date, 'error creating:', err.message); + throw err; + } + + if(result.open) { + if(result.filledAmount !== this.orders[this.id].filled) { + this.orders[this.id].filled = result.filledAmount; + this.emit('fill', this.calculateFilled()); + } + + // if we are already at limit we dont care where the top is + // note: might be string VS float + if(this.price == this.limit) { + this.timeout = setTimeout(this.checkOrder, this.checkInterval); + this.sticking = false; + return; + } + + this.api.getTicker((err, ticker) => { + if(err) + throw err; + + this.ticker = ticker; + + let top; + if(this.side === 'buy') + top = Math.min(ticker.bid, this.limit); + else + top = Math.max(ticker.ask, this.limit); + + // note: might be string VS float + if(top != this.price) + return this.move(top); + + this.timeout = setTimeout(this.checkOrder, this.checkInterval); + this.sticking = false; + }); + + return; + } + + if(!result.executed) { + // not open and not executed means it never hit the book + this.sticking = false; + this.status = states.REJECTED; + this.emitStatus(); + this.finish(); + return; + } + + // order got filled! + this.orders[this.id].filled = this.amount; + this.sticking = false; + this.emit('fill', this.amount); + this.filled(this.price); + + }); + } + + move(price) { + if(this.completed || this.completing) { + return false; + } + + this.status = states.MOVING; + this.emitStatus(); + + this.api.cancelOrder(this.id, (err, filled) => { + // it got filled before we could cancel + if(filled) { + this.orders[this.id].filled = this.amount; + this.emit('fill', this.amount); + return this.filled(this.price); + } + + // update to new price + this.price = this.api.roundPrice(price); + + this.createOrder(); + }); + + return true; + } + + calculateFilled() { + let totalFilled = 0; + _.each(this.orders, (order, id) => totalFilled += order.filled); + + return totalFilled; + } + + moveLimit(limit) { + if(this.completed || this.completing) { + return false; + } + + if(!limit) { + limit = this.moveLimitTo; + } + + if(this.limit === this.api.roundPrice(limit)) + // effectively nothing changed + return false; + + if( + this.status === states.INITIALIZING || + this.status === states.SUBMITTED || + this.status === states.MOVING || + this.sticking + ) { + this.moveLimitTo = limit; + this.movingLimit = true; + return; + } + + this.limit = this.api.roundPrice(limit); + + clearTimeout(this.timeout); + + this.movingLimit = false; + + if(this.side === 'buy' && this.limit < this.price) { + this.sticking = true; + this.move(this.limit); + } else if(this.side === 'sell' && this.limit > this.price) { + this.sticking = true; + this.move(this.limit); + } else { + this.timeout = setTimeout(this.checkOrder, this.checkInterval); + } + + return true; + } + + moveAmount(amount) { + if(this.completed || this.completing) + return false; + + if(!amount) + amount = this.moveAmountTo; + + if(this.amount === this.api.roundAmount(amount)) + // effectively nothing changed + return true; + + if(this.calculateFilled() > this.api.roundAmount(amount)) { + // the amount is now below how much we have + // already filled. + this.filled(); + return false; + } + + if( + this.status === states.INITIALIZING || + this.status === states.SUBMITTED || + this.status === states.MOVING || + this.sticking + ) { + this.moveAmountTo = amount; + this.movingAmount = true; + return; + } + + this.amount = this.api.roundAmount(amount - this.calculateFilled()); + + if(this.amount < this.data.market.minimalOrder.amount) { + if(this.calculateFilled()) { + // we already filled enough of the order! + this.filled(); + return false; + } else { + throw new Error("The amount " + this.amount + " is too small."); + } + } + + clearTimeout(this.timeout); + + this.movingAmount = false; + this.sticking = true; + + this.api.cancelOrder(this.id, filled => { + + if(filled) { + this.emit('fill', this.amount); + return this.filled(this.price); + } + + this.createOrder(); + }); + + return true; + } + + cancel() { + if(this.completed) + return; + + if( + this.status === states.SUBMITTED || + this.status === states.MOVING || + this.sticking + ) { + this.cancelling = true; + return; + } + + this.completing = true; + clearTimeout(this.timeout); + this.api.cancelOrder(this.id, (err, filled) => { + if(err) { + throw err; + } + + this.cancelling = false; + + if(filled) { + this.orders[this.id].filled = this.amount; + this.emit('fill', this.amount); + return this.filled(this.price); + } + + this.status = states.CANCELLED; + this.emitStatus(); + + this.finish(false); + }) + } + +} + +module.exports = StickyOrder; \ No newline at end of file diff --git a/exchange/package-lock.json b/exchange/package-lock.json new file mode 100644 index 000000000..37abd401d --- /dev/null +++ b/exchange/package-lock.json @@ -0,0 +1,796 @@ +{ + "name": "gekko-broker", + "version": "0.0.2", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/caseless": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz", + "integrity": "sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A==" + }, + "@types/form-data": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", + "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", + "requires": { + "@types/node": "10.1.2" + } + }, + "@types/node": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.1.2.tgz", + "integrity": "sha512-bjk1RIeZBCe/WukrFToIVegOf91Pebr8cXYBwLBIsfiGWVQ+ifwWsT59H3RxrWzWrzd1l/Amk1/ioY5Fq3/bpA==" + }, + "@types/request": { + "version": "2.47.0", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.47.0.tgz", + "integrity": "sha512-/KXM5oev+nNCLIgBjkwbk8VqxmzI56woD4VUxn95O+YeQ8hJzcSmIZ1IN3WexiqBb6srzDo2bdMbsXxgXNkz5Q==", + "requires": { + "@types/caseless": "0.12.1", + "@types/form-data": "2.2.1", + "@types/node": "10.1.2", + "@types/tough-cookie": "2.3.3" + } + }, + "@types/tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha512-MDQLxNFRLasqS4UlkWMSACMKeSm1x4Q3TxzUC7KQUsh6RK1ZrQ0VEyE3yzXcBu+K8ejVj4wuX32eUG02yNp+YQ==" + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "requires": { + "lodash": "4.17.10" + } + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "2.5.6", + "regenerator-runtime": "0.11.1" + } + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "bignumber.js": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-6.0.0.tgz", + "integrity": "sha512-x247jIuy60/+FtMRvscqfxtVHQf8AGx2hm9c6btkgC0x/hp9yt+teISNhvF8WlwRkCc5yF2fDECH8SIMe8j+GA==" + }, + "binance": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/binance/-/binance-1.3.3.tgz", + "integrity": "sha512-1eV2QUoH/Z0FZPiGjigJg4udXV9Uu6Clr0Sg1xsX3xStgPfzXz0juA3mllQIiIaHx7dmfAQgEiZIyeJLx5ajag==", + "requires": { + "request": "2.86.0", + "underscore": "1.9.0", + "ws": "3.3.3" + } + }, + "bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha1-SfiW1uhYpKSZ34XDj7OZua/4QPg=" + }, + "bitfinex-api-node": { + "version": "2.0.0-beta.1", + "resolved": "https://registry.npmjs.org/bitfinex-api-node/-/bitfinex-api-node-2.0.0-beta.1.tgz", + "integrity": "sha512-UVKh4PAaijplm/VWs3ZLONQo8mhF0bemDZjEQCKtJteNlB+crmqRp/fPuWC2ZTYX2ZQ//J0zriOhEUiuwwGSDA==", + "requires": { + "bignumber.js": "6.0.0", + "cbq": "0.0.1", + "crc-32": "1.2.0", + "debug": "2.6.9", + "lodash": "4.17.10", + "lodash.throttle": "4.1.1", + "request": "2.86.0", + "request-promise": "4.2.2", + "ws": "3.3.3" + } + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "requires": { + "hoek": "4.2.1" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "cbq": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/cbq/-/cbq-0.0.1.tgz", + "integrity": "sha512-MCLjfpHAcI3gPdx26xoQx2JqRdNMf68ovhJkqNSiZIx/yQP6yNYYKbf8ww2J6kgNTPGIrXyugFlNz5jmGtg8BQ==" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "coinfalcon": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/coinfalcon/-/coinfalcon-1.0.3.tgz", + "integrity": "sha512-dzyLdeDGY9Fg4zewCFolK/TjB/Mrf9tpBupx7IAqhZcYH6jY5z7xxMywIgJnf4bbRKMIEnJ2GJFqgue9M1nwnw==", + "requires": { + "babel-runtime": "6.26.0" + } + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "core-js": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.6.tgz", + "integrity": "sha512-lQUVfQi0aLix2xpyjrrJEvfuYCqPc/HwmTKsC/VNf8q0zsjX7SQZtp4+oRONN5Tsur9GDETPjj+Ub2iDiGZfSQ==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "requires": { + "exit-on-epipe": "1.0.1", + "printj": "1.1.2" + } + }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "requires": { + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "requires": { + "hoek": "4.2.1" + } + } + } + }, + "ctype": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", + "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=", + "optional": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" + } + }, + "gdax": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/gdax/-/gdax-0.7.0.tgz", + "integrity": "sha512-lJHXlGchJVNtql8VWH+Idalehl5T5N6O9g5MUEW7IOuPtsbb7D15vgz6MOx1NgSyZe0fSIINv9s0HxujYB3sqg==", + "requires": { + "@types/request": "2.47.0", + "bignumber.js": "5.0.0", + "bintrees": "1.0.2", + "request": "2.86.0", + "ws": "4.1.0" + }, + "dependencies": { + "bignumber.js": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-5.0.0.tgz", + "integrity": "sha512-KWTu6ZMVk9sxlDJQh2YH1UOnfDP8O8TpxUxgQG/vKASoSnEjK9aVuOueFaPcQEYQ5fyNXNTOYwYw3099RYebWg==" + }, + "ws": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-4.1.0.tgz", + "integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==", + "requires": { + "async-limiter": "1.0.0", + "safe-buffer": "5.1.2" + } + } + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + } + }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.1", + "sntp": "2.1.0" + } + }, + "hoek": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, + "mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=" + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "requires": { + "mime-db": "1.33.0" + } + }, + "moment": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.1.tgz", + "integrity": "sha512-shJkRTSebXvsVqk56I+lkb2latjBs8I+pc2TzWc545y2iFnSjm7Wg0QMh+ZWcdSLQyGEau5jI8ocnmkyTgr9YQ==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "nonce": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/nonce/-/nonce-1.0.4.tgz", + "integrity": "sha1-7nMCrejBvvR28wG4yR9cxRpIdhI=" + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "poloniex.js": { + "version": "git://github.com/askmike/poloniex.js.git#69f5e254353e66d135070844fc3328efcbe3641c", + "requires": { + "nonce": "1.0.4", + "request": "2.33.0" + }, + "dependencies": { + "asn1": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", + "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", + "optional": true + }, + "assert-plus": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", + "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", + "optional": true + }, + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "optional": true + }, + "aws-sign2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", + "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", + "optional": true + }, + "boom": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz", + "integrity": "sha1-emNune1O/O+xnO9JR6PGffrukRs=", + "requires": { + "hoek": "0.9.1" + } + }, + "combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", + "optional": true, + "requires": { + "delayed-stream": "0.0.5" + } + }, + "cryptiles": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz", + "integrity": "sha1-7ZH/HxetE9N0gohZT4pIoNJvMlw=", + "optional": true, + "requires": { + "boom": "0.4.2" + } + }, + "delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", + "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=", + "optional": true + }, + "forever-agent": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz", + "integrity": "sha1-bQ4JxJIflKJ/Y9O0nF/v8epMUTA=" + }, + "form-data": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", + "integrity": "sha1-kavXiKupcCsaq/qLwBAxoqyeOxI=", + "optional": true, + "requires": { + "async": "0.9.2", + "combined-stream": "0.0.7", + "mime": "1.2.11" + } + }, + "hawk": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.0.0.tgz", + "integrity": "sha1-uQuxaYByhUEdp//LjdJZhQLTtS0=", + "optional": true, + "requires": { + "boom": "0.4.2", + "cryptiles": "0.2.2", + "hoek": "0.9.1", + "sntp": "0.2.4" + } + }, + "hoek": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", + "integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=" + }, + "http-signature": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", + "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", + "optional": true, + "requires": { + "asn1": "0.1.11", + "assert-plus": "0.1.5", + "ctype": "0.5.3" + } + }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, + "oauth-sign": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz", + "integrity": "sha1-y1QPk7srIqfVlBaRoojWDo6pOG4=", + "optional": true + }, + "qs": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-0.6.6.tgz", + "integrity": "sha1-bgFQmP9RlouKPIGQAdXyyJvEsQc=" + }, + "request": { + "version": "2.33.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.33.0.tgz", + "integrity": "sha1-UWeHgTFyYHDsYzdS6iMKI3ncZf8=", + "requires": { + "aws-sign2": "0.5.0", + "forever-agent": "0.5.2", + "form-data": "0.1.4", + "hawk": "1.0.0", + "http-signature": "0.10.1", + "json-stringify-safe": "5.0.1", + "mime": "1.2.11", + "node-uuid": "1.4.8", + "oauth-sign": "0.3.0", + "qs": "0.6.6", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.3.0" + } + }, + "sntp": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz", + "integrity": "sha1-+4hfGLDzqtGJ+CSGJTa87ux1CQA=", + "optional": true, + "requires": { + "hoek": "0.9.1" + } + }, + "tunnel-agent": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.3.0.tgz", + "integrity": "sha1-rWgbaPUyGtKCfEz7G31d8s/pQu4=", + "optional": true + } + } + }, + "printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "request": { + "version": "2.86.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.86.0.tgz", + "integrity": "sha512-BQZih67o9r+Ys94tcIW4S7Uu8pthjrQVxhsZ/weOwHbDfACxvIyvnAbzFQxjy1jMtvFSzv5zf4my6cZsJBbVzw==", + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.7.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.18", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" + } + }, + "request-promise": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.2.tgz", + "integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=", + "requires": { + "bluebird": "3.5.1", + "request-promise-core": "1.1.1", + "stealthy-require": "1.1.1", + "tough-cookie": "2.3.4" + }, + "dependencies": { + "request-promise-core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", + "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "requires": { + "lodash": "4.17.10" + } + } + } + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "requires": { + "hoek": "4.2.1" + } + }, + "sshpk": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", + "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" + }, + "underscore": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.0.tgz", + "integrity": "sha512-4IV1DSSxC1QK48j9ONFK1MoIAKKkbE8i7u55w2R6IqBqbT7A/iG7aZBCR2Bi8piF0Uz+i/MG1aeqLwl/5vqF+A==" + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "requires": { + "async-limiter": "1.0.0", + "safe-buffer": "5.1.2", + "ultron": "1.1.1" + } + } + } +} diff --git a/exchange/package.json b/exchange/package.json new file mode 100644 index 000000000..59e3f614f --- /dev/null +++ b/exchange/package.json @@ -0,0 +1,35 @@ +{ + "name": "gekko-broker", + "version": "0.0.2", + "description": "Gekko's order execution library for bitcoin & crypto exchanges", + "main": "gekkoBroker.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "github.com/askmike/gekko" + }, + "keywords": [ + "crypto", + "bitcoin", + "exchange", + "execution", + "trade" + ], + "author": "Mike van Rossum ", + "license": "MIT", + "dependencies": { + "async": "^2.6.0", + "binance": "^1.3.3", + "bitfinex-api-node": "^2.0.0-beta", + "bluebird": "^3.5.1", + "coinfalcon": "^1.0.3", + "gdax": "^0.7.0", + "lodash": "^4.17.5", + "moment": "^2.22.1", + "request-promise": "^4.2.2", + "retry": "^0.12.0", + "poloniex.js": "git://github.com/askmike/poloniex.js.git#69f5e254353e66d135070844fc3328efcbe3641c" + } +} diff --git a/exchange/portfolioManager.js b/exchange/portfolioManager.js new file mode 100644 index 000000000..c9b00d0bc --- /dev/null +++ b/exchange/portfolioManager.js @@ -0,0 +1,87 @@ +/* + The Portfolio class holds data about the portfolio +*/ + +const _ = require('lodash'); +const async = require('async'); +const errors = require('./exchangeErrors'); +// const EventEmitter = require('events'); + +class Portfolio { + constructor(config, api) { + _.bindAll(this); + this.config = config; + this.api = api; + this.balances = {}; + this.fee = null; + } + + getBalance(fund) { + return this.getFund(fund).amount; + } + + // return the [fund] based on the data we have in memory + getFund(fund) { + return _.find(this.balances, function(f) { return f.name === fund}); + } + + // convert into the portfolio expected by the performanceAnalyzer + convertBalances(asset,currency) { // rename? + var asset = _.find(this.balances, a => a.name === this.config.asset).amount; + var currency = _.find(this.balances, a => a.name === this.config.currency).amount; + + return { + currency, + asset, + balance: currency + (asset * this.ticker.bid) + } + } + + setBalances(callback) { + let set = (err, fullPortfolio) => { + if(err) { + console.log(err); + throw new errors.ExchangeError(err); + } + + // only include the currency/asset of this market + const balances = [ this.config.currency, this.config.asset ] + .map(name => { + let item = _.find(fullPortfolio, {name}); + + if(!item) { + // assume we have 0 + item = { name, amount: 0 }; + } + + return item; + }); + + this.balances = balances; + + if(_.isFunction(callback)) + callback(); + } + + this.api.getPortfolio(set); + } + + setFee(callback) { + this.api.getFee((err, fee) => { + if(err) + throw new errors.ExchangeError(err); + + this.fee = fee; + + if(_.isFunction(callback)) + callback(); + }); + } + + setTicker(ticker) { + this.ticker = ticker; + } + +} + +module.exports = Portfolio \ No newline at end of file diff --git a/util/genMarketFiles/update-binance.js b/exchange/util/genMarketFiles/update-binance.js similarity index 86% rename from util/genMarketFiles/update-binance.js rename to exchange/util/genMarketFiles/update-binance.js index 64ebf31f5..0d5a337e4 100644 --- a/util/genMarketFiles/update-binance.js +++ b/exchange/util/genMarketFiles/update-binance.js @@ -3,6 +3,7 @@ const fs = require('fs'); const request = require('request-promise'); const Promise = require('bluebird'); + let getOrderMinSize = currency => { if (currency === 'BTC') return 0.001; else if (currency === 'ETH') return 0.01; @@ -25,8 +26,8 @@ request(options) throw new Error('Unable to fetch product list, response was empty'); } - let assets = _.unique(_.map(body.data, market => market.baseAsset)); - let currencies = _.unique(_.map(body.data, market => market.quoteAsset)); + let assets = _.uniqBy(_.map(body.data, market => market.baseAsset)); + let currencies = _.uniqBy(_.map(body.data, market => market.quoteAsset)); let pairs = _.map(body.data, market => { return { pair: [market.quoteAsset, market.baseAsset], @@ -41,7 +42,7 @@ request(options) return { assets: assets, currencies: currencies, markets: pairs }; }) .then(markets => { - fs.writeFileSync('../../exchanges/binance-markets.json', JSON.stringify(markets, null, 2)); + fs.writeFileSync('../../wrappers/binance-marskets.json', JSON.stringify(markets, null, 2)); console.log(`Done writing Binance market data`); }) .catch(err => { diff --git a/util/genMarketFiles/update-bitfinex.js b/exchange/util/genMarketFiles/update-bitfinex.js similarity index 100% rename from util/genMarketFiles/update-bitfinex.js rename to exchange/util/genMarketFiles/update-bitfinex.js diff --git a/util/genMarketFiles/update-coinfalcon.js b/exchange/util/genMarketFiles/update-coinfalcon.js similarity index 100% rename from util/genMarketFiles/update-coinfalcon.js rename to exchange/util/genMarketFiles/update-coinfalcon.js diff --git a/util/genMarketFiles/update-kraken.js b/exchange/util/genMarketFiles/update-kraken.js similarity index 100% rename from util/genMarketFiles/update-kraken.js rename to exchange/util/genMarketFiles/update-kraken.js diff --git a/exchanges/DEBUG_exchange-simulator.js b/exchange/wrappers/DEBUG_exchange-simulator.js similarity index 97% rename from exchanges/DEBUG_exchange-simulator.js rename to exchange/wrappers/DEBUG_exchange-simulator.js index 2a6f05606..5df3e78ee 100644 --- a/exchanges/DEBUG_exchange-simulator.js +++ b/exchange/wrappers/DEBUG_exchange-simulator.js @@ -2,7 +2,6 @@ const _ = require('lodash'); const moment = require('moment'); -const log = require('../core/log'); const TREND_DURATION = 1000; diff --git a/exchanges/binance-markets.json b/exchange/wrappers/binance-markets.json similarity index 100% rename from exchanges/binance-markets.json rename to exchange/wrappers/binance-markets.json diff --git a/exchanges/binance.js b/exchange/wrappers/binance.js similarity index 55% rename from exchanges/binance.js rename to exchange/wrappers/binance.js index e9055f58c..d00ce5811 100644 --- a/exchanges/binance.js +++ b/exchange/wrappers/binance.js @@ -1,10 +1,9 @@ const moment = require('moment'); const _ = require('lodash'); -const util = require('../core/util'); -const Errors = require('../core/error'); -const log = require('../core/log'); +const Errors = require('../exchangeErrors'); const marketData = require('./binance-markets.json'); +const retry = require('../exchangeUtils').retry; const Binance = require('binance'); @@ -55,11 +54,9 @@ Trader.prototype.processError = function(funcName, error) { if (!error) return undefined; if (!error.message || !error.message.match(recoverableErrors)) { - log.error(`[binance.js] (${funcName}) returned an irrecoverable error: ${error}`); return new Errors.AbortError('[binance.js] ' + error.message || error); } - log.debug(`[binance.js] (${funcName}) returned an error, retrying: ${error}`); return new Errors.RetryError('[binance.js] ' + error.message || error); }; @@ -110,39 +107,28 @@ Trader.prototype.getTrades = function(since, callback, descending) { } let handler = (cb) => this.binance.aggTrades(reqData, this.handleResponse('getTrades', cb)); - util.retryCustom(retryForever, _.bind(handler, this), _.bind(processResults, this)); + retry(retryForever, _.bind(handler, this), _.bind(processResults, this)); }; Trader.prototype.getPortfolio = function(callback) { - var setBalance = function(err, data) { - log.debug(`[binance.js] entering "setBalance" callback after api call, err: ${err} data: ${JSON.stringify(data)}`) + const setBalance = (err, data) => { if (err) return callback(err); - var findAsset = function(item) { - return item.asset === this.asset; - } - var assetAmount = parseFloat(_.find(data.balances, _.bind(findAsset, this)).free); + const findAsset = item => item.asset === this.asset; + const assetAmount = parseFloat(_.find(data.balances, findAsset).free); - var findCurrency = function(item) { - return item.asset === this.currency; - } - var currencyAmount = parseFloat(_.find(data.balances, _.bind(findCurrency, this)).free); + const findCurrency = item => item.asset === this.currency; + const currencyAmount = parseFloat(_.find(data.balances, findCurrency).free); if (!_.isNumber(assetAmount) || _.isNaN(assetAmount)) { - log.error( - `Binance did not return portfolio for ${this.asset}, assuming 0.` - ); assetAmount = 0; } if (!_.isNumber(currencyAmount) || _.isNaN(currencyAmount)) { - log.error( - `Binance did not return portfolio for ${this.currency}, assuming 0.` - ); currencyAmount = 0; } - var portfolio = [ + const portfolio = [ { name: this.asset, amount: assetAmount }, { name: this.currency, amount: currencyAmount }, ]; @@ -150,25 +136,25 @@ Trader.prototype.getPortfolio = function(callback) { return callback(undefined, portfolio); }; - let handler = (cb) => this.binance.account({}, this.handleResponse('getPortfolio', cb)); - util.retryCustom(retryForever, _.bind(handler, this), _.bind(setBalance, this)); + const fetch = cb => this.binance.account({}, this.handleResponse('getPortfolio', cb)); + retry(retryForever, fetch, setBalance); }; // This uses the base maker fee (0.1%), and does not account for BNB discounts Trader.prototype.getFee = function(callback) { - var makerFee = 0.1; + const makerFee = 0.1; callback(undefined, makerFee / 100); }; Trader.prototype.getTicker = function(callback) { - var setTicker = function(err, data) { - log.debug(`[binance.js] entering "getTicker" callback after api call, err: ${err} data: ${(data || []).length} symbols`); - if (err) return callback(err); + const setTicker = (err, data) => { + if (err) + return callback(err); - var findSymbol = function(ticker) { - return ticker.symbol === this.pair; - } - var result = _.find(data, _.bind(findSymbol, this)); + var result = _.find(data, ticker => ticker.symbol === this.pair); + + if(!result) + return callback(new Error(`Market ${this.pair} not found on Binance`)); var ticker = { ask: parseFloat(result.askPrice), @@ -178,8 +164,8 @@ Trader.prototype.getTicker = function(callback) { callback(undefined, ticker); }; - let handler = (cb) => this.binance._makeRequest({}, this.handleResponse('getTicker', cb), 'api/v1/ticker/allBookTickers'); - util.retryCustom(retryForever, _.bind(handler, this), _.bind(setTicker, this)); + const handler = cb => this.binance._makeRequest({}, this.handleResponse('getTicker', cb), 'api/v1/ticker/allBookTickers'); + retry(retryForever, handler, setTicker); }; // Effectively counts the number of decimal places, so 0.001 or 0.234 results in 3 @@ -190,7 +176,7 @@ Trader.prototype.getPrecision = function(tickSize) { return p; }; -Trader.prototype.roundAmount = function(amount, tickSize) { +Trader.prototype.round = function(amount, tickSize) { var precision = 100000000; var t = this.getPrecision(tickSize); @@ -203,70 +189,86 @@ Trader.prototype.roundAmount = function(amount, tickSize) { return amount; }; -Trader.prototype.getLotSize = function(tradeType, amount, price, callback) { - amount = this.roundAmount(amount, this.market.minimalOrder.amount); - if (amount < this.market.minimalOrder.amount) - return callback(undefined, { amount: 0, price: 0 }); +Trader.prototype.roundAmount = function(amount) { + return this.round(amount, this.market.minimalOrder.amount); +} - price = this.roundAmount(price, this.market.minimalOrder.price) - if (price < this.market.minimalOrder.price) - return callback(undefined, { amount: 0, price: 0 }); +Trader.prototype.roundPrice = function(price) { + return this.round(price, this.market.minimalOrder.price); +} - if (amount * price < this.market.minimalOrder.order) - return callback(undefined, { amount: 0, price: 0}); +Trader.prototype.isValidPrice = function(price) { + return price >= this.market.minimalOrder.price; +} - callback(undefined, { amount: amount, price: price }); +Trader.prototype.isValidLot = function(price, amount) { + return amount * price >= this.market.minimalOrder.order; } Trader.prototype.addOrder = function(tradeType, amount, price, callback) { - log.debug(`[binance.js] (addOrder) ${tradeType.toUpperCase()} ${amount} ${this.asset} @${price} ${this.currency}`); - - var setOrder = function(err, data) { - log.debug(`[binance.js] entering "setOrder" callback after api call, err: ${err} data: ${JSON.stringify(data)}`); + const setOrder = (err, data) => { if (err) return callback(err); - var txid = data.orderId; - log.debug(`[binance.js] added order with txid: ${txid}`); + const txid = data.orderId; callback(undefined, txid); }; - let reqData = { + const reqData = { symbol: this.pair, side: tradeType.toUpperCase(), type: 'LIMIT', - timeInForce: 'GTC', // Good to cancel (I think, not really covered in docs, but is default) + timeInForce: 'GTC', quantity: amount, price: price, timestamp: new Date().getTime() }; - let handler = (cb) => this.binance.newOrder(reqData, this.handleResponse('addOrder', cb)); - util.retryCustom(retryCritical, _.bind(handler, this), _.bind(setOrder, this)); + const handler = cb => this.binance.newOrder(reqData, this.handleResponse('addOrder', cb)); + retry(retryCritical, handler, setOrder); }; Trader.prototype.getOrder = function(order, callback) { - var get = function(err, data) { - log.debug(`[binance.js] entering "getOrder" callback after api call, err ${err} data: ${JSON.stringify(data)}`); + const get = (err, data) => { if (err) return callback(err); - var price = parseFloat(data.price); - var amount = parseFloat(data.executedQty); - - // Data.time is a 13 digit millisecon unix time stamp. - // https://momentjs.com/docs/#/parsing/unix-timestamp-milliseconds/ - var date = moment(data.time); + let price = 0; + let amount = 0; + let date = moment(0); - callback(undefined, { price, amount, date }); - }.bind(this); + const fees = {}; - let reqData = { + const trades = _.filter(data, t => { + // note: the API returns a string after creating + return t.orderId == order; + }); + + if(!trades.length) { + return callback(new Error('Trades not found')); + } + + _.each(trades, trade => { + date = moment(trade.time); + price = ((price * amount) + (+trade.price * trade.qty)) / (+trade.qty + amount); + amount += +trade.qty; + + if(fees[trade.commissionAsset]) + fees[trade.commissionAsset] += (+trade.commission); + else + fees[trade.commissionAsset] = (+trade.commission); + }); + + callback(undefined, { price, amount, date, fees }); + } + + const reqData = { symbol: this.pair, - orderId: order, + // if this order was not part of the last 500 trades we won't find it.. + limit: 500, }; - let handler = (cb) => this.binance.queryOrder(reqData, this.handleResponse('getOrder', cb)); - util.retryCustom(retryCritical, _.bind(handler, this), _.bind(get, this)); + const handler = cb => this.binance.myTrades(reqData, this.handleResponse('getOrder', cb)); + retry(retryCritical, handler, get); }; Trader.prototype.buy = function(amount, price, callback) { @@ -278,35 +280,52 @@ Trader.prototype.sell = function(amount, price, callback) { }; Trader.prototype.checkOrder = function(order, callback) { - var check = function(err, data) { - log.debug(`[binance.js] entering "checkOrder" callback after api call, err ${err} data: ${JSON.stringify(data)}`); + + const check = (err, data) => { if (err) return callback(err); - var stillThere = data.status === 'NEW' || data.status === 'PARTIALLY_FILLED'; - var canceledManually = data.status === 'CANCELED' || data.status === 'REJECTED' || data.status === 'EXPIRED'; - callback(undefined, !stillThere && !canceledManually); + const status = data.status; + + if( + status === 'CANCELED' || + status === 'REJECTED' || + // for good measure: GB does not + // submit orders that can expire yet + status === 'EXPIRED' + ) { + return callback(undefined, { executed: false, open: false }); + } else if( + status === 'NEW' || + status === 'PARTIALLY_FILLED' + ) { + return callback(undefined, { executed: false, open: true, filledAmount: +data.executedQty }); + } else if(status === 'FILLED') { + return callback(undefined, { executed: true, open: false }) + } + + console.log('what status?', status); + throw status; }; - let reqData = { + const reqData = { symbol: this.pair, orderId: order, }; - let handler = (cb) => this.binance.queryOrder(reqData, this.handleResponse('checkOrder', cb)); - util.retryCustom(retryCritical, _.bind(handler, this), _.bind(check, this)); + const fetcher = cb => this.binance.queryOrder(reqData, this.handleResponse('checkOrder', cb)); + retry(retryCritical, fetcher, check); }; Trader.prototype.cancelOrder = function(order, callback) { // callback for cancelOrder should be true if the order was already filled, otherwise false - var cancel = function(err, data) { - log.debug(`[binance.js] entering "cancelOrder" callback after api call, err ${err} data: ${JSON.stringify(data)}`); + const cancel = (err, data) => { if (err) { if(data && data.msg === 'UNKNOWN_ORDER') { // this seems to be the response we get when an order was filled - return callback(true); // tell the thing the order was already filled + return callback(undefined, true); // tell the thing the order was already filled } return callback(err); } - callback(undefined); + callback(undefined, false); }; let reqData = { @@ -314,8 +333,8 @@ Trader.prototype.cancelOrder = function(order, callback) { orderId: order, }; - let handler = (cb) => this.binance.cancelOrder(reqData, this.handleResponse('cancelOrder', cb)); - util.retryCustom(retryForever, _.bind(handler, this), _.bind(cancel, this)); + const fetcher = cb => this.binance.cancelOrder(reqData, this.handleResponse('cancelOrder', cb)); + retry(retryForever, fetcher, cancel); }; Trader.prototype.initMarkets = function(callback) { @@ -333,7 +352,7 @@ Trader.getCapabilities = function() { providesHistory: 'date', providesFullHistory: true, tid: 'tid', - tradable: true, + tradable: true }; }; diff --git a/exchanges/bitcoin-co-id.js b/exchange/wrappers/bitcoin-co-id.js similarity index 100% rename from exchanges/bitcoin-co-id.js rename to exchange/wrappers/bitcoin-co-id.js diff --git a/exchanges/bitfinex-markets.json b/exchange/wrappers/bitfinex-markets.json similarity index 100% rename from exchanges/bitfinex-markets.json rename to exchange/wrappers/bitfinex-markets.json diff --git a/exchange/wrappers/bitfinex.js b/exchange/wrappers/bitfinex.js new file mode 100644 index 000000000..ddaf5380a --- /dev/null +++ b/exchange/wrappers/bitfinex.js @@ -0,0 +1,304 @@ + +const Bitfinex = require("bitfinex-api-node"); +const _ = require('lodash'); +const moment = require('moment'); + +const Errors = require('../exchangeErrors'); +const retry = require('../exchangeUtils').retry; + +const marketData = require('./bitfinex-markets.json'); + +var Trader = function(config) { + _.bindAll(this); + if(_.isObject(config)) { + this.key = config.key; + this.secret = config.secret; + } + this.name = 'Bitfinex'; + this.balance; + this.price; + this.asset = config.asset; + this.currency = config.currency; + this.pair = this.asset + this.currency; + this.bitfinex = new Bitfinex.RESTv1({apiKey: this.key, apiSecret: this.secret, transform: true}); +} + +const includes = (str, list) => { + if(!_.isString(str)) + return false; + + return _.some(list, item => str.includes(item)); +} + +const recoverableErrors = [ + 'SOCKETTIMEDOUT', + 'ESOCKETTIMEDOUT', + 'TIMEDOUT', + 'CONNRESET', + 'CONNREFUSED', + 'NOTFOUND', + '429', + '443', + '504', + '503', + '500', + '502', + 'Empty response', + 'Nonce is too small' +]; + +Trader.prototype.handleResponse = function(funcName, callback) { + return (error, data) => { + if(!error && _.isEmpty(data)) { + error = new Error('Empty response'); + } + + if(error) { + const message = error.message; + console.log(new Date, 'ERROR!', funcName, message); + + // in case we just cancelled our balances might not have + // settled yet, retry. + if( + funcName === 'submitOrder' && + message.includes('not enough exchange balance') + ) { + error.retry = 20; + return callback(error); + } + + // most likely problem with v1 api + if( + funcName === 'submitOrder' && + message.includes('Cannot evaluate your available balance, please try again') + ) { + error.retry = 10; + return callback(error); + } + + // in some situations bfx returns 404 on + // orders created recently + if( + funcName === 'checkOrder' && + message.includes('Not Found') + ) { + error.retry = 25; + return callback(error); + } + + if(includes(message, recoverableErrors)) { + error.notFatal = true; + return callback(error); + } + } + + return callback(error, data); + } +}; + +Trader.prototype.getPortfolio = function(callback) { + const processResponse = (err, data) => { + if (err) return callback(err); + + // We are only interested in funds in the "exchange" wallet + data = data.filter(c => c.type === 'exchange'); + + const asset = _.find(data, c => c.currency.toUpperCase() === this.asset); + const currency = _.find(data, c => c.currency.toUpperCase() === this.currency); + + let assetAmount, currencyAmount; + + if(_.isObject(asset) && _.isNumber(+asset.available) && !_.isNaN(+asset.available)) + assetAmount = +asset.available; + else { + assetAmount = 0; + } + + if(_.isObject(currency) && _.isNumber(+currency.available) && !_.isNaN(+currency.available)) + currencyAmount = +currency.available; + else { + currencyAmount = 0; + } + + const portfolio = [ + { name: this.asset, amount: assetAmount }, + { name: this.currency, amount: currencyAmount }, + ]; + + callback(undefined, portfolio); + }; + + const fetch = cb => this.bitfinex.wallet_balances(this.handleResponse('getPortfolio', cb)); + retry(null, fetch, processResponse); +} + +Trader.prototype.getTicker = function(callback) { + const processResponse = (err, data) => { + if (err) + return callback(err); + + callback(undefined, {bid: +data.bid, ask: +data.ask}); + }; + + const fetch = cb => this.bitfinex.ticker(this.pair, this.handleResponse('getTicker', cb)); + retry(null, fetch, processResponse); +} + +Trader.prototype.getFee = function(callback) { + const makerFee = 0.1; + // const takerFee = 0.2; + callback(undefined, makerFee / 100); +} + +Trader.prototype.roundAmount = function(amount) { + return Math.floor(amount*100000000)/100000000; +} + +Trader.prototype.roundPrice = function(price) { + // todo: calc significant digits + return price; +} + +Trader.prototype.submitOrder = function(type, amount, price, callback) { + const processResponse = (err, data) => { + if (err) + return callback(err); + + callback(null, data.order_id); + } + + const fetch = cb => this.bitfinex.new_order(this.pair, + amount + '', + price + '', + this.name.toLowerCase(), + type, + 'exchange limit', + this.handleResponse('submitOrder', cb) + ); + + retry(null, fetch, processResponse); +} + +Trader.prototype.buy = function(amount, price, callback) { + this.submitOrder('buy', amount, price, callback); +} + +Trader.prototype.sell = function(amount, price, callback) { + this.submitOrder('sell', amount, price, callback); +} + +Trader.prototype.checkOrder = function(order_id, callback) { + const processResponse = (err, data) => { + if (err) { + console.log('this is after we have retried fetching it'); + // this is after we have retried fetching it + // in this.handleResponse. + if(err.message.includes('Not Found')) { + return callback(undefined, { + open: false, + executed: true + }); + } + + return callback(err); + } + + return callback(undefined, { + open: data.is_live, + executed: data.original_amount === data.executed_amount, + filledAmount: +data.executed_amount + }); + } + + const fetcher = cb => this.bitfinex.order_status(order_id, this.handleResponse('checkOrder', cb)); + retry(null, fetcher, processResponse); +} + + +Trader.prototype.getOrder = function(order_id, callback) { + const processResponse = (err, data) => { + if (err) return callback(err); + + var price = parseFloat(data.avg_execution_price); + var amount = parseFloat(data.executed_amount); + var date = moment.unix(data.timestamp); + + const processPastTrade = (err, data) => { + if (err) return callback(err); + + console.log('processPastTrade', data); + const trade = _.first(data); + + const fees = { + [trade.fee_currency]: trade.fee_amount + } + + callback(undefined, {price, amount, date, fees}); + } + + // we need another API call to fetch the fees + const feeFetcher = cb => this.bitfinex.past_trades(this.currency, {since: data.timestamp}, this.handleResponse('pastTrades', cb)); + retry(null, feeFetcher, processPastTrade); + + callback(undefined, {price, amount, date}); + }; + + const fetcher = cb => this.bitfinex.order_status(order_id, this.handleResponse('getOrder', cb)); + retry(null, fetcher, processResponse); +} + + +Trader.prototype.cancelOrder = function(order_id, callback) { + const processResponse = (err, data) => { + if (err) { + return callback(err); + } + + return callback(undefined, false); + } + + const handler = cb => this.bitfinex.cancel_order(order_id, this.handleResponse('cancelOrder', cb)); + retry(null, handler, processResponse); +} + +Trader.prototype.getTrades = function(since, callback, descending) { + const processResponse = (err, data) => { + if (err) return callback(err); + + var trades = _.map(data, function(trade) { + return { + tid: trade.tid, + date: trade.timestamp, + price: +trade.price, + amount: +trade.amount + } + }); + + callback(undefined, descending ? trades : trades.reverse()); + }; + + var path = this.pair; + if(since) + path += '?limit_trades=2000'; + + const handler = cb => this.bitfinex.trades(path, this.handleResponse('getTrades', cb)); + retry(null, handler, processResponse); +} + +Trader.getCapabilities = function () { + return { + name: 'Bitfinex', + slug: 'bitfinex', + currencies: marketData.currencies, + assets: marketData.assets, + markets: marketData.markets, + requires: ['key', 'secret'], + tid: 'tid', + providesFullHistory: true, + providesHistory: 'date', + tradable: true, + forceReorderDelay: true + }; +} + +module.exports = Trader; diff --git a/exchanges/bitfinex.js b/exchange/wrappers/bitfinex_v2.js.prep similarity index 50% rename from exchanges/bitfinex.js rename to exchange/wrappers/bitfinex_v2.js.prep index bd22bd4ff..bc3aa01bd 100644 --- a/exchanges/bitfinex.js +++ b/exchange/wrappers/bitfinex_v2.js.prep @@ -1,11 +1,11 @@ +// NOT USED, see: https://github.com/bitfinexcom/bitfinex-api-node/issues/321 const Bitfinex = require("bitfinex-api-node"); const _ = require('lodash'); const moment = require('moment'); -const util = require('../core/util'); -const Errors = require('../core/error'); -const log = require('../core/log'); +const Errors = require('../exchangeErrors'); +const retry = require('../exchangeUtils').retry; const marketData = require('./bitfinex-markets.json'); @@ -20,47 +20,56 @@ var Trader = function(config) { this.price; this.asset = config.asset; this.currency = config.currency; - this.pair = this.asset + this.currency; - this.bitfinex = new Bitfinex(this.key, this.secret, { version: 1 }).rest; + this.pair = 't' + this.asset + this.currency; + this.bitfinex = new Bitfinex.RESTv2({apiKey: this.key, apiSecret: this.secret, transform: true}); } -var retryCritical = { - retries: 10, - factor: 1.2, - minTimeout: 10 * 1000, - maxTimeout: 60 * 1000 -}; +const includes = (str, list) => { + if(!_.isString(str)) + return false; -var retryForever = { - forever: true, - factor: 1.2, - minTimeout: 10 * 1000, - maxTimeout: 300 * 1000 -}; + return _.some(list, item => str.includes(item)); +} // Probably we need to update these string -var recoverableErrors = new RegExp(/(SOCKETTIMEDOUT|TIMEDOUT|CONNRESET|CONNREFUSED|NOTFOUND|429|443|5\d\d)/g); +const recoverableErrors = [ + 'SOCKETTIMEDOUT', + 'TIMEDOUT', + 'CONNRESET', + 'CONNREFUSED', + 'NOTFOUND', + '429', + '443', + '504', + '503', + '500', + '502', + 'Empty response' +]; -Trader.prototype.processError = function(funcName, error) { - if (!error) return undefined; +Trader.prototype.handleResponse = function(funcName, callback) { + return (error, data) => { + if(!error && _.isEmpty(data)) { + error = new Error('Empty response'); + } - if (!error.message.match(recoverableErrors)) { - log.error(`[bitfinex.js] (${funcName}) returned an irrecoverable error: ${error.message}`); - return new Errors.AbortError('[bitfinex.js] ' + error.message); - } + if(error) { + const message = error.message || error; - log.debug(`[bitfinex.js] (${funcName}) returned an error, retrying: ${error.message}`); - return new Errors.RetryError('[bitfinex.js] ' + error.message); -}; + if(!includes(message, recoverableErrors)) { + const error = new Error(message); + error.notFatal = true; + return callback(error); + } + } -Trader.prototype.handleResponse = function(funcName, callback) { - return (error, data, body) => { - return callback(this.processError(funcName, error), data); + return callback(error, data); } }; Trader.prototype.getPortfolio = function(callback) { - let process = (err, data) => { + const processResponse = (err, data) => { + console.log('processResponse', {err, data}); if (err) return callback(err); // We are only interested in funds in the "exchange" wallet @@ -69,19 +78,19 @@ Trader.prototype.getPortfolio = function(callback) { const asset = _.find(data, c => c.currency.toUpperCase() === this.asset); const currency = _.find(data, c => c.currency.toUpperCase() === this.currency); + console.log(currency.balance); + let assetAmount, currencyAmount; - if(_.isObject(asset) && _.isNumber(+asset.available) && !_.isNaN(+asset.available)) - assetAmount = +asset.available; + if(_.isObject(asset) && _.isNumber(+asset.balanceAvailable) && !_.isNaN(+asset.balanceAvailable)) + assetAmount = +asset.balanceAvailable; else { - log.error(`Bitfinex did not provide ${this.asset} amount, assuming 0`); assetAmount = 0; } - if(_.isObject(currency) && _.isNumber(+currency.available) && !_.isNaN(+currency.available)) - currencyAmount = +currency.available; + if(_.isObject(currency) && _.isNumber(+currency.balanceAvailable) && !_.isNaN(+currency.balanceAvailable)) + currencyAmount = +currency.balanceAvailable; else { - log.error(`Bitfinex did not provide ${this.currency} amount, assuming 0`); currencyAmount = 0; } @@ -93,40 +102,45 @@ Trader.prototype.getPortfolio = function(callback) { callback(undefined, portfolio); }; - let handler = (cb) => this.bitfinex.wallet_balances(this.handleResponse('getPortfolio', cb)); - util.retryCustom(retryForever, _.bind(handler, this), _.bind(process, this)); + const fetch = cb => this.bitfinex.wallets(this.handleResponse('getPortfolio', cb)); + retry(null, fetch, processResponse); } Trader.prototype.getTicker = function(callback) { - let process = (err, data) => { - if (err) return callback(err); + const processResponse = (err, data) => { + if (err) + return callback(err); - // whenever we reach this point we have valid - // data, the callback is still the same since - // we are inside the same javascript scope. - callback(undefined, {bid: +data.bid, ask: +data.ask}) + callback(undefined, {bid: +data.bid, ask: +data.ask}); }; - - let handler = (cb) => this.bitfinex.ticker(this.pair, this.handleResponse('getTicker', cb)); - util.retryCustom(retryForever, _.bind(handler, this), _.bind(process, this)); + + const fetch = cb => this.bitfinex.ticker(this.pair, this.handleResponse('getTicker', cb)); + retry(null, fetch, processResponse); } -// This assumes that only limit orders are being placed, so fees are the -// "maker fee" of 0.1%. It does not take into account volume discounts. Trader.prototype.getFee = function(callback) { - var makerFee = 0.1; + const makerFee = 0.1; + // const takerFee = 0.2; callback(undefined, makerFee / 100); } +Trader.prototype.roundAmount = function(amount) { + return Math.floor(amount*100000000)/100000000; +} + +Trader.prototype.roundPrice = function(price) { + // todo: calc significant digits + return price; +} + Trader.prototype.submit_order = function(type, amount, price, callback) { - let process = (err, data) => { + const processResponse = (err, data) => { if (err) return callback(err); callback(err, data.order_id); } - amount = Math.floor(amount*100000000)/100000000; - let handler = (cb) => this.bitfinex.new_order(this.pair, + const fetcher = cb => this.bitfinex.new_order(this.pair, amount + '', price + '', this.name.toLowerCase(), @@ -135,7 +149,7 @@ Trader.prototype.submit_order = function(type, amount, price, callback) { this.handleResponse('submitOrder', cb) ); - util.retryCustom(retryCritical, _.bind(handler, this), _.bind(process, this)); + retry(retryCritical, fetcher, processResponse); } Trader.prototype.buy = function(amount, price, callback) { @@ -147,19 +161,23 @@ Trader.prototype.sell = function(amount, price, callback) { } Trader.prototype.checkOrder = function(order_id, callback) { - let process = (err, data) => { + const processResponse = (err, data) => { if (err) return callback(err); - callback(undefined, !data.is_live); + return callback(undefined, { + open: data.is_live, + executed: data.original_amount === data.executed_amount, + filled: +data.executed_amount + }); } - let handler = (cb) => this.bitfinex.order_status(order_id, this.handleResponse('checkOrder', cb)); - util.retryCustom(retryCritical, _.bind(handler, this), _.bind(process, this)); + const fetcher = cb => this.bitfinex.order_status(order_id, this.handleResponse('checkOrder', cb)); + retry(retryCritical, fetcher, processResponse); } Trader.prototype.getOrder = function(order_id, callback) { - let process = (err, data) => { + const processResponse = (err, data) => { if (err) return callback(err); var price = parseFloat(data.avg_execution_price); @@ -169,24 +187,24 @@ Trader.prototype.getOrder = function(order_id, callback) { callback(undefined, {price, amount, date}); }; - let handler = (cb) => this.bitfinex.order_status(order_id, this.handleResponse('getOrder', cb)); - util.retryCustom(retryCritical, _.bind(handler, this), _.bind(process, this)); + const fetcher = cb => this.bitfinex.order_status(order_id, this.handleResponse('getOrder', cb)); + retry(retryCritical, fetcher, processResponse); } Trader.prototype.cancelOrder = function(order_id, callback) { - let process = (err, data) => { + const processResponse = (err, data) => { if (err) return callback(err); return callback(undefined); } - let handler = (cb) => this.bitfinex.cancel_order(order_id, this.handleResponse('cancelOrder', cb)); - util.retryCustom(retryForever, _.bind(handler, this), _.bind(process, this)); + const handler = cb => this.bitfinex.cancel_order(order_id, this.handleResponse('cancelOrder', cb)); + retry(retryForever, handler, processResponse); } Trader.prototype.getTrades = function(since, callback, descending) { - let process = (err, data) => { + const processResponse = (err, data) => { if (err) return callback(err); var trades = _.map(data, function(trade) { @@ -205,8 +223,8 @@ Trader.prototype.getTrades = function(since, callback, descending) { if(since) path += '?limit_trades=2000'; - let handler = (cb) => this.bitfinex.trades(path, this.handleResponse('getTrades', cb)); - util.retryCustom(retryForever, _.bind(handler, this), _.bind(process, this)); + const handler = cb => this.bitfinex.trades(path, this.handleResponse('getTrades', cb)); + retry(retryForever, handler, processResponse); } Trader.getCapabilities = function () { diff --git a/exchanges/bitstamp.js b/exchange/wrappers/bitstamp.js similarity index 100% rename from exchanges/bitstamp.js rename to exchange/wrappers/bitstamp.js diff --git a/exchanges/bittrex.js b/exchange/wrappers/bittrex.js similarity index 100% rename from exchanges/bittrex.js rename to exchange/wrappers/bittrex.js diff --git a/exchanges/bitx.js b/exchange/wrappers/bitx.js similarity index 100% rename from exchanges/bitx.js rename to exchange/wrappers/bitx.js diff --git a/exchanges/btc-markets.js b/exchange/wrappers/btc-markets.js similarity index 100% rename from exchanges/btc-markets.js rename to exchange/wrappers/btc-markets.js diff --git a/exchanges/btcc.js b/exchange/wrappers/btcc.js similarity index 100% rename from exchanges/btcc.js rename to exchange/wrappers/btcc.js diff --git a/exchanges/bx.in.th.js b/exchange/wrappers/bx.in.th.js similarity index 100% rename from exchanges/bx.in.th.js rename to exchange/wrappers/bx.in.th.js diff --git a/exchanges/cexio.js b/exchange/wrappers/cexio.js similarity index 100% rename from exchanges/cexio.js rename to exchange/wrappers/cexio.js diff --git a/exchanges/coinfalcon-markets.json b/exchange/wrappers/coinfalcon-markets.json similarity index 100% rename from exchanges/coinfalcon-markets.json rename to exchange/wrappers/coinfalcon-markets.json diff --git a/exchange/wrappers/coinfalcon.js b/exchange/wrappers/coinfalcon.js new file mode 100644 index 000000000..31e04f4ba --- /dev/null +++ b/exchange/wrappers/coinfalcon.js @@ -0,0 +1,294 @@ +const moment = require('moment'); +const _ = require('lodash'); +const marketData = require('./coinfalcon-markets.json'); + +const CoinFalcon = require('coinfalcon'); + +var Trader = function(config) { + _.bindAll(this); + + if (_.isObject(config)) { + this.key = config.key; + this.secret = config.secret; + this.currency = config.currency.toUpperCase(); + this.asset = config.asset.toUpperCase(); + } + + this.pair = this.asset + '-' + this.currency; + this.name = 'coinfalcon'; + + this.market = _.find(Trader.getCapabilities().markets, (market) => { + return market.pair[0] === this.currency && market.pair[1] === this.asset + }); + + this.coinfalcon = new CoinFalcon.Client(this.key, this.secret); + + this.interval = 1500; +}; + +const includes = (str, list) => { + if(!_.isString(str)) + return false; + + return _.some(list, item => str.includes(item)); +} + +const recoverableErrors = [ + 'SOCKETTIMEDOUT', + 'TIMEDOUT', + 'CONNRESET', + 'CONNREFUSED', + 'NOTFOUND', + '429', + '522', + '429', + '504', + '503', + '500', + '502', + '408' +]; + +Trader.prototype.processResponse = function(method, args, next) { + const catcher = err => { + if(!err || !err.message) + err = new Error(err || 'Empty error'); + + if(includes(err.message, recoverableErrors)) + return this.retry(method, args); + + console.log(new Date, '[cf] big error!', err); + + return next(err); + } + + return { + failure: catcher, + success: data => { + if(!data) + return catcher(); + + if(data.error) + return catcher(data.error); + + if(includes(data, ['Please complete the security check to proceed.'])) + return next(new Error( + 'Your IP has been flagged by CloudFlare. ' + + 'As such Gekko Broker cannot access Coinfalcon.' + )); + + next(undefined, data); + } + } +} + +Trader.prototype.retry = function(method, args) { + var wait = +moment.duration(1, 'seconds'); + + // run the failed method again with the same arguments after wait + setTimeout(() => { + console.log('cf retry..'); + console.log(args); + method.apply(this, args); + }, wait); +}; + +Trader.prototype.getTicker = function(callback) { + const handle = this.processResponse(this.getTicker, [callback], (err, res) => { + if(err) + return callback(err); + + callback(null, {bid: +res.data.bids[0].price, ask: +res.data.asks[0].price}) + }); + + var url = "markets/" + this.pair + "/orders?level=1" + + this.coinfalcon.get(url).then(handle.success).catch(handle.failure); +}; + +Trader.prototype.getFee = function(callback) { + var fees = 0; // 0% for making! + callback(false, fees / 100); +}; + +Trader.prototype.getPortfolio = function(callback) { + const handle = this.processResponse(this.getPortfolio, [callback], (err, res) => { + if(err) + return callback(err); + + var portfolio = res.data.map(account => ({ + name: account.currency_code.toUpperCase(), + amount: parseFloat(account.available_balance) + })); + + callback(null, portfolio); + }); + + this.coinfalcon.get('user/accounts').then(handle.success).catch(handle.failure); +}; + +Trader.prototype.addOrder = function(type, amount, price, callback) { + const args = _.toArray(arguments); + + const handle = this.processResponse(this.addOrder, args, (err, res) => { + if(err) + return callback(err); + + callback(false, res.data.id); + }); + + const payload = { + order_type: type, + operation_type: 'limit_order', + market: this.pair, + size: amount + '', + price: price + '' + } + + this.coinfalcon.post('user/orders', payload).then(handle.success).catch(handle.failure); +}; + +['buy', 'sell'].map(function(type) { + Trader.prototype[type] = function(amount, price, callback) { + this.addOrder(type, amount, price, callback); + }; +}); + +const round = function(number, precision) { + var factor = Math.pow(10, precision); + var tempNumber = number * factor; + var roundedTempNumber = Math.round(tempNumber); + return roundedTempNumber / factor; +}; + +Trader.prototype.roundAmount = function(amount) { + return round(amount, 8); +} + +Trader.prototype.roundPrice = function(price) { + let rounding; + + if(this.pair.includes('EUR')) + rounding = 2; + else + rounding = 5; + return round(price, rounding); +} + +Trader.prototype.getOrder = function(order, callback) { + const args = _.toArray(arguments); + const handle = this.processResponse(this.getOrder, args, (err, res) => { + if(err) + return callback(err); + + const price = parseFloat(res.data.price); + const amount = parseFloat(res.data.size_filled); + const date = moment(res.data.created_at); + callback(false, { price, amount, date }); + }); + + this.coinfalcon.get('user/orders/' + order).then(handle.success).catch(handle.failure); +}; + +Trader.prototype.checkOrder = function(order, callback) { + const args = _.toArray(arguments); + + const handle = this.processResponse(this.checkOrder, args, (err, res) => { + if(err) + return callback(err); + + // https://docs.coinfalcon.com/#list-orders + const status = res.data.status; + + if(status === 'canceled') { + return callback(undefined, { executed: false, open: false }); + } if(status === 'fulfilled') { + return callback(undefined, { executed: true, open: false }); + } if( + status === 'pending' || + status === 'partially_filled' || + status === 'open' + ) { + return callback(undefined, { executed: false, open: true, filledAmount: +res.data.size_filled }); + } + + console.error(res.data); + callback(new Error('Unknown status ' + status)); + }); + + this.coinfalcon.get('user/orders/' + order).then(handle.success).catch(handle.failure); +}; + +Trader.prototype.cancelOrder = function(order, callback) { + const args = _.toArray(arguments); + + const handle = this.processResponse(this.cancelOrder, args, (err, res) => { + if(err) { + if(err.message.includes('has wrong status.')) { + return callback(undefined, true); + } + return callback(err); + } + + callback(undefined, false); + }); + + this.coinfalcon.delete('user/orders/' + order).then(handle.success).catch(handle.failure); +}; + +Trader.prototype.getTrades = function(since, callback, descending) { + var args = _.toArray(arguments); + + var success = function(res) { + var parsedTrades = []; + _.each( + res.data, + function(trade) { + parsedTrades.push({ + tid: trade.id, + date: moment(trade.created_at).unix(), + price: parseFloat(trade.price), + amount: parseFloat(trade.size), + }); + }, + this + ); + + if (descending) { + callback(null, parsedTrades); + } else { + callback(null, parsedTrades.reverse()); + } + }.bind(this); + + var failure = function (err) { + err = new Error(err); + return this.retry(this.getTrades, args, err); + }.bind(this); + + var url = "markets/" + this.pair + "/trades" + + if (since) { + url += '?since_time=' + (_.isString(since) ? since : since.format()); + } + + this.coinfalcon.get(url).then(success).catch(failure); +}; + +Trader.getCapabilities = function () { + return { + name: 'CoinFalcon', + slug: 'coinfalcon', + assets: marketData.assets, + currencies: marketData.currencies, + markets: marketData.markets, + requires: ['key', 'secret'], + providesHistory: 'date', + providesFullHistory: true, + tid: 'tid', + tradable: true, + forceReorderDelay: false + }; +} + +module.exports = Trader; diff --git a/exchanges/coingi.js b/exchange/wrappers/coingi.js similarity index 100% rename from exchanges/coingi.js rename to exchange/wrappers/coingi.js diff --git a/exchanges/gdax.js b/exchange/wrappers/gdax.js similarity index 64% rename from exchanges/gdax.js rename to exchange/wrappers/gdax.js index 7ef0ab9c6..8f2d731e7 100644 --- a/exchanges/gdax.js +++ b/exchange/wrappers/gdax.js @@ -1,15 +1,14 @@ -var Gdax = require('gdax'); -var _ = require('lodash'); -var moment = require('moment'); +const Gdax = require('gdax'); +const _ = require('lodash'); +const moment = require('moment'); -const util = require('../core/util'); -const Errors = require('../core/error'); -const log = require('../core/log'); +const errors = require('../exchangeErrors'); +const retry = require('../exchangeUtils').retry; const BATCH_SIZE = 100; const QUERY_DELAY = 350; -var Trader = function(config) { +const Trader = function(config) { _.bindAll(this); this.post_only = true; @@ -40,7 +39,6 @@ var Trader = function(config) { } this.gdax_public = new Gdax.PublicClient( - this.pair, this.use_sandbox ? this.api_sandbox_url : undefined ); this.gdax = new Gdax.AuthenticatedClient( @@ -51,59 +49,58 @@ var Trader = function(config) { ); }; -var retryCritical = { - retries: 10, - factor: 1.2, - minTimeout: 10 * 1000, - maxTimeout: 60 * 1000, -}; - -var retryForever = { - forever: true, - factor: 1.2, - minTimeout: 10 * 1000, - maxTimeout: 300 * 1000, -}; +const recoverableErrors = [ + 'SOCKETTIMEDOUT', + 'TIMEDOUT', + 'CONNRESET', + 'CONNREFUSED', + 'NOTFOUND', + 'Rate limit exceeded', + 'Response code 5' +]; -// Probably we need to update these string -var recoverableErrors = new RegExp( - /(SOCKETTIMEDOUT|TIMEDOUT|CONNRESET|CONNREFUSED|NOTFOUND|Rate limit exceeded|Response code 5)/ -); +const includes = (str, list) => { + if(!_.isString(str)) + return false; -Trader.prototype.processError = function(funcName, error) { - if (!error) return undefined; - - if (!error.message.match(recoverableErrors)) { - log.error( - `[gdax.js] (${funcName}) returned an irrecoverable error: ${ - error.message - }` - ); - return new Errors.AbortError('[gdax.js] ' + error.message); - } - - log.debug( - `[gdax.js] (${funcName}) returned an error, retrying: ${error.message}` - ); - return new Errors.RetryError('[gdax.js] ' + error.message); -}; + return _.some(list, item => str.includes(item)); +} -Trader.prototype.handleResponse = function(funcName, callback) { +Trader.prototype.processResponse = function(method, next) { return (error, response, body) => { - if (body && !_.isEmpty(body.message)) error = new Error(body.message); - else if ( + if(!error && body && !_.isEmpty(body.message)) { + error = new Error(body.message); + } + + if( response && response.statusCode < 200 && response.statusCode >= 300 - ) + ) { error = new Error(`Response code ${response.statusCode}`); + } - return callback(this.processError(funcName, error), body); - }; -}; + if(error) { + if(includes(error.message, recoverableErrors)) { + error.notFatal = true; + } + + if( + ['buy', 'sell'].includes(method) && + error.message.includes('Insufficient funds') + ) { + error.retry = 10; + } + + return next(error); + } + + return next(undefined, body); + } +} Trader.prototype.getPortfolio = function(callback) { - var result = function(err, data) { + const result = (err, data) => { if (err) return callback(err); var portfolio = data.map(function(account) { @@ -115,117 +112,148 @@ Trader.prototype.getPortfolio = function(callback) { callback(undefined, portfolio); }; - let handler = cb => - this.gdax.getAccounts(this.handleResponse('getPortfolio', cb)); - util.retryCustom(retryForever, _.bind(handler, this), _.bind(result, this)); + const fetch = cb => + this.gdax.getAccounts(this.processResponse('getPortfolio', cb)); + retry(null, fetch, result); }; Trader.prototype.getTicker = function(callback) { - var result = function(err, data) { + const result = (err, data) => { if (err) return callback(err); callback(undefined, { bid: +data.bid, ask: +data.ask }); }; - let handler = cb => - this.gdax_public.getProductTicker(this.handleResponse('getTicker', cb)); - util.retryCustom(retryForever, _.bind(handler, this), _.bind(result, this)); + const fetch = cb => + this.gdax_public.getProductTicker(this.pair, this.processResponse('getTicker', cb)); + retry(null, fetch, result); }; Trader.prototype.getFee = function(callback) { //https://www.gdax.com/fees - const fee = this.asset == 'BTC' ? 0.0025 : 0.003; + // const fee = this.asset == 'BTC' ? 0.0025 : 0.003; + const fee = 0; //There is no maker fee, not sure if we need taker fee here //If post only is enabled, gdax only does maker trades which are free callback(undefined, this.post_only ? 0 : fee); }; +Trader.prototype.roundPrice = function(price) { + return this.getMaxDecimalsNumber(price, this.currency == 'BTC' ? 5 : 2); +} + +Trader.prototype.roundAmount = function(amount) { + return this.getMaxDecimalsNumber(amount); +} + Trader.prototype.buy = function(amount, price, callback) { - var buyParams = { + const buyParams = { price: this.getMaxDecimalsNumber(price, this.currency == 'BTC' ? 5 : 2), size: this.getMaxDecimalsNumber(amount), product_id: this.pair, post_only: this.post_only, }; - var result = (err, data) => { - if (err) return callback(err); + const result = (err, data) => { + if (err) { + console.log({buyParams}, err.message); + return callback(err); + } callback(undefined, data.id); }; - let handler = cb => - this.gdax.buy(buyParams, this.handleResponse('buy', cb)); - util.retryCustom(retryCritical, _.bind(handler, this), _.bind(result, this)); + const fetch = cb => + this.gdax.buy(buyParams, this.processResponse('buy', cb)); + retry(null, fetch, result); }; Trader.prototype.sell = function(amount, price, callback) { - var sellParams = { + const sellParams = { price: this.getMaxDecimalsNumber(price, this.currency == 'BTC' ? 5 : 2), size: this.getMaxDecimalsNumber(amount), product_id: this.pair, post_only: this.post_only, }; - var result = function(err, data) { - if (err) return callback(err); + const result = (err, data) => { + if (err) { + console.log({sellParams}, err.message); + return callback(err); + } + + if(data.message && data.message.includes('Insufficient funds')) { + err = new Error(data.message); + err.retryOnce = true; + return callback(err); + } + callback(undefined, data.id); }; - let handler = cb => - this.gdax.sell(sellParams, this.handleResponse('sell', cb)); - util.retryCustom(retryCritical, _.bind(handler, this), _.bind(result, this)); + const fetch = cb => + this.gdax.sell(sellParams, this.processResponse('sell', cb)); + retry(null, fetch, result); }; Trader.prototype.checkOrder = function(order, callback) { - var result = function(err, data) { + const result = (err, data) => { if (err) return callback(err); + // @link: + // https://stackoverflow.com/questions/48132078/available-gdax-order-statuses-and-meanings var status = data.status; - if (status == 'done') { - return callback(undefined, true); - } else if (status == 'rejected') { - return callback(undefined, false); - } else if (status == 'pending') { - return callback(undefined, false); + if(status == 'pending') { + // technically not open yet, but will be soon + return callback(undefined, { executed: false, open: true, filledAmount: 0 }); + } if (status === 'done' || status === 'settled') { + return callback(undefined, { executed: true, open: false }); + } else if (status === 'rejected') { + return callback(undefined, { executed: false, open: false }); + } else if(status === 'open' || status === 'active') { + return callback(undefined, { executed: false, open: true, filledAmount: parseFloat(data.filled_size) }); } - callback(undefined, false); + + callback(new Error('Unknown status ' + status)); }; - let handler = cb => - this.gdax.getOrder(order, this.handleResponse('checkOrder', cb)); - util.retryCustom(retryCritical, _.bind(handler, this), _.bind(result, this)); + const fetch = cb => + this.gdax.getOrder(order, this.processResponse('checkOrder', cb)); + retry(null, fetch, result); }; Trader.prototype.getOrder = function(order, callback) { - var result = function(err, data) { + const result = (err, data) => { if (err) return callback(err); - var price = parseFloat(data.price); - var amount = parseFloat(data.filled_size); - var date = moment(data.done_at); + const price = parseFloat(data.price); + const amount = parseFloat(data.filled_size); + const date = moment(data.done_at); + const fees = { + // you always pay fee in the base currency on gdax + [this.currency]: +data.fill_fees, + } - callback(undefined, { price, amount, date }); + callback(undefined, { price, amount, date, fees }); }; - let handler = cb => - this.gdax.getOrder(order, this.handleResponse('getOrder', cb)); - util.retryCustom(retryForever, _.bind(handler, this), _.bind(result, this)); + const fetch = cb => + this.gdax.getOrder(order, this.processResponse('getOrder', cb)); + retry(null, fetch, result); }; Trader.prototype.cancelOrder = function(order, callback) { // callback for cancelOrder should be true if the order was already filled, otherwise false - var result = function(err, data) { + const result = (err, data) => { if(err) { - log.error('Error cancelling order:', err); - return callback(true); // need to catch the specific error but usually an error on cancel means it was filled + return callback(null, true); // need to catch the specific error but usually an error on cancel means it was filled } - return callback(false); + return callback(null, false); }; - let handler = cb => - this.gdax.cancelOrder(order, this.handleResponse('cancelOrder', cb)); - util.retryCustom(retryForever, _.bind(handler, this), _.bind(result, this)); + const fetch = cb => + this.gdax.cancelOrder(order, this.processResponse('cancelOrder', cb)); + retry(null, fetch, result); }; Trader.prototype.getTrades = function(since, callback, descending) { @@ -257,13 +285,14 @@ Trader.prototype.getTrades = function(since, callback, descending) { setTimeout(() => { let handler = cb => this.gdax_public.getProductTrades( + this.pair, { after: last.trade_id - BATCH_SIZE * lastScan, limit: BATCH_SIZE, }, - this.handleResponse('getTrades', cb) + this.processResponse('getTrades', cb) ); - util.retryCustom( + retry( retryForever, _.bind(handler, this), _.bind(process, this) @@ -297,10 +326,11 @@ Trader.prototype.getTrades = function(since, callback, descending) { setTimeout(() => { let handler = cb => this.gdax_public.getProductTrades( + this.pair, { after: this.scanbackTid + BATCH_SIZE + 1, limit: BATCH_SIZE }, - this.handleResponse('getTrades', cb) + this.processResponse('getTrades', cb) ); - util.retryCustom( + retry( retryForever, _.bind(handler, this), _.bind(process, this) @@ -326,10 +356,11 @@ Trader.prototype.getTrades = function(since, callback, descending) { if (this.scanbackTid) { let handler = cb => this.gdax_public.getProductTrades( + this.pair, { after: this.scanbackTid + BATCH_SIZE + 1, limit: BATCH_SIZE }, - this.handleResponse('getTrades', cb) + this.processResponse('getTrades', cb) ); - util.retryCustom( + retry( retryForever, _.bind(handler, this), _.bind(process, this) @@ -342,10 +373,11 @@ Trader.prototype.getTrades = function(since, callback, descending) { let handler = cb => this.gdax_public.getProductTrades( + this.pair, { limit: BATCH_SIZE }, - this.handleResponse('getTrades', cb) + this.processResponse('getTrades', cb) ); - util.retryCustom(retryForever, _.bind(handler, this), _.bind(process, this)); + retry(retryForever, _.bind(handler, this), _.bind(process, this)); }; Trader.prototype.getMaxDecimalsNumber = function(number, decimalLimit = 8) { diff --git a/exchanges/gemini.js b/exchange/wrappers/gemini.js similarity index 100% rename from exchanges/gemini.js rename to exchange/wrappers/gemini.js diff --git a/exchanges/kraken-markets.json b/exchange/wrappers/kraken-markets.json similarity index 100% rename from exchanges/kraken-markets.json rename to exchange/wrappers/kraken-markets.json diff --git a/exchanges/kraken.js b/exchange/wrappers/kraken.js similarity index 100% rename from exchanges/kraken.js rename to exchange/wrappers/kraken.js diff --git a/exchanges/lakebtc.js b/exchange/wrappers/lakebtc.js similarity index 100% rename from exchanges/lakebtc.js rename to exchange/wrappers/lakebtc.js diff --git a/exchanges/mexbt.js b/exchange/wrappers/mexbt.js similarity index 100% rename from exchanges/mexbt.js rename to exchange/wrappers/mexbt.js diff --git a/exchanges/mtgox.js b/exchange/wrappers/mtgox.js similarity index 100% rename from exchanges/mtgox.js rename to exchange/wrappers/mtgox.js diff --git a/exchanges/okcoin.js b/exchange/wrappers/okcoin.js similarity index 100% rename from exchanges/okcoin.js rename to exchange/wrappers/okcoin.js diff --git a/exchange/wrappers/poloniex.js b/exchange/wrappers/poloniex.js new file mode 100644 index 000000000..67bb98c6b --- /dev/null +++ b/exchange/wrappers/poloniex.js @@ -0,0 +1,415 @@ +const Poloniex = require("poloniex.js"); +const _ = require('lodash'); +const moment = require('moment'); +const retry = require('../exchangeUtils').retry; +const marketData = require('./poloniex-markets.json'); + +const Trader = function(config) { + _.bindAll(this); + if(_.isObject(config)) { + this.key = config.key; + this.secret = config.secret; + this.currency = config.currency; + this.asset = config.asset; + } + this.name = 'Poloniex'; + this.balance; + this.price; + + this.pair = this.currency + '_' + this.asset; + + this.poloniex = new Poloniex(this.key, this.secret); +} + +const recoverableErrors = [ + 'SOCKETTIMEDOUT', + 'TIMEDOUT', + 'CONNRESET', + 'CONNREFUSED', + 'NOTFOUND', + '429', + '522', + '504', + '503', + '500', + '502', + 'Empty response', + 'Please try again in a few minutes.', + 'Nonce must be greater than', + 'Internal error. Please try again.' +]; + +// errors that might mean +// the API call succeeded. +const unknownResultErrors = [ + 'ETIMEDOUT', +] + +const includes = (str, list) => { + if(!_.isString(str)) + return false; + + return _.some(list, item => str.includes(item)); +} + +Trader.prototype.processResponse = function(next, fn, payload) { + // TODO: in very rare cases the callback is + // called twice (first on ETIMEDOUT later + // without error). Temp workaround. + next = _.once(next); + + return (err, data) => { + let error; + + if(err) { + if(err.message) { + error = err; + } else { + error = new Error(err); + } + } else if(!data) { + error = new Error('Empty response'); + } else if(data.error) { + error = new Error(data.error); + } else if(includes(data, ['Please complete the security check to proceed.'])) { + error = new Error( + 'Your IP has been flagged by CloudFlare. ' + + 'As such Gekko Broker cannot access Poloniex.' + ); + data = undefined; + } else if(includes(data, ['Please try again in a few minutes.'])) { + error = new Error('Please try again in a few minutes.'); + error.notFatal = true; + data = undefined; + } else if(includes(data, [''])) { + error = new Error(data); + data = undefined; + } + + if(error) { + + console.log(new Date, 'error, fn:', fn); + + if(fn === 'cancelOrder' || fn === 'order' || fn === 'checkOrder') { + console.log(new Date, 'ERROR!', fn, error.message); + } + + if(includes(error.message, recoverableErrors)) { + error.notFatal = true; + } + + // not actually an error, means order never executed against other trades + if(fn === 'getOrder' && + error.message.includes('Order not found, or you are not the person who placed it.') + ) { + error = undefined; + data = { unfilled: true }; + console.log('UNKNOWN ORDER!', payload); + process.exit(); + } + + if(fn === 'cancelOrder') { + + // already filled + if(includes(error.message, ['Invalid order number, or you are not the person who placed the order.'])) { + console.log(new Date, 'cancelOrder invalid order'); + error = undefined; + data = { filled: true }; + } + + // it might be cancelled + else if(includes(error.message, unknownResultErrors)) { + return setTimeout(() => { + this.getOpenOrders((err, orders) => { + if(err) { + return next(error); + } + + const order = _.find(orders, o => o.orderNumber == payload); + + // the cancel did not work since the order still exists + if(order) { + return next(error); + } + + next(undefined, {success: 1}); + }); + }, this.checkInterval); + } + } + + if(fn === 'order') { + // we need to check whether the order was actually created + if(includes(error.message, unknownResultErrors)) { + return setTimeout(() => { + this.findLastOrder(2, payload, (err, lastTrade) => { + if(lastTrade) { + console.log(new Date, 'create order passing lastTrade', lastTrade); + return next(undefined, lastTrade); + } + + console.log(new Date, 'create order, ETIMEDOUT'); + next(error); + }); + + }, this.checkInterval); + } + + if(includes(error.message, ['Not enough'])) { + error.retry = 2; + } + } + } + + return next(error, data); + } +} + +Trader.prototype.findLastOrder = function(since, side, callback) { + this.getOpenOrders((err, result) => { + if(err) { + return callback(err); + } + + result = result.filter(t => t.type === side); + + if(!result.length) { + return callback(undefined, undefined); + } + + let order; + if(since) { + const threshold = moment().subtract(since, 'm'); + order = _.find(result, o => moment.utc(o.date) > threshold); + } else { + order = _.last(result); + } + + callback(undefined, order); + }); +} + +Trader.prototype.getOpenOrders = function(callback) { + const fetch = next => this.poloniex.returnOpenOrders(this.currency, this.asset, this.processResponse(next)); + retry(null, fetch, callback); +} + +Trader.prototype.getPortfolio = function(callback) { + const handle = (err, data) => { + if(err) { + return callback(err); + } + + var assetAmount = parseFloat( data[this.asset] ); + var currencyAmount = parseFloat( data[this.currency] ); + + if( + !_.isNumber(assetAmount) || _.isNaN(assetAmount) || + !_.isNumber(currencyAmount) || _.isNaN(currencyAmount) + ) { + assetAmount = 0; + currencyAmount = 0; + } + + var portfolio = [ + { name: this.asset, amount: assetAmount }, + { name: this.currency, amount: currencyAmount } + ]; + + callback(undefined, portfolio); + } + + const fetch = next => this.poloniex.myBalances(this.processResponse(next)); + retry(null, fetch, handle); +} + +Trader.prototype.getTicker = function(callback) { + const handle = (err, data) => { + if(err) + return callback(err); + + var tick = data[this.pair]; + + callback(null, { + bid: parseFloat(tick.highestBid), + ask: parseFloat(tick.lowestAsk), + }); + }; + + + const fetch = next => this.poloniex.getTicker(this.processResponse(next)); + retry(null, fetch, handle); +} + +Trader.prototype.getFee = function(callback) { + const handle = (err, data) => { + if(err) + return callback(err); + + callback(undefined, parseFloat(data.makerFee)); + } + + const fetch = next => this.poloniex._private('returnFeeInfo', this.processResponse(next)); + retry(null, fetch, handle); +} + +Trader.prototype.roundAmount = function(amount) { + return +amount; +} + +Trader.prototype.roundPrice = function(price) { + return +price; +} + +Trader.prototype.createOrder = function(side, amount, price, callback) { + const handle = (err, result) => { + if(err) { + return callback(err); + } + + callback(undefined, result.orderNumber); + } + + const fetch = next => { + this.poloniex[side](this.currency, this.asset, price, amount, this.processResponse(next, 'order', side)) + }; + retry(null, fetch, handle); +} + +Trader.prototype.buy = function(amount, price, callback) { + this.createOrder('buy', amount, price, callback); +} + +Trader.prototype.sell = function(amount, price, callback) { + this.createOrder('sell', amount, price, callback); +} + +Trader.prototype.checkOrder = function(id, callback) { + const handle = (err, result) => { + + if(err) { + return callback(err); + } + + if(result.completed) { + return callback(undefined, { executed: true, open: false }); + } + + const order = _.find(result, function(o) { return o.orderNumber === id }); + if(!order) { + // if the order is not open it's fully executed + return callback(undefined, { executed: true, open: false }); + } + + callback(undefined, { executed: false, open: true, filledAmount: order.startingAmount - order.amount }); + } + + const fetch = next => this.poloniex.myOpenOrders(this.currency, this.asset, this.processResponse(next, 'checkOrder')); + retry(null, fetch, handle); +} + +Trader.prototype.getOrder = function(order, callback) { + const handle = (err, result) => { + if(err) + return callback(err); + + var price = 0; + var amount = 0; + var date = moment(0); + + if(result.unfilled) { + return callback(null, {price, amount, date}); + } + + _.each(result, trade => { + date = moment(trade.date); + price = ((price * amount) + (+trade.rate * trade.amount)) / (+trade.amount + amount); + amount += +trade.amount; + }); + + callback(err, {price, amount, date}); + }; + + const fetch = next => this.poloniex.returnOrderTrades(order, this.processResponse(next, 'getOrder', order)); + retry(null, fetch, handle); +} + +Trader.prototype.cancelOrder = function(order, callback) { + const handle = (err, result) => { + if(err) { + return callback(err); + } + + if(result.filled) { + return callback(undefined, true); + } + + if(!result.success) { + return callback(undefined, false); + } + + callback(undefined, false); + }; + + const fetch = next => this.poloniex.cancelOrder(this.currency, this.asset, order, this.processResponse(next, 'cancelOrder', order)); + retry(null, fetch, handle); +} + +Trader.prototype.getTrades = function(since, callback, descending) { + + const firstFetch = !!since; + const args = _.toArray(arguments); + + const handle = this.processResponse(this.sell, args, (err, result) => { + if(err) + return callback(err); + + // Edge case, see here: + // @link https://github.com/askmike/gekko/issues/479 + if(firstFetch && _.size(result) === 50000) + return callback( + [ + 'Poloniex did not provide enough data. Read this:', + 'https://github.com/askmike/gekko/issues/479' + ].join('\n\n') + ); + + result = _.map(result, function(trade) { + return { + tid: trade.tradeID, + amount: +trade.amount, + date: moment.utc(trade.date).unix(), + price: +trade.rate + }; + }); + + callback(null, result.reverse()); + }); + + var params = { + currencyPair: this.pair + } + + if(since) + params.start = since.unix(); + + this.poloniex._public('returnTradeHistory', params, handle); +} + +Trader.getCapabilities = function () { + return { + name: 'Poloniex', + slug: 'poloniex', + currencies: marketData.currencies, + assets: marketData.assets, + markets: marketData.markets, + currencyMinimums: {BTC: 0.0001, ETH: 0.0001, XMR: 0.0001, USDT: 1.0}, + requires: ['key', 'secret'], + tid: 'tid', + providesHistory: 'date', + providesFullHistory: true, + tradable: true + }; +} + +module.exports = Trader; diff --git a/exchanges/quadriga.js b/exchange/wrappers/quadriga.js similarity index 100% rename from exchanges/quadriga.js rename to exchange/wrappers/quadriga.js diff --git a/exchanges/wex.nz.js b/exchange/wrappers/wex.nz.js similarity index 100% rename from exchanges/wex.nz.js rename to exchange/wrappers/wex.nz.js diff --git a/exchanges/zaif.jp.js b/exchange/wrappers/zaif.jp.js similarity index 100% rename from exchanges/zaif.jp.js rename to exchange/wrappers/zaif.jp.js diff --git a/exchanges/coinfalcon.js b/exchanges/coinfalcon.js deleted file mode 100644 index 0df5b7aba..000000000 --- a/exchanges/coinfalcon.js +++ /dev/null @@ -1,252 +0,0 @@ -const moment = require('moment'); -const util = require('../core/util'); -const _ = require('lodash'); -const log = require('../core/log'); -const marketData = require('./coinfalcon-markets.json'); - -const CoinFalcon = require('coinfalcon'); - -var Trader = function(config) { - _.bindAll(this); - - if (_.isObject(config)) { - this.key = config.key; - this.secret = config.secret; - this.currency = config.currency.toUpperCase(); - this.asset = config.asset.toUpperCase(); - } - - this.pair = this.asset + '-' + this.currency; - this.name = 'coinfalcon'; - - this.coinfalcon = new CoinFalcon.Client(this.key, this.secret); -}; - -var recoverableErrors = new RegExp( - /(SOCKETTIMEDOUT|TIMEDOUT|CONNRESET|CONNREFUSED|NOTFOUND|429|522)/ -); - -Trader.prototype.retry = function(method, args, error) { - var self = this; - // make sure the callback (and any other fn) is bound to Trader - _.each(args, function(arg, i) { - if (_.isFunction(arg)) { - args[i] = _.bind(arg, self); - } - }); - - log.debug('[CoinFalcon] ', this.name, "Retrying..."); - - if (!error || !error.message.match(recoverableErrors)) { - log.error('[CoinFalcon] ', this.name, 'returned an irrecoverable error'); - _.each(args, function(arg, i) { - if (_.isFunction(arg)) { - arg(error, null); - return; - } - }); - return; - } - - var wait = +moment.duration(5, 'seconds'); - - // run the failed method again with the same arguments after wait - setTimeout(function() { - method.apply(self, args); - }, wait); -}; - -Trader.prototype.getTicker = function(callback) { - var success = function(res) { - callback(null, {bid: +res.data.bids[0].price, ask: +res.data.asks[0].price}) - }; - - var failure = function(err) { - log.error('[CoinFalcon] error getting ticker', err); - callback(err, null); - }; - - var url = "markets/" + this.pair + "/orders?level=1" - - this.coinfalcon.get(url).then(success).catch(failure); -}; - -Trader.prototype.getFee = function(callback) { - var fees = 0.25; // 0.25% for both sell & buy - callback(false, fees / 100); -}; - -Trader.prototype.getPortfolio = function(callback) { - var success = function(res) { - if (_.has(res, 'error')) { - var err = new Error(res.error); - callback(err, null); - } else { - var portfolio = res.data.map(function(account) { - return { - name: account.currency, - amount: parseFloat(account.available) - } - }); - callback(null, portfolio); - } - }; - - var failure = function(err) { - log.error('[CoinFalcon] error getting portfolio', err); - callback(err, null); - } - - this.coinfalcon.get('user/accounts').then(success).catch(failure); -}; - -Trader.prototype.addOrder = function(type, amount, price, callback) { - var args = _.toArray(arguments); - - var success = function(res) { - if (_.has(res, 'error')) { - var err = new Error(res.error); - failure(err); - } else { - callback(false, res.data.id) - } - }; - - var failure = function(err) { - log.error('[CoinFalcon] unable to ' + type.toLowerCase(), err); - return this.retry(this.addOrder, args, err); - }.bind(this); - - var payload = { - order_type: type, - operation_type: 'limit_order', - market: this.pair, - size: amount, - price: price - } - - this.coinfalcon.post('user/orders', payload).then(success).catch(failure); -}; - -['buy', 'sell'].map(function(type) { - Trader.prototype[type] = function(amount, price, callback) { - this.addOrder(type, amount, price, callback); - }; -}); - -Trader.prototype.getOrder = function(order, callback) { - var success = function(res) { - if (_.has(res, 'error')) { - var err = new Error(res.error); - failure(err); - } else { - var price = parseFloat(res.data.price); - var amount = parseFloat(res.data.size); - var date = moment(res.data.created_at); - callback(false, { price, amount, date }); - } - }; - - var failure = function(err) { - log.error('[CoinFalcon] unable to getOrder', err); - callback(err, null); - }.bind(this); - - this.coinfalcon.get('user/orders/' + order).then(success).catch(failure); -}; - -Trader.prototype.checkOrder = function(order, callback) { - var success = function(res) { - if (_.has(res, 'error')) { - var err = new Error(res.error); - failure(err); - } else { - var filled = res.data.status == "canceled" || res.data.status == "fulfilled"; - callback(false, filled); - } - }; - - var failure = function(err) { - log.error('[CoinFalcon] unable to checkOrder', err); - callback(err, null); - }.bind(this); - - this.coinfalcon.get('user/orders/' + order).then(success).catch(failure); -}; - -Trader.prototype.cancelOrder = function(order, callback) { - var args = _.toArray(arguments); - var success = function(res) { - if (_.has(res, 'error')) { - var err = new Error(res.error); - failure(err); - } else { - callback(false, res.data.id) - } - }; - - var failure = function(err) { - log.error('[CoinFalcon] unable to cancel', err); - return this.retry(this.cancelOrder, args, err); - }.bind(this); - - this.coinfalcon.delete('user/orders?id=' + order).then(success).catch(failure); -}; - -Trader.prototype.getTrades = function(since, callback, descending) { - var args = _.toArray(arguments); - - var success = function(res) { - var parsedTrades = []; - _.each( - res.data, - function(trade) { - parsedTrades.push({ - tid: moment(trade.created_at).unix(), // Saving unix timestamp in tid as CoinFalcon has string uuid which can not be compared in importer - date: moment(trade.created_at).unix(), - price: parseFloat(trade.price), - amount: parseFloat(trade.size), - }); - }, - this - ); - - if (descending) { - callback(null, parsedTrades); - } else { - callback(null, parsedTrades.reverse()); - } - }.bind(this); - - var failure = function (err) { - err = new Error(err); - log.error('[CoinFalcon] error getting trades', err); - return this.retry(this.getTrades, args, err); - }.bind(this); - - var url = "markets/" + this.pair + "/trades" - - if (since) { - url += '?since_time=' + (_.isString(since) ? since : since.toISOString()); - } - - this.coinfalcon.get(url).then(success).catch(failure); -}; - -Trader.getCapabilities = function () { - return { - name: 'CoinFalcon', - slug: 'coinfalcon', - assets: marketData.assets, - currencies: marketData.currencies, - markets: marketData.markets, - requires: ['key', 'secret'], - providesHistory: 'date', - providesFullHistory: true, - tid: 'tid', - tradable: true, - forceReorderDelay: false - }; -} - -module.exports = Trader; diff --git a/exchanges/poloniex.js b/exchanges/poloniex.js deleted file mode 100644 index fac00eb97..000000000 --- a/exchanges/poloniex.js +++ /dev/null @@ -1,264 +0,0 @@ -const Poloniex = require("poloniex.js"); -const util = require('../core/util.js'); -const _ = require('lodash'); -const moment = require('moment'); -const log = require('../core/log'); -const marketData = require('./poloniex-markets.json'); - -// Helper methods -function joinCurrencies(currencyA, currencyB){ - return currencyA + '_' + currencyB; -} - -var Trader = function(config) { - _.bindAll(this); - if(_.isObject(config)) { - this.key = config.key; - this.secret = config.secret; - this.currency = config.currency; - this.asset = config.asset; - } - this.name = 'Poloniex'; - this.balance; - this.price; - - this.pair = [this.currency, this.asset].join('_'); - this.market = _.find(Trader.getCapabilities().markets, (market) => { - return market.pair[0] === this.currency && market.pair[1] === this.asset - }); - - this.poloniex = new Poloniex(this.key, this.secret); -} - -// if the exchange errors we try the same call again after -// waiting 10 seconds -Trader.prototype.retry = function(method, args) { - var wait = +moment.duration(10, 'seconds'); - log.debug(this.name, 'returned an error, retrying..'); - - var self = this; - - // make sure the callback (and any other fn) - // is bound to Trader - _.each(args, function(arg, i) { - if(_.isFunction(arg)) - args[i] = _.bind(arg, self); - }); - - // run the failed method again with the same - // arguments after wait - setTimeout( - function() { method.apply(self, args) }, - wait - ); -} - -Trader.prototype.getPortfolio = function(callback) { - var args = _.toArray(arguments); - var set = function(err, data) { - if(err) - return this.retry(this.getPortfolio, args); - - var assetAmount = parseFloat( data[this.asset] ); - var currencyAmount = parseFloat( data[this.currency] ); - - if( - !_.isNumber(assetAmount) || _.isNaN(assetAmount) || - !_.isNumber(currencyAmount) || _.isNaN(currencyAmount) - ) { - log.info('asset:', this.asset); - log.info('currency:', this.currency); - log.info('exchange data:', data); - util.die('Gekko was unable to set the portfolio'); - } - - var portfolio = [ - { name: this.asset, amount: assetAmount }, - { name: this.currency, amount: currencyAmount } - ]; - - callback(err, portfolio); - }.bind(this); - - this.poloniex.myBalances(set); -} - -Trader.prototype.getTicker = function(callback) { - var args = _.toArray(arguments); - this.poloniex.getTicker(function(err, data) { - if(err) - return this.retry(this.getTicker, args); - - var tick = data[this.pair]; - - callback(null, { - bid: parseFloat(tick.highestBid), - ask: parseFloat(tick.lowestAsk), - }); - - }.bind(this)); -} - -Trader.prototype.getFee = function(callback) { - var set = function(err, data) { - if(err || data.error) - return callback(err || data.error); - - callback(false, parseFloat(data.makerFee)); - } - this.poloniex._private('returnFeeInfo', _.bind(set, this)); -} - -Trader.prototype.getLotSize = function(tradeType, amount, price, callback) { - if (amount < this.market.minimalOrder.amount) - return callback(undefined, { amount: 0, price: 0 }); - - if (amount * price < this.market.minimalOrder.order) - return callback(undefined, { amount: 0, price: 0}); - - callback(undefined, { amount: amount, price: price }); -} - -Trader.prototype.buy = function(amount, price, callback) { - var args = _.toArray(arguments); - var set = function(err, result) { - if(err || result.error) { - log.error('unable to buy:', err, result); - return this.retry(this.buy, args); - } - - callback(null, result.orderNumber); - }.bind(this); - - this.poloniex.buy(this.currency, this.asset, price, amount, set); -} - -Trader.prototype.sell = function(amount, price, callback) { - var args = _.toArray(arguments); - var set = function(err, result) { - if(err || result.error) { - log.error('unable to sell:', err, result); - return this.retry(this.sell, args); - } - - callback(null, result.orderNumber); - }.bind(this); - - this.poloniex.sell(this.currency, this.asset, price, amount, set); -} - -Trader.prototype.checkOrder = function(order, callback) { - var check = function(err, result) { - var stillThere = _.find(result, function(o) { return o.orderNumber === order }); - callback(err, !stillThere); - }.bind(this); - - this.poloniex.myOpenOrders(this.currency, this.asset, check); -} - -Trader.prototype.getOrder = function(order, callback) { - - var get = function(err, result) { - - if(err) - return callback(err); - - var price = 0; - var amount = 0; - var date = moment(0); - - if(result.error === 'Order not found, or you are not the person who placed it.') - return callback(null, {price, amount, date}); - - _.each(result, trade => { - - date = moment(trade.date); - price = ((price * amount) + (+trade.rate * trade.amount)) / (+trade.amount + amount); - amount += +trade.amount; - - }); - - callback(err, {price, amount, date}); - }.bind(this); - - this.poloniex.returnOrderTrades(order, get); -} - -Trader.prototype.cancelOrder = function(order, callback) { - var args = _.toArray(arguments); - var cancel = function(err, result) { - - // check if order is gone already - if(result.error === 'Invalid order number, or you are not the person who placed the order.') - return callback(true); - - if(err || !result.success) { - log.error('unable to cancel order', order, '(', err, result, '), retrying'); - return this.retry(this.cancelOrder, args); - } - - callback(); - }.bind(this); - - this.poloniex.cancelOrder(this.currency, this.asset, order, cancel); -} - -Trader.prototype.getTrades = function(since, callback, descending) { - - var firstFetch = !!since; - - var args = _.toArray(arguments); - var process = function(err, result) { - if(err) { - return this.retry(this.getTrades, args); - } - - // Edge case, see here: - // @link https://github.com/askmike/gekko/issues/479 - if(firstFetch && _.size(result) === 50000) - util.die( - [ - 'Poloniex did not provide enough data. Read this:', - 'https://github.com/askmike/gekko/issues/479' - ].join('\n\n') - ); - - result = _.map(result, function(trade) { - return { - tid: trade.tradeID, - amount: +trade.amount, - date: moment.utc(trade.date).unix(), - price: +trade.rate - }; - }); - - callback(null, result.reverse()); - }; - - var params = { - currencyPair: joinCurrencies(this.currency, this.asset) - } - - if(since) - params.start = since.unix(); - - this.poloniex._public('returnTradeHistory', params, _.bind(process, this)); -} - -Trader.getCapabilities = function () { - return { - name: 'Poloniex', - slug: 'poloniex', - currencies: marketData.currencies, - assets: marketData.assets, - markets: marketData.markets, - currencyMinimums: {BTC: 0.0001, ETH: 0.0001, XMR: 0.0001, USDT: 1.0}, - requires: ['key', 'secret'], - tid: 'tid', - providesHistory: 'date', - providesFullHistory: true, - tradable: true - }; -} - -module.exports = Trader; diff --git a/package-lock.json b/package-lock.json index fb277a4ba..3f1b856d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,32 +4,23 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@sinonjs/formatio": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", - "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", - "dev": true, - "requires": { - "samsam": "1.3.0" - } - }, "@slack/client": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/@slack/client/-/client-3.16.0.tgz", - "integrity": "sha512-CWr7a3rTVrN5Vs8GYReRAvTourbXHOqB1zglcskj05ICH4GZL5BOAza2ARai+qc3Nz0nY08Bozi1x0014KOqlg==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@slack/client/-/client-3.13.0.tgz", + "integrity": "sha512-45hHqycaVK4UVL0e1jRK1bEOyb2/a3V6QJAgP43Bg03y2En4BZ3J3Qe7zNh09VTYsQ6KJeHTOWldDhNJUbdkTw==", "requires": { "async": "1.5.2", - "bluebird": "3.5.1", + "bluebird": "3.5.0", "eventemitter3": "1.2.0", "https-proxy-agent": "1.0.0", "inherits": "2.0.3", - "lodash": "4.17.10", + "lodash": "4.17.4", "pkginfo": "0.4.1", - "request": "2.76.0", + "request": "2.83.0", "retry": "0.9.0", "url-join": "0.0.1", - "winston": "2.4.2", - "ws": "1.1.5" + "winston": "2.3.1", + "ws": "1.1.4" }, "dependencies": { "async": { @@ -38,36 +29,9 @@ "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" }, "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" - }, - "request": { - "version": "2.76.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.76.0.tgz", - "integrity": "sha1-vkRQWv73A2CgQ2lVEGvjlF2VVg4=", - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.7.0", - "caseless": "0.11.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "2.0.6", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "node-uuid": "1.4.8", - "oauth-sign": "0.8.2", - "qs": "6.3.2", - "stringstream": "0.0.6", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.4.3" - } + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" }, "retry": { "version": "0.9.0", @@ -122,24 +86,20 @@ "version": "3.4.7", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=" + }, + "uuid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" } } }, - "JSONStream": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.3.tgz", - "integrity": "sha512-3Sp6WZZ/lXl+nTDoGpGWHEpTnnC6X5fnkolYZR6nwIfzbxxvA8utPWe1gCt7i0m9uVGsSz2IS8K8mJ7HmlduMg==", - "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" - } - }, "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", + "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", "requires": { - "mime-types": "2.1.18", + "mime-types": "2.1.17", "negotiator": "0.6.1" } }, @@ -165,7 +125,7 @@ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { "co": "4.6.0", - "fast-deep-equal": "1.1.0", + "fast-deep-equal": "1.0.0", "fast-json-stable-stringify": "2.0.0", "json-schema-traverse": "0.3.1" } @@ -201,22 +161,22 @@ "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" }, "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", + "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=" }, "async": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/async/-/async-2.1.2.tgz", "integrity": "sha1-YSpKtF70KnDN6Aa62G7m2wR+g4U=", "requires": { - "lodash": "4.17.10" + "lodash": "4.17.4" }, "dependencies": { "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" } } }, @@ -236,18 +196,9 @@ "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" }, "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "requires": { - "core-js": "2.5.7", - "regenerator-runtime": "0.11.1" - } + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" }, "balanced-match": { "version": "1.0.0", @@ -263,38 +214,6 @@ "tweetnacl": "0.14.5" } }, - "binance": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/binance/-/binance-1.3.3.tgz", - "integrity": "sha512-1eV2QUoH/Z0FZPiGjigJg4udXV9Uu6Clr0Sg1xsX3xStgPfzXz0juA3mllQIiIaHx7dmfAQgEiZIyeJLx5ajag==", - "requires": { - "request": "2.87.0", - "underscore": "1.9.0", - "ws": "3.3.3" - }, - "dependencies": { - "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" - }, - "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "requires": { - "async-limiter": "1.0.0", - "safe-buffer": "5.1.2", - "ultron": "1.1.1" - } - } - } - }, - "bintrees": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.0.tgz", - "integrity": "sha1-nqCaZnLBE0tejEMToT5HzKloxyA=" - }, "bitcoin-co-id-update": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/bitcoin-co-id-update/-/bitcoin-co-id-update-0.0.2.tgz", @@ -302,9 +221,21 @@ "requires": { "crypto": "1.0.1", "querystring": "0.2.0", - "request": "2.87.0", - "underscore": "1.9.0", + "request": "2.83.0", + "underscore": "1.8.3", "verror": "1.10.0" + }, + "dependencies": { + "crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==" + }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" + } } }, "bitexthai": { @@ -312,63 +243,67 @@ "resolved": "https://registry.npmjs.org/bitexthai/-/bitexthai-0.1.0.tgz", "integrity": "sha1-R2wfRisgZnu2vnub9kkGcAlQ2zA=", "requires": { - "lodash": "4.17.10" + "lodash": "4.17.4" }, "dependencies": { "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" } } }, "bitfinex-api-node": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bitfinex-api-node/-/bitfinex-api-node-1.2.1.tgz", - "integrity": "sha512-pG4BMCD7T/R1vkLhLdHPim4Lbfbkdyt/yTaJ+A48vrzGsQO7MwxIRRs6rEx1Acm/vpsUyksbOaQyladh2T8Whw==", - "requires": { - "debug": "2.6.9", - "lodash": "4.17.10", - "request": "2.87.0", - "request-promise": "4.2.2", - "ws": "3.3.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bitfinex-api-node/-/bitfinex-api-node-1.2.0.tgz", + "integrity": "sha512-I15gldEWF+lPWQkK4mtAgjgKW+5e4ajiB/Fs+2B9z5VWAu93H4IffW6w5Qz3+awG9X2AE+Jz1Rrytg8QVvVWDA==", + "requires": { + "debug": "2.6.8", + "lodash": "4.17.4", + "request": "2.83.0", + "request-promise": "4.2.1", + "ws": "3.2.0" }, "dependencies": { "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "request-promise": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.1.tgz", + "integrity": "sha1-fuxWyJMXqCLL/qmbA5zlQ8LhX2c=", + "requires": { + "bluebird": "3.5.0", + "request-promise-core": "1.1.1", + "stealthy-require": "1.1.1", + "tough-cookie": "2.3.2" + } }, "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.0.tgz", + "integrity": "sha1-sHoualQagV/Go0zNRTO67DB8qGQ=" }, "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.2.0.tgz", + "integrity": "sha512-hTS3mkXm/j85jTQOIcwVz3yK3up9xHgPtgEhDBOH3G18LDOZmSAG1omJeXejLKJakx+okv8vS1sopgs7rw0kVw==", "requires": { "async-limiter": "1.0.0", - "safe-buffer": "5.1.2", - "ultron": "1.1.1" + "safe-buffer": "5.1.1", + "ultron": "1.1.0" } } } }, "bitstamp": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/bitstamp/-/bitstamp-1.0.6.tgz", - "integrity": "sha512-TZDi2OvckUWNl9qDotuOjQsdR9KfByqhy+4eRo2GmpmUbzvG9Fu+fnC9VGeeX9Kc5yAgHWLyvrlOq+6QYdi4eg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bitstamp/-/bitstamp-1.0.3.tgz", + "integrity": "sha512-SlXhUil7zB5nzrpaspNrJ8UpGv+xeXuYsRMBRtwrXSlP70OFFDRe8P6T9VHLtf60Ra6yHnoroSHd/1FeQMuJkg==", "requires": { "underscore": "1.4.4" - }, - "dependencies": { - "underscore": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", - "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=" - } } }, "bitx": { @@ -405,9 +340,9 @@ } }, "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", + "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" }, "boolbase": { "version": "1.0.0", @@ -423,9 +358,9 @@ } }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -448,10 +383,10 @@ "verror": "1.6.1" }, "dependencies": { - "crypto": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/crypto/-/crypto-0.0.3.tgz", - "integrity": "sha1-RwqBuGvkxe4XrMggeh9TFa4g27A=" + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" }, "extsprintf": { "version": "1.2.0", @@ -464,10 +399,26 @@ "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", "requires": { "async": "2.1.2", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "har-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "requires": { + "chalk": "1.1.3", + "commander": "2.13.0", + "is-my-json-valid": "2.16.1", + "pinkie-promise": "2.0.1" } }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, "qs": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.1.tgz", @@ -481,7 +432,7 @@ "aws-sign2": "0.6.0", "bl": "1.0.3", "caseless": "0.11.0", - "combined-stream": "1.0.6", + "combined-stream": "1.0.5", "extend": "3.0.1", "forever-agent": "0.6.1", "form-data": "1.0.1", @@ -491,11 +442,11 @@ "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", + "mime-types": "2.1.17", "node-uuid": "1.4.8", "oauth-sign": "0.8.2", "qs": "5.2.1", - "stringstream": "0.0.6", + "stringstream": "0.0.5", "tough-cookie": "2.2.2", "tunnel-agent": "0.4.3" } @@ -505,6 +456,11 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz", "integrity": "sha1-yDoYMPTl7wuT7yo0iOck+N4Basc=" }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" + }, "underscore": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", @@ -532,6 +488,17 @@ "verror": "1.6.1" }, "dependencies": { + "ajv": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.3.tgz", + "integrity": "sha1-wG9Zh3jETGsWGrr+NGa4GtGBTtI=", + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "json-schema-traverse": "0.3.1", + "json-stable-stringify": "1.0.1" + } + }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -547,14 +514,9 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", "requires": { - "hoek": "4.2.1" + "hoek": "4.2.0" } }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, "cryptiles": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", @@ -568,7 +530,7 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", "requires": { - "hoek": "4.2.1" + "hoek": "4.2.0" } } } @@ -579,21 +541,26 @@ "integrity": "sha1-WtlGwi9bMrp/jNdCZxHG6KP8JSk=" }, "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", + "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", "requires": { "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" + "combined-stream": "1.0.5", + "mime-types": "2.1.17" } }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, "har-validator": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "requires": { - "ajv": "5.5.2", + "ajv": "5.2.3", "har-schema": "2.0.0" } }, @@ -604,14 +571,14 @@ "requires": { "boom": "4.3.1", "cryptiles": "3.1.2", - "hoek": "4.2.1", - "sntp": "2.1.0" + "hoek": "4.2.0", + "sntp": "2.0.2" } }, "hoek": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", - "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", + "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" }, "http-signature": { "version": "1.2.0", @@ -620,13 +587,18 @@ "requires": { "assert-plus": "1.0.0", "jsprim": "1.4.1", - "sshpk": "1.14.1" + "sshpk": "1.13.1" } }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" }, "request": { "version": "2.83.0", @@ -634,43 +606,43 @@ "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", "requires": { "aws-sign2": "0.7.0", - "aws4": "1.7.0", + "aws4": "1.6.0", "caseless": "0.12.0", - "combined-stream": "1.0.6", + "combined-stream": "1.0.5", "extend": "3.0.1", "forever-agent": "0.6.1", - "form-data": "2.3.2", + "form-data": "2.3.1", "har-validator": "5.0.3", "hawk": "6.0.2", "http-signature": "1.2.0", "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", + "mime-types": "2.1.17", "oauth-sign": "0.8.2", "performance-now": "2.1.0", - "qs": "6.5.2", - "safe-buffer": "5.1.2", - "stringstream": "0.0.6", - "tough-cookie": "2.3.4", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", "tunnel-agent": "0.6.0", - "uuid": "3.2.1" + "uuid": "3.1.0" } }, "sntp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.0.2.tgz", + "integrity": "sha1-UGQRDwr4X3z9t9a2ekACjOUrSys=", "requires": { - "hoek": "4.2.1" + "hoek": "4.2.0" } }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", "requires": { - "safe-buffer": "5.1.2" + "punycode": "1.4.1" } }, "underscore": { @@ -678,11 +650,6 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" - }, "verror": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/verror/-/verror-1.6.1.tgz", @@ -700,9 +667,9 @@ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "cexio": { "version": "0.0.5", @@ -724,12 +691,27 @@ "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", "requires": { - "assertion-error": "1.1.0", + "assertion-error": "1.0.2", "check-error": "1.0.2", "deep-eql": "3.0.1", "get-func-name": "2.0.0", "pathval": "1.1.0", - "type-detect": "4.0.8" + "type-detect": "4.0.7" + }, + "dependencies": { + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "requires": { + "type-detect": "4.0.7" + } + }, + "type-detect": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.7.tgz", + "integrity": "sha512-4Rh17pAMVdMWzktddFhISRnUnFIStObtUMNGzDwlA6w/77bmGv3aBbRdCmQR6IjzfkTo9otnW+2K/cDRhKSxDA==" + } } }, "chalk": { @@ -779,21 +761,14 @@ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, "co-body": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/co-body/-/co-body-5.2.0.tgz", - "integrity": "sha512-sX/LQ7LqUhgyaxzbe7IqwPeTr2yfpfUIQ/dgpKo6ZI4y4lpQA0YxAomWIY+7I7rHWcG02PG+OuPREzMW/5tszQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-5.1.1.tgz", + "integrity": "sha1-2XeB0eM0S6SoIP0YBr3fg0FQUjY=", "requires": { "inflation": "2.0.0", - "qs": "6.5.2", - "raw-body": "2.3.3", - "type-is": "1.6.16" - }, - "dependencies": { - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - } + "qs": "6.4.0", + "raw-body": "2.3.2", + "type-is": "1.6.15" } }, "co-from-stream": { @@ -823,14 +798,6 @@ "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", "integrity": "sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==" }, - "coinfalcon": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/coinfalcon/-/coinfalcon-1.0.5.tgz", - "integrity": "sha512-TyzLmcE2Vmll6oCyZSdEkla/thVZPjLxhDwlPyYKB5/Uv5wf/dzwox3Q2DrnJcOKRAQ1bKcHWmR2dFQKaEoK+A==", - "requires": { - "babel-runtime": "6.26.0" - } - }, "coingi": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/coingi/-/coingi-1.0.7.tgz", @@ -849,14 +816,34 @@ "graceful-readlink": "1.0.1" } }, - "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "diff": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", + "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=" + }, + "glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", "requires": { - "ms": "2.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=" + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, "mocha": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", @@ -892,17 +879,17 @@ "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" }, "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", "requires": { "delayed-stream": "1.0.0" } }, "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==" }, "composition": { "version": "2.3.0", @@ -933,7 +920,7 @@ "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.1.tgz", "integrity": "sha1-fIphX1SBxhq58WyDNzG8uPZjuZs=", "requires": { - "depd": "1.1.2", + "depd": "1.1.1", "keygrip": "1.0.2" } }, @@ -942,11 +929,6 @@ "resolved": "https://registry.npmjs.org/copy-to/-/copy-to-2.0.1.tgz", "integrity": "sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU=" }, - "core-js": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -966,9 +948,9 @@ } }, "crypto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", - "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==" + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-0.0.3.tgz", + "integrity": "sha1-RwqBuGvkxe4XrMggeh9TFa4g27A=" }, "css-select": { "version": "1.0.0", @@ -1013,9 +995,9 @@ } }, "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", "requires": { "ms": "2.0.0" } @@ -1028,23 +1010,15 @@ "mimic-response": "1.0.0" } }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "requires": { - "type-detect": "4.0.8" - } - }, "deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" }, "deep-extend": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", - "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.0.tgz", + "integrity": "sha1-bvSgmwX5iw41jW2T1Mo8rsZnKAM=" }, "delayed-stream": { "version": "1.0.0", @@ -1057,9 +1031,9 @@ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" }, "destroy": { "version": "1.0.4", @@ -1067,9 +1041,10 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, "diff": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", - "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=" + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "dev": true }, "dom-serializer": { "version": "0.1.0", @@ -1154,7 +1129,7 @@ }, "event-stream": { "version": "3.3.4", - "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", "requires": { "duplexer": "0.1.1", @@ -1192,9 +1167,9 @@ "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" }, "fast-json-stable-stringify": { "version": "2.0.0", @@ -1217,19 +1192,28 @@ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", + "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", "requires": { "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "formatio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", + "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", + "dev": true, + "requires": { + "samsam": "1.3.0" } }, "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz", + "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8=" }, "from": { "version": "0.1.7", @@ -1241,175 +1225,59 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "gdax": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/gdax/-/gdax-0.4.2.tgz", - "integrity": "sha1-jo0GIi7Zfl40l11W9R6/SaVvqHI=", - "requires": { - "async": "1.5.0", - "bintrees": "1.0.0", - "lodash.assign": "3.0.0", - "lodash.foreach": "3.0.0", - "lodash.partial": "3.0.0", - "nock": "3.6.0", - "num": "0.2.1", - "request": "2.74.0", - "ws": "1.1.1" + "gekko": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/gekko/-/gekko-0.0.9.tgz", + "integrity": "sha1-Q8ot+bv+VC+lntagY8leNVPaVVg=" + }, + "gemini-exchange-coffee-api": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/gemini-exchange-coffee-api/-/gemini-exchange-coffee-api-2.0.6.tgz", + "integrity": "sha1-JHML1brLG0Fs1LzcQQyBzwjI9pc=", + "requires": { + "coffeescript": "1.12.7", + "request": "2.30.0" }, "dependencies": { + "asn1": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", + "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", + "optional": true + }, + "assert-plus": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", + "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", + "optional": true + }, "async": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.0.tgz", - "integrity": "sha1-J5ZkJyNXOFlWVjP8YnRES+4vjOM=" + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "optional": true }, - "bl": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", - "integrity": "sha1-/cqHGplxOqANGeO7ukHER4emU5g=", + "aws-sign2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", + "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", + "optional": true + }, + "boom": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz", + "integrity": "sha1-emNune1O/O+xnO9JR6PGffrukRs=", "requires": { - "readable-stream": "2.0.6" + "hoek": "0.9.1" } }, - "form-data": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", - "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", + "combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", + "optional": true, "requires": { - "async": "2.6.1", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" - }, - "dependencies": { - "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", - "requires": { - "lodash": "4.17.10" - } - } - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" - }, - "qs": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.3.tgz", - "integrity": "sha1-HPyyXBCpsrSDBT/zn138kjOQjP4=" - }, - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "0.10.31", - "util-deprecate": "1.0.2" - } - }, - "request": { - "version": "2.74.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.74.0.tgz", - "integrity": "sha1-dpPKdou7DqXIzgjAhKRe+gW4kqs=", - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.7.0", - "bl": "1.1.2", - "caseless": "0.11.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "1.0.1", - "har-validator": "2.0.6", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "node-uuid": "1.4.8", - "oauth-sign": "0.8.2", - "qs": "6.2.3", - "stringstream": "0.0.6", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.4.3" - } - }, - "ws": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.1.tgz", - "integrity": "sha1-CC3bbGQehdS7RR8D1S8G6r2x8Bg=", - "requires": { - "options": "0.0.6", - "ultron": "1.0.2" - } - } - } - }, - "gekko": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/gekko/-/gekko-0.0.9.tgz", - "integrity": "sha1-Q8ot+bv+VC+lntagY8leNVPaVVg=" - }, - "gemini-exchange-coffee-api": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/gemini-exchange-coffee-api/-/gemini-exchange-coffee-api-2.0.6.tgz", - "integrity": "sha1-JHML1brLG0Fs1LzcQQyBzwjI9pc=", - "requires": { - "coffeescript": "1.12.7", - "request": "2.30.0" - }, - "dependencies": { - "asn1": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", - "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", - "optional": true - }, - "assert-plus": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", - "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", - "optional": true - }, - "async": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", - "optional": true - }, - "aws-sign2": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", - "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", - "optional": true - }, - "boom": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz", - "integrity": "sha1-emNune1O/O+xnO9JR6PGffrukRs=", - "requires": { - "hoek": "0.9.1" - } - }, - "combined-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", - "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", - "optional": true, - "requires": { - "delayed-stream": "0.0.5" + "delayed-stream": "0.0.5" } }, "cryptiles": { @@ -1471,6 +1339,11 @@ "ctype": "0.5.3" } }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, "oauth-sign": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz", @@ -1566,9 +1439,10 @@ } }, "glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", - "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -1590,10 +1464,10 @@ "is-retry-allowed": "1.1.0", "is-stream": "1.1.0", "isurl": "1.0.0", - "lowercase-keys": "1.0.1", + "lowercase-keys": "1.0.0", "p-cancelable": "0.3.0", - "p-timeout": "1.2.1", - "safe-buffer": "5.1.2", + "p-timeout": "1.2.0", + "safe-buffer": "5.1.1", "timed-out": "4.0.1", "url-parse-lax": "1.0.0", "url-to-options": "1.0.1" @@ -1605,9 +1479,10 @@ "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" }, "growl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=" + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "dev": true }, "har-schema": { "version": "2.0.0", @@ -1615,14 +1490,12 @@ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "requires": { - "chalk": "1.1.3", - "commander": "2.15.1", - "is-my-json-valid": "2.17.2", - "pinkie-promise": "2.0.1" + "ajv": "5.5.2", + "har-schema": "2.0.0" } }, "has-ansi": { @@ -1634,21 +1507,22 @@ } }, "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true }, "has-symbol-support-x": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", - "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.1.tgz", + "integrity": "sha512-JkaetveU7hFbqnAC1EV1sF4rlojU2D4Usc5CmS69l6NfmPDnpnFUegzFg33eDkkpNCxZ0mQp65HwUDrNFS/8MA==" }, "has-to-string-tag-x": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", "requires": { - "has-symbol-support-x": "1.4.2" + "has-symbol-support-x": "1.4.1" } }, "hawk": { @@ -1706,18 +1580,18 @@ "integrity": "sha1-oxpc+IyHPsu1eWkH1NbxMujAHko=", "requires": { "deep-equal": "1.0.1", - "http-errors": "1.6.3" + "http-errors": "1.6.2" } }, "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", "requires": { - "depd": "1.1.2", + "depd": "1.1.1", "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": "1.5.0" + "setprototypeof": "1.0.3", + "statuses": "1.3.1" } }, "http-signature": { @@ -1727,7 +1601,7 @@ "requires": { "assert-plus": "0.2.0", "jsprim": "1.4.1", - "sshpk": "1.14.1" + "sshpk": "1.13.1" } }, "https-proxy-agent": { @@ -1736,14 +1610,14 @@ "integrity": "sha1-NffabEjOTdv6JkiRrFk+5f+GceY=", "requires": { "agent-base": "2.1.1", - "debug": "2.6.9", + "debug": "2.6.8", "extend": "3.0.1" } }, "humanize-duration": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.14.0.tgz", - "integrity": "sha512-ebqI6Aaw64T7e7bZoFeoHkvlyy7mpVDqXCPm9gLFi9S42QWD3PWQ1FMwDKJo0y8/sXcfZ9hughSadLwTfmafmw==" + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.10.1.tgz", + "integrity": "sha512-FHD+u5OKj8TSsSdMHJxSCC78N5Rt4ecil6sWvI+xPbUKhxvHmkKo/V8imbR1m2dXueZYLIl7PcSYX9i/oEiOIA==" }, "humanize-number": { "version": "0.0.2", @@ -1751,12 +1625,9 @@ "integrity": "sha1-EcCvakcWQ2M1iFiASPF5lUFInBg=" }, "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "requires": { - "safer-buffer": "2.1.2" - } + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" }, "inflation": { "version": "2.0.0", @@ -1777,38 +1648,27 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, - "int": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/int/-/int-0.1.1.tgz", - "integrity": "sha1-18efL4PP9QXTXoaYD4H6FPM4ekw=" - }, "ipcee": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/ipcee/-/ipcee-1.0.6.tgz", "integrity": "sha1-PI3I5nh9gdIkyY6POcvvCeBV5tQ=", "requires": { - "debug": "2.6.9", + "debug": "2.6.8", "eventemitter2": "2.2.2" } }, "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-my-ip-valid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==" + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", + "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=" }, "is-my-json-valid": { - "version": "2.17.2", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz", - "integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==", + "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", - "is-my-ip-valid": "1.0.0", "jsonpointer": "4.0.1", "xtend": "4.0.1" } @@ -1883,6 +1743,14 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "requires": { + "jsonify": "0.0.0" + } + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -1898,6 +1766,11 @@ "resolved": "https://registry.npmjs.org/jsonic/-/jsonic-0.3.0.tgz", "integrity": "sha1-tUXalfVDkuWLPdoF9fLjd6bJ0b8=" }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -1908,6 +1781,15 @@ "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" }, + "JSONStream": { + "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" + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -1938,33 +1820,33 @@ "integrity": "sha1-rTKXxVcGneqLz+ek+kkbdcXd65E=" }, "koa": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/koa/-/koa-1.6.0.tgz", - "integrity": "sha512-tW7xJGDG4LyhFUTtzIyqJCIaJIFgkre1tJPGNe/moRKOIU0L9vEIhW5z7iMX7FJTkYm45urdbPOGBp0VlWF03w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/koa/-/koa-1.4.0.tgz", + "integrity": "sha1-X79tkMZq4Si3hnyi5UjOh0NDbXY=", "requires": { - "accepts": "1.3.5", + "accepts": "1.3.4", "co": "4.6.0", "composition": "2.3.0", "content-disposition": "0.5.2", "content-type": "1.0.4", "cookies": "0.7.1", - "debug": "2.6.9", + "debug": "2.6.8", "delegates": "1.0.0", "destroy": "1.0.4", "error-inject": "1.0.0", "escape-html": "1.0.3", - "fresh": "0.5.2", + "fresh": "0.3.0", "http-assert": "1.3.0", - "http-errors": "1.6.3", + "http-errors": "1.6.2", "koa-compose": "2.5.1", "koa-is-json": "1.0.0", - "mime-types": "2.1.18", + "mime-types": "2.1.17", "on-finished": "2.3.0", "only": "0.0.2", "parseurl": "1.3.2", - "statuses": "1.5.0", - "type-is": "1.6.16", - "vary": "1.1.2" + "statuses": "1.3.1", + "type-is": "1.6.15", + "vary": "1.1.1" } }, "koa-bodyparser": { @@ -1972,7 +1854,7 @@ "resolved": "https://registry.npmjs.org/koa-bodyparser/-/koa-bodyparser-2.5.0.tgz", "integrity": "sha1-PrckP0eZii53LbBfbcTg9PPMvfA=", "requires": { - "co-body": "5.2.0", + "co-body": "5.1.1", "copy-to": "2.0.1" } }, @@ -2015,8 +1897,8 @@ "integrity": "sha1-Tb26fnFZU9VobAO3w/29IUYx+HA=", "requires": { "co": "4.6.0", - "debug": "2.6.9", - "http-errors": "1.6.3", + "debug": "2.6.8", + "http-errors": "1.6.2", "methods": "1.1.2", "path-to-regexp": "1.7.0" } @@ -2027,9 +1909,9 @@ "integrity": "sha1-WkriRVZGgMbs9geeknX6UXOoYdw=", "requires": { "co": "4.6.0", - "debug": "2.6.9", + "debug": "2.6.8", "mz": "2.7.0", - "resolve-path": "1.4.0" + "resolve-path": "1.3.3" } }, "koa-static": { @@ -2037,7 +1919,7 @@ "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-2.1.0.tgz", "integrity": "sha1-z+KS6n2ryWqnI+SkiGFcxlrnQWk=", "requires": { - "debug": "2.6.9", + "debug": "2.6.8", "koa-send": "3.3.0" } }, @@ -2047,14 +1929,7 @@ "integrity": "sha512-zrBbpGMS+H1EzCOMG4/8lXbEdL8pLmjx8VqSCyTGgHpvNxNzyhX2ViG8zt8PPyYA7TlxbGB1vBsn6QyUfFBnvQ==", "requires": { "got": "7.1.0", - "qs": "6.5.2" - }, - "dependencies": { - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - } + "qs": "6.4.0" } }, "lakebtc_nodejs": { @@ -2063,13 +1938,6 @@ "integrity": "sha1-lvGPr5/gnCjjxlUp1Te02051C00=", "requires": { "underscore": "1.4.4" - }, - "dependencies": { - "underscore": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", - "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=" - } } }, "limit-request-promise": { @@ -2087,7 +1955,7 @@ "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.1.1.tgz", "integrity": "sha1-JgIeT29W/Uwwn2vx69jJepWsH7U=", "requires": { - "bluebird": "3.5.1", + "bluebird": "3.5.0", "request-promise-core": "1.1.1", "stealthy-require": "1.1.1" } @@ -2099,11 +1967,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" }, - "lodash._arrayeach": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", - "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=" - }, "lodash._baseassign": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", @@ -2123,42 +1986,6 @@ "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=" }, - "lodash._baseeach": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash._baseeach/-/lodash._baseeach-3.0.4.tgz", - "integrity": "sha1-z4cGVyyhROjZ11InyZDamC+TKvM=", - "requires": { - "lodash.keys": "3.1.2" - } - }, - "lodash._baseslice": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._baseslice/-/lodash._baseslice-3.0.3.tgz", - "integrity": "sha1-qkrj3FPu1TsI3i4zYrOTV7XIfXU=" - }, - "lodash._bindcallback": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" - }, - "lodash._createassigner": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", - "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", - "requires": { - "lodash._bindcallback": "3.0.1", - "lodash._isiterateecall": "3.0.9", - "lodash.restparam": "3.6.1" - } - }, - "lodash._createwrapper": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._createwrapper/-/lodash._createwrapper-3.2.0.tgz", - "integrity": "sha1-30U+ZkFjIXuJWkVAZa8cR6DqPE0=", - "requires": { - "lodash._root": "3.0.1" - } - }, "lodash._getnative": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", @@ -2169,25 +1996,6 @@ "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" }, - "lodash._replaceholders": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._replaceholders/-/lodash._replaceholders-3.0.0.tgz", - "integrity": "sha1-iru3EmxDH37XRPe6rznwi8m9nVg=" - }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" - }, - "lodash.assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.0.0.tgz", - "integrity": "sha1-93SdFYCkEgJzo3H1SmaxTJ1yJvo=", - "requires": { - "lodash._baseassign": "3.2.0", - "lodash._createassigner": "3.1.1" - } - }, "lodash.create": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", @@ -2198,17 +2006,6 @@ "lodash._isiterateecall": "3.0.9" } }, - "lodash.foreach": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-3.0.0.tgz", - "integrity": "sha1-HbUp13oExYxS8YbxTeMucEPog6Q=", - "requires": { - "lodash._arrayeach": "3.0.0", - "lodash._baseeach": "3.0.4", - "lodash._bindcallback": "3.0.1", - "lodash.isarray": "3.0.4" - } - }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -2235,31 +2032,16 @@ "lodash.isarray": "3.0.4" } }, - "lodash.partial": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash.partial/-/lodash.partial-3.0.0.tgz", - "integrity": "sha1-H6mgMweqKDu41PFave4PL0AJCpY=", - "requires": { - "lodash._baseslice": "3.0.3", - "lodash._createwrapper": "3.2.0", - "lodash._replaceholders": "3.0.0" - } - }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" - }, "lolex": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.0.tgz", - "integrity": "sha512-uJkH2e0BVfU5KOJUevbTOtpDduooSarH5PopO+LfM/vZf8Z9sJzODqKev804JYM2i++ktJfUmC1le4LwFQ1VMg==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.1.tgz", + "integrity": "sha512-mQuW55GhduF3ppo+ZRUTz1PRjEh1hS5BbqU7d8D0ez2OKxHDod7StPPeAVKisZR5aLkHZjdGWSL42LSONUJsZw==", "dev": true }, "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=" }, "map-stream": { "version": "0.1.0", @@ -2273,7 +2055,7 @@ "requires": { "charenc": "0.0.2", "crypt": "0.0.2", - "is-buffer": "1.1.6" + "is-buffer": "1.1.5" } }, "media-typer": { @@ -2298,16 +2080,16 @@ "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=" }, "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" }, "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", "requires": { - "mime-db": "1.33.0" + "mime-db": "1.30.0" } }, "mimic-response": { @@ -2320,7 +2102,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "1.1.8" } }, "minimist": { @@ -2337,28 +2119,27 @@ } }, "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.0.tgz", + "integrity": "sha512-ukB2dF+u4aeJjc6IGtPNnJXfeby5d4ZqySlIBT0OEyva/DrMjVm5HkQxKnHDLKEfEQBsEnwTg9HHhtPHJdTd8w==", "dev": true, "requires": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", + "browser-stdout": "1.3.0", + "commander": "2.11.0", "debug": "3.1.0", - "diff": "3.5.0", + "diff": "3.3.1", "escape-string-regexp": "1.0.5", "glob": "7.1.2", - "growl": "1.10.5", + "growl": "1.10.3", "he": "1.1.1", - "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "5.4.0" + "supports-color": "4.4.0" }, "dependencies": { - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", "dev": true }, "debug": { @@ -2370,45 +2151,13 @@ "ms": "2.0.0" } }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "2.0.0" } } } @@ -2420,9 +2169,9 @@ "dev": true }, "moment": { - "version": "2.22.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.1.tgz", - "integrity": "sha512-shJkRTSebXvsVqk56I+lkb2latjBs8I+pc2TzWc545y2iFnSjm7Wg0QMh+ZWcdSLQyGEau5jI8ocnmkyTgr9YQ==" + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", + "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==" }, "ms": { "version": "2.0.0", @@ -2445,9 +2194,9 @@ } }, "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", + "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=" }, "negotiator": { "version": "0.6.1", @@ -2455,82 +2204,45 @@ "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, "nise": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.3.3.tgz", - "integrity": "sha512-v1J/FLUB9PfGqZLGDBhQqODkbLotP0WtLo9R4EJY2PPu5f5Xg4o0rA8FDlmrjFSv9vBBKcfnOSpfYYuu5RTHqg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.2.0.tgz", + "integrity": "sha512-q9jXh3UNsMV28KeqI43ILz5+c3l+RiNW8mhurEwCKckuHQbL+hTJIKKTiUlCPKlgQ/OukFvSnKB/Jk3+sFbkGA==", "dev": true, "requires": { - "@sinonjs/formatio": "2.0.0", + "formatio": "1.2.0", "just-extend": "1.1.27", - "lolex": "2.7.0", + "lolex": "1.6.0", "path-to-regexp": "1.7.0", "text-encoding": "0.6.4" - } - }, - "nock": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/nock/-/nock-3.6.0.tgz", - "integrity": "sha1-0mxAAEs0SaZVuRt0rjxW/ALIRSU=", - "requires": { - "chai": "3.5.0", - "debug": "2.6.9", - "deep-equal": "1.0.1", - "json-stringify-safe": "5.0.1", - "lodash": "2.4.1", - "mkdirp": "0.5.1", - "propagate": "0.3.1" }, "dependencies": { - "chai": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", - "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", - "requires": { - "assertion-error": "1.1.0", - "deep-eql": "0.1.3", - "type-detect": "1.0.0" - } - }, - "deep-eql": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", - "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", - "requires": { - "type-detect": "0.1.1" - }, - "dependencies": { - "type-detect": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", - "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=" - } - } - }, - "lodash": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz", - "integrity": "sha1-W3cjA03aTSYuWkb7LFjXzCL3FCA=" - }, - "type-detect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", - "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=" + "lolex": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", + "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", + "dev": true } } }, - "node-uuid": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", - "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" - }, "node-wex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/node-wex/-/node-wex-1.0.4.tgz", - "integrity": "sha512-whE6tDpqbg9X0txHffIkGTbnxs4+ZxnyWUxDfRlxxT9UFQuktE7/l4dSAZgAzfF/AsU3YuBYeeNKaD6FBPmVHQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/node-wex/-/node-wex-1.0.3.tgz", + "integrity": "sha512-iZtQOltQLOV4NuPxm2K5XDMjILehikE0hYDG2CHYWloddkSvzUnKrQP4fzb+RNUpeWSnCO9CHS7PjmEFOFG2xQ==", "requires": { "request": "2.83.0" }, "dependencies": { + "ajv": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.3.tgz", + "integrity": "sha1-wG9Zh3jETGsWGrr+NGa4GtGBTtI=", + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "json-schema-traverse": "0.3.1", + "json-stable-stringify": "1.0.1" + } + }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -2546,14 +2258,9 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", "requires": { - "hoek": "4.2.1" + "hoek": "4.2.0" } }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, "cryptiles": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", @@ -2567,27 +2274,32 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", "requires": { - "hoek": "4.2.1" + "hoek": "4.2.0" } } } }, "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", + "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", "requires": { "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" + "combined-stream": "1.0.5", + "mime-types": "2.1.17" } }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, "har-validator": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "requires": { - "ajv": "5.5.2", + "ajv": "5.2.3", "har-schema": "2.0.0" } }, @@ -2598,14 +2310,14 @@ "requires": { "boom": "4.3.1", "cryptiles": "3.1.2", - "hoek": "4.2.1", - "sntp": "2.1.0" + "hoek": "4.2.0", + "sntp": "2.0.2" } }, "hoek": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", - "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", + "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" }, "http-signature": { "version": "1.2.0", @@ -2614,13 +2326,18 @@ "requires": { "assert-plus": "1.0.0", "jsprim": "1.4.1", - "sshpk": "1.14.1" + "sshpk": "1.13.1" } }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" }, "request": { "version": "2.83.0", @@ -2628,49 +2345,44 @@ "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", "requires": { "aws-sign2": "0.7.0", - "aws4": "1.7.0", + "aws4": "1.6.0", "caseless": "0.12.0", - "combined-stream": "1.0.6", + "combined-stream": "1.0.5", "extend": "3.0.1", "forever-agent": "0.6.1", - "form-data": "2.3.2", + "form-data": "2.3.1", "har-validator": "5.0.3", "hawk": "6.0.2", "http-signature": "1.2.0", "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", + "mime-types": "2.1.17", "oauth-sign": "0.8.2", "performance-now": "2.1.0", - "qs": "6.5.2", - "safe-buffer": "5.1.2", - "stringstream": "0.0.6", - "tough-cookie": "2.3.4", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", "tunnel-agent": "0.6.0", - "uuid": "3.2.1" + "uuid": "3.1.0" } }, "sntp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.0.2.tgz", + "integrity": "sha1-UGQRDwr4X3z9t9a2ekACjOUrSys=", "requires": { - "hoek": "4.2.1" + "hoek": "4.2.0" } }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", "requires": { - "safe-buffer": "5.1.2" + "punycode": "1.4.1" } - }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" } } }, @@ -2679,10 +2391,10 @@ "resolved": "https://registry.npmjs.org/node.bittrex.api/-/node.bittrex.api-0.4.4.tgz", "integrity": "sha512-zNrwiSufttRBfPeSJfQLRDd9AHQuAL2IVxJEdEtNvwqvqHsdRvPkiQfANOzPy+0jFM/J8/t6/+gJ8Df+0GkgiQ==", "requires": { - "JSONStream": "1.3.3", "event-stream": "3.3.4", "jsonic": "0.3.0", - "request": "2.87.0", + "JSONStream": "1.3.1", + "request": "2.83.0", "signalr-client": "0.0.17" } }, @@ -2707,14 +2419,6 @@ "boolbase": "1.0.0" } }, - "num": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/num/-/num-0.2.1.tgz", - "integrity": "sha1-Agqy79KldZ5VA5HivFoEwnifWNo=", - "requires": { - "int": "0.1.1" - } - }, "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", @@ -2737,10 +2441,10 @@ "verror": "1.6.1" }, "dependencies": { - "crypto": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/crypto/-/crypto-0.0.3.tgz", - "integrity": "sha1-RwqBuGvkxe4XrMggeh9TFa4g27A=" + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" }, "extsprintf": { "version": "1.2.0", @@ -2753,10 +2457,26 @@ "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", "requires": { "async": "2.1.2", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" + "combined-stream": "1.0.5", + "mime-types": "2.1.17" } }, + "har-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "requires": { + "chalk": "1.1.3", + "commander": "2.13.0", + "is-my-json-valid": "2.16.1", + "pinkie-promise": "2.0.1" + } + }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, "qs": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.1.tgz", @@ -2770,7 +2490,7 @@ "aws-sign2": "0.6.0", "bl": "1.0.3", "caseless": "0.11.0", - "combined-stream": "1.0.6", + "combined-stream": "1.0.5", "extend": "3.0.1", "forever-agent": "0.6.1", "form-data": "1.0.1", @@ -2780,11 +2500,11 @@ "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", + "mime-types": "2.1.17", "node-uuid": "1.4.8", "oauth-sign": "0.8.2", "qs": "5.2.1", - "stringstream": "0.0.6", + "stringstream": "0.0.5", "tough-cookie": "2.2.2", "tunnel-agent": "0.4.3" } @@ -2794,290 +2514,139 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz", "integrity": "sha1-yDoYMPTl7wuT7yo0iOck+N4Basc=" }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" + }, "underscore": { "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" - }, - "verror": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.6.1.tgz", - "integrity": "sha1-I2QCBgZIwhnRFiwkUdHDQaDhyc4=", - "requires": { - "core-util-is": "1.0.2", - "extsprintf": "1.2.0" - } - } - } - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1.0.2" - } - }, - "only": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", - "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=" - }, - "opn": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/opn/-/opn-4.0.2.tgz", - "integrity": "sha1-erwi5kTf9jsKltWrfyeQwPAavJU=", - "requires": { - "object-assign": "4.1.1", - "pinkie-promise": "2.0.1" - } - }, - "options": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", - "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" - }, - "p-cancelable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", - "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==" - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-timeout": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", - "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", - "requires": { - "p-finally": "1.0.0" - } - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" - }, - "passthrough-counter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/passthrough-counter/-/passthrough-counter-1.0.0.tgz", - "integrity": "sha1-GWfZ5m2lcrXAI8eH2xEqOHqxZvo=" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", - "requires": { - "isarray": "0.0.1" - } - }, - "pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=" - }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "requires": { - "through": "2.3.8" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "2.0.4" - } - }, - "pkginfo": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.1.tgz", - "integrity": "sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=" - }, - "poloniex.js": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/poloniex.js/-/poloniex.js-0.0.7.tgz", - "integrity": "sha1-B0crcBZtztjjaI0eqI7+3ZBrKeU=", - "requires": { - "nonce": "1.0.4", - "request": "2.33.0" - }, - "dependencies": { - "asn1": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", - "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", - "optional": true - }, - "assert-plus": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", - "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", - "optional": true - }, - "async": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", - "optional": true - }, - "aws-sign2": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", - "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", - "optional": true - }, - "boom": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz", - "integrity": "sha1-emNune1O/O+xnO9JR6PGffrukRs=", - "requires": { - "hoek": "0.9.1" - } - }, - "combined-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", - "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", - "optional": true, - "requires": { - "delayed-stream": "0.0.5" - } - }, - "cryptiles": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz", - "integrity": "sha1-7ZH/HxetE9N0gohZT4pIoNJvMlw=", - "optional": true, - "requires": { - "boom": "0.4.2" - } - }, - "delayed-stream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", - "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=", - "optional": true - }, - "forever-agent": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz", - "integrity": "sha1-bQ4JxJIflKJ/Y9O0nF/v8epMUTA=" - }, - "form-data": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", - "integrity": "sha1-kavXiKupcCsaq/qLwBAxoqyeOxI=", - "optional": true, - "requires": { - "async": "0.9.2", - "combined-stream": "0.0.7", - "mime": "1.2.11" - } - }, - "hawk": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.0.0.tgz", - "integrity": "sha1-uQuxaYByhUEdp//LjdJZhQLTtS0=", - "optional": true, - "requires": { - "boom": "0.4.2", - "cryptiles": "0.2.2", - "hoek": "0.9.1", - "sntp": "0.2.4" - } - }, - "hoek": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", - "integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=" - }, - "http-signature": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", - "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", - "optional": true, - "requires": { - "asn1": "0.1.11", - "assert-plus": "0.1.5", - "ctype": "0.5.3" - } - }, - "oauth-sign": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz", - "integrity": "sha1-y1QPk7srIqfVlBaRoojWDo6pOG4=", - "optional": true - }, - "qs": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/qs/-/qs-0.6.6.tgz", - "integrity": "sha1-bgFQmP9RlouKPIGQAdXyyJvEsQc=" - }, - "request": { - "version": "2.33.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.33.0.tgz", - "integrity": "sha1-UWeHgTFyYHDsYzdS6iMKI3ncZf8=", - "requires": { - "aws-sign2": "0.5.0", - "forever-agent": "0.5.2", - "form-data": "0.1.4", - "hawk": "1.0.0", - "http-signature": "0.10.1", - "json-stringify-safe": "5.0.1", - "mime": "1.2.11", - "node-uuid": "1.4.8", - "oauth-sign": "0.3.0", - "qs": "0.6.6", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.3.0" - } - }, - "sntp": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz", - "integrity": "sha1-+4hfGLDzqtGJ+CSGJTa87ux1CQA=", - "optional": true, + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" + }, + "verror": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.6.1.tgz", + "integrity": "sha1-I2QCBgZIwhnRFiwkUdHDQaDhyc4=", "requires": { - "hoek": "0.9.1" + "core-util-is": "1.0.2", + "extsprintf": "1.2.0" } - }, - "tunnel-agent": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.3.0.tgz", - "integrity": "sha1-rWgbaPUyGtKCfEz7G31d8s/pQu4=", - "optional": true } } }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=" + }, + "opn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/opn/-/opn-4.0.2.tgz", + "integrity": "sha1-erwi5kTf9jsKltWrfyeQwPAavJU=", + "requires": { + "object-assign": "4.1.1", + "pinkie-promise": "2.0.1" + } + }, + "options": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" + }, + "p-cancelable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", + "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==" + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-timeout": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.0.tgz", + "integrity": "sha1-mCD5lDTFgXhotPNICe5SkWYNW2w=", + "requires": { + "p-finally": "1.0.0" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "passthrough-counter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passthrough-counter/-/passthrough-counter-1.0.0.tgz", + "integrity": "sha1-GWfZ5m2lcrXAI8eH2xEqOHqxZvo=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "requires": { + "isarray": "0.0.1" + } + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=" + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "requires": { + "through": "2.3.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "2.0.4" + } + }, + "pkginfo": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.1.tgz", + "integrity": "sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=" + }, "prepend-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", @@ -3120,11 +2689,6 @@ } } }, - "propagate": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-0.3.1.tgz", - "integrity": "sha1-46hEBKfs6CDda76p9tkk4xNa4Jw=" - }, "proxyquire": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-1.8.0.tgz", @@ -3148,7 +2712,7 @@ "requires": { "mime": "1.2.11", "request": "2.44.0", - "websocket": "1.0.26" + "websocket": "1.0.24" }, "dependencies": { "asn1": { @@ -3269,6 +2833,11 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=" }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, "oauth-sign": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.4.0.tgz", @@ -3308,8 +2877,8 @@ "node-uuid": "1.4.8", "oauth-sign": "0.4.0", "qs": "1.2.2", - "stringstream": "0.0.6", - "tough-cookie": "2.3.4", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", "tunnel-agent": "0.4.3" } }, @@ -3321,20 +2890,25 @@ "requires": { "hoek": "0.9.1" } + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" } } }, "qs": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", - "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=" + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" }, "quadrigacx": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/quadrigacx/-/quadrigacx-0.0.7.tgz", "integrity": "sha1-vptrBG28vDpNqRbBpQtJ2kiulsQ=", "requires": { - "request": "2.87.0" + "request": "2.83.0" } }, "querystring": { @@ -3343,13 +2917,13 @@ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, "raw-body": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", "requires": { "bytes": "3.0.0", - "http-errors": "1.6.3", - "iconv-lite": "0.4.23", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", "unpipe": "1.0.0" } }, @@ -3372,48 +2946,52 @@ "string_decoder": "0.10.31" } }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - }, "relieve": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/relieve/-/relieve-2.2.1.tgz", "integrity": "sha1-7PG6kC2B0yaGef8StamGlunncXY=", "requires": { - "bluebird": "3.5.1", - "debug": "2.6.9", + "bluebird": "3.5.0", + "debug": "2.6.8", "eventemitter2": "2.2.2", "ipcee": "1.0.6", "uuid": "2.0.3" + }, + "dependencies": { + "uuid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" + } } }, "request": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", - "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "version": "2.83.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", + "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", "requires": { "aws-sign2": "0.7.0", - "aws4": "1.7.0", + "aws4": "1.6.0", "caseless": "0.12.0", - "combined-stream": "1.0.6", + "combined-stream": "1.0.5", "extend": "3.0.1", "forever-agent": "0.6.1", - "form-data": "2.3.2", + "form-data": "2.3.1", "har-validator": "5.0.3", + "hawk": "6.0.2", "http-signature": "1.2.0", "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", + "mime-types": "2.1.17", "oauth-sign": "0.8.2", "performance-now": "2.1.0", - "qs": "6.5.2", - "safe-buffer": "5.1.2", - "tough-cookie": "2.3.4", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", "tunnel-agent": "0.6.0", - "uuid": "3.2.1" + "uuid": "3.1.0" }, "dependencies": { "assert-plus": { @@ -3426,30 +3004,48 @@ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "requires": { + "hoek": "4.2.0" + } }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "requires": { + "hoek": "4.2.0" + } + } } }, - "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.0", + "sntp": "2.1.0" } }, + "hoek": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", + "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -3457,26 +3053,29 @@ "requires": { "assert-plus": "1.0.0", "jsprim": "1.4.1", - "sshpk": "1.14.1" + "sshpk": "1.13.1" } }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", "requires": { - "safe-buffer": "5.1.2" + "hoek": "4.2.0" } }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + "tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "requires": { + "punycode": "1.4.1" + } } } }, @@ -3489,11 +3088,23 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.2.tgz", "integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=", + "dev": true, "requires": { - "bluebird": "3.5.1", + "bluebird": "3.5.0", "request-promise-core": "1.1.1", "stealthy-require": "1.1.1", - "tough-cookie": "2.3.4" + "tough-cookie": "2.3.3" + }, + "dependencies": { + "tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "dev": true, + "requires": { + "punycode": "1.4.1" + } + } } }, "request-promise-core": { @@ -3501,13 +3112,13 @@ "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", "requires": { - "lodash": "4.17.10" + "lodash": "4.17.4" }, "dependencies": { "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" } } }, @@ -3518,12 +3129,29 @@ "dev": true }, "resolve-path": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", - "integrity": "sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc=", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.3.3.tgz", + "integrity": "sha1-TYOrpkaMK45jKldeP1Kw+g2+Glw=", "requires": { - "http-errors": "1.6.3", + "http-errors": "1.5.1", "path-is-absolute": "1.0.1" + }, + "dependencies": { + "http-errors": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.5.1.tgz", + "integrity": "sha1-eIwNLB3iyBuebowBhDtrl+uSB1A=", + "requires": { + "inherits": "2.0.3", + "setprototypeof": "1.0.2", + "statuses": "1.3.1" + } + }, + "setprototypeof": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.2.tgz", + "integrity": "sha1-gaVSFB7BBLiOic44MQOtXGZWTQg=" + } } }, "retry": { @@ -3537,14 +3165,9 @@ "integrity": "sha1-/s5hv6DBtSoga9axgZgYS91SOjs=" }, "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, "samsam": { "version": "1.3.0", @@ -3558,47 +3181,47 @@ "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" }, "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" }, "signalr-client": { "version": "0.0.17", "resolved": "https://registry.npmjs.org/signalr-client/-/signalr-client-0.0.17.tgz", "integrity": "sha1-pSF383ziSOzIcibdEDxB/3DIKbE=", "requires": { - "websocket": "1.0.26" + "websocket": "1.0.24" } }, "sinon": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.5.0.tgz", - "integrity": "sha512-trdx+mB0VBBgoYucy6a9L7/jfQOmvGeaKZT4OOJ+lPAtI8623xyGr8wLiE4eojzBS8G9yXbhx42GHUOVLr4X2w==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.2.0.tgz", + "integrity": "sha512-FAdCcQ6lUAakWQMVRSIhiQU90d5EH1k3V6wRPrjxcYsv4vlBHjFzWLeoD63GoTKrFkfzVQs209aFW8V3cGLNtA==", "dev": true, "requires": { - "@sinonjs/formatio": "2.0.0", - "diff": "3.2.0", + "diff": "3.3.1", + "formatio": "1.2.0", "lodash.get": "4.4.2", - "lolex": "2.7.0", - "nise": "1.3.3", - "supports-color": "5.4.0", - "type-detect": "4.0.8" + "lolex": "2.3.1", + "nise": "1.2.0", + "supports-color": "5.1.0", + "type-detect": "4.0.7" }, "dependencies": { - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", + "integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "2.0.0" } + }, + "type-detect": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.7.tgz", + "integrity": "sha512-4Rh17pAMVdMWzktddFhISRnUnFIStObtUMNGzDwlA6w/77bmGv3aBbRdCmQR6IjzfkTo9otnW+2K/cDRhKSxDA==", + "dev": true } } }, @@ -4021,11 +3644,6 @@ "version": "2.0.0", "bundled": true }, - "nan": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", - "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=" - }, "node-pre-gyp": { "version": "0.6.38", "bundled": true, @@ -4222,6 +3840,14 @@ } } }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + }, "string-width": { "version": "1.0.2", "bundled": true, @@ -4231,13 +3857,6 @@ "strip-ansi": "3.0.1" } }, - "string_decoder": { - "version": "1.0.3", - "bundled": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, "stringstream": { "version": "0.0.5", "bundled": true @@ -4336,9 +3955,9 @@ } }, "sshpk": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", - "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", "requires": { "asn1": "0.2.3", "assert-plus": "1.0.0", @@ -4363,17 +3982,17 @@ "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" }, "stats-lite": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/stats-lite/-/stats-lite-2.1.1.tgz", - "integrity": "sha512-5QkxGCWGMbeQ+PXqI2N7ES6kW4IimvbMQBCKvZbekaEpf3InckVHiIXdCJbZsKUjLE7a3jha2cTEJqtOGGcVMw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/stats-lite/-/stats-lite-2.1.0.tgz", + "integrity": "sha1-R2hU/biNA1xJvLv/cEyNhe6Esbo=", "requires": { "isnumber": "1.0.0" } }, "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" }, "stealthy-require": { "version": "1.1.1", @@ -4394,9 +4013,9 @@ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, "stringstream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", - "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==" + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" }, "strip-ansi": { "version": "3.0.1", @@ -4459,17 +4078,20 @@ "integrity": "sha512-O7L5hhSQHxuufWUdcTRPfuTh3phKfAZ/dqfxZFoxPCj2RYmpaSGLEIs016FCXItQwNr08yefUB5TSjzRYnajTA==" }, "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", "requires": { "punycode": "1.4.1" } }, "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.1" + } }, "tweetnacl": { "version": "0.14.5", @@ -4482,28 +4104,23 @@ "resolved": "https://registry.npmjs.org/twitter/-/twitter-1.7.1.tgz", "integrity": "sha1-B2I3jx3BwFDkj2ZqypBOJLGpYvQ=", "requires": { - "deep-extend": "0.5.1", - "request": "2.87.0" + "deep-extend": "0.5.0", + "request": "2.83.0" } }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" - }, "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", "requires": { "media-typer": "0.3.0", - "mime-types": "2.1.18" + "mime-types": "2.1.17" } }, "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.2.tgz", + "integrity": "sha1-EBezLZhP9VbroQD1AViauhrOLgQ=", "requires": { "is-typedarray": "1.0.0" } @@ -4514,9 +4131,9 @@ "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=" }, "underscore": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.0.tgz", - "integrity": "sha512-4IV1DSSxC1QK48j9ONFK1MoIAKKkbE8i7u55w2R6IqBqbT7A/iG7aZBCR2Bi8piF0Uz+i/MG1aeqLwl/5vqF+A==" + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", + "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=" }, "unpipe": { "version": "1.0.0", @@ -4547,14 +4164,14 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", - "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" }, "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", + "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" }, "verror": { "version": "1.10.0", @@ -4574,20 +4191,20 @@ } }, "websocket": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.26.tgz", - "integrity": "sha512-fjcrYDPIQxpTnqFQ9JjxUQcdvR89MFAOjPBlF+vjOt49w/XW4fJknUoMz/mDIn2eK1AdslVojcaOxOqyZZV8rw==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.24.tgz", + "integrity": "sha1-dJA+dfJUW2suHeFCW8HJBZF6GJA=", "requires": { - "debug": "2.6.9", - "nan": "2.10.0", - "typedarray-to-buffer": "3.1.5", + "debug": "2.6.8", + "nan": "2.7.0", + "typedarray-to-buffer": "3.1.2", "yaeti": "0.0.6" } }, "winston": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.2.tgz", - "integrity": "sha512-4S/Ad4ZfSNl8OccCLxnJmNISWcm2joa6Q0YGDxlxMzH0fgSwWsjMt+SmlNwCqdpaPg3ev1HKkMBsIiXeSUwpbA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.3.1.tgz", + "integrity": "sha1-C0hCDZeMAYBM8CMLZIhhWYIloRk=", "requires": { "async": "1.0.0", "colors": "1.0.3", @@ -4610,9 +4227,9 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", - "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.4.tgz", + "integrity": "sha1-V/QNA2gy5fUFVmKjl8Tedu1mv2E=", "requires": { "options": "0.0.6", "ultron": "1.0.2" @@ -4629,16 +4246,16 @@ "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" }, "zaif.jp": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/zaif.jp/-/zaif.jp-0.1.16.tgz", - "integrity": "sha512-rQ7gbB260Q2hPMbOerkGq4dhBEp9w0lrzF6i3aKbN3b9qZSrOct9x/aj/rr6bH2udgxs5dtz0HOm+ZEmYdFU+A==", + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/zaif.jp/-/zaif.jp-0.1.15.tgz", + "integrity": "sha1-ai7bpNI8JyS2xBMeNAZr4EwoSXo=", "requires": { "@you21979/http-api-error": "0.0.2", "@you21979/object-util": "0.0.1", "@you21979/simple-verify": "0.0.2", "limit-request-promise": "0.1.2", - "request": "2.87.0", - "ws": "1.1.5" + "request": "2.83.0", + "ws": "1.1.4" } } } diff --git a/package.json b/package.json index 7669cbcd8..3a248cf7c 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "dependencies": { "@slack/client": "^3.10.0", "async": "2.1.2", - "binance": "^1.3.1", "bitcoin-co-id-update": "0.0.2", "bitexthai": "^0.1.0", "bitfinex-api-node": "^1.2.0", @@ -27,10 +26,8 @@ "btc-markets": "0.0.10", "cexio": "0.0.x", "co-fs": "^1.2.0", - "coinfalcon": "^1.0.3", "coingi": "^1.0.7", "commander": "^2.13.0", - "gdax": "^0.4.2", "gekko": "0.0.9", "gemini-exchange-coffee-api": "2.0.6", "humanize-duration": "^3.10.0", @@ -48,7 +45,6 @@ "node.bittrex.api": "^0.4.3", "okcoin-china": "0.0.7", "opn": "^4.0.2", - "poloniex.js": "0.0.7", "promisify-node": "^0.4.0", "prompt-lite": "0.1.1", "pushbullet": "1.4.3", diff --git a/plugins/trader/portfolio.js b/plugins/trader/portfolio.js deleted file mode 100644 index 801007fda..000000000 --- a/plugins/trader/portfolio.js +++ /dev/null @@ -1,113 +0,0 @@ -/* - The Portfolio class holds the most recent data about the portfolio and ticker -*/ - -var _ = require('lodash') -var util = require('../../core/util') -var dirs = util.dirs() -var events = require('events') -var log = require(dirs.core + 'log') -var async = require('async') - -class Portfolio{ - constructor(conf,exchange){ - _.bindAll(this) - this.conf = conf - this.exchange = exchange - this.portfolio = {} - this.fee = null - this.ticker = null - } - - getBalance(fund) { - return this.getFund(fund).amount; - } - - // return the [fund] based on the data we have in memory - getFund(fund) { - return _.find(this.portfolio, function(f) { return f.name === fund}); - } - - // convert into the portfolio expected by the performanceAnalyzer - convertPortfolio(asset,currency) { // rename? - var asset = _.find(this.portfolio, a => a.name === this.conf.asset).amount; - var currency = _.find(this.portfolio, a => a.name === this.conf.currency).amount; - - return { - currency, - asset, - balance: currency + (asset * this.ticker.bid) - } - } - - logPortfolio() { - log.info(this.exchange.name, 'portfolio:'); - _.each(this.portfolio, function(fund) { - log.info('\t', fund.name + ':', parseFloat(fund.amount).toFixed(12)); - }); - }; - - setPortfolio(callback) { - let set = (err, fullPortfolio) => { - if(err) - util.die(err); - - // only include the currency/asset of this market - const portfolio = [ this.conf.currency, this.conf.asset ] - .map(name => { - let item = _.find(fullPortfolio, {name}); - - if(!item) { - log.debug(`unable to find "${name}" in portfolio provided by exchange, assuming 0.`); - item = {name, amount: 0}; - } - - return item; - }); - - if(_.isEmpty(this.portfolio)) { - this.portfolio = portfolio; - this.emit('portfolioUpdate', this.convertPortfolio(this.conf.asset,this.conf.currency,this.ticker.bid)); - } - this.portfolio = portfolio; - - - if(_.isFunction(callback)) - callback(); - - } - - this.exchange.getPortfolio(set); - } - - setFee(callback) { - let set = (err, fee) => { - this.fee = fee; - - if(err) - util.die(err); - - if(_.isFunction(callback)) - callback(); - } - this.exchange.getFee(set); - } - - setTicker(callback) { - let set = (err, ticker) => { - this.ticker = ticker; - - if(err) - util.die(err); - - if(_.isFunction(callback)) - callback(); - } - this.exchange.getTicker(set); - } - -} - -util.makeEventEmitter(Portfolio) - -module.exports = Portfolio diff --git a/plugins/trader/portfolioManager.js b/plugins/trader/portfolioManager.js deleted file mode 100644 index 332cab79e..000000000 --- a/plugins/trader/portfolioManager.js +++ /dev/null @@ -1,109 +0,0 @@ -/* - The portfolio manager is responsible for making sure that - all decisions are turned into Trades. -*/ - -var _ = require('lodash'); -var util = require('../../core/util'); -var dirs = util.dirs(); -var events = require('events'); -var log = require(dirs.core + 'log'); -var async = require('async'); -var checker = require(dirs.core + 'exchangeChecker.js'); -var moment = require('moment'); -var Portfolio = require('./portfolio'); -var Trade = require('./trade'); - -var Manager = function(conf) { - this.conf = conf; - - var error = checker.cantTrade(conf); - if(error) - util.die(error); - - // create an exchange - let exchangeMeta = checker.settings(conf); - var Exchange = require(dirs.exchanges + exchangeMeta.slug); - this.exchange = new Exchange(conf); - - // create a portfolio - this.portfolio = new Portfolio(conf,this.exchange); - - //setup event relay - this.portfolio.on('portfolioUpdate', portfolioUpdate => { - this.emit('portfolioUpdate', portfolioUpdate); - }); - - // contains instantiated trade classes - this.currentTrade = false - this.tradeHistory = []; - -}; - -// teach our trader events -util.makeEventEmitter(Manager); - -Manager.prototype.init = function(callback) { - log.debug('portfolioManager : getting balance & fee from', this.exchange.name); - - let prepare = () => { - log.info('trading at', this.exchange.name, 'ACTIVE'); - log.info(this.exchange.name, 'trading fee will be:', this.portfolio.fee * 10000 + '%'); // Move fee into Exchange class? - this.portfolio.logPortfolio(); - callback(); - } - - async.series([ - this.portfolio.setFee.bind(this.portfolio), - this.portfolio.setTicker.bind(this.portfolio), - this.portfolio.setPortfolio.bind(this.portfolio) - ], prepare); -} - -Manager.prototype.trade = function(what) { - - let makeNewTrade = () => { - this.newTrade(what) - } - - // if an active trade is currently happening - if(this.currentTrade && this.currentTrade.isActive){ - if(this.currentTrade.action !== what){ - // if the action is different, stop the current trade, then start a new one - this.currentTrade.deactivate(makeNewTrade) - } else{ - // do nothing, the trade is already going - } - } else { - makeNewTrade() - } -}; - -// instantiate a new trade object -Manager.prototype.newTrade = function(what) { - log.debug("portfolioManager : newTrade() : creating a new Trade class to ", what, this.conf.asset, "/", this.conf.currency) - - // push the current (asummed to be inactive) trade to the history - if(this.currentTrade){ - this.tradeHistory.push(this.currentTrade) - } - - this.currentTrade = new Trade({ - action: what, - exchange:this.exchange, - currency: this.conf.currency, - asset: this.conf.asset, - portfolio: this.portfolio, - orderUpdateDelay: this.conf.orderUpdateDelay, - keepAsset: (this.conf.keepAsset) ? this.conf.keepAsset : false - }) - - //setup event relay - this.currentTrade.on('trade', trade => { - this.emit('trade', trade); - }); - - return this.currentTrade; -}; - -module.exports = Manager;