diff --git a/.gitignore b/.gitignore index 410e87b53..49dedbdb7 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ *.vi *~ *.sass-cache +gekko-conf* + # OS or Editor folders .DS_Store @@ -38,4 +40,5 @@ dwsync.xml node_modules candles.csv cexio.db +cryptsy.db history diff --git a/config.js b/config.js index 30f0e269f..c9280e7cb 100644 --- a/config.js +++ b/config.js @@ -22,9 +22,11 @@ config.debug = false; // 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' + key: '', + secret: '', + currency: 'BTC', + asset: 'DOGE' } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -42,12 +44,12 @@ config.tradingAdvisor = { config.DEMA = { // EMA weight (α) // the higher the weight, the more smooth (and delayed) the line - short: 10, - long: 21, + short: 11, + long: 31, // amount of candles to remember and base initial EMAs on // the difference between the EMAs (to act as triggers) - sellTreshold: -0.025, - buyTreshold: 0.025 + sellTreshold: -0.35, + buyTreshold: 0.25 }; // MACD settings: @@ -88,7 +90,8 @@ config.PPO = { // Enabling this will activate trades for the market being // watched by config.watch config.trader = { - enabled: false, + enabled: true, + tradePercent: 10, key: '', secret: '', username: '' // your username, only fill in when using bitstamp or cexio @@ -112,7 +115,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.03, // how much slippage should Gekko assume per trade? slippage: 0.05 } diff --git a/core/candleManager.js b/core/candleManager.js index 1687fb96a..366829c56 100644 --- a/core/candleManager.js +++ b/core/candleManager.js @@ -793,6 +793,7 @@ Manager.prototype.addEmtpyCandles = function(candles, start, end) { if(min > max) { console.log('c', candles, 's', start, 'e', end); + console.log('min', min, 'max', max); throw 'Weird error 2'; } diff --git a/core/portfolioManager.js b/core/portfolioManager.js index d4af4b203..25579cd39 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; } @@ -122,7 +123,9 @@ Manager.prototype.trade = function(what) { return; 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') { @@ -138,6 +141,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') { @@ -153,6 +161,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); } @@ -183,7 +196,7 @@ Manager.prototype.buy = function(amount, price) { var currency = this.getFund(this.currency); var minimum = this.getMinimum(price); var availabe = this.getBalance(this.currency) / price; - + log.debug('Buying ', amount, 'with ', availabe, 'available, and a minimum of', minimum); // if not suficient funds if(amount > availabe) { return log.info( @@ -195,12 +208,12 @@ Manager.prototype.buy = function(amount, price) { ); } - // if order to small + // if order too small if(amount < minimum) { return log.info( 'wanted to buy', this.asset, - 'but the amount is to small', + 'but the amount is too small', '(' + amount + ')', 'at', this.exchange.name @@ -229,9 +242,9 @@ Manager.prototype.sell = function(amount, price) { var minimum = this.getMinimum(price); var availabe = this.getBalance(this.asset); - + log.debug('Selling ', amount, 'with ', availabe, 'available, and a minimum of', minimum); // if not suficient funds - if(amount < availabe) { + if(amount > availabe) { return log.info( 'wanted to buy but insufficient', this.asset, @@ -241,12 +254,12 @@ Manager.prototype.sell = function(amount, price) { ); } - // if order to small + // if order too small if(amount < minimum) { return log.info( 'wanted to buy', this.currency, - 'but the amount is to small', + 'but the amount is too small', '(' + amount + ')', 'at', this.exchange.name diff --git a/exchanges.js b/exchanges.js index 7158cf259..b7e5ffced 100644 --- a/exchanges.js +++ b/exchanges.js @@ -184,6 +184,27 @@ var exchanges = [ requires: ['key', 'secret', 'username'], providesHistory: false }, + { + name: 'Cryptsy', + slug: 'cryptsy', + direct: false, + infinityOrder: false, + currencies: ['BTC', 'LTC'], + assets: ['DOGE', 'DVC', 'PPC' ], + markets: [ + { + pair: ['BTC', 'DOGE'], market_id: 132, minimalOrder: { amount: 100, unit: 'asset' } + }, + { + pair: ['LTC', 'DOGE'], minimalOrder: { amount: 1, unit: 'asset' } + }, + { + pair: ['BTC', 'DVC'], minimalOrder: { amount: 100, 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..7d5c51830 --- /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.03); +} + +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/methods/DEMA.js b/methods/DEMA.js index c757d4ff9..b26db1442 100644 --- a/methods/DEMA.js +++ b/methods/DEMA.js @@ -51,7 +51,15 @@ TradingMethod.prototype.calculateAdvice = function() { var message = '@ ' + price.toFixed(8) + ' (' + diff.toFixed(5) + ')'; - if(diff > settings.buyTreshold) { + if (this.currentTrend == undefined ) { + // We just started the program and we don't have a trend, so set it and wait until next time. + if (diff > settings.buyTreshold) + this.currentTrend = 'up'; + else + this.currentTrend = 'down'; + this.advice(); + + } else if(diff > settings.buyTreshold) { log.debug('we are currently in uptrend', message); if(this.currentTrend !== 'up') { diff --git a/package.json b/package.json index f96818f5f..52acc1f8d 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "author": "Mike van Rossum ", "dependencies": { + "cryptsy-api": "0.1.x", "mtgox-apiv2": "1.0.x", "lodash": "2.x", "moment": "2.4.x", diff --git a/plugins/trader.js b/plugins/trader.js index 9d9f35e84..c8ebcc4d1 100644 --- a/plugins/trader.js +++ b/plugins/trader.js @@ -16,14 +16,14 @@ Trader.prototype.processAdvice = function(advice) { log.info( 'Trader', 'Received advice to go long', - 'Buying ', config.trader.asset + 'Buying', config.trader.asset ); } else if(advice.recommandation == 'short') { this.manager.trade('SELL'); log.info( 'Trader', 'Received advice to go short', - 'Selling ', config.trader.asset + 'Selling', config.trader.asset ); } }