recalculate fiatBalance as balance at start of session
This commit is contained in:
parent
4925843906
commit
96f480a73c
2 changed files with 63 additions and 45 deletions
|
|
@ -19,19 +19,21 @@ Error.prototype.toJSON = function () {
|
||||||
|
|
||||||
var poll = function(req, res) {
|
var poll = function(req, res) {
|
||||||
var rateRec = _trader.rate();
|
var rateRec = _trader.rate();
|
||||||
var satoshiBalanceRec = _trader.balance;
|
var balanceRec = _trader.balance;
|
||||||
|
var fingerprint = req.connection.getPeerCertificate().fingerprint;
|
||||||
|
|
||||||
// `rateRec` and `satoshiBalanceRec` are both objects, so there's no danger
|
// `rateRec` and `balanceRec` are both objects, so there's no danger
|
||||||
// of misinterpreting rate or balance === 0 as 'Server initializing'.
|
// of misinterpreting rate or balance === 0 as 'Server initializing'.
|
||||||
if (!rateRec || !satoshiBalanceRec) {
|
if (!rateRec || !balanceRec) {
|
||||||
return res.json({err: 'Server initializing'});
|
return res.json({err: 'Server initializing'});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Date.now() - rateRec.timestamp > STALE_TICKER) {
|
var now = Date.now();
|
||||||
|
if (now - rateRec.timestamp > STALE_TICKER) {
|
||||||
return res.json({err: 'Stale ticker'});
|
return res.json({err: 'Stale ticker'});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Date.now() - rateRec.timestamp > STALE_BALANCE) {
|
if (now - balanceRec.timestamp > STALE_BALANCE) {
|
||||||
return res.json({err: 'Stale balance'});
|
return res.json({err: 'Stale balance'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,16 +42,16 @@ var poll = function(req, res) {
|
||||||
res.json({
|
res.json({
|
||||||
err: null,
|
err: null,
|
||||||
rate: rate * _trader.config.exchanges.settings.commission,
|
rate: rate * _trader.config.exchanges.settings.commission,
|
||||||
fiat: _trader.fiatBalance(0, 0),
|
fiat: _trader.fiatBalance(fingerprint),
|
||||||
locale: _trader.config.brain.locale,
|
locale: _trader.config.brain.locale,
|
||||||
txLimit: parseInt(_trader.config.exchanges.settings.compliance.maximum.limit, 10)
|
txLimit: parseInt(_trader.config.exchanges.settings.compliance.maximum.limit, 10)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var trade = function (req, res) {
|
var trade = function (req, res) {
|
||||||
_trader.trade(req.body.fiat, req.body.satoshis, req.body.currency, function(err) {
|
var fingerprint = req.connection.getPeerCertificate().fingerprint;
|
||||||
res.json({err: err});
|
_trader.trade(req.body, fingerprint);
|
||||||
});
|
res.json({err: null});
|
||||||
};
|
};
|
||||||
|
|
||||||
var send = function(req, res) {
|
var send = function(req, res) {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ var winston = require('winston');
|
||||||
|
|
||||||
var SATOSHI_FACTOR = Math.pow(10, 8);
|
var SATOSHI_FACTOR = Math.pow(10, 8);
|
||||||
|
|
||||||
|
// TODO: Define this somewhere more global
|
||||||
|
var SESSION_TIMEOUT = 60 * 60 * 1000; // an hour
|
||||||
|
|
||||||
var Trader = module.exports = function (db) {
|
var Trader = module.exports = function (db) {
|
||||||
if (!db) {
|
if (!db) {
|
||||||
throw new Error('`db` is required');
|
throw new Error('`db` is required');
|
||||||
|
|
@ -18,6 +21,7 @@ var Trader = module.exports = function (db) {
|
||||||
});
|
});
|
||||||
|
|
||||||
this._tradeQueue = [];
|
this._tradeQueue = [];
|
||||||
|
this._sessionInfo = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
Trader.prototype._findExchange = function (name) {
|
Trader.prototype._findExchange = function (name) {
|
||||||
|
|
@ -79,12 +83,13 @@ Trader.prototype._consolidateTrades = function () {
|
||||||
return tradeRec;
|
return tradeRec;
|
||||||
};
|
};
|
||||||
|
|
||||||
Trader.prototype._purchase = function (trade) {
|
Trader.prototype._purchase = function (trade, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var rate = self.rate(trade.currency);
|
var rate = self.rate(trade.currency);
|
||||||
self.tradeExchange.purchase(trade.satoshis, rate.rate, function (err) {
|
self.tradeExchange.purchase(trade.satoshis, rate.rate, function (err) {
|
||||||
// TODO: don't ignore purchase errors
|
if (err) return cb(err);
|
||||||
self.pollBalance();
|
self.pollBalance();
|
||||||
|
cb();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -114,65 +119,60 @@ Trader.prototype.configure = function (config) {
|
||||||
this.pollRate();
|
this.pollRate();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// IMPORTANT: This function returns the estimated minimum available balance
|
||||||
* return fiat balance
|
// in fiat as of the start of the current user session on the device. User
|
||||||
*
|
// session starts when a user presses then Start button and ends when we
|
||||||
* in input to this function, balance has the following parameters...
|
// send the bitcoins.
|
||||||
*
|
Trader.prototype.fiatBalance = function (deviceFingerprint) {
|
||||||
* balance.transferBalance - in satoshis
|
var rawRate = this.rate(this.config.exchanges.settings.currency).rate;
|
||||||
* balance.tradeBalance - in USD
|
|
||||||
*
|
|
||||||
* Have added conversion here, but this really needs to be thought through, lamassu-bitstamp should perhaps
|
|
||||||
* return balance in satoshis
|
|
||||||
*/
|
|
||||||
Trader.prototype.fiatBalance = function (transferSatoshis, tradeFiat) {
|
|
||||||
var rate = this.rate(this.config.exchanges.settings.currency).rate;
|
|
||||||
var balance = this.balance;
|
var balance = this.balance;
|
||||||
var commission = this.config.exchanges.settings.commission;
|
var commission = this.config.exchanges.settings.commission;
|
||||||
|
|
||||||
if (!rate || !balance) {
|
if (!rawRate || !balance) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The rate is actually our commission times real rate.
|
// The rate is actually our commission times real rate.
|
||||||
rate = commission * rate;
|
var rate = commission * rawRate;
|
||||||
|
|
||||||
// `lowBalanceMargin` is our safety net. It's a number > 1, and we divide
|
// `lowBalanceMargin` is our safety net. It's a number > 1, and we divide
|
||||||
// all our balances by it to provide a safety margin.
|
// all our balances by it to provide a safety margin.
|
||||||
var lowBalanceMargin = this.config.exchanges.settings.lowBalanceMargin;
|
var lowBalanceMargin = this.config.exchanges.settings.lowBalanceMargin;
|
||||||
|
|
||||||
// `balance.transferBalance` is the balance of our transfer account (the one
|
// `balance.transferBalance` is the balance of our transfer account (the one
|
||||||
// we use to send Bitcoins to clients). `transferSatoshis` is the number
|
// we use to send Bitcoins to clients).
|
||||||
// of satoshis we're expected to send for this transaction. By subtracting
|
var transferBalance = balance.transferBalance;
|
||||||
// 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
|
// Since `transferBalance` is in satoshis, we need to turn it into
|
||||||
// Bitcoins and then fiat to learn how much fiat currency we can exchange.
|
// bitcoins and then fiat to learn how much fiat currency we can exchange.
|
||||||
//
|
//
|
||||||
// Unit validity proof: [ $ ] = [ (B * 10^8) / 10^8 * $/B ]
|
// Unit validity proof: [ $ ] = [ (B * 10^8) / 10^8 * $/B ]
|
||||||
// [ $ ] = [ B * $/B ]
|
// [ $ ] = [ B * $/B ]
|
||||||
// [ $ ] = [ $ ]
|
// [ $ ] = [ $ ]
|
||||||
var fiatTransferBalance = ((adjustedTransferBalance / SATOSHI_FACTOR) * rate) / lowBalanceMargin;
|
var fiatTransferBalance = ((transferBalance / SATOSHI_FACTOR) * rate) / lowBalanceMargin;
|
||||||
|
|
||||||
// If this server is also configured to trade received fiat for Bitcoins,
|
// 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.
|
// we also need to calculate if we have enough funds on our trade exchange.
|
||||||
if (balance.tradeBalance === null) return fiatTransferBalance;
|
if (balance.tradeBalance === null) return fiatTransferBalance;
|
||||||
var tradeBalance = balance.tradeBalance;
|
var tradeBalance = balance.tradeBalance;
|
||||||
|
|
||||||
// We need to secure `tradeFiat` (amount of fiat in this transaction) and
|
// We're reporting balance as of the start of the user session.
|
||||||
// enough fiat to cover our trading queue (trades aren't executed immediately).
|
var sessionInfo = this._sessionInfo[deviceFingerprint];
|
||||||
var adjustedFiat = tradeFiat + this._tradeQueueFiatBalance(rate);
|
var sessionBalance = sessionInfo ? sessionInfo.tradeBalance : tradeBalance;
|
||||||
|
var fiatTradeBalance = sessionBalance / lowBalanceMargin;
|
||||||
// So we subtract `adjustedFiat` from `tradeBalance` and again, apply
|
|
||||||
// `lowBalanceMargin`.
|
|
||||||
var fiatTradeBalance = (tradeBalance - adjustedFiat) / lowBalanceMargin;
|
|
||||||
|
|
||||||
// And we return the smallest number.
|
// And we return the smallest number.
|
||||||
return Math.min(fiatTransferBalance, fiatTradeBalance);
|
return Math.min(fiatTransferBalance, fiatTradeBalance);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Trader.prototype._clearSession = function (deviceFingerprint) {
|
||||||
|
var sessionInfo = this._sessionInfo[deviceFingerprint];
|
||||||
|
if (sessionInfo) {
|
||||||
|
clearTimeout(sessionInfo.reaper);
|
||||||
|
delete this._sessionInfo[deviceFingerprint];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Trader.prototype.sendBitcoins = function (deviceFingerprint, tx, cb) {
|
Trader.prototype.sendBitcoins = function (deviceFingerprint, tx, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
|
@ -182,6 +182,7 @@ Trader.prototype.sendBitcoins = function (deviceFingerprint, tx, cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
|
this._clearSession(deviceFingerprint);
|
||||||
return self.transferExchange.sendBitcoins(
|
return self.transferExchange.sendBitcoins(
|
||||||
tx.toAddress,
|
tx.toAddress,
|
||||||
tx.satoshis,
|
tx.satoshis,
|
||||||
|
|
@ -205,9 +206,21 @@ Trader.prototype.sendBitcoins = function (deviceFingerprint, tx, cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Trader.prototype.trade = function (fiat, satoshis, currency, callback) {
|
Trader.prototype.trade = function (rec, deviceFingerprint) {
|
||||||
this._tradeQueue.push({fiat: fiat, satoshis: satoshis, currency: currency});
|
// This is where we record starting trade balance at the beginning
|
||||||
callback(null);
|
// of the user session
|
||||||
|
var sessionInfo = this._sessionInfo[deviceFingerprint];
|
||||||
|
var self = this;
|
||||||
|
if (!sessionInfo) {
|
||||||
|
this._sessionInfo[deviceFingerprint] = {
|
||||||
|
tradeBalance: this.balance.tradeBalance,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
reaper: setTimeout(function () {
|
||||||
|
delete self._sessionInfo[deviceFingerprint];
|
||||||
|
}, SESSION_TIMEOUT)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this._tradeQueue.push({fiat: rec.fiat, satoshis: rec.satoshis, currency: rec.currency});
|
||||||
};
|
};
|
||||||
|
|
||||||
Trader.prototype.executeTrades = function () {
|
Trader.prototype.executeTrades = function () {
|
||||||
|
|
@ -231,7 +244,10 @@ Trader.prototype.executeTrades = function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.info('making a trade: %d', trade.satoshis / Math.pow(10, 8));
|
this.logger.info('making a trade: %d', trade.satoshis / Math.pow(10, 8));
|
||||||
this._purchase(trade);
|
var self = this;
|
||||||
|
this._purchase(trade, function (err) {
|
||||||
|
if (err) self.logger.error(err);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Trader.prototype.startPolling = function () {
|
Trader.prototype.startPolling = function () {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue