More refactoring on Trader

This commit is contained in:
Maciej Małecki 2014-04-14 15:16:53 +02:00
parent 05fef31fce
commit 1f3c59dbc0
6 changed files with 88 additions and 277 deletions

View file

@ -23,17 +23,44 @@ var express = require('express');
var fs = require('fs'); var fs = require('fs');
var LamassuConfig = require('lamassu-config'); var LamassuConfig = require('lamassu-config');
var routes = require('./routes'); var routes = require('./routes');
var Trader = require('./trader');
var PostgresqlInterface = require('./protocol/db/postgresql_interface');
module.exports = function (options) { module.exports = function (options) {
var app = express(); var app = express();
var connectionString; var connectionString;
var server; var server;
var config; var config;
var trader;
var db;
connectionString = options.postgres || connectionString = options.postgres ||
'postgres://lamassu:lamassu@localhost/lamassu'; 'postgres://lamassu:lamassu@localhost/lamassu';
config = new LamassuConfig(connectionString); config = new LamassuConfig(connectionString);
db = new PostgresqlInterface(connectionString);
trader = new Trader(db);
config.load(function (err, config) {
if (err) {
console.error('Loading config failed');
throw err;
}
trader.configure(config);
trader.startPolling();
});
config.on('configUpdate', function () {
config.load(function (err, config) {
if (err) {
return console.error('Error while reloading config');
}
trader.configure(config);
console.log('Config reloaded');
});
});
app.use(express.logger()); app.use(express.logger());
app.use(express.favicon()); app.use(express.favicon());
@ -56,10 +83,10 @@ module.exports = function (options) {
server = https.createServer(serverOptions, app); server = https.createServer(serverOptions, app);
} }
config.load(function(err, conf) { var authMiddleware = function (req, res, next) {
if (err) { console.log(err); process.exit(1); } req.device = {};
return next();
var authMiddleware = function (req, res, next) { return next(); }; };
if (options.https) { if (options.https) {
authMiddleware = function(req, res, next) { authMiddleware = function(req, res, next) {
@ -76,8 +103,7 @@ module.exports = function (options) {
}; };
} }
routes.init(app, conf, config, authMiddleware); routes.init(app, trader, authMiddleware);
});
return server; return server;
}; };

View file

@ -1,145 +0,0 @@
'use strict';
require('date-utils');
//var async = require('async');
var winston = require('winston');
var logger = new (winston.Logger)({transports:[new (winston.transports.Console)()]});
var path = require('path');
var _transferExchange;
var _tickerExchange;
var _tradeExchange;
var _rates = {};
var _config;
var _commission;
var _config;
var SATOSHI_FACTOR = Math.pow(10, 8);
exports.ticker = require('./ticker');
exports.trade = require('./trade');
exports.send = require('./send');
exports.balance = require('./balance');
exports._tradeExchange = null;
exports._transferExchange = null;
exports.findExchange = function (name) {
var exchange;
try {
exchange = require('lamassu-' + name);
} catch (err) {
if (!err.message.match(/Cannot find module/)) throw err;
exchange = require(path.join(path.dirname(__dirname), 'exchanges', name));
}
return exchange;
};
exports.findTicker = function (name) {
var exchange = exports.findExchange(name);
return exchange.ticker || exchange;
};
exports.findTrader = function (name) {
var exchange = exports.findExchange(name);
return exchange.trader || exchange;
};
exports.findWallet = function (name) {
var exchange = exports.findExchange(name);
return exchange.wallet || exchange;
};
exports.triggerBalance = function triggerBalance() {
this.balance.triggerBalance();
};
exports.init = function(config) {
_config = config;
if (config.settings.lowBalanceMargin < 1) {
throw new Error('`settings.lowBalanceMargin` has to be >= 1');
}
var tickerExchangeCode = config.plugins.current.ticker;
var tickerExchangeConfig = config.plugins.settings[tickerExchangeCode] || {};
tickerExchangeConfig.currency = config.settings.currency;
_tickerExchange = exports.findTicker(tickerExchangeCode).factory(tickerExchangeConfig);
var tradeExchangeCode = config.plugins.current.trade;
if (tradeExchangeCode) {
var tradeExchangeConfig = config.plugins.settings[tradeExchangeCode];
_tradeExchange = exports.findTrader(tradeExchangeCode).factory(tradeExchangeConfig);
}
var transferExchangeCode = config.plugins.current.transfer;
var transferExchangeConfig = config.plugins.settings[transferExchangeCode];
_commission = config.settings.commission;
_transferExchange = exports.findWallet(transferExchangeCode).factory(transferExchangeConfig);
var doRequestTradeExchange = _tradeExchange && tradeExchangeCode !== transferExchangeCode;
exports._tradeExchange = _tradeExchange;
exports._transferExchange = _transferExchange;
exports.ticker.init(config, exports, _tickerExchange);
exports.trade.init(config, exports, _tradeExchange, exports.ticker);
exports.send.init(config, exports, _transferExchange, exports.ticker);
exports.balance.init(config, exports, _transferExchange,
doRequestTradeExchange ? _tradeExchange : null);
};
/**
* return fiat balance
*
* in input to this function, balance has the following parameters...
*
* balance.transferBalance - in satoshis
* balance.tradeBalance - in USD
*
* Have added conversion here, but this really needs to be thought through, lamassu-bitstamp should perhaps
* return balance in satoshis
*/
exports.fiatBalance = function(rate, balance, transferSatoshis, tradeFiat, callback) {
if (!rate || !balance) return 0;
// The rate is actually our commission times real rate.
rate = _commission * rate;
// `lowBalanceMargin` is our safety net. It's a number > 1, and we divide
// all our balances by it to provide a safety margin.
var lowBalanceMargin = _config.settings.lowBalanceMargin;
// `balance.transferBalance` is the balance of our transfer account (the one
// we use to send Bitcoins to clients). `transferSatoshis` is the number
// of satoshis we're expected to send for this transaction. By subtracting
// them, we get `adjustedTransferBalance`, amount of satoshis we'll have
// after the transaction.
var adjustedTransferBalance = balance.transferBalance - transferSatoshis;
// Since `adjustedTransferBalance` is in Satoshis, we need to turn it into
// Bitcoins and then fiat to learn how much fiat currency we can exchange.
//
// Unit validity proof: [ $ ] = [ (B * 10^8) / 10^8 * $/B ]
// [ $ ] = [ B * $/B ]
// [ $ ] = [ $ ]
var fiatTransferBalance = ((adjustedTransferBalance / SATOSHI_FACTOR) * rate) / lowBalanceMargin;
// If this server is also configured to trade received fiat for Bitcoins,
// we also need to calculate if we have enough funds on our trade exchange.
if (balance.tradeBalance === null) return fiatTransferBalance;
var tradeBalance = balance.tradeBalance;
// We need to secure `tradeFiat` (amount of fiat in this transaction) and
// enough fiat to cover our trading queue (trades aren't executed immediately).
var adjustedFiat = tradeFiat + exports.trade.queueFiatBalance(rate);
// So we subtract `adjustedFiat` from `tradeBalance` and again, apply
// `lowBalanceMargin`.
var fiatTradeBalance = (tradeBalance - adjustedFiat) / lowBalanceMargin;
// And we return the smallest number.
return Math.min(fiatTransferBalance, fiatTradeBalance);
};

View file

@ -1,37 +0,0 @@
'use strict';
var _transferExchange;
var _api;
var _config;
var _conString = process.env.DATABASE_URL || 'postgres://lamassu:lamassu@localhost/lamassu';
var _db = require('../db/postgresql_interface').factory(_conString);
exports.init = function(config, api, transferExchange) {
_api = api;
_config = config;
_transferExchange = transferExchange;
};
exports.setDomain = function(domain) {
_transferExchange.setDomain(domain);
};
exports.sendBitcoins = function sendBitcoins(deviceFingerprint, tx, cb) {
_db.summonTransaction(deviceFingerprint, tx, function (err, isNew, txHash) {
if (err) return cb(err);
if (isNew) return _transferExchange.sendBitcoins(tx.toAddress, tx.satoshis,
_config.settings.transactionFee, function(err, txHash) {
if (err) {
_db.reportTransactionError(tx, err);
return cb(err);
}
cb(null, txHash);
_db.completeTransaction(tx, txHash);
_api.triggerBalance();
});
// transaction exists, but txHash might be null,
// in which case ATM should continue polling
cb(null, txHash);
});
};

View file

@ -1,33 +0,0 @@
'use strict';
require('date-utils');
var winston = require('winston');
var logger = new (winston.Logger)({transports:[new (winston.transports.Console)()]});
var _tickerExchange;
var _api;
var _rates = {};
var _pollRate = function(currency) {
logger.info('polling for rate...');
_tickerExchange.ticker(currency, function(err, rate) {
if (err) return;
logger.info('Rate update:', rate);
_rates[currency] = {rate: rate, timestamp: new Date()};
});
};
exports.init = function(config, api, tickerExchange) {
_api = api;
_tickerExchange = tickerExchange;
_pollRate(config.settings.currency);
setInterval(function () {
_pollRate(config.settings.currency);
}, 60 * 1000);
};
exports.rate = function(currency) {
if (!_rates[currency]) return null;
return _rates[currency];
};

View file

@ -1,9 +1,6 @@
'use strict'; 'use strict';
var api = exports.api = require('./protocol/api/api'); var _trader;
var _config;
var _lamassuConfig;
var _commission;
// Make sure these are higher than polling interval // Make sure these are higher than polling interval
// or there will be a lot of errors // or there will be a lot of errors
@ -26,8 +23,8 @@ var poll = function(req, res) {
}); });
} }
var rateRec = api.ticker.rate(req.params.currency); var rateRec = _trader.rate(req.params.currency);
var satoshiBalanceRec = api.balance.balance(); var satoshiBalanceRec = _trader.balance;
if (rateRec === null || satoshiBalanceRec === null) if (rateRec === null || satoshiBalanceRec === null)
return res.json({err: 'Server initializing'}); return res.json({err: 'Server initializing'});
@ -40,10 +37,10 @@ var poll = function(req, res) {
res.json({ res.json({
err: null, err: null,
rate: rate * _commission, rate: rate * _trader.config.exchanges.settings.commission,
fiat: api.fiatBalance(rate, satoshiBalanceRec, 0, 0), fiat: api.fiatBalance(0, 0),
currency: req.params.currency, currency: req.params.currency,
txLimit: parseInt(_config.exchanges.settings.compliance.maximum.limit, 10) txLimit: parseInt(_trader.config.exchanges.settings.compliance.maximum.limit, 10)
}); });
}; };
@ -76,33 +73,13 @@ var pair = function(req, res) {
); );
}; };
exports.init = function(app, config, lamassuConfig, authMiddleware) { exports.init = function(app, trader, authMiddleware) {
_config = config; _trader = trader;
_lamassuConfig = lamassuConfig;
api.init(_config.exchanges);
_commission = _config.exchanges.settings.commission;
exports._tradeExchange = api._tradeExchange;
exports._transferExchange = api._transferExchange;
app.get('/poll/:currency', authMiddleware, poll); app.get('/poll/:currency', authMiddleware, poll);
app.post('/trade', authMiddleware, trade); app.post('/trade', authMiddleware, trade);
app.post('/send', authMiddleware, send); app.post('/send', authMiddleware, send);
app.post('/pair', pair); app.post('/pair', pair);
lamassuConfig.on('configUpdate', function () {
_lamassuConfig.load(function(err, config) {
if (err) {
return console.error('Error while reloading config');
}
_config = config;
api.init(_config.exchanges);
console.log('Config reloaded');
});
});
return app; return app;
}; };

View file

@ -1,6 +1,8 @@
'use strict'; 'use strict';
var path = require('path'); var path = require('path');
var async = require('async');
var winston = require('winston');
var SATOSHI_FACTOR = Math.pow(10, 8); var SATOSHI_FACTOR = Math.pow(10, 8);
@ -10,6 +12,7 @@ var Trader = module.exports = function (db) {
} }
this.db = db; this.db = db;
this.rates = {};
this.logger = new (winston.Logger)({ this.logger = new (winston.Logger)({
transports: [new (winston.transports.Console)()] transports: [new (winston.transports.Console)()]
}); });
@ -29,38 +32,38 @@ Trader.prototype._findExchange = function (name) {
}; };
Trader.prototype._findTicker = function (name) { Trader.prototype._findTicker = function (name) {
var exchange = Trader.prototype.findExchange(name); var exchange = Trader.prototype._findExchange(name);
return exchange.ticker || exchange; return exchange.ticker || exchange;
}; };
Trader.prototype._findTrader = function (name) { Trader.prototype._findTrader = function (name) {
var exchange = Trader.prototype.findExchange(name); var exchange = Trader.prototype._findExchange(name);
return exchange.trader || exchange; return exchange.trader || exchange;
}; };
Trader.prototype._findWallet = function (name) { Trader.prototype._findWallet = function (name) {
var exchange = Trader.prototype.findExchange(name); var exchange = Trader.prototype._findExchange(name);
return exchange.wallet || exchange; return exchange.wallet || exchange;
}; };
Trader.prototype.configure = function (config) { Trader.prototype.configure = function (config) {
if (config.settings.lowBalanceMargin < 1) { if (config.exchanges.settings.lowBalanceMargin < 1) {
throw new Error('`settings.lowBalanceMargin` has to be >= 1'); throw new Error('`settings.lowBalanceMargin` has to be >= 1');
} }
var tickerExchangeCode = config.plugins.current.ticker; var tickerExchangeCode = config.exchanges.plugins.current.ticker;
var tickerExchangeConfig = config.plugins.settings[tickerExchangeCode] || {}; var tickerExchangeConfig = config.exchanges.plugins.settings[tickerExchangeCode] || {};
tickerExchangeConfig.currency = config.settings.currency; tickerExchangeConfig.currency = config.exchanges.settings.currency;
this.tickerExchange = exports.findTicker(tickerExchangeCode).factory(tickerExchangeConfig); this.tickerExchange = this._findTicker(tickerExchangeCode).factory(tickerExchangeConfig);
var tradeExchangeCode = config.plugins.current.trade; var tradeExchangeCode = config.exchanges.plugins.current.trade;
if (tradeExchangeCode) { if (tradeExchangeCode) {
var tradeExchangeConfig = config.plugins.settings[tradeExchangeCode]; var tradeExchangeConfig = config.exchanges.plugins.settings[tradeExchangeCode];
this.tradeExchange = this._findTrader(tradeExchangeCode).factory(tradeExchangeConfig); this.tradeExchange = this._findTrader(tradeExchangeCode).factory(tradeExchangeConfig);
} }
var transferExchangeCode = config.plugins.current.transfer; var transferExchangeCode = config.exchanges.plugins.current.transfer;
var transferExchangeConfig = config.plugins.settings[transferExchangeCode]; var transferExchangeConfig = config.exchanges.plugins.settings[transferExchangeCode];
this.transferExchange = this._findWallet(transferExchangeCode).factory(transferExchangeConfig); this.transferExchange = this._findWallet(transferExchangeCode).factory(transferExchangeConfig);
this.config = config; this.config = config;
@ -154,7 +157,11 @@ Trader.prototype.sendBitcoins = function (deviceFingerprint, tx, cb) {
}; };
Trader.prototype.startPolling = function () { Trader.prototype.startPolling = function () {
this.pollBalance();
this.pollRate();
setInterval(this.pollBalance.bind(this), 60 * 1000); setInterval(this.pollBalance.bind(this), 60 * 1000);
setInterval(this.pollRate.bind(this), 60 * 1000);
}; };
Trader.prototype.pollBalance = function () { Trader.prototype.pollBalance = function () {
@ -177,3 +184,19 @@ Trader.prototype.pollBalance = function () {
self.balance = balance; self.balance = balance;
}); });
}; };
Trader.prototype.pollRate = function () {
var self = this;
var currency = self.config.exchanges.settings.currency;
self.logger.info('polling for rate...');
self.tickerExchange.ticker(currency, function(err, rate) {
if (err) return;
self.logger.info('Rate update:', rate);
self.rates[currency] = {rate: rate, timestamp: new Date()};
});
};
Trader.prototype.rate = function (currency) {
return this.rates[currency];
};