Skip to content

Commit

Permalink
First shot of cex.io exchange
Browse files Browse the repository at this point in the history
  • Loading branch information
johndoe75 committed Nov 28, 2013
1 parent 4a0f697 commit a1b5b23
Show file tree
Hide file tree
Showing 11 changed files with 313 additions and 17 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ dwsync.xml
# Folders to ignore

node_modules
candles.csv
candles.csv
cexio.db
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Gekko currently supports paper & live trading at the following exchanges:
* [Mt. Gox](https://mtgox.com/)
* [BTC-e](https://btc-e.com/)
* [Bitstamp](https://bitstamp.net)
* [cex.io] (https://cex.io)

### Backtesting

Expand Down
25 changes: 24 additions & 1 deletion config.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ config.normal = {
// password: '',
// }

// example cex.io config;

//config.normal = {
// enabled: true,
// exchange: 'cexio',
// currency: 'BTC', // @ cexio we pay BTC
// asset: 'GHS', // @ cexio we buy GH/s
// tradingEnabled: false,
// username: '', // this option MUST be set for cex.io
// key: '',
// secret: '',
//}


// want Gekko to send a mail on buy or sell advice?
config.mail = {
enabled: false,
Expand Down Expand Up @@ -139,9 +153,18 @@ config.traders = [
currency: 'USD',
asset: 'BTC',
enabled: false
},
{
exchange: 'cex.io',
key: '',
secret: '',
currency: 'BTC',
asset: 'GHS',
enabled: false
}
];

config.debug = false; // for additional logging / debugging

module.exports = config;
module.exports = config;

13 changes: 10 additions & 3 deletions docs/Configuring_gekko.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ The easiest way to configure Gekko is in the normal zone of the config:
secret: '',
}

When using with cex.io marketplace, you need to add option `username` set to the username you're registered with at cex.io.

## Exchanges

Gekko currently supports these exchanges:

* [Mt. Gox](https://mtgox.com) (MtGox)
* [BTC-e](http://btc-e.com) (BTCe)
* [Bitstamp](http://bitstamp.com) (Bitstamp)*
* [BTC-e](https://btc-e.com) (BTCe)
* [Bitstamp](https://bitstamp.com) (Bitstamp)*
* [cex.io] (https://cex.io) (cexio)

You have to tell Gekko what market to monitor on the selected exchange, A market is defined by a `currency` and an `asset`. here are all supported combinations per exchange:

Expand All @@ -32,9 +35,13 @@ You have to tell Gekko what market to monitor on the selected exchange, A market
currencies: USD, EUR, RUR
assets: BTC

* Bitstamp:
* Bitstamp:
currencies: USD
assets: BTC

* cex.io:
currencies: BTC, *NMC (not yet supported)*
assets: GHS

*Even though Bitcoin is a currency Gekko treats is like an asset when you are trading USD vs BTC.*

Expand Down
222 changes: 222 additions & 0 deletions exchanges/cexio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
var cexio = require('cexio'),
moment = require('moment'),
nedb = require('nedb'),
async = require('async'),
db = new nedb({filename: 'cexio.db', autoload: true}),
_ = require('lodash'),
util = require('../util'),
log = require('../log');

var Trader = function(config) {
this.user = config.username;
this.key = config.key;
this.secret = config.secret;
this.pair = 'ghs_' + config.currency.toLowerCase();
this.name = 'cex.io';
this.next_tid = 0;

_.bindAll(this);

this.cexio = new cexio(this.pair, this.user,
this.key, this.secret);
}

Trader.prototype.getTrades = function(since, callback, descending) {
var self = this;
var last_tid = next_tid = 0;

if(since && !_.isNumber(since))
since = util.toMicro(since);

var args = _.toArray(arguments);

// FIXME: fetching and updating our db shall be done in an seperate
// thread (timeout-callback) rather than here. This method shall
// only fetch and return from local db.

async.waterfall([
function(callback) {
db.find({}, function(err, docs) {
if(!docs || docs.length === 0)
tid = 263000;
else
tid = 1 + _.max(docs, 'tid').tid;

//log.info(self.name, 'Updating cex.io historical data store');
log.debug(self.name, 'fetching from tid ' + tid);

self.cexio.trades({since: tid},
function(err, trades) {
if(err || !trades || trades.length === 0)
return self.retry(self.getTrades, args);
else {
trades = trades.reverse();
_.forEach(trades, function(trade) {
// convert to int
trade.amount = Number(trade.amount);
trade.price = Number(trade.price);
trade.tid = Number(trade.tid);
trade.date = Number(trade.date);
db.insert(trade);
});
}
callback();
});
});
},
function(callback) {
if(!since) {
since = new Date().getTime() * 1000;
since -= (10 * 1000 * 1000);
}
since = Math.floor(since / 1000 / 1000);
log.debug('fetching since ' + since);

db.find({'date': {$gte: since}}, function(err, docs) {
docs = _.sortBy(docs, 'tid');
// log.debug(self.name, docs);
if(!docs || docs.length === 0)
return self.retry(self.getTrades, args);
callback(null, docs);
});
}
], function(err, result) {
if(err) return log.error(self.name, 'error: ' + err);
callback(result);
});
}

Trader.prototype.buy = function(amount, price, callback) {
// Prevent "You incorrectly entered one of fields."
// because of more than 8 decimals.
amount *= 100000000;
amount = Math.floor(amount);
amount /= 100000000;

// test placing orders which will not be filled
//price /=10; price = price.toFixed(8);

log.debug('BUY', amount, 'GHS @', price, 'BTC');

var set = function(err, data) {
if(err)
return log.error('unable to buy:', err);
if(data.error)
return log.error('unable to buy:', data.error);

log.debug('BUY order placed. Order ID', data.id);
callback(data.id);
};

this.cexio.place_order('buy', amount, price, _.bind(set, this));
}

Trader.prototype.sell = function(amount, price, callback) {
// Prevent "You incorrectly entered one of fields."
// because of more than 8 decimals.
amount *= 100000000;
amount = Math.ceil(amount);
amount /= 100000000;

// test placing orders which will not be filled
//price *= 10; price = price.toFixed(8);

log.debug('SELL', amount, 'GHS @', price, 'BTC');

var set = function(err, data) {
if(err)
return log.error('unable to sell:', err);
if(data.error)
return log.error('unable to sell:', data.error);

log.debug('SELL order placed. Order ID', data.id);
callback(data.id);
};

this.cexio.place_order('sell', amount, price, _.bind(set, this));
}

Trader.prototype.retry = function(method, args) {
var wait = +moment.duration(10, 'seconds');
log.debug(this.name, 'returned an error, retrying..');

var self = this;

// make sure the callback (and any other fn)
// is bound to Trader
_.each(args, function(arg, i) {
if(_.isFunction(arg))
args[i] = _.bind(arg, self);
});

// run the failed method again with the same
// arguments after wait
setTimeout(
function() { method.apply(self, args) },
wait
);
}

Trader.prototype.getPortfolio = function(callback) {
var calculate = function(err, data) {
if(err)
return this.retry(this.cexio.getInfo, calculate);

currency = parseFloat(data.BTC.available) - parseFloat(data.BTC.orders);
assets = parseFloat(data.GHS.available) - parseFloat(data.GHS.orders);

var portfolio = [];
portfolio.push({name: 'BTC', amount: currency});
portfolio.push({name: 'GHS', amount: assets});
callback(err, portfolio);
}
this.cexio.balance(_.bind(calculate, this));
}

Trader.prototype.getTicker = function(callback) {
var set = function(err, data) {
var ticker = {
ask: data.ask,
bid: data.bid
};
callback(err, ticker);
}
this.cexio.ticker(_.bind(set, this));
}

Trader.prototype.getFee = function(callback) {
// cexio does currently don't take a fee on trades
callback(false, 0.0);
}

Trader.prototype.checkOrder = function(order, callback) {
var check = function(err, result) {

if(err)
callback(false, true);
if(result.error)
callback(false, true);

var exists = false;
_.forEach(result, function(entry) {
if(entry.id === order) {
exists = true; return;
}
});
callback(err, !exists);
};

this.cexio.open_orders(_.bind(check, this));
}

Trader.prototype.cancelOrder = function(order) {
var check= function(err, result) {
if(err)
log.error('cancel order failed:', err);
if(result.error)
log.error('cancel order failed:', result.error);
}
this.cexio.cancel_order(order, check);
}

module.exports = Trader;
3 changes: 2 additions & 1 deletion exchanges/mtgox.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,5 @@ Trader.prototype.getTicker = function(callback) {
this.mtgox.ticker(_.bind(set, this));
}

module.exports = Trader;
module.exports = Trader;

6 changes: 3 additions & 3 deletions logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ Logger.prototype.trackProfits = function(what, price, meta) {

Logger.prototype.finish = function(data) {
var round = function(amount) {
return amount.toFixed(3);
return amount.toFixed(6);
}

console.log();
Expand Down Expand Up @@ -167,7 +167,7 @@ Logger.prototype.finish = function(data) {

Logger.prototype.report = function(timespan) {
var round = function(amount) {
return amount.toFixed(3);
return amount.toFixed(6);
}

log.info(
Expand Down Expand Up @@ -207,4 +207,4 @@ Logger.prototype.report = function(timespan) {



module.exports = Logger;
module.exports = Logger;
17 changes: 14 additions & 3 deletions methods/exponential-moving-averages.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ TradingMethod.prototype.calculateEMAs = function() {

log.debug('calced EMA properties for new candle:');
_.each(['short', 'long', 'diff'], function(e) {
log.debug('\t', e, 'ema', _.last(this.ema[e]).toFixed(3));
if(config.normal.exchange === 'cexio')
log.debug('\t', e, 'ema', _.last(this.ema[e]).toFixed(8));
else
log.debug('\t', e, 'ema', _.last(this.ema[e]).toFixed(3));
}, this);

if(!this.fetchingHistorical)
Expand Down Expand Up @@ -126,9 +129,17 @@ TradingMethod.prototype.calculateEMAdiff = function() {
}

TradingMethod.prototype.advice = function() {
var diff = _.last(this.ema.diff).toFixed(3);
var price = _.last(this.candles.close).toFixed(3);
// @ cexio we need to be more precise due to low prices
// and low margins on trade. All others use 3 digist.

var diff = _.last(this.ema.diff).toFixed(3),
price = _.last(this.candles.close).toFixed(8);

if(config.normal.exchange !== 'cexio')
price = price.toFixed(3);

var message = '@ ' + price + ' (' + diff + ')';

if(config.backtest.enabled)
message += '\tat \t' + moment.unix(this.currentTimestamp).format('YYYY-MM-DD HH:mm:ss');

Expand Down
2 changes: 1 addition & 1 deletion methods/realtime-candle-fetcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,4 @@ CandleCalculator.prototype.calculateCandle = function() {
this.emit('calculated new candle');
}

module.exports = CandleCalculator;
module.exports = CandleCalculator;
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
"moment": "2.0.x",
"btc-e": "0.0.x",
"bitcoincharts": "0.0.x",
"cexio": "0.0.1",
"emailjs": "git://github.com/eleith/emailjs.git",
"prompt-lite": "0.1.x",
"bitstamp": "0.0.4",
"async": "0.2.x",
"nedb": "*",
"line-reader": "0.2.x"
},
"engines": { "node": "0.10.x" },
Expand Down
Loading

0 comments on commit a1b5b23

Please sign in to comment.