From dbb292a30a65e986a6d4cfd5caa0adbcb8aec1dc Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Thu, 1 Feb 2018 17:05:53 +0100 Subject: [PATCH 01/57] add dedicated events page --- docs/extending/add_a_plugin.md | 57 +--------------------------------- docs/internals/architecture.md | 6 ++-- 2 files changed, 4 insertions(+), 59 deletions(-) diff --git a/docs/extending/add_a_plugin.md b/docs/extending/add_a_plugin.md index 5bf1293c8..05760494f 100644 --- a/docs/extending/add_a_plugin.md +++ b/docs/extending/add_a_plugin.md @@ -21,66 +21,11 @@ Note that in order to use custom plugins, you have to run Gekko over [the comman *And more! Take a look in the `gekko/plugins folder.`* -## What kind of events can I listen to? - -Note that these are low level internal events to the plugin system, they have overlap with the websocket events being streamed to the UI but are not the same. - -- `candle`: Every time Gekko calculated a new 1 minute candle from the market. -- `advice`: Every time the trading strategy has new advice. -- `trade`: Every time a trading plugin (either the live trader or the paper trader) has completed a trade. -- `portfolioUpdate`: Is broadcasted once a trading plugin has an updated portflio. - -Each of these events contains a javascript object describing the latest data. - ## Implementing a new plugin If you want to add your own plugin you need to expose a constructor function inside `plugins/[slugname of plugin].js`. The object needs methods based on which event you want -to listen to: - -- market feed / candle: `processCandle(candle, callback)`. - This method will be fed a minute candles like: - - { - start: [moment object of the start time of the candle], - open: [number, open of candle], - high: [number, high of candle], - low: [number, low of candle], - close: [number, close of candle], - vwp: [number, average weighted price of candle], - volume: [number, total volume volume], - trades: [number, amount of trades] - } - - As well as a callback method. You are required to call this method - once you are done processing the candle. - -- advice feed / advice `processAdvice(advice)`: - This method will be fed an advice like: - - { - recommendation: [position to take, either long or short], - portfolio: [amount of portfolio you should move to position] **DECREPATED** - } - -- trading feed / trade `processTrade(trade)`: - This method will be fed a trade like: - - { - action: [either "buy" or "sell"], - price: [number, price that was sold at], - date: [moment object, exchange time trade completed at], - portfolio: [object containing amount in currency and asset], - balance: [number, total worth of portfolio] - } - -- trading feed / portfolioUpdate `portfolioUpdate(portfolio)`: - This method will be fed an portfolioUpdate like: - - { - currency: [number, portfolio amount of currency], - asset: [number, portfolio amount of asset] - } +to listen to. All events can be found in [the events page](../architecture/events.md). You also need to add an entry for your plugin inside `plugins.js` which registers your plugin for use with Gekko. Finally you need to add a configuration object to `sample-config.js` with at least: diff --git a/docs/internals/architecture.md b/docs/internals/architecture.md index 2f20bf773..f831b31aa 100644 --- a/docs/internals/architecture.md +++ b/docs/internals/architecture.md @@ -11,7 +11,7 @@ Communicaton between those two components is handled by Node.JS' [Stream API](ht ## A Market -All markets in Gekko eventually output `candle` data. Where these candles come from and how old they are does not matter to the GekkoStream they get piped into. On default Gekko looks for markets in the [`core/markets/` directory](https://github.com/askmike/gekko/tree/stable/core/markets) (but changing that is [not too hard](https://github.com/askmike/gekko/blob/72a858339afb5a856179c716ec4ea13070a6c87c/gekko.js#L48-L49)). The top orange block in the picture is a BudFox market (the default). +All markets in Gekko eventually output `candle` data. Where these candles come from and how old they are does not matter to the GekkoStream they get piped into. On default Gekko looks for markets in the [`core/markets/` directory](https://github.com/askmike/gekko/tree/stable/core/markets). The top orange block in the picture is a BudFox market (the default semi-realtime market that gets live data from exchanges). Example Markets that come included with Gekko are: @@ -23,7 +23,7 @@ Example Markets that come included with Gekko are: A GekkoStream is nothing more than a collection of [plugins](../commandline/plugins.md). Plugins are simple modules that can subscribe to events, and do something based on event data. The most basic event every GekkoStream has is the "candle" event, this event comes from the market. -However **plugins are allowed to broadcast their own events, which other plugins can subscribe to**. An example of this is the `tradingAdvisor` plugin. This plugin will implement a [trading method](https://github.com/askmike/gekko/blob/stable/docs/Trading_methods.md) that will be fed candle data. As soon as the trading method suggests to take a certain position in the market ("I detect an uptrend, I advice to go **long**") it will broadcast an `advice` event. The `paperTrader` is a plugin that simulates trading using these advices, the `trader` is a plugin that creates real market orders based on these advices. You can decide to only turn the `paperTrader` on or to turn the `trader` on (you now have a live trading bot). +However **plugins are allowed to broadcast their own events, which other plugins can subscribe to**. An example of this is the `tradingAdvisor` plugin. This plugin will implement a [strategy](../strategies/example_strategies) that will be fed candle data. As soon as the strategy suggests to take a certain position in the market ("I detect an uptrend, I advice to go **long**") it will broadcast an `advice` event. The `paperTrader` is a plugin that simulates trading using these advices, the `trader` is a plugin that creates real market orders based on these advices. You can decide to only turn the `paperTrader` on or to turn the `trader` on (you now have a live trading bot). When you run a backtest using Gekko the following things happen: @@ -35,7 +35,7 @@ When you run a backtest using Gekko the following things happen: ## Plugins & Adapters -Those two core components describe the majority of Gekko's flow. A lot "core functionality" like saving candles to disk are simply plugins that push all candles to a database adapter. +Those two core components describe the majority of Gekko's flow. A lot "core functionality" like saving candles to disk are simply plugins that push all candles to a database. ## Seperated architecture From 95c8ffbf29e287bfef6f80585983a9cbda3e8e2d Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Thu, 1 Feb 2018 20:03:20 +0100 Subject: [PATCH 02/57] add events page --- docs/internals/events.md | 71 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 docs/internals/events.md diff --git a/docs/internals/events.md b/docs/internals/events.md new file mode 100644 index 000000000..5653e2598 --- /dev/null +++ b/docs/internals/events.md @@ -0,0 +1,71 @@ +# Events + +As described in the [architecture](./architecture.md) events play a key role in the complete system: they relay all information between seperate components. This makes the codebase scalable, testable and it seperates concerns. + +Events are not just relayed between core components in a GekkoStream, if you run the Gekko UI they are also broadcasted (via the UI server) over the websocket to the web UI. This means that all events broadcasted by any plugin automatically end up in the web UI. + +Note that all events from Gekko come from a plugin (with the exception of the `candle` event, which comes from the market), and no plugin is required for Gekko to run, this means it might be possible that some events are never broadcasted since their originating plugin is not active. If a plugin + +## List of events + +- [candle](#candle-event): Every time Gekko calculas a new one minute candle from the market. +- [advice](#advice-event): Every time the trading strategy has new advice. +- [trade](#trade-event): Every time a trading plugin (either the live trader or the paper trader) has completed a trade. +- [portfolioUpdate](#portfolioUpdate-event): Is broadcasted once a trading plugin has an updated portflio. + +### candle event + +- What: an object containing a one minute candle from the market. +- When: In liquid markets roughly every minute. +- Subscribe: Your plugin can subscribe to this event by registering the `processCandle` method. +- Async: When subscribing to this event the second argument will be a callback which you are expected to call when done handling this event. +- Notes: + - Depending on the gekko configuration these candles might be historical on startup. + - In illiquid markets (of less than a trade per minute) Gekko will caculate these candles in batches and a few might come at the same time. +- Example: + { + start: [moment object of the start time of the candle], + open: [number, open of candle], + high: [number, high of candle], + low: [number, low of candle], + close: [number, close of candle], + vwp: [number, average weighted price of candle], + volume: [number, total volume volume], + trades: [number, amount of trades] + } + +### advice event + +- What: an object containing an advice from the strategy, the advice will either be LONG or SHORT. +- When: This depends on the strategy and the candleSize. +- Subscribe: Your plugin can subscribe to this event by registering the `processAdvice` method. +- Example: + { + recommendation: [position to take, either long or short], + portfolio: [amount of portfolio you should move to position] **DECREPATED** + } + +### trade event + +- What: an object containing the summary of a single completed trade (buy or sell). +- When: Some point in time after the advice event, at the same time as the trade event. +- Subscribe: Your plugin can subscribe to this event by registering the `processTrade` method. +- Example: + { + action: [either "buy" or "sell"], + price: [number, price that was sold at], + date: [moment object, exchange time trade completed at], + portfolio: [object containing amount in currency and asset], + balance: [number, total worth of portfolio] + } + +### portfolioUpdate event + +- What: an object containing updated portfolion information. +- When: Some point in time after the advice event, at the same time as the trade event. +- Subscribe: Your plugin can subscribe to this event by registering the `processPortfolioUpdate` method. +- Example: + { + currency: [number, portfolio amount of currency], + asset: [number, portfolio amount of asset] + } From 16d96736dae0ef596be76a8dc642147bbfa51f2d Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Thu, 1 Feb 2018 20:13:19 +0100 Subject: [PATCH 03/57] streamline events intro --- docs/internals/events.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/internals/events.md b/docs/internals/events.md index 5653e2598..869cca6fa 100644 --- a/docs/internals/events.md +++ b/docs/internals/events.md @@ -2,9 +2,11 @@ As described in the [architecture](./architecture.md) events play a key role in the complete system: they relay all information between seperate components. This makes the codebase scalable, testable and it seperates concerns. -Events are not just relayed between core components in a GekkoStream, if you run the Gekko UI they are also broadcasted (via the UI server) over the websocket to the web UI. This means that all events broadcasted by any plugin automatically end up in the web UI. +if you run the Gekko UI events are relayed between core components as well as broadcasted (via the UI server) to the web UI. This means that all events broadcasted by any plugin automatically end up in the web UI. -Note that all events from Gekko come from a plugin (with the exception of the `candle` event, which comes from the market), and no plugin is required for Gekko to run, this means it might be possible that some events are never broadcasted since their originating plugin is not active. If a plugin +Note that all events from Gekko come from a plugin (with the exception of the `candle` event, which comes from the market), and no plugin is required for Gekko to run, this means it might be possible that some events are never broadcasted since their originating plugin is not active. If a plugin wants to listen to an event that will never be broadcasted (because of a lack of another plugin) this will be warn in the console like so: + + (WARN): Paper Trader wanted to listen to the tradingAdvisor, however the tradingAdvisor is disabled. ## List of events @@ -61,7 +63,7 @@ Note that all events from Gekko come from a plugin (with the exception of the `c ### portfolioUpdate event -- What: an object containing updated portfolion information. +- What: an object containing updated portfolio information. - When: Some point in time after the advice event, at the same time as the trade event. - Subscribe: Your plugin can subscribe to this event by registering the `processPortfolioUpdate` method. - Example: From fc4ac031a96447adfd1691478a060bdb911bf03b Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Thu, 1 Feb 2018 20:14:27 +0100 Subject: [PATCH 04/57] typo --- docs/internals/events.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/internals/events.md b/docs/internals/events.md index 869cca6fa..c86f765d2 100644 --- a/docs/internals/events.md +++ b/docs/internals/events.md @@ -4,7 +4,7 @@ As described in the [architecture](./architecture.md) events play a key role in if you run the Gekko UI events are relayed between core components as well as broadcasted (via the UI server) to the web UI. This means that all events broadcasted by any plugin automatically end up in the web UI. -Note that all events from Gekko come from a plugin (with the exception of the `candle` event, which comes from the market), and no plugin is required for Gekko to run, this means it might be possible that some events are never broadcasted since their originating plugin is not active. If a plugin wants to listen to an event that will never be broadcasted (because of a lack of another plugin) this will be warn in the console like so: +Note that all events from Gekko come from a plugin (with the exception of the `candle` event, which comes from the market), and no plugin is required for Gekko to run, this means it might be possible that some events are never broadcasted since their originating plugin is not active. If a plugin wants to listen to an event that will never be broadcasted (because of a lack of another plugin) this will be warned in the console like so: (WARN): Paper Trader wanted to listen to the tradingAdvisor, however the tradingAdvisor is disabled. From 7f41958e941bd7414b8291e751e3135561586eda Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Thu, 1 Feb 2018 20:27:35 +0100 Subject: [PATCH 05/57] catch unsuccessful broadcast --- web/server.js | 10 ++++++++-- web/state/listManager.js | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/web/server.js b/web/server.js index 6f266579c..8b2fa740d 100644 --- a/web/server.js +++ b/web/server.js @@ -25,7 +25,13 @@ const broadcast = data => { _.each( wss.clients, - client => client.send(JSON.stringify(data)) + client => { + try { + client.send(JSON.stringify(data); + } catch(e) { + log.warn('unable to send data to client'); + } + }) ); } cache.set('broadcast', broadcast); @@ -75,7 +81,7 @@ app .use(router.routes()) .use(router.allowedMethods()); -server.timeout = config.api.timeout||120000; +server.timeout = config.api.timeout || 120000; server.on('request', app.callback()); server.listen(config.api.port, config.api.host, '::', () => { const host = `${config.ui.host}:${config.ui.port}${config.ui.path}`; diff --git a/web/state/listManager.js b/web/state/listManager.js index 7c40d72e4..b4094160c 100644 --- a/web/state/listManager.js +++ b/web/state/listManager.js @@ -27,7 +27,7 @@ ListManager.prototype.update = function(id, updates) { return true; } -// push a value to a array proprty of an item +// push a value to a array property of an item ListManager.prototype.push = function(id, prop, value) { let item = this._list.find(i => i.id === id); if(!item) From 28326bfa679aba40e86c8169d16c10360593c2fd Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Thu, 1 Feb 2018 21:47:35 +0100 Subject: [PATCH 06/57] use gekko events for market start and market update --- core/budfox/budfox.js | 12 ++++++++++++ core/budfox/marketDataProvider.js | 9 +++++---- core/cp.js | 4 ++-- core/gekkoStream.js | 2 +- core/pipeline.js | 5 +++-- docs/internals/events.md | 22 ++++++++++++++++++++++ 6 files changed, 45 insertions(+), 9 deletions(-) diff --git a/core/budfox/budfox.js b/core/budfox/budfox.js index 8ae305518..7d049834a 100644 --- a/core/budfox/budfox.js +++ b/core/budfox/budfox.js @@ -42,6 +42,18 @@ var BudFox = function(config) { this.candleManager.processTrades ); + // relay a marketUpdate event + this.marketDataProvider.on( + 'marketUpdate', + e => this.emit('marketUpdate', e) + ); + + // relay a marketStart event + this.marketDataProvider.on( + 'marketStart', + e => this.emit('marketStart', e) + ); + // Output the candles this.candleManager.on( 'candles', diff --git a/core/budfox/marketDataProvider.js b/core/budfox/marketDataProvider.js index 7542375cf..64ad036bc 100644 --- a/core/budfox/marketDataProvider.js +++ b/core/budfox/marketDataProvider.js @@ -35,12 +35,13 @@ Manager.prototype.retrieve = function() { Manager.prototype.relayTrades = function(batch) { this.emit('trades', batch); - this.sendStartAt(batch); - cp.update(batch.last.date.format()); + this.sendMarketStart(batch); + // cp.update(batch.last.date.format()); + this.emit('marketUpdate', batch.last.date); } -Manager.prototype.sendStartAt = _.once(function(batch) { - cp.startAt(batch.first.date.format()) +Manager.prototype.sendMarketStart = _.once(function(batch) { + this.emit('marketStart', batch.first.date); }); module.exports = Manager; \ No newline at end of file diff --git a/core/cp.js b/core/cp.js index 91ae5e598..7248ef213 100644 --- a/core/cp.js +++ b/core/cp.js @@ -17,8 +17,8 @@ var message = (type, payload) => { var cp = { // string like: '2016-12-03T22:23:00.000Z' - update: latest => message('update', { latest }), - startAt: startAt => message('startAt', { startAt }), + // update: latest => message('update', { latest }), + // startAt: startAt => message('startAt', { startAt }), // object like: // diff --git a/core/gekkoStream.js b/core/gekkoStream.js index eadda12ab..f64d3c4f8 100644 --- a/core/gekkoStream.js +++ b/core/gekkoStream.js @@ -46,7 +46,7 @@ Gekko.prototype.shutdown = function() { if (c.finalize) c.finalize(callback); else callback(); }, - function() { + () => { // If we are a child process, we signal to the parent to kill the child once it is done // so that is has time to process all remaining events (and send report data) if (env === 'child-process') process.send('done'); diff --git a/core/pipeline.js b/core/pipeline.js index 6590f23b3..d171f9638 100644 --- a/core/pipeline.js +++ b/core/pipeline.js @@ -88,8 +88,9 @@ var pipeline = (settings) => { pluginSubscriptions.filter(s => _.isArray(s.emitter)), subscription => { var singleEventEmitters = subscription.emitter - .filter(s => _.size(plugins.filter(p => p.meta.slug === s) - )); + .filter( + s => _.size(plugins.filter(p => p.meta.slug === s)) + ); if(_.size(singleEventEmitters) > 1) { var error = `Multiple plugins are broadcasting`; diff --git a/docs/internals/events.md b/docs/internals/events.md index c86f765d2..740ced0e2 100644 --- a/docs/internals/events.md +++ b/docs/internals/events.md @@ -15,6 +15,11 @@ Note that all events from Gekko come from a plugin (with the exception of the `c - [trade](#trade-event): Every time a trading plugin (either the live trader or the paper trader) has completed a trade. - [portfolioUpdate](#portfolioUpdate-event): Is broadcasted once a trading plugin has an updated portflio. +Beside those there are also two additional market events, note that those are only emitted when Gekko is running in either realtime or importing mode (NOT during a backtest for performance reasons). + +- [marketStart](#marketStart-event): Once, when the market just started. +- [marketUpdate](#marketUpdate-event): Whenever the market has fetched new raw market data. + ### candle event - What: an object containing a one minute candle from the market. @@ -71,3 +76,20 @@ Note that all events from Gekko come from a plugin (with the exception of the `c currency: [number, portfolio amount of currency], asset: [number, portfolio amount of asset] } + +### marketStart event + +- What: a moment object describing the first date of the market data. +- When: when the market is started. +- Subscribe: Your plugin can subscribe to this event by registering the `processMarketStart` method. +- Example: + [moment object describing the date of the first market data] + +### marketUpdate event + +- What: a moment object describing the point in time for up to which the market has market data. +- When: every few seconds. +- Subscribe: Your plugin can subscribe to this event by registering the `processMarketUpdate` method. +- Example: + [moment object describing the date of the latest market data] + From 4c647e2f8412dabafe0e713ed4713b5c0f127b4d Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Fri, 2 Feb 2018 04:32:44 +0700 Subject: [PATCH 07/57] hook up plugins to market events --- core/budfox/budfox.js | 27 ------------ core/budfox/marketDataProvider.js | 1 - core/pipeline.js | 73 ++++++++++++++++++++++--------- plugins.js | 7 +++ subscriptions.js | 9 +++- 5 files changed, 67 insertions(+), 50 deletions(-) diff --git a/core/budfox/budfox.js b/core/budfox/budfox.js index 7d049834a..f6d068227 100644 --- a/core/budfox/budfox.js +++ b/core/budfox/budfox.js @@ -61,19 +61,6 @@ var BudFox = function(config) { ); this.heart.pump(); - - // Budfox also reports: - - // Trades & last trade - // - // this.marketDataProvider.on( - // 'trades', - // this.broadcast('trades') - // ); - // this.marketDataProvider.on( - // 'trades', - // this.broadcastTrade - // ); } var Readable = require('stream').Readable; @@ -88,18 +75,4 @@ BudFox.prototype.pushCandles = function(candles) { _.each(candles, this.push); } -// BudFox.prototype.broadcastTrade = function(trades) { -// _.defer(function() { -// this.emit('trade', trades.last); -// }.bind(this)); -// } - -// BudFox.prototype.broadcast = function(message) { -// return function(payload) { -// _.defer(function() { -// this.emit(message, payload); -// }.bind(this)); -// }.bind(this); -// } - module.exports = BudFox; diff --git a/core/budfox/marketDataProvider.js b/core/budfox/marketDataProvider.js index 64ad036bc..83eed4ebc 100644 --- a/core/budfox/marketDataProvider.js +++ b/core/budfox/marketDataProvider.js @@ -36,7 +36,6 @@ Manager.prototype.relayTrades = function(batch) { this.emit('trades', batch); this.sendMarketStart(batch); - // cp.update(batch.last.date.format()); this.emit('marketUpdate', batch.last.date); } diff --git a/core/pipeline.js b/core/pipeline.js index d171f9638..25fe4204b 100644 --- a/core/pipeline.js +++ b/core/pipeline.js @@ -44,6 +44,8 @@ var pipeline = (settings) => { // and how they should hooked up to consumers. var subscriptions = require(dirs.gekko + 'subscriptions'); + var market; + // Instantiate each enabled plugin var loadPlugins = function(next) { // load all plugins @@ -74,7 +76,6 @@ var pipeline = (settings) => { // Subscribe all plugins to other emitting plugins var subscribePlugins = function(next) { - // events broadcasted by plugins var pluginSubscriptions = _.filter( subscriptions, @@ -108,7 +109,7 @@ var pipeline = (settings) => { _.each(plugins, function(plugin) { _.each(pluginSubscriptions, function(sub) { - if(_.has(plugin, sub.handler)) { + if(plugin[sub.handler]) { // if a plugin wants to listen // to something disabled @@ -144,12 +145,10 @@ var pipeline = (settings) => { _.each(plugins, function(plugin) { _.each(marketSubscriptions, function(sub) { - // for now, only subscribe to candles - if(sub.event !== 'candle') - return; - - if(_.has(plugin, sub.handler)) - candleConsumers.push(plugin); + if(plugin[sub.handler]) { + if(sub.event === 'candle') + candleConsumers.push(plugin); + } }); }); @@ -157,7 +156,6 @@ var pipeline = (settings) => { next(); } - // TODO: move this somewhere where it makes more sense var prepareMarket = function(next) { if(mode === 'backtest' && config.backtest.daterange === 'scan') require(dirs.core + 'prepareDateRange')(next); @@ -165,6 +163,48 @@ var pipeline = (settings) => { next(); } + var setupMarket = function(next) { + // load a market based on the config (or fallback to mode) + let marketType; + if(config.market) + marketType = config.market.type; + else + marketType = mode; + + var Market = require(dirs.markets + marketType); + + market = new Market(config); + + next(); + } + + var subscribePluginsToMarket = function(next) { + + // events broadcasted by the market + var marketSubscriptions = _.filter( + subscriptions, + {emitter: 'market'} + ); + + _.each(plugins, function(plugin) { + _.each(marketSubscriptions, function(sub) { + + if(sub.event === 'candle') + // these are handled via the market stream + return; + + if(plugin[sub.handler]) { + console.log(plugin.meta, sub.event) + market.on(sub.event, plugin[sub.handler]); + } + + }); + }); + + next(); + + } + log.info('Setting up Gekko in', mode, 'mode'); log.info(''); @@ -173,19 +213,12 @@ var pipeline = (settings) => { loadPlugins, referenceEmitters, subscribePlugins, - prepareMarket + prepareMarket, + setupMarket, + subscribePluginsToMarket ], function() { - // load a market based on the config (or fallback to mode) - let marketType; - if(config.market) - marketType = config.market.type; - else - marketType = mode; - - var Market = require(dirs.markets + marketType); - - var market = new Market(config); + var gekko = new GekkoStream(candleConsumers); market diff --git a/plugins.js b/plugins.js index af5f8dca2..5b0ef28c2 100644 --- a/plugins.js +++ b/plugins.js @@ -190,6 +190,13 @@ var plugins = [ slug: 'ifttt', async: false, modes: ['realtime'] + }, + { + name: 'event logger', + description: 'Logs all gekko events.', + slug: 'eventLogger', + async: false, + modes: ['realtime'] } ]; diff --git a/subscriptions.js b/subscriptions.js index 285dd3885..a10fa3a97 100644 --- a/subscriptions.js +++ b/subscriptions.js @@ -11,8 +11,13 @@ var subscriptions = [ }, { emitter: 'market', - event: 'history', - handler: 'processHistory' + event: 'marketUpdate', + handler: 'processMarketUpdate' + }, + { + emitter: 'market', + event: 'marketStart', + handler: 'processMarketStart' }, { emitter: 'tradingAdvisor', From c5dbb78680ee01bffafce4ec4b7fbdb1f3d9e962 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Fri, 2 Feb 2018 04:37:42 +0700 Subject: [PATCH 08/57] add eventLogger plugin --- plugins/eventLogger.js | 14 ++++++++++++++ sample-config.js | 4 ++++ 2 files changed, 18 insertions(+) create mode 100644 plugins/eventLogger.js diff --git a/plugins/eventLogger.js b/plugins/eventLogger.js new file mode 100644 index 000000000..dca2abccc --- /dev/null +++ b/plugins/eventLogger.js @@ -0,0 +1,14 @@ +const log = require('../core/log'); +const _ = require('lodash'); +const subscriptions = require('../subscriptions'); + + +var EventLogger = function() {} + +_.each(subscriptions, sub => { + EventLogger.prototype[sub.handler] = event => { + log.info(`[EVENT ${sub.event}]`, event); + } +}) + +module.exports = EventLogger; \ No newline at end of file diff --git a/sample-config.js b/sample-config.js index 0719eb1b3..e1fdf7a02 100644 --- a/sample-config.js +++ b/sample-config.js @@ -225,6 +225,10 @@ config.adviceLogger = { muteSoft: true // disable advice printout if it's soft } +config.eventLogger = { + enabled: false +} + config.pushover = { enabled: false, sendPushoverOnStart: false, From 5e459c3e3f921332c8cd6080615d0b430b360367 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Fri, 2 Feb 2018 13:50:21 +0700 Subject: [PATCH 09/57] add additional strat events "stratStart" and "stratUpdate" --- core/pipeline.js | 1 - docs/internals/events.md | 60 ++++++++++++++++++++---- plugins/eventLogger.js | 4 +- plugins/tradingAdvisor/tradingAdvisor.js | 10 +++- subscriptions.js | 10 ++++ 5 files changed, 70 insertions(+), 15 deletions(-) diff --git a/core/pipeline.js b/core/pipeline.js index 25fe4204b..729855ffd 100644 --- a/core/pipeline.js +++ b/core/pipeline.js @@ -194,7 +194,6 @@ var pipeline = (settings) => { return; if(plugin[sub.handler]) { - console.log(plugin.meta, sub.event) market.on(sub.event, plugin[sub.handler]); } diff --git a/docs/internals/events.md b/docs/internals/events.md index 740ced0e2..4cd7e070d 100644 --- a/docs/internals/events.md +++ b/docs/internals/events.md @@ -1,6 +1,6 @@ # Events -As described in the [architecture](./architecture.md) events play a key role in the complete system: they relay all information between seperate components. This makes the codebase scalable, testable and it seperates concerns. +As described in the [architecture](./architecture.md) events play a key role in the complete system: they relay all information between seperate components (like plugins). This makes the codebase scalable, testable and it seperates concerns. if you run the Gekko UI events are relayed between core components as well as broadcasted (via the UI server) to the web UI. This means that all events broadcasted by any plugin automatically end up in the web UI. @@ -13,7 +13,9 @@ Note that all events from Gekko come from a plugin (with the exception of the `c - [candle](#candle-event): Every time Gekko calculas a new one minute candle from the market. - [advice](#advice-event): Every time the trading strategy has new advice. - [trade](#trade-event): Every time a trading plugin (either the live trader or the paper trader) has completed a trade. -- [portfolioUpdate](#portfolioUpdate-event): Is broadcasted once a trading plugin has an updated portflio. +- [portfolioUpdate](#portfolioUpdate-event): Every time a trading plugin has an updated portflio. +- [stratStat](#stratStart-event): Once, with the first data this strategy is based on. +- [stratUpdate](#stratUpdate-event): Every time the strategy has processed new data. Beside those there are also two additional market events, note that those are only emitted when Gekko is running in either realtime or importing mode (NOT during a backtest for performance reasons). @@ -22,7 +24,7 @@ Beside those there are also two additional market events, note that those are on ### candle event -- What: an object containing a one minute candle from the market. +- What: An object containing a one minute candle from the market. - When: In liquid markets roughly every minute. - Subscribe: Your plugin can subscribe to this event by registering the `processCandle` method. - Async: When subscribing to this event the second argument will be a callback which you are expected to call when done handling this event. @@ -43,7 +45,7 @@ Beside those there are also two additional market events, note that those are on ### advice event -- What: an object containing an advice from the strategy, the advice will either be LONG or SHORT. +- What: An object containing an advice from the strategy, the advice will either be LONG or SHORT. - When: This depends on the strategy and the candleSize. - Subscribe: Your plugin can subscribe to this event by registering the `processAdvice` method. - Example: @@ -54,7 +56,7 @@ Beside those there are also two additional market events, note that those are on ### trade event -- What: an object containing the summary of a single completed trade (buy or sell). +- What: An object containing the summary of a single completed trade (buy or sell). - When: Some point in time after the advice event, at the same time as the trade event. - Subscribe: Your plugin can subscribe to this event by registering the `processTrade` method. - Example: @@ -68,7 +70,7 @@ Beside those there are also two additional market events, note that those are on ### portfolioUpdate event -- What: an object containing updated portfolio information. +- What: An object containing updated portfolio information. - When: Some point in time after the advice event, at the same time as the trade event. - Subscribe: Your plugin can subscribe to this event by registering the `processPortfolioUpdate` method. - Example: @@ -77,18 +79,56 @@ Beside those there are also two additional market events, note that those are on asset: [number, portfolio amount of asset] } +### stratStart event + +- What: An object describing the first candle of the strat has processed. +- When: when the strategy is initialized is started. +- Subscribe: Your plugin can subscribe to this event by registering the `processStratStart` method. +- Notes: + - There are scenarios where the date of this event is before the date of the marketStart, this can happen when the strategy requires historical data and Gekko was able to load some from disk (this process bypasses the market). +- Example: + { + start: [moment object of the start time of the candle], + open: [number, open of candle], + high: [number, high of candle], + low: [number, low of candle], + close: [number, close of candle], + vwp: [number, average weighted price of candle], + volume: [number, total volume volume], + trades: [number, amount of trades] + } + +### stratUpdate event + +- What: An object describing an updated candle the strat has processed. +- When: when the strategy is initialized is started. +- Subscribe: Your plugin can subscribe to this event by registering the `processStratStart` method. +- Notes: + - This event is guaranteed to happen before any possible advice of the same candle, this can happen when the strategy uses async indicators (for example from TAlib or Tulip). +- Example: + { + start: [moment object of the start time of the candle], + open: [number, open of candle], + high: [number, high of candle], + low: [number, low of candle], + close: [number, close of candle], + vwp: [number, average weighted price of candle], + volume: [number, total volume volume], + trades: [number, amount of trades] + } + ### marketStart event -- What: a moment object describing the first date of the market data. -- When: when the market is started. +- What: A moment object describing the first date of the market data. +- When: When the market is started. - Subscribe: Your plugin can subscribe to this event by registering the `processMarketStart` method. - Example: [moment object describing the date of the first market data] ### marketUpdate event -- What: a moment object describing the point in time for up to which the market has market data. -- When: every few seconds. +- What: A moment object describing the point in time for up to which the market has market data. +- When: Every few seconds. - Subscribe: Your plugin can subscribe to this event by registering the `processMarketUpdate` method. - Example: [moment object describing the date of the latest market data] diff --git a/plugins/eventLogger.js b/plugins/eventLogger.js index dca2abccc..599640ec8 100644 --- a/plugins/eventLogger.js +++ b/plugins/eventLogger.js @@ -7,8 +7,8 @@ var EventLogger = function() {} _.each(subscriptions, sub => { EventLogger.prototype[sub.handler] = event => { - log.info(`[EVENT ${sub.event}]`, event); + log.info(`[EVENT ${sub.event}]\n`, event); } -}) +}); module.exports = EventLogger; \ No newline at end of file diff --git a/plugins/tradingAdvisor/tradingAdvisor.js b/plugins/tradingAdvisor/tradingAdvisor.js index a57144b68..e7b0e0bb9 100644 --- a/plugins/tradingAdvisor/tradingAdvisor.js +++ b/plugins/tradingAdvisor/tradingAdvisor.js @@ -64,7 +64,8 @@ Actor.prototype.setupTradingMethod = function() { .on('advice', this.relayAdvice); this.batcher - .on('candle', this.processCustomCandle); + .on('candle', this.processStratCandle) + .once('candle', this.relayFirstStratCandle) } // HANDLERS @@ -75,8 +76,9 @@ Actor.prototype.processCandle = function(candle, done) { } // propogate a custom sized candle to the trading method -Actor.prototype.processCustomCandle = function(candle) { +Actor.prototype.processStratCandle = function(candle) { this.method.tick(candle); + this.emit('stratUpdate', candle); } // pass through shutdown handler @@ -89,5 +91,9 @@ Actor.prototype.relayAdvice = function(advice) { this.emit('advice', advice); } +Actor.prototype.relayFirstStratCandle = function(candle) { + this.emit('stratStart', candle); +} + module.exports = Actor; diff --git a/subscriptions.js b/subscriptions.js index a10fa3a97..f1578efa5 100644 --- a/subscriptions.js +++ b/subscriptions.js @@ -24,6 +24,16 @@ var subscriptions = [ event: 'advice', handler: 'processAdvice' }, + { + emitter: 'tradingAdvisor', + event: 'stratStart', + handler: 'processStratStart' + }, + { + emitter: 'tradingAdvisor', + event: 'stratUpdate', + handler: 'processStratUpdate' + }, { emitter: ['trader', 'paperTrader'], event: 'trade', From c7aedf2bd424279e4984a13895472f187263a286 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Mon, 5 Feb 2018 22:25:45 +0800 Subject: [PATCH 10/57] make sure to properly enclose broadcast catch wrap --- web/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/server.js b/web/server.js index 8b2fa740d..39ea3f97d 100644 --- a/web/server.js +++ b/web/server.js @@ -27,7 +27,7 @@ const broadcast = data => { wss.clients, client => { try { - client.send(JSON.stringify(data); + client.send(JSON.stringify(data)); } catch(e) { log.warn('unable to send data to client'); } From 875a3b1de86731104431cd6a95f7a86aeac7bfc4 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Tue, 6 Feb 2018 16:51:14 +0800 Subject: [PATCH 11/57] rm stratStart event --- core/budfox/candleManager.js | 10 ---------- core/cp.js | 18 ------------------ docs/internals/events.md | 11 +++++------ plugins/tradingAdvisor/tradingAdvisor.js | 4 ---- 4 files changed, 5 insertions(+), 38 deletions(-) diff --git a/core/budfox/candleManager.js b/core/budfox/candleManager.js index a37801f45..2df33a89c 100644 --- a/core/budfox/candleManager.js +++ b/core/budfox/candleManager.js @@ -21,10 +21,6 @@ var Manager = function() { this.candleCreator .on('candles', this.relayCandles); - - this.messageFirstCandle = _.once(candle => { - cp.firstCandle(candle); - }) }; util.makeEventEmitter(Manager); @@ -34,12 +30,6 @@ Manager.prototype.processTrades = function(tradeBatch) { Manager.prototype.relayCandles = function(candles) { this.emit('candles', candles); - - if(!_.size(candles)) - return; - - this.messageFirstCandle(_.first(candles)); - cp.lastCandle(_.last(candles)); } module.exports = Manager; diff --git a/core/cp.js b/core/cp.js index 7248ef213..17bc0730d 100644 --- a/core/cp.js +++ b/core/cp.js @@ -16,24 +16,6 @@ var message = (type, payload) => { } var cp = { - // string like: '2016-12-03T22:23:00.000Z' - // update: latest => message('update', { latest }), - // startAt: startAt => message('startAt', { startAt }), - - // object like: - // - // { - // start: '2016-12-03T22:23:00.000Z', - // open: 765, - // high: 765, - // low: 765, - // close: 765, - // vwp: 765, - // volume: 0, - // trades: 0 - // } - lastCandle: lastCandle => message('lastCandle', { lastCandle }), - firstCandle: firstCandle => message('firstCandle', { firstCandle }), // object like: // diff --git a/docs/internals/events.md b/docs/internals/events.md index 4cd7e070d..bd16bdbdb 100644 --- a/docs/internals/events.md +++ b/docs/internals/events.md @@ -13,8 +13,7 @@ Note that all events from Gekko come from a plugin (with the exception of the `c - [candle](#candle-event): Every time Gekko calculas a new one minute candle from the market. - [advice](#advice-event): Every time the trading strategy has new advice. - [trade](#trade-event): Every time a trading plugin (either the live trader or the paper trader) has completed a trade. -- [portfolioUpdate](#portfolioUpdate-event): Every time a trading plugin has an updated portflio. -- [stratStat](#stratStart-event): Once, with the first data this strategy is based on. +- [portfolioUpdate](#portfolioUpdate-event): Every time the portfolio has changed. - [stratUpdate](#stratUpdate-event): Every time the strategy has processed new data. Beside those there are also two additional market events, note that those are only emitted when Gekko is running in either realtime or importing mode (NOT during a backtest for performance reasons). @@ -81,8 +80,8 @@ Beside those there are also two additional market events, note that those are on ### stratStart event -- What: An object describing the first candle of the strat has processed. -- When: when the strategy is initialized is started. +- What: An object describing the first candle the strat has processed. +- When: when the strategy is initialized. - Subscribe: Your plugin can subscribe to this event by registering the `processStratStart` method. - Notes: - There are scenarios where the date of this event is before the date of the marketStart, this can happen when the strategy requires historical data and Gekko was able to load some from disk (this process bypasses the market). @@ -102,9 +101,9 @@ Beside those there are also two additional market events, note that those are on - What: An object describing an updated candle the strat has processed. - When: when the strategy is initialized is started. -- Subscribe: Your plugin can subscribe to this event by registering the `processStratStart` method. +- Subscribe: Your plugin can subscribe to this event by registering the `processStratUpdate` method. - Notes: - - This event is guaranteed to happen before any possible advice of the same candle, this can happen when the strategy uses async indicators (for example from TAlib or Tulip). + - This event is not guaranteed to happen before any possible advice of the same candle, this situation can happen when the strategy uses async indicators (for example from TAlib or Tulip). - Example: { start: [moment object of the start time of the candle], diff --git a/plugins/tradingAdvisor/tradingAdvisor.js b/plugins/tradingAdvisor/tradingAdvisor.js index e7b0e0bb9..7ed5569ec 100644 --- a/plugins/tradingAdvisor/tradingAdvisor.js +++ b/plugins/tradingAdvisor/tradingAdvisor.js @@ -91,9 +91,5 @@ Actor.prototype.relayAdvice = function(advice) { this.emit('advice', advice); } -Actor.prototype.relayFirstStratCandle = function(candle) { - this.emit('stratStart', candle); -} - module.exports = Actor; From 7caf0d31a6d4ea102939d1d8ebbf775ce1c9dac9 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Tue, 6 Feb 2018 16:55:11 +0800 Subject: [PATCH 12/57] rm all stratEvent docs --- docs/internals/events.md | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/docs/internals/events.md b/docs/internals/events.md index bd16bdbdb..049135a28 100644 --- a/docs/internals/events.md +++ b/docs/internals/events.md @@ -78,25 +78,6 @@ Beside those there are also two additional market events, note that those are on asset: [number, portfolio amount of asset] } -### stratStart event - -- What: An object describing the first candle the strat has processed. -- When: when the strategy is initialized. -- Subscribe: Your plugin can subscribe to this event by registering the `processStratStart` method. -- Notes: - - There are scenarios where the date of this event is before the date of the marketStart, this can happen when the strategy requires historical data and Gekko was able to load some from disk (this process bypasses the market). -- Example: - { - start: [moment object of the start time of the candle], - open: [number, open of candle], - high: [number, high of candle], - low: [number, low of candle], - close: [number, close of candle], - vwp: [number, average weighted price of candle], - volume: [number, total volume volume], - trades: [number, amount of trades] - } - ### stratUpdate event - What: An object describing an updated candle the strat has processed. From 9a4b2b1408262f81955b53bde41dd4c2bf16090c Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Fri, 9 Feb 2018 14:30:09 +0800 Subject: [PATCH 13/57] remove stratStart subscription --- subscriptions.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/subscriptions.js b/subscriptions.js index f1578efa5..19bdb0457 100644 --- a/subscriptions.js +++ b/subscriptions.js @@ -24,11 +24,6 @@ var subscriptions = [ event: 'advice', handler: 'processAdvice' }, - { - emitter: 'tradingAdvisor', - event: 'stratStart', - handler: 'processStratStart' - }, { emitter: 'tradingAdvisor', event: 'stratUpdate', From 0c35666f19c848e35839d89892ad9f62061cdcb7 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Sun, 11 Feb 2018 18:11:10 +0800 Subject: [PATCH 14/57] introduce portfolioChange & portfolioValueChange events --- docs/internals/events.md | 21 +++++++++++++++++---- plugins/trader/exchanges | 1 + subscriptions.js | 19 +++++++++++++++++-- 3 files changed, 35 insertions(+), 6 deletions(-) create mode 120000 plugins/trader/exchanges diff --git a/docs/internals/events.md b/docs/internals/events.md index 049135a28..1f298e953 100644 --- a/docs/internals/events.md +++ b/docs/internals/events.md @@ -13,8 +13,11 @@ Note that all events from Gekko come from a plugin (with the exception of the `c - [candle](#candle-event): Every time Gekko calculas a new one minute candle from the market. - [advice](#advice-event): Every time the trading strategy has new advice. - [trade](#trade-event): Every time a trading plugin (either the live trader or the paper trader) has completed a trade. -- [portfolioUpdate](#portfolioUpdate-event): Every time the portfolio has changed. - [stratUpdate](#stratUpdate-event): Every time the strategy has processed new data. +- [portfolioChange](#portfolioChange-event): Every time the content of the portfolio has changed. +- [portfolioTick](#portfolioTick-event): Every time the total worth of the portfolio has changed. +- [report](#report-event): Every time the profit report has updated. +- [roundtrip](#roundtrip-event): Every time a new roundtrip has been completed. Beside those there are also two additional market events, note that those are only emitted when Gekko is running in either realtime or importing mode (NOT during a backtest for performance reasons). @@ -67,17 +70,27 @@ Beside those there are also two additional market events, note that those are on balance: [number, total worth of portfolio] } -### portfolioUpdate event +### portfolioChange event -- What: An object containing updated portfolio information. +- What: An object containing new portfolio contents (amount of asset & currency). - When: Some point in time after the advice event, at the same time as the trade event. -- Subscribe: Your plugin can subscribe to this event by registering the `processPortfolioUpdate` method. +- Subscribe: Your plugin can subscribe to this event by registering the `processPortfolioChange` method. - Example: { currency: [number, portfolio amount of currency], asset: [number, portfolio amount of asset] } +### portfolioValueChange event + +- What: An object containing the total portfolio worth (amount of asset & currency calculated in currency). +- When: Every time the value of the portfolio has changed, if the strategy is in a LONG position this will be every minute. +- Subscribe: Your plugin can subscribe to this event by registering the `processPortfolioValueChange` method. +- Example: + { + value: [number, portfolio amount of currency] + } + ### stratUpdate event - What: An object describing an updated candle the strat has processed. diff --git a/plugins/trader/exchanges b/plugins/trader/exchanges new file mode 120000 index 000000000..691492e57 --- /dev/null +++ b/plugins/trader/exchanges @@ -0,0 +1 @@ +../../exchanges/ \ No newline at end of file diff --git a/subscriptions.js b/subscriptions.js index 19bdb0457..df2370d71 100644 --- a/subscriptions.js +++ b/subscriptions.js @@ -36,8 +36,23 @@ var subscriptions = [ }, { emitter: ['trader', 'paperTrader'], - event: 'portfolioUpdate', - handler: 'processPortfolioUpdate' + event: 'portfolioChange', + handler: 'processPortfolioChange' + }, + { + emitter: ['trader', 'paperTrader'], + event: 'portfolioValueChange', + handler: 'processPortfolioValueChange' + }, + { + emitter: ['performanceAnalyzer'], + event: 'performanceReport', + handler: 'processPerformanceReport' + }, + { + emitter: ['performanceAnalyzer'], + event: 'roundtrip', + handler: 'processRoundtrip' }, ]; From 1af48aa60014ddb9ff70f4fa6e1806f8a7e739d2 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Mon, 12 Feb 2018 17:36:05 +0800 Subject: [PATCH 15/57] introduce events to describe trades async --- docs/internals/events.md | 91 +++++++++++++++++++++++++++++----------- subscriptions.js | 14 ++++++- 2 files changed, 78 insertions(+), 27 deletions(-) diff --git a/docs/internals/events.md b/docs/internals/events.md index 1f298e953..719d7fb72 100644 --- a/docs/internals/events.md +++ b/docs/internals/events.md @@ -8,15 +8,19 @@ Note that all events from Gekko come from a plugin (with the exception of the `c (WARN): Paper Trader wanted to listen to the tradingAdvisor, however the tradingAdvisor is disabled. -## List of events +*NOTE: Events describe async communication about what is happening, it's hard to guarentee the proper order of events during backtests which pipe in historical candles as fast as the plugins can consume them. Stabalizing this is a work in progress but expect things to break until proper behaviour has been validated under a variaty of platform circumstances (OS, hardware, etc).* + +## List of events emitted by standard plugins - [candle](#candle-event): Every time Gekko calculas a new one minute candle from the market. +- [stratUpdate](#stratUpdate-event): Every time the strategy has processed new market data. - [advice](#advice-event): Every time the trading strategy has new advice. -- [trade](#trade-event): Every time a trading plugin (either the live trader or the paper trader) has completed a trade. -- [stratUpdate](#stratUpdate-event): Every time the strategy has processed new data. +- [tradeInitiated](#tradeInitiated-event): Every time a trading plugin (either the live trader or the paper trader) is going to start a new trade (buy or sell). +- [tradeCompleted](#tradeCompleted-event): Every time a trading plugin (either the live trader or the paper trader) has completed a trade. +- [tradeAborted](#tradeAborted-event): Every time a trading plugin (either the live trader or the paper trader) has NOT acted on new advice (due to unsufficiant funds or a similar reason). - [portfolioChange](#portfolioChange-event): Every time the content of the portfolio has changed. -- [portfolioTick](#portfolioTick-event): Every time the total worth of the portfolio has changed. -- [report](#report-event): Every time the profit report has updated. +- [portfolioValueChange](#portfolioValueChange-event): Every time value of the portfolio has changed. +- [performanceReport](#performanceReport-event): Every time the profit report was updated. - [roundtrip](#roundtrip-event): Every time a new roundtrip has been completed. Beside those there are also two additional market events, note that those are only emitted when Gekko is running in either realtime or importing mode (NOT during a backtest for performance reasons). @@ -45,6 +49,25 @@ Beside those there are also two additional market events, note that those are on trades: [number, amount of trades] } +### stratUpdate event + +- What: An object describing an updated candle the strat has processed. +- When: when the strategy is initialized is started. +- Subscribe: Your plugin can subscribe to this event by registering the `processStratUpdate` method. +- Notes: + - This event is not guaranteed to happen before any possible advice of the same candle, this situation can happen when the strategy uses async indicators (for example from TAlib or Tulip). +- Example: + { + start: [moment object of the start time of the candle], + open: [number, open of candle], + high: [number, high of candle], + low: [number, low of candle], + close: [number, close of candle], + vwp: [number, average weighted price of candle], + volume: [number, total volume volume], + trades: [number, amount of trades] + } + ### advice event - What: An object containing an advice from the strategy, the advice will either be LONG or SHORT. @@ -56,15 +79,42 @@ Beside those there are also two additional market events, note that those are on portfolio: [amount of portfolio you should move to position] **DECREPATED** } -### trade event +### tradeInitiated event -- What: An object containing the summary of a single completed trade (buy or sell). -- When: Some point in time after the advice event, at the same time as the trade event. -- Subscribe: Your plugin can subscribe to this event by registering the `processTrade` method. +- What: An object singaling that a new trade will be executed. +- When: At the same time as the advice event if the trader will try to trade. +- Subscribe: Your plugin can subscribe to this event by registering the `processTradeInitiated` method. - Example: { action: [either "buy" or "sell"], - price: [number, price that was sold at], + date: [moment object, exchange time trade completed at], + portfolio: [object containing amount in currency and asset], + balance: [number, total worth of portfolio] + } + +### tradeAborted event + +- What: An object singaling the fact that the trader will ignore the advice. +- When: At the same time as the advice event if the trader will NOT try to trade. +- Subscribe: Your plugin can subscribe to this event by registering the `processTradeAborted` method. +- Example: + { + action: [either "buy" or "sell"], + date: [moment object, exchange time trade completed at], + portfolio: [object containing amount in currency and asset], + balance: [number, total worth of portfolio], + reason: "Not enough funds" + } + +### tradeCompleted event + +- What: An object containing details of a completed trade. +- When: Some point in time after the tradeInitiated event. +- Subscribe: Your plugin can subscribe to this event by registering the `processTradeCompleted` method. +- Example: + { + action: [either "buy" or "sell"], + price: [number, average price that was sold at], date: [moment object, exchange time trade completed at], portfolio: [object containing amount in currency and asset], balance: [number, total worth of portfolio] @@ -73,7 +123,7 @@ Beside those there are also two additional market events, note that those are on ### portfolioChange event - What: An object containing new portfolio contents (amount of asset & currency). -- When: Some point in time after the advice event, at the same time as the trade event. +- When: Some point in time after the advice event, at the same time as the tradeCompleted event. - Subscribe: Your plugin can subscribe to this event by registering the `processPortfolioChange` method. - Example: { @@ -91,23 +141,14 @@ Beside those there are also two additional market events, note that those are on value: [number, portfolio amount of currency] } -### stratUpdate event +### performanceReport event -- What: An object describing an updated candle the strat has processed. -- When: when the strategy is initialized is started. -- Subscribe: Your plugin can subscribe to this event by registering the `processStratUpdate` method. -- Notes: - - This event is not guaranteed to happen before any possible advice of the same candle, this situation can happen when the strategy uses async indicators (for example from TAlib or Tulip). +- What: An object containing a summary of the performance of the "tradebot" (advice signals + execution). +- When: At the same time as every new candle. +- Subscribe: Your plugin can subscribe to this event by registering the `processPortfolioValueChange` method. - Example: { - start: [moment object of the start time of the candle], - open: [number, open of candle], - high: [number, high of candle], - low: [number, low of candle], - close: [number, close of candle], - vwp: [number, average weighted price of candle], - volume: [number, total volume volume], - trades: [number, amount of trades] + value: [number, portfolio amount of currency] } ### marketStart event diff --git a/subscriptions.js b/subscriptions.js index df2370d71..d403a821b 100644 --- a/subscriptions.js +++ b/subscriptions.js @@ -31,8 +31,18 @@ var subscriptions = [ }, { emitter: ['trader', 'paperTrader'], - event: 'trade', - handler: 'processTrade' + event: 'tradeInitiated', + handler: 'processTradeInitiated' + }, + { + emitter: ['trader', 'paperTrader'], + event: 'tradeAborted', + handler: 'processTradeAborted' + }, + { + emitter: ['trader', 'paperTrader'], + event: 'tradeCompleted', + handler: 'processTradeCompleted' }, { emitter: ['trader', 'paperTrader'], From 08beb2b85866568ccfd80699329588bb7a3c3259 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Mon, 12 Feb 2018 17:41:32 +0800 Subject: [PATCH 16/57] add stratWarmupCompleted event --- docs/internals/events.md | 14 ++++++++++++++ subscriptions.js | 5 +++++ 2 files changed, 19 insertions(+) diff --git a/docs/internals/events.md b/docs/internals/events.md index 719d7fb72..2dfe27d84 100644 --- a/docs/internals/events.md +++ b/docs/internals/events.md @@ -14,6 +14,7 @@ Note that all events from Gekko come from a plugin (with the exception of the `c - [candle](#candle-event): Every time Gekko calculas a new one minute candle from the market. - [stratUpdate](#stratUpdate-event): Every time the strategy has processed new market data. +- [stratWarmupCompleted](#stratWarmupCompleted-event): When the strategy is done warming up. - [advice](#advice-event): Every time the trading strategy has new advice. - [tradeInitiated](#tradeInitiated-event): Every time a trading plugin (either the live trader or the paper trader) is going to start a new trade (buy or sell). - [tradeCompleted](#tradeCompleted-event): Every time a trading plugin (either the live trader or the paper trader) has completed a trade. @@ -68,6 +69,19 @@ Beside those there are also two additional market events, note that those are on trades: [number, amount of trades] } +### stratWarmupCompleted event + +- What: An object signaling that the strategy is now completely warmed up +and will start signaling advice. +- When: Once the strategy consumed more market data than defined by the required history. +- Subscribe: Your plugin can subscribe to this event by registering the `processWarmupCompleted` method. +- Notes: + - This event is triggered on init when the strategy does not require any history (and thus no warmup time). +- Example: + { + start: [moment object of the start time of the last candle in the warmup], + } + ### advice event - What: An object containing an advice from the strategy, the advice will either be LONG or SHORT. diff --git a/subscriptions.js b/subscriptions.js index d403a821b..c3a3e382d 100644 --- a/subscriptions.js +++ b/subscriptions.js @@ -19,6 +19,11 @@ var subscriptions = [ event: 'marketStart', handler: 'processMarketStart' }, + { + emitter: 'tradingAdvisor', + event: 'stratWarmupCompleted', + handler: 'processStratWarmupCompleted' + }, { emitter: 'tradingAdvisor', event: 'advice', From 9ca7caf2cb5a756ab92d9bde8501cc0fe26ddb58 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Tue, 13 Feb 2018 17:44:26 +0800 Subject: [PATCH 17/57] implement stratWarmupCompleted --- docs/internals/events.md | 2 +- plugins/tradingAdvisor/baseTradingMethod.js | 71 ++++++++++----------- plugins/tradingAdvisor/tradingAdvisor.js | 7 +- 3 files changed, 38 insertions(+), 42 deletions(-) diff --git a/docs/internals/events.md b/docs/internals/events.md index 2dfe27d84..731d4b799 100644 --- a/docs/internals/events.md +++ b/docs/internals/events.md @@ -79,7 +79,7 @@ and will start signaling advice. - This event is triggered on init when the strategy does not require any history (and thus no warmup time). - Example: { - start: [moment object of the start time of the last candle in the warmup], + start: [moment object of the start time of the first candle after the warmup], } ### advice event diff --git a/plugins/tradingAdvisor/baseTradingMethod.js b/plugins/tradingAdvisor/baseTradingMethod.js index b132a2e66..089d3cf46 100644 --- a/plugins/tradingAdvisor/baseTradingMethod.js +++ b/plugins/tradingAdvisor/baseTradingMethod.js @@ -56,6 +56,8 @@ var Base = function(settings) { this.candlePropsCacheSize = 1000; this.deferredTicks = []; + this.completedWarmup = false; + this._prevAdvice; this.candleProps = { @@ -195,58 +197,49 @@ Base.prototype.tick = function(candle) { ) ); } - - this.propogateCustomCandle(candle); -} - -// if this is a child process the parent might -// be interested in the custom candle. -if(ENV !== 'child-process') { - Base.prototype.propogateCustomCandle = _.noop; -} else { - Base.prototype.propogateCustomCandle = function(candle) { - process.send({ - type: 'candle', - candle: candle - }); - } } Base.prototype.propogateTick = function(candle) { this.candle = candle; - this.update(candle); + this.processedTicks++; var isAllowedToCheck = this.requiredHistory <= this.age; - // in live mode we might receive more candles - // than minimally needed. In that case check - // whether candle start time is > startTime - var isPremature; - - if(mode === 'realtime'){ - // Subtract number of minutes in current candle for instant start - let startTimeMinusCandleSize = startTime.clone(); - startTimeMinusCandleSize.subtract(this.tradingAdvisor.candleSize, "minutes"); - - isPremature = candle.start < startTimeMinusCandleSize; - } - else{ - isPremature = false; + if(!this.completedWarmup) { + + // in live mode we might receive more candles + // than minimally needed. In that case check + // whether candle start time is > startTime + var isPremature = false; + + if(mode === 'realtime'){ + let startTimeMinusCandleSize = startTime.clone(); + startTimeMinusCandleSize.subtract(this.tradingAdvisor.candleSize, "minutes"); + + isPremature = candle.start < startTimeMinusCandleSize; + } + + if(isAllowedToCheck && !isPremature) { + this.completedWarmup = true; + this.emit( + 'stratWarmupCompleted', + {start: candle.start.clone()} + ); + } } - if(isAllowedToCheck && !isPremature) { + if(this.completedWarmup) { this.log(candle); this.check(candle); - } - this.processedTicks++; - if( - this.asyncTick && - this.hasSyncIndicators && - this.deferredTicks.length - ) { - return this.tick(this.deferredTicks.shift()) + if( + this.asyncTick && + this.hasSyncIndicators && + this.deferredTicks.length + ) { + return this.tick(this.deferredTicks.shift()) + } } // are we totally finished? diff --git a/plugins/tradingAdvisor/tradingAdvisor.js b/plugins/tradingAdvisor/tradingAdvisor.js index 7ed5569ec..1a9661c07 100644 --- a/plugins/tradingAdvisor/tradingAdvisor.js +++ b/plugins/tradingAdvisor/tradingAdvisor.js @@ -61,11 +61,14 @@ Actor.prototype.setupTradingMethod = function() { this.method = new Consultant(tradingSettings); this.method - .on('advice', this.relayAdvice); + .on('advice', this.relayAdvice) + .on( + 'stratWarmupCompleted', + e => this.emit('stratWarmupCompleted', e) + ); this.batcher .on('candle', this.processStratCandle) - .once('candle', this.relayFirstStratCandle) } // HANDLERS From d2a214613bfad4161a5e69a9da5922cb8214a52e Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Tue, 13 Feb 2018 17:59:31 +0800 Subject: [PATCH 18/57] implement stratUpdate event --- plugins/tradingAdvisor/baseTradingMethod.js | 5 +++++ plugins/tradingAdvisor/tradingAdvisor.js | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/tradingAdvisor/baseTradingMethod.js b/plugins/tradingAdvisor/baseTradingMethod.js index 089d3cf46..0f7f86d5f 100644 --- a/plugins/tradingAdvisor/baseTradingMethod.js +++ b/plugins/tradingAdvisor/baseTradingMethod.js @@ -242,6 +242,11 @@ Base.prototype.propogateTick = function(candle) { } } + this.emit('stratUpdate', { + start: candle.start, + // TODO: add indicator results + }) + // are we totally finished? var done = this.age === this.processedTicks; if(done && this.finishCb) diff --git a/plugins/tradingAdvisor/tradingAdvisor.js b/plugins/tradingAdvisor/tradingAdvisor.js index 1a9661c07..9ae4c79ec 100644 --- a/plugins/tradingAdvisor/tradingAdvisor.js +++ b/plugins/tradingAdvisor/tradingAdvisor.js @@ -65,7 +65,11 @@ Actor.prototype.setupTradingMethod = function() { .on( 'stratWarmupCompleted', e => this.emit('stratWarmupCompleted', e) - ); + ) + .on( + 'stratUpdate', + e => this.emit('stratUpdate', e) + ) this.batcher .on('candle', this.processStratCandle) From e78cfbbe629cc9655af450746c0566451292f382 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Wed, 21 Feb 2018 14:31:57 +0800 Subject: [PATCH 19/57] error when plugins consume candles too slow --- core/gekkoStream.js | 54 ++++++++++++++++++++++++++++++++++++--------- core/pipeline.js | 6 ++--- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/core/gekkoStream.js b/core/gekkoStream.js index f64d3c4f8..521a80531 100644 --- a/core/gekkoStream.js +++ b/core/gekkoStream.js @@ -1,13 +1,16 @@ // Small writable stream wrapper that // passes data to all `candleConsumers`. -var Writable = require('stream').Writable; -var _ = require('lodash'); -var async = require('async'); +const Writable = require('stream').Writable; +const _ = require('lodash'); +const async = require('async'); +const moment = require('moment'); -var util = require('./util'); -var env = util.gekkoEnv(); -var mode = util.gekkoMode(); +const util = require('./util'); +const env = util.gekkoEnv(); +const mode = util.gekkoMode(); +const config = util.getConfig(); +const log = require(util.dirs().core + 'log'); var Gekko = function(candleConsumers) { this.candleConsumers = candleConsumers; @@ -20,13 +23,42 @@ Gekko.prototype = Object.create(Writable.prototype, { constructor: { value: Gekko } }); -Gekko.prototype._write = function(chunk, encoding, _done) { - var done = _.after(this.candleConsumers.length, _done); - _.each(this.candleConsumers, function(c) { - c.processCandle(chunk, done); - }); +if(config.debug) { + Gekko.prototype._write = function(chunk, encoding, _done) { + + const start = moment(); + var relayed = false; + var at = null; + + const timer = setTimeout(() => { + if(!relayed) + log.error([ + `The plugin "${at}" has not processed a candle for 0.5 seconds.`, + `This will cause Gekko to slow down or stop working completely.` + ].join(' ')); + }, 1000); + + const done = _.after(this.candleConsumers.length, () => { + relayed = true; + clearInterval(timer); + _done(); + }); + _.each(this.candleConsumers, function(c) { + at = c.meta.name; + c.processCandle(chunk, done); + }); + } +} else { + // skip decoration + Gekko.prototype._write = function(chunk, encoding, _done) { + const done = _.after(this.candleConsumers.length, _done); + _.each(this.candleConsumers, function(c) { + c.processCandle(chunk, done); + }); + } } + Gekko.prototype.finalize = function() { var tradingMethod = _.find( this.candleConsumers, diff --git a/core/pipeline.js b/core/pipeline.js index 729855ffd..ee7940333 100644 --- a/core/pipeline.js +++ b/core/pipeline.js @@ -218,17 +218,17 @@ var pipeline = (settings) => { ], function() { - var gekko = new GekkoStream(candleConsumers); + var gekkoStream = new GekkoStream(candleConsumers); market - .pipe(gekko) + .pipe(gekkoStream) // convert JS objects to JSON string // .pipe(new require('stringify-stream')()) // output to standard out // .pipe(process.stdout); - market.on('end', gekko.finalize); + market.on('end', gekkoStream.finalize); } ); From 0d975707009456720f59b98c102c334c05e35585 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Wed, 21 Feb 2018 14:35:10 +0800 Subject: [PATCH 20/57] make sure to callback after consuming candle --- plugins/eventLogger.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/eventLogger.js b/plugins/eventLogger.js index 599640ec8..993869519 100644 --- a/plugins/eventLogger.js +++ b/plugins/eventLogger.js @@ -2,12 +2,13 @@ const log = require('../core/log'); const _ = require('lodash'); const subscriptions = require('../subscriptions'); - -var EventLogger = function() {} +const EventLogger = function() {} _.each(subscriptions, sub => { - EventLogger.prototype[sub.handler] = event => { + EventLogger.prototype[sub.handler] = (event, next) => { log.info(`[EVENT ${sub.event}]\n`, event); + if(_.isFunction(next)) + next(); } }); From d29f7859e3512c9225de00866d1f26383b7079d1 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Thu, 22 Feb 2018 16:39:34 +0800 Subject: [PATCH 21/57] var cleanup --- plugins/tradingAdvisor/baseTradingMethod.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/tradingAdvisor/baseTradingMethod.js b/plugins/tradingAdvisor/baseTradingMethod.js index 0f7f86d5f..f3248a173 100644 --- a/plugins/tradingAdvisor/baseTradingMethod.js +++ b/plugins/tradingAdvisor/baseTradingMethod.js @@ -214,8 +214,9 @@ Base.prototype.propogateTick = function(candle) { var isPremature = false; if(mode === 'realtime'){ - let startTimeMinusCandleSize = startTime.clone(); - startTimeMinusCandleSize.subtract(this.tradingAdvisor.candleSize, "minutes"); + const startTimeMinusCandleSize = startTime + .clone() + .subtract(this.tradingAdvisor.candleSize, "minutes"); isPremature = candle.start < startTimeMinusCandleSize; } @@ -302,8 +303,7 @@ Base.prototype.addIndicator = function(name, type, parameters) { } Base.prototype.advice = function(newPosition, _candle) { - // ignore soft advice coming from legacy - // strategies. + // ignore legacy soft advice if(!newPosition) return; From 39936386a012b08ab7ec830ad5bfd4e989b22348 Mon Sep 17 00:00:00 2001 From: Clifford Roche Date: Tue, 13 Feb 2018 18:33:35 -0500 Subject: [PATCH 22/57] Fix issue with trade events being deferred too long --- plugins/tradingAdvisor/baseTradingMethod.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/plugins/tradingAdvisor/baseTradingMethod.js b/plugins/tradingAdvisor/baseTradingMethod.js index f3248a173..293e19a89 100644 --- a/plugins/tradingAdvisor/baseTradingMethod.js +++ b/plugins/tradingAdvisor/baseTradingMethod.js @@ -319,13 +319,11 @@ Base.prototype.advice = function(newPosition, _candle) { this._prevAdvice = newPosition; - _.defer(function() { - this.emit('advice', { - recommendation: newPosition, - portfolio: 1, - candle - }); - }.bind(this)); + this.emit('advice', { + recommendation: newPosition, + portfolio: 1, + candle + }); } // Because the trading method might be async we need From b722b173b432b68ed1fb5fc14625a5d7b7975bd1 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Sat, 3 Mar 2018 15:45:54 +0800 Subject: [PATCH 23/57] force order of market events --- core/budfox/budfox.js | 24 +++++++++++------------ core/budfox/candleCreator.js | 2 +- core/budfox/marketDataProvider.js | 4 ++-- core/eventLogger.js | 32 ------------------------------- docs/internals/events.md | 14 +++++--------- 5 files changed, 20 insertions(+), 56 deletions(-) delete mode 100644 core/eventLogger.js diff --git a/core/budfox/budfox.js b/core/budfox/budfox.js index f6d068227..26d371f8e 100644 --- a/core/budfox/budfox.js +++ b/core/budfox/budfox.js @@ -30,18 +30,6 @@ var BudFox = function(config) { // BudFox data flow: - // on every `tick` retrieve trade data - this.heart.on( - 'tick', - this.marketDataProvider.retrieve - ); - - // on new trade data create candles - this.marketDataProvider.on( - 'trades', - this.candleManager.processTrades - ); - // relay a marketUpdate event this.marketDataProvider.on( 'marketUpdate', @@ -60,6 +48,18 @@ var BudFox = function(config) { this.pushCandles ); + // on every `tick` retrieve trade data + this.heart.on( + 'tick', + this.marketDataProvider.retrieve + ); + + // on new trade data create candles + this.marketDataProvider.on( + 'trades', + this.candleManager.processTrades + ); + this.heart.pump(); } diff --git a/core/budfox/candleCreator.js b/core/budfox/candleCreator.js index c5f39e353..36c094041 100644 --- a/core/budfox/candleCreator.js +++ b/core/budfox/candleCreator.js @@ -109,7 +109,7 @@ CandleCreator.prototype.calculateCandles = function() { // catch error from high volume getTrades if (this.lastTrade !== undefined) - // create a string referencing to minute this trade happened in + // create a string referencing the minute this trade happened in var lastMinute = this.lastTrade.date.format('YYYY-MM-DD HH:mm'); var candles = _.map(this.buckets, function(bucket, name) { diff --git a/core/budfox/marketDataProvider.js b/core/budfox/marketDataProvider.js index 83eed4ebc..2cf3cd9cc 100644 --- a/core/budfox/marketDataProvider.js +++ b/core/budfox/marketDataProvider.js @@ -33,10 +33,10 @@ Manager.prototype.retrieve = function() { Manager.prototype.relayTrades = function(batch) { - this.emit('trades', batch); - this.sendMarketStart(batch); this.emit('marketUpdate', batch.last.date); + + this.emit('trades', batch); } Manager.prototype.sendMarketStart = _.once(function(batch) { diff --git a/core/eventLogger.js b/core/eventLogger.js deleted file mode 100644 index addc5fc2c..000000000 --- a/core/eventLogger.js +++ /dev/null @@ -1,32 +0,0 @@ -var _ = require('lodash'); - -var util = require('./util'); -var dirs = util.dirs(); -var log = require(dirs.core + 'log'); - -var EventLogger = function() { - _.bindAll(this); -} - -var subscriptions = require(dirs.core + 'subscriptions'); -_.each(subscriptions, function(subscription) { - EventLogger.prototype[subscription.handler] = function(e) { - if(subscription.event === 'tick') - log.empty(); - - if(_.has(e, 'data')) - log.debug( - '\tnew event:', - subscription.event, - '(' + _.size(e.data), - 'items)' - ); - else - log.debug( - '\tnew event:', - subscription.event - ); - } -}); - -module.exports = EventLogger; diff --git a/docs/internals/events.md b/docs/internals/events.md index 731d4b799..3a5777426 100644 --- a/docs/internals/events.md +++ b/docs/internals/events.md @@ -24,7 +24,7 @@ Note that all events from Gekko come from a plugin (with the exception of the `c - [performanceReport](#performanceReport-event): Every time the profit report was updated. - [roundtrip](#roundtrip-event): Every time a new roundtrip has been completed. -Beside those there are also two additional market events, note that those are only emitted when Gekko is running in either realtime or importing mode (NOT during a backtest for performance reasons). +Beside those there are also two additional market events that are only emitted when Gekko is running in either realtime or importing mode (NOT during a backtest for performance reasons). - [marketStart](#marketStart-event): Once, when the market just started. - [marketUpdate](#marketUpdate-event): Whenever the market has fetched new raw market data. @@ -59,14 +59,10 @@ Beside those there are also two additional market events, note that those are on - This event is not guaranteed to happen before any possible advice of the same candle, this situation can happen when the strategy uses async indicators (for example from TAlib or Tulip). - Example: { - start: [moment object of the start time of the candle], - open: [number, open of candle], - high: [number, high of candle], - low: [number, low of candle], - close: [number, close of candle], - vwp: [number, average weighted price of candle], - volume: [number, total volume volume], - trades: [number, amount of trades] + date: [moment object of the start time of the candle], + indicators: { + mymacd: [number, result of running this indicator over current candle] + } } ### stratWarmupCompleted event From 410dff5a9d36bb3c47f17e126f1c12ac7f29d8e4 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Mon, 5 Mar 2018 16:31:31 +0700 Subject: [PATCH 24/57] remove cpRelay out of performance analyzer --- plugins/performanceAnalyzer/cpRelay.js | 28 ----------- .../performanceAnalyzer.js | 48 +++++++++---------- 2 files changed, 23 insertions(+), 53 deletions(-) delete mode 100644 plugins/performanceAnalyzer/cpRelay.js diff --git a/plugins/performanceAnalyzer/cpRelay.js b/plugins/performanceAnalyzer/cpRelay.js deleted file mode 100644 index c43c35c6c..000000000 --- a/plugins/performanceAnalyzer/cpRelay.js +++ /dev/null @@ -1,28 +0,0 @@ -// relay paper trade results using cp - -const _ = require('lodash'); -const moment = require('moment'); - -const util = require('../../core/util.js'); -const dirs = util.dirs(); -const mode = util.gekkoMode(); -const log = require(dirs.core + 'log'); -const cp = require(dirs.core + 'cp'); - -const Relay = function() {} - -Relay.prototype.handleTrade = function(trade, report) { - cp.trade(trade); - cp.report(report); -} - -Relay.prototype.handleRoundtrip = function(rt) { - cp.roundtrip(rt); -} - -Relay.prototype.finalize = function(report) { - cp.report(report); -} - - -module.exports = Relay; \ No newline at end of file diff --git a/plugins/performanceAnalyzer/performanceAnalyzer.js b/plugins/performanceAnalyzer/performanceAnalyzer.js index 0e5ede9dc..f0cc0b8be 100644 --- a/plugins/performanceAnalyzer/performanceAnalyzer.js +++ b/plugins/performanceAnalyzer/performanceAnalyzer.js @@ -1,3 +1,4 @@ + const _ = require('lodash'); const moment = require('moment'); @@ -9,12 +10,7 @@ const config = util.getConfig(); const perfConfig = config.performanceAnalyzer; const watchConfig = config.watch; -// Load the proper module that handles the results -var Handler; -if(ENV === 'child-process') - Handler = require('./cpRelay'); -else - Handler = require('./logger'); +const Logger = require('./logger'); const PerformanceAnalyzer = function() { _.bindAll(this); @@ -30,7 +26,7 @@ const PerformanceAnalyzer = function() { this.currency = watchConfig.currency; this.asset = watchConfig.asset; - this.handler = new Handler(watchConfig); + this.logger = new Logger(watchConfig); this.trades = 0; @@ -43,7 +39,11 @@ const PerformanceAnalyzer = function() { } } +// teach our plugin events +util.makeEventEmitter(PerformanceAnalyzer); + PerformanceAnalyzer.prototype.processCandle = function(candle, done) { + console.log('processCandle'); this.price = candle.close; this.dates.end = candle.start; @@ -57,17 +57,19 @@ PerformanceAnalyzer.prototype.processCandle = function(candle, done) { done(); } -PerformanceAnalyzer.prototype.processPortfolioUpdate = function(portfolio) { - this.start = portfolio; - this.current = _.clone(portfolio); -} +// PerformanceAnalyzer.prototype.processPortfolioUpdate = function(portfolio) { +// this.start = portfolio; +// this.current = _.clone(portfolio); +// } PerformanceAnalyzer.prototype.processTrade = function(trade) { + console.log('processTrade'); this.trades++; this.current = trade.portfolio; const report = this.calculateReportStatistics(); - this.handler.handleTrade(trade, report); + + this.logger.handleTrade(trade, report); this.logRoundtripPart(trade); } @@ -95,10 +97,6 @@ PerformanceAnalyzer.prototype.logRoundtripPart = function(trade) { } } -PerformanceAnalyzer.prototype.round = function(amount) { - return amount.toFixed(8); -} - PerformanceAnalyzer.prototype.handleRoundtrip = function() { var roundtrip = { entryAt: this.roundTrip.entry.date, @@ -116,7 +114,7 @@ PerformanceAnalyzer.prototype.handleRoundtrip = function() { roundtrip.profit = (100 * roundtrip.exitBalance / roundtrip.entryBalance) - 100; this.roundTrips.push(roundtrip); - this.handler.handleRoundtrip(roundtrip); + this.logger.handleRoundtrip(roundtrip); // we need a cache for sharpe @@ -130,15 +128,15 @@ PerformanceAnalyzer.prototype.handleRoundtrip = function() { PerformanceAnalyzer.prototype.calculateReportStatistics = function() { // the portfolio's balance is measured in {currency} - let balance = this.current.currency + this.price * this.current.asset; - let profit = balance - this.start.balance; + const balance = this.current.currency + this.price * this.current.asset; + const profit = balance - this.start.balance; - let timespan = moment.duration( + const timespan = moment.duration( this.dates.end.diff(this.dates.start) ); - let relativeProfit = balance / this.start.balance * 100 - 100 + const relativeProfit = balance / this.start.balance * 100 - 100; - let report = { + const report = { currency: this.currency, asset: this.asset, @@ -151,8 +149,8 @@ PerformanceAnalyzer.prototype.calculateReportStatistics = function() { profit: profit, relativeProfit: relativeProfit, - yearlyProfit: this.round(profit / timespan.asYears()), - relativeYearlyProfit: this.round(relativeProfit / timespan.asYears()), + yearlyProfit: profit / timespan.asYears(), + relativeYearlyProfit: relativeProfit / timespan.asYears(), startPrice: this.startPrice, endPrice: this.endPrice, @@ -168,7 +166,7 @@ PerformanceAnalyzer.prototype.calculateReportStatistics = function() { PerformanceAnalyzer.prototype.finalize = function(done) { const report = this.calculateReportStatistics(); - this.handler.finalize(report); + this.logger.finalize(report); done(); } From 0eabf0810af4bfb9fdd55827df29b7d9fe376b4e Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Mon, 5 Mar 2018 16:47:39 +0700 Subject: [PATCH 25/57] make sure we dont report on no trades --- plugins/performanceAnalyzer/performanceAnalyzer.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/plugins/performanceAnalyzer/performanceAnalyzer.js b/plugins/performanceAnalyzer/performanceAnalyzer.js index f0cc0b8be..9bd4c4ee5 100644 --- a/plugins/performanceAnalyzer/performanceAnalyzer.js +++ b/plugins/performanceAnalyzer/performanceAnalyzer.js @@ -43,7 +43,6 @@ const PerformanceAnalyzer = function() { util.makeEventEmitter(PerformanceAnalyzer); PerformanceAnalyzer.prototype.processCandle = function(candle, done) { - console.log('processCandle'); this.price = candle.close; this.dates.end = candle.start; @@ -57,13 +56,7 @@ PerformanceAnalyzer.prototype.processCandle = function(candle, done) { done(); } -// PerformanceAnalyzer.prototype.processPortfolioUpdate = function(portfolio) { -// this.start = portfolio; -// this.current = _.clone(portfolio); -// } - PerformanceAnalyzer.prototype.processTrade = function(trade) { - console.log('processTrade'); this.trades++; this.current = trade.portfolio; @@ -165,6 +158,10 @@ PerformanceAnalyzer.prototype.calculateReportStatistics = function() { } PerformanceAnalyzer.prototype.finalize = function(done) { + if(!_.size(this.trades)) { + return done(); + } + const report = this.calculateReportStatistics(); this.logger.finalize(report); done(); From decddd854382342c32f4351ac9ea9ff8701984a7 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Mon, 5 Mar 2018 16:59:26 +0700 Subject: [PATCH 26/57] rm mentions of simulated --- plugins/performanceAnalyzer/logger.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/plugins/performanceAnalyzer/logger.js b/plugins/performanceAnalyzer/logger.js index b2a176bb8..e9d655743 100644 --- a/plugins/performanceAnalyzer/logger.js +++ b/plugins/performanceAnalyzer/logger.js @@ -1,4 +1,4 @@ -// log paper trade results using the logger +// log trade performance results const _ = require('lodash'); const moment = require('moment'); @@ -7,8 +7,6 @@ const humanizeDuration = require('humanize-duration'); const util = require('../../core/util.js'); const dirs = util.dirs(); const mode = util.gekkoMode(); -const config = util.getConfig(); -const calcConfig = config.paperTrader; const log = require(dirs.core + 'log'); const Logger = function(watchConfig) { @@ -22,10 +20,6 @@ Logger.prototype.round = function(amount) { return amount.toFixed(8); } -Logger.prototype.handleStartBalance = function() { - // noop -} - // used for: // - realtime logging (per advice) // - backtest logging (on finalize) @@ -35,10 +29,10 @@ Logger.prototype.logReport = function(trade, report) { var start = this.round(report.startBalance); var current = this.round(report.balance); - log.info(`(PROFIT REPORT) original simulated balance:\t ${start} ${this.currency}`); - log.info(`(PROFIT REPORT) current simulated balance:\t ${current} ${this.currency}`); + log.info(`(PROFIT REPORT) original balance:\t ${start} ${this.currency}`); + log.info(`(PROFIT REPORT) current balance:\t ${current} ${this.currency}`); log.info( - `(PROFIT REPORT) simulated profit:\t\t ${this.round(report.profit)} ${this.currency}`, + `(PROFIT REPORT) profit:\t\t ${this.round(report.profit)} ${this.currency}`, `(${this.round(report.relativeProfit)}%)` ); } From eeec247197dd17eb5cfbefc8d2c7f90332e49c7a Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Fri, 16 Mar 2018 16:18:00 +0700 Subject: [PATCH 27/57] defer processCandle until the strat is completely done processing the candle --- core/candleBatcher.js | 17 ++- docs/internals/events.md | 6 +- plugins/tradingAdvisor/baseTradingMethod.js | 111 ++++++++++---------- plugins/tradingAdvisor/tradingAdvisor.js | 15 ++- test/candleBatcher.js | 11 ++ 5 files changed, 99 insertions(+), 61 deletions(-) diff --git a/core/candleBatcher.js b/core/candleBatcher.js index 713fd4c10..f773e4126 100644 --- a/core/candleBatcher.js +++ b/core/candleBatcher.js @@ -18,6 +18,7 @@ var CandleBatcher = function(candleSize) { this.candleSize = candleSize; this.smallCandles = []; + this.calculatedCandles = []; _.bindAll(this); } @@ -28,20 +29,34 @@ CandleBatcher.prototype.write = function(candles) { if(!_.isArray(candles)) throw 'candles is not an array'; + this.emitted = 0; + _.each(candles, function(candle) { this.smallCandles.push(candle); this.check(); }, this); + + return this.emitted; } CandleBatcher.prototype.check = function() { if(_.size(this.smallCandles) % this.candleSize !== 0) return; - this.emit('candle', this.calculate()); + this.emitted++; + this.calculatedCandles.push(this.calculate()); this.smallCandles = []; } +CandleBatcher.prototype.flush = function() { + _.each( + this.calculatedCandles, + candle => this.emit('candle', candle) + ); + + this.calculatedCandles = []; +} + CandleBatcher.prototype.calculate = function() { var first = this.smallCandles.shift(); diff --git a/docs/internals/events.md b/docs/internals/events.md index 3a5777426..37111bcb8 100644 --- a/docs/internals/events.md +++ b/docs/internals/events.md @@ -86,7 +86,7 @@ and will start signaling advice. - Example: { recommendation: [position to take, either long or short], - portfolio: [amount of portfolio you should move to position] **DECREPATED** + date: [moment object of this advice] } ### tradeInitiated event @@ -96,6 +96,7 @@ and will start signaling advice. - Subscribe: Your plugin can subscribe to this event by registering the `processTradeInitiated` method. - Example: { + id: [number identifying this unique trade] action: [either "buy" or "sell"], date: [moment object, exchange time trade completed at], portfolio: [object containing amount in currency and asset], @@ -109,6 +110,7 @@ and will start signaling advice. - Subscribe: Your plugin can subscribe to this event by registering the `processTradeAborted` method. - Example: { + id: [number identifying this unique trade] action: [either "buy" or "sell"], date: [moment object, exchange time trade completed at], portfolio: [object containing amount in currency and asset], @@ -123,8 +125,10 @@ and will start signaling advice. - Subscribe: Your plugin can subscribe to this event by registering the `processTradeCompleted` method. - Example: { + id: [number identifying this unique trade] action: [either "buy" or "sell"], price: [number, average price that was sold at], + cost: [ideal execution cost - ], date: [moment object, exchange time trade completed at], portfolio: [object containing amount in currency and asset], balance: [number, total worth of portfolio] diff --git a/plugins/tradingAdvisor/baseTradingMethod.js b/plugins/tradingAdvisor/baseTradingMethod.js index 293e19a89..f073c26ff 100644 --- a/plugins/tradingAdvisor/baseTradingMethod.js +++ b/plugins/tradingAdvisor/baseTradingMethod.js @@ -100,7 +100,8 @@ var Base = function(settings) { // teach our base trading method events util.makeEventEmitter(Base); -Base.prototype.tick = function(candle) { + +Base.prototype.tick = function(candle, done) { if( this.asyncTick && @@ -113,7 +114,8 @@ Base.prototype.tick = function(candle) { // updated with future candles. // // See @link: https://github.com/askmike/gekko/issues/837#issuecomment-316549691 - return this.deferredTicks.push(candle); + this.deferredTicks.push(candle); + return done(); } this.age++; @@ -150,53 +152,59 @@ Base.prototype.tick = function(candle) { // update the trading method if(!this.asyncTick) { this.propogateTick(candle); - } else { - var next = _.after( - _.size(this.talibIndicators) + _.size(this.tulipIndicators), - () => this.propogateTick(candle) - ); - - var basectx = this; + return done(); + } - // handle result from talib - var talibResultHander = function(err, result) { - if(err) - util.die('TALIB ERROR:', err); + this.tickDone = done; - // fn is bound to indicator - this.result = _.mapValues(result, v => _.last(v)); - next(candle); + var next = _.after( + _.size(this.talibIndicators) + _.size(this.tulipIndicators), + () => { + this.propogateTick(candle); + this.tickDone(); } + ); - // handle result from talib - _.each( - this.talibIndicators, - indicator => indicator.run( - basectx.candleProps, - talibResultHander.bind(indicator) - ) - ); - - // handle result from tulip - var tulindResultHander = function(err, result) { - if(err) - util.die('TULIP ERROR:', err); - - // fn is bound to indicator - this.result = _.mapValues(result, v => _.last(v)); - next(candle); - } + var basectx = this; - // handle result from tulip indicators - _.each( - this.tulipIndicators, - indicator => indicator.run( - basectx.candleProps, - tulindResultHander.bind(indicator) - ) - ); + // handle result from talib + var talibResultHander = function(err, result) { + if(err) + util.die('TALIB ERROR:', err); + + // fn is bound to indicator + this.result = _.mapValues(result, v => _.last(v)); + next(candle); + } + + // handle result from talib + _.each( + this.talibIndicators, + indicator => indicator.run( + basectx.candleProps, + talibResultHander.bind(indicator) + ) + ); + + // handle result from tulip + var tulindResultHander = function(err, result) { + if(err) + util.die('TULIP ERROR:', err); + + // fn is bound to indicator + this.result = _.mapValues(result, v => _.last(v)); + next(candle); } + + // handle result from tulip indicators + _.each( + this.tulipIndicators, + indicator => indicator.run( + basectx.candleProps, + tulindResultHander.bind(indicator) + ) + ); } Base.prototype.propogateTick = function(candle) { @@ -244,9 +252,8 @@ Base.prototype.propogateTick = function(candle) { } this.emit('stratUpdate', { - start: candle.start, - // TODO: add indicator results - }) + date: candle.start, + }); // are we totally finished? var done = this.age === this.processedTicks; @@ -302,7 +309,7 @@ Base.prototype.addIndicator = function(name, type, parameters) { // some indicators need a price stream, others need full candles } -Base.prototype.advice = function(newPosition, _candle) { +Base.prototype.advice = function(newPosition) { // ignore legacy soft advice if(!newPosition) return; @@ -311,18 +318,12 @@ Base.prototype.advice = function(newPosition, _candle) { if(newPosition === this._prevAdvice) return; - // cache the candle this advice is based on - if(_candle) - var candle = _candle; - else - var candle = this.candle; - this._prevAdvice = newPosition; + console.log('emitting advice', newPosition); + this.emit('advice', { - recommendation: newPosition, - portfolio: 1, - candle + recommendation: newPosition }); } diff --git a/plugins/tradingAdvisor/tradingAdvisor.js b/plugins/tradingAdvisor/tradingAdvisor.js index 9ae4c79ec..fe939c515 100644 --- a/plugins/tradingAdvisor/tradingAdvisor.js +++ b/plugins/tradingAdvisor/tradingAdvisor.js @@ -78,14 +78,20 @@ Actor.prototype.setupTradingMethod = function() { // HANDLERS // process the 1m candles Actor.prototype.processCandle = function(candle, done) { - this.batcher.write([candle]); - done(); + this.candle = candle; + const completedBatches = this.batcher.write([candle]); + if(completedBatches) { + this.next = _.after(completedBatches, done); + } else { + done(); + this.next = _.noop; + } + this.batcher.flush(); } // propogate a custom sized candle to the trading method Actor.prototype.processStratCandle = function(candle) { - this.method.tick(candle); - this.emit('stratUpdate', candle); + this.method.tick(candle, this.next); } // pass through shutdown handler @@ -95,6 +101,7 @@ Actor.prototype.finish = function(done) { // EMITTERS Actor.prototype.relayAdvice = function(advice) { + advice.date = this.candle.start.clone().add(1, 'minute'); this.emit('advice', advice); } diff --git a/test/candleBatcher.js b/test/candleBatcher.js index 0cafadf62..034d6b604 100644 --- a/test/candleBatcher.js +++ b/test/candleBatcher.js @@ -53,12 +53,22 @@ describe('core/candleBatcher', function() { expect(spy.called).to.be.false; }); + it('should not emit an event when not flushed', function() { + cb = new CandleBatcher(2); + + var spy = sinon.spy(); + cb.on('candle', spy); + cb.write( candles ); + expect(spy.called).to.be.false; + }); + it('should emit 5 events when fed 10 candles', function() { cb = new CandleBatcher(2); var spy = sinon.spy(); cb.on('candle', spy); cb.write( candles ); + cb.flush(); expect(spy.callCount).to.equal(5); }); @@ -84,6 +94,7 @@ describe('core/candleBatcher', function() { var spy = sinon.spy(); cb.on('candle', spy); cb.write( _candles ); + cb.flush(); var cbResult = _.first(_.first(spy.args)); expect(cbResult).to.deep.equal(result); From 505ed0185ba1b3c09359a1b28be04ae6489d3a05 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Mon, 19 Mar 2018 14:39:40 +0700 Subject: [PATCH 28/57] implement tradeInitialized & tradeCompleted events --- plugins/paperTrader/paperTrader.js | 67 +++++++++++++++++------------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/plugins/paperTrader/paperTrader.js b/plugins/paperTrader/paperTrader.js index 1bbb720c9..228c32165 100644 --- a/plugins/paperTrader/paperTrader.js +++ b/plugins/paperTrader/paperTrader.js @@ -25,28 +25,6 @@ const PaperTrader = function() { // teach our paper trader events util.makeEventEmitter(PaperTrader); -PaperTrader.prototype.relayTrade = function(advice) { - var what = advice.recommendation; - var price = advice.candle.close; - var at = advice.candle.start; - - let action; - if(what === 'short') - action = 'sell'; - else if(what === 'long') - action = 'buy'; - else - return; - - this.emit('trade', { - action, - price, - portfolio: _.clone(this.portfolio), - balance: this.portfolio.currency + this.price * this.portfolio.asset, - date: at - }); -} - PaperTrader.prototype.relayPortfolio = function() { this.emit('portfolioUpdate', _.clone(this.portfolio)); } @@ -69,12 +47,14 @@ PaperTrader.prototype.setStartBalance = function() { // calculates Gekko's profit in %. PaperTrader.prototype.updatePosition = function(advice) { let what = advice.recommendation; - let price = advice.candle.close; + + let executionPrice; // virtually trade all {currency} to {asset} // at the current price (minus fees) if(what === 'long') { - this.portfolio.asset += this.extractFee(this.portfolio.currency / price); + this.portfolio.asset += this.extractFee(this.portfolio.currency / this.price); + executionPrice = this.extractFee(this.price); this.portfolio.currency = 0; this.trades++; } @@ -82,18 +62,49 @@ PaperTrader.prototype.updatePosition = function(advice) { // virtually trade all {currency} to {asset} // at the current price (minus fees) else if(what === 'short') { - this.portfolio.currency += this.extractFee(this.portfolio.asset * price); + this.portfolio.currency += this.extractFee(this.portfolio.asset * this.price); + executionPrice = this.price + this.price - this.extractFee(this.price); this.portfolio.asset = 0; this.trades++; } + + return executionPrice; +} + +PaperTrader.prototype.getPortfolio = function() { + this.portfolio.balance = this.portfolio.currency + this.price * this.portfolio.asset; + return _.clone(this.portfolio); } PaperTrader.prototype.processAdvice = function(advice) { - if(advice.recommendation === 'soft') + let action; + if(advice.recommendation === 'short') + action = 'sell'; + else if(advice.recommendation === 'long') + action = 'buy'; + else return; - this.updatePosition(advice); - this.relayTrade(advice); + this.tradeId = _.uniqueId(); + + this.emit('tradeInitiated', { + id: this.tradeId, + action, + portfolio: this.getPortfolio(), + date: advice.date, + }); + + const executionPrice = this.updatePosition(advice); + console.log('price', this.price); + + this.emit('tradeCompleted', { + id: this.tradeId, + action, + price: executionPrice, + portfolio: this.getPortfolio(), + date: advice.date + }); + } PaperTrader.prototype.processCandle = function(candle, done) { From 4d52ca907d14f39463538c4a524ce2eb5c7066f1 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Mon, 19 Mar 2018 17:35:59 +0700 Subject: [PATCH 29/57] use a LIFO stack based event emittor --- core/emitter.js | 34 +++++++++++++++++++++ core/gekkoStream.js | 26 ++++++++++++++-- plugins/paperTrader/paperTrader.js | 9 +++--- plugins/tradingAdvisor/tradingAdvisor.js | 39 ++++++++++++++++++++++-- 4 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 core/emitter.js diff --git a/core/emitter.js b/core/emitter.js new file mode 100644 index 000000000..aa589cd8b --- /dev/null +++ b/core/emitter.js @@ -0,0 +1,34 @@ +// Gekko uses a custom event emitter within the GekkoStream (the plugins) to guarantee +// the correct order of events that are triggered by eachother. Turns sync events from +// FIFO into a LIFO stack based model. +// +// More details here: https://github.com/askmike/gekko/pull/1850#issuecomment-364842963 + +const util = require('util'); +const events = require('events'); +const NativeEventEmitter = events.EventEmitter; + +const GekkoEventEmitter = function() { + NativeEventEmitter.call(this); + this.defferedEvents = []; +} + +util.inherits(GekkoEventEmitter, NativeEventEmitter); + +// push to stack +GekkoEventEmitter.prototype.deferredEmit = function(name, payload) { + this.defferedEvents.push({name, payload}); +} + +// resolve LIFO +GekkoEventEmitter.prototype.broadcastDeferredEmit = function() { + if(this.defferedEvents.length === 0) + return false; + + const event = this.defferedEvents.shift(); + + this.emit(event.name, event.payload); + return true; +} + +module.exports = GekkoEventEmitter; \ No newline at end of file diff --git a/core/gekkoStream.js b/core/gekkoStream.js index 521a80531..ebb93b235 100644 --- a/core/gekkoStream.js +++ b/core/gekkoStream.js @@ -16,6 +16,9 @@ var Gekko = function(candleConsumers) { this.candleConsumers = candleConsumers; Writable.call(this, {objectMode: true}); + this.defferedProducers = this.candleConsumers + .filter(p => p.broadcastDeferredEmit); + this.finalize = _.bind(this.finalize, this); } @@ -24,6 +27,7 @@ Gekko.prototype = Object.create(Writable.prototype, { }); if(config.debug) { + // decorate with more debug information Gekko.prototype._write = function(chunk, encoding, _done) { const start = moment(); @@ -41,23 +45,39 @@ if(config.debug) { const done = _.after(this.candleConsumers.length, () => { relayed = true; clearInterval(timer); + this.flushDefferedEvents(); _done(); }); _.each(this.candleConsumers, function(c) { at = c.meta.name; c.processCandle(chunk, done); - }); + }, this); } } else { // skip decoration Gekko.prototype._write = function(chunk, encoding, _done) { - const done = _.after(this.candleConsumers.length, _done); + const done = _.after(this.candleConsumers.length, () => { + this.flushDefferedEvents(); + _done(); + }); _.each(this.candleConsumers, function(c) { c.processCandle(chunk, done); - }); + }, this); } } +Gekko.prototype.flushDefferedEvents = function() { + const broadcasted = _.find( + this.defferedProducers, + producer => producer.broadcastDeferredEmit() + ); + + // If we braodcasted anything we might have + // triggered more events, recurse until we + // have fully broadcasted everything. + if(broadcasted) + this.flushDefferedEvents(); +} Gekko.prototype.finalize = function() { var tradingMethod = _.find( diff --git a/plugins/paperTrader/paperTrader.js b/plugins/paperTrader/paperTrader.js index 228c32165..698baef33 100644 --- a/plugins/paperTrader/paperTrader.js +++ b/plugins/paperTrader/paperTrader.js @@ -22,9 +22,6 @@ const PaperTrader = function() { } } -// teach our paper trader events -util.makeEventEmitter(PaperTrader); - PaperTrader.prototype.relayPortfolio = function() { this.emit('portfolioUpdate', _.clone(this.portfolio)); } @@ -77,6 +74,7 @@ PaperTrader.prototype.getPortfolio = function() { } PaperTrader.prototype.processAdvice = function(advice) { + console.log('PaperTrader.prototype.processAdvice'); let action; if(advice.recommendation === 'short') action = 'sell'; @@ -87,7 +85,7 @@ PaperTrader.prototype.processAdvice = function(advice) { this.tradeId = _.uniqueId(); - this.emit('tradeInitiated', { + this.deferredEmit('tradeInitiated', { id: this.tradeId, action, portfolio: this.getPortfolio(), @@ -97,7 +95,7 @@ PaperTrader.prototype.processAdvice = function(advice) { const executionPrice = this.updatePosition(advice); console.log('price', this.price); - this.emit('tradeCompleted', { + this.deferredEmit('tradeCompleted', { id: this.tradeId, action, price: executionPrice, @@ -108,6 +106,7 @@ PaperTrader.prototype.processAdvice = function(advice) { } PaperTrader.prototype.processCandle = function(candle, done) { + console.log('PaperTrader.prototype.processCandle'); this.price = candle.close; if(!this.portfolio.balance) diff --git a/plugins/tradingAdvisor/tradingAdvisor.js b/plugins/tradingAdvisor/tradingAdvisor.js index fe939c515..6a029e6a0 100644 --- a/plugins/tradingAdvisor/tradingAdvisor.js +++ b/plugins/tradingAdvisor/tradingAdvisor.js @@ -11,6 +11,35 @@ var CandleBatcher = require(dirs.core + 'candleBatcher'); var moment = require('moment'); var isLeecher = config.market && config.market.type === 'leech'; +const Emitter = require(util.dirs().core + 'emitter'); + +// const a = new Emitter(); +// console.log(a.deferredEmit); +// throw 'a'; + +// const makeEventEmitter = ctx => { +// ctx.prototype = Object.create(MyEmitter); + +// } + + +// class TradingAdviser extends Emitter { +// constructor() { +// super(); + +// this.on('bla', () => console.log('asdasdasd')); + +// // console.log(this.on, this.defferedEmit); + +// this.defferedEmit('bla', 1); +// // this.broadcastDeferredEmit(); +// } +// } + +// var a = new TradingAdviser; +// module.exports = TradingAdviser; +// return; + var Actor = function(done) { _.bindAll(this); @@ -22,6 +51,8 @@ var Actor = function(done) { this.setupTradingMethod(); + // makeEventEmitter(this); + var mode = util.gekkoMode(); // the stitcher will try to pump in historical data @@ -36,8 +67,6 @@ var Actor = function(done) { done(); } -util.makeEventEmitter(Actor); - Actor.prototype.setupTradingMethod = function() { if(!fs.existsSync(dirs.methods + this.methodName + '.js')) @@ -102,8 +131,12 @@ Actor.prototype.finish = function(done) { // EMITTERS Actor.prototype.relayAdvice = function(advice) { advice.date = this.candle.start.clone().add(1, 'minute'); - this.emit('advice', advice); + this.deferredEmit('advice', advice); } +// var a = new Actor(_.noop); +// console.log(a.defferedEvents); +// throw 'a'; + module.exports = Actor; From d05b4fe5ffdead75cf2ea0282ed2fabaa9c2775b Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Tue, 20 Mar 2018 16:27:06 +0700 Subject: [PATCH 30/57] make all plugins fifo event emitters --- core/emitter.js | 4 ++-- core/pluginUtil.js | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/core/emitter.js b/core/emitter.js index aa589cd8b..97e053682 100644 --- a/core/emitter.js +++ b/core/emitter.js @@ -1,6 +1,6 @@ // Gekko uses a custom event emitter within the GekkoStream (the plugins) to guarantee // the correct order of events that are triggered by eachother. Turns sync events from -// FIFO into a LIFO stack based model. +// LIFO into a FIFO stack based model. // // More details here: https://github.com/askmike/gekko/pull/1850#issuecomment-364842963 @@ -20,7 +20,7 @@ GekkoEventEmitter.prototype.deferredEmit = function(name, payload) { this.defferedEvents.push({name, payload}); } -// resolve LIFO +// resolve FIFO GekkoEventEmitter.prototype.broadcastDeferredEmit = function() { if(this.defferedEvents.length === 0) return false; diff --git a/core/pluginUtil.js b/core/pluginUtil.js index e3a5dbacc..1c28c977a 100644 --- a/core/pluginUtil.js +++ b/core/pluginUtil.js @@ -1,5 +1,6 @@ var _ = require('lodash'); var async = require('async'); +var Emitter = require('./emitter'); var util = require(__dirname + '/util'); @@ -8,6 +9,7 @@ var log = require(util.dirs().core + 'log'); var config = util.getConfig(); var pluginDir = util.dirs().plugins; var gekkoMode = util.gekkoMode(); +var inherits = require('util').inherits; var pluginHelper = { // Checks whether we can load a module @@ -92,12 +94,18 @@ var pluginHelper = { var Constructor = require(pluginDir + plugin.slug); if(plugin.async) { + inherits(Constructor, Emitter); var instance = new Constructor(util.defer(function(err) { next(err, instance); }), plugin); + Emitter.call(instance); + instance.meta = plugin; } else { + inherits(Constructor, Emitter); var instance = new Constructor(plugin); + Emitter.call(instance); + instance.meta = plugin; _.defer(function() { next(null, instance); From 350efd33667865bd21a98cafebf26799d077b16a Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Tue, 20 Mar 2018 16:41:07 +0700 Subject: [PATCH 31/57] refer to blogpost with background information --- core/emitter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/emitter.js b/core/emitter.js index 97e053682..409c5d943 100644 --- a/core/emitter.js +++ b/core/emitter.js @@ -2,7 +2,7 @@ // the correct order of events that are triggered by eachother. Turns sync events from // LIFO into a FIFO stack based model. // -// More details here: https://github.com/askmike/gekko/pull/1850#issuecomment-364842963 +// More details here: https://forum.gekko.wizb.it/thread-56579.html const util = require('util'); const events = require('events'); From e7d7e56f7a0e777d4fede0c552964f9863616ecd Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Tue, 20 Mar 2018 19:17:05 +0700 Subject: [PATCH 32/57] add native gekko indicator results to stratUpdate event --- plugins/tradingAdvisor/baseTradingMethod.js | 6 ++++ plugins/tradingAdvisor/tradingAdvisor.js | 35 ++------------------- 2 files changed, 8 insertions(+), 33 deletions(-) diff --git a/plugins/tradingAdvisor/baseTradingMethod.js b/plugins/tradingAdvisor/baseTradingMethod.js index f073c26ff..b30e64d3b 100644 --- a/plugins/tradingAdvisor/baseTradingMethod.js +++ b/plugins/tradingAdvisor/baseTradingMethod.js @@ -251,8 +251,14 @@ Base.prototype.propogateTick = function(candle) { } } + const indicators = {}; + _.each(this.indicators, (indicator, name) => { + indicators[name] = indicator.result; + }); + this.emit('stratUpdate', { date: candle.start, + indicators }); // are we totally finished? diff --git a/plugins/tradingAdvisor/tradingAdvisor.js b/plugins/tradingAdvisor/tradingAdvisor.js index 6a029e6a0..f25fc69c6 100644 --- a/plugins/tradingAdvisor/tradingAdvisor.js +++ b/plugins/tradingAdvisor/tradingAdvisor.js @@ -11,35 +11,6 @@ var CandleBatcher = require(dirs.core + 'candleBatcher'); var moment = require('moment'); var isLeecher = config.market && config.market.type === 'leech'; -const Emitter = require(util.dirs().core + 'emitter'); - -// const a = new Emitter(); -// console.log(a.deferredEmit); -// throw 'a'; - -// const makeEventEmitter = ctx => { -// ctx.prototype = Object.create(MyEmitter); - -// } - - -// class TradingAdviser extends Emitter { -// constructor() { -// super(); - -// this.on('bla', () => console.log('asdasdasd')); - -// // console.log(this.on, this.defferedEmit); - -// this.defferedEmit('bla', 1); -// // this.broadcastDeferredEmit(); -// } -// } - -// var a = new TradingAdviser; -// module.exports = TradingAdviser; -// return; - var Actor = function(done) { _.bindAll(this); @@ -51,8 +22,6 @@ var Actor = function(done) { this.setupTradingMethod(); - // makeEventEmitter(this); - var mode = util.gekkoMode(); // the stitcher will try to pump in historical data @@ -93,11 +62,11 @@ Actor.prototype.setupTradingMethod = function() { .on('advice', this.relayAdvice) .on( 'stratWarmupCompleted', - e => this.emit('stratWarmupCompleted', e) + e => this.deferredEmit('stratWarmupCompleted', e) ) .on( 'stratUpdate', - e => this.emit('stratUpdate', e) + e => this.deferredEmit('stratUpdate', e) ) this.batcher From 59b5c70356022ed3701bdcd65be822e65d510259 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Fri, 23 Mar 2018 18:28:47 +0700 Subject: [PATCH 33/57] implement roundtrip, roundtripUpdate & performanceUpdate events --- docs/internals/events.md | 77 +++++++++++++++---- plugins.js | 3 +- plugins/eventLogger.js | 2 +- plugins/paperTrader/paperTrader.js | 52 +++++++++---- plugins/performanceAnalyzer/logger.js | 4 +- .../performanceAnalyzer.js | 67 +++++++++++----- subscriptions.js | 5 ++ 7 files changed, 155 insertions(+), 55 deletions(-) diff --git a/docs/internals/events.md b/docs/internals/events.md index 37111bcb8..e2e4223fa 100644 --- a/docs/internals/events.md +++ b/docs/internals/events.md @@ -54,7 +54,7 @@ Beside those there are also two additional market events that are only emitted w - What: An object describing an updated candle the strat has processed. - When: when the strategy is initialized is started. -- Subscribe: Your plugin can subscribe to this event by registering the `processStratUpdate` method. +- Subscribe: You can subscribe to this event by registering the `processStratUpdate` method. - Notes: - This event is not guaranteed to happen before any possible advice of the same candle, this situation can happen when the strategy uses async indicators (for example from TAlib or Tulip). - Example: @@ -70,7 +70,7 @@ Beside those there are also two additional market events that are only emitted w - What: An object signaling that the strategy is now completely warmed up and will start signaling advice. - When: Once the strategy consumed more market data than defined by the required history. -- Subscribe: Your plugin can subscribe to this event by registering the `processWarmupCompleted` method. +- Subscribe: You can subscribe to this event by registering the `processWarmupCompleted` method. - Notes: - This event is triggered on init when the strategy does not require any history (and thus no warmup time). - Example: @@ -80,9 +80,9 @@ and will start signaling advice. ### advice event -- What: An object containing an advice from the strategy, the advice will either be LONG or SHORT. +- What: An advice from the strategy, the advice will either be LONG or SHORT. - When: This depends on the strategy and the candleSize. -- Subscribe: Your plugin can subscribe to this event by registering the `processAdvice` method. +- Subscribe: You can subscribe to this event by registering the `processAdvice` method. - Example: { recommendation: [position to take, either long or short], @@ -93,13 +93,13 @@ and will start signaling advice. - What: An object singaling that a new trade will be executed. - When: At the same time as the advice event if the trader will try to trade. -- Subscribe: Your plugin can subscribe to this event by registering the `processTradeInitiated` method. +- Subscribe: You can subscribe to this event by registering the `processTradeInitiated` method. - Example: { id: [number identifying this unique trade] action: [either "buy" or "sell"], date: [moment object, exchange time trade completed at], - portfolio: [object containing amount in currency and asset], + portfolio: [object containing amount in currency, asset and total balance], balance: [number, total worth of portfolio] } @@ -107,22 +107,20 @@ and will start signaling advice. - What: An object singaling the fact that the trader will ignore the advice. - When: At the same time as the advice event if the trader will NOT try to trade. -- Subscribe: Your plugin can subscribe to this event by registering the `processTradeAborted` method. +- Subscribe: You can subscribe to this event by registering the `processTradeAborted` method. - Example: { id: [number identifying this unique trade] action: [either "buy" or "sell"], date: [moment object, exchange time trade completed at], - portfolio: [object containing amount in currency and asset], - balance: [number, total worth of portfolio], - reason: "Not enough funds" + reason: [string explaining why the trade was aborted] } ### tradeCompleted event -- What: An object containing details of a completed trade. +- What: Details of a completed trade. - When: Some point in time after the tradeInitiated event. -- Subscribe: Your plugin can subscribe to this event by registering the `processTradeCompleted` method. +- Subscribe: You can subscribe to this event by registering the `processTradeCompleted` method. - Example: { id: [number identifying this unique trade] @@ -138,18 +136,18 @@ and will start signaling advice. - What: An object containing new portfolio contents (amount of asset & currency). - When: Some point in time after the advice event, at the same time as the tradeCompleted event. -- Subscribe: Your plugin can subscribe to this event by registering the `processPortfolioChange` method. +- Subscribe: You can subscribe to this event by registering the `processPortfolioChange` method. - Example: { currency: [number, portfolio amount of currency], - asset: [number, portfolio amount of asset] + asset: [number, portfolio amount of asset], } ### portfolioValueChange event - What: An object containing the total portfolio worth (amount of asset & currency calculated in currency). - When: Every time the value of the portfolio has changed, if the strategy is in a LONG position this will be every minute. -- Subscribe: Your plugin can subscribe to this event by registering the `processPortfolioValueChange` method. +- Subscribe: You can subscribe to this event by registering the `processPortfolioValueChange` method. - Example: { value: [number, portfolio amount of currency] @@ -159,10 +157,55 @@ and will start signaling advice. - What: An object containing a summary of the performance of the "tradebot" (advice signals + execution). - When: At the same time as every new candle. -- Subscribe: Your plugin can subscribe to this event by registering the `processPortfolioValueChange` method. +- Subscribe: You can subscribe to this event by registering the `processPerformanceReport` method. - Example: { - value: [number, portfolio amount of currency] + startTime: Moment<'2017-03-25 19:41:00'>, + endTime: Moment<'2017-03-25 20:01:00'>, + timespan: 36000000, + market: -0.316304880517734, + balance: 1016.7200029226638, + profit: -26.789997197336106, + relativeProfit: -2.5672966425099304, + yearlyProfit: '-704041.12634599', + relativeYearlyProfit: '-67468.55576516', + startPrice: 945.80000002, + endPrice: 942.80838846, + trades: 10, + roundtrips: 5, + startBalance: 1043.5100001199999, + sharpe: -2.676305165560598 + } + +### roundtrip event + +- What: A summary of a completed roundtrip (buy + sell signal). +- When: After every roundtrip: a completed sell trade event that superceded a buy sell trade event. +- Subscribe: You can subscribe to this event by registering the `processRoundtrip` method. +- Example: + { + entryAt: Moment<'2017-03-25 19:41:00'>, + entryPrice: 10.21315498, + entryBalance: 98.19707799420277, + exitAt: Moment<'2017-03-25 19:41:00'> + exitPrice: 10.22011632, + exitBalance: 97.9692176, + duration: 3600000, + pnl: -0.2278603942027786, + profit: -0.2320439659276161, + } + +### roundtripUpdate event + +- What: An updated summary of a currently open roundtrip. +- When: After every candle for as long as the bot is in a long position. +- Subscribe: You can subscribe to this event by registering the `processRoundtripUpdate` method. +- Example: + { + at: Moment<'2017-03-25 19:41:00'>, + duration: 3600000, + uPnl: -0.2278603942027786, + uProfit: -0.2320439659276161, } ### marketStart event diff --git a/plugins.js b/plugins.js index 5b0ef28c2..140bf9863 100644 --- a/plugins.js +++ b/plugins.js @@ -143,6 +143,7 @@ var plugins = [ slug: 'performanceAnalyzer', async: false, modes: ['realtime', 'backtest'], + emits: ['roundtrip', 'roundtripUpdate', 'performanceUpdate'], path: config => 'performanceAnalyzer/performanceAnalyzer.js', }, { @@ -196,7 +197,7 @@ var plugins = [ description: 'Logs all gekko events.', slug: 'eventLogger', async: false, - modes: ['realtime'] + modes: ['realtime', 'backtest'] } ]; diff --git a/plugins/eventLogger.js b/plugins/eventLogger.js index 993869519..e73bccb2c 100644 --- a/plugins/eventLogger.js +++ b/plugins/eventLogger.js @@ -6,7 +6,7 @@ const EventLogger = function() {} _.each(subscriptions, sub => { EventLogger.prototype[sub.handler] = (event, next) => { - log.info(`[EVENT ${sub.event}]\n`, event); + log.info(`\t\t\t\t[EVENT ${sub.event}]\n`, event); if(_.isFunction(next)) next(); } diff --git a/plugins/paperTrader/paperTrader.js b/plugins/paperTrader/paperTrader.js index 698baef33..f386d242f 100644 --- a/plugins/paperTrader/paperTrader.js +++ b/plugins/paperTrader/paperTrader.js @@ -18,12 +18,26 @@ const PaperTrader = function() { this.portfolio = { asset: calcConfig.simulationBalance.asset, currency: calcConfig.simulationBalance.currency, - balance: false } + + this.balance = false; + + if(this.portfolio.asset > 0) { + this.exposed = true; + } +} + +PaperTrader.prototype.relayPortfolioChange = function() { + this.deferredEmit('portfolioChange', { + asset: this.portfolio.asset, + currency: this.portfolio.currency + }); } -PaperTrader.prototype.relayPortfolio = function() { - this.emit('portfolioUpdate', _.clone(this.portfolio)); +PaperTrader.prototype.relayPortfolioValueChange = function() { + this.deferredEmit('portfolioValueChange', { + balance: this.getBalance() + }); } PaperTrader.prototype.extractFee = function(amount) { @@ -35,8 +49,7 @@ PaperTrader.prototype.extractFee = function(amount) { } PaperTrader.prototype.setStartBalance = function() { - this.portfolio.balance = this.portfolio.currency + this.price * this.portfolio.asset; - this.relayPortfolio(); + this.balance = this.getBalance(); } // after every succesfull trend ride we hopefully end up @@ -54,6 +67,7 @@ PaperTrader.prototype.updatePosition = function(advice) { executionPrice = this.extractFee(this.price); this.portfolio.currency = 0; this.trades++; + this.exposed = true; } // virtually trade all {currency} to {asset} @@ -62,19 +76,18 @@ PaperTrader.prototype.updatePosition = function(advice) { this.portfolio.currency += this.extractFee(this.portfolio.asset * this.price); executionPrice = this.price + this.price - this.extractFee(this.price); this.portfolio.asset = 0; + this.exposed = false; this.trades++; } return executionPrice; } -PaperTrader.prototype.getPortfolio = function() { - this.portfolio.balance = this.portfolio.currency + this.price * this.portfolio.asset; - return _.clone(this.portfolio); +PaperTrader.prototype.getBalance = function() { + return this.portfolio.currency + this.price * this.portfolio.asset; } PaperTrader.prototype.processAdvice = function(advice) { - console.log('PaperTrader.prototype.processAdvice'); let action; if(advice.recommendation === 'short') action = 'sell'; @@ -88,29 +101,38 @@ PaperTrader.prototype.processAdvice = function(advice) { this.deferredEmit('tradeInitiated', { id: this.tradeId, action, - portfolio: this.getPortfolio(), + portfolio: _.clone(this.portfolio), + balance: this.getBalance(), date: advice.date, }); const executionPrice = this.updatePosition(advice); - console.log('price', this.price); + + this.relayPortfolioChange(); + this.relayPortfolioValueChange(); this.deferredEmit('tradeCompleted', { id: this.tradeId, action, price: executionPrice, - portfolio: this.getPortfolio(), + portfolio: this.portfolio, + balance: this.getBalance(), date: advice.date }); - } PaperTrader.prototype.processCandle = function(candle, done) { - console.log('PaperTrader.prototype.processCandle'); this.price = candle.close; - if(!this.portfolio.balance) + if(!this.balance) { this.setStartBalance(); + this.relayPortfolioChange(); + this.relayPortfolioValueChange(); + } + + if(this.exposed) { + this.relayPortfolioValueChange(); + } done(); } diff --git a/plugins/performanceAnalyzer/logger.js b/plugins/performanceAnalyzer/logger.js index e9d655743..18edc5a9f 100644 --- a/plugins/performanceAnalyzer/logger.js +++ b/plugins/performanceAnalyzer/logger.js @@ -45,7 +45,7 @@ Logger.prototype.logRoundtrip = function(rt) { const display = [ rt.entryAt.utc().format('YYYY-MM-DD HH:mm'), rt.exitAt.utc().format('YYYY-MM-DD HH:mm'), - (moment.duration(rt.duration).humanize() + " ").slice(0,16), + (moment.duration(rt.duration).humanize() + " ").slice(0, 16), rt.pnl.toFixed(2), rt.profit.toFixed(2) ]; @@ -53,8 +53,6 @@ Logger.prototype.logRoundtrip = function(rt) { log.info('(ROUNDTRIP)', display.join('\t')); } - - if(mode === 'backtest') { // we only want to log a summarized one line report, like: // 2016-12-19 20:12:00: Paper trader simulated a BUY 0.000 USDT => 1.098 BTC diff --git a/plugins/performanceAnalyzer/performanceAnalyzer.js b/plugins/performanceAnalyzer/performanceAnalyzer.js index 9bd4c4ee5..3eb3a19f9 100644 --- a/plugins/performanceAnalyzer/performanceAnalyzer.js +++ b/plugins/performanceAnalyzer/performanceAnalyzer.js @@ -37,14 +37,27 @@ const PerformanceAnalyzer = function() { entry: false, exit: false } + + this.portfolio = {}; + this.balance; + + this.start = {}; + this.openRoundTrip = false; } -// teach our plugin events -util.makeEventEmitter(PerformanceAnalyzer); +PerformanceAnalyzer.prototype.processPortfolioValueChange = function(event) { + if(!this.start.balance) + this.start.balance = event.balance; +} + +PerformanceAnalyzer.prototype.processPortfolioChange = function(event) { + if(!this.start.portfolio) + this.start.portfolio = event; +} PerformanceAnalyzer.prototype.processCandle = function(candle, done) { this.price = candle.close; - this.dates.end = candle.start; + this.dates.end = candle.start.clone().add(1, 'minute'); if(!this.dates.start) { this.dates.start = candle.start; @@ -53,23 +66,41 @@ PerformanceAnalyzer.prototype.processCandle = function(candle, done) { this.endPrice = candle.close; + if(this.openRoundTrip) { + this.emitRoundtripUpdate(); + } + done(); } -PerformanceAnalyzer.prototype.processTrade = function(trade) { +PerformanceAnalyzer.prototype.emitRoundtripUpdate = function() { + const uPnl = this.price - this.roundTrip.entry.price; + + this.deferredEmit('roundtripUpdate', { + at: this.dates.end, + duration: this.dates.end.diff(this.roundTrip.entry.date), + uPnl, + uProfit: uPnl / this.roundTrip.entry.total * 100 + }) +} + +PerformanceAnalyzer.prototype.processTradeCompleted = function(trade) { this.trades++; - this.current = trade.portfolio; + this.portfolio = trade.portfolio; + this.balance = trade.balance; const report = this.calculateReportStatistics(); this.logger.handleTrade(trade, report); - this.logRoundtripPart(trade); + this.registerRoundtripPart(trade); + + this.deferredEmit('performanceReport', report); } -PerformanceAnalyzer.prototype.logRoundtripPart = function(trade) { - // this is not part of a valid roundtrip +PerformanceAnalyzer.prototype.registerRoundtripPart = function(trade) { if(this.trades === 1 && trade.action === 'sell') { + // this is not part of a valid roundtrip return; } @@ -79,18 +110,20 @@ PerformanceAnalyzer.prototype.logRoundtripPart = function(trade) { price: trade.price, total: trade.portfolio.asset * trade.price, } + this.openRoundTrip = true; } else if(trade.action === 'sell') { this.roundTrip.exit = { date: trade.date, price: trade.price, total: trade.portfolio.currency } + this.openRoundTrip = false; - this.handleRoundtrip(); + this.handleCompletedRoundtrip(); } } -PerformanceAnalyzer.prototype.handleRoundtrip = function() { +PerformanceAnalyzer.prototype.handleCompletedRoundtrip = function() { var roundtrip = { entryAt: this.roundTrip.entry.date, entryPrice: this.roundTrip.entry.price, @@ -109,6 +142,8 @@ PerformanceAnalyzer.prototype.handleRoundtrip = function() { this.roundTrips.push(roundtrip); this.logger.handleRoundtrip(roundtrip); + this.deferredEmit('roundtrip', roundtrip); + // we need a cache for sharpe // every time we have a new roundtrip @@ -121,25 +156,21 @@ PerformanceAnalyzer.prototype.handleRoundtrip = function() { PerformanceAnalyzer.prototype.calculateReportStatistics = function() { // the portfolio's balance is measured in {currency} - const balance = this.current.currency + this.price * this.current.asset; - const profit = balance - this.start.balance; + const profit = this.balance - this.start.balance; const timespan = moment.duration( this.dates.end.diff(this.dates.start) ); - const relativeProfit = balance / this.start.balance * 100 - 100; + const relativeProfit = this.balance / this.start.balance * 100 - 100; const report = { - currency: this.currency, - asset: this.asset, - startTime: this.dates.start.utc().format('YYYY-MM-DD HH:mm:ss'), endTime: this.dates.end.utc().format('YYYY-MM-DD HH:mm:ss'), timespan: timespan.humanize(), market: this.endPrice * 100 / this.startPrice - 100, - balance: balance, - profit: profit, + balance: this.balance, + profit, relativeProfit: relativeProfit, yearlyProfit: profit / timespan.asYears(), diff --git a/subscriptions.js b/subscriptions.js index c3a3e382d..7f1b85fed 100644 --- a/subscriptions.js +++ b/subscriptions.js @@ -64,6 +64,11 @@ var subscriptions = [ event: 'performanceReport', handler: 'processPerformanceReport' }, + { + emitter: ['performanceAnalyzer'], + event: 'roundtripUpdate', + handler: 'processRoundtripUpdate' + }, { emitter: ['performanceAnalyzer'], event: 'roundtrip', From 587ddcb4507cfc78076e0d2bb0aa40b2c872d5fb Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Sat, 24 Mar 2018 16:53:51 +0700 Subject: [PATCH 34/57] properly catch no trade scenario --- plugins/performanceAnalyzer/performanceAnalyzer.js | 2 +- plugins/tradingAdvisor/baseTradingMethod.js | 8 +++----- plugins/tradingAdvisor/tradingAdvisor.js | 4 ---- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/plugins/performanceAnalyzer/performanceAnalyzer.js b/plugins/performanceAnalyzer/performanceAnalyzer.js index 3eb3a19f9..b96089889 100644 --- a/plugins/performanceAnalyzer/performanceAnalyzer.js +++ b/plugins/performanceAnalyzer/performanceAnalyzer.js @@ -189,7 +189,7 @@ PerformanceAnalyzer.prototype.calculateReportStatistics = function() { } PerformanceAnalyzer.prototype.finalize = function(done) { - if(!_.size(this.trades)) { + if(!this.trades) { return done(); } diff --git a/plugins/tradingAdvisor/baseTradingMethod.js b/plugins/tradingAdvisor/baseTradingMethod.js index b30e64d3b..09d6f8fb3 100644 --- a/plugins/tradingAdvisor/baseTradingMethod.js +++ b/plugins/tradingAdvisor/baseTradingMethod.js @@ -326,17 +326,15 @@ Base.prototype.advice = function(newPosition) { this._prevAdvice = newPosition; - console.log('emitting advice', newPosition); - this.emit('advice', { recommendation: newPosition }); } -// Because the trading method might be async we need -// to be sure we only stop after all candles are -// processed. Base.prototype.finish = function(done) { + // Because the trading method might be async we need + // to be sure we only stop after all candles are + // processed. if(!this.asyncTick) { this.end(); return done(); diff --git a/plugins/tradingAdvisor/tradingAdvisor.js b/plugins/tradingAdvisor/tradingAdvisor.js index f25fc69c6..888b1031a 100644 --- a/plugins/tradingAdvisor/tradingAdvisor.js +++ b/plugins/tradingAdvisor/tradingAdvisor.js @@ -103,9 +103,5 @@ Actor.prototype.relayAdvice = function(advice) { this.deferredEmit('advice', advice); } -// var a = new Actor(_.noop); -// console.log(a.defferedEvents); -// throw 'a'; - module.exports = Actor; From 4df7af29ed773f13b74ee5fbf952cc097ffb9006 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Sun, 25 Mar 2018 00:41:58 +0700 Subject: [PATCH 35/57] pass all plugins to gekkoStream --- core/gekkoStream.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/core/gekkoStream.js b/core/gekkoStream.js index ebb93b235..d59c22ca1 100644 --- a/core/gekkoStream.js +++ b/core/gekkoStream.js @@ -12,11 +12,13 @@ const mode = util.gekkoMode(); const config = util.getConfig(); const log = require(util.dirs().core + 'log'); -var Gekko = function(candleConsumers) { - this.candleConsumers = candleConsumers; +var Gekko = function(plugins) { + this.plugins = plugins; + this.candleConsumers = plugins + .filter(plugin => plugin.processCandle); Writable.call(this, {objectMode: true}); - this.defferedProducers = this.candleConsumers + this.defferedProducers = this.plugins .filter(p => p.broadcastDeferredEmit); this.finalize = _.bind(this.finalize, this); @@ -42,7 +44,7 @@ if(config.debug) { ].join(' ')); }, 1000); - const done = _.after(this.candleConsumers.length, () => { + const flushEvents = _.after(this.candleConsumers.length, () => { relayed = true; clearInterval(timer); this.flushDefferedEvents(); @@ -50,18 +52,18 @@ if(config.debug) { }); _.each(this.candleConsumers, function(c) { at = c.meta.name; - c.processCandle(chunk, done); + c.processCandle(chunk, flushEvents); }, this); } } else { // skip decoration Gekko.prototype._write = function(chunk, encoding, _done) { - const done = _.after(this.candleConsumers.length, () => { + const flushEvents = _.after(this.defferedProducers.length, () => { this.flushDefferedEvents(); _done(); }); _.each(this.candleConsumers, function(c) { - c.processCandle(chunk, done); + c.processCandle(chunk, flushEvents); }, this); } } @@ -93,7 +95,7 @@ Gekko.prototype.finalize = function() { Gekko.prototype.shutdown = function() { async.eachSeries( - this.candleConsumers, + this.plugins, function(c, callback) { if (c.finalize) c.finalize(callback); else callback(); From 4995f224e4fb9482841f69ebfd97fbe558f75c04 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Sun, 25 Mar 2018 00:52:39 +0700 Subject: [PATCH 36/57] pass all plugins into gekkostream --- core/pipeline.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/pipeline.js b/core/pipeline.js index ee7940333..afa8671d8 100644 --- a/core/pipeline.js +++ b/core/pipeline.js @@ -217,8 +217,8 @@ var pipeline = (settings) => { subscribePluginsToMarket ], function() { - - var gekkoStream = new GekkoStream(candleConsumers); + + var gekkoStream = new GekkoStream(plugins); market .pipe(gekkoStream) From 479d321b5d31fb656e926cc3ab9588e326770734 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Sun, 25 Mar 2018 00:54:00 +0700 Subject: [PATCH 37/57] create plugin to handle backtest results --- core/workers/pipeline/child.js | 2 +- plugins.js | 7 +++ plugins/backtestResultExporter.js | 71 +++++++++++++++++++++++++++++++ web/routes/baseConfig.js | 9 ++++ 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 plugins/backtestResultExporter.js diff --git a/core/workers/pipeline/child.js b/core/workers/pipeline/child.js index 2eb12bf6c..06e60617a 100644 --- a/core/workers/pipeline/child.js +++ b/core/workers/pipeline/child.js @@ -50,6 +50,6 @@ process.on('message', function(m) { }); process.on('disconnect', function() { - console.log("disconnect"); + console.log('disconnect'); process.exit(-1); }) \ No newline at end of file diff --git a/plugins.js b/plugins.js index 140bf9863..e072f47e4 100644 --- a/plugins.js +++ b/plugins.js @@ -198,6 +198,13 @@ var plugins = [ slug: 'eventLogger', async: false, modes: ['realtime', 'backtest'] + }, + { + name: 'backtest result export', + description: 'Exports the results of a gekko backtest', + slug: 'backtestResultExporter', + async: false, + modes: ['backtest'] } ]; diff --git a/plugins/backtestResultExporter.js b/plugins/backtestResultExporter.js new file mode 100644 index 000000000..34ac65d0c --- /dev/null +++ b/plugins/backtestResultExporter.js @@ -0,0 +1,71 @@ +const log = require('../core/log'); +const _ = require('lodash'); +const util = require('../core/util.js'); +const config = util.getConfig(); +const moment = require('moment'); +const fs = require('fs'); + +var Actor = function() { + this.performanceReport; + this.roundtrips = []; + this.stratUpdates = []; + + if(!config.backtestResultExporter.data.stratUpdates) + this.processStratUpdate = _.noop; + + if(!config.backtestResultExporter.data.roundtrips) + this.processRoundtrip = _.noop; + + _.bindAll(this); +} + +Actor.prototype.processRoundtrip = function(roundtrip) { + this.roundtrips.push({ + ...roundtrip, + entryAt: roundtrip.entryAt.unix(), + exitAt: roundtrip.exitAt.unix() + }); +}; + +Actor.prototype.processStratUpdate = function(stratUpdate) { + this.stratUpdates.push({ + ...stratUpdate, + date: stratUpdate.date.unix() + }); +} + +Actor.prototype.processPerformanceReport = function(performanceReport) { + this.performanceReport = performanceReport; +} + +Actor.prototype.finalize = function(done) { + const backtest = { + performanceReport: this.performanceReport + }; + + if(config.backtestResultExporter.data.stratUpdates) + backtest.stratUpdates = this.stratUpdates; + + if(config.backtestResultExporter.data.roundtrips) + backtest.roundtrips = this.roundtrips; + + process.send({backtest}); + + if(!config.backtestResultExporter.writeToDisk) + return done(); + + const now = moment().format('YYYY-MM-DD HH:mm:ss'); + const filename = `backtest-${config.tradingAdvisor.method}-${now}.log`; + fs.writeFile( + util.dirs().gekko + filename, + JSON.stringify(backtest), + err => { + if(err) + log.error('unable to write backtest result', err); + + done(); + } + ); +}; + +module.exports = Actor; diff --git a/web/routes/baseConfig.js b/web/routes/baseConfig.js index 98dbb1506..c5e908e4f 100644 --- a/web/routes/baseConfig.js +++ b/web/routes/baseConfig.js @@ -24,6 +24,15 @@ config.adviceWriter = { muteSoft: true, } +config.backtestResultExporter = { + enabled: true, + writeToDisk: true, + data: { + stratUpdates: true, + roundtrips: true + } +} + config.trader = { orderUpdateDelay: 1 // Number of minutes to adjust unfilled order prices } From 3847362a50481a57450df3d32a913b18f60bd186 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Sun, 25 Mar 2018 11:54:07 +0700 Subject: [PATCH 38/57] only wait for actual candle consumers to handle candles --- core/gekkoStream.js | 2 +- web/routes/baseConfig.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/gekkoStream.js b/core/gekkoStream.js index d59c22ca1..4f30b6e7f 100644 --- a/core/gekkoStream.js +++ b/core/gekkoStream.js @@ -58,7 +58,7 @@ if(config.debug) { } else { // skip decoration Gekko.prototype._write = function(chunk, encoding, _done) { - const flushEvents = _.after(this.defferedProducers.length, () => { + const flushEvents = _.after(this.candleConsumers.length, () => { this.flushDefferedEvents(); _done(); }); diff --git a/web/routes/baseConfig.js b/web/routes/baseConfig.js index c5e908e4f..e5e2937b6 100644 --- a/web/routes/baseConfig.js +++ b/web/routes/baseConfig.js @@ -6,8 +6,8 @@ var config = {}; // GENERAL SETTINGS // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -config.silent = false; -config.debug = true; +config.silent = true; +config.debug = false; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CONFIGURING TRADING ADVICE @@ -26,7 +26,7 @@ config.adviceWriter = { config.backtestResultExporter = { enabled: true, - writeToDisk: true, + writeToDisk: false, data: { stratUpdates: true, roundtrips: true From cea17ca222d27a038f4cb1e69330ba5cd282ca94 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Sun, 25 Mar 2018 12:21:55 +0700 Subject: [PATCH 39/57] only flush events from plugins that actually emit --- core/gekkoStream.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/gekkoStream.js b/core/gekkoStream.js index 4f30b6e7f..e4d2c40bd 100644 --- a/core/gekkoStream.js +++ b/core/gekkoStream.js @@ -18,8 +18,8 @@ var Gekko = function(plugins) { .filter(plugin => plugin.processCandle); Writable.call(this, {objectMode: true}); - this.defferedProducers = this.plugins - .filter(p => p.broadcastDeferredEmit); + this.producers = this.plugins + .filter(p => p.meta.emits); this.finalize = _.bind(this.finalize, this); } @@ -39,7 +39,7 @@ if(config.debug) { const timer = setTimeout(() => { if(!relayed) log.error([ - `The plugin "${at}" has not processed a candle for 0.5 seconds.`, + `The plugin "${at}" has not processed a candle for 1 second.`, `This will cause Gekko to slow down or stop working completely.` ].join(' ')); }, 1000); @@ -70,7 +70,7 @@ if(config.debug) { Gekko.prototype.flushDefferedEvents = function() { const broadcasted = _.find( - this.defferedProducers, + this.producers, producer => producer.broadcastDeferredEmit() ); From a42e5a0e1d4a31c646a45ec725bd733b63993546 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Sun, 25 Mar 2018 13:01:04 +0700 Subject: [PATCH 40/57] rm the id of the small candle --- core/candleBatcher.js | 3 ++- test/candleBatcher.js | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/core/candleBatcher.js b/core/candleBatcher.js index f773e4126..dd64d456b 100644 --- a/core/candleBatcher.js +++ b/core/candleBatcher.js @@ -58,7 +58,8 @@ CandleBatcher.prototype.flush = function() { } CandleBatcher.prototype.calculate = function() { - var first = this.smallCandles.shift(); + // remove the id property of the small candle + var { id, ...first } = this.smallCandles.shift(); first.vwp = first.vwp * first.volume; diff --git a/test/candleBatcher.js b/test/candleBatcher.js index 034d6b604..06013ca68 100644 --- a/test/candleBatcher.js +++ b/test/candleBatcher.js @@ -12,16 +12,16 @@ var dirs = utils.dirs(); var CandleBatcher = require(dirs.core + 'candleBatcher'); var candles = [ - {"start":moment("2015-02-14T23:57:00.000Z"),"open":257.19,"high":257.19,"low":257.18,"close":257.18,"vwp":257.18559990418294,"volume":0.97206065,"trades":2}, - {"start":moment("2015-02-14T23:58:00.000Z"),"open":257.02,"high":257.02,"low":256.98,"close":256.98,"vwp":257.0175849772836,"volume":4.1407478,"trades":2}, - {"start":moment("2015-02-14T23:59:00.000Z"),"open":256.85,"high":256.99,"low":256.85,"close":256.99,"vwp":256.9376998467,"volume":6,"trades":6}, - {"start":moment("2015-02-15T00:00:00.000Z"),"open":256.81,"high":256.82,"low":256.81,"close":256.82,"vwp":256.815,"volume":4,"trades":2}, - {"start":moment("2015-02-15T00:01:00.000Z"),"open":256.81,"high":257.02,"low":256.81,"close":257.01,"vwp":256.94666666666666,"volume":6,"trades":3}, - {"start":moment("2015-02-15T00:02:00.000Z"),"open":257.03,"high":257.03,"low":256.33,"close":256.33,"vwp":256.74257263558013,"volume":6.7551178,"trades":6}, - {"start":moment("2015-02-15T00:03:00.000Z"),"open":257.02,"high":257.47,"low":257.02,"close":257.47,"vwp":257.26466004728906,"volume":3.7384995300000003,"trades":3}, - {"start":moment("2015-02-15T00:04:00.000Z"),"open":257.47,"high":257.48,"low":257.37,"close":257.38,"vwp":257.4277429116875,"volume":8,"trades":6}, - {"start":moment("2015-02-15T00:05:00.000Z"),"open":257.38,"high":257.45,"low":257.38,"close":257.45,"vwp":257.3975644932184,"volume":7.97062564,"trades":4}, - {"start":moment("2015-02-15T00:06:00.000Z"),"open":257.46,"high":257.48,"low":257.46,"close":257.48,"vwp":257.47333333333336,"volume":7.5,"trades":4} + {id: 1, "start":moment("2015-02-14T23:57:00.000Z"),"open":257.19,"high":257.19,"low":257.18,"close":257.18,"vwp":257.18559990418294,"volume":0.97206065,"trades":2}, + {id: 2, "start":moment("2015-02-14T23:58:00.000Z"),"open":257.02,"high":257.02,"low":256.98,"close":256.98,"vwp":257.0175849772836,"volume":4.1407478,"trades":2}, + {id: 3, "start":moment("2015-02-14T23:59:00.000Z"),"open":256.85,"high":256.99,"low":256.85,"close":256.99,"vwp":256.9376998467,"volume":6,"trades":6}, + {id: 4, "start":moment("2015-02-15T00:00:00.000Z"),"open":256.81,"high":256.82,"low":256.81,"close":256.82,"vwp":256.815,"volume":4,"trades":2}, + {id: 5, "start":moment("2015-02-15T00:01:00.000Z"),"open":256.81,"high":257.02,"low":256.81,"close":257.01,"vwp":256.94666666666666,"volume":6,"trades":3}, + {id: 6, "start":moment("2015-02-15T00:02:00.000Z"),"open":257.03,"high":257.03,"low":256.33,"close":256.33,"vwp":256.74257263558013,"volume":6.7551178,"trades":6}, + {id: 7, "start":moment("2015-02-15T00:03:00.000Z"),"open":257.02,"high":257.47,"low":257.02,"close":257.47,"vwp":257.26466004728906,"volume":3.7384995300000003,"trades":3}, + {id: 8, "start":moment("2015-02-15T00:04:00.000Z"),"open":257.47,"high":257.48,"low":257.37,"close":257.38,"vwp":257.4277429116875,"volume":8,"trades":6}, + {id: 9, "start":moment("2015-02-15T00:05:00.000Z"),"open":257.38,"high":257.45,"low":257.38,"close":257.45,"vwp":257.3975644932184,"volume":7.97062564,"trades":4}, + {id: 10, "start":moment("2015-02-15T00:06:00.000Z"),"open":257.46,"high":257.48,"low":257.46,"close":257.48,"vwp":257.47333333333336,"volume":7.5,"trades":4} ]; describe('core/candleBatcher', function() { @@ -98,7 +98,7 @@ describe('core/candleBatcher', function() { var cbResult = _.first(_.first(spy.args)); expect(cbResult).to.deep.equal(result); - + expect(cbResult.id).to.equal(undefined); }); }); \ No newline at end of file From 5e4d4d8f959131f21652b4dcd55d5fdc65e86ed7 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Sun, 25 Mar 2018 14:32:23 +0700 Subject: [PATCH 41/57] add stratCandle event --- docs/internals/events.md | 55 ++++++++++++++++-------- plugins.js | 2 +- plugins/backtestResultExporter.js | 21 ++++++++- plugins/tradingAdvisor/tradingAdvisor.js | 8 +++- subscriptions.js | 5 +++ 5 files changed, 68 insertions(+), 23 deletions(-) diff --git a/docs/internals/events.md b/docs/internals/events.md index e2e4223fa..6280e451d 100644 --- a/docs/internals/events.md +++ b/docs/internals/events.md @@ -13,9 +13,9 @@ Note that all events from Gekko come from a plugin (with the exception of the `c ## List of events emitted by standard plugins - [candle](#candle-event): Every time Gekko calculas a new one minute candle from the market. -- [stratUpdate](#stratUpdate-event): Every time the strategy has processed new market data. - [stratWarmupCompleted](#stratWarmupCompleted-event): When the strategy is done warming up. -- [advice](#advice-event): Every time the trading strategy has new advice. +- [advice](#advice-event): Every time the trading strategy is fed a new candle. +- [stratUpdate](#stratUpdate-event): Every time the strategy has processed a new strat candle. - [tradeInitiated](#tradeInitiated-event): Every time a trading plugin (either the live trader or the paper trader) is going to start a new trade (buy or sell). - [tradeCompleted](#tradeCompleted-event): Every time a trading plugin (either the live trader or the paper trader) has completed a trade. - [tradeAborted](#tradeAborted-event): Every time a trading plugin (either the live trader or the paper trader) has NOT acted on new advice (due to unsufficiant funds or a similar reason). @@ -36,8 +36,9 @@ Beside those there are also two additional market events that are only emitted w - Subscribe: Your plugin can subscribe to this event by registering the `processCandle` method. - Async: When subscribing to this event the second argument will be a callback which you are expected to call when done handling this event. - Notes: - - Depending on the gekko configuration these candles might be historical on startup. + - Depending on the gekko configuration these candles might be historical on startup. If this is a concern for consumers, make sure to deal with this properly. - In illiquid markets (of less than a trade per minute) Gekko will caculate these candles in batches and a few might come at the same time. + - These are always one minute candles, this is the lowest level of market data flowing through a gekko stream. - Example: { start: [moment object of the start time of the candle], @@ -50,21 +51,6 @@ Beside those there are also two additional market events that are only emitted w trades: [number, amount of trades] } -### stratUpdate event - -- What: An object describing an updated candle the strat has processed. -- When: when the strategy is initialized is started. -- Subscribe: You can subscribe to this event by registering the `processStratUpdate` method. -- Notes: - - This event is not guaranteed to happen before any possible advice of the same candle, this situation can happen when the strategy uses async indicators (for example from TAlib or Tulip). -- Example: - { - date: [moment object of the start time of the candle], - indicators: { - mymacd: [number, result of running this indicator over current candle] - } - } - ### stratWarmupCompleted event - What: An object signaling that the strategy is now completely warmed up @@ -78,6 +64,39 @@ and will start signaling advice. start: [moment object of the start time of the first candle after the warmup], } +### stratCandle event + +- What: An object describing an updated strat candle the strat has processed. +- When: when the strategy is initialized is started. +- Subscribe: You can subscribe to this event by registering the `processStratCandle` method. +- Notes: + - This is the candle that the strategy sees: if you configured the candleSize to 60 (minutes) this event will containt a 60 minute candle. +- Example: + { + start: [moment object of the start time of the candle], + open: [number, open of candle], + high: [number, high of candle], + low: [number, low of candle], + close: [number, close of candle], + vwp: [number, average weighted price of candle], + volume: [number, total volume volume], + trades: [number, amount of trades] + } + + +### stratUpdate event + +- What: An object describing updated state of the strategy based on a new strat candle. +- When: when the strategy has +- Subscribe: You can subscribe to this event by registering the `processStratUpdate` method. +- Example: + { + date: [moment object of the start time of the candle], + indicators: { + mymacd: [number, result of running this indicator over current candle] + } + } + ### advice event - What: An advice from the strategy, the advice will either be LONG or SHORT. diff --git a/plugins.js b/plugins.js index e072f47e4..4e08b84b7 100644 --- a/plugins.js +++ b/plugins.js @@ -38,7 +38,7 @@ var plugins = [ slug: 'tradingAdvisor', async: true, modes: ['realtime', 'backtest'], - emits: ['advice'], + emits: ['advice', 'stratWarmupCompleted', 'stratCandle', 'stratUpdate'], path: config => 'tradingAdvisor/tradingAdvisor.js', }, { diff --git a/plugins/backtestResultExporter.js b/plugins/backtestResultExporter.js index 34ac65d0c..a87ebe4e6 100644 --- a/plugins/backtestResultExporter.js +++ b/plugins/backtestResultExporter.js @@ -9,16 +9,30 @@ var Actor = function() { this.performanceReport; this.roundtrips = []; this.stratUpdates = []; + this.stratCandles = []; + + if(!config.backtestResultExporter.data.candles) + this.processStratUpdate = null; if(!config.backtestResultExporter.data.stratUpdates) - this.processStratUpdate = _.noop; + this.processStratUpdate = null; if(!config.backtestResultExporter.data.roundtrips) - this.processRoundtrip = _.noop; + this.processRoundtrip = null; + + if(!config.backtestResultExporter.data.stratCandles) + this.processStratCandles = null; _.bindAll(this); } +Actor.prototype.processStratCandle = function(candle) { + this.stratCandles.push({ + ...candle, + start: candle.start.unix() + }) +}; + Actor.prototype.processRoundtrip = function(roundtrip) { this.roundtrips.push({ ...roundtrip, @@ -49,6 +63,9 @@ Actor.prototype.finalize = function(done) { if(config.backtestResultExporter.data.roundtrips) backtest.roundtrips = this.roundtrips; + if(config.backtestResultExporter.data.stratCandles) + backtest.stratCandles = this.stratCandles; + process.send({backtest}); if(!config.backtestResultExporter.writeToDisk) diff --git a/plugins/tradingAdvisor/tradingAdvisor.js b/plugins/tradingAdvisor/tradingAdvisor.js index 888b1031a..169b9f0f2 100644 --- a/plugins/tradingAdvisor/tradingAdvisor.js +++ b/plugins/tradingAdvisor/tradingAdvisor.js @@ -70,7 +70,11 @@ Actor.prototype.setupTradingMethod = function() { ) this.batcher - .on('candle', this.processStratCandle) + .on('candle', _candle => { + const { id, ...candle } = _candle; + this.deferredEmit('stratCandle', candle); + this.emitStratCandle(candle); + }); } // HANDLERS @@ -88,7 +92,7 @@ Actor.prototype.processCandle = function(candle, done) { } // propogate a custom sized candle to the trading method -Actor.prototype.processStratCandle = function(candle) { +Actor.prototype.emitStratCandle = function(candle) { this.method.tick(candle, this.next); } diff --git a/subscriptions.js b/subscriptions.js index 7f1b85fed..ea1a85033 100644 --- a/subscriptions.js +++ b/subscriptions.js @@ -29,6 +29,11 @@ var subscriptions = [ event: 'advice', handler: 'processAdvice' }, + { + emitter: 'tradingAdvisor', + event: 'stratCandle', + handler: 'processStratCandle' + }, { emitter: 'tradingAdvisor', event: 'stratUpdate', From 5e9c6709f8cf0dd2fdbbcc56117083ace85e79d6 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Sun, 25 Mar 2018 14:43:25 +0700 Subject: [PATCH 42/57] properly handle stratUpdates --- plugins/backtestResultExporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/backtestResultExporter.js b/plugins/backtestResultExporter.js index a87ebe4e6..f6f5a1524 100644 --- a/plugins/backtestResultExporter.js +++ b/plugins/backtestResultExporter.js @@ -12,7 +12,7 @@ var Actor = function() { this.stratCandles = []; if(!config.backtestResultExporter.data.candles) - this.processStratUpdate = null; + this.processStratCandles = null; if(!config.backtestResultExporter.data.stratUpdates) this.processStratUpdate = null; From 674af1d60c19132d7c9baf5f7b2184bac49ea4ce Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Sun, 25 Mar 2018 14:46:01 +0700 Subject: [PATCH 43/57] clarify strat events during warmup --- docs/internals/events.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/internals/events.md b/docs/internals/events.md index 6280e451d..f714e0518 100644 --- a/docs/internals/events.md +++ b/docs/internals/events.md @@ -71,6 +71,7 @@ and will start signaling advice. - Subscribe: You can subscribe to this event by registering the `processStratCandle` method. - Notes: - This is the candle that the strategy sees: if you configured the candleSize to 60 (minutes) this event will containt a 60 minute candle. + - Strat Candles are emitted while the strategy is still warming up (before the `stratWarmupCompleted` event). - Example: { start: [moment object of the start time of the candle], @@ -89,6 +90,8 @@ and will start signaling advice. - What: An object describing updated state of the strategy based on a new strat candle. - When: when the strategy has - Subscribe: You can subscribe to this event by registering the `processStratUpdate` method. +- Notes: + - Strat updates are emitted while the strategy is still warming up (before the `stratWarmupCompleted` event). - Example: { date: [moment object of the start time of the candle], From 0d6ce816b7830c40380eb8a2e62ec8c58c37a634 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Sun, 25 Mar 2018 15:10:35 +0700 Subject: [PATCH 44/57] allow the exporting of raw trades --- plugins/backtestResultExporter.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/plugins/backtestResultExporter.js b/plugins/backtestResultExporter.js index f6f5a1524..df9c771e1 100644 --- a/plugins/backtestResultExporter.js +++ b/plugins/backtestResultExporter.js @@ -10,9 +10,7 @@ var Actor = function() { this.roundtrips = []; this.stratUpdates = []; this.stratCandles = []; - - if(!config.backtestResultExporter.data.candles) - this.processStratCandles = null; + this.trades = []; if(!config.backtestResultExporter.data.stratUpdates) this.processStratUpdate = null; @@ -23,6 +21,9 @@ var Actor = function() { if(!config.backtestResultExporter.data.stratCandles) this.processStratCandles = null; + if(!config.backtestResultExporter.data.trades) + this.processTradeCompleted = null; + _.bindAll(this); } @@ -41,6 +42,13 @@ Actor.prototype.processRoundtrip = function(roundtrip) { }); }; +Actor.prototype.processTradeCompleted = function(trade) { + this.trades.push({ + ...trade, + date: trade.date.unix() + }); +}; + Actor.prototype.processStratUpdate = function(stratUpdate) { this.stratUpdates.push({ ...stratUpdate, @@ -66,6 +74,9 @@ Actor.prototype.finalize = function(done) { if(config.backtestResultExporter.data.stratCandles) backtest.stratCandles = this.stratCandles; + if(config.backtestResultExporter.data.trades) + backtest.trades = this.trades; + process.send({backtest}); if(!config.backtestResultExporter.writeToDisk) From 7bbb59c03dc7f1a1ab208004b1c83ad70ee003b0 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Sun, 25 Mar 2018 15:11:50 +0700 Subject: [PATCH 45/57] hookup backtest UI to new event flow --- .../messageHandlers/backtestHandler.js | 42 +++++-------------- core/workers/pipeline/parent.js | 1 - web/routes/baseConfig.js | 6 ++- .../components/backtester/result/result.vue | 12 +++++- .../backtester/result/roundtripTable.vue | 2 +- 5 files changed, 26 insertions(+), 37 deletions(-) diff --git a/core/workers/pipeline/messageHandlers/backtestHandler.js b/core/workers/pipeline/messageHandlers/backtestHandler.js index 7e3e68fc1..c021fe3ec 100644 --- a/core/workers/pipeline/messageHandlers/backtestHandler.js +++ b/core/workers/pipeline/messageHandlers/backtestHandler.js @@ -1,41 +1,21 @@ -// listen to all messages and internally queue -// all candles and trades, when done report them -// all back at once +// Relay the backtest message it when it comes in. module.exports = done => { - var trades = []; - var roundtrips = [] - var candles = []; - var report = false; + let backtest; return { message: message => { - - if(message.type === 'candle') - candles.push(message.candle); - - else if(message.type === 'trade') - trades.push(message.trade); - - else if(message.type === 'roundtrip') - roundtrips.push(message.roundtrip); - - else if(message.type === 'report') - report = message.report; - - else if(message.log) - console.log(message.log); + if(message.backtest) { + done(null, message.backtest); + } }, exit: status => { - if(status !== 0) - done('Child process has died.'); - else - done(null, { - trades: trades, - candles: candles, - report: report, - roundtrips: roundtrips - }); + if(status !== 0) { + if(backtest) + console.error('Child process died after finishing backtest'); + else + done('Child process has died.'); + } } } } \ No newline at end of file diff --git a/core/workers/pipeline/parent.js b/core/workers/pipeline/parent.js index 6a9f33180..d24488a1f 100644 --- a/core/workers/pipeline/parent.js +++ b/core/workers/pipeline/parent.js @@ -19,7 +19,6 @@ module.exports = (mode, config, callback) => { }; child.on('message', function(m) { - if(m === 'ready') return child.send(message); diff --git a/web/routes/baseConfig.js b/web/routes/baseConfig.js index e5e2937b6..0639c8948 100644 --- a/web/routes/baseConfig.js +++ b/web/routes/baseConfig.js @@ -28,8 +28,10 @@ config.backtestResultExporter = { enabled: true, writeToDisk: false, data: { - stratUpdates: true, - roundtrips: true + stratUpdates: false, + roundtrips: true, + stratCandles: true, + trades: true } } diff --git a/web/vue/src/components/backtester/result/result.vue b/web/vue/src/components/backtester/result/result.vue index 617062a09..31a2cf765 100644 --- a/web/vue/src/components/backtester/result/result.vue +++ b/web/vue/src/components/backtester/result/result.vue @@ -3,9 +3,9 @@ .hr.contain div.contain h3 Backtest result - result-summary(:report='result.report') + result-summary(:report='result.performanceReport') .hr.contain - chart(:data='result', height='500') + chart(:data='candles', height='500') .hr.contain roundtripTable(:roundtrips='result.roundtrips') @@ -25,6 +25,14 @@ export default { roundtripTable, resultSummary, chart + }, + computed: { + candles: function() { + return { + candles: this.result.stratCandles, + trades: this.result.trades + }; + } } } diff --git a/web/vue/src/components/backtester/result/roundtripTable.vue b/web/vue/src/components/backtester/result/roundtripTable.vue index 7fc7b9f7f..ccb85dc8e 100644 --- a/web/vue/src/components/backtester/result/roundtripTable.vue +++ b/web/vue/src/components/backtester/result/roundtripTable.vue @@ -36,7 +36,7 @@ export default { methods: { diff: n => moment.duration(n).humanize(), humanizeDuration: (n) => window.humanizeDuration(n), - fmt: mom => moment.utc(mom).format('YYYY-MM-DD HH:mm'), + fmt: mom => moment.unix(mom).utc().format('YYYY-MM-DD HH:mm'), round: n => (+n).toFixed(3), }, } From 6ae42a21e314901690d7695f07adee8327168149 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Sun, 25 Mar 2018 16:57:05 +0700 Subject: [PATCH 46/57] make sure to print cp final words --- core/util.js | 9 ++++++++- core/workers/pipeline/parent.js | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/core/util.js b/core/util.js index d4fac292d..41a918088 100644 --- a/core/util.js +++ b/core/util.js @@ -98,11 +98,18 @@ var util = { else if(_gekkoEnv === 'child-process') var log = m => process.send({type: 'error', error: m}); + var instanceName; + + if(util.gekkoEnv() === 'standalone') + instanceName = 'Gekko'; + else + instanceName = 'This Gekko instance'; + if(m) { if(soft) { log('\n ERROR: ' + m + '\n\n'); } else { - log('\n\nGekko encountered an error and can\'t continue'); + log(`\n${instanceName} encountered an error and can\'t continue`); log('\nError:\n'); log(m, '\n\n'); log('\nMeta debug info:\n'); diff --git a/core/workers/pipeline/parent.js b/core/workers/pipeline/parent.js index d24488a1f..ca3b65d6e 100644 --- a/core/workers/pipeline/parent.js +++ b/core/workers/pipeline/parent.js @@ -25,6 +25,9 @@ module.exports = (mode, config, callback) => { if(m === 'done') return child.send({what: 'exit'}); + if(m && m.type === 'error') + return console.error(m.error); + handle.message(m); }); From a694b094f4b77ee5712ce18b399b990cf7fe4f13 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Sun, 25 Mar 2018 18:50:30 +0700 Subject: [PATCH 47/57] upgrade backtest API call to use backtestResultExporter plugin --- plugins/backtestResultExporter.js | 12 ++++++---- sample-config.js | 11 ++++++++++ web/routes/backtest.js | 22 ++----------------- web/server.js | 2 +- .../src/components/backtester/backtester.vue | 13 +---------- 5 files changed, 23 insertions(+), 37 deletions(-) diff --git a/plugins/backtestResultExporter.js b/plugins/backtestResultExporter.js index df9c771e1..75d0c23b4 100644 --- a/plugins/backtestResultExporter.js +++ b/plugins/backtestResultExporter.js @@ -79,9 +79,13 @@ Actor.prototype.finalize = function(done) { process.send({backtest}); - if(!config.backtestResultExporter.writeToDisk) - return done(); + if(config.backtestResultExporter.writeToDisk) + this.writeToDisk(done) + else + done(); +}; +Actor.prototype.writeToDisk = function(next) { const now = moment().format('YYYY-MM-DD HH:mm:ss'); const filename = `backtest-${config.tradingAdvisor.method}-${now}.log`; fs.writeFile( @@ -91,9 +95,9 @@ Actor.prototype.finalize = function(done) { if(err) log.error('unable to write backtest result', err); - done(); + next(); } ); -}; +} module.exports = Actor; diff --git a/sample-config.js b/sample-config.js index e1fdf7a02..4b5343c85 100644 --- a/sample-config.js +++ b/sample-config.js @@ -391,6 +391,17 @@ config.adviceWriter = { muteSoft: true, } +config.backtestResultExporter = { + enabled: false, + writeToDisk: false, + data: { + stratUpdates: false, + roundtrips: true, + stratCandles: true, + trades: true + } +} + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CONFIGURING ADAPTER // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/web/routes/backtest.js b/web/routes/backtest.js index 9032bc4c0..44510411d 100644 --- a/web/routes/backtest.js +++ b/web/routes/backtest.js @@ -25,25 +25,7 @@ module.exports = function *() { var req = this.request.body; - _.merge(config, base, req.gekkoConfig); + _.merge(config, base, req); - var result = yield pipelineRunner(mode, config); - - if(!req.data.report) - delete result.report; - - if(!req.data.roundtrips) - delete result.roundtrips; - - if(!req.data.trades) - delete result.trades; - - // todo: indicatorResults - - result.candles = _.map( - result.candles, - c => _.pick(c, req.data.candleProps) - ); - - this.body = result; + this.body = yield pipelineRunner(mode, config); } \ No newline at end of file diff --git a/web/server.js b/web/server.js index 39ea3f97d..c66d2c5eb 100644 --- a/web/server.js +++ b/web/server.js @@ -31,7 +31,7 @@ const broadcast = data => { } catch(e) { log.warn('unable to send data to client'); } - }) + } ); } cache.set('broadcast', broadcast); diff --git a/web/vue/src/components/backtester/backtester.vue b/web/vue/src/components/backtester/backtester.vue index 0883ea22b..baef6d770 100644 --- a/web/vue/src/components/backtester/backtester.vue +++ b/web/vue/src/components/backtester/backtester.vue @@ -40,18 +40,7 @@ export default { run: function() { this.backtestState = 'fetching'; - const req = { - gekkoConfig: this.config, - data: { - candleProps: ['close', 'start'], - indicatorResults: true, - report: true, - roundtrips: true, - trades: true - } - } - - post('backtest', req, (error, response) => { + post('backtest', this.config, (error, response) => { this.backtestState = 'fetched'; this.backtestResult = response; }); From 2041cd2b0bf0347b7e8523b5bc77a6b958a07b58 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Mon, 26 Mar 2018 13:46:22 +0700 Subject: [PATCH 48/57] update to new backtest api call --- .../components/backtester/backtestConfigBuilder.vue | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/web/vue/src/components/backtester/backtestConfigBuilder.vue b/web/vue/src/components/backtester/backtestConfigBuilder.vue index ffdb03c88..90a1a53ec 100644 --- a/web/vue/src/components/backtester/backtestConfigBuilder.vue +++ b/web/vue/src/components/backtester/backtestConfigBuilder.vue @@ -66,9 +66,20 @@ export default { { backtest: { daterange: this.range + }, + backtestResultExporter: { + enabled: true, + writeToDisk: false, + data: { + stratUpdates: false, + roundtrips: true, + stratCandles: true, + // stratCandleProps: ['close', 'start'] todo! + trades: true + } } }, - { performanceAnalyzer: this.performanceAnalyzer } + { performanceAnalyzer: this.performanceAnalyzer }, ); config.valid = this.validConfig(config); From 8e2760a3cf0ab4b4d30506930d51368a261f3f16 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Mon, 26 Mar 2018 15:23:54 +0700 Subject: [PATCH 49/57] allow for specifying what candle props to return --- plugins/backtestResultExporter.js | 21 +++++++++++++++---- .../backtester/backtestConfigBuilder.vue | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/plugins/backtestResultExporter.js b/plugins/backtestResultExporter.js index 75d0c23b4..fe2a0e0ee 100644 --- a/plugins/backtestResultExporter.js +++ b/plugins/backtestResultExporter.js @@ -12,6 +12,8 @@ var Actor = function() { this.stratCandles = []; this.trades = []; + this.candleProps = config.backtestResultExporter.data.stratCandleProps; + if(!config.backtestResultExporter.data.stratUpdates) this.processStratUpdate = null; @@ -28,10 +30,21 @@ var Actor = function() { } Actor.prototype.processStratCandle = function(candle) { - this.stratCandles.push({ - ...candle, - start: candle.start.unix() - }) + let strippedCandle; + + if(!this.candleProps) { + strippedCandle = { + ...candle, + start: candle.start.unix() + } + } else { + strippedCandle = { + ..._.pick(candle, this.candleProps), + start: candle.start.unix() + } + } + + this.stratCandles.push(strippedCandle); }; Actor.prototype.processRoundtrip = function(roundtrip) { diff --git a/web/vue/src/components/backtester/backtestConfigBuilder.vue b/web/vue/src/components/backtester/backtestConfigBuilder.vue index 90a1a53ec..67ccb25c5 100644 --- a/web/vue/src/components/backtester/backtestConfigBuilder.vue +++ b/web/vue/src/components/backtester/backtestConfigBuilder.vue @@ -74,7 +74,7 @@ export default { stratUpdates: false, roundtrips: true, stratCandles: true, - // stratCandleProps: ['close', 'start'] todo! + stratCandleProps: ['close'], trades: true } } From a6393177b0dae3a6fd50df4ff0cfdd0341fff96e Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Sun, 1 Apr 2018 20:06:33 +0700 Subject: [PATCH 50/57] make sure we output the binance error, fix #2037 --- exchanges/binance.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exchanges/binance.js b/exchanges/binance.js index d0ad8fba1..c2bb08f8c 100644 --- a/exchanges/binance.js +++ b/exchanges/binance.js @@ -68,7 +68,7 @@ Trader.prototype.processError = function(funcName, error) { Trader.prototype.handleResponse = function(funcName, callback) { return (error, body) => { - if (body && !_.isEmpty(body.code)) { + if (body && body.code) { error = new Error(`Error ${body.code}: ${body.msg}`); } From c6faee17bdcad11604ce7e93339b7f1f187b0f0f Mon Sep 17 00:00:00 2001 From: stereohelix <34974709+stereohelix@users.noreply.github.com> Date: Thu, 17 May 2018 06:37:01 -0700 Subject: [PATCH 51/57] Performance Analyzer fixes and features (#2178) * Update performanceAnalyzer.js * Update logger.js * Update logger.js * Update performanceAnalyzer.js --- plugins/performanceAnalyzer/logger.js | 10 +++--- .../performanceAnalyzer.js | 34 ++++++++++++------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/plugins/performanceAnalyzer/logger.js b/plugins/performanceAnalyzer/logger.js index 18edc5a9f..e087c39c5 100644 --- a/plugins/performanceAnalyzer/logger.js +++ b/plugins/performanceAnalyzer/logger.js @@ -94,8 +94,7 @@ if(mode === 'backtest') { log.info(`(PROFIT REPORT) start time:\t\t\t ${report.startTime}`); log.info(`(PROFIT REPORT) end time:\t\t\t ${report.endTime}`); log.info(`(PROFIT REPORT) timespan:\t\t\t ${report.timespan}`); - if(report.sharpe) - log.info(`(PROFIT REPORT) sharpe ratio:\t\t\t ${report.sharpe}`); + log.info(`(PROFIT REPORT) exposure:\t\t\t ${report.exposure}`); log.info(); log.info(`(PROFIT REPORT) start price:\t\t\t ${report.startPrice} ${this.currency}`); log.info(`(PROFIT REPORT) end price:\t\t\t ${report.endPrice} ${this.currency}`); @@ -109,8 +108,11 @@ if(mode === 'backtest') { `(PROFIT REPORT) simulated yearly profit:\t ${report.yearlyProfit}`, `${this.currency} (${report.relativeYearlyProfit}%)` ); + + log.info(`(PROFIT REPORT) sharpe ratio:\t\t\t ${report.sharpe}`); + log.info(`(PROFIT REPORT) expected downside:\t\t ${report.downside}`); } - + Logger.prototype.handleRoundtrip = function(rt) { this.roundtrips.push(rt); } @@ -128,4 +130,4 @@ if(mode === 'backtest') { -module.exports = Logger; \ No newline at end of file +module.exports = Logger; diff --git a/plugins/performanceAnalyzer/performanceAnalyzer.js b/plugins/performanceAnalyzer/performanceAnalyzer.js index b96089889..9dd2fb18b 100644 --- a/plugins/performanceAnalyzer/performanceAnalyzer.js +++ b/plugins/performanceAnalyzer/performanceAnalyzer.js @@ -2,7 +2,7 @@ const _ = require('lodash'); const moment = require('moment'); -const stats = require('../../core/stats'); +const statslite = require('stats-lite'); const util = require('../../core/util'); const ENV = util.gekkoEnv(); @@ -30,9 +30,10 @@ const PerformanceAnalyzer = function() { this.trades = 0; - this.sharpe = 0; - + this.exposure = 0; + this.roundTrips = []; + this.losses = []; this.roundTrip = { entry: false, exit: false @@ -144,14 +145,12 @@ PerformanceAnalyzer.prototype.handleCompletedRoundtrip = function() { this.deferredEmit('roundtrip', roundtrip); - // we need a cache for sharpe - - // every time we have a new roundtrip - // update the cached sharpe ratio - this.sharpe = stats.sharpe( - this.roundTrips.map(r => r.profit), - perfConfig.riskFreeReturn - ); + // update cached exposure + this.exposure = this.exposure + Date.parse(this.roundTrip.exit.date) - Date.parse(this.roundTrip.entry.date); + // track losses separately for downside report + if (roundtrip.exitBalance < roundtrip.entryBalance) + this.losses.push(roundtrip); + } PerformanceAnalyzer.prototype.calculateReportStatistics = function() { @@ -162,6 +161,15 @@ PerformanceAnalyzer.prototype.calculateReportStatistics = function() { this.dates.end.diff(this.dates.start) ); const relativeProfit = this.balance / this.start.balance * 100 - 100; + + const percentExposure = this.exposure / (Date.parse(this.dates.end) - Date.parse(this.dates.start)); + + const sharpe = (relativeYearlyProfit - perfConfig.riskFreeReturn) + / statslite.stdev(this.roundTrips.map(r => r.profit)) + / Math.sqrt(this.trades / (this.trades - 2)); + + const downside = statslite.percentile(this.losses.map(r => r.profit), 0.25) + * Math.sqrt(this.trades / (this.trades - 2)); const report = { startTime: this.dates.start.utc().format('YYYY-MM-DD HH:mm:ss'), @@ -180,7 +188,9 @@ PerformanceAnalyzer.prototype.calculateReportStatistics = function() { endPrice: this.endPrice, trades: this.trades, startBalance: this.start.balance, - sharpe: this.sharpe + exposure: percentExposure, + sharpe, + downside } report.alpha = report.profit - report.market; From f948a2d347faab8270b9336bdaafe16ae57526bd Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Fri, 18 May 2018 18:45:31 +0200 Subject: [PATCH 52/57] define relativeYearlyProfit, fix #2190 --- plugins/performanceAnalyzer/performanceAnalyzer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/performanceAnalyzer/performanceAnalyzer.js b/plugins/performanceAnalyzer/performanceAnalyzer.js index 9dd2fb18b..00020cc49 100644 --- a/plugins/performanceAnalyzer/performanceAnalyzer.js +++ b/plugins/performanceAnalyzer/performanceAnalyzer.js @@ -161,6 +161,7 @@ PerformanceAnalyzer.prototype.calculateReportStatistics = function() { this.dates.end.diff(this.dates.start) ); const relativeProfit = this.balance / this.start.balance * 100 - 100; + const relativeYearlyProfit = relativeProfit / timespan.asYears(); const percentExposure = this.exposure / (Date.parse(this.dates.end) - Date.parse(this.dates.start)); @@ -182,7 +183,7 @@ PerformanceAnalyzer.prototype.calculateReportStatistics = function() { relativeProfit: relativeProfit, yearlyProfit: profit / timespan.asYears(), - relativeYearlyProfit: relativeProfit / timespan.asYears(), + relativeYearlyProfit, startPrice: this.startPrice, endPrice: this.endPrice, From b080ff71ef9049dc8e4bff7024ac85697c135c01 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Thu, 24 May 2018 15:54:15 +0200 Subject: [PATCH 53/57] update required node version to 8.11.2 --- .travis.yml | 2 +- appveyor.yml | 2 +- package-lock.json | 1524 +++++++++++++++++++-------------------------- package.json | 2 +- 4 files changed, 633 insertions(+), 897 deletions(-) diff --git a/.travis.yml b/.travis.yml index b92f2c940..868f1ec80 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,3 @@ language: node_js node_js: - - "8.0.0" \ No newline at end of file + - "8.11.2" \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index ddb5521a9..8c641601f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,7 @@ init: - git config --global core.autocrlf input environment: - nodejs_version: "6" + nodejs_version: "8.11.2" install: - ps: Install-Product node $env:nodejs_version diff --git a/package-lock.json b/package-lock.json index 295d68901..af25b8e0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,23 +4,32 @@ "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.13.0", - "resolved": "https://registry.npmjs.org/@slack/client/-/client-3.13.0.tgz", - "integrity": "sha512-45hHqycaVK4UVL0e1jRK1bEOyb2/a3V6QJAgP43Bg03y2En4BZ3J3Qe7zNh09VTYsQ6KJeHTOWldDhNJUbdkTw==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@slack/client/-/client-3.16.0.tgz", + "integrity": "sha512-CWr7a3rTVrN5Vs8GYReRAvTourbXHOqB1zglcskj05ICH4GZL5BOAza2ARai+qc3Nz0nY08Bozi1x0014KOqlg==", "requires": { "async": "1.5.2", - "bluebird": "3.5.0", + "bluebird": "3.5.1", "eventemitter3": "1.2.0", "https-proxy-agent": "1.0.0", "inherits": "2.0.3", - "lodash": "4.17.4", + "lodash": "4.17.10", "pkginfo": "0.4.1", - "request": "2.83.0", + "request": "2.76.0", "retry": "0.9.0", "url-join": "0.0.1", - "winston": "2.3.1", - "ws": "1.1.4" + "winston": "2.4.2", + "ws": "1.1.5" }, "dependencies": { "async": { @@ -29,9 +38,36 @@ "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" }, "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + "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" + } }, "retry": { "version": "0.9.0", @@ -86,20 +122,15 @@ "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=" } } }, "accepts": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", - "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", "requires": { - "mime-types": "2.1.17", + "mime-types": "2.1.18", "negotiator": "0.6.1" } }, @@ -125,7 +156,7 @@ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { "co": "4.6.0", - "fast-deep-equal": "1.0.0", + "fast-deep-equal": "1.1.0", "fast-json-stable-stringify": "2.0.0", "json-schema-traverse": "0.3.1" } @@ -161,22 +192,22 @@ "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" }, "assertion-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", - "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" }, "async": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/async/-/async-2.1.2.tgz", "integrity": "sha1-YSpKtF70KnDN6Aa62G7m2wR+g4U=", "requires": { - "lodash": "4.17.4" + "lodash": "4.17.10" }, "dependencies": { "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" } } }, @@ -196,16 +227,16 @@ "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" }, "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + "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.3", + "core-js": "2.5.6", "regenerator-runtime": "0.11.1" } }, @@ -225,12 +256,12 @@ } }, "binance": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/binance/-/binance-1.3.1.tgz", - "integrity": "sha1-ecicdUranlTtZiicVav6C/BkYUE=", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/binance/-/binance-1.3.3.tgz", + "integrity": "sha512-1eV2QUoH/Z0FZPiGjigJg4udXV9Uu6Clr0Sg1xsX3xStgPfzXz0juA3mllQIiIaHx7dmfAQgEiZIyeJLx5ajag==", "requires": { - "request": "2.83.0", - "underscore": "1.8.3", + "request": "2.87.0", + "underscore": "1.9.0", "ws": "3.3.3" }, "dependencies": { @@ -239,18 +270,13 @@ "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" }, - "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" - }, "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.1", + "safe-buffer": "5.1.2", "ultron": "1.1.1" } } @@ -268,173 +294,9 @@ "requires": { "crypto": "1.0.1", "querystring": "0.2.0", - "request": "2.83.0", - "underscore": "1.8.3", + "request": "2.87.0", + "underscore": "1.9.0", "verror": "1.10.0" - }, - "dependencies": { - "ajv": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.4.0.tgz", - "integrity": "sha1-MtHPCNvIDEMvQm8S4QslEfa0ZHQ=", - "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.0.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "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=" - }, - "boom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "requires": { - "hoek": "4.2.0" - } - }, - "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.0" - } - } - } - }, - "crypto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", - "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==" - }, - "form-data": { - "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.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.4.0", - "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.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", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.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.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" - }, - "request": { - "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.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "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.17", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "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.1.0" - } - }, - "sntp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", - "requires": { - "hoek": "4.2.0" - } - }, - "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" - } - }, - "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" - } } }, "bitexthai": { @@ -442,67 +304,84 @@ "resolved": "https://registry.npmjs.org/bitexthai/-/bitexthai-0.1.0.tgz", "integrity": "sha1-R2wfRisgZnu2vnub9kkGcAlQ2zA=", "requires": { - "lodash": "4.17.4" + "lodash": "4.17.10" }, "dependencies": { "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" } } }, "bitfinex-api-node": { - "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" + "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" }, "dependencies": { "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" }, "request-promise": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.1.tgz", - "integrity": "sha1-fuxWyJMXqCLL/qmbA5zlQ8LhX2c=", + "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.0", + "bluebird": "3.5.1", "request-promise-core": "1.1.1", "stealthy-require": "1.1.1", - "tough-cookie": "2.3.2" + "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" + } + } } }, "ultron": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.0.tgz", - "integrity": "sha1-sHoualQagV/Go0zNRTO67DB8qGQ=" + "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.2.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.2.0.tgz", - "integrity": "sha512-hTS3mkXm/j85jTQOIcwVz3yK3up9xHgPtgEhDBOH3G18LDOZmSAG1omJeXejLKJakx+okv8vS1sopgs7rw0kVw==", + "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.1", - "ultron": "1.1.0" + "safe-buffer": "5.1.2", + "ultron": "1.1.1" } } } }, "bitstamp": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bitstamp/-/bitstamp-1.0.3.tgz", - "integrity": "sha512-SlXhUil7zB5nzrpaspNrJ8UpGv+xeXuYsRMBRtwrXSlP70OFFDRe8P6T9VHLtf60Ra6yHnoroSHd/1FeQMuJkg==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/bitstamp/-/bitstamp-1.0.6.tgz", + "integrity": "sha512-TZDi2OvckUWNl9qDotuOjQsdR9KfByqhy+4eRo2GmpmUbzvG9Fu+fnC9VGeeX9Kc5yAgHWLyvrlOq+6QYdi4eg==", "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": { @@ -539,9 +418,9 @@ } }, "bluebird": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", - "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" }, "boolbase": { "version": "1.0.0", @@ -557,9 +436,9 @@ } }, "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { "balanced-match": "1.0.0", @@ -567,9 +446,9 @@ } }, "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, "btc-china-fork": { @@ -584,10 +463,10 @@ "verror": "1.6.1" }, "dependencies": { - "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + "crypto": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-0.0.3.tgz", + "integrity": "sha1-RwqBuGvkxe4XrMggeh9TFa4g27A=" }, "extsprintf": { "version": "1.2.0", @@ -600,26 +479,10 @@ "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", "requires": { "async": "2.1.2", - "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" + "combined-stream": "1.0.6", + "mime-types": "2.1.18" } }, - "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", @@ -633,7 +496,7 @@ "aws-sign2": "0.6.0", "bl": "1.0.3", "caseless": "0.11.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "extend": "3.0.1", "forever-agent": "0.6.1", "form-data": "1.0.1", @@ -643,11 +506,11 @@ "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", + "mime-types": "2.1.18", "node-uuid": "1.4.8", "oauth-sign": "0.8.2", "qs": "5.2.1", - "stringstream": "0.0.5", + "stringstream": "0.0.6", "tough-cookie": "2.2.2", "tunnel-agent": "0.4.3" } @@ -657,11 +520,6 @@ "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", @@ -689,17 +547,6 @@ "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", @@ -715,9 +562,14 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", "requires": { - "hoek": "4.2.0" + "hoek": "4.2.1" } }, + "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", @@ -731,7 +583,7 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", "requires": { - "hoek": "4.2.0" + "hoek": "4.2.1" } } } @@ -742,26 +594,21 @@ "integrity": "sha1-WtlGwi9bMrp/jNdCZxHG6KP8JSk=" }, "form-data": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", - "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", + "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.5", - "mime-types": "2.1.17" + "combined-stream": "1.0.6", + "mime-types": "2.1.18" } }, - "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.2.3", + "ajv": "5.5.2", "har-schema": "2.0.0" } }, @@ -772,14 +619,14 @@ "requires": { "boom": "4.3.1", "cryptiles": "3.1.2", - "hoek": "4.2.0", - "sntp": "2.0.2" + "hoek": "4.2.1", + "sntp": "2.1.0" } }, "hoek": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", - "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" + "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", @@ -788,18 +635,13 @@ "requires": { "assert-plus": "1.0.0", "jsprim": "1.4.1", - "sshpk": "1.13.1" + "sshpk": "1.14.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.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "request": { "version": "2.83.0", @@ -807,43 +649,43 @@ "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", "requires": { "aws-sign2": "0.7.0", - "aws4": "1.6.0", + "aws4": "1.7.0", "caseless": "0.12.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "extend": "3.0.1", "forever-agent": "0.6.1", - "form-data": "2.3.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.17", + "mime-types": "2.1.18", "oauth-sign": "0.8.2", "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.3", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "stringstream": "0.0.6", + "tough-cookie": "2.3.4", "tunnel-agent": "0.6.0", - "uuid": "3.1.0" + "uuid": "3.2.1" } }, "sntp": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.0.2.tgz", - "integrity": "sha1-UGQRDwr4X3z9t9a2ekACjOUrSys=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", "requires": { - "hoek": "4.2.0" + "hoek": "4.2.1" } }, - "tough-cookie": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", - "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "punycode": "1.4.1" + "safe-buffer": "5.1.2" } }, "underscore": { @@ -851,6 +693,11 @@ "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", @@ -868,9 +715,9 @@ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" }, "cexio": { "version": "0.0.5", @@ -893,12 +740,12 @@ "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", "dev": true, "requires": { - "assertion-error": "1.0.2", + "assertion-error": "1.1.0", "check-error": "1.0.2", "deep-eql": "3.0.1", "get-func-name": "2.0.0", "pathval": "1.1.0", - "type-detect": "4.0.7" + "type-detect": "4.0.8" }, "dependencies": { "deep-eql": { @@ -907,13 +754,13 @@ "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { - "type-detect": "4.0.7" + "type-detect": "4.0.8" } }, "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==", + "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==", "dev": true } } @@ -966,14 +813,21 @@ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, "co-body": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/co-body/-/co-body-5.1.1.tgz", - "integrity": "sha1-2XeB0eM0S6SoIP0YBr3fg0FQUjY=", + "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==", "requires": { "inflation": "2.0.0", - "qs": "6.4.0", - "raw-body": "2.3.2", - "type-is": "1.6.15" + "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==" + } } }, "co-from-stream": { @@ -1004,9 +858,9 @@ "integrity": "sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==" }, "coinfalcon": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/coinfalcon/-/coinfalcon-1.0.3.tgz", - "integrity": "sha512-dzyLdeDGY9Fg4zewCFolK/TjB/Mrf9tpBupx7IAqhZcYH6jY5z7xxMywIgJnf4bbRKMIEnJ2GJFqgue9M1nwnw==", + "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" } @@ -1017,17 +871,17 @@ "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" }, "combined-stream": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "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" } }, "commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==" + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" }, "composition": { "version": "2.3.0", @@ -1059,7 +913,7 @@ "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.1.tgz", "integrity": "sha1-fIphX1SBxhq58WyDNzG8uPZjuZs=", "requires": { - "depd": "1.1.1", + "depd": "1.1.2", "keygrip": "1.0.2" } }, @@ -1069,9 +923,9 @@ "integrity": "sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU=" }, "core-js": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", - "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" + "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", @@ -1092,9 +946,9 @@ } }, "crypto": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/crypto/-/crypto-0.0.3.tgz", - "integrity": "sha1-RwqBuGvkxe4XrMggeh9TFa4g27A=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==" }, "css-select": { "version": "1.0.0", @@ -1139,9 +993,9 @@ } }, "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "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" } @@ -1175,9 +1029,9 @@ "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" }, "deep-extend": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.0.tgz", - "integrity": "sha1-bvSgmwX5iw41jW2T1Mo8rsZnKAM=" + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==" }, "delayed-stream": { "version": "1.0.0", @@ -1190,9 +1044,9 @@ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, "destroy": { "version": "1.0.4", @@ -1200,9 +1054,9 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, "diff": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", - "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, "dom-serializer": { @@ -1288,7 +1142,7 @@ }, "event-stream": { "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", "requires": { "duplexer": "0.1.1", @@ -1326,9 +1180,9 @@ "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" }, "fast-deep-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" + "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", @@ -1351,28 +1205,19 @@ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", - "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", "requires": { "asynckit": "0.4.0", - "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" + "combined-stream": "1.0.6", + "mime-types": "2.1.18" } }, "fresh": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz", - "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8=" + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, "from": { "version": "0.1.7", @@ -1414,56 +1259,35 @@ "readable-stream": "2.0.6" } }, - "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" - }, "form-data": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", "requires": { - "async": "2.5.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" + "async": "2.6.1", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" }, "dependencies": { "async": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", - "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==", + "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.4" + "lodash": "4.17.10" } } } }, - "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" - } - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" - }, - "node-uuid": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", - "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + "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", @@ -1489,10 +1313,10 @@ "integrity": "sha1-dpPKdou7DqXIzgjAhKRe+gW4kqs=", "requires": { "aws-sign2": "0.6.0", - "aws4": "1.6.0", + "aws4": "1.7.0", "bl": "1.1.2", "caseless": "0.11.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "extend": "3.0.1", "forever-agent": "0.6.1", "form-data": "1.0.1", @@ -1502,20 +1326,15 @@ "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", + "mime-types": "2.1.18", "node-uuid": "1.4.8", "oauth-sign": "0.8.2", "qs": "6.2.3", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", + "stringstream": "0.0.6", + "tough-cookie": "2.3.4", "tunnel-agent": "0.4.3" } }, - "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" - }, "ws": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.1.tgz", @@ -1641,11 +1460,6 @@ "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", @@ -1767,19 +1581,19 @@ "is-retry-allowed": "1.1.0", "is-stream": "1.1.0", "isurl": "1.0.0", - "lowercase-keys": "1.0.0", + "lowercase-keys": "1.0.1", "p-cancelable": "0.3.0", - "p-timeout": "1.2.0", - "safe-buffer": "5.1.1", + "p-timeout": "1.2.1", + "safe-buffer": "5.1.2", "timed-out": "4.0.1", "url-parse-lax": "1.0.0", "url-to-options": "1.0.1" } }, "growl": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, "har-schema": { @@ -1788,12 +1602,14 @@ "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=", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" + "chalk": "1.1.3", + "commander": "2.15.1", + "is-my-json-valid": "2.17.2", + "pinkie-promise": "2.0.1" } }, "has-ansi": { @@ -1805,22 +1621,22 @@ } }, "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "has-symbol-support-x": { - "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==" + "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==" }, "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.1" + "has-symbol-support-x": "1.4.2" } }, "hawk": { @@ -1879,18 +1695,18 @@ "integrity": "sha1-oxpc+IyHPsu1eWkH1NbxMujAHko=", "requires": { "deep-equal": "1.0.1", - "http-errors": "1.6.2" + "http-errors": "1.6.3" } }, "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "requires": { - "depd": "1.1.1", + "depd": "1.1.2", "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": "1.3.1" + "setprototypeof": "1.1.0", + "statuses": "1.5.0" } }, "http-signature": { @@ -1900,7 +1716,7 @@ "requires": { "assert-plus": "0.2.0", "jsprim": "1.4.1", - "sshpk": "1.13.1" + "sshpk": "1.14.1" } }, "https-proxy-agent": { @@ -1909,14 +1725,14 @@ "integrity": "sha1-NffabEjOTdv6JkiRrFk+5f+GceY=", "requires": { "agent-base": "2.1.1", - "debug": "2.6.8", + "debug": "2.6.9", "extend": "3.0.1" } }, "humanize-duration": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.10.1.tgz", - "integrity": "sha512-FHD+u5OKj8TSsSdMHJxSCC78N5Rt4ecil6sWvI+xPbUKhxvHmkKo/V8imbR1m2dXueZYLIl7PcSYX9i/oEiOIA==" + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.14.0.tgz", + "integrity": "sha512-ebqI6Aaw64T7e7bZoFeoHkvlyy7mpVDqXCPm9gLFi9S42QWD3PWQ1FMwDKJo0y8/sXcfZ9hughSadLwTfmafmw==" }, "humanize-number": { "version": "0.0.2", @@ -1924,9 +1740,12 @@ "integrity": "sha1-EcCvakcWQ2M1iFiASPF5lUFInBg=" }, "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + "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" + } }, "inflation": { "version": "2.0.0", @@ -1958,22 +1777,28 @@ "resolved": "https://registry.npmjs.org/ipcee/-/ipcee-1.0.6.tgz", "integrity": "sha1-PI3I5nh9gdIkyY6POcvvCeBV5tQ=", "requires": { - "debug": "2.6.8", + "debug": "2.6.9", "eventemitter2": "2.2.2" } }, "is-buffer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", - "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=" + "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==" }, "is-my-json-valid": { - "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==", + "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==", "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" } @@ -2048,14 +1873,6 @@ "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", @@ -2066,11 +1883,6 @@ "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", @@ -2082,9 +1894,9 @@ "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" }, "JSONStream": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", - "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", + "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" @@ -2120,33 +1932,33 @@ "integrity": "sha1-rTKXxVcGneqLz+ek+kkbdcXd65E=" }, "koa": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/koa/-/koa-1.4.0.tgz", - "integrity": "sha1-X79tkMZq4Si3hnyi5UjOh0NDbXY=", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/koa/-/koa-1.6.0.tgz", + "integrity": "sha512-tW7xJGDG4LyhFUTtzIyqJCIaJIFgkre1tJPGNe/moRKOIU0L9vEIhW5z7iMX7FJTkYm45urdbPOGBp0VlWF03w==", "requires": { - "accepts": "1.3.4", + "accepts": "1.3.5", "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.8", + "debug": "2.6.9", "delegates": "1.0.0", "destroy": "1.0.4", "error-inject": "1.0.0", "escape-html": "1.0.3", - "fresh": "0.3.0", + "fresh": "0.5.2", "http-assert": "1.3.0", - "http-errors": "1.6.2", + "http-errors": "1.6.3", "koa-compose": "2.5.1", "koa-is-json": "1.0.0", - "mime-types": "2.1.17", + "mime-types": "2.1.18", "on-finished": "2.3.0", "only": "0.0.2", "parseurl": "1.3.2", - "statuses": "1.3.1", - "type-is": "1.6.15", - "vary": "1.1.1" + "statuses": "1.5.0", + "type-is": "1.6.16", + "vary": "1.1.2" } }, "koa-bodyparser": { @@ -2154,7 +1966,7 @@ "resolved": "https://registry.npmjs.org/koa-bodyparser/-/koa-bodyparser-2.5.0.tgz", "integrity": "sha1-PrckP0eZii53LbBfbcTg9PPMvfA=", "requires": { - "co-body": "5.1.1", + "co-body": "5.2.0", "copy-to": "2.0.1" } }, @@ -2197,8 +2009,8 @@ "integrity": "sha1-Tb26fnFZU9VobAO3w/29IUYx+HA=", "requires": { "co": "4.6.0", - "debug": "2.6.8", - "http-errors": "1.6.2", + "debug": "2.6.9", + "http-errors": "1.6.3", "methods": "1.1.2", "path-to-regexp": "1.7.0" } @@ -2209,9 +2021,9 @@ "integrity": "sha1-WkriRVZGgMbs9geeknX6UXOoYdw=", "requires": { "co": "4.6.0", - "debug": "2.6.8", + "debug": "2.6.9", "mz": "2.7.0", - "resolve-path": "1.3.3" + "resolve-path": "1.4.0" } }, "koa-static": { @@ -2219,7 +2031,7 @@ "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-2.1.0.tgz", "integrity": "sha1-z+KS6n2ryWqnI+SkiGFcxlrnQWk=", "requires": { - "debug": "2.6.8", + "debug": "2.6.9", "koa-send": "3.3.0" } }, @@ -2229,7 +2041,14 @@ "integrity": "sha512-zrBbpGMS+H1EzCOMG4/8lXbEdL8pLmjx8VqSCyTGgHpvNxNzyhX2ViG8zt8PPyYA7TlxbGB1vBsn6QyUfFBnvQ==", "requires": { "got": "7.1.0", - "qs": "6.4.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==" + } } }, "lakebtc_nodejs": { @@ -2238,6 +2057,13 @@ "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": { @@ -2250,14 +2076,29 @@ "request-promise": "4.1.1" }, "dependencies": { + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + }, "request-promise": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.1.1.tgz", "integrity": "sha1-JgIeT29W/Uwwn2vx69jJepWsH7U=", "requires": { - "bluebird": "3.5.0", + "bluebird": "3.5.1", "request-promise-core": "1.1.1", "stealthy-require": "1.1.1" + }, + "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" + } + } } } } @@ -2404,15 +2245,15 @@ "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" }, "lolex": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.1.tgz", - "integrity": "sha512-mQuW55GhduF3ppo+ZRUTz1PRjEh1hS5BbqU7d8D0ez2OKxHDod7StPPeAVKisZR5aLkHZjdGWSL42LSONUJsZw==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.6.0.tgz", + "integrity": "sha512-e1UtIo1pbrIqEXib/yMjHciyqkng5lc0rrIbytgjmRgDR9+2ceNIAcwOWSgylRjoEP9VdVguCSRwnNmlbnOUwA==", "dev": true }, "lowercase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", - "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" }, "map-stream": { "version": "0.1.0", @@ -2426,7 +2267,7 @@ "requires": { "charenc": "0.0.2", "crypt": "0.0.2", - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } }, "media-typer": { @@ -2451,16 +2292,16 @@ "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=" }, "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + "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.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "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.30.0" + "mime-db": "1.33.0" } }, "mimic-response": { @@ -2474,7 +2315,7 @@ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "1.1.8" + "brace-expansion": "1.1.11" } }, "minimist": { @@ -2491,29 +2332,24 @@ } }, "mocha": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.0.tgz", - "integrity": "sha512-ukB2dF+u4aeJjc6IGtPNnJXfeby5d4ZqySlIBT0OEyva/DrMjVm5HkQxKnHDLKEfEQBsEnwTg9HHhtPHJdTd8w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", "dev": true, "requires": { - "browser-stdout": "1.3.0", - "commander": "2.11.0", + "browser-stdout": "1.3.1", + "commander": "2.15.1", "debug": "3.1.0", - "diff": "3.3.1", + "diff": "3.5.0", "escape-string-regexp": "1.0.5", "glob": "7.1.2", - "growl": "1.10.3", + "growl": "1.10.5", "he": "1.1.1", + "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "4.4.0" + "supports-color": "5.4.0" }, "dependencies": { - "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", - "dev": true - }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -2524,12 +2360,12 @@ } }, "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } } } @@ -2541,9 +2377,9 @@ "dev": true }, "moment": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", - "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==" + "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", @@ -2566,9 +2402,9 @@ } }, "nan": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", - "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=" + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" }, "negotiator": { "version": "0.6.1", @@ -2576,24 +2412,16 @@ "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, "nise": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.2.0.tgz", - "integrity": "sha512-q9jXh3UNsMV28KeqI43ILz5+c3l+RiNW8mhurEwCKckuHQbL+hTJIKKTiUlCPKlgQ/OukFvSnKB/Jk3+sFbkGA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.3.3.tgz", + "integrity": "sha512-v1J/FLUB9PfGqZLGDBhQqODkbLotP0WtLo9R4EJY2PPu5f5Xg4o0rA8FDlmrjFSv9vBBKcfnOSpfYYuu5RTHqg==", "dev": true, "requires": { - "formatio": "1.2.0", + "@sinonjs/formatio": "2.0.0", "just-extend": "1.1.27", - "lolex": "1.6.0", + "lolex": "2.6.0", "path-to-regexp": "1.7.0", "text-encoding": "0.6.4" - }, - "dependencies": { - "lolex": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", - "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", - "dev": true - } } }, "nock": { @@ -2602,7 +2430,7 @@ "integrity": "sha1-0mxAAEs0SaZVuRt0rjxW/ALIRSU=", "requires": { "chai": "3.5.0", - "debug": "2.6.8", + "debug": "2.6.9", "deep-equal": "1.0.1", "json-stringify-safe": "5.0.1", "lodash": "2.4.1", @@ -2615,7 +2443,7 @@ "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", "requires": { - "assertion-error": "1.0.2", + "assertion-error": "1.1.0", "deep-eql": "0.1.3", "type-detect": "1.0.0" } @@ -2627,25 +2455,19 @@ } } }, + "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.3", - "resolved": "https://registry.npmjs.org/node-wex/-/node-wex-1.0.3.tgz", - "integrity": "sha512-iZtQOltQLOV4NuPxm2K5XDMjILehikE0hYDG2CHYWloddkSvzUnKrQP4fzb+RNUpeWSnCO9CHS7PjmEFOFG2xQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-wex/-/node-wex-1.0.4.tgz", + "integrity": "sha512-whE6tDpqbg9X0txHffIkGTbnxs4+ZxnyWUxDfRlxxT9UFQuktE7/l4dSAZgAzfF/AsU3YuBYeeNKaD6FBPmVHQ==", "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", @@ -2661,9 +2483,14 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", "requires": { - "hoek": "4.2.0" + "hoek": "4.2.1" } }, + "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", @@ -2677,32 +2504,27 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", "requires": { - "hoek": "4.2.0" + "hoek": "4.2.1" } } } }, "form-data": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", - "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", + "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.5", - "mime-types": "2.1.17" + "combined-stream": "1.0.6", + "mime-types": "2.1.18" } }, - "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.2.3", + "ajv": "5.5.2", "har-schema": "2.0.0" } }, @@ -2713,14 +2535,14 @@ "requires": { "boom": "4.3.1", "cryptiles": "3.1.2", - "hoek": "4.2.0", - "sntp": "2.0.2" + "hoek": "4.2.1", + "sntp": "2.1.0" } }, "hoek": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", - "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" + "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", @@ -2729,18 +2551,13 @@ "requires": { "assert-plus": "1.0.0", "jsprim": "1.4.1", - "sshpk": "1.13.1" + "sshpk": "1.14.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.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "request": { "version": "2.83.0", @@ -2748,44 +2565,49 @@ "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", "requires": { "aws-sign2": "0.7.0", - "aws4": "1.6.0", + "aws4": "1.7.0", "caseless": "0.12.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "extend": "3.0.1", "forever-agent": "0.6.1", - "form-data": "2.3.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.17", + "mime-types": "2.1.18", "oauth-sign": "0.8.2", "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.3", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "stringstream": "0.0.6", + "tough-cookie": "2.3.4", "tunnel-agent": "0.6.0", - "uuid": "3.1.0" + "uuid": "3.2.1" } }, "sntp": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.0.2.tgz", - "integrity": "sha1-UGQRDwr4X3z9t9a2ekACjOUrSys=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", "requires": { - "hoek": "4.2.0" + "hoek": "4.2.1" } }, - "tough-cookie": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", - "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "punycode": "1.4.1" + "safe-buffer": "5.1.2" } + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" } } }, @@ -2796,8 +2618,8 @@ "requires": { "event-stream": "3.3.4", "jsonic": "0.3.0", - "JSONStream": "1.3.1", - "request": "2.83.0", + "JSONStream": "1.3.3", + "request": "2.87.0", "signalr-client": "0.0.17" } }, @@ -2852,10 +2674,10 @@ "verror": "1.6.1" }, "dependencies": { - "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + "crypto": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-0.0.3.tgz", + "integrity": "sha1-RwqBuGvkxe4XrMggeh9TFa4g27A=" }, "extsprintf": { "version": "1.2.0", @@ -2868,26 +2690,10 @@ "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", "requires": { "async": "2.1.2", - "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" + "combined-stream": "1.0.6", + "mime-types": "2.1.18" } }, - "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", @@ -2901,7 +2707,7 @@ "aws-sign2": "0.6.0", "bl": "1.0.3", "caseless": "0.11.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "extend": "3.0.1", "forever-agent": "0.6.1", "form-data": "1.0.1", @@ -2911,11 +2717,11 @@ "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", + "mime-types": "2.1.18", "node-uuid": "1.4.8", "oauth-sign": "0.8.2", "qs": "5.2.1", - "stringstream": "0.0.5", + "stringstream": "0.0.6", "tough-cookie": "2.2.2", "tunnel-agent": "0.4.3" } @@ -2925,11 +2731,6 @@ "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", @@ -2993,9 +2794,9 @@ "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=", + "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" } @@ -3169,11 +2970,6 @@ "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", @@ -3200,7 +2996,7 @@ "node-uuid": "1.4.8", "oauth-sign": "0.3.0", "qs": "0.6.6", - "tough-cookie": "2.3.2", + "tough-cookie": "2.3.4", "tunnel-agent": "0.3.0" } }, @@ -3291,7 +3087,7 @@ "requires": { "mime": "1.2.11", "request": "2.44.0", - "websocket": "1.0.24" + "websocket": "1.0.26" }, "dependencies": { "asn1": { @@ -3412,11 +3208,6 @@ "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", @@ -3456,8 +3247,8 @@ "node-uuid": "1.4.8", "oauth-sign": "0.4.0", "qs": "1.2.2", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", + "stringstream": "0.0.6", + "tough-cookie": "2.3.4", "tunnel-agent": "0.4.3" } }, @@ -3469,25 +3260,20 @@ "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.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", + "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=" }, "quadrigacx": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/quadrigacx/-/quadrigacx-0.0.7.tgz", "integrity": "sha1-vptrBG28vDpNqRbBpQtJ2kiulsQ=", "requires": { - "request": "2.83.0" + "request": "2.87.0" } }, "querystring": { @@ -3496,13 +3282,13 @@ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", "requires": { "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", "unpipe": "1.0.0" } }, @@ -3535,47 +3321,38 @@ "resolved": "https://registry.npmjs.org/relieve/-/relieve-2.2.1.tgz", "integrity": "sha1-7PG6kC2B0yaGef8StamGlunncXY=", "requires": { - "bluebird": "3.5.0", - "debug": "2.6.8", + "bluebird": "3.5.1", + "debug": "2.6.9", "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.83.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", - "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", + "version": "2.87.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", + "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", "requires": { "aws-sign2": "0.7.0", - "aws4": "1.6.0", + "aws4": "1.7.0", "caseless": "0.12.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "extend": "3.0.1", "forever-agent": "0.6.1", - "form-data": "2.3.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.17", + "mime-types": "2.1.18", "oauth-sign": "0.8.2", "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.3", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.3.4", "tunnel-agent": "0.6.0", - "uuid": "3.1.0" + "uuid": "3.2.1" }, "dependencies": { "assert-plus": { @@ -3588,48 +3365,30 @@ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, - "boom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "requires": { - "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", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "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.0" - } - } + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" } }, - "hawk": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "requires": { - "boom": "4.3.1", - "cryptiles": "3.1.2", - "hoek": "4.2.0", - "sntp": "2.1.0" + "ajv": "5.5.2", + "har-schema": "2.0.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", @@ -3637,29 +3396,26 @@ "requires": { "assert-plus": "1.0.0", "jsprim": "1.4.1", - "sshpk": "1.13.1" + "sshpk": "1.14.1" } }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, - "sntp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "hoek": "4.2.0" + "safe-buffer": "5.1.2" } }, - "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" - } + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" } } }, @@ -3674,38 +3430,29 @@ "integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=", "dev": true, "requires": { - "bluebird": "3.5.0", + "bluebird": "3.5.1", "request-promise-core": "1.1.1", "stealthy-require": "1.1.1", - "tough-cookie": "2.3.3" + "tough-cookie": "2.3.4" }, "dependencies": { - "tough-cookie": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", - "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + }, + "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=", "dev": true, "requires": { - "punycode": "1.4.1" + "lodash": "4.17.10" } } } }, - "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.4" - }, - "dependencies": { - "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" - } - } - }, "resolve": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", @@ -3713,29 +3460,12 @@ "dev": true }, "resolve-path": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.3.3.tgz", - "integrity": "sha1-TYOrpkaMK45jKldeP1Kw+g2+Glw=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc=", "requires": { - "http-errors": "1.5.1", + "http-errors": "1.6.3", "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": { @@ -3749,9 +3479,14 @@ "integrity": "sha1-/s5hv6DBtSoga9axgZgYS91SOjs=" }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "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==" }, "samsam": { "version": "1.3.0", @@ -3765,46 +3500,46 @@ "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" }, "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, "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.24" + "websocket": "1.0.26" } }, "sinon": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.2.0.tgz", - "integrity": "sha512-FAdCcQ6lUAakWQMVRSIhiQU90d5EH1k3V6wRPrjxcYsv4vlBHjFzWLeoD63GoTKrFkfzVQs209aFW8V3cGLNtA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.5.0.tgz", + "integrity": "sha512-trdx+mB0VBBgoYucy6a9L7/jfQOmvGeaKZT4OOJ+lPAtI8623xyGr8wLiE4eojzBS8G9yXbhx42GHUOVLr4X2w==", "dev": true, "requires": { - "diff": "3.3.1", - "formatio": "1.2.0", + "@sinonjs/formatio": "2.0.0", + "diff": "3.5.0", "lodash.get": "4.4.2", - "lolex": "2.3.1", - "nise": "1.2.0", - "supports-color": "5.1.0", - "type-detect": "4.0.7" + "lolex": "2.6.0", + "nise": "1.3.3", + "supports-color": "5.4.0", + "type-detect": "4.0.8" }, "dependencies": { "supports-color": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", - "integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.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==", + "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==", "dev": true } } @@ -4228,6 +3963,11 @@ "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, @@ -4426,8 +4166,7 @@ }, "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==", + "bundled": true, "requires": { "safe-buffer": "5.1.1" } @@ -4539,9 +4278,9 @@ } }, "sshpk": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "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", @@ -4566,17 +4305,17 @@ "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" }, "stats-lite": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/stats-lite/-/stats-lite-2.1.0.tgz", - "integrity": "sha1-R2hU/biNA1xJvLv/cEyNhe6Esbo=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/stats-lite/-/stats-lite-2.1.1.tgz", + "integrity": "sha512-5QkxGCWGMbeQ+PXqI2N7ES6kW4IimvbMQBCKvZbekaEpf3InckVHiIXdCJbZsKUjLE7a3jha2cTEJqtOGGcVMw==", "requires": { "isnumber": "1.0.0" } }, "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, "stealthy-require": { "version": "1.1.1", @@ -4597,9 +4336,9 @@ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==" }, "strip-ansi": { "version": "3.0.1", @@ -4662,20 +4401,17 @@ "integrity": "sha512-O7L5hhSQHxuufWUdcTRPfuTh3phKfAZ/dqfxZFoxPCj2RYmpaSGLEIs016FCXItQwNr08yefUB5TSjzRYnajTA==" }, "tough-cookie": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", - "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "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.1" - } + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" }, "tweetnacl": { "version": "0.14.5", @@ -4688,8 +4424,8 @@ "resolved": "https://registry.npmjs.org/twitter/-/twitter-1.7.1.tgz", "integrity": "sha1-B2I3jx3BwFDkj2ZqypBOJLGpYvQ=", "requires": { - "deep-extend": "0.5.0", - "request": "2.83.0" + "deep-extend": "0.5.1", + "request": "2.87.0" } }, "type-detect": { @@ -4698,18 +4434,18 @@ "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=" }, "type-is": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", - "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", "requires": { "media-typer": "0.3.0", - "mime-types": "2.1.17" + "mime-types": "2.1.18" } }, "typedarray-to-buffer": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.2.tgz", - "integrity": "sha1-EBezLZhP9VbroQD1AViauhrOLgQ=", + "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==", "requires": { "is-typedarray": "1.0.0" } @@ -4720,9 +4456,9 @@ "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=" }, "underscore": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", - "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.0.tgz", + "integrity": "sha512-4IV1DSSxC1QK48j9ONFK1MoIAKKkbE8i7u55w2R6IqBqbT7A/iG7aZBCR2Bi8piF0Uz+i/MG1aeqLwl/5vqF+A==" }, "unpipe": { "version": "1.0.0", @@ -4753,14 +4489,14 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" }, "vary": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", - "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, "verror": { "version": "1.10.0", @@ -4780,20 +4516,20 @@ } }, "websocket": { - "version": "1.0.24", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.24.tgz", - "integrity": "sha1-dJA+dfJUW2suHeFCW8HJBZF6GJA=", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.26.tgz", + "integrity": "sha512-fjcrYDPIQxpTnqFQ9JjxUQcdvR89MFAOjPBlF+vjOt49w/XW4fJknUoMz/mDIn2eK1AdslVojcaOxOqyZZV8rw==", "requires": { - "debug": "2.6.8", - "nan": "2.7.0", - "typedarray-to-buffer": "3.1.2", + "debug": "2.6.9", + "nan": "2.10.0", + "typedarray-to-buffer": "3.1.5", "yaeti": "0.0.6" } }, "winston": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/winston/-/winston-2.3.1.tgz", - "integrity": "sha1-C0hCDZeMAYBM8CMLZIhhWYIloRk=", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.2.tgz", + "integrity": "sha512-4S/Ad4ZfSNl8OccCLxnJmNISWcm2joa6Q0YGDxlxMzH0fgSwWsjMt+SmlNwCqdpaPg3ev1HKkMBsIiXeSUwpbA==", "requires": { "async": "1.0.0", "colors": "1.0.3", @@ -4817,9 +4553,9 @@ "dev": true }, "ws": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.4.tgz", - "integrity": "sha1-V/QNA2gy5fUFVmKjl8Tedu1mv2E=", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", "requires": { "options": "0.0.6", "ultron": "1.0.2" @@ -4836,16 +4572,16 @@ "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" }, "zaif.jp": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/zaif.jp/-/zaif.jp-0.1.15.tgz", - "integrity": "sha1-ai7bpNI8JyS2xBMeNAZr4EwoSXo=", + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/zaif.jp/-/zaif.jp-0.1.16.tgz", + "integrity": "sha512-rQ7gbB260Q2hPMbOerkGq4dhBEp9w0lrzF6i3aKbN3b9qZSrOct9x/aj/rr6bH2udgxs5dtz0HOm+ZEmYdFU+A==", "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.83.0", - "ws": "1.1.4" + "request": "2.87.0", + "ws": "1.1.5" } } } diff --git a/package.json b/package.json index a85d69456..95c795044 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "sinon": "^4.2.0" }, "engines": { - "node": ">=6.0" + "node": ">=8.11.2" }, "license": "MIT", "repository": { From 7358e55c002f74e8db5c2516f484a7e798693428 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Thu, 24 May 2018 15:56:12 +0200 Subject: [PATCH 54/57] run appveyor tests using node v9 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 8c641601f..e9069ae41 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,7 @@ init: - git config --global core.autocrlf input environment: - nodejs_version: "8.11.2" + nodejs_version: "9" install: - ps: Install-Product node $env:nodejs_version From 52f6e5d14f01d5896390d6cdf90971c632e8ecc5 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Mon, 28 May 2018 17:48:23 +0200 Subject: [PATCH 55/57] pull async indicator wrap code out of base strat --- .../tradingAdvisor/asyncIndicatorRunner.js | 150 +++++++++++++ plugins/tradingAdvisor/baseTradingMethod.js | 206 ++++-------------- plugins/tradingAdvisor/tradingAdvisor.js | 6 +- 3 files changed, 200 insertions(+), 162 deletions(-) create mode 100644 plugins/tradingAdvisor/asyncIndicatorRunner.js diff --git a/plugins/tradingAdvisor/asyncIndicatorRunner.js b/plugins/tradingAdvisor/asyncIndicatorRunner.js new file mode 100644 index 000000000..da632a8b7 --- /dev/null +++ b/plugins/tradingAdvisor/asyncIndicatorRunner.js @@ -0,0 +1,150 @@ +const _ = require('lodash'); +const fs = require('fs'); +const util = require('../../core/util'); +const config = util.getConfig(); +const dirs = util.dirs(); +const log = require(dirs.core + 'log'); + +const talib = require(dirs.core + 'talib'); +const tulind = require(dirs.core + 'tulind'); + +const allowedTalibIndicators = _.keys(talib); +const allowedTulipIndicators = _.keys(tulind); + +const AsyncIndicatorRunner = function() { + this.talibIndicators = {}; + this.tulipIndicators = {}; + + this.candleProps = { + open: [], + high: [], + low: [], + close: [], + volume: [] + }; + + this.candlePropsCacheSize = 1000; + + this.inflight = false; + this.backlog = []; + this.age = 0; +} + +AsyncIndicatorRunner.prototype.processCandle = function(candle, next) { + if(this.inflight) { + return this.backlog.push({candle, next}); + } + + this.age++; + this.inflight = true; + + this.candleProps.open.push(candle.open); + this.candleProps.high.push(candle.high); + this.candleProps.low.push(candle.low); + this.candleProps.close.push(candle.close); + this.candleProps.volume.push(candle.volume); + + if(this.age > this.candlePropsCacheSize) { + this.candleProps.open.shift(); + this.candleProps.high.shift(); + this.candleProps.low.shift(); + this.candleProps.close.shift(); + this.candleProps.volume.shift(); + } + + this.calculateIndicators(next); +} + +AsyncIndicatorRunner.prototype.calculateIndicators = function(next) { + const done = _.after( + _.size(this.talibIndicators) + _.size(this.tulipIndicators), + this.handlePostFlight(next) + ); + + // handle result from talib + const talibResultHander = name => (err, result) => { + if(err) + util.die('TALIB ERROR:', err); + + this.talibIndicators[name].result = _.mapValues(result, v => _.last(v)); + done(); + } + + // handle result from talib + _.each( + this.talibIndicators, + (indicator, name) => indicator.run( + this.candleProps, + talibResultHander(name) + ) + ); + + // handle result from tulip + var tulindResultHander = name => (err, result) => { + if(err) + util.die('TULIP ERROR:', err); + + this.tulipIndicators[name].result = _.mapValues(result, v => _.last(v)); + done(); + } + + // handle result from tulip indicators + _.each( + this.tulipIndicators, + (indicator, name) => indicator.run( + this.candleProps, + tulindResultHander(name) + ) + ); +} + +AsyncIndicatorRunner.prototype.handlePostFlight = function(next) { + return () => { + next(); + this.inflight = false; + + if(this.backlog.length) { + const { candle, next } = this.backlog.shift(); + this.processCandle(candle, next); + } + } +} + +AsyncIndicatorRunner.prototype.addTalibIndicator = function(name, type, parameters) { + if(!talib) + util.die('Talib is not enabled'); + + if(!_.contains(allowedTalibIndicators, type)) + util.die('I do not know the talib indicator ' + type); + + if(this.setup) + util.die('Can only add talib indicators in the init method!'); + + var basectx = this; + + this.talibIndicators[name] = { + run: talib[type].create(parameters), + result: NaN + } +} + +AsyncIndicatorRunner.prototype.addTulipIndicator = function(name, type, parameters) { + if(!tulind) { + util.die('Tulip indicators is not enabled'); + } + + if(!_.contains(allowedTulipIndicators, type)) + util.die('I do not know the tulip indicator ' + type); + + if(this.setup) + util.die('Can only add tulip indicators in the init method!'); + + var basectx = this; + + this.tulipIndicators[name] = { + run: tulind[type].create(parameters), + result: NaN + } +} + +module.exports = AsyncIndicatorRunner; \ No newline at end of file diff --git a/plugins/tradingAdvisor/baseTradingMethod.js b/plugins/tradingAdvisor/baseTradingMethod.js index 09d6f8fb3..aadcd068a 100644 --- a/plugins/tradingAdvisor/baseTradingMethod.js +++ b/plugins/tradingAdvisor/baseTradingMethod.js @@ -1,27 +1,19 @@ -var _ = require('lodash'); -var fs = require('fs'); -var util = require('../../core/util'); -var config = util.getConfig(); -var dirs = util.dirs(); -var log = require(dirs.core + 'log'); - -var ENV = util.gekkoEnv(); -var mode = util.gekkoMode(); -var startTime = util.getStartTime(); - -var talib = require(dirs.core + 'talib'); -if(talib == null) { - log.warn('TALIB indicators could not be loaded, they will be unavailable.'); -} +const _ = require('lodash'); +const fs = require('fs'); +const util = require('../../core/util'); +const config = util.getConfig(); +const dirs = util.dirs(); +const log = require(dirs.core + 'log'); -var tulind = require(dirs.core + 'tulind'); -if(tulind == null) { - log.warn('TULIP indicators could not be loaded, they will be unavailable.'); -} +const ENV = util.gekkoEnv(); +const mode = util.gekkoMode(); +const startTime = util.getStartTime(); + +const indicatorsPath = dirs.methods + 'indicators/'; +const indicatorFiles = fs.readdirSync(indicatorsPath); +const Indicators = {}; -var indicatorsPath = dirs.methods + 'indicators/'; -var indicatorFiles = fs.readdirSync(indicatorsPath); -var Indicators = {}; +const AsyncIndicatorRunner = require('./asyncIndicatorRunner'); _.each(indicatorFiles, function(indicator) { const indicatorName = indicator.split(".")[0]; @@ -33,9 +25,7 @@ _.each(indicatorFiles, function(indicator) { } }); -var allowedIndicators = _.keys(Indicators); -var allowedTalibIndicators = _.keys(talib); -var allowedTulipIndicators = _.keys(tulind); +const allowedIndicators = _.keys(Indicators); var Base = function(settings) { _.bindAll(this); @@ -50,25 +40,14 @@ var Base = function(settings) { this.requiredHistory = 0; this.priceValue = 'close'; this.indicators = {}; - this.talibIndicators = {}; - this.tulipIndicators = {}; this.asyncTick = false; - this.candlePropsCacheSize = 1000; this.deferredTicks = []; this.completedWarmup = false; - this._prevAdvice; + this.asyncIndicatorRunner = new AsyncIndicatorRunner(); - this.candleProps = { - open: [], - high: [], - low: [], - close: [], - volume: [], - vwp: [], - trades: [] - }; + this._prevAdvice; // make sure we have all methods _.each(['init', 'check'], function(fn) { @@ -90,11 +69,10 @@ var Base = function(settings) { this.setup = true; - if(_.size(this.talibIndicators) || _.size(this.tulipIndicators)) + if(_.size(this.asyncIndicatorRunner.talibIndicators) || _.size(this.asyncIndicatorRunner.tulipIndicators)) this.asyncTick = true; - - if(_.size(this.indicators)) - this.hasSyncIndicators = true; + else + delete this.asyncIndicatorRunner; } // teach our base trading method events @@ -102,44 +80,35 @@ util.makeEventEmitter(Base); Base.prototype.tick = function(candle, done) { + this.age++; - if( - this.asyncTick && - this.hasSyncIndicators && - this.age !== this.processedTicks - ) { - // Gekko will call talib and run strat - // functions when talib is done, but by - // this time the sync indicators might be - // updated with future candles. - // - // See @link: https://github.com/askmike/gekko/issues/837#issuecomment-316549691 - this.deferredTicks.push(candle); - return done(); + const afterAsync = () => { + this.calculateSyncIndicators(candle, done); } - this.age++; - if(this.asyncTick) { - this.candleProps.open.push(candle.open); - this.candleProps.high.push(candle.high); - this.candleProps.low.push(candle.low); - this.candleProps.close.push(candle.close); - this.candleProps.volume.push(candle.volume); - this.candleProps.vwp.push(candle.vwp); - this.candleProps.trades.push(candle.trades); - - if(this.age > this.candlePropsCacheSize) { - this.candleProps.open.shift(); - this.candleProps.high.shift(); - this.candleProps.low.shift(); - this.candleProps.close.shift(); - this.candleProps.volume.shift(); - this.candleProps.vwp.shift(); - this.candleProps.trades.shift(); - } + this.asyncIndicatorRunner.processCandle(candle, () => { + + if(!this.talibIndicators) { + this.talibIndicators = this.asyncIndicatorRunner.talibIndicators; + this.tulipIndicators = this.asyncIndicatorRunner.tulipIndicators; + } + + afterAsync(); + }); + } else { + afterAsync(); } +} + +Base.prototype.isBusy = function() { + if(!this.asyncTick) + return false; + + return this.asyncIndicatorRunner.inflight; +} +Base.prototype.calculateSyncIndicators = function(candle, done) { // update all indicators var price = candle[this.priceValue]; _.each(this.indicators, function(i) { @@ -149,62 +118,9 @@ Base.prototype.tick = function(candle, done) { i.update(candle); },this); - // update the trading method - if(!this.asyncTick) { - this.propogateTick(candle); - - return done(); - } - - this.tickDone = done; - - var next = _.after( - _.size(this.talibIndicators) + _.size(this.tulipIndicators), - () => { - this.propogateTick(candle); - this.tickDone(); - } - ); - - var basectx = this; - - // handle result from talib - var talibResultHander = function(err, result) { - if(err) - util.die('TALIB ERROR:', err); + this.propogateTick(candle); - // fn is bound to indicator - this.result = _.mapValues(result, v => _.last(v)); - next(candle); - } - - // handle result from talib - _.each( - this.talibIndicators, - indicator => indicator.run( - basectx.candleProps, - talibResultHander.bind(indicator) - ) - ); - - // handle result from tulip - var tulindResultHander = function(err, result) { - if(err) - util.die('TULIP ERROR:', err); - - // fn is bound to indicator - this.result = _.mapValues(result, v => _.last(v)); - next(candle); - } - - // handle result from tulip indicators - _.each( - this.tulipIndicators, - indicator => indicator.run( - basectx.candleProps, - tulindResultHander.bind(indicator) - ) - ); + return done(); } Base.prototype.propogateTick = function(candle) { @@ -268,39 +184,11 @@ Base.prototype.propogateTick = function(candle) { } Base.prototype.addTalibIndicator = function(name, type, parameters) { - if(!talib) - util.die('Talib is not enabled'); - - if(!_.contains(allowedTalibIndicators, type)) - util.die('I do not know the talib indicator ' + type); - - if(this.setup) - util.die('Can only add talib indicators in the init method!'); - - var basectx = this; - - this.talibIndicators[name] = { - run: talib[type].create(parameters), - result: NaN - } + this.asyncIndicatorRunner.addTalibIndicator(name, type, parameters); } Base.prototype.addTulipIndicator = function(name, type, parameters) { - if(!tulind) - util.die('Tulip indicators is not enabled'); - - if(!_.contains(allowedTulipIndicators, type)) - util.die('I do not know the tulip indicator ' + type); - - if(this.setup) - util.die('Can only add tulip indicators in the init method!'); - - var basectx = this; - - this.tulipIndicators[name] = { - run: tulind[type].create(parameters), - result: NaN - } + this.asyncIndicatorRunner.addTulipIndicator(name, type, parameters); } Base.prototype.addIndicator = function(name, type, parameters) { diff --git a/plugins/tradingAdvisor/tradingAdvisor.js b/plugins/tradingAdvisor/tradingAdvisor.js index 169b9f0f2..1f013fce2 100644 --- a/plugins/tradingAdvisor/tradingAdvisor.js +++ b/plugins/tradingAdvisor/tradingAdvisor.js @@ -81,9 +81,9 @@ Actor.prototype.setupTradingMethod = function() { // process the 1m candles Actor.prototype.processCandle = function(candle, done) { this.candle = candle; - const completedBatches = this.batcher.write([candle]); - if(completedBatches) { - this.next = _.after(completedBatches, done); + const completedBatch = this.batcher.write([candle]); + if(completedBatch) { + this.next = done; } else { done(); this.next = _.noop; From 658bf76eba395f9f76492ad8b5c0b30643455c99 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Mon, 28 May 2018 17:55:14 +0200 Subject: [PATCH 56/57] remove cp.js --- core/budfox/candleManager.js | 1 - core/budfox/marketDataProvider.js | 1 - core/cp.js | 72 ---- core/markets/importer.js | 2 - core/markets/leech.js | 6 - .../messageHandlers/importerHandler.js | 4 +- package-lock.json | 392 ++++++++++++++++-- package.json | 2 + plugins.js | 13 +- plugins/backtestResultExporter.js | 21 +- plugins/tradingAdvisor/baseTradingMethod.js | 4 +- 11 files changed, 390 insertions(+), 128 deletions(-) delete mode 100644 core/cp.js diff --git a/core/budfox/candleManager.js b/core/budfox/candleManager.js index 2df33a89c..b90cc692d 100644 --- a/core/budfox/candleManager.js +++ b/core/budfox/candleManager.js @@ -10,7 +10,6 @@ var util = require(__dirname + '/../util'); var dirs = util.dirs(); var config = util.getConfig(); var log = require(dirs.core + 'log'); -var cp = require(dirs.core + 'cp'); var CandleCreator = require(dirs.budfox + 'candleCreator'); diff --git a/core/budfox/marketDataProvider.js b/core/budfox/marketDataProvider.js index 2cf3cd9cc..6576f20e7 100644 --- a/core/budfox/marketDataProvider.js +++ b/core/budfox/marketDataProvider.js @@ -10,7 +10,6 @@ const util = require(__dirname + '/../util'); const MarketFetcher = require('./marketFetcher'); const dirs = util.dirs(); -const cp = require(dirs.core + 'cp'); const Manager = function(config) { diff --git a/core/cp.js b/core/cp.js deleted file mode 100644 index 17bc0730d..000000000 --- a/core/cp.js +++ /dev/null @@ -1,72 +0,0 @@ -// functions that emit data to the parent process. -// -// noops if this gekko instance is not a child process! - -var _ = require('lodash'); -var util = require('./util'); -var config = util.getConfig(); -var dirs = util.dirs(); -var moment = require('moment'); - -var ENV = util.gekkoEnv(); - -var message = (type, payload) => { - payload.type = type; - process.send(payload); -} - -var cp = { - - // object like: - // - // { - // action: 'buy', - // price: 942.80838846, - // portfolio: { asset: 1.07839516, currency: 0, balance: false }, - // balance: 1016.7200029226638, - // date: - // } - trade: trade => message('trade', { trade }), - // object like: - // { - // currency: 'USDT', - // asset: 'BTC', - // startTime: '2017-03-25 19:41:00', - // endTime: '2017-03-25 20:01:00', - // timespan: '20 minutes', - // market: -0.316304880517734, - // balance: 1016.7200029226638, - // profit: -26.789997197336106, - // relativeProfit: -2.5672966425099304, - // yearlyProfit: '-704041.12634599', - // relativeYearlyProfit: '-67468.55576516', - // startPrice: 945.80000002, - // endPrice: 942.80838846, - // trades: 10, - // startBalance: 1043.5100001199999, - // sharpe: -2.676305165560598 - // } - report: report => message('report', { report }), - - // object like: - // { - // entryAt: Moment<'2017-03-25 19:41:00'>, - // entryPrice: 10.21315498, - // entryBalance: 98.19707799420277, - // exitAt: Moment<'2017-03-25 19:41:00'> - // exitPrice: 10.22011632, - // exitBalance: 97.9692176, - // duration: 3600000, - // pnl: -0.2278603942027786, - // profit: -0.2320439659276161, - // } - roundtrip: roundtrip => message('roundtrip', { roundtrip }), -} - -if(ENV !== 'child-process') { - _.each(cp, (val, key) => { - cp[key] = _.noop; - }); -} - -module.exports = cp; \ No newline at end of file diff --git a/core/markets/importer.js b/core/markets/importer.js index 14df16ad1..7c68f4cb7 100644 --- a/core/markets/importer.js +++ b/core/markets/importer.js @@ -4,7 +4,6 @@ var config = util.getConfig(); var dirs = util.dirs(); var log = require(dirs.core + 'log'); var moment = require('moment'); -var cp = require(dirs.core + 'cp'); var adapter = config[config.adapter]; var daterange = config.importer.daterange; @@ -107,7 +106,6 @@ Market.prototype.processTrades = function(trades) { if(_.size(trades)) { let lastAtTS = _.last(trades).date; let lastAt = moment.unix(lastAtTS).utc().format(); - cp.update(lastAt); } setTimeout(this.get, 1000); diff --git a/core/markets/leech.js b/core/markets/leech.js index 2c8ed5149..ee2f314eb 100644 --- a/core/markets/leech.js +++ b/core/markets/leech.js @@ -10,7 +10,6 @@ const dirs = util.dirs(); const config = util.getConfig(); const exchangeChecker = require(dirs.core + 'exchangeChecker'); -const cp = require(dirs.core + 'cp'); const adapter = config[config.adapter]; const Reader = require(dirs.gekko + adapter.path + '/reader'); @@ -88,13 +87,8 @@ Market.prototype.processCandles = function(err, candles) { }, this); this.sendStartAt(_.first(candles)); - cp.lastCandle(_.last(candles)); this.latestTs = _.last(candles).start.unix() + 1; } -Market.prototype.sendStartAt = _.once(function(candle) { - cp.firstCandle(candle); -}); - module.exports = Market; diff --git a/core/workers/pipeline/messageHandlers/importerHandler.js b/core/workers/pipeline/messageHandlers/importerHandler.js index 8b97543df..8ad70d977 100644 --- a/core/workers/pipeline/messageHandlers/importerHandler.js +++ b/core/workers/pipeline/messageHandlers/importerHandler.js @@ -3,10 +3,10 @@ module.exports = cb => { return { message: message => { - if(message.type === 'update') + if(message.event === 'marketUpdate') cb(null, { done: false, - latest: message.latest + latest: message.payload }) else if(message.type === 'error') { diff --git a/package-lock.json b/package-lock.json index af25b8e0c..e3b7d308d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -125,6 +125,20 @@ } } }, + "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" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", @@ -176,6 +190,54 @@ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "5.1.2" + } + } + } + }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -243,8 +305,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "bcrypt-pbkdf": { "version": "1.0.1", @@ -282,6 +343,11 @@ } } }, + "bindings": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz", + "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==" + }, "bintrees": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.0.tgz", @@ -439,7 +505,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -807,6 +872,11 @@ } } }, + "chownr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -852,6 +922,11 @@ "resolved": "https://registry.npmjs.org/co-read/-/co-read-0.0.1.tgz", "integrity": "sha1-+Bs+uKhmdf7FHj2IOn9WToc8k4k=" }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, "coffeescript": { "version": "1.12.7", "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", @@ -895,8 +970,12 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "content-disposition": { "version": "0.5.2", @@ -1053,6 +1132,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -1224,11 +1308,33 @@ "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" }, + "fs-minipass": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "requires": { + "minipass": "2.3.3" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.3" + } }, "gdax": { "version": "0.4.2", @@ -1559,7 +1665,6 @@ "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", @@ -1639,6 +1744,11 @@ "has-symbol-support-x": "1.4.2" } }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "hawk": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", @@ -1747,6 +1857,14 @@ "safer-buffer": "2.1.2" } }, + "ignore-walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "requires": { + "minimatch": "3.0.4" + } + }, "inflation": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.0.0.tgz", @@ -1756,7 +1874,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -1767,6 +1884,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, "int": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/int/-/int-0.1.1.tgz", @@ -1786,6 +1908,14 @@ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } + }, "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", @@ -1893,15 +2023,6 @@ "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" }, - "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" - } - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -2309,11 +2430,15 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz", "integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=" }, + "minctest": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/minctest/-/minctest-0.0.2.tgz", + "integrity": "sha1-mDOxA3vZWrkIYzrQ3Fkn0Ocq3tM=" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "1.1.11" } @@ -2323,6 +2448,23 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, + "minipass": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.3.tgz", + "integrity": "sha512-/jAn9/tEX4gnpyRATxgHEOV6xbcyxgT7iUnxo9Y3+OB0zX00TgKIv/2FZCf5brBbICcwbLqVv2ImjvWWrQMSYw==", + "requires": { + "safe-buffer": "5.1.2", + "yallist": "3.0.2" + } + }, + "minizlib": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", + "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", + "requires": { + "minipass": "2.3.3" + } + }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -2406,6 +2548,16 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" }, + "needle": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.1.tgz", + "integrity": "sha512-t/ZswCM9JTWjAdXS9VpvqhI2Ct2sL2MdY4fUXqGJaGBk13ge99ObqRksRTbBE56K+wxUXwwfZYOuZHifFW9q+Q==", + "requires": { + "debug": "2.6.9", + "iconv-lite": "0.4.23", + "sax": "1.2.4" + } + }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", @@ -2455,6 +2607,23 @@ } } }, + "node-pre-gyp": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.9.1.tgz", + "integrity": "sha1-8RwHUW3ZL4cZnbx+GDjqt81WyeA=", + "requires": { + "detect-libc": "1.0.3", + "mkdirp": "0.5.1", + "needle": "2.2.1", + "nopt": "4.0.1", + "npm-packlist": "1.1.10", + "npmlog": "4.1.2", + "rc": "1.2.8", + "rimraf": "2.6.2", + "semver": "5.4.1", + "tar": "4.4.4" + } + }, "node-uuid": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", @@ -2616,9 +2785,9 @@ "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", - "JSONStream": "1.3.3", "request": "2.87.0", "signalr-client": "0.0.17" } @@ -2636,6 +2805,40 @@ "resolved": "https://registry.npmjs.org/nonce/-/nonce-1.0.4.tgz", "integrity": "sha1-7nMCrejBvvR28wG4yR9cxRpIdhI=" }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1.1.1", + "osenv": "0.1.5" + } + }, + "npm-bundled": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.3.tgz", + "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==" + }, + "npm-packlist": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.10.tgz", + "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", + "requires": { + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.3" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "1.1.5", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, "nth-check": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", @@ -2652,6 +2855,11 @@ "int": "0.1.1" } }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", @@ -2759,7 +2967,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1.0.2" } @@ -2783,6 +2990,25 @@ "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, "p-cancelable": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", @@ -3292,6 +3518,29 @@ "unpipe": "1.0.0" } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "0.6.0", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, "read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", @@ -3478,6 +3727,14 @@ "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", "integrity": "sha1-/s5hv6DBtSoga9axgZgYS91SOjs=" }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "requires": { + "glob": "7.1.2" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -3494,16 +3751,31 @@ "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", "dev": true }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "semver": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, "signalr-client": { "version": "0.0.17", "resolved": "https://registry.npmjs.org/signalr-client/-/signalr-client-0.0.17.tgz", @@ -4164,13 +4436,6 @@ } } }, - "string_decoder": { - "version": "1.0.3", - "bundled": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, "string-width": { "version": "1.0.2", "bundled": true, @@ -4180,6 +4445,13 @@ "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 @@ -4330,6 +4602,16 @@ "duplexer": "0.1.1" } }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", @@ -4348,11 +4630,38 @@ "ansi-regex": "2.1.1" } }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, + "talib": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/talib/-/talib-1.0.4.tgz", + "integrity": "sha512-s4QITrKVUZ4feHl78296sMKLRWBG8dbuBPE0nr8NHVJimzrz1GVI27PBfBHlx0Bx1+Bmu8/6IbQM+84x0B0eKQ==", + "requires": { + "nan": "2.10.0" + } + }, + "tar": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.4.tgz", + "integrity": "sha512-mq9ixIYfNF9SK0IS/h2HKMu8Q2iaCuhDDsZhdEag/FHv8fOaYld4vN7ouMgcSSt5WKZzPs8atclTcJm36OTh4w==", + "requires": { + "chownr": "1.0.1", + "fs-minipass": "1.2.5", + "minipass": "2.3.3", + "minizlib": "1.1.0", + "mkdirp": "0.5.1", + "safe-buffer": "5.1.2", + "yallist": "3.0.2" + } + }, "text-encoding": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", @@ -4408,6 +4717,17 @@ "punycode": "1.4.1" } }, + "tulind": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/tulind/-/tulind-0.8.10.tgz", + "integrity": "sha1-/VB5TJxfGOCp870CJT6/JLu93DE=", + "requires": { + "bindings": "1.3.0", + "minctest": "0.0.2", + "nan": "2.10.0", + "node-pre-gyp": "0.9.1" + } + }, "tunnel-agent": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", @@ -4526,6 +4846,14 @@ "yaeti": "0.0.6" } }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "1.0.2" + } + }, "winston": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.2.tgz", @@ -4549,8 +4877,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { "version": "1.1.5", @@ -4571,6 +4898,11 @@ "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" }, + "yallist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" + }, "zaif.jp": { "version": "0.1.16", "resolved": "https://registry.npmjs.org/zaif.jp/-/zaif.jp-0.1.16.tgz", diff --git a/package.json b/package.json index 95c795044..57ced7bb8 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,10 @@ "semver": "5.4.1", "sqlite3": "^3.1.13", "stats-lite": "^2.0.4", + "talib": "^1.0.4", "tiny-promisify": "^0.1.1", "toml": "^2.3.0", + "tulind": "^0.8.10", "twitter": "^1.7.1", "zaif.jp": "^0.1.4" }, diff --git a/plugins.js b/plugins.js index 4e08b84b7..415d9f4b7 100644 --- a/plugins.js +++ b/plugins.js @@ -193,18 +193,25 @@ var plugins = [ modes: ['realtime'] }, { - name: 'event logger', + name: 'Event logger', description: 'Logs all gekko events.', slug: 'eventLogger', async: false, modes: ['realtime', 'backtest'] }, - { - name: 'backtest result export', + { + name: 'Backtest result export', description: 'Exports the results of a gekko backtest', slug: 'backtestResultExporter', async: false, modes: ['backtest'] + }, + { + name: 'Child to parent', + description: 'Relays events from the child to the parent process', + slug: 'childToParent', + async: false, + modes: ['realtime'] } ]; diff --git a/plugins/backtestResultExporter.js b/plugins/backtestResultExporter.js index fe2a0e0ee..9563753b7 100644 --- a/plugins/backtestResultExporter.js +++ b/plugins/backtestResultExporter.js @@ -1,3 +1,6 @@ +// Small plugin that subscribes to some events, stores +// them and sends it to the parent process. + const log = require('../core/log'); const _ = require('lodash'); const util = require('../core/util.js'); @@ -5,7 +8,7 @@ const config = util.getConfig(); const moment = require('moment'); const fs = require('fs'); -var Actor = function() { +const BacktestResultExporter = function() { this.performanceReport; this.roundtrips = []; this.stratUpdates = []; @@ -29,7 +32,7 @@ var Actor = function() { _.bindAll(this); } -Actor.prototype.processStratCandle = function(candle) { +BacktestResultExporter.prototype.processStratCandle = function(candle) { let strippedCandle; if(!this.candleProps) { @@ -47,7 +50,7 @@ Actor.prototype.processStratCandle = function(candle) { this.stratCandles.push(strippedCandle); }; -Actor.prototype.processRoundtrip = function(roundtrip) { +BacktestResultExporter.prototype.processRoundtrip = function(roundtrip) { this.roundtrips.push({ ...roundtrip, entryAt: roundtrip.entryAt.unix(), @@ -55,25 +58,25 @@ Actor.prototype.processRoundtrip = function(roundtrip) { }); }; -Actor.prototype.processTradeCompleted = function(trade) { +BacktestResultExporter.prototype.processTradeCompleted = function(trade) { this.trades.push({ ...trade, date: trade.date.unix() }); }; -Actor.prototype.processStratUpdate = function(stratUpdate) { +BacktestResultExporter.prototype.processStratUpdate = function(stratUpdate) { this.stratUpdates.push({ ...stratUpdate, date: stratUpdate.date.unix() }); } -Actor.prototype.processPerformanceReport = function(performanceReport) { +BacktestResultExporter.prototype.processPerformanceReport = function(performanceReport) { this.performanceReport = performanceReport; } -Actor.prototype.finalize = function(done) { +BacktestResultExporter.prototype.finalize = function(done) { const backtest = { performanceReport: this.performanceReport }; @@ -98,7 +101,7 @@ Actor.prototype.finalize = function(done) { done(); }; -Actor.prototype.writeToDisk = function(next) { +BacktestResultExporter.prototype.writeToDisk = function(next) { const now = moment().format('YYYY-MM-DD HH:mm:ss'); const filename = `backtest-${config.tradingAdvisor.method}-${now}.log`; fs.writeFile( @@ -113,4 +116,4 @@ Actor.prototype.writeToDisk = function(next) { ); } -module.exports = Actor; +module.exports = BacktestResultExporter; diff --git a/plugins/tradingAdvisor/baseTradingMethod.js b/plugins/tradingAdvisor/baseTradingMethod.js index aadcd068a..1615e6443 100644 --- a/plugins/tradingAdvisor/baseTradingMethod.js +++ b/plugins/tradingAdvisor/baseTradingMethod.js @@ -178,8 +178,8 @@ Base.prototype.propogateTick = function(candle) { }); // are we totally finished? - var done = this.age === this.processedTicks; - if(done && this.finishCb) + const completed = this.age === this.processedTicks; + if(completed && this.finishCb) this.finishCb(); } From ea6df427ff8238e5e356a862ab70f13b57161ad2 Mon Sep 17 00:00:00 2001 From: Mike van Rossum Date: Mon, 28 May 2018 18:16:45 +0200 Subject: [PATCH 57/57] remove tulind & talib from default deps --- package-lock.json | 667 +++++++++++++--------------------------------- package.json | 2 - 2 files changed, 190 insertions(+), 479 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8e48dc4bf..fb277a4ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gekko", - "version": "0.5.13", + "version": "0.5.14", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -134,11 +134,6 @@ "through": "2.3.8" } }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, "accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", @@ -190,54 +185,6 @@ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.6" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "5.1.2" - } - } - } - }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -298,7 +245,7 @@ "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", + "core-js": "2.5.7", "regenerator-runtime": "0.11.1" } }, @@ -343,11 +290,6 @@ } } }, - "bindings": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz", - "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==" - }, "bintrees": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.0.tgz", @@ -363,18 +305,6 @@ "request": "2.87.0", "underscore": "1.9.0", "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": { @@ -409,27 +339,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" }, - "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" - } - } - } - }, "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", @@ -523,10 +432,9 @@ } }, "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==", - "dev": true + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=" }, "btc-china-fork": { "version": "0.0.6", @@ -815,7 +723,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", - "dev": true, "requires": { "assertion-error": "1.1.0", "check-error": "1.0.2", @@ -823,23 +730,6 @@ "get-func-name": "2.0.0", "pathval": "1.1.0", "type-detect": "4.0.8" - }, - "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==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "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==", - "dev": true - } } }, "chalk": { @@ -862,8 +752,7 @@ "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" }, "cheerio": { "version": "0.19.0", @@ -884,11 +773,6 @@ } } }, - "chownr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" - }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -934,11 +818,6 @@ "resolved": "https://registry.npmjs.org/co-read/-/co-read-0.0.1.tgz", "integrity": "sha1-+Bs+uKhmdf7FHj2IOn9WToc8k4k=" }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, "coffeescript": { "version": "1.12.7", "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", @@ -952,6 +831,61 @@ "babel-runtime": "6.26.0" } }, + "coingi": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/coingi/-/coingi-1.0.7.tgz", + "integrity": "sha512-qI7/mAGhqGH650Q3pWNoliJeOEl73fPMBI4RRAnnBlI5iPDqtemsQb+OSPpHtFHXgdL7YlJ5nCR+Aqtaq2rsVA==", + "requires": { + "chai": "4.1.2", + "got": "7.1.0", + "mocha": "3.5.3" + }, + "dependencies": { + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "requires": { + "graceful-readlink": "1.0.1" + } + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "requires": { + "ms": "2.0.0" + } + }, + "mocha": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", + "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.6.8", + "diff": "3.2.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.1", + "growl": "1.9.2", + "he": "1.1.1", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + } + }, + "supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "requires": { + "has-flag": "1.0.0" + } + } + } + }, "colors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", @@ -984,11 +918,6 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, "content-disposition": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", @@ -1014,9 +943,9 @@ "integrity": "sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU=" }, "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==" + "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", @@ -1100,18 +1029,11 @@ } }, "deep-eql": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", - "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "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": "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=" - } + "type-detect": "4.0.8" } }, "deep-equal": { @@ -1144,16 +1066,10 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" - }, "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", + "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=" }, "dom-serializer": { "version": "0.1.0", @@ -1320,34 +1236,11 @@ "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" }, - "fs-minipass": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", - "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", - "requires": { - "minipass": "2.3.3" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.3" - } - }, "gdax": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/gdax/-/gdax-0.4.2.tgz", @@ -1650,8 +1543,7 @@ "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" }, "get-stream": { "version": "3.0.0", @@ -1674,9 +1566,9 @@ } }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -1707,11 +1599,15 @@ "url-to-options": "1.0.1" } }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" + }, "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=" }, "har-schema": { "version": "2.0.0", @@ -1738,10 +1634,9 @@ } }, "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" }, "has-symbol-support-x": { "version": "1.4.2", @@ -1756,11 +1651,6 @@ "has-symbol-support-x": "1.4.2" } }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" - }, "hawk": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", @@ -1775,8 +1665,7 @@ "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" }, "hoek": { "version": "2.16.3", @@ -1869,14 +1758,6 @@ "safer-buffer": "2.1.2" } }, - "ignore-walk": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", - "requires": { - "minimatch": "3.0.4" - } - }, "inflation": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.0.0.tgz", @@ -1896,11 +1777,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, "int": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/int/-/int-0.1.1.tgz", @@ -1920,14 +1796,6 @@ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "1.0.1" - } - }, "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", @@ -2020,6 +1888,11 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=" + }, "jsonic": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/jsonic/-/jsonic-0.3.0.tgz", @@ -2209,11 +2082,6 @@ "request-promise": "4.1.1" }, "dependencies": { - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" - }, "request-promise": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.1.1.tgz", @@ -2222,16 +2090,6 @@ "bluebird": "3.5.1", "request-promise-core": "1.1.1", "stealthy-require": "1.1.1" - }, - "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" - } - } } } } @@ -2260,6 +2118,11 @@ "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" }, + "lodash._basecreate": { + "version": "3.0.3", + "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", @@ -2325,6 +2188,16 @@ "lodash._createassigner": "3.1.1" } }, + "lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "requires": { + "lodash._baseassign": "3.2.0", + "lodash._basecreate": "3.0.3", + "lodash._isiterateecall": "3.0.9" + } + }, "lodash.foreach": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-3.0.0.tgz", @@ -2378,9 +2251,9 @@ "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" }, "lolex": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.6.0.tgz", - "integrity": "sha512-e1UtIo1pbrIqEXib/yMjHciyqkng5lc0rrIbytgjmRgDR9+2ceNIAcwOWSgylRjoEP9VdVguCSRwnNmlbnOUwA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.0.tgz", + "integrity": "sha512-uJkH2e0BVfU5KOJUevbTOtpDduooSarH5PopO+LfM/vZf8Z9sJzODqKev804JYM2i++ktJfUmC1le4LwFQ1VMg==", "dev": true }, "lowercase-keys": { @@ -2442,11 +2315,6 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz", "integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=" }, - "minctest": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/minctest/-/minctest-0.0.2.tgz", - "integrity": "sha1-mDOxA3vZWrkIYzrQ3Fkn0Ocq3tM=" - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -2460,23 +2328,6 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, - "minipass": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.3.tgz", - "integrity": "sha512-/jAn9/tEX4gnpyRATxgHEOV6xbcyxgT7iUnxo9Y3+OB0zX00TgKIv/2FZCf5brBbICcwbLqVv2ImjvWWrQMSYw==", - "requires": { - "safe-buffer": "5.1.2", - "yallist": "3.0.2" - } - }, - "minizlib": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", - "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", - "requires": { - "minipass": "2.3.3" - } - }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -2504,6 +2355,12 @@ "supports-color": "5.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==", + "dev": true + }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -2513,6 +2370,38 @@ "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", @@ -2560,16 +2449,6 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" }, - "needle": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.1.tgz", - "integrity": "sha512-t/ZswCM9JTWjAdXS9VpvqhI2Ct2sL2MdY4fUXqGJaGBk13ge99ObqRksRTbBE56K+wxUXwwfZYOuZHifFW9q+Q==", - "requires": { - "debug": "2.6.9", - "iconv-lite": "0.4.23", - "sax": "1.2.4" - } - }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", @@ -2583,7 +2462,7 @@ "requires": { "@sinonjs/formatio": "2.0.0", "just-extend": "1.1.27", - "lolex": "2.6.0", + "lolex": "2.7.0", "path-to-regexp": "1.7.0", "text-encoding": "0.6.4" } @@ -2612,30 +2491,33 @@ "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=" } } }, - "node-pre-gyp": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.9.1.tgz", - "integrity": "sha1-8RwHUW3ZL4cZnbx+GDjqt81WyeA=", - "requires": { - "detect-libc": "1.0.3", - "mkdirp": "0.5.1", - "needle": "2.2.1", - "nopt": "4.0.1", - "npm-packlist": "1.1.10", - "npmlog": "4.1.2", - "rc": "1.2.8", - "rimraf": "2.6.2", - "semver": "5.4.1", - "tar": "4.4.4" - } - }, "node-uuid": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", @@ -2817,40 +2699,6 @@ "resolved": "https://registry.npmjs.org/nonce/-/nonce-1.0.4.tgz", "integrity": "sha1-7nMCrejBvvR28wG4yR9cxRpIdhI=" }, - "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" - } - }, - "npm-bundled": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.3.tgz", - "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==" - }, - "npm-packlist": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.10.tgz", - "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", - "requires": { - "ignore-walk": "3.0.1", - "npm-bundled": "1.0.3" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "requires": { - "are-we-there-yet": "1.1.5", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, "nth-check": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", @@ -2867,11 +2715,6 @@ "int": "0.1.1" } }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", @@ -3002,25 +2845,6 @@ "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, "p-cancelable": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", @@ -3065,8 +2889,7 @@ "pathval": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", - "dev": true + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=" }, "pause-stream": { "version": "0.0.11", @@ -3530,29 +3353,6 @@ "unpipe": "1.0.0" } }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "0.6.0", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } - } - }, "read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", @@ -3689,28 +3489,25 @@ "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", "request-promise-core": "1.1.1", "stealthy-require": "1.1.1", "tough-cookie": "2.3.4" + } + }, + "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" }, "dependencies": { "lodash": { "version": "4.17.10", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - }, - "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=", - "dev": true, - "requires": { - "lodash": "4.17.10" - } + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" } } }, @@ -3739,14 +3536,6 @@ "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", "integrity": "sha1-/s5hv6DBtSoga9axgZgYS91SOjs=" }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "requires": { - "glob": "7.1.2" - } - }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -3763,31 +3552,16 @@ "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", "dev": true }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, "semver": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, "signalr-client": { "version": "0.0.17", "resolved": "https://registry.npmjs.org/signalr-client/-/signalr-client-0.0.17.tgz", @@ -3803,14 +3577,20 @@ "dev": true, "requires": { "@sinonjs/formatio": "2.0.0", - "diff": "3.5.0", + "diff": "3.2.0", "lodash.get": "4.4.2", - "lolex": "2.6.0", + "lolex": "2.7.0", "nise": "1.3.3", "supports-color": "5.4.0", "type-detect": "4.0.8" }, "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", @@ -3819,12 +3599,6 @@ "requires": { "has-flag": "3.0.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==", - "dev": true } } }, @@ -4614,16 +4388,6 @@ "duplexer": "0.1.1" } }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", @@ -4642,38 +4406,11 @@ "ansi-regex": "2.1.1" } }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, - "talib": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/talib/-/talib-1.0.4.tgz", - "integrity": "sha512-s4QITrKVUZ4feHl78296sMKLRWBG8dbuBPE0nr8NHVJimzrz1GVI27PBfBHlx0Bx1+Bmu8/6IbQM+84x0B0eKQ==", - "requires": { - "nan": "2.10.0" - } - }, - "tar": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.4.tgz", - "integrity": "sha512-mq9ixIYfNF9SK0IS/h2HKMu8Q2iaCuhDDsZhdEag/FHv8fOaYld4vN7ouMgcSSt5WKZzPs8atclTcJm36OTh4w==", - "requires": { - "chownr": "1.0.1", - "fs-minipass": "1.2.5", - "minipass": "2.3.3", - "minizlib": "1.1.0", - "mkdirp": "0.5.1", - "safe-buffer": "5.1.2", - "yallist": "3.0.2" - } - }, "text-encoding": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", @@ -4729,17 +4466,6 @@ "punycode": "1.4.1" } }, - "tulind": { - "version": "0.8.10", - "resolved": "https://registry.npmjs.org/tulind/-/tulind-0.8.10.tgz", - "integrity": "sha1-/VB5TJxfGOCp870CJT6/JLu93DE=", - "requires": { - "bindings": "1.3.0", - "minctest": "0.0.2", - "nan": "2.10.0", - "node-pre-gyp": "0.9.1" - } - }, "tunnel-agent": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", @@ -4761,9 +4487,9 @@ } }, "type-detect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", - "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=" + "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", @@ -4858,14 +4584,6 @@ "yaeti": "0.0.6" } }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "requires": { - "string-width": "1.0.2" - } - }, "winston": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.2.tgz", @@ -4910,11 +4628,6 @@ "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" }, - "yallist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" - }, "zaif.jp": { "version": "0.1.16", "resolved": "https://registry.npmjs.org/zaif.jp/-/zaif.jp-0.1.16.tgz", diff --git a/package.json b/package.json index 6538d1bf5..7669cbcd8 100644 --- a/package.json +++ b/package.json @@ -58,10 +58,8 @@ "semver": "5.4.1", "sqlite3": "^3.1.13", "stats-lite": "^2.0.4", - "talib": "^1.0.4", "tiny-promisify": "^0.1.1", "toml": "^2.3.0", - "tulind": "^0.8.10", "twitter": "^1.7.1", "zaif.jp": "^0.1.4" },