From 916aab03d4ca3c8ece15c47fb2499e5c4d1af2af Mon Sep 17 00:00:00 2001 From: kuzetsa Date: Thu, 5 Dec 2013 15:43:18 -0500 Subject: [PATCH 01/80] buy GHS constantly until price peak, but still have the option for a panic sell so long as the price is increasing by "fast enough" (value for "fast enoguh" is still part of config.EMA) assume that the price IS NOT about to crash. since the price is still on the way up, it's best to keep buying GHS (this code is meant to be most optimal on cex.io) because there will be a mining income on any newly acquired GHS once the price starts to level off (up trend rate drops below buyTreshold) this new code will simply hold the GHS which will continue to mine The code for "sellTreshold" is unmodified, so it is up to the end-user to decide how severe of a market signal for "price crash" should be used to trigger the panic sell. --- methods/exponential-moving-averages.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/methods/exponential-moving-averages.js b/methods/exponential-moving-averages.js index fd137efd8..9bdd15d3d 100644 --- a/methods/exponential-moving-averages.js +++ b/methods/exponential-moving-averages.js @@ -156,11 +156,15 @@ TradingMethod.prototype.advice = function() { if(diff > settings.buyTreshold) { log.debug('we are currently in uptrend (' + diff + ')'); - if(this.currentTrend !== 'up') { - this.currentTrend = 'up'; - this.emit('advice', 'BUY', price, message); - } else - this.emit('advice', 'HOLD', price, message); +// *** buyTreshold is now honored unconditionally *** + this.emit('advice', 'BUY', price, message); + +// *** this commented out code only buys during start of an up trend *** +// if(this.currentTrend !== 'up') { +// this.currentTrend = 'up'; +// this.emit('advice', 'BUY', price, message); +// } else +// this.emit('advice', 'HOLD', price, message); } else if(diff < settings.sellTreshold) { log.debug('we are currently in a downtrend', message); From aded1571fc9a094a3fbc2621e3ad8b8dddf1585b Mon Sep 17 00:00:00 2001 From: kuzetsa Date: Mon, 9 Dec 2013 11:18:59 -0500 Subject: [PATCH 02/80] make default logging level more verbose (not silent) during trend calculations This logging change makes it easier to see the effects of the algorithm change from 72ab99f in action WITHOUT full debug log level. Debug log level is more verbose than most users will need, and my own experience is that it drove me crazy last time I had it turned on for more than a few hours. --- methods/exponential-moving-averages.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/methods/exponential-moving-averages.js b/methods/exponential-moving-averages.js index 9bdd15d3d..1cbaee9af 100644 --- a/methods/exponential-moving-averages.js +++ b/methods/exponential-moving-averages.js @@ -154,7 +154,7 @@ TradingMethod.prototype.advice = function() { message += '\tat \t' + moment.unix(this.currentTimestamp).format('YYYY-MM-DD HH:mm:ss'); if(diff > settings.buyTreshold) { - log.debug('we are currently in uptrend (' + diff + ')'); + log.info('we are currently in uptrend (' + diff + ')'); // *** buyTreshold is now honored unconditionally *** this.emit('advice', 'BUY', price, message); @@ -167,7 +167,7 @@ TradingMethod.prototype.advice = function() { // this.emit('advice', 'HOLD', price, message); } else if(diff < settings.sellTreshold) { - log.debug('we are currently in a downtrend', message); + log.info('we are currently in a downtrend', message); if(this.currentTrend !== 'down') { this.currentTrend = 'down'; @@ -176,7 +176,7 @@ TradingMethod.prototype.advice = function() { this.emit('advice', 'HOLD', price, message); } else { - log.debug('we are currently not in an up or down trend', message); + log.info('we are currently not in an up or down trend', message); this.emit('advice', 'HOLD', price, message); } } From 010c12e2bb5825970a97435c8dc233e728db8f81 Mon Sep 17 00:00:00 2001 From: Richard Date: Mon, 23 Jun 2014 23:41:42 -0700 Subject: [PATCH 03/80] Cryptsy integration --- config.js | 12 +- exchanges.js | 24 ++++ exchanges/cryptsy.js | 283 +++++++++++++++++++++++++++++++++++++++++++ gekko.js | 4 +- package.json | 3 +- 5 files changed, 317 insertions(+), 9 deletions(-) create mode 100644 exchanges/cryptsy.js diff --git a/config.js b/config.js index 1f53c50df..8c56b85a1 100644 --- a/config.js +++ b/config.js @@ -22,9 +22,9 @@ config.debug = true; // for additional logging / debugging // Monitor the live market config.watch = { enabled: true, - exchange: 'Bitstamp', // 'MtGox', 'BTCe', 'Bitstamp', 'cexio' or 'kraken' - currency: 'USD', - asset: 'BTC' + exchange: 'cryptsy', // 'MtGox', 'BTCe', 'Bitstamp', 'cexio', 'cryptsy' or 'kraken' + currency: 'BTC', + asset: 'LTC' } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -111,7 +111,7 @@ config.custom = { // Enabling this will activate trades for the market being // watched by config.watch config.trader = { - enabled: false, + enabled: true, key: '', secret: '', username: '' // your username, only fill in when using bitstamp or cexio @@ -135,7 +135,7 @@ config.profitSimulator = { // only want report after a sell? set to `false`. verbose: false, // how much fee in % does each trade cost? - fee: 0.6, + fee: 0.0025, // how much slippage should Gekko assume per trade? slippage: 0.05 } @@ -239,6 +239,6 @@ config.backtest = { // understand this. // // Not sure? Read this first: https://github.com/askmike/gekko/issues/201 -config['I understand that Gekko only automates MY OWN trading strategies'] = false; +config['I understand that Gekko only automates MY OWN trading strategies'] = true; module.exports = config; \ No newline at end of file diff --git a/exchanges.js b/exchanges.js index aceda7b69..9b0ac4c07 100644 --- a/exchanges.js +++ b/exchanges.js @@ -184,6 +184,30 @@ var exchanges = [ requires: ['key', 'secret', 'username'], providesHistory: false }, + { + name: 'Cryptsy', + slug: 'cryptsy', + direct: false, + infinityOrder: false, + currencies: ['BTC'], + assets: ['DOGE', 'DVC', 'PPC', 'LTC', 'DRK' ], + markets: [ + { + pair: ['BTC', 'DOGE'], market_id: 132, minimalOrder: { amount: 100, unit: 'asset' } + }, + { + pair: ['BTC', 'DVC'], market_id: 40, minimalOrder: { amount: 1, unit: 'asset' } + }, + { + pair: ['BTC', 'LTC'], market_id: 3, minimalOrder: { amount: 0.001, unit: 'asset' } + }, + { + pair: ['BTC', 'DRK'], market_id: 155, minimalOrder: { amount: 0.001, unit: 'asset' } + } + ], + requires: ['key', 'secret'], + providesHistory: false + }, { name: 'Kraken', slug: 'kraken', diff --git a/exchanges/cryptsy.js b/exchanges/cryptsy.js new file mode 100644 index 000000000..2cb3a021e --- /dev/null +++ b/exchanges/cryptsy.js @@ -0,0 +1,283 @@ +var cryptsy = require("cryptsy-api"); + moment = require('moment'), + async = require('async'), + _ = require('lodash'), + util = require('../core/util'), + log = require('../core/log'); + + +var Trader = function(config) { + this.key = config.key; + this.secret = config.secret; + this.currency = config.currency; + this.asset = config.asset; + this.pair = config.asset.toUpperCase() + config.currency.toUpperCase(); + + if( config.market_id ) + this.market_id = config.market_id; + + this.name = 'Cryptsy'; + + this.cryptsy = new cryptsy( + this.key, + this.secret + ); + + this.market = this.pair; + + _.bindAll(this); +} + + +Trader.prototype.return_trades = function(market, callback) { + + var m_id; + var main_trades; + var client = this.cryptsy; + + //log.debug('client is ', client); + client.getmarketid(market, function(market_id) { + //log.debug('id is', market_id); + // Display user's trades in that market + client.markettrades(market_id, function(trades) { + m_id = market_id; + //log.debug("Grabbing trades for id ", market_id); + if(trades.length) { + //log.debug("There are ", trades.length, 'trades'); + var full_array = []; + //trades = trades.reverse(); + trades.forEach( function(trade) { + // convert to int + trade.amount = Number(trade.quantity); + trade.price = Number(trade.tradeprice); + trade.tid = Number(trade.tradeid); + // ISSUE: this assumes that the local machine is in PDT + trade.date = moment(Date.parse(trade.datetime)).utc().unix(); + full_array.push(trade); + }); + + callback(null, full_array); + } + }); + }); + //this.market_id = m_id; +} + + +Trader.prototype.get_bid_ask = function(market, callback) { + + var m_id; + var main_trades; + var client = this.cryptsy; + + //log.debug('client is ', client); + client.getmarketid(market, function(market_id) { + //log.debug('id is', market_id); + // Display user's trades in that market + client.markettrades(market_id, function(trades) { + //log.debug("Grabbing trades for id ", market_id); + if(trades.length) { + var data_output = { }; + trades = trades.reverse(); + trades.forEach( function(trade) { + // convert to int + if(trade.initiate_ordertype.toLowerCase() == 'sell') { + //log.debug("Sell with initiate_ordertype", trade.initiate_ordertype, 'so using the price as the ask'); + data_output.bid = Number(trade.tradeprice); + } else { + //log.debug("Buy with initiate_ordertype", trade.initiate_ordertype, 'so using the price as the bid'); + data_output.ask = Number(trade.tradeprice); + } + data_output.datetime = trade.datetime; + }); + + callback(null, data_output); + } + }); + }); + //this.market_id = m_id; +} + +Trader.prototype.return_mkt_id = function(market, callback) { + + var client = this.cryptsy; + + //log.debug('client is ', client); + client.getmarketid(market, function(market_id) { + callback(null, market_id); + }); + //this.market_id = m_id; +} + + +Trader.prototype.getTrades = function(since, callback, descending) { + var args = _.toArray(arguments); + var mkt_id = this.market; + + var process = function(err, trades) { + //log.debug("Err is ", err, 'and length of trades is', trades); + if(err || !trades || trades.length === 0) + return this.retry(this.getTrades, args, err); + + var f = parseFloat; + + if(descending) + callback(null, trades); + else + callback(null, trades.reverse()); + }; + + this.return_trades(mkt_id, _.bind(process, this)); + +} + + + +Trader.prototype.buy = function(amount, price, callback) { + + var mkt_name = this.market; + // [MM]: Something about cryptsy's orders seems to be behind the actual market, which causes orders to go unfilled. + // Make the amount slightly on the upside of the actual price. + price = price * 1.003; + + log.debug('BUY', amount, this.asset, ' @', price, this.currency); + this.place_order(mkt_name, 'buy', amount, price, _.bind(callback, this)); +} + + +Trader.prototype.sell = function(amount, price, callback) { + + var mkt_name = this.market; + // [MM]: Something about cryptsy's orders seems to be behind the actual market, which causes orders to go unfilled. + // Make the amount slightly on the downside of the actual price. + price = price * 0.997; + + log.debug('SELL', amount, this.asset, ' @', price, this.currency); + this.place_order(mkt_name, 'sell', amount, price, _.bind(callback, this)); +} + + +Trader.prototype.place_order = function(market_name, trans_type, amount, price, callback) { + + var client = this.cryptsy; + + //log.debug(trans_type, 'order placed for ', amount, this.asset, ' @', price, this.currency); + + //log.debug('client is ', client); + client.getmarketid(market_name, function(market_id) { + //log.debug('id is', market_id); + client.createorder(market_id, trans_type, amount, price, function(orderid) { + callback(null, orderid); + + }); + }); +} + + +Trader.prototype.retry = function(method, args, err) { + var wait = +moment.duration(10, 'seconds'); + log.debug(this.name, 'returned an error in method', method.name, ', retrying..', err, 'waiting for', wait, 'ms'); + + if (!_.isFunction(method)) { + log.error(this.name, 'failed to retry, no method supplied.'); + return; + } + + var self = this; + + // make sure the callback (and any other fn) + // is bound to Trader + _.each(args, function(arg, i) { + if(_.isFunction(arg)) + args[i] = _.bind(arg, self); + }); + + // run the failed method again with the same + // arguments after wait + setTimeout( + function() { method.apply(self, args) }, + wait + ); +} + +Trader.prototype.getPortfolio = function(callback) { + var args = _.toArray(arguments); + var curr_balance, asst_balance; + var curr = this.currency; + var asst = this.asset; + + var calculate = function(data) { + if(!data) + return this.retry(this.getPortfolio, args, null); + balances = data.balances_available; + holds = data.balances_hold; + + curr_balance = parseFloat(balances[curr]) + asst_balance = parseFloat(balances[asst]); +/* + if(holds) { + if(parseFloat(holds[curr])){ + curr_balance -= parseFloat(holds[curr]) + } + + if( parseFloat(holds[asst])){ + asst_balance -= parseFloat(holds[asst]); + } + } +*/ + var portfolio = []; + portfolio.push({name: curr, amount: curr_balance}); + portfolio.push({name: asst, amount: asst_balance}); + callback(null, portfolio); + } + + this.cryptsy.getinfo(_.bind(calculate, this)); +} + +Trader.prototype.getTicker = function(callback) { + + var mkt_name = this.market; + var set = function(err, data) { + log.debug('Timestamp is', data.datetime, 'with bid ', data.bid, 'and ask ', data.ask); + var ticker = { + ask: data.ask, + bid: data.bid + }; + callback(err, ticker); + } + this.get_bid_ask(mkt_name, _.bind(set, this)); +} + +Trader.prototype.getFee = function(callback) { + callback(false, 0.0025); +} + +Trader.prototype.checkOrder = function(order, callback) { + var check = function(err, result) { + + if(err) + callback(false, true); + + var exists = false; + _.forEach(result, function(entry) { + if(entry.orderid === order) { + exists = true; return; + } + }); + callback(err, !exists); + }; + + this.cryptsy.allmyorders(_.bind(check, this)); +} + +Trader.prototype.cancelOrder = function(order) { + var check= function(err, result) { + if(err) + log.error('cancel order failed:', err); + if(typeof(result) !== 'undefined' && result.error) + log.error('cancel order failed:', result.error); + } + this.cryptsy.cancelorder(order, check); +} + +module.exports = Trader; diff --git a/gekko.js b/gekko.js index 8fa5b5f80..d32faf4eb 100644 --- a/gekko.js +++ b/gekko.js @@ -34,10 +34,10 @@ if(util.getArgument('v')) { // make sure the current node version is recent enough if(!util.recentNode()) util.die([ - 'Your local version of nodejs is to old. ', + 'Your local version of nodejs is too old. ', 'You have ', process.version, - ' and you need atleast ', + ' and you need at least ', util.getRequiredNodeVersion() ].join('')); diff --git a/package.json b/package.json index ba0b56f60..5cdc0632c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ }, "author": "Mike van Rossum ", "dependencies": { - "mtgox-apiv2": "1.1.x", + "cryptsy-api": "0.1.x", + "mtgox-apiv2": "1.0.x", "lodash": "2.x", "moment": "2.4.x", "btc-e": "0.0.x", From acf11d97fb544c4956940e4b2bd724a3936f4cad Mon Sep 17 00:00:00 2001 From: Richard Date: Mon, 23 Jun 2014 23:47:57 -0700 Subject: [PATCH 04/80] config - add missing keys --- config.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config.js b/config.js index 8c56b85a1..91efef54f 100644 --- a/config.js +++ b/config.js @@ -23,6 +23,8 @@ config.debug = true; // for additional logging / debugging config.watch = { enabled: true, exchange: 'cryptsy', // 'MtGox', 'BTCe', 'Bitstamp', 'cexio', 'cryptsy' or 'kraken' + key: '', + secret: '', currency: 'BTC', asset: 'LTC' } @@ -111,7 +113,7 @@ config.custom = { // Enabling this will activate trades for the market being // watched by config.watch config.trader = { - enabled: true, + enabled: false, key: '', secret: '', username: '' // your username, only fill in when using bitstamp or cexio From e08999f2abb3df6ef4f63aca6614549432573d7a Mon Sep 17 00:00:00 2001 From: Richard Date: Tue, 24 Jun 2014 00:31:40 -0700 Subject: [PATCH 05/80] bug fixes - works great for cryptsy now! --- config.js | 1 + core/portfolioManager.js | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/config.js b/config.js index 91efef54f..8286d9775 100644 --- a/config.js +++ b/config.js @@ -114,6 +114,7 @@ config.custom = { // watched by config.watch config.trader = { enabled: false, + tradePercent: 10, key: '', secret: '', username: '' // your username, only fill in when using bitstamp or cexio diff --git a/core/portfolioManager.js b/core/portfolioManager.js index b34aac57f..ba386cdc5 100644 --- a/core/portfolioManager.js +++ b/core/portfolioManager.js @@ -42,6 +42,7 @@ var Manager = function(conf) { }); this.minimalOrder = this.marketConfig.minimalOrder; + this.tradePercent = conf.tradePercent; this.currency = conf.currency; this.asset = conf.asset; } @@ -124,7 +125,9 @@ Manager.prototype.trade = function(what) { this.action = what; var act = function() { - var amount, price; + var amount, price, total_balance; + + total_balance = this.getBalance(this.currency) + this.getBalance(this.asset) * this.ticker.bid; if(what === 'BUY') { @@ -140,6 +143,11 @@ Manager.prototype.trade = function(what) { else price = this.ticker.ask; + if(this.tradePercent) { + log.debug('Trade Percent: adjusting amount', amount, 'by ', this.tradePercent, '%'); + amount = amount * this.tradePercent / 100; + } + this.buy(amount, price); } else if(what === 'SELL') { @@ -155,6 +163,11 @@ Manager.prototype.trade = function(what) { price = false; else price = this.ticker.bid; + + if(this.tradePercent) { + log.debug('Trade Percent: adjusting amount', amount, 'by ', this.tradePercent, '%'); + amount = amount * this.tradePercent / 100; + } this.sell(amount, price); } @@ -197,7 +210,7 @@ Manager.prototype.buy = function(amount, price) { ); } - // if order to small + // if order too small if(amount < minimum) { return log.info( 'Wanted to buy', @@ -232,7 +245,7 @@ Manager.prototype.sell = function(amount, price) { var availabe = this.getBalance(this.asset); // if not suficient funds - if(amount < availabe) { + if(amount > availabe) { return log.info( 'Wanted to buy but insufficient', this.asset, @@ -242,7 +255,7 @@ Manager.prototype.sell = function(amount, price) { ); } - // if order to small + // if order too small if(amount < minimum) { return log.info( 'Wanted to buy', From cd89180b3e65a0c2cd8f7ccfcc015de37bf9e2c2 Mon Sep 17 00:00:00 2001 From: dowjames Date: Tue, 24 Jun 2014 09:14:14 -0700 Subject: [PATCH 06/80] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8d632c35f..7450d9c74 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Gekko works on the following exchanges: - CEX.io - Kraken - BTC-e -- ~~Cryptsy~~ (In the [pipeline](https://github.com/askmike/gekko/pull/200)) +- Cryptsy ## Installing Gekko @@ -150,4 +150,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +THE SOFTWARE. From 9eb4c7f66af95ded61c3c81d47d188ff4c782e10 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Wed, 25 Jun 2014 09:35:55 -0400 Subject: [PATCH 07/80] Rewrite of the README markdown file. No longer references the original pre-fork repository, changed wording on a few parts to indicate the purpose of the fork, and updated attribution under license & credits. --- README.md | 104 +++++++++++++++++++----------------------------------- 1 file changed, 36 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 8d632c35f..5ec3f0330 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,30 @@ # Important note -You are looking at the brand new and completetly different version of Gekko. We've tested it for quite a while though it might be possible that you encounter bugs. If you encounter a bug: check out in [the issues](https://github.com/askmike/gekko/issues/) if we are aware of it and if not create a new one :) +You are looking at the brand new and completetly different version of Gekko. We've tested it for quite a while though it might be possible that you encounter bugs. If you encounter a bug: check out in [the issues](https://github.com/kuzetsa/gekko/issues/) if we are aware of it and if not create a new one :) -# Gekko [![Build Status](https://travis-ci.org/askmike/gekko.png)](https://travis-ci.org/askmike/gekko) - -![Gordon Gekko](http://mikevanrossum.nl/static/gekko.jpg) +# Gekko *The most valuable commodity I know of is information.* -Gordon Gekko -Gekko is a Bitcoin trading bot and backtesting platform that connects to popular Bitcoin exchanges. It is written in javascript and runs on [nodejs](http://nodejs.org). +Gekko is a cloud mining reinvestment platform which based on [REST APIs](http://en.wikipedia.org/wiki/Representational_state_transfer) +It is written in javascript and runs on [nodejs](http://nodejs.org). This is the open source do-it-yourself version, we are planning on running hosted Gekkos in the cloud which does not require you to download and install anything, configure any textfiles or deal with the commandline. If you are looking for such a solution, sign up at [Wizbit](http://wizb.it) and we'll let you know once it's out! *Use Gekko at you own risk.* -## Main features - -* Trading platform: - * Paper trading - * Live trading (trade bot) - * ~~Backtester~~ -* Market interface: - * Emit market events - * Basic IRC Bot - -## Automated Trading platform - -Gekko can watch the realtime markets. You can apply automated trading methods to realtime data coming in to do live or simulated trading (automated trading or paper trading). Gekko also stores the market data it sees so you can run the trading methods with simulate trades on a set of historical data to see whether they would have been profitable during that time (backtesting). - -Gekko, as well as the current bitcoin exchanges, are not built for HFT or anything related to being the fastest. The trading methods Gekko can do are based on indicators used by human day traders. The result is that Gekko does not look at data below the one minute timescale and (depending on configuration) and will normally not trade more than a couple of times per week (also depending on configuration). - -**So Gekko is not:** - -- A trading platform for human day traders with a GUI and charts. -- A High frequency trading bot designed to operate on < minute resolution. -- A fully automated trading bot that you turn on and will generate profit without you having to do anything. -- An exchange. +**Automated reinvestment** -**Automated trading** +Gekko comes with a few [technical analysis (TA)](http://en.wikipedia.org/wiki/Technical_analysis) methods which implement a single indicator (DEMA, MACD, RSI and PPO). The parameters of these indicators are all configurable and changing them changes the outcome drastically. Additionally Gekko also provides an easy way to write your own trading methods in javascript. Read more about that in the [documentation](https://github.com/kuzetsa/gekko/blob/master/docs/internals/trading_methods.md). -Gekko comes with a couple of trading methods that implement a single indicator (DEMA, MACD, RSI and PPO). The parameters of these indicators are all configurable and changing them changes the outcome drastically. Additionally Gekko also provides an easy way to write your own trading methods in javascript. Read more about that in the [documentation](https://github.com/askmike/gekko/blob/master/docs/internals/trading_methods.md). +**Note:** *much of this information is outdated, and/or not relevant to +reinvestment-only mode.* ## Market interface -Gekko also has a plugin system that can do certain things whenever something happens or let Gekko communicate through more platforms. Gekko currently knows these plugins: +Gekko also has a plugin system that can do certain things whenever something happens or let Gekko communicate through more platforms. Gekko implements the following example plugins: - Campfire: Enables Gekko to talk on [Campfire](https://campfirenow.com/) and report latest market data and advice. - IRC bot: Enables Gekko to talk on IRC and report latest market data and advice. @@ -55,84 +34,73 @@ Gekko also has a plugin system that can do certain things whenever something hap ## Supported exchanges -Gekko works on the following exchanges: +Gekko reinvests cloud mining income on the following platforms: -- Mt. Gox -- Bitstamp - CEX.io -- Kraken -- BTC-e -- ~~Cryptsy~~ (In the [pipeline](https://github.com/askmike/gekko/pull/200)) ## Installing Gekko -Windows user? Here is a [step-by-step guide](https://github.com/askmike/gekko/blob/master/docs/installing_gekko_on_windows.md) on how to get Gekko running on Windows. +Windows user? Here is a [step-by-step guide](https://github.com/kuzetsa/gekko/blob/master/docs/installing_gekko_on_windows.md) on how to get Gekko running on Windows. -Gekko runs on [nodejs](http://nodejs.org/), once you have that installed you can either download all files in [a zip](https://github.com/askmike/gekko/archive/master.zip) or clone the repository via git: +Gekko runs on [nodejs](http://nodejs.org/), once you have that installed you can either download all files in [a zip](https://github.com/kuzetsa/gekko/archive/master.zip) or clone the repository via git: - git clone git://github.com/askmike/gekko.git +``` + git clone git://github.com/kuzetsa/gekko.git cd gekko +``` You need to download Gekko's dependencies, which can easily be done with [npm](http://npmjs.org): +``` npm install +``` -## Configuring Gekko +## Configuring Gekko / before you start -> Configuring Gekko consists of three parts: -> -> - Watching a realtime market -> - Automate trading advice -> - Enabling plugins - -Read the [configuring Gekko documentation](https://github.com/askmike/gekko/tree/master/docs/Configuring_gekko.md) for a detailed explanation. +Read the [configuring Gekko documentation](https://github.com/kuzetsa/gekko/tree/master/docs/Configuring_gekko.md) for a detailed explanation. ## Running Gekko To run the bot you just have to start Gekko: +``` node gekko +``` -You can also run Gekko silently or use more complex features, for examples check out the [advanced features](https://github.com/askmike/gekko/tree/master/docs/Advanced_features.md). +You can also run Gekko silently or use more complex features, for examples check out the [advanced features](https://github.com/kuzetsa/gekko/tree/master/docs/Advanced_features.md). ## Updating Gekko If you installed the bot via git you can easily fetch the latest updates by running: - git pull && npm update +``` + git pull + npm update +``` ## How does Gekko work? -![Gekko 0.1.0 architecture](http://data.wizb.it/misc/gekko-0.1.0-architecture.jpg) - If you want to contribute or are interested in how Gekko works: -- Read about [Gekko's overall architecture](https://github.com/askmike/gekko/tree/master/docs/internals/architecture.md). -- Read on how to add [a new exchange to Gekko](https://github.com/askmike/gekko/tree/master/docs/internals/exchanges.md). -- Read on how to [create your own plugin](https://github.com/askmike/gekko/tree/master/docs/internals/plugins.md). -- Implement [your own trading method](https://github.com/askmike/gekko/blob/master/docs/internals/trading_methods.md) and share it back. - -## TODO - -* Backtester -* More exchanges (bitfinex, btcchina) -* More indicators -* Webbased interface +- Read about [Gekko's overall architecture](https://github.com/kuzetsa/gekko/tree/master/docs/internals/architecture.md). +- Read on how to add [a new exchange to Gekko](https://github.com/kuzetsa/gekko/tree/master/docs/internals/exchanges.md). +- Read on how to [create your own plugin](https://github.com/kuzetsa/gekko/tree/master/docs/internals/plugins.md). +- Implement [your own trading method](https://github.com/kuzetsa/gekko/blob/master/docs/internals/trading_methods.md) +and share it back. ## Credits * The title is inspired by [Bateman](https://github.com/fearofcode/bateman). * This project is inspired by the [GoxTradingBot](https://github.com/virtimus/GoxTradingBot/) Chrome plugin (though no code is taken from it). - -## Final - -If Gekko helped you in any way, you can always leave me a tip at (BTC) 13r1jyivitShUiv9FJvjLH7Nh1ZZptumwW +* Original implementation of gekko was written by [Mike van Rossum](https://github.com/askmike/gekko) +* After 2014, June 25th, reinvestment-only fork maintained by [Sarah White AKA kuzetsa](https://github.com/kuzetsa/gekko) ## License The MIT License (MIT) -Copyright (c) 2014 Mike van Rossum +Copyright (c) 2014 Mike van Rossum (original author, initial code in gekkobot) +Copyright (c) 2013-2014 Sarah White (*AKA **kuzetsa**, forked 2014 June 25th*) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -150,4 +118,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +THE SOFTWARE. From 50d88027af19bd42f50ace7c40849a991e2be988 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Wed, 25 Jun 2014 10:08:15 -0400 Subject: [PATCH 08/80] I think a gecko is a type of lizard, right? Either way, this is the LIZARD UPDATE (initial bugfixes and changed some behavior) 1) Added several lines of code related to bugs with inconsistent formatting / display / logging of information, and made the verbose "debug" logging less... just less (much of information is now in the default level without needing to enabled debug logging)... Also changed the grammar on various things to prevent linewrapping from too much words, or simple cleanups to make 8 decimal places be usd correctly (in addition to fixing a few rounding errors) 2) Hardcoded a trading fee for cex.io platform, as it now has one (this value doesn't seem to have a way to fetch via API, so I guess it can be configurable in the future. The config parser needs an audit though, so the workaround was to simply hardcode a non-zero fee) 3) Included the "launcher" script I personally use, as the provided one doesn't restart itself automatically. 4) Removed the "sell when price is high" logic. This fork of gekkobot is for reinvestment of the income from cloud mining. Period. Full stop. --- launcher | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100755 launcher diff --git a/launcher b/launcher new file mode 100755 index 000000000..d6aa0e0d7 --- /dev/null +++ b/launcher @@ -0,0 +1,16 @@ +#!/bin/bash +# kuzetsa's gekko [re]launcher + +while true; do + echo [`date +%Y.%m.%d/@%H%M.%S/%Z`] attempting to \(re\)start gekko... + sleep 5 + echo [`date +%Y.%m.%d/@%H%M.%S/%Z`] launching node.js gekko bot NOW + echo -e '\n' + node gekko + echo -e '\n' + echo [`date +%Y.%m.%d/@%H%M.%S/%Z`] gekko seems to have stopped, waiting 30 seconds before restarting + sleep 5 + echo [`date +%Y.%m.%d/@%H%M.%S/%Z`] Press Ctrl + C now if you REALLY want to stop gekko + sleep 20 + echo loop starts 5 delay +done From 514cc1ec84f9750d3683f900903f9ba0c2c10249 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Wed, 25 Jun 2014 10:35:29 -0400 Subject: [PATCH 09/80] Accidentally only included the launcher on previous commit (oops) 1) Added several lines of code related to bugs with inconsistent formatting / display / logging of information, and made the verbose "debug" logging less... just less (much of information is now in the default level without needing to enabled debug logging)... Also changed the grammar on various things to prevent linewrapping from too much words, or simple cleanups to make 8 decimal places be usd correctly (in addition to fixing a few rounding errors) 2) Hardcoded a trading fee for cex.io platform, as it now has one (this value doesn't seem to have a way to fetch via API, so I guess it can be configurable in the future. The config parser needs an audit though, so the workaround was to simply hardcode a non-zero fee) 3) Removed the "sell when price is high" logic. This fork of gekkobot is for reinvestment of the income from cloud mining. Period. Full stop. --- config.js | 57 ++++++++++++++++++----------------- core/baseTradingMethod.js | 7 +++-- core/candleManager.js | 2 +- core/portfolioManager.js | 62 +++++++++++++++++++++++++++----------- exchanges.js | 4 ++- exchanges/cexio.js | 2 +- methods/MACD.js | 63 +++++++-------------------------------- methods/indicators/EMA.js | 2 +- plugins/adviceLogger.js | 14 ++++----- plugins/trader.js | 4 +-- 10 files changed, 103 insertions(+), 114 deletions(-) diff --git a/config.js b/config.js index 1f53c50df..f2ec8c7de 100644 --- a/config.js +++ b/config.js @@ -1,5 +1,5 @@ -// Everything is explained here: -// https://github.com/askmike/gekko/blob/master/docs/Configuring_gekko.md +// Everything is explained here: +// https://github.com/kuzetsa/gekko/blob/master/docs/Configuring_gekko.md var config = {}; @@ -13,7 +13,7 @@ config.history = { // and load historical data from? directory: './history/' } -config.debug = true; // for additional logging / debugging +config.debug = false; // for additional logging / debugging // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // WATCHING A MARKET @@ -22,9 +22,9 @@ config.debug = true; // for additional logging / debugging // Monitor the live market config.watch = { enabled: true, - exchange: 'Bitstamp', // 'MtGox', 'BTCe', 'Bitstamp', 'cexio' or 'kraken' - currency: 'USD', - asset: 'BTC' + exchange: 'cexio', // 'MtGox', 'BTCe', 'Bitstamp', 'cexio' or 'kraken' + currency: 'BTC', + asset: 'GHS' } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -33,9 +33,9 @@ config.watch = { config.tradingAdvisor = { enabled: true, - method: 'DEMA', - candleSize: 60, - historySize: 50 + method: 'MACD', + candleSize: 1, + historySize: 113 } // Exponential Moving Averages settings: @@ -56,13 +56,13 @@ config.DEMA = { config.MACD = { // EMA weight (α) // the higher the weight, the more smooth (and delayed) the line - short: 10, - long: 21, - signal: 9, + short: 53, + long: 109, + signal: 41, // the difference between the EMAs (to act as triggers) thresholds: { - down: -0.025, - up: 0.025, + down: -9999, + up: 0.00000001, // How many candle intervals should a trend persist // before we consider it real? persistence: 1 @@ -78,8 +78,8 @@ config.PPO = { signal: 9, // the difference between the EMAs (to act as triggers) thresholds: { - down: -0.025, - up: 0.025, + down: -9999, + up: 0.00000001, // How many candle intervals should a trend persist // before we consider it real? persistence: 2 @@ -112,9 +112,9 @@ config.custom = { // watched by config.watch config.trader = { enabled: false, - key: '', - secret: '', - username: '' // your username, only fill in when using bitstamp or cexio + key: 'see next line', + secret: 'some sort of hash goes here', + username: 'fakename change this', // your username, as required by cexio } config.adviceLogger = { @@ -123,7 +123,7 @@ config.adviceLogger = { // do you want Gekko to calculate the profit of its own advice? config.profitSimulator = { - enabled: true, + enabled: false, // report the profit in the currency or the asset? reportInCurrency: true, // start balance, on what the current balance is compared with @@ -133,11 +133,11 @@ config.profitSimulator = { currency: 100, }, // only want report after a sell? set to `false`. - verbose: false, + verbose: true, // how much fee in % does each trade cost? - fee: 0.6, + fee: 1, // how much slippage should Gekko assume per trade? - slippage: 0.05 + slippage: 0.5 } // want Gekko to send a mail on buy or sell advice? @@ -150,7 +150,7 @@ config.mailer = { // You don't have to set your password here, if you leave it blank we will ask it // when Gekko's starts. // - // NOTE: Gekko is an open source project < https://github.com/askmike/gekko >, + // NOTE: Gekko is an open source project < https://github.com/kuzetsa/gekko >, // make sure you looked at the code or trust the maintainer of this bot when you // fill in your email and password. // @@ -209,7 +209,8 @@ config.redisBeacon = { ] } -// not in a working state +// the web interface not currently supported, maintainer of the plugin quit +// (not in a working state) // read: https://github.com/askmike/gekko/issues/156 config.webserver = { enabled: false, @@ -238,7 +239,7 @@ config.backtest = { // it doesn't advice on itself, only set to true if you truly // understand this. // -// Not sure? Read this first: https://github.com/askmike/gekko/issues/201 -config['I understand that Gekko only automates MY OWN trading strategies'] = false; +config['I understand that Gekko only automates MY OWN trading +strategies'] = false; -module.exports = config; \ No newline at end of file +module.exports = config; diff --git a/core/baseTradingMethod.js b/core/baseTradingMethod.js index b8a0cbe96..015cfcd71 100644 --- a/core/baseTradingMethod.js +++ b/core/baseTradingMethod.js @@ -58,8 +58,9 @@ var Base = function() { else log.warn('\t', 'Warning, trading method has no name'); - if(!config.debug || !this.log) - this.log = function() {}; +// if(!config.debug || !this.log) +// DO NOT nuke the log for MACD logging, let line-by-line tweaks happen +// this.log = function() {}; this.setup = true; } @@ -115,4 +116,4 @@ Base.prototype.advice = function(newPosition) { }); } -module.exports = Base; \ No newline at end of file +module.exports = Base; diff --git a/core/candleManager.js b/core/candleManager.js index 1687fb96a..043aad470 100644 --- a/core/candleManager.js +++ b/core/candleManager.js @@ -591,7 +591,7 @@ Manager.prototype.processTrades = function(data) { return this.emit('processed'); } - log.debug('processing', _.size(trades), 'trade(s)'); + log.info('processing', _.size(trades), 'trade(s)'); log.debug( 'from', moment.unix(_.first(trades).date).utc().format(), diff --git a/core/portfolioManager.js b/core/portfolioManager.js index b34aac57f..dd51630a0 100644 --- a/core/portfolioManager.js +++ b/core/portfolioManager.js @@ -28,6 +28,8 @@ var Manager = function(conf) { var Exchange = require('../exchanges/' + this.exchangeSlug); this.exchange = new Exchange(conf); + this.limiter = (new Date()).getTime(); + this.conf = conf; this.portfolio = {}; this.fee; @@ -121,6 +123,16 @@ Manager.prototype.trade = function(what) { if(what !== 'BUY' && what !== 'SELL') return; + +// is it time yet? +var rightnow = (new Date()).getTime(); + if(rightnow <= this.limiter) + return; + +// ok, so reset the limiter +this.limiter = (new Date()).getTime() + 9000; + + this.action = what; var act = function() { @@ -131,8 +143,13 @@ Manager.prototype.trade = function(what) { // do we need to specify the amount we want to buy? if(this.infinityOrderExchange) amount = 10000; - else - amount = this.getBalance(this.currency) / this.ticker.ask; + else { + amount = 1 - this.fee; + amount = (100000000 * amount * this.getBalance(this.currency)) - 2; + amount = Math.floor(Math.floor(amount) / this.ticker.ask) - 1; + amount = Math.max(0, amount); + amount /= 100000000; + } // can we just create a MKT order? if(this.directExchange) @@ -142,6 +159,7 @@ Manager.prototype.trade = function(what) { this.buy(amount, price); + } else if(what === 'SELL') { // do we need to specify the amount we want to sell? @@ -155,8 +173,10 @@ Manager.prototype.trade = function(what) { price = false; else price = this.ticker.bid; - - this.sell(amount, price); + + // LIZARD!!! + // this.sell(amount, price); + } }; async.series([ @@ -179,7 +199,7 @@ Manager.prototype.getMinimum = function(price) { Manager.prototype.buy = function(amount, price) { // sometimes cex.io specifies a price w/ > 8 decimals price *= 100000000; - price = Math.floor(price); + price = Math.ceil(price); price /= 100000000; var currency = this.getFund(this.currency); @@ -189,10 +209,10 @@ Manager.prototype.buy = function(amount, price) { // if not suficient funds if(amount > availabe) { return log.info( - 'Wanted to buy but insufficient', + 'Insufficient', this.currency, - '(' + availabe + ')', - 'at', + '(' + availabe.toFixed(8) + ')', + 'to BUY at', this.exchange.name ); } @@ -200,18 +220,18 @@ Manager.prototype.buy = function(amount, price) { // if order to small if(amount < minimum) { return log.info( - 'Wanted to buy', - this.asset, - 'but the amount is too small', - '(' + amount + ')', + 'Ignore tiny', + 'BUY order', + '(' + amount.toFixed(8) + ' ' + this.asset + ')', + 'at', this.exchange.name ); } log.info( - 'Attempting too BUY', - amount, + 'Placing BUY order for', + amount.toFixed(8), this.asset, 'at', this.exchange.name @@ -225,7 +245,7 @@ Manager.prototype.buy = function(amount, price) { Manager.prototype.sell = function(amount, price) { // sometimes cex.io specifies a price w/ > 8 decimals price *= 100000000; - price = Math.ceil(price); + price = Math.floor(price); price /= 100000000; var minimum = this.getMinimum(price); @@ -236,7 +256,7 @@ Manager.prototype.sell = function(amount, price) { return log.info( 'Wanted to buy but insufficient', this.asset, - '(' + availabe + ')', + '(' + availabe.toFixed(8) + ')', 'at', this.exchange.name ); @@ -248,7 +268,7 @@ Manager.prototype.sell = function(amount, price) { 'Wanted to buy', this.currency, 'but the amount is too small', - '(' + amount + ')', + '(' + amount.toFixed(8) + ')', 'at', this.exchange.name ); @@ -296,7 +316,7 @@ Manager.prototype.checkOrder = function() { Manager.prototype.logPortfolio = function() { log.info(this.exchange.name, 'portfolio:'); _.each(this.portfolio, function(fund) { - log.info('\t', fund.name + ':', fund.amount); + log.info('\t', fund.name + ':', fund.amount.toFixed(8)); }); } @@ -312,6 +332,12 @@ Manager.prototype.recheckPortfolio = function() { // the asset (GHS) as we are assuming the value // of the asset will go up. Manager.prototype.enforcePosition = function() { + +log.info('refreshed', this.exchange.name, 'portfolio:'); +_.each(this.portfolio, function(fund) { + log.info('\t', fund.name + ':', fund.amount.toFixed(8)); +}); + if(this.action !== 'BUY') return; diff --git a/exchanges.js b/exchanges.js index aceda7b69..310607520 100644 --- a/exchanges.js +++ b/exchanges.js @@ -178,7 +178,9 @@ var exchanges = [ assets: ['GHS'], markets: [ { - pair: ['BTC', 'GHS'], minimalOrder: { amount: 0.000001, unit: 'currency' } + pair: ['BTC', 'GHS'], minimalOrder: { amount: 0.00161803, +unit: +'asset' } } ], requires: ['key', 'secret', 'username'], diff --git a/exchanges/cexio.js b/exchanges/cexio.js index e0ae470ab..29d961ecd 100644 --- a/exchanges/cexio.js +++ b/exchanges/cexio.js @@ -149,7 +149,7 @@ Trader.prototype.getTicker = function(callback) { Trader.prototype.getFee = function(callback) { // cexio does currently don't take a fee on trades // TODO: isn't there an API call for this? - callback(false, 0.0); + callback(false, 0.0075); } Trader.prototype.checkOrder = function(order, callback) { diff --git a/methods/MACD.js b/methods/MACD.js index 26168efba..01fbaeffe 100644 --- a/methods/MACD.js +++ b/methods/MACD.js @@ -4,6 +4,8 @@ (updated a couple of times since, check git history) + Modified by kuzetsa 2014 June 22 (CEXIO lizards variant) + */ // helpers @@ -57,11 +59,11 @@ method.log = function() { var signal = macd.signal.result; log.debug('calculated MACD properties for candle:'); - log.debug('\t', 'short:', macd.short.result.toFixed(digits)); - log.debug('\t', 'long:', macd.long.result.toFixed(digits)); - log.debug('\t', 'macd:', diff.toFixed(digits)); - log.debug('\t', 'signal:', signal.toFixed(digits)); - log.debug('\t', 'macdiff:', macd.result.toFixed(digits)); + log.info('\t', 'short:', macd.short.result.toFixed(digits)); + log.info('\t', 'long:', macd.long.result.toFixed(digits)); + log.info('\t', 'macd:', diff.toFixed(digits)); + log.info('\t', 'signal:', signal.toFixed(digits)); + log.info('\t', 'macdiff:', macd.result.toFixed(digits)); } method.check = function() { @@ -80,68 +82,25 @@ method.check = function() { // reset the state for the new trend this.trend = { duration: 0, - persisted: false, direction: 'up', - adviced: false }; this.trend.duration++; - log.debug('In uptrend since', this.trend.duration, 'candle(s)'); - - if(this.trend.duration >= settings.thresholds.persistence) - this.trend.persisted = true; + log.info('In uptrend since', this.trend.duration, 'candle(s)'); - if(this.trend.persisted && !this.trend.adviced) { - this.trend.adviced = true; this.advice('long'); - } else - this.advice(); - } else if(macddiff < settings.thresholds.down) { + } else { - // new trend detected - if(this.trend.direction !== 'down') - // reset the state for the new trend this.trend = { duration: 0, - persisted: false, - direction: 'down', - adviced: false + direction: 'lizards', }; - this.trend.duration++; - - log.debug('In downtrend since', this.trend.duration, 'candle(s)'); - - if(this.trend.duration >= settings.thresholds.persistence) - this.trend.persisted = true; - - if(this.trend.persisted && !this.trend.adviced) { - this.trend.adviced = true; - this.advice('short'); - } else - this.advice(); - - } else { - - log.debug('In no trend'); - - // we're not in an up nor in a downtrend - // but for now we ignore sideways trends - // - // read more @link: - // - // https://github.com/askmike/gekko/issues/171 - // this.trend = { - // direction: 'none', - // duration: 0, - // persisted: false, - // adviced: false - // }; + this.advice('lizards'); - this.advice(); } } diff --git a/methods/indicators/EMA.js b/methods/indicators/EMA.js index 1d3d50a0b..c6c9dbd97 100644 --- a/methods/indicators/EMA.js +++ b/methods/indicators/EMA.js @@ -1,4 +1,4 @@ -// @link http://en.wikipedia.org/wiki/Exponential_moving_average#Exponential_moving_average +// @link http://en.wikipedia.org/wiki/Exponential_moving_average#Exponential_moving_average var Indicator = function(weight) { this.weight = weight; diff --git a/plugins/adviceLogger.js b/plugins/adviceLogger.js index 29bb2f9f4..52cdfbd85 100644 --- a/plugins/adviceLogger.js +++ b/plugins/adviceLogger.js @@ -14,12 +14,12 @@ Actor.prototype.processTrade = function(trade) { }; Actor.prototype.processAdvice = function(advice) { - console.log() - log.info('We have new trading advice!'); - log.info('\t Position to take:', advice.recommandation); - log.info('\t Market price:', this.price); - log.info('\t Based on market time:', this.marketTime.format('YYYY-MM-DD HH:mm:ss')); - console.log() + // console.log() + log.debug('We have new trading advice!'); + log.debug('\t Position to take:', advice.recommandation); + log.info('\t Market price:', Number(this.price).toFixed(8)); + log.info('\t At timestamp:', this.marketTime.format('YYYY-MM-DD HH:mm:ss')); + // console.log() }; -module.exports = Actor; \ No newline at end of file +module.exports = Actor; diff --git a/plugins/trader.js b/plugins/trader.js index 9d9f35e84..4450fa65d 100644 --- a/plugins/trader.js +++ b/plugins/trader.js @@ -13,14 +13,14 @@ var Trader = function(next) { Trader.prototype.processAdvice = function(advice) { if(advice.recommandation == 'long') { this.manager.trade('BUY'); - log.info( + log.debug( 'Trader', 'Received advice to go long', 'Buying ', config.trader.asset ); } else if(advice.recommandation == 'short') { this.manager.trade('SELL'); - log.info( + log.debug( 'Trader', 'Received advice to go short', 'Selling ', config.trader.asset From 5cdac403168dfd6a430c24f2a0c08647636df165 Mon Sep 17 00:00:00 2001 From: kuzetsa Date: Wed, 25 Jun 2014 11:43:39 -0400 Subject: [PATCH 10/80] markdown fixes & removed reference to hosting copyright line in license had some glitchy markdown formatting, and removed yet another section which was irrelevant (*to this fork*) --- README.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5ec3f0330..6f80ef1de 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,7 @@ You are looking at the brand new and completetly different version of Gekko. We' -Gordon Gekko Gekko is a cloud mining reinvestment platform which based on [REST APIs](http://en.wikipedia.org/wiki/Representational_state_transfer) -It is written in javascript and runs on [nodejs](http://nodejs.org). - -This is the open source do-it-yourself version, we are planning on running hosted Gekkos in the cloud which does not require you to download and install anything, configure any textfiles or deal with the commandline. If you are looking for such a solution, sign up at [Wizbit](http://wizb.it) and we'll let you know once it's out! +It is written in javascript and runs on [node.js](https://github.com/joyent/node). *Use Gekko at you own risk.* @@ -19,8 +17,7 @@ This is the open source do-it-yourself version, we are planning on running hoste Gekko comes with a few [technical analysis (TA)](http://en.wikipedia.org/wiki/Technical_analysis) methods which implement a single indicator (DEMA, MACD, RSI and PPO). The parameters of these indicators are all configurable and changing them changes the outcome drastically. Additionally Gekko also provides an easy way to write your own trading methods in javascript. Read more about that in the [documentation](https://github.com/kuzetsa/gekko/blob/master/docs/internals/trading_methods.md). -**Note:** *much of this information is outdated, and/or not relevant to -reinvestment-only mode.* +**Note:** *much of this information is outdated, and/or not relevant to reinvestment-only mode.* ## Market interface @@ -85,8 +82,7 @@ If you want to contribute or are interested in how Gekko works: - Read about [Gekko's overall architecture](https://github.com/kuzetsa/gekko/tree/master/docs/internals/architecture.md). - Read on how to add [a new exchange to Gekko](https://github.com/kuzetsa/gekko/tree/master/docs/internals/exchanges.md). - Read on how to [create your own plugin](https://github.com/kuzetsa/gekko/tree/master/docs/internals/plugins.md). -- Implement [your own trading method](https://github.com/kuzetsa/gekko/blob/master/docs/internals/trading_methods.md) -and share it back. +- Implement [your own trading method](https://github.com/kuzetsa/gekko/blob/master/docs/internals/trading_methods.md) and share it back. ## Credits @@ -100,7 +96,7 @@ and share it back. The MIT License (MIT) Copyright (c) 2014 Mike van Rossum (original author, initial code in gekkobot) -Copyright (c) 2013-2014 Sarah White (*AKA **kuzetsa**, forked 2014 June 25th*) +Copyright (c) 2013-2014 Sarah White (*AKA* **kuzetsa**, *forked 2014 June 25th*) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 37ad643ba9ee4e9bfdfee6fe9da1defda8749b5c Mon Sep 17 00:00:00 2001 From: kuzetsa Date: Wed, 25 Jun 2014 11:44:37 -0400 Subject: [PATCH 11/80] missing newline caused and issue markdown hates me :( --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6f80ef1de..05cd17b12 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ You are looking at the brand new and completetly different version of Gekko. We' -Gordon Gekko Gekko is a cloud mining reinvestment platform which based on [REST APIs](http://en.wikipedia.org/wiki/Representational_state_transfer) + It is written in javascript and runs on [node.js](https://github.com/joyent/node). *Use Gekko at you own risk.* From df95e2fbcecc3422c224d37fe14192d5eed01c67 Mon Sep 17 00:00:00 2001 From: kuzetsa Date: Wed, 25 Jun 2014 12:01:00 -0400 Subject: [PATCH 12/80] README.md rewrite Beautification of the markdown, change to the content and prose, as well as cleaning up the grammar. --- README.md | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 05cd17b12..1ee3c7173 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,28 @@ -# Important note +### Gekko (*origin of the name*) -You are looking at the brand new and completetly different version of Gekko. We've tested it for quite a while though it might be possible that you encounter bugs. If you encounter a bug: check out in [the issues](https://github.com/kuzetsa/gekko/issues/) if we are aware of it and if not create a new one :) +#### 1987 movie "Wall Street" +> *The most valuable commodity I know of is information.* +> -- Gordon Gekko -# Gekko +## Gekko (*this tool / bot*) -*The most valuable commodity I know of is information.* +This tool is based on [REST APIs](http://en.wikipedia.org/wiki/Representational_state_transfer), and is written the [node.js](https://github.com/joyent/node) dialect of javascript, which itself based on v8, Google's open source JavaScript engine. --Gordon Gekko +*Use Gekko at you own risk.* -Gekko is a cloud mining reinvestment platform which based on [REST APIs](http://en.wikipedia.org/wiki/Representational_state_transfer) +## Automated reinvestment -It is written in javascript and runs on [node.js](https://github.com/joyent/node). +Gekko comes with a few [technical analysis (TA)](http://en.wikipedia.org/wiki/Technical_analysis) methods which implement a single indicator (*DEMA, MACD, RSI, and PPO*). The parameters of these indicators are all configurable and changing them changes the outcome drastically. Gekko's interface allows you to add your own trading methods. Read more about that in the [documentation](https://github.com/kuzetsa/gekko/blob/master/docs/internals/trading_methods.md). -*Use Gekko at you own risk.* +**Note:** *much of this information is outdated, and/or not relevant to reinvestment-only mode.* -**Automated reinvestment** +## Supported exchanges -Gekko comes with a few [technical analysis (TA)](http://en.wikipedia.org/wiki/Technical_analysis) methods which implement a single indicator (DEMA, MACD, RSI and PPO). The parameters of these indicators are all configurable and changing them changes the outcome drastically. Additionally Gekko also provides an easy way to write your own trading methods in javascript. Read more about that in the [documentation](https://github.com/kuzetsa/gekko/blob/master/docs/internals/trading_methods.md). +Gekko reinvests cloud mining income on the following platforms: -**Note:** *much of this information is outdated, and/or not relevant to reinvestment-only mode.* +- CEX.io -## Market interface +## Plugin interface Gekko also has a plugin system that can do certain things whenever something happens or let Gekko communicate through more platforms. Gekko implements the following example plugins: @@ -30,12 +32,6 @@ Gekko also has a plugin system that can do certain things whenever something hap - Profit Simulator (paper trader): Hold a fake portfolio and simulate trades based on advice. - Redis Beacon: Broadcast events propagating through Gekko on [Redis pub/sub](http://redis.io/topics/pubsub). -## Supported exchanges - -Gekko reinvests cloud mining income on the following platforms: - -- CEX.io - ## Installing Gekko Windows user? Here is a [step-by-step guide](https://github.com/kuzetsa/gekko/blob/master/docs/installing_gekko_on_windows.md) on how to get Gekko running on Windows. From 1b99918f8445135033d68552e30b5ac336fe8184 Mon Sep 17 00:00:00 2001 From: kuzetsa Date: Wed, 25 Jun 2014 12:02:16 -0400 Subject: [PATCH 13/80] minor cosmetic change --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 1ee3c7173..85fe10a71 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,7 @@ ## Gekko (*this tool / bot*) -This tool is based on [REST APIs](http://en.wikipedia.org/wiki/Representational_state_transfer), and is written the [node.js](https://github.com/joyent/node) dialect of javascript, which itself based on v8, Google's open source JavaScript engine. - -*Use Gekko at you own risk.* +This tool is based on [REST APIs](http://en.wikipedia.org/wiki/Representational_state_transfer), and is written the [node.js](https://github.com/joyent/node) dialect of javascript, which itself based on v8, Google's open source JavaScript engine. **WARNING:** *Use Gekko at you own risk.* ## Automated reinvestment From f991f953b8d95faf7361755b6f8b1e021cbae470 Mon Sep 17 00:00:00 2001 From: kuzetsa Date: Wed, 25 Jun 2014 12:05:08 -0400 Subject: [PATCH 14/80] last markdown fix (README.md is done for now) from this point, not much should be happening other than bugfixes, new features, accepting pull requests, etc. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 85fe10a71..7d3222f84 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ If you want to contribute or are interested in how Gekko works: The MIT License (MIT) Copyright (c) 2014 Mike van Rossum (original author, initial code in gekkobot) + Copyright (c) 2013-2014 Sarah White (*AKA* **kuzetsa**, *forked 2014 June 25th*) Permission is hereby granted, free of charge, to any person obtaining a copy From dc7ea9649e6bda02b055019a6ff99f877e5831d0 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Wed, 25 Jun 2014 15:11:43 -0400 Subject: [PATCH 15/80] cosmetic logging bugfix (no functionality change) Fixes erroneous order of parameters passed to string builder: Example: Ignore tiny BUY order (0.00003315 GHS) at cex.io was intended to be: Ignore tiny (0.00003315 GHS) BUY order at cex.io --- core/portfolioManager.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/portfolioManager.js b/core/portfolioManager.js index dd51630a0..3c6ebbdd9 100644 --- a/core/portfolioManager.js +++ b/core/portfolioManager.js @@ -221,9 +221,8 @@ Manager.prototype.buy = function(amount, price) { if(amount < minimum) { return log.info( 'Ignore tiny', - 'BUY order', '(' + amount.toFixed(8) + ' ' + this.asset + ')', - + 'BUY order', 'at', this.exchange.name ); From 8183b2612a011a1b385dc2889356fce98f641636 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Wed, 25 Jun 2014 15:19:59 -0400 Subject: [PATCH 16/80] poorly placed linebreak (cleanup) --- config.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config.js b/config.js index f2ec8c7de..83e806f08 100644 --- a/config.js +++ b/config.js @@ -239,7 +239,6 @@ config.backtest = { // it doesn't advice on itself, only set to true if you truly // understand this. // -config['I understand that Gekko only automates MY OWN trading -strategies'] = false; +config['I understand that Gekko only automates MY OWN trading strategies'] = false; module.exports = config; From 738d58bb3f80af060e843e920141d2aa75546bb0 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Wed, 25 Jun 2014 21:46:35 -0400 Subject: [PATCH 17/80] replaced old logic which erronously assumed no action was needed if neither a buy nor a sell was taking place --- core/portfolioManager.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/portfolioManager.js b/core/portfolioManager.js index 3c6ebbdd9..edad38b67 100644 --- a/core/portfolioManager.js +++ b/core/portfolioManager.js @@ -120,7 +120,11 @@ Manager.prototype.getBalance = function(fund) { // is this a infinityOrderExchange (does it support order // requests bigger then the current balance?) Manager.prototype.trade = function(what) { - if(what !== 'BUY' && what !== 'SELL') + +// lizards ALWAYS happen instead of buy orders + this.action = what; + + if(what !== 'BUY') return; @@ -132,9 +136,6 @@ var rightnow = (new Date()).getTime(); // ok, so reset the limiter this.limiter = (new Date()).getTime() + 9000; - - this.action = what; - var act = function() { var amount, price; From 42bfaa7d3b3760d6776c3cf19cd5f1452c6b41d8 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Wed, 25 Jun 2014 21:51:46 -0400 Subject: [PATCH 18/80] non-sell / non-buy order to exit the "enforce" position... this bug was hard to track down. --- plugins/trader.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/plugins/trader.js b/plugins/trader.js index 4450fa65d..174fc49c2 100644 --- a/plugins/trader.js +++ b/plugins/trader.js @@ -18,14 +18,9 @@ Trader.prototype.processAdvice = function(advice) { 'Received advice to go long', 'Buying ', config.trader.asset ); - } else if(advice.recommandation == 'short') { - this.manager.trade('SELL'); - log.debug( - 'Trader', - 'Received advice to go short', - 'Selling ', config.trader.asset - ); - } + } else + this.manager.trade('LIZARD'); +// lizards are not going cause a sell order. EVER!!! } module.exports = Trader; From 7e8706a3db43c858a52d7ed7907e38b07ee1c02b Mon Sep 17 00:00:00 2001 From: Sarah White Date: Thu, 26 Jun 2014 12:13:49 -0400 Subject: [PATCH 19/80] cex.io fee changed to 0.2%, minimum trade exactly 1 satoshi the exact value is 500 satoshi (0.00000500 BTC) the improved rounding calculation makes it possible --- exchanges.js | 4 +--- exchanges/cexio.js | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/exchanges.js b/exchanges.js index 310607520..be1b12d5e 100644 --- a/exchanges.js +++ b/exchanges.js @@ -178,9 +178,7 @@ var exchanges = [ assets: ['GHS'], markets: [ { - pair: ['BTC', 'GHS'], minimalOrder: { amount: 0.00161803, -unit: -'asset' } + pair: ['BTC', 'GHS'], minimalOrder: { amount: 0.00000500, unit: 'currency' } } ], requires: ['key', 'secret', 'username'], diff --git a/exchanges/cexio.js b/exchanges/cexio.js index 29d961ecd..bd10f4dd6 100644 --- a/exchanges/cexio.js +++ b/exchanges/cexio.js @@ -149,7 +149,7 @@ Trader.prototype.getTicker = function(callback) { Trader.prototype.getFee = function(callback) { // cexio does currently don't take a fee on trades // TODO: isn't there an API call for this? - callback(false, 0.0075); + callback(false, 0.002); } Trader.prototype.checkOrder = function(order, callback) { From 82004f790e000d4b1ee3d4b9206ef5c7fe12aed3 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Thu, 26 Jun 2014 14:44:08 -0400 Subject: [PATCH 20/80] Initial trading methods cleanup, making more than just MACD work right --- methods/MACD.js | 6 ++-- methods/PPO.js | 77 +++++++++++-------------------------------------- 2 files changed, 19 insertions(+), 64 deletions(-) diff --git a/methods/MACD.js b/methods/MACD.js index 01fbaeffe..8efeb3fab 100644 --- a/methods/MACD.js +++ b/methods/MACD.js @@ -4,7 +4,7 @@ (updated a couple of times since, check git history) - Modified by kuzetsa 2014 June 22 (CEXIO lizards variant) + Modified by kuzetsa 2014 June 26 (CEXIO lizards variant) */ @@ -30,9 +30,7 @@ method.init = function() { // report it. this.trend = { direction: 'none', - duration: 0, - persisted: false, - adviced: false + duration: 0 }; // how many candles do we need as a base diff --git a/methods/PPO.js b/methods/PPO.js index d70710bc8..7914d6415 100644 --- a/methods/PPO.js +++ b/methods/PPO.js @@ -4,6 +4,8 @@ (updated a couple of times since, check git history) + Modified by kuzetsa 2014 June 26 (CEXIO lizards variant) + */ // helpers @@ -23,9 +25,7 @@ method.init = function() { this.trend = { direction: 'none', - duration: 0, - persisted: false, - adviced: false + duration: 0 }; this.requiredHistory = config.tradingAdvisor.historySize; @@ -52,14 +52,14 @@ method.log = function() { var ppoSignal = ppo.PPOsignal.result; log.debug('calculated MACD properties for candle:'); - log.debug('\t', 'short:', short.toFixed(digits)); - log.debug('\t', 'long:', long.toFixed(digits)); - log.debug('\t', 'macd:', macd.toFixed(digits)); - log.debug('\t', 'macdsignal:', macdSignal.toFixed(digits)); - log.debug('\t', 'machist:', (macd - macdSignal).toFixed(digits)); - log.debug('\t', 'ppo:', result.toFixed(digits)); - log.debug('\t', 'pposignal:', ppoSignal.toFixed(digits)); - log.debug('\t', 'ppohist:', (result - ppoSignal).toFixed(digits)); + log.info('\t', 'short:', short.toFixed(digits)); + log.info('\t', 'long:', long.toFixed(digits)); + log.info('\t', 'macd:', macd.toFixed(digits)); + log.info('\t', 'macdsignal:', macdSignal.toFixed(digits)); + log.info('\t', 'machist:', (macd - macdSignal).toFixed(digits)); + log.info('\t', 'ppo:', result.toFixed(digits)); + log.info('\t', 'pposignal:', ppoSignal.toFixed(digits)); + log.info('\t', 'ppohist:', (result - ppoSignal).toFixed(digits)); } method.check = function() { @@ -83,68 +83,25 @@ method.check = function() { if(this.trend.direction !== 'up') this.trend = { duration: 0, - persisted: false, - direction: 'up', - adviced: false + direction: 'up' }; this.trend.duration++; - log.debug('In uptrend since', this.trend.duration, 'candle(s)'); - - if(this.trend.duration >= settings.thresholds.persistence) - this.trend.persisted = true; + log.info('In uptrend since', this.trend.duration, 'candle(s)'); - if(this.trend.persisted && !this.trend.adviced) { - this.trend.adviced = true; this.advice('long'); - } else - this.advice(); - - } else if(ppoHist < settings.thresholds.down) { - // new trend detected - if(this.trend.direction !== 'down') + } else { + this.trend = { duration: 0, - persisted: false, - direction: 'down', - adviced: false + direction: 'lizards' }; - this.trend.duration++; - - log.debug('In downtrend since', this.trend.duration, 'candle(s)'); - - if(this.trend.duration >= settings.thresholds.persistence) - this.trend.persisted = true; - - if(this.trend.persisted && !this.trend.adviced) { - this.trend.adviced = true; - this.advice('short'); - } else - this.advice(); - - - } else { - - log.debug('In no trend'); - - // we're not in an up nor in a downtrend - // but for now we ignore sideways trends - // - // read more @link: - // - // https://github.com/askmike/gekko/issues/171 - // this.trend = { - // direction: 'none', - // duration: 0, - // persisted: false, - // adviced: false - // }; + this.advice('lizards'); - this.advice(); } } From de8407509f2bc3aeef7856e9afbca3159cf3a6e4 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Thu, 26 Jun 2014 17:39:07 -0400 Subject: [PATCH 21/80] Added new x2 (double) EMA-based MACD method --- methods/indicators/x2EMA.js | 20 +++++++ methods/indicators/x2MACD.js | 26 +++++++++ methods/x2MACD.js | 105 +++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 methods/indicators/x2EMA.js create mode 100644 methods/indicators/x2MACD.js create mode 100644 methods/x2MACD.js diff --git a/methods/indicators/x2EMA.js b/methods/indicators/x2EMA.js new file mode 100644 index 000000000..3d75de1ca --- /dev/null +++ b/methods/indicators/x2EMA.js @@ -0,0 +1,20 @@ +// filename "DEMA.js" already in use by some old legacy code +// name x2EMA means: "double EMA" (usually called DEMA) +// (2 * x1EMA(x)) - x1EMA(x1EMA(x)) + +var x1EMA = require('./EMA.js'); + +var Indicator = function(weight) { + this.EMA = new x1EMA(weight); + this.EMAprime = new x1EMA(weight); +} + +Indicator.prototype.update = function(weight) { +var EMA = 0.0; + this.EMA.update(weight); + EMA = this.EMA.result; + this.EMAprime.update(EMA); + this.result = (2 * EMA) - this.EMAprime.result; +} + +module.exports = Indicator; diff --git a/methods/indicators/x2MACD.js b/methods/indicators/x2MACD.js new file mode 100644 index 000000000..6426025b0 --- /dev/null +++ b/methods/indicators/x2MACD.js @@ -0,0 +1,26 @@ +// DEMA-based MACD by kuzetsa, 2014 June 26 +var x2EMA = require('./x2EMA.js'); + +var Indicator = function(config) { + this.diff = false; + this.short = new x2EMA(config.short); + this.long = new x2EMA(config.long); + this.signal = new x2EMA(config.signal); +} + +Indicator.prototype.update = function(price) { + this.short.update(price); + this.long.update(price); + this.calculateEMAdiff(); + this.signal.update(this.diff); + this.result = this.diff - this.signal.result; +} + +Indicator.prototype.calculateEMAdiff = function() { + var shortEMA = this.short.result; + var longEMA = this.long.result; + + this.diff = shortEMA - longEMA; +} + +module.exports = Indicator; diff --git a/methods/x2MACD.js b/methods/x2MACD.js new file mode 100644 index 000000000..3d0f34f07 --- /dev/null +++ b/methods/x2MACD.js @@ -0,0 +1,105 @@ +/* + + MACD, double-EMA variant + by kuzetsa 2014 June 26 + (CEXIO lizards variant) + + only change is x2EMA instead of (x1)EMA + + */ + +// helpers +var _ = require('lodash'); +var log = require('../core/log.js'); + +// configuration +var config = require('../core/util.js').getConfig(); +var settings = config.x2MACD; + +// let's create our own method +var method = {}; + +// prepare everything our method needs +method.init = function() { + + this.name = 'x2MACD'; + + // keep state about the current trend + // here, on every new candle we use this + // state object to check if we need to + // report it. + this.trend = { + direction: 'none', + duration: 0 + }; + + // how many candles do we need as a base + // before we can start giving advice? + this.requiredHistory = config.tradingAdvisor.historySize; + + // define the indicators we need + this.addIndicator('x2macd', 'x2MACD', settings); + +} + +// what happens on every new candle? +method.update = function(candle) { + // nothing! +} + +// for debugging purposes: log the last calculated +// EMAs and diff. +method.log = function() { + var digits = 8; + var x2macd = this.indicators.x2macd; + + var diff = x2macd.diff; + var signal = x2macd.signal.result; + + log.debug('calculated MACD properties for candle:'); + log.info('\t', 'short:', x2macd.short.result.toFixed(digits)); + log.info('\t', 'long:', x2macd.long.result.toFixed(digits)); + log.info('\t', 'macd:', diff.toFixed(digits)); + log.info('\t', 'signal:', signal.toFixed(digits)); + log.info('\t', 'macdiff:', x2macd.result.toFixed(digits)); +} + +method.check = function() { + var price = this.lastPrice; + var x2macd = this.indicators.x2macd; + + var long = x2macd.long.result; + var short = x2macd.short.result; + var signal = x2macd.signal.result; + var macddiff = x2macd.result; + + if(macddiff > settings.thresholds.up) { + + // new trend detected + if(this.trend.direction !== 'up') + // reset the state for the new trend + this.trend = { + duration: 0, + direction: 'up', + }; + + this.trend.duration++; + + log.info('In uptrend since', this.trend.duration, 'candle(s)'); + + this.advice('long'); + + } else { + + this.trend = { + duration: 0, + direction: 'lizards', + }; + + + this.advice('lizards'); + + } +} + +module.exports = method; From 7e5dfdbb0246db7741b25e8b1c818142b48eee5b Mon Sep 17 00:00:00 2001 From: Sarah White Date: Thu, 26 Jun 2014 17:42:21 -0400 Subject: [PATCH 22/80] Config changes to enable the new x2MACD method --- config.js | 24 ++++++++++++++++-------- core/baseTradingMethod.js | 4 ++++ plugins/tradingAdvisor.js | 1 + 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/config.js b/config.js index 83e806f08..061afa82b 100644 --- a/config.js +++ b/config.js @@ -33,9 +33,9 @@ config.watch = { config.tradingAdvisor = { enabled: true, - method: 'MACD', + method: 'x2MACD', candleSize: 1, - historySize: 113 + historySize: 239 } // Exponential Moving Averages settings: @@ -63,9 +63,20 @@ config.MACD = { thresholds: { down: -9999, up: 0.00000001, - // How many candle intervals should a trend persist - // before we consider it real? - persistence: 1 + } +}; + +// x2MACD settings: +config.x2MACD = { + // EMA weight (α) + // the higher the weight, the more smooth (and delayed) the line + short: 53, + long: 109, + signal: 41, + // the difference between the EMAs (to act as triggers) + thresholds: { + down: -9999, + up: 0.00000001, } }; @@ -80,9 +91,6 @@ config.PPO = { thresholds: { down: -9999, up: 0.00000001, - // How many candle intervals should a trend persist - // before we consider it real? - persistence: 2 } }; diff --git a/core/baseTradingMethod.js b/core/baseTradingMethod.js index 015cfcd71..11021622d 100644 --- a/core/baseTradingMethod.js +++ b/core/baseTradingMethod.js @@ -6,6 +6,10 @@ var log = require('../core/log.js'); var indicatorsPath = '../methods/indicators/'; var Indicators = { + x2MACD: { + factory: require(indicatorsPath + 'x2MACD'), + input: 'price' + }, MACD: { factory: require(indicatorsPath + 'MACD'), input: 'price' diff --git a/plugins/tradingAdvisor.js b/plugins/tradingAdvisor.js index 78e4f00cc..5b5db18b8 100644 --- a/plugins/tradingAdvisor.js +++ b/plugins/tradingAdvisor.js @@ -6,6 +6,7 @@ var config = util.getConfig(); var methods = [ 'MACD', + 'x2MACD', 'DEMA', 'PPO', 'RSI', From 2fe7355d2051feb66563cbe39701d25550c75d64 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 29 Jun 2014 00:18:50 -0400 Subject: [PATCH 23/80] nikiehihsa is Heikin-Ashi spelled backwards --- methods/indicators/nikiehihsa.js | 59 ++++++++++++++++++++++ methods/nikiehihsa.js | 84 ++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 methods/indicators/nikiehihsa.js create mode 100644 methods/nikiehihsa.js diff --git a/methods/indicators/nikiehihsa.js b/methods/indicators/nikiehihsa.js new file mode 100644 index 000000000..9c2ff3e60 --- /dev/null +++ b/methods/indicators/nikiehihsa.js @@ -0,0 +1,59 @@ +// inspired by Heikin-Ashi candles +// NOT QUITE based on xClose average +// ...weighted geometric mean instead + +Indicator.prototype.update = function(candle) { + +var EMA = require('./EMA.js'); + +var Indicator = function(config) { + this.diff = false; + this.shortC = new EMA(config.short); + this.longC = new EMA(config.long); + this.shortO = new EMA(config.short); + this.longO = new EMA(config.long); + this.shortL = new EMA(config.short); + this.longL = new EMA(config.long); + this.shortH = new EMA(config.short); + this.longH = new EMA(config.long); + this.signal = new EMA(config.signal); +} + +Indicator.prototype.update = function(candle) { + var open = candle.o; + var close = candle.c; + var high = candle.h; + var low = candle.l; + + this.shortC.update(close); + this.longC.update(close); + this.shortO.update(open); + this.longO.update(open); + this.shortH.update(high); + this.longH.update(high); + this.shortL.update(low); + this.longL.update(low); + this.calculateEMAdiff(); + this.signal.update(this.diff); + this.result = this.diff - this.signal.result; +} + +Indicator.prototype.calculateEMAdiff = function() { + + var Sc = pow(this.shortC.result, 3); + var Lc = pow(this.longC.result, 3); + var So = this.shortO.result; + var Lo = this.longO.result; + var Sh = pow(this.shortH.result, 2); + var Lh = pow(this.longH.result, 2); + var Sl = pow(this.shortL.result, 2); + var Ll = pow(this.longL.result, 2); + var eightroot = 1 / 8; + + var short = pow((Sc * So * Sh * Sl), eightroot); + var long = pow((Lc * Lo * Lh * Ll), eightroot); + + this.diff = short - long; +} + +module.exports = Indicator; diff --git a/methods/nikiehihsa.js b/methods/nikiehihsa.js new file mode 100644 index 000000000..e95c96bcc --- /dev/null +++ b/methods/nikiehihsa.js @@ -0,0 +1,84 @@ +var _ = require('lodash'); +var log = require('../core/log.js'); + +// configuration +var config = require('../core/util.js').getConfig(); +var settings = config.nikiehihsa; + +// nikiehihsa by kuzetsa, 2014 June 29 +var method = {}; + +// prepare everything our method needs +method.init = function() { + + this.name = 'nikiehihsa'; + + this.trend = { + direction: 'none', + duration: 0 + }; + + // how many candles do we need as a base + // before we can start giving advice? + this.requiredHistory = config.tradingAdvisor.historySize; + + // define the indicators we need + this.addIndicator('nikiehihsa', 'nikiehihsa', settings); + +} + +// what happens on every new candle? +method.update = function(candle) { + // NOTHING!!! + // (just kidding, OOP takes care of it) +} + +method.log = function() { + var digits = 8; + var nikiehihsa = this.indicators.nikiehihsa; + + log.info('\t', '[shortEMA]CLOSE:', nikiehihsa.shortC.result.toFixed(digits)); + log.info('\t', '[longEMA]CLOSE:', nikiehihsa.longC.result.toFixed(digits)); + log.info('\t', 'macd:', nikiehihsa.diff.toFixed(digits)); + log.info('\t', 'signal:', nikiehihsa.signal.toFixed(digits)); + log.info('\t', 'macdiff:', nikiehihsa.result.toFixed(digits)); +} + +method.check = function() { + var nikiehihsa = this.indicators.nikiehihsa; + + var macddiff = nikiehihsa.result; + // This is not a histogram... + // it's more of an oscillator + // and it's nearly identical to MACD + // ... but it uses the entire candle + + if(macddiff > settings.thresholds.up) { + + // new trend detected + if(this.trend.direction !== 'up') + // reset the state for the new trend + this.trend = { + duration: 0, + direction: 'up', + }; + + this.trend.duration++; + + log.info('In uptrend since', this.trend.duration, 'candle(s)'); + + this.advice('long'); + + } else { + + this.trend = { + duration: 0, + direction: 'lizards', + }; + + this.advice('lizards'); + + } +} + +module.exports = method; From ec8ed6f2a3b80b2e95d58d456a4c0ca8a60d5734 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 29 Jun 2014 00:19:35 -0400 Subject: [PATCH 24/80] config changes to enable nikiehihsa method --- config.js | 16 +++++++++++++++- core/baseTradingMethod.js | 4 ++++ plugins/tradingAdvisor.js | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/config.js b/config.js index 061afa82b..51ef81247 100644 --- a/config.js +++ b/config.js @@ -33,7 +33,7 @@ config.watch = { config.tradingAdvisor = { enabled: true, - method: 'x2MACD', + method: 'nikiehihsa', candleSize: 1, historySize: 239 } @@ -80,6 +80,20 @@ config.x2MACD = { } }; +// nikiehihsa settings: +config.nikiehihsa = { + // EMA weight (α) + // the higher the weight, the more smooth (and delayed) the line + short: 53, + long: 109, + signal: 41, + // the difference between the EMAs (to act as triggers) + thresholds: { + down: -9999, + up: 0.00000001, + } +}; + // PPO settings: config.PPO = { // EMA weight (α) diff --git a/core/baseTradingMethod.js b/core/baseTradingMethod.js index 11021622d..8f661208f 100644 --- a/core/baseTradingMethod.js +++ b/core/baseTradingMethod.js @@ -6,6 +6,10 @@ var log = require('../core/log.js'); var indicatorsPath = '../methods/indicators/'; var Indicators = { + nikiehihsa: { + factory: require(indicatorsPath + 'nikiehihsa'), + input: 'candle' + }, x2MACD: { factory: require(indicatorsPath + 'x2MACD'), input: 'price' diff --git a/plugins/tradingAdvisor.js b/plugins/tradingAdvisor.js index 5b5db18b8..8d19ad824 100644 --- a/plugins/tradingAdvisor.js +++ b/plugins/tradingAdvisor.js @@ -7,6 +7,7 @@ var config = util.getConfig(); var methods = [ 'MACD', 'x2MACD', + 'nikiehihsa', 'DEMA', 'PPO', 'RSI', From c325e25ba78fd9657dff275a4f901311755e7c67 Mon Sep 17 00:00:00 2001 From: kuzetsa Date: Sun, 29 Jun 2014 12:19:36 -0400 Subject: [PATCH 25/80] AGPLv3 all commits by kuzetsa [MULTI-LICENSE] All commits in this project submitted by kuzetsa are AGPLv3. --- LICENSE.md | 661 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 661 insertions(+) create mode 100644 LICENSE.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..dba13ed2d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. From 8c1cc250eaacc699f815d3faac75bffd07825fb5 Mon Sep 17 00:00:00 2001 From: kuzetsa Date: Sun, 29 Jun 2014 12:35:30 -0400 Subject: [PATCH 26/80] README update for AGPLv3 [MULTI-LICENSE] Just to be extra clear: This code [b]IS[/b] copyrighted, but you have a license to use this code with a few legal restrictions. If you use this code for any purpose (*noncommercial or otherwise*) ... ... blah blah blah, subject to whatever jurisdiction or legal process applies, etc. etc. etc. I'm releasing my **own code** work on the gekko project under [AGPLv3](https://github.com/kuzetsa/gekko/blob/c325e25ba78fd9657dff275a4f901311755e7c67/LICENSE.md) --- README.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7d3222f84..520f80281 100644 --- a/README.md +++ b/README.md @@ -88,12 +88,31 @@ If you want to contribute or are interested in how Gekko works: ## License -The MIT License (MIT) +(*Legal rights and restrictions to copy and use this code*) -Copyright (c) 2014 Mike van Rossum (original author, initial code in gekkobot) +#### [GNU Affero General Public License (AGPL) version 3](https://github.com/kuzetsa/gekko/blob/c325e25ba78fd9657dff275a4f901311755e7c67/LICENSE.md) Copyright (c) 2013-2014 Sarah White (*AKA* **kuzetsa**, *forked 2014 June 25th*) + This file is part of gekko. + + gekko is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + gekko is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Foobar. If not, see . + +#### The MIT License (MIT) + +Copyright (c) 2014 Mike van Rossum (*original author, initial code in gekkobot*) + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights From 3997fce3238491c21a4d00afc3124a20a7485b25 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 29 Jun 2014 16:53:02 -0400 Subject: [PATCH 27/80] un-duplicated a misplaced line that mis-pasted code served no purpose ... and also caused a crash --- methods/indicators/nikiehihsa.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/methods/indicators/nikiehihsa.js b/methods/indicators/nikiehihsa.js index 9c2ff3e60..eaf043b43 100644 --- a/methods/indicators/nikiehihsa.js +++ b/methods/indicators/nikiehihsa.js @@ -2,8 +2,6 @@ // NOT QUITE based on xClose average // ...weighted geometric mean instead -Indicator.prototype.update = function(candle) { - var EMA = require('./EMA.js'); var Indicator = function(config) { From 393830b13f95b277aa1da543ac342b57952d5e9e Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 29 Jun 2014 17:09:09 -0400 Subject: [PATCH 28/80] "result" property needed specififed the EMA object doesn't expose default property as such, the "result" needs to be specified --- methods/nikiehihsa.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/methods/nikiehihsa.js b/methods/nikiehihsa.js index e95c96bcc..d76b078c1 100644 --- a/methods/nikiehihsa.js +++ b/methods/nikiehihsa.js @@ -40,7 +40,7 @@ method.log = function() { log.info('\t', '[shortEMA]CLOSE:', nikiehihsa.shortC.result.toFixed(digits)); log.info('\t', '[longEMA]CLOSE:', nikiehihsa.longC.result.toFixed(digits)); log.info('\t', 'macd:', nikiehihsa.diff.toFixed(digits)); - log.info('\t', 'signal:', nikiehihsa.signal.toFixed(digits)); + log.info('\t', 'signal:', nikiehihsa.signal.result.toFixed(digits)); log.info('\t', 'macdiff:', nikiehihsa.result.toFixed(digits)); } From 26ba336f6beba487a3acd3d32d088dc21db6189a Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 29 Jun 2014 17:11:51 -0400 Subject: [PATCH 29/80] pow is a method from the Math() object pow() does not work Math.pow() is correct This special sauce was the last thing needed nikiehihsa.js now works as expected (no crash) --- methods/indicators/nikiehihsa.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/methods/indicators/nikiehihsa.js b/methods/indicators/nikiehihsa.js index eaf043b43..9a822ffb4 100644 --- a/methods/indicators/nikiehihsa.js +++ b/methods/indicators/nikiehihsa.js @@ -38,18 +38,18 @@ Indicator.prototype.update = function(candle) { Indicator.prototype.calculateEMAdiff = function() { - var Sc = pow(this.shortC.result, 3); - var Lc = pow(this.longC.result, 3); + var Sc = Math.pow(this.shortC.result, 3); + var Lc = Math.pow(this.longC.result, 3); var So = this.shortO.result; var Lo = this.longO.result; - var Sh = pow(this.shortH.result, 2); - var Lh = pow(this.longH.result, 2); - var Sl = pow(this.shortL.result, 2); - var Ll = pow(this.longL.result, 2); + var Sh = Math.pow(this.shortH.result, 2); + var Lh = Math.pow(this.longH.result, 2); + var Sl = Math.pow(this.shortL.result, 2); + var Ll = Math.pow(this.longL.result, 2); var eightroot = 1 / 8; - var short = pow((Sc * So * Sh * Sl), eightroot); - var long = pow((Lc * Lo * Lh * Ll), eightroot); + var short = Math.pow((Sc * So * Sh * Sl), eightroot); + var long = Math.pow((Lc * Lo * Lh * Ll), eightroot); this.diff = short - long; } From 7d0768c8ae18c5191482c00d3d0838c90ca69cc4 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 29 Jun 2014 17:16:53 -0400 Subject: [PATCH 30/80] "lizard" is the launcher now removed the unsuppoted /bin/ directory the "lizard" script is fewer lines ...this makes it easier to maintain: easier to audit, debug, change, etc. after installation and configuration all you should need to do is: $ ./lizard --- bin/gekko_launch.sh | 18 ------------------ bin/gekko_launch_screen.sh | 3 --- bin/gekko_log_grab.sh | 1 - bin/gekko_screen_grab.sh | 1 - 4 files changed, 23 deletions(-) delete mode 100755 bin/gekko_launch.sh delete mode 100755 bin/gekko_launch_screen.sh delete mode 100755 bin/gekko_log_grab.sh delete mode 100755 bin/gekko_screen_grab.sh diff --git a/bin/gekko_launch.sh b/bin/gekko_launch.sh deleted file mode 100755 index 442f61aab..000000000 --- a/bin/gekko_launch.sh +++ /dev/null @@ -1,18 +0,0 @@ -#navigate to gekko folder, change this if your location is different -cd ~/gekko - -# zip previous log files for this config -now="`date +%Y%m%d%H%M%S`" -current_log=log/gekko_log.$1.txt -archive_log=log/gekko_log.$1.$now.txt - -if [ -f $current_log ]; then - mv log/gekko_log.$1.txt $archive_log - zip -vu log/gekko_logs.zip $archive_log - - # remove raw text files now that they're zipped - rm $archive_log -fi - -# finally launch gekko and log output to log file as well as stdout -node gekko config="config/user/$1.js" 2>&1 | tee $current_log diff --git a/bin/gekko_launch_screen.sh b/bin/gekko_launch_screen.sh deleted file mode 100755 index f1e88249c..000000000 --- a/bin/gekko_launch_screen.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -screen -dmS gekko_$1 ~/gekko/bin/gekko_launch.sh $1 - diff --git a/bin/gekko_log_grab.sh b/bin/gekko_log_grab.sh deleted file mode 100755 index bad586ca2..000000000 --- a/bin/gekko_log_grab.sh +++ /dev/null @@ -1 +0,0 @@ -tail -f ~/gekko/log/gekko_log.$1.txt diff --git a/bin/gekko_screen_grab.sh b/bin/gekko_screen_grab.sh deleted file mode 100755 index 88ff923ca..000000000 --- a/bin/gekko_screen_grab.sh +++ /dev/null @@ -1 +0,0 @@ -screen -dR gekko_$1 From 37a6b53913c23e65a61c30ea917d470531420cbe Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 29 Jun 2014 20:23:47 -0400 Subject: [PATCH 31/80] "lizard" launcher script forgot to include this earlier (oops) --- lizard | 3 +++ 1 file changed, 3 insertions(+) create mode 100755 lizard diff --git a/lizard b/lizard new file mode 100755 index 000000000..e1a60c64e --- /dev/null +++ b/lizard @@ -0,0 +1,3 @@ +#!/bin/bash +# kuzetsa's gekko lizard wrapper +./launcher | tee -i lizard.log From c5cbd7e5bea7777ffb3a0dd41e4d21345bae46b3 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 29 Jun 2014 20:24:25 -0400 Subject: [PATCH 32/80] candleManager.js now passes jslint/jshint only took 2 hours, felt like more :( --- core/candleManager.js | 317 +++++++++++++++++++++++++++--------------- 1 file changed, 203 insertions(+), 114 deletions(-) diff --git a/core/candleManager.js b/core/candleManager.js index 043aad470..5127290e9 100644 --- a/core/candleManager.js +++ b/core/candleManager.js @@ -94,15 +94,17 @@ var Manager = function() { // 'realtime updating' // make sure the historical folder exists - if(!fs.existsSync(this.historyPath)) + if(!fs.existsSync(this.historyPath)) { fs.mkdirSync(this.historyPath); - - if(!fs.existsSync(this.historyPath)) + } + + if(!fs.existsSync(this.historyPath)) { throw 'Gekko is unable to save historical data, do I have sufficient rights?'; + } this.on('processed', function() { log.debug('Processed trades, sleeping for', this.fetch.next.m.fromNow(true) + '...'); - }) + }); // .on('full history build', this.emitRealtime) }; @@ -114,31 +116,39 @@ Util.inherits(Manager, EventEmitter); Manager.prototype.requiredDays = function(timespan, from) { var days = []; - if(!from) - var start = this.current.day.clone(); - else - var start = from.clone(); +// variable declaration style cleanup +// to make sure we pass jslint / jshint + + var start = {}; + + if(!from) { + start = this.current.day.clone(); + } else { + start = from.clone(); + } + var to = start.clone().subtract('m', timespan).startOf('day'); while(start >= to) { days.push(this.mom(start.clone())); start.subtract('d', 1); - }; + } return days; -} +}; Manager.prototype.checkHistory = function() { log.debug('checking history'); - this.history = {} + this.history = {}; this.history.required = tradingAdvisor.enabled; - if(this.history.required) + if(this.history.required) { this.history.timespan = tradingAdvisor.candleSize * tradingAdvisor.historySize; - else + } else { // load a only the current day this.history.timespan = 0; + } this.history.days = this.requiredDays(this.history.timespan); // load them @@ -153,13 +163,14 @@ Manager.prototype.checkHistory = function() { this.processHistoryStats(); }, this) ); -} +}; // create a new daily database and load it Manager.prototype.createDatabase = function(mom) { - if(mom.dayString in this.days && this.days[mom.dayString].handle) + if(mom.dayString in this.days && this.days[mom.dayString].handle) { return log.debug('Skipping creation of already loaded database', mom.dayString); - + } + var filename = this.databaseName(mom); if(this.databaseExists(filename)) { @@ -189,12 +200,13 @@ Manager.prototype.createDatabase = function(mom) { day.handle.ensureIndex({fieldName: 's', unique: true}); this.days[day.string] = day; -} +}; // load a daily database and store it in this.days Manager.prototype.loadDatabase = function(mom) { - if(mom.dayString in this.days && this.days[mom.dayString].mounted) + if(mom.dayString in this.days && this.days[mom.dayString].mounted) { return log.debug('Skipping loading', mom.dayString, ', it already exists'); + } var filename = this.databaseName(mom); @@ -217,8 +229,9 @@ Manager.prototype.loadDatabase = function(mom) { this.days[day.string] = day; - if(!this.databaseExists(day)) + if(!this.databaseExists(day)) { return; + } day.mounted = true; day.exists = true; @@ -230,36 +243,41 @@ Manager.prototype.loadDatabase = function(mom) { } catch(e) { util.die('Database file ' + filename + ' is corrupt, delete or rename that file and try again.'); } -} +}; Manager.prototype.databaseExists = function(day) { - if(_.isString(day)) + if(_.isString(day)) { // we have a filename instead day = {filename: day}; + } - if(!day.filename) + if(!day.filename) { // we have a mom instead day.filename = this.databaseName(day); + } return fs.existsSync(day.filename); -} +}; // calculate stats of a daily database and store state // in this.days Manager.prototype.setDatabaseMeta = function(mom, cb) { var day = this.days[mom.dayString]; - if(!day.exists) + if(!day.exists) { return cb(); + } day.handle.find({s: {$gt: -1}}, _.bind(function(err, minutes) { - if(err) + if(err) { return cb(err); + } day.minutes = _.size(minutes); - if(!day.minutes) + if(!day.minutes) { return cb(); + } // this day has candles, store stats day.empty = false; @@ -272,7 +290,7 @@ Manager.prototype.setDatabaseMeta = function(mom, cb) { cb(); }, this)); -} +}; Manager.prototype.deleteDay = function(day, safe) { log.debug( @@ -280,17 +298,20 @@ Manager.prototype.deleteDay = function(day, safe) { day.string, 'as corrupt' ); - if(safe) + + if(safe) { fs.renameSync( day.filename, day.filename.replace('.db', '') + ' [incomplete ~ ' + (+moment()) + '].db' ); - else + } else { fs.unlinkSync(day.filename); + } - if(day.string in this.days) + if(day.string in this.days) { delete this.days[day.string]; -} + } +}; // loop through this.days and check every meta to // see what history we have available @@ -310,40 +331,53 @@ Manager.prototype.checkDaysMeta = function(err, results) { var isFirstDay = equals(this.current.day, day.time); - if(!day.exists) + if(!day.exists) { return false; - if(day.empty) + } + + if(day.empty) { return this.deleteDay(day); - if(!day.minutes) + } + + if(!day.minutes) { return false; + } - if(!isFirstDay && day.endCandle.s !== MINUTES_IN_DAY) + if(!isFirstDay && day.endCandle.s !== MINUTES_IN_DAY) { // this day doesn't end at midnight return false; - + } + // we have content available.minutes += day.minutes; available.first = this.mom(day.start); - if(isFirstDay) + if(isFirstDay) { available.last = this.mom(day.end); + } // if current day it needs to go back to // midnight if we want to consider next day - if(isFirstDay && day.startCandle.s !== 0) + if(isFirstDay && day.startCandle.s !== 0) { return false; + } // if not current day, it needs to be full - if(!isFirstDay && !day.full) + if(!isFirstDay && !day.full) { return false; + } // this day is approved, up to next day return true; + + // this inline callback function was a mess + // but at least now passes jslint / jshint + // -- kuzetsa, 2014 June 29 }, this); this.history.available = available; -} +}; @@ -370,7 +404,11 @@ Manager.prototype.processHistoryStats = function() { // how many more minutes do we need? history.toFetch = history.timespan - history.available.minutes; - if(!history.toFetch < 1) { + // (!A < B) is confusing + // (A >= B) is easier to read + // and actually passes jshint / jslint + //-- kuzetsa, 2014 June 29 + if(history.toFetch >= 1) { // we have appear to have full history // though we need to verify on fetch // that we don't have a gap @@ -388,14 +426,15 @@ Manager.prototype.processHistoryStats = function() { completeAt: done }; - if(!history.available.minutes) + if(!history.available.minutes) { state.empty = true; + } this.history.missing = history.toFetch; state = _.extend(state, history.available); this.emit('history state', state); -} +}; Manager.prototype.setFetchMeta = function(data) { @@ -404,7 +443,7 @@ Manager.prototype.setFetchMeta = function(data) { end: this.mom(data.end), next: this.mom(utc().add('ms', data.nextIn)) }; -} +}; // we need to verify after the first fetch @@ -424,14 +463,16 @@ Manager.prototype.checkHistoryAge = function(data) { return; } - if(history.available.last.minute === MINUTES_IN_DAY) + if(history.available.last.minute === MINUTES_IN_DAY) { this.increaseDay(); + } this.minumum = history.available.last.m.clone().add('m', 1); - if(this.minumum > this.fetch.start.m) + if(this.minumum > this.fetch.start.m) { // we're all good, process normally return; + } // there is a gap, mark current day as corrupted and process log.warn('The history we found is to old, we have to build a new one'); @@ -440,26 +481,32 @@ Manager.prototype.checkHistoryAge = function(data) { // reset available history history.available.gap = true; - history.available.minutes = 0 + history.available.minutes = 0; history.available.first = false; history.toFetch = history.timespan; -} +}; // Calculate when we have enough historical data // to provide normal advice Manager.prototype.calculateAdviceTime = function(next, args) { - if(this.state !== 'building history') + if(this.state !== 'building history') { // we are not building history, either // we don't need to or we're already done return next(args); + } var history = this.history; var toFetch = history.toFetch; - if(toFetch < 0) + // this notation seems confusing + // what does "less than zero" + // even mean in this context? + //-- kuzetsa, 2014 June 29 + if(toFetch < 0) { // we have full history return this.broadcastHistory(next, args); + } var doneAt = this.startTime.clone().add('m', toFetch + 1).startOf('minute').local(); // we have partly history @@ -474,19 +521,24 @@ Manager.prototype.calculateAdviceTime = function(next, args) { ); next(args); -} +}; // broadcast full history Manager.prototype.broadcastHistory = function(next, args) { var history = this.history; - if(history.available.last) + //variable declaration style cleanup + //to make sure we pass jslint / jshint + var last = {}; + var m = {}; + + if(history.available.last) { // first run - var last = this.mom(history.available.last.m); - else { + last = this.mom(history.available.last.m); + } else { // after filling full history while fetching live - var m = this.minuteToMoment(this.mostRecentCandle.s, this.current.day) - var last = this.mom(m); + m = this.minuteToMoment(this.mostRecentCandle.s, this.current.day); + last = this.mom(m); } var first = this.mom(last.m.clone().subtract('m', history.timespan)); @@ -505,21 +557,24 @@ Manager.prototype.broadcastHistory = function(next, args) { var from = 0; var to = MINUTES_IN_DAY; - if(equals(mom.day, last.day)) + if(equals(mom.day, last.day)) { // on first (most recent) day to = last.minute; + } - if(equals(mom.day, first.day)) + if(equals(mom.day, first.day)) { // on last (oldest) day from = first.minute; + } this.getCandles(mom, from, to, next); - } + }; // emit all candles together var emit = function(err, batches) { - if(err || !batches) + if(err || !batches) { throw err; + } // transport batches = _.map(batches, function(batch, i) { @@ -539,14 +594,14 @@ Manager.prototype.broadcastHistory = function(next, args) { }); next(args); - } + }; async.map( days, _.bind(iterator, this), _.bind(emit, this) ); -} +}; // grab candles in a specific day // @@ -563,7 +618,7 @@ Manager.prototype.getCandles = function(mom, from, to, cb) { $gte: from } }, cb); -} +}; // grab a batch of trades and for each full minute // create a new candle @@ -571,8 +626,9 @@ Manager.prototype.processTrades = function(data) { this.setFetchMeta(data); - if(this.fetch.start.day > this.current.day) + if(this.fetch.start.day > this.current.day) { util.die('FATAL: Fetch data appears from the future, don\'t know how to process'); + } // if first run if(!this.minumum) { @@ -584,6 +640,7 @@ Manager.prototype.processTrades = function(data) { this.calculateAdviceTime(this.processTrades, data); return; } + var trades = this.filterTrades(data.all); if(!_.size(trades)) { @@ -601,12 +658,19 @@ Manager.prototype.processTrades = function(data) { var candles = this.calculateCandles(trades); + // variable declaration style cleanup + // to make sure we pass jslint / jshint + var ghostCandle = {}; + var batch = {}; + var batches = []; + if(this.fetchHasNewDay()) { // multple days - var batches = this.splitCandleDays(candles); + batches = this.splitCandleDays(candles); - if(_.size(batches) > 2) + if(_.size(batches) > 2) { util.error('More than 2 days of new data per fetch not supported'); + } // we are dealing with two days log.debug('fetch spans midnight'); @@ -618,7 +682,7 @@ Manager.prototype.processTrades = function(data) { // we have to create a fake candle so we can fill gap // after midnight with empty candles reflecting last price - var ghostCandle = _.clone(_.last(batches[0])); + ghostCandle = _.clone(_.last(batches[0])); ghostCandle.s = -1; batches[1] = this.addEmtpyCandles(_.last(batches), ghostCandle); @@ -632,37 +696,44 @@ Manager.prototype.processTrades = function(data) { // but if the most recent candle is from yesterday ... if(this.mostRecentCandle && this.mostRecentCandle.s === MINUTES_IN_DAY) { - var ghostCandle = _.clone(this.mostRecentCandle); + ghostCandle = _.clone(this.mostRecentCandle); ghostCandle.s = -1; - var batch = this.addEmtpyCandles(candles, ghostCandle); - } else - var batch = this.addEmtpyCandles(candles, this.mostRecentCandle); + batch = this.addEmtpyCandles(candles, ghostCandle); + } else { + batch = this.addEmtpyCandles(candles, this.mostRecentCandle); + } this.leftovers = batch.pop(); - var batches = [ batch ]; - }; + batches = [ batch ]; + } // set threshold for next fetch this.minumum = this.fetch.end.m.clone(); // if we only have leftovers we can skip insert clause - if(_.size(batches) === 1 && _.size(_.first(batches)) === 0) + if(_.size(batches) === 1 && _.size(_.first(batches)) === 0) { return this.finishInsert(); - + } // now we have candles in batches per day, insert and process result async.eachSeries(batches, this.insertCandles, this.finishInsert); -} +}; // filter out all trades that are do not belong to the last candle Manager.prototype.filterTrades = function(trades) { log.debug('minimum trade treshold:', this.minumum.utc().format('YYYY-MM-DD HH:mm:ss'), 'UTC'); - return trades = _.filter(trades, function(trade) { + trades = _.filter(trades, function(trade) { return this.minumum < moment.unix(trade.date).utc(); }, this); -} + + // it is unlear what (when / how) we + // return due to the async callback + //-- kuzetsa, 2014 June 29 + + return trades; +}; // turn a batch of trades into 1m candles. Is sorted as // long as the batch of trades are. @@ -722,7 +793,7 @@ Manager.prototype.calculateCandles = function(trades) { }); return candles; -} +}; // split a batch of candles into seperate arrays per day Manager.prototype.splitCandleDays = function(candles) { @@ -730,8 +801,9 @@ Manager.prototype.splitCandleDays = function(candles) { var last = 0; _.each(candles, function(c) { - if(c.s < last) + if(c.s < last) { batches.push([]); + } last = c.s; @@ -739,7 +811,7 @@ Manager.prototype.splitCandleDays = function(candles) { }, this); return batches; -} +}; @@ -761,8 +833,9 @@ Manager.prototype.splitCandleDays = function(candles) { // [midnight up to][batch without gaps][up to next midnight - 1] Manager.prototype.addEmtpyCandles = function(candles, start, end) { var length = _.size(candles); - if(!length) + if(!length) { return candles; + } if(start) { // put the start candle in front @@ -779,17 +852,24 @@ Manager.prototype.addEmtpyCandles = function(candles, start, end) { // if this was last and we don't // have to fill a gap after this batch // we're done - if(i === last && !end) + if(i === last && !end) { return; + } var min = c.s + 1; - if(i === last && end) - var max = end + 1; - else - var max = candles[i + 1].s; + //variable declaration style cleanup + //to make sure we pass jslint / jshint + var max = 0; - var empty, prevClose; + if(i === last && end) { + max = end + 1; + } else { + max = candles[i + 1].s; + } + + var empty = {}; + var prevClose = 0; if(min > max) { console.log('c', candles, 's', start, 'e', end); @@ -801,7 +881,7 @@ Manager.prototype.addEmtpyCandles = function(candles, start, end) { empty.s = min; empty.v = 0; - var prevClose = empty.c; + prevClose = empty.c; // set o, h, l, p to previous // close price empty.o = prevClose; @@ -816,21 +896,23 @@ Manager.prototype.addEmtpyCandles = function(candles, start, end) { // we added start to fill the gap from before // previous candles to this batch - if(start) + if(start) { candles.shift(); + } var all = candles.concat(emptyCandles); return this.sortCandles(all); -} +}; // store a batch of candles under the day specified by // `this.current`. If cb is specified runs this after // inserting all candles (inserts 1 per tick). Manager.prototype.insertCandles = function(candles, cb) { - if(!_.size(candles)) + if(!_.size(candles)) { return cb(); + } // because this function is async make // sure we are using the right day. @@ -845,7 +927,7 @@ Manager.prototype.insertCandles = function(candles, cb) { var iterator = function(c, next) { // async func wrapper to not pass error // and halt the async chain - var _next = function() { next() }; + var _next = function() { next(); }; this.defer(function() { log.debug( @@ -873,15 +955,16 @@ Manager.prototype.insertCandles = function(candles, cb) { next(); }); - } + }; var done = function() { - if(this.processMidnight) + if(this.processMidnight) { this.increaseDay(); + } this.processMidnight = false; cb(); - } + }; this.mostRecentCandle = _.last(candles); @@ -905,17 +988,18 @@ Manager.prototype.insertCandles = function(candles, cb) { ); }, this)); -} +}; // unmount a day from memory and mark the cache // ready for GC Manager.prototype.unloadDay = function(dayString) { - if(!this.days[dayString]) + if(!this.days[dayString]) { return; + } this.days[dayString].mounted = false; this.days[dayString].handle = false; -} +}; Manager.prototype.increaseDay = function() { log.debug('shifting past midnight'); @@ -924,31 +1008,33 @@ Manager.prototype.increaseDay = function() { this.unloadDay(this.current.dayString); this.setDay(this.current.day.clone().add('d', 1)); -} +}; Manager.prototype.finishInsert = function(err, results) { this.emit('processed'); -} +}; // // HELPERS Manager.prototype.setDay = function(m) { this.current = this.mom(m.clone()); -} +}; // how many days are in this trade batch? Manager.prototype.fetchHasNewDay = function() { return !equals(this.fetch.end.day, this.current.day); -} +}; Manager.prototype.sortCandles = function(candles) { return candles.sort(function(a, b) { return a.s - b.s; }); -} +}; Manager.prototype.minuteToMoment = function(minutes, day) { - if(!day) + if(!day) { day = this.current.day; + } + day = day.clone().startOf('day'); return day.clone().add('minutes', minutes); }; @@ -966,12 +1052,13 @@ Manager.prototype.mom = function(m) { minute: this.momentToMinute(m), day: m.clone().startOf('day'), dayString: m.format('YYYY-MM-DD') - } -} + }; +}; Manager.prototype.databaseName = function(mom) { - if(!('dayString' in mom)) + if(!('dayString' in mom)) { mom = this.mom(mom); + } return this.historyPath + [ config.watch.exchange, @@ -979,14 +1066,15 @@ Manager.prototype.databaseName = function(mom) { config.watch.asset, mom.dayString + '.db' ].join('-'); -} +}; // small wrapper around a fake candle // to make it easier to throw them around Manager.prototype.transportCandle = function(c, day) { // check whether we got a mom or a moment - if(!day.m) + if(!day.m) { day = this.mom(day.clone()); + } delete c._id; @@ -994,18 +1082,19 @@ Manager.prototype.transportCandle = function(c, day) { candle: c, time: day.m.clone().add('m', c.s), day: day - } -} + }; +}; + Manager.prototype.transportCandles = function(candles, day) { return _.map(candles, function(c) { return this.transportCandle(c, day); }, this); -} +}; // executes cb on next tick while // maintaining context (to `Manager`). Manager.prototype.defer = function(cb) { return _.defer(_.bind(cb, this)); -} +}; module.exports = Manager; From cd1ec13fb7ce6841b460b1e6b802a2be672d9863 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 29 Jun 2014 23:17:19 -0400 Subject: [PATCH 33/80] MACD variant using Patrick Mulloy's TEMA with secondary mod by kuzetsa, 2014 June 29: inspired by Heikin-Ashi candles NOT QUITE based on xClose average ...weighted geometric mean instead --- methods/indicators/x3EMA.js | 35 ++++++++++++ methods/indicators/x3nikiehihsa.js | 59 +++++++++++++++++++++ methods/x3nikiehihsa.js | 85 ++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 methods/indicators/x3EMA.js create mode 100644 methods/indicators/x3nikiehihsa.js create mode 100644 methods/x3nikiehihsa.js diff --git a/methods/indicators/x3EMA.js b/methods/indicators/x3EMA.js new file mode 100644 index 000000000..448d0ffcb --- /dev/null +++ b/methods/indicators/x3EMA.js @@ -0,0 +1,35 @@ +// Credits for the indicator: +// Patrick Mulloy, 1994 +// name x3EMA means: "triple EMA" AKA "TEMA" + +// (3 * (x1EMA(x) - x1EMA(x1EMA(x)))) + x1EMA(x1EMA(x1EMA(x) +// easier readability: +// (3 * (EMA - EMAprime)) + EMADoublePrime + + +var x1EMA = require('./EMA.js'); + +var Indicator = function(weight) { + this.EMA = new x1EMA(weight); + this.EMAprime = new x1EMA(weight); + this.EMADoublePrime = new x1EMA(weight); + }; + +Indicator.prototype.update = function(weight) { +var EMA = 0.0; +var EMAprime = 0.0; +var EMADoublePrime = 0.0; + + this.EMA.update(weight); + EMA = this.EMA.result; + + this.EMAprime.update(EMA); + EMAprime = this.EMAprime.result; + + this.EMADoublePrime.update(EMAprime); + EMADoublePrime = this.EMADoublePrime.result; + + this.result = (3 * (EMA - EMAprime)) + EMADoublePrime; +}; + +module.exports = Indicator; diff --git a/methods/indicators/x3nikiehihsa.js b/methods/indicators/x3nikiehihsa.js new file mode 100644 index 000000000..1194728c1 --- /dev/null +++ b/methods/indicators/x3nikiehihsa.js @@ -0,0 +1,59 @@ +// MACD variant using Patrick Mulloy's TEMA +// with secondary mod by kuzetsa, 2014 June 29: +// inspired by Heikin-Ashi candles +// NOT QUITE based on xClose average +// ...weighted geometric mean instead + +var x3EMA = require('./x3EMA.js'); + +var Indicator = function(config) { + this.diff = false; + this.shortC = new x3EMA(config.short); + this.longC = new x3EMA(config.long); + this.shortO = new x3EMA(config.short); + this.longO = new x3EMA(config.long); + this.shortL = new x3EMA(config.short); + this.longL = new x3EMA(config.long); + this.shortH = new x3EMA(config.short); + this.longH = new x3EMA(config.long); + this.signal = new x3EMA(config.signal); +}; + +Indicator.prototype.update = function(candle) { + var open = candle.o; + var close = candle.c; + var high = candle.h; + var low = candle.l; + + this.shortC.update(close); + this.longC.update(close); + this.shortO.update(open); + this.longO.update(open); + this.shortH.update(high); + this.longH.update(high); + this.shortL.update(low); + this.longL.update(low); + this.calculateEMAdiff(); + this.signal.update(this.diff); + this.result = this.diff - this.signal.result; +}; + +Indicator.prototype.calculateEMAdiff = function() { + + var Sc = Math.pow(this.shortC.result, 3); + var Lc = Math.pow(this.longC.result, 3); + var So = this.shortO.result; + var Lo = this.longO.result; + var Sh = Math.pow(this.shortH.result, 2); + var Lh = Math.pow(this.longH.result, 2); + var Sl = Math.pow(this.shortL.result, 2); + var Ll = Math.pow(this.longL.result, 2); + var eightroot = 1 / 8; + + var short = Math.pow((Sc * So * Sh * Sl), eightroot); + var long = Math.pow((Lc * Lo * Lh * Ll), eightroot); + + this.diff = short - long; +}; + +module.exports = Indicator; diff --git a/methods/x3nikiehihsa.js b/methods/x3nikiehihsa.js new file mode 100644 index 000000000..90febb4f3 --- /dev/null +++ b/methods/x3nikiehihsa.js @@ -0,0 +1,85 @@ +var _ = require('lodash'); +var log = require('../core/log.js'); + +// configuration +var config = require('../core/util.js').getConfig(); +var settings = config.x3nikiehihsa; + +// x3nikiehihsa by kuzetsa, 2014 June 29 +var method = {}; + +// prepare everything our method needs +method.init = function() { + + this.name = 'x3nikiehihsa'; + + this.trend = { + direction: 'none', + duration: 0 + }; + + // how many candles do we need as a base + // before we can start giving advice? + this.requiredHistory = config.tradingAdvisor.historySize; + + // define the indicators we need + this.addIndicator('x3nikiehihsa', 'x3nikiehihsa', settings); + +}; + +// what happens on every new candle? +method.update = function(candle) { + // NOTHING!!! + // (just kidding, OOP takes care of it) +}; + +method.log = function() { + var digits = 8; + var x3nikiehihsa = this.indicators.x3nikiehihsa; + + log.info('\t', '[shortEMA]CLOSE:', x3nikiehihsa.shortC.result.toFixed(digits)); + log.info('\t', '[longEMA]CLOSE:', x3nikiehihsa.longC.result.toFixed(digits)); + log.info('\t', 'macd:', x3nikiehihsa.diff.toFixed(digits)); + log.info('\t', 'signal:', x3nikiehihsa.signal.result.toFixed(digits)); + log.info('\t', 'macdiff:', x3nikiehihsa.result.toFixed(digits)); +}; + +method.check = function() { + var x3nikiehihsa = this.indicators.x3nikiehihsa; + + var macddiff = x3nikiehihsa.result; + // This is not a histogram... + // it's more of an oscillator + // and it's nearly identical to MACD + // ... but it uses the entire candle + + if(macddiff > settings.thresholds.up) { + + // new trend detected + if(this.trend.direction !== 'up') { + // reset the state for the new trend + this.trend = { + duration: 0, + direction: 'up', + }; + } + + this.trend.duration++; + + log.info('In uptrend since', this.trend.duration, 'candle(s)'); + + this.advice('long'); + + } else { + + this.trend = { + duration: 0, + direction: 'lizards', + }; + + this.advice('lizards'); + + } +}; + +module.exports = method; From e784f5c8d3f39a6f76efd98260bc55cdc8f69128 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 29 Jun 2014 23:18:25 -0400 Subject: [PATCH 34/80] config changes to enable x3nikiehihsa method Also, a bit of cleanup to make configs cleaner mostly just following basic jshint/jslint rules --- config.js | 25 +++++++++++++++++----- core/baseTradingMethod.js | 45 +++++++++++++++++++++++++-------------- plugins/tradingAdvisor.js | 10 +++++---- 3 files changed, 55 insertions(+), 25 deletions(-) diff --git a/config.js b/config.js index 51ef81247..6eb5b7e54 100644 --- a/config.js +++ b/config.js @@ -12,7 +12,8 @@ config.history = { // in what directory should Gekko store // and load historical data from? directory: './history/' -} +}; + config.debug = false; // for additional logging / debugging // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -25,7 +26,7 @@ config.watch = { exchange: 'cexio', // 'MtGox', 'BTCe', 'Bitstamp', 'cexio' or 'kraken' currency: 'BTC', asset: 'GHS' -} +}; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CONFIGURING TRADING ADVICE @@ -33,10 +34,10 @@ config.watch = { config.tradingAdvisor = { enabled: true, - method: 'nikiehihsa', + method: 'x3nikiehihsa', candleSize: 1, - historySize: 239 -} + historySize: 887 +}; // Exponential Moving Averages settings: config.DEMA = { @@ -94,6 +95,20 @@ config.nikiehihsa = { } }; +// x3nikiehihsa settings: +config.x3nikiehihsa = { + // EMA weight (α) + // the higher the weight, the more smooth (and delayed) the line + short: 53, + long: 109, + signal: 41, + // the difference between the EMAs (to act as triggers) + thresholds: { + down: -9999, + up: 0.00000001, + } +}; + // PPO settings: config.PPO = { // EMA weight (α) diff --git a/core/baseTradingMethod.js b/core/baseTradingMethod.js index 8f661208f..c9e0beb31 100644 --- a/core/baseTradingMethod.js +++ b/core/baseTradingMethod.js @@ -6,10 +6,14 @@ var log = require('../core/log.js'); var indicatorsPath = '../methods/indicators/'; var Indicators = { - nikiehihsa: { - factory: require(indicatorsPath + 'nikiehihsa'), + x3nikiehihsa: { + factory: require(indicatorsPath + 'x3nikiehihsa'), input: 'candle' }, + nikiehihsa: { + factory: require(indicatorsPath + 'nikiehihsa'), + input: 'candle' + }, x2MACD: { factory: require(indicatorsPath + 'x2MACD'), input: 'price' @@ -48,12 +52,14 @@ var Base = function() { // make sure we have all methods _.each(['init', 'check'], function(fn) { - if(!this[fn]) - util.die('No ' + fn + ' function in this trading method found.') + if(!this[fn]) { + util.die('No ' + fn + ' function in this trading method found.'); + } }, this); - if(!this.update) + if(!this.update) { this.update = function() {}; + } // let's run the implemented starting point this.init(); @@ -61,17 +67,18 @@ var Base = function() { // should be set up now, check some things // to make sure everything is implemented // correctly. - if(this.name) + if(this.name) { log.info('\t', 'Using trading method:', this.name); - else + } else { log.warn('\t', 'Warning, trading method has no name'); + } // if(!config.debug || !this.log) // DO NOT nuke the log for MACD logging, let line-by-line tweaks happen // this.log = function() {}; this.setup = true; -} +}; // teach our base trading method events var Util = require('util'); @@ -84,10 +91,13 @@ Base.prototype.tick = function(candle) { // update all indicators var price = candle[this.priceValue]; _.each(this.indicators, function(i) { - if(i.input === 'price') + if(i.input === 'price') { i.update(price); - if(i.input === 'candle') + } + + if(i.input === 'candle') { i.update(candle); + } }); this.update(candle); @@ -99,29 +109,32 @@ Base.prototype.tick = function(candle) { // update previous price this.lastPrice = price; -} +}; Base.prototype.addIndicator = function(name, type, parameters) { - if(!_.contains(allowedIndicators, type)) + if(!_.contains(allowedIndicators, type)) { util.die('I do not know the indicator ' + type); + } - if(this.setup) + if(this.setup) { util.die('Can only add indicators in the init method!'); + } this.indicators[name] = new Indicators[type].factory(parameters); // some indicators need a price stream, others need full candles this.indicators[name].input = Indicators[type].input; -} +}; Base.prototype.advice = function(newPosition) { - if(!newPosition) + if(!newPosition) { return this.emit('soft advice'); + } this.emit('advice', { recommandation: newPosition, portfolio: 1 }); -} +}; module.exports = Base; diff --git a/plugins/tradingAdvisor.js b/plugins/tradingAdvisor.js index 8d19ad824..87a353a11 100644 --- a/plugins/tradingAdvisor.js +++ b/plugins/tradingAdvisor.js @@ -8,6 +8,7 @@ var methods = [ 'MACD', 'x2MACD', 'nikiehihsa', + 'x3nikiehihsa', 'DEMA', 'PPO', 'RSI', @@ -19,8 +20,9 @@ var Actor = function() { var methodName = config.tradingAdvisor.method; - if(!_.contains(methods, methodName)) + if(!_.contains(methods, methodName)) { util.die('Gekko doesn\'t know the method ' + methodName); + } var Consultant = require('../core/baseTradingMethod'); @@ -32,11 +34,11 @@ var Actor = function() { Consultant.prototype[name] = fn; }); - this.method = new Consultant; -} + this.method = new Consultant(); +}; Actor.prototype.processCandle = function(candle) { this.method.tick(candle); -} +}; module.exports = Actor; From 17e971c6984a9bd2e42ebd40dd443b2dcbc79c1a Mon Sep 17 00:00:00 2001 From: kuzetsa Date: Mon, 30 Jun 2014 15:10:33 -0400 Subject: [PATCH 35/80] Credit for the fancy zero-lag EMA This TEMA and DEMA code (*x2EMA and x3EMA*) were published in 1994 Further study and optimization is needed, but they work well. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 520f80281..72c5c2aba 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ If you want to contribute or are interested in how Gekko works: * This project is inspired by the [GoxTradingBot](https://github.com/virtimus/GoxTradingBot/) Chrome plugin (though no code is taken from it). * Original implementation of gekko was written by [Mike van Rossum](https://github.com/askmike/gekko) * After 2014, June 25th, reinvestment-only fork maintained by [Sarah White AKA kuzetsa](https://github.com/kuzetsa/gekko) +* Credits for the reduced-lag EMA methods go to Patrick G. Mulloy for publishing "Smoothing Data With Less Lag" in 1994 ## License From 880d8efd4b9ce6a6df27492a22876b4aafac8529 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Mon, 30 Jun 2014 15:43:51 -0400 Subject: [PATCH 36/80] ZERO-lag trader method yet another MACD based on Mulloy's work from 1994 no warranty, use at your own risk, blah blah blah --- methods/ZERO.js | 85 ++++++++++++++++++++++++++++++++++++++ methods/indicators/ZERO.js | 64 ++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 methods/ZERO.js create mode 100644 methods/indicators/ZERO.js diff --git a/methods/ZERO.js b/methods/ZERO.js new file mode 100644 index 000000000..408c5c34c --- /dev/null +++ b/methods/ZERO.js @@ -0,0 +1,85 @@ +var _ = require('lodash'); +var log = require('../core/log.js'); + +// configuration +var config = require('../core/util.js').getConfig(); +var settings = config.zero; + +// ZERO-lag MACD variant by kuzetsa, 2014 June 30 +var method = {}; + +// prepare everything our method needs +method.init = function() { + + this.name = 'ZERO'; + + this.trend = { + direction: 'none', + duration: 0 + }; + + // how many candles do we need as a base + // before we can start giving advice? + this.requiredHistory = config.tradingAdvisor.historySize; + + // define the indicators we need + this.addIndicator('zero', 'ZERO', settings); + +}; + +// what happens on every new candle? +method.update = function(candle) { + // NOTHING!!! + // (just kidding, OOP takes care of it) +}; + +method.log = function() { + var digits = 8; + var zero = this.indicators.zero; + + log.info('\t', '[shortEMA]CLOSE:', zero.shortC.result.toFixed(digits)); + log.info('\t', '[longEMA]CLOSE:', zero.longC.result.toFixed(digits)); + log.info('\t', 'macd:', zero.diff.toFixed(digits)); + log.info('\t', 'signal:', zero.signal.result.toFixed(digits)); + log.info('\t', 'macdiff:', zero.result.toFixed(digits)); +}; + +method.check = function() { + var zero = this.indicators.zero; + + var macddiff = zero.result; + // This is not a histogram... + // it's more of an oscillator + // and it's nearly identical to MACD + // ... but it uses the entire candle + + if(macddiff > settings.thresholds.up) { + + // new trend detected + if(this.trend.direction !== 'up') { + // reset the state for the new trend + this.trend = { + duration: 0, + direction: 'up', + }; + } + + this.trend.duration++; + + log.info('In uptrend since', this.trend.duration, 'candle(s)'); + + this.advice('long'); + + } else { + + this.trend = { + duration: 0, + direction: 'lizards', + }; + + this.advice('lizards'); + + } +}; + +module.exports = method; diff --git a/methods/indicators/ZERO.js b/methods/indicators/ZERO.js new file mode 100644 index 000000000..01035d979 --- /dev/null +++ b/methods/indicators/ZERO.js @@ -0,0 +1,64 @@ +// MACD variant using Patrick Mulloy's ideas +// with additional mod by kuzetsa, 2014 June 30: + +// Heikin-Ashi candles, etc. +// NOT QUITE based on xClose average +// ...weighted geometric mean instead +// this method should have less overshoot +// using ONLY x3EMA was a bit too jumpy + +var x3EMA = require('./x3EMA.js'); +var x2EMA = require('./x2EMA.js'); +var x1EMA = require('./EMA.js'); + +var Indicator = function(config) { + this.diff = false; + this.shortC = new x3EMA(config.short); + this.longC = new x2EMA(config.long); + this.shortO = new x3EMA(config.short); + this.longO = new x2EMA(config.long); + this.shortL = new x3EMA(config.short); + this.longL = new x2EMA(config.long); + this.shortH = new x3EMA(config.short); + this.longH = new x2EMA(config.long); + this.signal = new x1EMA(config.signal); +}; + +Indicator.prototype.update = function(candle) { + var open = candle.o; + var close = candle.c; + var high = candle.h; + var low = candle.l; + + this.shortC.update(close); + this.longC.update(close); + this.shortO.update(open); + this.longO.update(open); + this.shortH.update(high); + this.longH.update(high); + this.shortL.update(low); + this.longL.update(low); + this.calculateEMAdiff(); + this.signal.update(this.diff); + this.result = this.diff - this.signal.result; +}; + +Indicator.prototype.calculateEMAdiff = function() { + + var Sc = Math.pow(this.shortC.result, 3); + var Lc = Math.pow(this.longC.result, 3); + var So = this.shortO.result; + var Lo = this.longO.result; + var Sh = Math.pow(this.shortH.result, 2); + var Lh = Math.pow(this.longH.result, 2); + var Sl = Math.pow(this.shortL.result, 2); + var Ll = Math.pow(this.longL.result, 2); + var eightroot = 1 / 8; + + var short = Math.pow((Sc * So * Sh * Sl), eightroot); + var long = Math.pow((Lc * Lo * Lh * Ll), eightroot); + + this.diff = short - long; +}; + +module.exports = Indicator; From bbcd2045a91404665e5fd400908626778c274ee3 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Mon, 30 Jun 2014 15:45:11 -0400 Subject: [PATCH 37/80] config changes to add the ZERO-lag method This method attempts to make the best use of the each EMA variant, but isn't tuned (yet?) Tinker around with the EMA weights if you want to use it, because these weights are semi-random. Use the values from the default config.js... ... or don't ... or do something else but do so AT YOUR OWN RISK --- config.js | 16 +++++++++++++++- core/baseTradingMethod.js | 4 ++++ plugins/tradingAdvisor.js | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/config.js b/config.js index 6eb5b7e54..0a9f07094 100644 --- a/config.js +++ b/config.js @@ -34,7 +34,7 @@ config.watch = { config.tradingAdvisor = { enabled: true, - method: 'x3nikiehihsa', + method: 'ZERO', candleSize: 1, historySize: 887 }; @@ -109,6 +109,20 @@ config.x3nikiehihsa = { } }; +// ZERO settings: +config.x3nikiehihsa = { + // EMA weight (α) + // the higher the weight, the more smooth (and delayed) the line + short: 43, + long: 83, + signal: 29, + // the difference between the EMAs (to act as triggers) + thresholds: { + down: -9999, + up: 0.00000001, + } +}; + // PPO settings: config.PPO = { // EMA weight (α) diff --git a/core/baseTradingMethod.js b/core/baseTradingMethod.js index c9e0beb31..966f9dd6f 100644 --- a/core/baseTradingMethod.js +++ b/core/baseTradingMethod.js @@ -6,6 +6,10 @@ var log = require('../core/log.js'); var indicatorsPath = '../methods/indicators/'; var Indicators = { + ZERO: { + factory: require(indicatorsPath + 'ZERO'), + input: 'candle' + }, x3nikiehihsa: { factory: require(indicatorsPath + 'x3nikiehihsa'), input: 'candle' diff --git a/plugins/tradingAdvisor.js b/plugins/tradingAdvisor.js index 87a353a11..8cb50b06f 100644 --- a/plugins/tradingAdvisor.js +++ b/plugins/tradingAdvisor.js @@ -5,6 +5,7 @@ var _ = require('lodash'); var config = util.getConfig(); var methods = [ + 'ZERO', 'MACD', 'x2MACD', 'nikiehihsa', From f5436593b4e4bb2cb1b26f5e59b6b3b3aada2979 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Mon, 30 Jun 2014 16:16:20 -0400 Subject: [PATCH 38/80] case sensitivity error I was impatient and didn't test this before pushing --- methods/ZERO.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/methods/ZERO.js b/methods/ZERO.js index 408c5c34c..f183eda9a 100644 --- a/methods/ZERO.js +++ b/methods/ZERO.js @@ -3,7 +3,7 @@ var log = require('../core/log.js'); // configuration var config = require('../core/util.js').getConfig(); -var settings = config.zero; +var settings = config.ZERO; // ZERO-lag MACD variant by kuzetsa, 2014 June 30 var method = {}; From 0ef5105a845858d7d372cf814f1caf2221bc8b0d Mon Sep 17 00:00:00 2001 From: Sarah White Date: Mon, 30 Jun 2014 23:04:50 -0400 Subject: [PATCH 39/80] filter to reduce "false positive" buy signals using ONLY the "macdiff" was a bad design. --- methods/ZERO.js | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/methods/ZERO.js b/methods/ZERO.js index f183eda9a..c5dde2ad9 100644 --- a/methods/ZERO.js +++ b/methods/ZERO.js @@ -5,7 +5,7 @@ var log = require('../core/log.js'); var config = require('../core/util.js').getConfig(); var settings = config.ZERO; -// ZERO-lag MACD variant by kuzetsa, 2014 June 30 +// ZERO-lag MACD variant by kuzetsa, 2014 June/July var method = {}; // prepare everything our method needs @@ -45,34 +45,31 @@ method.log = function() { }; method.check = function() { - var zero = this.indicators.zero; - var macddiff = zero.result; - // This is not a histogram... - // it's more of an oscillator - // and it's nearly identical to MACD - // ... but it uses the entire candle + var zero = this.indicators.zero; + var macd = zero.diff; + var macdiff = zero.result; + var minup = settings.thresholds.up; + var filtered = Math.min(macd, macdiff, minup); - if(macddiff > settings.thresholds.up) { + if(filtered >= minup) { // new trend detected if(this.trend.direction !== 'up') { - // reset the state for the new trend this.trend = { duration: 0, direction: 'up', }; + this.advice('long'); } this.trend.duration++; - log.info('In uptrend since', this.trend.duration, 'candle(s)'); - this.advice('long'); - } else { this.trend = { + // lizard duration always zero duration: 0, direction: 'lizards', }; From 86d21904e9ff99fb27fa6a2938180f8ea7a114c7 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Mon, 30 Jun 2014 23:24:51 -0400 Subject: [PATCH 40/80] workaround for old hardcoded behavior send the "lizard" signal to terminate the "keep buying" logic for cex.io this patch only changes ZERO-lag MACD --- methods/ZERO.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/methods/ZERO.js b/methods/ZERO.js index c5dde2ad9..d071dc7b5 100644 --- a/methods/ZERO.js +++ b/methods/ZERO.js @@ -61,6 +61,14 @@ method.check = function() { direction: 'up', }; this.advice('long'); + } else { + // portfolioManager.js needs audited + // as a workaround, this logic will + // IMMEDIATELY release the lizards + // but it doesn't reset the trend + // ... it's to un-enforce position + // kuzetsa, 2014 June/July + this.advice('lizard'); } this.trend.duration++; From 8907421b72bb66c28e20a09a5f514cc6ddab6087 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Tue, 1 Jul 2014 12:14:20 -0400 Subject: [PATCH 41/80] Fixed ZERO config was reusing some config.js code and messed up the clone forgot to rename the new ZERO section, fixed it now:) --- config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.js b/config.js index 0a9f07094..5538c77ac 100644 --- a/config.js +++ b/config.js @@ -110,7 +110,7 @@ config.x3nikiehihsa = { }; // ZERO settings: -config.x3nikiehihsa = { +config.ZERO = { // EMA weight (α) // the higher the weight, the more smooth (and delayed) the line short: 43, From a77e86cd01abd7cb8a5311cb6be50fe8d7a652c3 Mon Sep 17 00:00:00 2001 From: kuzetsa Date: Wed, 2 Jul 2014 12:01:53 -0400 Subject: [PATCH 42/80] This fork is sublicensed AGPLv3 Code in this fork / repository is copyrighted, and all commits are owned by their respective copyright holders. The initial license chosen (*before I created my fork*) was the MIT license. I, [Sarah White (kuzetsa)](https://github.com/kuzetsa/) have personally opted to use the "sublicensing" right under the permissive MIT license to sublicense my own work. [All of my own code and documentation commits](https://github.com/kuzetsa/gekko/commits?author=kuzetsa) in this project are under the [AGPLv3 license](https://github.com/kuzetsa/gekko/blob/c325e25ba78fd9657dff275a4f901311755e7c67/LICENSE.md) --- This notice is written in markdown, and will be merged into the next commit. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 72c5c2aba..6fdecc2a8 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,8 @@ Copyright (c) 2013-2014 Sarah White (*AKA* **kuzetsa**, *forked 2014 June 25th*) You should have received a copy of the GNU Affero General Public License along with Foobar. If not, see . +--- + #### The MIT License (MIT) Copyright (c) 2014 Mike van Rossum (*original author, initial code in gekkobot*) From e4f4b30811b54ceb72d6a03497d0b21b09ca7a32 Mon Sep 17 00:00:00 2001 From: kuzetsa Date: Wed, 2 Jul 2014 12:14:39 -0400 Subject: [PATCH 43/80] The modifications in this fork are licensed AGPLv3 Code in this fork / repository is copyrighted, and all commits are owned by their respective copyright holders. The initial license chosen (*before I created my fork*) was the MIT license. I am hereby exercising my right under the sublicensing clause of the MIT license to sublicense MY OWN modifications to instead be under the AGPLv3 license. (There may periodically be more notices like this, and/or copyright headers added to the top of source files) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6fdecc2a8..a92629d64 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,8 @@ Copyright (c) 2013-2014 Sarah White (*AKA* **kuzetsa**, *forked 2014 June 25th*) --- +> I, [Sarah White (kuzetsa)](https://github.com/kuzetsa/) have personally opted to use the "sublicensing" right under the permissive MIT license to sublicense my own work. [All of my own code and documentation commits](https://github.com/kuzetsa/gekko/commits?author=kuzetsa) in this project are under the [AGPLv3 license](https://github.com/kuzetsa/gekko/blob/c325e25ba78fd9657dff275a4f901311755e7c67/LICENSE.md) + #### The MIT License (MIT) Copyright (c) 2014 Mike van Rossum (*original author, initial code in gekkobot*) From 6edc3570919a238b76ee5e926940ee6c4d1104db Mon Sep 17 00:00:00 2001 From: Sarah White Date: Wed, 2 Jul 2014 13:44:03 -0400 Subject: [PATCH 44/80] pull in cryptsy API from dowjames the conflicts are going to take a few commits ... should hopefully be resolved and working soon --- exchanges/cryptsy.js | 283 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 exchanges/cryptsy.js diff --git a/exchanges/cryptsy.js b/exchanges/cryptsy.js new file mode 100644 index 000000000..2cb3a021e --- /dev/null +++ b/exchanges/cryptsy.js @@ -0,0 +1,283 @@ +var cryptsy = require("cryptsy-api"); + moment = require('moment'), + async = require('async'), + _ = require('lodash'), + util = require('../core/util'), + log = require('../core/log'); + + +var Trader = function(config) { + this.key = config.key; + this.secret = config.secret; + this.currency = config.currency; + this.asset = config.asset; + this.pair = config.asset.toUpperCase() + config.currency.toUpperCase(); + + if( config.market_id ) + this.market_id = config.market_id; + + this.name = 'Cryptsy'; + + this.cryptsy = new cryptsy( + this.key, + this.secret + ); + + this.market = this.pair; + + _.bindAll(this); +} + + +Trader.prototype.return_trades = function(market, callback) { + + var m_id; + var main_trades; + var client = this.cryptsy; + + //log.debug('client is ', client); + client.getmarketid(market, function(market_id) { + //log.debug('id is', market_id); + // Display user's trades in that market + client.markettrades(market_id, function(trades) { + m_id = market_id; + //log.debug("Grabbing trades for id ", market_id); + if(trades.length) { + //log.debug("There are ", trades.length, 'trades'); + var full_array = []; + //trades = trades.reverse(); + trades.forEach( function(trade) { + // convert to int + trade.amount = Number(trade.quantity); + trade.price = Number(trade.tradeprice); + trade.tid = Number(trade.tradeid); + // ISSUE: this assumes that the local machine is in PDT + trade.date = moment(Date.parse(trade.datetime)).utc().unix(); + full_array.push(trade); + }); + + callback(null, full_array); + } + }); + }); + //this.market_id = m_id; +} + + +Trader.prototype.get_bid_ask = function(market, callback) { + + var m_id; + var main_trades; + var client = this.cryptsy; + + //log.debug('client is ', client); + client.getmarketid(market, function(market_id) { + //log.debug('id is', market_id); + // Display user's trades in that market + client.markettrades(market_id, function(trades) { + //log.debug("Grabbing trades for id ", market_id); + if(trades.length) { + var data_output = { }; + trades = trades.reverse(); + trades.forEach( function(trade) { + // convert to int + if(trade.initiate_ordertype.toLowerCase() == 'sell') { + //log.debug("Sell with initiate_ordertype", trade.initiate_ordertype, 'so using the price as the ask'); + data_output.bid = Number(trade.tradeprice); + } else { + //log.debug("Buy with initiate_ordertype", trade.initiate_ordertype, 'so using the price as the bid'); + data_output.ask = Number(trade.tradeprice); + } + data_output.datetime = trade.datetime; + }); + + callback(null, data_output); + } + }); + }); + //this.market_id = m_id; +} + +Trader.prototype.return_mkt_id = function(market, callback) { + + var client = this.cryptsy; + + //log.debug('client is ', client); + client.getmarketid(market, function(market_id) { + callback(null, market_id); + }); + //this.market_id = m_id; +} + + +Trader.prototype.getTrades = function(since, callback, descending) { + var args = _.toArray(arguments); + var mkt_id = this.market; + + var process = function(err, trades) { + //log.debug("Err is ", err, 'and length of trades is', trades); + if(err || !trades || trades.length === 0) + return this.retry(this.getTrades, args, err); + + var f = parseFloat; + + if(descending) + callback(null, trades); + else + callback(null, trades.reverse()); + }; + + this.return_trades(mkt_id, _.bind(process, this)); + +} + + + +Trader.prototype.buy = function(amount, price, callback) { + + var mkt_name = this.market; + // [MM]: Something about cryptsy's orders seems to be behind the actual market, which causes orders to go unfilled. + // Make the amount slightly on the upside of the actual price. + price = price * 1.003; + + log.debug('BUY', amount, this.asset, ' @', price, this.currency); + this.place_order(mkt_name, 'buy', amount, price, _.bind(callback, this)); +} + + +Trader.prototype.sell = function(amount, price, callback) { + + var mkt_name = this.market; + // [MM]: Something about cryptsy's orders seems to be behind the actual market, which causes orders to go unfilled. + // Make the amount slightly on the downside of the actual price. + price = price * 0.997; + + log.debug('SELL', amount, this.asset, ' @', price, this.currency); + this.place_order(mkt_name, 'sell', amount, price, _.bind(callback, this)); +} + + +Trader.prototype.place_order = function(market_name, trans_type, amount, price, callback) { + + var client = this.cryptsy; + + //log.debug(trans_type, 'order placed for ', amount, this.asset, ' @', price, this.currency); + + //log.debug('client is ', client); + client.getmarketid(market_name, function(market_id) { + //log.debug('id is', market_id); + client.createorder(market_id, trans_type, amount, price, function(orderid) { + callback(null, orderid); + + }); + }); +} + + +Trader.prototype.retry = function(method, args, err) { + var wait = +moment.duration(10, 'seconds'); + log.debug(this.name, 'returned an error in method', method.name, ', retrying..', err, 'waiting for', wait, 'ms'); + + if (!_.isFunction(method)) { + log.error(this.name, 'failed to retry, no method supplied.'); + return; + } + + var self = this; + + // make sure the callback (and any other fn) + // is bound to Trader + _.each(args, function(arg, i) { + if(_.isFunction(arg)) + args[i] = _.bind(arg, self); + }); + + // run the failed method again with the same + // arguments after wait + setTimeout( + function() { method.apply(self, args) }, + wait + ); +} + +Trader.prototype.getPortfolio = function(callback) { + var args = _.toArray(arguments); + var curr_balance, asst_balance; + var curr = this.currency; + var asst = this.asset; + + var calculate = function(data) { + if(!data) + return this.retry(this.getPortfolio, args, null); + balances = data.balances_available; + holds = data.balances_hold; + + curr_balance = parseFloat(balances[curr]) + asst_balance = parseFloat(balances[asst]); +/* + if(holds) { + if(parseFloat(holds[curr])){ + curr_balance -= parseFloat(holds[curr]) + } + + if( parseFloat(holds[asst])){ + asst_balance -= parseFloat(holds[asst]); + } + } +*/ + var portfolio = []; + portfolio.push({name: curr, amount: curr_balance}); + portfolio.push({name: asst, amount: asst_balance}); + callback(null, portfolio); + } + + this.cryptsy.getinfo(_.bind(calculate, this)); +} + +Trader.prototype.getTicker = function(callback) { + + var mkt_name = this.market; + var set = function(err, data) { + log.debug('Timestamp is', data.datetime, 'with bid ', data.bid, 'and ask ', data.ask); + var ticker = { + ask: data.ask, + bid: data.bid + }; + callback(err, ticker); + } + this.get_bid_ask(mkt_name, _.bind(set, this)); +} + +Trader.prototype.getFee = function(callback) { + callback(false, 0.0025); +} + +Trader.prototype.checkOrder = function(order, callback) { + var check = function(err, result) { + + if(err) + callback(false, true); + + var exists = false; + _.forEach(result, function(entry) { + if(entry.orderid === order) { + exists = true; return; + } + }); + callback(err, !exists); + }; + + this.cryptsy.allmyorders(_.bind(check, this)); +} + +Trader.prototype.cancelOrder = function(order) { + var check= function(err, result) { + if(err) + log.error('cancel order failed:', err); + if(typeof(result) !== 'undefined' && result.error) + log.error('cancel order failed:', result.error); + } + this.cryptsy.cancelorder(order, check); +} + +module.exports = Trader; From 3d66a2daecb853524719c67bdbd76d57c39ea402 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Wed, 2 Jul 2014 16:32:18 -0400 Subject: [PATCH 45/80] fixed typos when version number is wrong --- gekko.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gekko.js b/gekko.js index 8fa5b5f80..5df7fb1f8 100644 --- a/gekko.js +++ b/gekko.js @@ -34,10 +34,10 @@ if(util.getArgument('v')) { // make sure the current node version is recent enough if(!util.recentNode()) util.die([ - 'Your local version of nodejs is to old. ', + 'Your local version of nodejs is too old. ', 'You have ', process.version, - ' and you need atleast ', + ' and you need at least ', util.getRequiredNodeVersion() ].join('')); @@ -245,4 +245,4 @@ async.series( // everything is setup! emitters.market.start(); } -); \ No newline at end of file +); From c98df8cb8c0393a54f5f546ff79fef9829a6668e Mon Sep 17 00:00:00 2001 From: Sarah White Date: Thu, 3 Jul 2014 22:50:36 -0400 Subject: [PATCH 46/80] Only buy new GHS once per day This is just a change of the EMA weights --- config.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/config.js b/config.js index 5538c77ac..3d9785ec4 100644 --- a/config.js +++ b/config.js @@ -32,11 +32,15 @@ config.watch = { // CONFIGURING TRADING ADVICE // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// value of p+12 where (p,p+6,p+12) are all prime +// sexy prime triple: 1427, 1433, 1439 :) +// 1429 is also prime (1440 minutes per day) + config.tradingAdvisor = { enabled: true, method: 'ZERO', candleSize: 1, - historySize: 887 + historySize: 1439 }; // Exponential Moving Averages settings: @@ -109,13 +113,16 @@ config.x3nikiehihsa = { } }; +// value of p+12 where (p,p+6,p+12) are all prime +// sexy prime triple: 1427, 1433, 1439 :) +// 1429 is also prime (1440 minutes per day) // ZERO settings: config.ZERO = { // EMA weight (α) // the higher the weight, the more smooth (and delayed) the line - short: 43, - long: 83, - signal: 29, + short: 1427, + long: 1433, + signal: 1429, // the difference between the EMAs (to act as triggers) thresholds: { down: -9999, From d65d80e5f4e79883ce818d3e314239f85cf46e54 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Fri, 4 Jul 2014 03:21:25 -0400 Subject: [PATCH 47/80] removed x1 EMA from zero method it was a failed experiment. only using DEMA and TEMA now --- methods/indicators/ZERO.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/methods/indicators/ZERO.js b/methods/indicators/ZERO.js index 01035d979..9e8d6a736 100644 --- a/methods/indicators/ZERO.js +++ b/methods/indicators/ZERO.js @@ -9,7 +9,6 @@ var x3EMA = require('./x3EMA.js'); var x2EMA = require('./x2EMA.js'); -var x1EMA = require('./EMA.js'); var Indicator = function(config) { this.diff = false; @@ -21,7 +20,7 @@ var Indicator = function(config) { this.longL = new x2EMA(config.long); this.shortH = new x3EMA(config.short); this.longH = new x2EMA(config.long); - this.signal = new x1EMA(config.signal); + this.signal = new x3EMA(config.signal); }; Indicator.prototype.update = function(candle) { From bde6184727695d1c814a14edd8d8d5c49d7af572 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Fri, 4 Jul 2014 11:49:16 -0400 Subject: [PATCH 48/80] future support for LTC/BTC, NMC/BTC, GHS/NMC, IXC/BTC There are still more hardcoded strings to remove... The underlying exchange API already supports these :) --- exchanges/cexio.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/exchanges/cexio.js b/exchanges/cexio.js index bd10f4dd6..0494597ba 100644 --- a/exchanges/cexio.js +++ b/exchanges/cexio.js @@ -9,7 +9,9 @@ var Trader = function(config) { this.user = config.username; this.key = config.key; this.secret = config.secret; - this.pair = 'ghs_' + config.currency.toLowerCase(); + this.currency = config.currency.toUpperCase(); + this.asset = config.asset.toUpperCase(); + this.pair = this.asset + '_' + this.currency; this.name = 'cex.io'; this.cexio = new CEXio( @@ -46,7 +48,7 @@ Trader.prototype.buy = function(amount, price, callback) { amount = Math.floor(amount); amount /= 100000000; - log.debug('BUY', amount, 'GHS @', price, 'BTC'); + log.debug('BUY', amount, this.asset, '@', price, this.currency); var set = function(err, data) { if(err) @@ -71,7 +73,7 @@ Trader.prototype.sell = function(amount, price, callback) { // test placing orders which will not be filled //price *= 10; price = price.toFixed(8); - log.debug('SELL', amount, 'GHS @', price, 'BTC'); + log.debug('SELL', amount, this.asset, '@', price, this.currency); var set = function(err, data) { if(err) From f69acaa24bb8fa9cd71b6e3ded80c17e73a98074 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sat, 5 Jul 2014 00:05:27 -0400 Subject: [PATCH 49/80] ZERO method now has a crystal ball divination to predict the future... calculus is magic? (it's just metaphor) --- config.js | 2 ++ methods/ZERO.js | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/config.js b/config.js index 3d9785ec4..dfa41bb89 100644 --- a/config.js +++ b/config.js @@ -123,6 +123,8 @@ config.ZERO = { short: 1427, long: 1433, signal: 1429, + // how optimistic is the MACD extrapolation going to be? + crystalball: 0.00001234, // the difference between the EMAs (to act as triggers) thresholds: { down: -9999, diff --git a/methods/ZERO.js b/methods/ZERO.js index d071dc7b5..5eda1b350 100644 --- a/methods/ZERO.js +++ b/methods/ZERO.js @@ -50,7 +50,8 @@ method.check = function() { var macd = zero.diff; var macdiff = zero.result; var minup = settings.thresholds.up; - var filtered = Math.min(macd, macdiff, minup); + var divination = macd + settings.crystalball; + var filtered = Math.min(macd, macdiff, divination); if(filtered >= minup) { From 7c7b9b514b6312b9b2e08761da00e17e3e34d8ab Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sat, 5 Jul 2014 11:27:18 -0400 Subject: [PATCH 50/80] (fix) ZERO method now has working crystal ball previous commit didn't actually work... the macd value needed to be obsoleted --- methods/ZERO.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/methods/ZERO.js b/methods/ZERO.js index 5eda1b350..7cc5a5178 100644 --- a/methods/ZERO.js +++ b/methods/ZERO.js @@ -50,8 +50,9 @@ method.check = function() { var macd = zero.diff; var macdiff = zero.result; var minup = settings.thresholds.up; + // "divination" lets the signal happen sooner var divination = macd + settings.crystalball; - var filtered = Math.min(macd, macdiff, divination); + var filtered = Math.min(macdiff, divination); if(filtered >= minup) { From d61cc6bbe6939d9d98218459a8ef1fa140443b95 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sat, 5 Jul 2014 14:03:11 -0400 Subject: [PATCH 51/80] candleStore.js audit & refactor (ECMA compliance) No changes to the logic were made in this commit. Technically the code executed and ran on node.js... now it passes jshint / jslint and is ECMA compliant. --- core/candleStore.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/core/candleStore.js b/core/candleStore.js index 352ecc358..1789c0672 100644 --- a/core/candleStore.js +++ b/core/candleStore.js @@ -15,7 +15,7 @@ var Day = function(day) { this.state = "uninitialized"; this.candles = []; this.filename = "history-" + day.toString() + ".csv"; -} +}; Day.prototype.addCandles = function(candles) { this.candles = this.candles.concat(candles); @@ -42,12 +42,12 @@ var Store = function() { this.unzip, this.readFile ); -} +}; Store.prototype.openDay = function(day, callback) { // Load only if the open day changed, or we never opened a day - if(this.day == null || day != this.day.day) { - prepareNewDay(day); + if(this.day === null || day !== this.day.day) { + this.prepareNewDay(day); this.loadDay(function(err, candles) { if(!err) { this.day.addCandles(candles); @@ -56,41 +56,41 @@ Store.prototype.openDay = function(day, callback) { callback(err, candles); }); } -} +}; Store.prototype.loadDay = function(day, callback) { this.read(day.filename, function(candles) { callback(null, candles); }); -} +}; Store.prototype.prepareNewDay = function(day) { - if(this.day.state != 'loading') { + if(this.day.state !== 'loading') { // Do we need to keep this.day.state = 'closing'; this.day = new Day(day); } -} +}; // Queue's candles to be added as soon as a day is loaded Store.prototype.addCandles = function(candles) { //NOTE: this.queue is array of arrays. this.queue.push(candles); this.flush(); -} +}; // If there is a day in open state, append all queued candles to it. Store.prototype.flush = function() { //TODO(yin): Help, this.day.state can get easily stuck locked. - if(this.queue.length > 0 && this.day != null && this.day.state = 'open') { + if(this.queue.length > 0 && this.day !== null && this.day.state === 'open') { this.day.addCandles(_.flatten(this.queue)); this.queue = []; this.day.state = 'saving'; this.write(this.day.filename, this.day.candles, function(err) { this.day.state = 'open'; - }) + }); } -} +}; Store.prototype.toCSV = function(file, candles, next) { var csv = _.map(candles, function(properties) { @@ -98,31 +98,31 @@ Store.prototype.toCSV = function(file, candles, next) { }).join('\n'); next(null, file, csv); -} +}; Store.prototype.deflate = function(file, csv, next) { zlib.deflate(csv, function(err, buffer) { next(err, file, buffer); }); -} +}; Store.prototype.writeFile = function(file, gzip, next) { fs.writeFile(this.directory + file, gzip, function(err) { next(err); }); -} +}; Store.prototype.readFile = function(file, next) { fs.readFile(this.directory + file, function(err, buffer) { next(err, buffer); }); -} +}; Store.prototype.unzip = function(buffer, next) { zlib.unzip(buffer, function(err, buffer) { next(err, buffer.toString()); }); -} +}; Store.prototype.toArray = function(csv, next) { var f = parseFloat; @@ -136,13 +136,13 @@ Store.prototype.toArray = function(csv, next) { l: f(l[3]), c: f(l[4]), p: f(l[5]) - } + }; }); next(obj); -} +}; //TODO(yin):Exported for tests -Store.Day = Day +Store.Day = new Day(); module.exports = Store; \ No newline at end of file From 4ef18a95f3d508f8e230f7a70520413c5de23369 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sat, 5 Jul 2014 14:44:55 -0400 Subject: [PATCH 52/80] History database FIXED!!! (older data now works) The problem was the distinction between: "Last minute of the day" compared to "how many minute are in a day" These are not the same concept. Last minute starts 60 seconds before midnight They are numbered 0 to 1439, total is still 1440 This bug is related to an "off by one" error I was really glad to see it was so easy to fix :) --- core/candleManager.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/core/candleManager.js b/core/candleManager.js index 5127290e9..3e35330af 100644 --- a/core/candleManager.js +++ b/core/candleManager.js @@ -68,7 +68,12 @@ var equals = util.equals; // even though we have leap seconds, every // day has the same amount of minutes -var MINUTES_IN_DAY = 1439; + +// these are NOT the same thing!!! +// -- kuzetsa, 2014 July 5th +var MINUTES_IN_DAY = 1440; +var START_OF_LAST_MINUTE_IN_DAY = 1439; + var Manager = function() { _.bindAll(this); @@ -343,8 +348,10 @@ Manager.prototype.checkDaysMeta = function(err, results) { return false; } - if(!isFirstDay && day.endCandle.s !== MINUTES_IN_DAY) { - // this day doesn't end at midnight + if(!isFirstDay && day.endCandle.s !== START_OF_LAST_MINUTE_IN_DAY) { + // the day REALLY DOES end at midnight, however... + // the last minute is 1 minute before midnight + // kuzetsa, 2014 July 5th return false; } @@ -463,7 +470,7 @@ Manager.prototype.checkHistoryAge = function(data) { return; } - if(history.available.last.minute === MINUTES_IN_DAY) { + if(history.available.last.minute === START_OF_LAST_MINUTE_IN_DAY) { this.increaseDay(); } @@ -555,7 +562,7 @@ Manager.prototype.broadcastHistory = function(next, args) { // get the candles for each required day var iterator = function(mom, next) { var from = 0; - var to = MINUTES_IN_DAY; + var to = START_OF_LAST_MINUTE_IN_DAY; if(equals(mom.day, last.day)) { // on first (most recent) day @@ -676,7 +683,7 @@ Manager.prototype.processTrades = function(data) { log.debug('fetch spans midnight'); // old day - batches[0] = this.addEmtpyCandles(_.first(batches), this.mostRecentCandle, MINUTES_IN_DAY); + batches[0] = this.addEmtpyCandles(_.first(batches), this.mostRecentCandle, START_OF_LAST_MINUTE_IN_DAY); // new day @@ -695,7 +702,7 @@ Manager.prototype.processTrades = function(data) { // we already know they are all from current day // but if the most recent candle is from yesterday ... - if(this.mostRecentCandle && this.mostRecentCandle.s === MINUTES_IN_DAY) { + if(this.mostRecentCandle && this.mostRecentCandle.s === START_OF_LAST_MINUTE_IN_DAY) { ghostCandle = _.clone(this.mostRecentCandle); ghostCandle.s = -1; batch = this.addEmtpyCandles(candles, ghostCandle); @@ -828,7 +835,7 @@ Manager.prototype.splitCandleDays = function(candles) { // add empty candles. End is a minute # since midnight. // // for example: -// addEmtpyCandles(candles, 0, MINUTES_IN_DAY) +// addEmtpyCandles(candles, 0, START_OF_LAST_MINUTE_IN_DAY) // would return an array of candles from: // [midnight up to][batch without gaps][up to next midnight - 1] Manager.prototype.addEmtpyCandles = function(candles, start, end) { From 9ebec59e56da3e2f52e9927a6f256d06cf94e958 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 6 Jul 2014 21:44:42 -0400 Subject: [PATCH 53/80] EMA rebalance the main long/short are now TEMA signal line is DEMA --- methods/indicators/ZERO.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/methods/indicators/ZERO.js b/methods/indicators/ZERO.js index 9e8d6a736..fbfc8b71c 100644 --- a/methods/indicators/ZERO.js +++ b/methods/indicators/ZERO.js @@ -13,14 +13,14 @@ var x2EMA = require('./x2EMA.js'); var Indicator = function(config) { this.diff = false; this.shortC = new x3EMA(config.short); - this.longC = new x2EMA(config.long); + this.longC = new x3EMA(config.long); this.shortO = new x3EMA(config.short); - this.longO = new x2EMA(config.long); + this.longO = new x3EMA(config.long); this.shortL = new x3EMA(config.short); - this.longL = new x2EMA(config.long); + this.longL = new x3EMA(config.long); this.shortH = new x3EMA(config.short); - this.longH = new x2EMA(config.long); - this.signal = new x3EMA(config.signal); + this.longH = new x3EMA(config.long); + this.signal = new x2EMA(config.signal); }; Indicator.prototype.update = function(candle) { From bb3a6364ae1b5eaf4aafbd398534cd481db7e340 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Mon, 7 Jul 2014 02:47:22 -0400 Subject: [PATCH 54/80] re-tune ZERO method MACD filtering "crystal ball" happens before EMA cross filter out later signals as "false buy" --- methods/ZERO.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/methods/ZERO.js b/methods/ZERO.js index 7cc5a5178..1ee977f76 100644 --- a/methods/ZERO.js +++ b/methods/ZERO.js @@ -54,7 +54,7 @@ method.check = function() { var divination = macd + settings.crystalball; var filtered = Math.min(macdiff, divination); - if(filtered >= minup) { + if ((filtered >= minup) && (macd <= 0)) { // new trend detected if(this.trend.direction !== 'up') { From 10934990dd9cac3d2a794001cc5d6c1ba12d8686 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Mon, 7 Jul 2014 08:15:06 -0400 Subject: [PATCH 55/80] The ZERO crystalball is now a window MACD has to be within plus or minus by the "crystalball" window. This shouldn't need changed for sells. ... further study is still needed :) --- methods/ZERO.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/methods/ZERO.js b/methods/ZERO.js index 1ee977f76..db6325d18 100644 --- a/methods/ZERO.js +++ b/methods/ZERO.js @@ -51,10 +51,11 @@ method.check = function() { var macdiff = zero.result; var minup = settings.thresholds.up; // "divination" lets the signal happen sooner - var divination = macd + settings.crystalball; + var crystalball = settings.crystalball; + var divination = macd + crystalball; var filtered = Math.min(macdiff, divination); - if ((filtered >= minup) && (macd <= 0)) { + if ((filtered >= minup) && (macd <= crystalball)) { // new trend detected if(this.trend.direction !== 'up') { From a17a2445c1464ee1e47556eac4f7f1d0141bee88 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Mon, 7 Jul 2014 15:20:47 -0400 Subject: [PATCH 56/80] Fix ZERO method to correctly detect uptrend Signal directionality (sign) makes a difference This logic should work better than original MACD --- methods/ZERO.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/methods/ZERO.js b/methods/ZERO.js index db6325d18..7119717b6 100644 --- a/methods/ZERO.js +++ b/methods/ZERO.js @@ -48,6 +48,7 @@ method.check = function() { var zero = this.indicators.zero; var macd = zero.diff; + var signal = zero.signal.result; var macdiff = zero.result; var minup = settings.thresholds.up; // "divination" lets the signal happen sooner @@ -55,7 +56,7 @@ method.check = function() { var divination = macd + crystalball; var filtered = Math.min(macdiff, divination); - if ((filtered >= minup) && (macd <= crystalball)) { + if ((filtered >= minup) && (macd <= crystalball) && (signal < 0)) { // new trend detected if(this.trend.direction !== 'up') { From 4304e8facad1b8a6750bd5ccac4a335295bc2084 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sat, 12 Jul 2014 16:35:11 -0400 Subject: [PATCH 57/80] Window stats "moving average" it's not quite a simple moving average calculates a series of percentile ranks currently: 5/10/25/40/50/60/75/90/95th the "50th" percentile is the median. --- methods/indicators/windowstats.js | 63 +++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 methods/indicators/windowstats.js diff --git a/methods/indicators/windowstats.js b/methods/indicators/windowstats.js new file mode 100644 index 000000000..11d13fc3c --- /dev/null +++ b/methods/indicators/windowstats.js @@ -0,0 +1,63 @@ +// window stats by kuzetsa, 2014 July 12 +// not quite a simple moving average +// contains a series of percentile ranks +// 5/10/25/40/50/60/75/90/95th +// the "50th" percentile is the median. + +var Indicator = function(period) { + this.p5th = 0; + this.p10th = 0; + this.p25th = 0; + this.p40th = 0; + this.p50th = 0; + this.p60th = 0; + this.p75th = 0; + this.p90th = 0; + this.p95th = 0; + + this.period = period; + this.window = []; + this.enough = false; + this.age = 0; +} + +Indicator.prototype.update = function(price) { + if (this.age >= this.period) { + this.enough = true; + } + if (this.enough) { + this.window.splice(0,1); // remove first (oldest) from moving window + } + + this.age++; + this.window.push(price); + this.calculate(price); + + return this.p50th; // return the median as a default +} + +Indicator.prototype.calculate = function() { + var p5th = floor(this.period * (5 / 100)); + var p10th = floor(this.period * (10 / 100)); + var p25th = floor(this.period * (25 / 100)); + var p40th = floor(this.period * (40 / 100)); + var p50th = floor(this.period * (50 / 100)); + var p60th = floor(this.period * (60 / 100)); + var p75th = floor(this.period * (75 / 100)); + var p90th = floor(this.period * (90 / 100)); + var p95th = floor(this.period * (95 / 100)); + + var sorted = this.window; + sorted.sort() + this.p5th = sorted(p5th); + this.p10th = sorted(p10th); + this.p25th = sorted(p25th); + this.p40th = sorted(p40th); + this.p50th = sorted(p50th); + this.p60th = sorted(p60th); + this.p75th = sorted(p75th); + this.p90th = sorted(p90th); + this.p95th = sorted(p95th); +} + +module.exports = Indicator; From 2015dff5bd8d34c89a5e06fb244131e8114ee5e9 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sat, 12 Jul 2014 16:36:12 -0400 Subject: [PATCH 58/80] Add "window stats" to ZERO method there is currently no logic to use it, just the code to do the calculations. This commit will support future logic. --- config.js | 2 ++ methods/indicators/ZERO.js | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/config.js b/config.js index dfa41bb89..97c6c5168 100644 --- a/config.js +++ b/config.js @@ -125,6 +125,8 @@ config.ZERO = { signal: 1429, // how optimistic is the MACD extrapolation going to be? crystalball: 0.00001234, + // how large is the stats window for sanity checking? + window: 4320, // the difference between the EMAs (to act as triggers) thresholds: { down: -9999, diff --git a/methods/indicators/ZERO.js b/methods/indicators/ZERO.js index fbfc8b71c..88d4b67c1 100644 --- a/methods/indicators/ZERO.js +++ b/methods/indicators/ZERO.js @@ -1,5 +1,5 @@ // MACD variant using Patrick Mulloy's ideas -// with additional mod by kuzetsa, 2014 June 30: +// with additional mods by kuzetsa, 2014 June/July: // Heikin-Ashi candles, etc. // NOT QUITE based on xClose average @@ -7,8 +7,14 @@ // this method should have less overshoot // using ONLY x3EMA was a bit too jumpy +// additionally, a new "averaging window" +// this stats window determines percentiles +// currently: 5/10/25/40/50/60/75/90/95th +// "50th" percentile is the median price. + var x3EMA = require('./x3EMA.js'); var x2EMA = require('./x2EMA.js'); +var windowstats = require('./windowstats.js'); var Indicator = function(config) { this.diff = false; @@ -21,6 +27,7 @@ var Indicator = function(config) { this.shortH = new x3EMA(config.short); this.longH = new x3EMA(config.long); this.signal = new x2EMA(config.signal); + this.windowstats = new windowstats(config.window); }; Indicator.prototype.update = function(candle) { @@ -28,7 +35,9 @@ Indicator.prototype.update = function(candle) { var close = candle.c; var high = candle.h; var low = candle.l; + var vwap = candle.p; + this.windowstats.update(vwap); this.shortC.update(close); this.longC.update(close); this.shortO.update(open); From ae6e8ef18a13568b9f1ba147281926b6b8feb0bd Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sat, 12 Jul 2014 16:48:37 -0400 Subject: [PATCH 59/80] correct syntax (node.js arrays & Math object floor method) The code barely looks different, but works now... The following "oops" fixed: array indexes use [] not () in node.js floor() method belongs to the Math object --- methods/indicators/windowstats.js | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/methods/indicators/windowstats.js b/methods/indicators/windowstats.js index 11d13fc3c..22bcfc513 100644 --- a/methods/indicators/windowstats.js +++ b/methods/indicators/windowstats.js @@ -37,27 +37,27 @@ Indicator.prototype.update = function(price) { } Indicator.prototype.calculate = function() { - var p5th = floor(this.period * (5 / 100)); - var p10th = floor(this.period * (10 / 100)); - var p25th = floor(this.period * (25 / 100)); - var p40th = floor(this.period * (40 / 100)); - var p50th = floor(this.period * (50 / 100)); - var p60th = floor(this.period * (60 / 100)); - var p75th = floor(this.period * (75 / 100)); - var p90th = floor(this.period * (90 / 100)); - var p95th = floor(this.period * (95 / 100)); + var p5th = Math.floor(this.period * (5 / 100)); + var p10th = Math.floor(this.period * (10 / 100)); + var p25th = Math.floor(this.period * (25 / 100)); + var p40th = Math.floor(this.period * (40 / 100)); + var p50th = Math.floor(this.period * (50 / 100)); + var p60th = Math.floor(this.period * (60 / 100)); + var p75th = Math.floor(this.period * (75 / 100)); + var p90th = Math.floor(this.period * (90 / 100)); + var p95th = Math.floor(this.period * (95 / 100)); var sorted = this.window; sorted.sort() - this.p5th = sorted(p5th); - this.p10th = sorted(p10th); - this.p25th = sorted(p25th); - this.p40th = sorted(p40th); - this.p50th = sorted(p50th); - this.p60th = sorted(p60th); - this.p75th = sorted(p75th); - this.p90th = sorted(p90th); - this.p95th = sorted(p95th); + this.p5th = sorted[p5th]; + this.p10th = sorted[p10th]; + this.p25th = sorted[p25th]; + this.p40th = sorted[p40th]; + this.p50th = sorted[p50th]; + this.p60th = sorted[p60th]; + this.p75th = sorted[p75th]; + this.p90th = sorted[p90th]; + this.p95th = sorted[p95th]; } module.exports = Indicator; From 2ab8bde058283a1b756380bd2d81a78b21d1216f Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 13 Jul 2014 14:00:14 -0400 Subject: [PATCH 60/80] BinarySearch to speed up the windowstats logic is still not implemented yet... mostly this change is needed because doing a full sort repeatedly is awful and wastes so much time it can sometimes take longer than 1 full minute: UNACCEPTABLE!!! The candles will break if anything takes longer than one minute, so optimization is required. The binary search will allow an incremental sort whereby the new value is inserted into the correct place. --- methods/indicators/BinarySearch.js | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 methods/indicators/BinarySearch.js diff --git a/methods/indicators/BinarySearch.js b/methods/indicators/BinarySearch.js new file mode 100644 index 000000000..6cdbc0d77 --- /dev/null +++ b/methods/indicators/BinarySearch.js @@ -0,0 +1,39 @@ +// http://en.wikipedia.org/wiki/Binary_search_algorithm +// this implementation by kuzetsa, 2014 July 13 +// working code has existed for over 50 years +// I'm only mostly sure this is bug-free... + +var BinarySearch = function(arrayToSearch, value) { + // "Array" object primative is case sensitive, + // but just to be clear, CamelCaseVerbosely + + var high = arrayToSearch.length - 1; // off by one + var low = 0; + var pivot = 0; + var WhatIsHere = 0; + + while (low <= high) { + + // It's fast to use >>> operator for an + // unsigned 32-bit binary shift right... + // the number of minutes you can express + // in 32 bits is thousands of YEARS!!! + + /*jshint bitwise: false */ + pivot = low + ((high - low) >>> 1); // was NOT a typo, jshint!!! + WhatIsHere = arrayToSearch[pivot]; + + if (WhatIsHere < value) { + low = pivot + 1; // "lower bound" adjustment + } else if (WhatIsHere > value) { + high = pivot - 1; // "upper bound" adjustment + } else { + return pivot; // neither less, nor greater than. + } + } + return pivot; // high and low crashed into each other + // a best-effort attempt was made, so just go with it + // we're here now, assume it's "close enough", right? +}; + +module.exports = BinarySearch; \ No newline at end of file From 482a410f78110c5198e29eb3c0ea8dd5c826b2d9 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 13 Jul 2014 16:00:05 -0400 Subject: [PATCH 61/80] Binary search for faster window stats "in-place" AKA "incremental sorting" Old code wasted time to do a full sort: http://en.wikipedia.org/wiki/Quicksort full sort is, on average: O(n log n) with a worst case of "quadratic time" http://en.wikipedia.org/wiki/Binary_search_algorithm the new incremental sort is O(log n) AKA logarithmic time, so much faster. algorithm coded 2014 June 13 -- kuzetsa --- methods/indicators/windowstats.js | 70 +++++++++++++++++++------------ 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/methods/indicators/windowstats.js b/methods/indicators/windowstats.js index 22bcfc513..b9fe9efc4 100644 --- a/methods/indicators/windowstats.js +++ b/methods/indicators/windowstats.js @@ -4,7 +4,14 @@ // 5/10/25/40/50/60/75/90/95th // the "50th" percentile is the median. +var BinarySearch = require('./BinarySearch.js'); + var Indicator = function(period) { + this.period = period; + this.window = []; + this.sorted = []; + this.enough = false; + this.age = 0; this.p5th = 0; this.p10th = 0; this.p25th = 0; @@ -14,50 +21,59 @@ var Indicator = function(period) { this.p75th = 0; this.p90th = 0; this.p95th = 0; - - this.period = period; - this.window = []; - this.enough = false; - this.age = 0; + this.p5ndx = Math.floor(this.period * (5 / 100)); + this.p10ndx = Math.floor(this.period * (10 / 100)); + this.p25ndx = Math.floor(this.period * (25 / 100)); + this.p40ndx = Math.floor(this.period * (40 / 100)); + this.p50ndx = Math.floor(this.period * (50 / 100)); + this.p60ndx = Math.floor(this.period * (60 / 100)); + this.p75ndx = Math.floor(this.period * (75 / 100)); + this.p90ndx = Math.floor(this.period * (90 / 100)); + this.p95ndx = Math.floor(this.period * (95 / 100)); } Indicator.prototype.update = function(price) { + var oldest = 0; + var index = 0; + if (this.age >= this.period) { this.enough = true; } + if (this.enough) { - this.window.splice(0,1); // remove first (oldest) from moving window + oldest = this.window[0]; + // remove first (oldest) from... + + // incremental sorting array: + index = BinarySearch(this.window, oldest); + this.sorted.splice(this.sorted, 1); // remove only + + // moving window: + this.window.splice(0, 1); } this.age++; + this.window.push(price); + + index = BinarySearch(this.window, price); + this.sorted.splice(index, 0, price) // insert only + this.calculate(price); return this.p50th; // return the median as a default } Indicator.prototype.calculate = function() { - var p5th = Math.floor(this.period * (5 / 100)); - var p10th = Math.floor(this.period * (10 / 100)); - var p25th = Math.floor(this.period * (25 / 100)); - var p40th = Math.floor(this.period * (40 / 100)); - var p50th = Math.floor(this.period * (50 / 100)); - var p60th = Math.floor(this.period * (60 / 100)); - var p75th = Math.floor(this.period * (75 / 100)); - var p90th = Math.floor(this.period * (90 / 100)); - var p95th = Math.floor(this.period * (95 / 100)); - - var sorted = this.window; - sorted.sort() - this.p5th = sorted[p5th]; - this.p10th = sorted[p10th]; - this.p25th = sorted[p25th]; - this.p40th = sorted[p40th]; - this.p50th = sorted[p50th]; - this.p60th = sorted[p60th]; - this.p75th = sorted[p75th]; - this.p90th = sorted[p90th]; - this.p95th = sorted[p95th]; + this.p5th = this.sorted[this.p5ndx]; + this.p10th = this.sorted[this.p10ndx]; + this.p25th = this.sorted[this.p25ndx]; + this.p40th = this.sorted[this.p40ndx]; + this.p50th = this.sorted[this.p50ndx]; + this.p60th = this.sorted[this.p60ndx]; + this.p75th = this.sorted[this.p75ndx]; + this.p90th = this.sorted[this.p90ndx]; + this.p95th = this.sorted[this.p95ndx]; } module.exports = Indicator; From 2c43465286a8238421dafe296022a85271794ea3 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 13 Jul 2014 16:46:17 -0400 Subject: [PATCH 62/80] windowstats refactor / cleanup just a few stylistic changes jshint is much happier now :) --- methods/indicators/windowstats.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/methods/indicators/windowstats.js b/methods/indicators/windowstats.js index b9fe9efc4..266b01a8a 100644 --- a/methods/indicators/windowstats.js +++ b/methods/indicators/windowstats.js @@ -30,7 +30,7 @@ var Indicator = function(period) { this.p75ndx = Math.floor(this.period * (75 / 100)); this.p90ndx = Math.floor(this.period * (90 / 100)); this.p95ndx = Math.floor(this.period * (95 / 100)); -} +}; Indicator.prototype.update = function(price) { var oldest = 0; @@ -45,7 +45,7 @@ Indicator.prototype.update = function(price) { // remove first (oldest) from... // incremental sorting array: - index = BinarySearch(this.window, oldest); + index = BinarySearch.call(this.window, oldest); this.sorted.splice(this.sorted, 1); // remove only // moving window: @@ -56,13 +56,13 @@ Indicator.prototype.update = function(price) { this.window.push(price); - index = BinarySearch(this.window, price); - this.sorted.splice(index, 0, price) // insert only + index = BinarySearch.call(this.window, price); + this.sorted.splice(index, 0, price); // insert only this.calculate(price); return this.p50th; // return the median as a default -} +}; Indicator.prototype.calculate = function() { this.p5th = this.sorted[this.p5ndx]; @@ -74,6 +74,6 @@ Indicator.prototype.calculate = function() { this.p75th = this.sorted[this.p75ndx]; this.p90th = this.sorted[this.p90ndx]; this.p95th = this.sorted[this.p95ndx]; -} +}; module.exports = Indicator; From 3c44c7505f7df06411061f8a7df2230f5e7f3c1e Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 13 Jul 2014 21:06:37 -0400 Subject: [PATCH 63/80] Debug code to view window stats still no logic to actually use it --- methods/ZERO.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/methods/ZERO.js b/methods/ZERO.js index 7119717b6..9fa34f4bc 100644 --- a/methods/ZERO.js +++ b/methods/ZERO.js @@ -36,6 +36,21 @@ method.update = function(candle) { method.log = function() { var digits = 8; var zero = this.indicators.zero; + var windowstats = zero.windowstats; + + // historical stats for the sanity checking window: + log.debug('(percentiles) window stats:'); + log.debug('\t', '5th:', windowstats.p5th.toFixed(digits)); + log.debug('\t', '10th:', windowstats.p10th.toFixed(digits)); + log.debug('\t', '25th:', windowstats.p25th.toFixed(digits)); + log.debug('\t', '40th:', windowstats.p40th.toFixed(digits)); + log.debug('\t', 'median:', windowstats.p50th.toFixed(digits)); + + // these work, but are irrelevant for buy-and-hold reinvestment: + // log.debug('\t', '60th:', windowstats.p60th.toFixed(digits)); + // log.debug('\t', '75th:', windowstats.p75th.toFixed(digits)); + // log.debug('\t', '90th:', windowstats.p90th.toFixed(digits)); + // log.debug('\t', '95th:', windowstats.p95th.toFixed(digits)); log.info('\t', '[shortEMA]CLOSE:', zero.shortC.result.toFixed(digits)); log.info('\t', '[longEMA]CLOSE:', zero.longC.result.toFixed(digits)); From 34e728e7bf0b001b02902c0f4e893c036ec02c8a Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 13 Jul 2014 21:10:23 -0400 Subject: [PATCH 64/80] Refactor binary search module Algorithm itself is unchanged: This refactor of the code does one thing: Array wrapper to get a per-instance scope --- methods/indicators/BinarySearch.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/methods/indicators/BinarySearch.js b/methods/indicators/BinarySearch.js index 6cdbc0d77..a3394d117 100644 --- a/methods/indicators/BinarySearch.js +++ b/methods/indicators/BinarySearch.js @@ -3,11 +3,15 @@ // working code has existed for over 50 years // I'm only mostly sure this is bug-free... -var BinarySearch = function(arrayToSearch, value) { +var ArrayWrapper = function(arrayToSearch) { // "Array" object primative is case sensitive, // but just to be clear, CamelCaseVerbosely + this.arrayToSearch = arrayToSearch; +}; + +ArrayWrapper.prototype.BinarySearch = function(value) { - var high = arrayToSearch.length - 1; // off by one + var high = this.arrayToSearch.length - 1; // off by one var low = 0; var pivot = 0; var WhatIsHere = 0; @@ -21,7 +25,7 @@ var BinarySearch = function(arrayToSearch, value) { /*jshint bitwise: false */ pivot = low + ((high - low) >>> 1); // was NOT a typo, jshint!!! - WhatIsHere = arrayToSearch[pivot]; + WhatIsHere = this.arrayToSearch[pivot]; if (WhatIsHere < value) { low = pivot + 1; // "lower bound" adjustment @@ -36,4 +40,4 @@ var BinarySearch = function(arrayToSearch, value) { // we're here now, assume it's "close enough", right? }; -module.exports = BinarySearch; \ No newline at end of file +module.exports = ArrayWrapper; From eefcf1db00162500569a9f53ea9e156690229925 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 13 Jul 2014 21:30:01 -0400 Subject: [PATCH 65/80] Refactor to use new BinarySearch wrapper & Sanitize prices The prices sometimes have rounding errors... ... kinda? Javascript's floating point is only 15 digits!!! It's more significant digits than needed. Volume-Weighted Average Price (VWAP) should only need 8 decimal places, more is just going to be that much more likely to have a rounding error or other issue which might cause unspecified behavior to occur. Enough for most things, but let's not push it. Only other changes are a refactor and corected the few spots where the wrong array was referenced. oops. really do need to sort the right array This all works correctly now with good performance: Last thing to do is actually use the new code... "moving median" (and various percentiles) will allow for a good "sanity check" to make sure the prices are actually good before reinvesting :) --- methods/indicators/windowstats.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/methods/indicators/windowstats.js b/methods/indicators/windowstats.js index 266b01a8a..61c7acd2b 100644 --- a/methods/indicators/windowstats.js +++ b/methods/indicators/windowstats.js @@ -35,6 +35,12 @@ var Indicator = function(period) { Indicator.prototype.update = function(price) { var oldest = 0; var index = 0; + var SearchTheArray = new BinarySearch(this.sorted); + + var SanitizedPrice = price; // catch floating point errors + SanitizedPrice *= 100000000; + SanitizedPrice = Math.round(SanitizedPrice); + SanitizedPrice /= 100000000; if (this.age >= this.period) { this.enough = true; @@ -45,21 +51,21 @@ Indicator.prototype.update = function(price) { // remove first (oldest) from... // incremental sorting array: - index = BinarySearch.call(this.window, oldest); - this.sorted.splice(this.sorted, 1); // remove only + index = SearchTheArray.BinarySearch(oldest); + this.sorted.splice(index, 1); // remove only // moving window: - this.window.splice(0, 1); + this.window.shift(); } this.age++; - this.window.push(price); + this.window.push(SanitizedPrice); - index = BinarySearch.call(this.window, price); - this.sorted.splice(index, 0, price); // insert only + index = SearchTheArray.BinarySearch(SanitizedPrice); + this.sorted.splice(index, 0, SanitizedPrice); // insert only - this.calculate(price); + this.calculate(); return this.p50th; // return the median as a default }; From b554b2637585650dc58cdbd699965514bd7306f0 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Sun, 13 Jul 2014 22:40:41 -0400 Subject: [PATCH 66/80] ZERO method sanity check using stats window current "proof of concept" to make sure the price is in the 25th percentile (cheap) before buying config with .window parameter under ZERO section: example: set window to 1440 minutes to use 1 day's data --- methods/ZERO.js | 4 +++- methods/indicators/windowstats.js | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/methods/ZERO.js b/methods/ZERO.js index 9fa34f4bc..85e2de922 100644 --- a/methods/ZERO.js +++ b/methods/ZERO.js @@ -39,6 +39,7 @@ method.log = function() { var windowstats = zero.windowstats; // historical stats for the sanity checking window: + log.debug('\t', 'VWAP:', windowstats.vwap.toFixed(digits)); log.debug('(percentiles) window stats:'); log.debug('\t', '5th:', windowstats.p5th.toFixed(digits)); log.debug('\t', '10th:', windowstats.p10th.toFixed(digits)); @@ -62,6 +63,7 @@ method.log = function() { method.check = function() { var zero = this.indicators.zero; + var windowstats = zero.windowstats; var macd = zero.diff; var signal = zero.signal.result; var macdiff = zero.result; @@ -71,7 +73,7 @@ method.check = function() { var divination = macd + crystalball; var filtered = Math.min(macdiff, divination); - if ((filtered >= minup) && (macd <= crystalball) && (signal < 0)) { + if ((filtered >= minup) && (macd <= crystalball) && (signal < 0) && (windowstats.vwap <= windowstats.p25th)) { // new trend detected if(this.trend.direction !== 'up') { diff --git a/methods/indicators/windowstats.js b/methods/indicators/windowstats.js index 61c7acd2b..37cd63aee 100644 --- a/methods/indicators/windowstats.js +++ b/methods/indicators/windowstats.js @@ -30,6 +30,7 @@ var Indicator = function(period) { this.p75ndx = Math.floor(this.period * (75 / 100)); this.p90ndx = Math.floor(this.period * (90 / 100)); this.p95ndx = Math.floor(this.period * (95 / 100)); + this.vwap = 0; }; Indicator.prototype.update = function(price) { @@ -41,6 +42,7 @@ Indicator.prototype.update = function(price) { SanitizedPrice *= 100000000; SanitizedPrice = Math.round(SanitizedPrice); SanitizedPrice /= 100000000; + this.vwap = SanitizedPrice; if (this.age >= this.period) { this.enough = true; From a9c8a94afa1c079d0f1855953d27019de2b9fd5d Mon Sep 17 00:00:00 2001 From: Sarah White Date: Wed, 16 Jul 2014 00:10:42 -0400 Subject: [PATCH 67/80] windowstats @ 1st, 99th percentile (12th~ish too) --- methods/indicators/windowstats.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/methods/indicators/windowstats.js b/methods/indicators/windowstats.js index 37cd63aee..ad25671df 100644 --- a/methods/indicators/windowstats.js +++ b/methods/indicators/windowstats.js @@ -12,8 +12,10 @@ var Indicator = function(period) { this.sorted = []; this.enough = false; this.age = 0; + this.p1st = 0; this.p5th = 0; this.p10th = 0; + this.pFancyEighth = 0; this.p25th = 0; this.p40th = 0; this.p50th = 0; @@ -21,8 +23,13 @@ var Indicator = function(period) { this.p75th = 0; this.p90th = 0; this.p95th = 0; + this.p99th = 0; + this.p1ndx = Math.floor(this.period * (1 / 100)); this.p5ndx = Math.floor(this.period * (5 / 100)); this.p10ndx = Math.floor(this.period * (10 / 100)); + var FancyEight = (0 - (1 / 3)); // one over cube root 541 + FancyEight = Math.pow(541, FancyEight); // is aprox 1/8th + this.pFancyEightNdx = Math.floor(this.period * FancyEight); this.p25ndx = Math.floor(this.period * (25 / 100)); this.p40ndx = Math.floor(this.period * (40 / 100)); this.p50ndx = Math.floor(this.period * (50 / 100)); @@ -30,6 +37,7 @@ var Indicator = function(period) { this.p75ndx = Math.floor(this.period * (75 / 100)); this.p90ndx = Math.floor(this.period * (90 / 100)); this.p95ndx = Math.floor(this.period * (95 / 100)); + this.p99ndx = Math.floor(this.period * (99 / 100)); this.vwap = 0; }; @@ -73,8 +81,10 @@ Indicator.prototype.update = function(price) { }; Indicator.prototype.calculate = function() { + this.p1st = this.sorted[this.p1ndx]; this.p5th = this.sorted[this.p5ndx]; this.p10th = this.sorted[this.p10ndx]; + this.pFancyEighth = this.sorted[this.pFancyEightNdx]; this.p25th = this.sorted[this.p25ndx]; this.p40th = this.sorted[this.p40ndx]; this.p50th = this.sorted[this.p50ndx]; @@ -82,6 +92,7 @@ Indicator.prototype.calculate = function() { this.p75th = this.sorted[this.p75ndx]; this.p90th = this.sorted[this.p90ndx]; this.p95th = this.sorted[this.p95ndx]; + this.p99th = this.sorted[this.p99ndx]; }; module.exports = Indicator; From b4cae1bb9993d8be2eeaeaf965b98c1ae403f96a Mon Sep 17 00:00:00 2001 From: Sarah White Date: Wed, 16 Jul 2014 00:12:34 -0400 Subject: [PATCH 68/80] ZERO filter much more agressive (buys less often) This is a tweak to the MACD sanity check logic: This ZERO-lag MACD is a momentum trading method, this update re-tunes one of the signal filters The momentum signal is still the main one :) Details: The initial "proof of concept" required price to be at or better than 25th percentile to buy... Changed price filtering to "better than 12th~ish" phrased another way: 75% of the time the price WAS flagged "too high" versus 87.7274659713 percent for the new version --- methods/ZERO.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/methods/ZERO.js b/methods/ZERO.js index 85e2de922..83fcc5498 100644 --- a/methods/ZERO.js +++ b/methods/ZERO.js @@ -41,8 +41,10 @@ method.log = function() { // historical stats for the sanity checking window: log.debug('\t', 'VWAP:', windowstats.vwap.toFixed(digits)); log.debug('(percentiles) window stats:'); + log.debug('\t', '1st:', windowstats.p1st.toFixed(digits)); log.debug('\t', '5th:', windowstats.p5th.toFixed(digits)); log.debug('\t', '10th:', windowstats.p10th.toFixed(digits)); + log.debug('\t', '12th~ish:', windowstats.pFancyEighth.toFixed(digits)); log.debug('\t', '25th:', windowstats.p25th.toFixed(digits)); log.debug('\t', '40th:', windowstats.p40th.toFixed(digits)); log.debug('\t', 'median:', windowstats.p50th.toFixed(digits)); @@ -52,6 +54,7 @@ method.log = function() { // log.debug('\t', '75th:', windowstats.p75th.toFixed(digits)); // log.debug('\t', '90th:', windowstats.p90th.toFixed(digits)); // log.debug('\t', '95th:', windowstats.p95th.toFixed(digits)); + // log.debug('\t', '99th:', windowstats.p99th.toFixed(digits)); log.info('\t', '[shortEMA]CLOSE:', zero.shortC.result.toFixed(digits)); log.info('\t', '[longEMA]CLOSE:', zero.longC.result.toFixed(digits)); @@ -73,7 +76,7 @@ method.check = function() { var divination = macd + crystalball; var filtered = Math.min(macdiff, divination); - if ((filtered >= minup) && (macd <= crystalball) && (signal < 0) && (windowstats.vwap <= windowstats.p25th)) { + if ((filtered >= minup) && (macd <= crystalball) && (signal < 0) && (windowstats.vwap <= windowstats.pFancyEighth)) { // new trend detected if(this.trend.direction !== 'up') { From e80623bb2bcc3569593284821db05259fbc0d2bf Mon Sep 17 00:00:00 2001 From: kuzetsa Date: Wed, 16 Jul 2014 11:14:32 -0400 Subject: [PATCH 69/80] Note about future NMC support & tweaked formatting 100% cosmetic changes... other than the mention of planned NMC support :) --- README.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a92629d64..eee1255bb 100644 --- a/README.md +++ b/README.md @@ -6,21 +6,25 @@ ## Gekko (*this tool / bot*) -This tool is based on [REST APIs](http://en.wikipedia.org/wiki/Representational_state_transfer), and is written the [node.js](https://github.com/joyent/node) dialect of javascript, which itself based on v8, Google's open source JavaScript engine. **WARNING:** *Use Gekko at you own risk.* +This tool is based on [REST APIs](http://en.wikipedia.org/wiki/Representational_state_transfer), and is written the [node.js](https://github.com/joyent/node) dialect of javascript, which itself based on v8, Google's open source JavaScript engine. +

 
 

+**WARNING:** *Use Gekko at you own risk.* -## Automated reinvestment +--- + +### Automated reinvestment Gekko comes with a few [technical analysis (TA)](http://en.wikipedia.org/wiki/Technical_analysis) methods which implement a single indicator (*DEMA, MACD, RSI, and PPO*). The parameters of these indicators are all configurable and changing them changes the outcome drastically. Gekko's interface allows you to add your own trading methods. Read more about that in the [documentation](https://github.com/kuzetsa/gekko/blob/master/docs/internals/trading_methods.md). **Note:** *much of this information is outdated, and/or not relevant to reinvestment-only mode.* -## Supported exchanges +### Supported exchanges Gekko reinvests cloud mining income on the following platforms: -- CEX.io +- CEX.io (*Currently only BTC, planned support for NMC reinvestment.*) -## Plugin interface +### Plugin interface Gekko also has a plugin system that can do certain things whenever something happens or let Gekko communicate through more platforms. Gekko implements the following example plugins: @@ -30,7 +34,7 @@ Gekko also has a plugin system that can do certain things whenever something hap - Profit Simulator (paper trader): Hold a fake portfolio and simulate trades based on advice. - Redis Beacon: Broadcast events propagating through Gekko on [Redis pub/sub](http://redis.io/topics/pubsub). -## Installing Gekko +### Installing Gekko Windows user? Here is a [step-by-step guide](https://github.com/kuzetsa/gekko/blob/master/docs/installing_gekko_on_windows.md) on how to get Gekko running on Windows. @@ -47,11 +51,11 @@ You need to download Gekko's dependencies, which can easily be done with [npm](h npm install ``` -## Configuring Gekko / before you start +### Configuring Gekko / before you start Read the [configuring Gekko documentation](https://github.com/kuzetsa/gekko/tree/master/docs/Configuring_gekko.md) for a detailed explanation. -## Running Gekko +### Running Gekko To run the bot you just have to start Gekko: @@ -61,7 +65,7 @@ To run the bot you just have to start Gekko: You can also run Gekko silently or use more complex features, for examples check out the [advanced features](https://github.com/kuzetsa/gekko/tree/master/docs/Advanced_features.md). -## Updating Gekko +### Updating Gekko If you installed the bot via git you can easily fetch the latest updates by running: @@ -70,7 +74,7 @@ If you installed the bot via git you can easily fetch the latest updates by runn npm update ``` -## How does Gekko work? +### How does Gekko work? If you want to contribute or are interested in how Gekko works: @@ -85,9 +89,9 @@ If you want to contribute or are interested in how Gekko works: * This project is inspired by the [GoxTradingBot](https://github.com/virtimus/GoxTradingBot/) Chrome plugin (though no code is taken from it). * Original implementation of gekko was written by [Mike van Rossum](https://github.com/askmike/gekko) * After 2014, June 25th, reinvestment-only fork maintained by [Sarah White AKA kuzetsa](https://github.com/kuzetsa/gekko) -* Credits for the reduced-lag EMA methods go to Patrick G. Mulloy for publishing "Smoothing Data With Less Lag" in 1994 +* Credits for the reduced-lag EMA methods go to Patrick G. Mulloy for publishing
"Smoothing Data With Less Lag" in 1994 -## License +### License (*Legal rights and restrictions to copy and use this code*) From cf58bb56bdbd31c7c8b85be9f3e41730de93f42e Mon Sep 17 00:00:00 2001 From: kuzetsa Date: Wed, 16 Jul 2014 11:23:15 -0400 Subject: [PATCH 70/80] credit to askmike for bitstamp Fixed usage of ambiguous first person pronoun: "I" --- docs/internals/exchanges.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/internals/exchanges.md b/docs/internals/exchanges.md index b3c62ec89..2a1275308 100644 --- a/docs/internals/exchanges.md +++ b/docs/internals/exchanges.md @@ -8,7 +8,7 @@ Gekko arranges all communication about when assets need to be bought or sold bet When you add a new exchange to Gekko you need to expose an object that has methods to query the exchange. This exchange file needs to reside in `gekko/exchanges` and the filename is the slug of the exchange name + `.js`. So for example the exchange for Mt. Gox is explained in `gekko/exchanges/mtgox.js`. -It is advised to use a npm module to query an exchange. This will seperate the abstract API calls from the Gekko specific stuff (In the case of Bitstamp there was no module yet, so I [created one](https://github.com/askmike/bitstamp)). +It is advised to use a npm module to query an exchange. This will seperate the abstract API calls from the Gekko specific stuff (In the case of Bitstamp, there was no module yet, so askmike [created one](https://github.com/askmike/bitstamp)). Besides this javascript file a new object needs to be added to `gekko/config.js` in the `config.traders` array. Here you can set the default currency and asset that will be available through the exchange. Keep enabled at false, this is up to the end user. From ad46d8e0cdbc6c1cb0b25f660f6da8f0e08fd249 Mon Sep 17 00:00:00 2001 From: kuzetsa Date: Tue, 26 Aug 2014 03:06:35 -0400 Subject: [PATCH 71/80] shorten default zerolag stats window & add comment The stats window SHOULD NOT be larger than your historySize!!! --- config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.js b/config.js index 97c6c5168..cb5bb546d 100644 --- a/config.js +++ b/config.js @@ -40,7 +40,7 @@ config.tradingAdvisor = { enabled: true, method: 'ZERO', candleSize: 1, - historySize: 1439 + historySize: 1440 }; // Exponential Moving Averages settings: @@ -126,7 +126,7 @@ config.ZERO = { // how optimistic is the MACD extrapolation going to be? crystalball: 0.00001234, // how large is the stats window for sanity checking? - window: 4320, + window: 1440, // SHOULD NOT be larger than your historySize!!! // the difference between the EMAs (to act as triggers) thresholds: { down: -9999, From 427e0373d96a864d3afb7e4c0d8c616c36f0552a Mon Sep 17 00:00:00 2001 From: kuzetsa Date: Wed, 3 Sep 2014 03:35:25 -0400 Subject: [PATCH 72/80] fixed lingering "foobar" reference in GPL template no changes to code, just a 1 word cleanup --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eee1255bb..99473749e 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ Copyright (c) 2013-2014 Sarah White (*AKA* **kuzetsa**, *forked 2014 June 25th*) GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License - along with Foobar. If not, see . + along with gekko. If not, see . --- From 1293f3e610562366304e8103e399da1a4e3b9702 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Tue, 11 Nov 2014 09:32:32 -0500 Subject: [PATCH 73/80] sexy primes do not matter there is no reason to use primes (sexy or otherwise) this algorithm doesn't even require integers!!! --- config.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/config.js b/config.js index cb5bb546d..b1f3e505f 100644 --- a/config.js +++ b/config.js @@ -32,10 +32,6 @@ config.watch = { // CONFIGURING TRADING ADVICE // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// value of p+12 where (p,p+6,p+12) are all prime -// sexy prime triple: 1427, 1433, 1439 :) -// 1429 is also prime (1440 minutes per day) - config.tradingAdvisor = { enabled: true, method: 'ZERO', @@ -113,9 +109,6 @@ config.x3nikiehihsa = { } }; -// value of p+12 where (p,p+6,p+12) are all prime -// sexy prime triple: 1427, 1433, 1439 :) -// 1429 is also prime (1440 minutes per day) // ZERO settings: config.ZERO = { // EMA weight (α) From ea246dc11cb5032881fe98ed6fc02e092ed37fc5 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Tue, 11 Nov 2014 09:36:42 -0500 Subject: [PATCH 74/80] removed sexy primes previous commit only removed confusing comments ...note: integers are not required for this algorithm (primes or otherwise) --- config.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config.js b/config.js index b1f3e505f..ddca80934 100644 --- a/config.js +++ b/config.js @@ -113,9 +113,9 @@ config.x3nikiehihsa = { config.ZERO = { // EMA weight (α) // the higher the weight, the more smooth (and delayed) the line - short: 1427, - long: 1433, - signal: 1429, + short: 257.608488, + long: 364.313417, + signal: 225.158074, // how optimistic is the MACD extrapolation going to be? crystalball: 0.00001234, // how large is the stats window for sanity checking? From fb2407b6307caf17d4e3d03a6801976401b2da5a Mon Sep 17 00:00:00 2001 From: Sarah White Date: Tue, 11 Nov 2014 09:41:46 -0500 Subject: [PATCH 75/80] fixed zero with known working config.js these settings are the ones I personally use --- config.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config.js b/config.js index ddca80934..ab9a1eb87 100644 --- a/config.js +++ b/config.js @@ -36,7 +36,7 @@ config.tradingAdvisor = { enabled: true, method: 'ZERO', candleSize: 1, - historySize: 1440 + historySize: 2345 }; // Exponential Moving Averages settings: @@ -117,13 +117,13 @@ config.ZERO = { long: 364.313417, signal: 225.158074, // how optimistic is the MACD extrapolation going to be? - crystalball: 0.00001234, + crystalball: 0.00000173, // how large is the stats window for sanity checking? - window: 1440, // SHOULD NOT be larger than your historySize!!! + window: 2345, // SHOULD NOT be larger than your historySize!!! // the difference between the EMAs (to act as triggers) thresholds: { down: -9999, - up: 0.00000001, + up: 0.00000042, } }; From 468723eadcdf3ab4fe5b39d4ca759268f2f6e188 Mon Sep 17 00:00:00 2001 From: Anton Date: Fri, 25 Apr 2014 16:41:43 -0400 Subject: [PATCH 76/80] Added support for Bitfinex exchange --- exchanges.js | 17 +++++ exchanges/bitfinex.js | 142 ++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 160 insertions(+) create mode 100644 exchanges/bitfinex.js diff --git a/exchanges.js b/exchanges.js index ead0fc2b4..7bdff7431 100644 --- a/exchanges.js +++ b/exchanges.js @@ -283,6 +283,23 @@ var exchanges = [ requires: ['key', 'secret'], monitorError: 'https://github.com/askmike/gekko/issues/210', providesHistory: false + }, + { + name: 'Bitfinex', + slug: 'bitfinex', + direct: false, + infinityOrder: false, + currencies: ['USD'], + assets: ['BTC'], + markets: [ + { + pair: ['USD', 'BTC'], minimalOrder: { amount: 0.01, unit: 'currency' } + } + ], + requires: ['key', 'secret'], + // TODO: should be possible to enable this for Bitfinex? + providesHistory: false + // fetchTimespan: 60 } ]; diff --git a/exchanges/bitfinex.js b/exchanges/bitfinex.js new file mode 100644 index 000000000..6dc4ecb2d --- /dev/null +++ b/exchanges/bitfinex.js @@ -0,0 +1,142 @@ + +var Bitfinex = require("bitfinex"); +var util = require('../core/util.js'); +var _ = require('lodash'); +var moment = require('moment'); +var log = require('../core/log'); + +// Module-wide constants +var exchangeName = 'bitfinex'; +// Bitfinex supports Litecoin, but this module currently only supports Bitcoin +var defaultAsset = 'btcusd'; + +var Trader = function(config) { + _.bindAll(this); + if(_.isObject(config)) { + this.key = config.key; + this.secret = config.secret; + } + this.name = 'Bitfinex'; + this.balance; + this.price; + + this.bitfinex = new Bitfinex(this.key, this.secret); +} + +// if the exchange errors we try the same call again after +// waiting 10 seconds +Trader.prototype.retry = function(method, args) { + var wait = +moment.duration(10, 'seconds'); + log.debug(this.name, 'returned an error, retrying..'); + + var self = this; + + // make sure the callback (and any other fn) + // is bound to Trader + _.each(args, function(arg, i) { + if(_.isFunction(arg)) + args[i] = _.bind(arg, self); + }); + + // run the failed method again with the same + // arguments after wait + setTimeout( + function() { method.apply(self, args) }, + wait + ); +} + +Trader.prototype.getPortfolio = function(callback) { + this.bitfinex.wallet_balances(function (err, data, body) { + var portfolio = _.map(body, function (asset) { + return { + name: asset.currency.toUpperCase(), + // TODO: use .amount instead of .available? + amount: +asset.available + } + }); + callback(err, portfolio); + }); +} + +Trader.prototype.getTicker = function(callback) { + this.bitfinex.ticker(defaultAsset, function (err, data, body) { + var tick = JSON.parse(body); + callback(err, { bid: +tick.bid, ask: +tick.ask }) + }); +} + +// This assumes that only limit orders are being placed, so fees are the +// "maker fee" of 0.1%. It does not take into account volume discounts. +Trader.prototype.getFee = function(callback) { + var makerFee = 0.1; + callback(false, makerFee / 100); +} + +function submit_order(bfx, type, amount, price, callback) { + // TODO: Bitstamp module included the following - is it necessary? + // amount *= 0.995; // remove fees + amount = Math.floor(amount*100000000)/100000000; + bfx.new_order(defaultAsset, amount, price, exchangeName, + type, + 'exchange limit', + function (err, data, body) { + if (err) + return log.error('unable to ' + type, err, body); + + var order = JSON.parse(body); + callback(err, order.order_id); + }); +} + +Trader.prototype.buy = function(amount, price, callback) { + submit_order(this.bitfinex, 'buy', amount, price, callback); + +} + +Trader.prototype.sell = function(amount, price, callback) { + submit_order(this.bitfinex, 'sell', amount, price, callback); +} + +Trader.prototype.checkOrder = function(order_id, callback) { + this.bitfinex.order_status(order_id, function (err, data, body) { + var result = JSON.parse(body); + callback(err, result.is_live); + }); +} + +Trader.prototype.cancelOrder = function(order_id, callback) { + this.bitfinex.cancel_order(order_id, function (err, data, body) { + var result = JSON.parse(body); + if (err || !result || !result.is_cancelled) + log.error('unable to cancel order', order, '(', err, result, ')'); + }); +} + +Trader.prototype.getTrades = function(since, callback, descending) { + var args = _.toArray(arguments); + var self = this; + + // Bitfinex API module does not support start date, but Bitfinex API does. + // Could implement here as in following comments: + // var start = since ? since.unix() : null; + this.bitfinex.trades(defaultAsset, /* start, */ function (err, data, body) { + if (err) + return self.retry(self.getTrades, args); + + var result = JSON.parse(body); + + var trades = _.map(result, function (trade) { + return { + "date": trade.timestamp, + "price": +trade.price, + "amount": +trade.amount // not mentioned in gekko exchange docs + } + }); + + callback(null, descending ? trades : trades.reverse()); + }); +} + +module.exports = Trader; + diff --git a/package.json b/package.json index 2fd9170a7..64a346c2d 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "btc-e": "0.0.x", "cexio": "0.0.x", "bitstamp": "0.1.x", + "bitfinex": "git+https://github.com/naddison36/bitfinex.git" "async": "0.2.x", "nedb": "0.9.4", "line-reader": "0.2.x", From 1092678efb1f7d65b6ceac46df4fe3bf0d876a0a Mon Sep 17 00:00:00 2001 From: Anton Date: Fri, 25 Apr 2014 16:46:49 -0400 Subject: [PATCH 77/80] Fix missing comma for bitfinex --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 64a346c2d..b75be0839 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "btc-e": "0.0.x", "cexio": "0.0.x", "bitstamp": "0.1.x", - "bitfinex": "git+https://github.com/naddison36/bitfinex.git" + "bitfinex": "git+https://github.com/naddison36/bitfinex.git", "async": "0.2.x", "nedb": "0.9.4", "line-reader": "0.2.x", From 92fdaf237f8f53043379923ce0f70f5ee8488195 Mon Sep 17 00:00:00 2001 From: ARD Date: Sun, 12 Oct 2014 23:28:09 -0400 Subject: [PATCH 78/80] Changed debug message to SMS --- plugins/smsPlivo.js | 116 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 plugins/smsPlivo.js diff --git a/plugins/smsPlivo.js b/plugins/smsPlivo.js new file mode 100644 index 000000000..728b4f8e8 --- /dev/null +++ b/plugins/smsPlivo.js @@ -0,0 +1,116 @@ +var _ = require('lodash'); +var plivo = require('plivo'); +var log = require('../core/log.js'); +var util = require('../core/util.js'); +var config = util.getConfig(); +var smsConfig = config.smsPlivo; + +var SMSPlivo = function(done) { + _.bindAll(this); + + this.price = 'N/A'; + this.client; + this.done = done; + this.setup(); +} + +SMSPlivo.prototype.setup = function(done) { + var errors = []; + if(_.isEmpty(smsConfig.to)) + errors.push("No destination number configured for SMS Plivo Config"); + if(_.isEmpty(smsConfig.from)) + errors.push("No sending number configured for SMS Plivo Config"); + if(_.isEmpty(smsConfig.authId)) + errors.push("No AuthId configured for SMS Plivo Config"); + if(_.isEmpty(smsConfig.authToken)) + errors.push("No AuthToken configured for SMS Plivo Config"); + + // init the client... + var api = plivo.RestAPI({ + authId: smsConfig.authId, + authToken: smsConfig.authToken, + }); + this.client = api; + + debugger; + if(smsConfig.sendMailOnStart && errors.length === 0) { + var messageText = [ + "Watching: ", + config.watch.exchange, + ' ', + config.watch.currency, + '/', + config.watch.asset, + ". Advice soon." + ].join(''); + + this.mail( + messageText, + _.bind(function(err) { + this.checkResults(err); + this.done(); + }, this) + ); + + } else if(errors.length !== 0){ + throw new Error(errors); + } else { + this.done(); + } + + log.debug('Setup SMS adviser.'); +} + +SMSPlivo.prototype.mail = function(content, done) { + var self = this; + + function buildMessage(){ + var message = smsConfig.smsPrefix + ' ' + content; + var params = { + 'src': smsConfig.from, // Caller Id + 'dst' : smsConfig.to, // User Number to Call + 'text' : message, + 'type' : "sms", + }; + return params; + } + + self.client.send_message(buildMessage(), function(status, response) { + log.debug('SMS Plivo Sending Status: ', status); + log.debug('SMS Plivo API Response: ', response); + var error = null; + if(status != 202 && status != 200){ + error = response; + self.checkResults(error); + } else { + done(); + } + }); +} + +SMSPlivo.prototype.processTrade = function(trade) { + this.price = trade.price; +} + +SMSPlivo.prototype.processAdvice = function(advice) { + var text = [ + 'Watching ', + config.watch.exchange, + '. New trend, go ', + advice.recommandation, + '.\n\nCurrent ', + config.watch.asset, + ' price is ', + this.price + ].join(''); + this.mail(text); +} + +SMSPlivo.prototype.checkResults = function(err) { + if(err) + log.warn('error sending SMS', err); + else + log.info('Send advice via SMS.'); +} + +module.exports = SMSPlivo; From 2c7b7e912bf7a2e68b101c63392bc2d8c06ff57c Mon Sep 17 00:00:00 2001 From: ARD Date: Mon, 13 Oct 2014 00:46:59 -0400 Subject: [PATCH 79/80] Added debug routes for plugins. --- plugins/mandrillMailer.js | 134 ++++++++++++++++++++++++++++++++++++++ plugins/smsPlivo.js | 30 +++++++-- 2 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 plugins/mandrillMailer.js diff --git a/plugins/mandrillMailer.js b/plugins/mandrillMailer.js new file mode 100644 index 000000000..6d24aa633 --- /dev/null +++ b/plugins/mandrillMailer.js @@ -0,0 +1,134 @@ +var _ = require('lodash'); +var mandrill = require('mandrill-api/mandrill'); +var log = require('../core/log.js'); +var util = require('../core/util.js'); +var config = util.getConfig(); +var mailConfig = config.mandrillMailer; + +var MandrillMailer = function(done) { + _.bindAll(this); + + this.price = 'N/A'; + this.client; + this.done = done; + this.setup(); +} + +MandrillMailer.prototype.setup = function(done) { + var errors = []; + if(_.isEmpty(mailConfig.to)) + errors.push("No destination address configured for Mandrill Mail Config"); + if(_.isEmpty(mailConfig.from)) + errors.push("No sending address configured for Mandrill Mail Config"); + if(_.isEmpty(mailConfig.apiKey)) + errors.push("No API Key configured for Mandrill Mail Config"); + + // init the client... + var mandrill_client = new mandrill.Mandrill(mailConfig.apiKey); + this.client = mandrill_client; + + debugger; + if(mailConfig.sendMailOnStart && errors.length === 0) { + var subject = "Gekko has started"; + var messageText = [ + "I've just started watching ", + config.watch.exchange, + ' ', + config.watch.currency, + '/', + config.watch.asset, + ". I'll let you know when I got some advice" + ].join(''); + + this.mail( + subject, + messageText, + _.bind(function(err) { + this.checkResults(err); + this.done(); + }, this) + ); + + } else if(errors.length !== 0){ + throw new Error(errors); + } else { + this.done(); + } + + if(mailConfig.testProcessAdvice){ + this.testProcessAdvice(); + } + + log.debug('Setup email (Mandrill) adviser successfully.'); +} + +MandrillMailer.prototype.mail = function(subject, content, done) { + var self = this; + var message = { + "text": content, + "to": [{ + "email": mailConfig.to, + "name": mailConfig.toName, + "type": "to" + }], + "from_name": mailConfig.fromName, + "from_email": mailConfig.from, + "subject": mailConfig.tag + subject + }; + + var ip_pool = "Main Pool"; + self.client.messages.send({ + "message": message, + "async": false, + "ip_pool": ip_pool + }, + function(result){ + log.debug("Mail sent successfully via Mandrill: ", result); + if(done){ + done(); + } + }, function(error){ + self.checkResults(error); + if(done){ + done(); + } + }); +} + +MandrillMailer.prototype.processTrade = function(trade) { + this.price = trade.price; +} + +MandrillMailer.prototype.processAdvice = function(advice) { + var text = [ + 'Gekko is watching ', + config.watch.exchange, + ' and has detected a new trend, advice is to go ', + advice.recommandation, + '.\n\nThe current ', + config.watch.asset, + ' price is ', + this.price + ].join(''); + + var subject = 'New advice: go ' + advice.recommandation; + + this.mail(subject, text); +} + +MandrillMailer.prototype.testProcessAdvice = function(){ + var advice = { + recommandation: "short" + }; + this.price = 0; + this.processAdvice(advice); +} + +MandrillMailer.prototype.checkResults = function(err) { + if(err) + log.warn('error sending email', err); + else + log.info('Send advice via email.'); +} + +module.exports = MandrillMailer; diff --git a/plugins/smsPlivo.js b/plugins/smsPlivo.js index 728b4f8e8..0a25f3a62 100644 --- a/plugins/smsPlivo.js +++ b/plugins/smsPlivo.js @@ -57,8 +57,12 @@ SMSPlivo.prototype.setup = function(done) { } else { this.done(); } + + if(smsConfig.testProcessAdvice){ + this.testProcessAdvice(); + } - log.debug('Setup SMS adviser.'); + log.debug('Setup SMS (Plivo) adviser successfully.'); } SMSPlivo.prototype.mail = function(content, done) { @@ -76,14 +80,18 @@ SMSPlivo.prototype.mail = function(content, done) { } self.client.send_message(buildMessage(), function(status, response) { - log.debug('SMS Plivo Sending Status: ', status); - log.debug('SMS Plivo API Response: ', response); - var error = null; + // 202 and 200 are normal status codes from Plivo. if(status != 202 && status != 200){ - error = response; - self.checkResults(error); + // note, reponse will contain status code. + self.checkResults(response); + if(done){ + done(); + } } else { - done(); + log.debug("SMS sent successfully via Plivo: ", response); + if(done){ + done(); + } } }); } @@ -106,6 +114,14 @@ SMSPlivo.prototype.processAdvice = function(advice) { this.mail(text); } +SMSPlivo.prototype.testProcessAdvice = function(){ + var advice = { + recommandation: "short" + }; + this.price = 0; + this.processAdvice(advice); +} + SMSPlivo.prototype.checkResults = function(err) { if(err) log.warn('error sending SMS', err); From 6695f347d139b88fbe61a7b5c0019b0ba8c4cba9 Mon Sep 17 00:00:00 2001 From: Sarah White Date: Tue, 18 Nov 2014 19:16:03 -0500 Subject: [PATCH 80/80] Updated package.json to point at the right repo. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b75be0839..3000495ec 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,6 @@ "engines": { "node": ">=0.10.x" }, "repository": { "type": "git", - "url": "https://github.com/askmike/gekko.git" + "url": "https://github.com/kuzetsa/gekko.git" } }