forked from askmike/gekko
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge https://github.com/askmike/gekko into anti-conflicts
Note: Does not use the original README.md Primarily because askmike's version is MIT licensed but the enhancements are AGPLv3 (copyright Sarah White AKA kuzetsa, 2013-2014)
- Loading branch information
Showing
4 changed files
with
433 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
Oops, something went wrong.