-
-
Save JohannesHoppe/617e13e527bcace2cc675ad2b4df7953 to your computer and use it in GitHub Desktop.
To be saved inside folder "/plugins/trader/" as supplemental files for pingpong strategy
This file contains hidden or 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
/* | |
The portfolio manager is responsible for making sure that | |
all decisions are turned into orders and make sure these orders | |
get executed. Besides the orders the manager also keeps track of | |
the client's portfolio. | |
NOTE: Execution strategy is limit orders (to not cross the book) | |
*/ | |
var _ = require('lodash'); | |
var util = require('../../core/util'); | |
var dirs = util.dirs(); | |
var events = require("events"); | |
var log = require(dirs.core + 'log'); | |
var async = require('async'); | |
var checker = require(dirs.core + 'exchangeChecker.js'); | |
var moment = require('moment'); | |
var async = require('async'); | |
var Manager = function(conf) { | |
_.bindAll(this); | |
var error = checker.cantTrade(conf); | |
if(error) | |
util.die(error); | |
this.exchangeMeta = checker.settings(conf); | |
// create an exchange | |
var Exchange = require(dirs.exchanges + this.exchangeMeta.slug); | |
this.exchange = new Exchange(conf); | |
this.conf = conf; | |
this.portfolio = {}; | |
this.fee; | |
this.action; | |
this.marketConfig = _.find(this.exchangeMeta.markets, function(p) { | |
return _.first(p.pair) === conf.currency.toUpperCase() && _.last(p.pair) === conf.asset.toUpperCase(); | |
}); | |
this.minimalOrder = this.marketConfig.minimalOrder; | |
this.currency = conf.currency; | |
this.asset = conf.asset; | |
this.keepAsset = 0; | |
if(_.isNumber(conf.keepAsset)) { | |
log.debug('Keep asset is active. Will try to keep at least ' + conf.keepAsset + ' ' + conf.asset); | |
this.keepAsset = conf.keepAsset; | |
} | |
// resets after every order | |
this.orders = []; | |
}; | |
// teach our trader events | |
util.makeEventEmitter(Manager); | |
Manager.prototype.init = function(callback) { | |
log.debug('getting ticker, balance & fee from', this.exchange.name); | |
var prepare = function() { | |
this.starting = false; | |
log.info('trading at', this.exchange.name, 'ACTIVE'); | |
log.info(this.exchange.name, 'trading fee will be:', this.fee * 100 + '%'); | |
this.logPortfolio(); | |
callback(); | |
}; | |
async.series([ | |
this.setTicker, | |
this.setPortfolio, | |
this.setFee | |
], _.bind(prepare, this)); | |
} | |
Manager.prototype.setPortfolio = function(callback) { | |
var set = function(err, fullPortfolio) { | |
if(err) | |
util.die(err); | |
// only include the currency/asset of this market | |
const portfolio = [ this.conf.currency, this.conf.asset ] | |
.map(name => { | |
let item = _.find(fullPortfolio, {name}); | |
if(!item) { | |
log.debug(`Unable to find "${name}" in portfolio provided by exchange, assuming 0.`); | |
item = {name, amount: 0}; | |
} | |
return item; | |
}); | |
if(_.isEmpty(this.portfolio)) | |
this.emit('portfolioUpdate', this.convertPortfolio(portfolio)); | |
this.portfolio = portfolio; | |
if(_.isFunction(callback)) | |
callback(); | |
}.bind(this); | |
util.retry(this.exchange.getPortfolio, set); | |
}; | |
Manager.prototype.setFee = function(callback) { | |
var set = function(err, fee) { | |
this.fee = fee; | |
if(err) | |
util.die(err); | |
if(_.isFunction(callback)) | |
callback(); | |
}.bind(this); | |
util.retry(this.exchange.getFee, set); | |
}; | |
Manager.prototype.setTicker = function(callback) { | |
var set = function(err, ticker) { | |
this.ticker = ticker; | |
if(err) | |
util.die(err); | |
if(_.isFunction(callback)) | |
callback(); | |
}.bind(this); | |
util.retry(this.exchange.getTicker, set); | |
}; | |
// return the [fund] based on the data we have in memory | |
Manager.prototype.getFund = function(fund) { | |
return _.find(this.portfolio, function(f) { return f.name === fund}); | |
}; | |
Manager.prototype.getBalance = function(fund) { | |
return this.getFund(fund).amount; | |
}; | |
// This function makes sure the limit order gets submitted | |
// to the exchange and initiates order registers watchers. | |
Manager.prototype.trade = function(what, retry) { | |
// if we are still busy executing the last trade | |
// cancel that one (and ignore results = assume not filled) | |
if(!retry && _.size(this.orders)) | |
return this.cancelLastOrder(() => this.trade(what)); | |
this.action = what; | |
var act = function() { | |
//============================================================================= | |
//To customize pingpong setting in following lines till next break (//=======) | |
//----------------------------------------------------------------------------- | |
var amounttobuy, amounttosell, price, upperprice, lowerprice, minimumbalance, minimumasset, lotsize; | |
//if signal BUY appears | |
if(what === 'BUY') { | |
//Split your bankroll at least into 10 lots. Lotsize is in currency (BTC). | |
lotsize = 0.00010500; | |
//define price to sell HI, 7 satoshi above current bid to make quick sell | |
price = this.ticker.bid + 0.00000007; | |
amounttosell = (lotsize + (0.005*lotsize)) / price; | |
//define price to buy LO, 0.5% below sell price | |
lowerprice = price - (0.005 * price); | |
amounttobuy = lotsize / lowerprice; | |
//define minimum parameter to execute pingpong trade | |
minimumasset = amounttosell + (0.01*amounttosell); | |
minimumbalance = 0.00011000; | |
//execute buy LO if balance > minimum parameter | |
if(this.getBalance(this.currency) > minimumbalance){ | |
this.buy(amounttobuy, lowerprice); | |
} | |
//execute sell HI if asset > minimum parameter | |
if(this.getBalance(this.asset)>minimumasset){ | |
this.sell(amounttosell, price); | |
} | |
//if signal SELL appears | |
} else if(what === 'SELL') { | |
//Split your bankroll at least into 10 lots. Lotsize is in currency (BTC). | |
lotsize = 0.00010500; | |
//define price to buy LO, 7 satoshi below current ask to make quick buy | |
price = this.ticker.ask - 0.00000007; | |
amounttobuy = lotsize / price; | |
//define price to sell HI, 0.5% above buy price | |
upperprice = price + (0.005*price); | |
amounttosell = (lotsize + (0.005*lotsize)) / upperprice; | |
//define minimum parameter to execute pingpong trade | |
minimumbalance = 0.00011000; | |
minimumasset = amounttosell + (0.01*amounttosell); | |
//execute sell HI if asset > minimum parameter | |
if(this.getBalance(this.asset) > minimumasset){ | |
this.sell(amounttosell, upperprice); | |
} | |
//execute buy LO if balance > minimum parameter | |
if(this.getBalance(this.currency) > minimumbalance){ | |
this.buy(amounttobuy, price); | |
} | |
} | |
}; | |
//========================================================================= | |
async.series([ | |
this.setTicker, | |
this.setPortfolio, | |
this.setFee | |
], _.bind(act, this)); | |
}; | |
Manager.prototype.getMinimum = function(price) { | |
if(this.minimalOrder.unit === 'currency') | |
return minimum = this.minimalOrder.amount / price; | |
else | |
return minimum = this.minimalOrder.amount; | |
}; | |
// first do a quick check to see whether we can buy | |
// the asset, if so BUY and keep track of the order | |
// (amount is in asset quantity) | |
Manager.prototype.buy = function(amount, price) { | |
var minimum = this.getMinimum(price); | |
// if order to small | |
if(amount < minimum) { | |
return log.error( | |
'Wanted to buy', | |
this.asset, | |
'but the amount is too small', | |
'(' + parseFloat(amount).toFixed(12) + ')', | |
'at', | |
this.exchange.name | |
); | |
} | |
log.info( | |
'Attempting to BUY', | |
amount, | |
this.asset, | |
'at', | |
this.exchange.name, | |
'price:', | |
price | |
); | |
this.exchange.buy(amount, price, this.noteOrder); | |
}; | |
// first do a quick check to see whether we can sell | |
// the asset, if so SELL and keep track of the order | |
// (amount is in asset quantity) | |
Manager.prototype.sell = function(amount, price) { | |
var minimum = this.getMinimum(price); | |
// if order to small | |
if(amount < minimum) { | |
return log.error( | |
'Wanted to buy', | |
this.currency, | |
'but the amount is too small', | |
'(' + parseFloat(amount).toFixed(12) + ')', | |
'at', | |
this.exchange.name | |
); | |
} | |
log.info( | |
'Attempting to SELL', | |
amount, | |
this.asset, | |
'at', | |
this.exchange.name, | |
'price:', | |
price | |
); | |
this.exchange.sell(amount, price, this.noteOrder); | |
}; | |
Manager.prototype.noteOrder = function(err, order) { | |
if(err) { | |
util.die(err); | |
} | |
this.orders.push(order); | |
// if after 1 minute the order is still there | |
// we cancel and calculate & make a new one | |
setTimeout(this.checkOrder, util.minToMs(1)); | |
}; | |
Manager.prototype.cancelLastOrder = function(done) { | |
this.exchange.cancelOrder(_.last(this.orders), alreadyFilled => { | |
if(alreadyFilled) | |
return this.relayOrder(done); | |
this.orders = []; | |
done(); | |
}); | |
} | |
// check whether the order got fully filled | |
// if it is not: cancel & instantiate a new order | |
Manager.prototype.checkOrder = function() { | |
var handleCheckResult = function(err, filled) { | |
if(!filled) { | |
//log.info(this.action, 'order was not (fully) filled, cancelling and creating new order'); | |
//this.exchange.cancelOrder(_.last(this.orders), _.bind(handleCancelResult, this)); | |
return; | |
} | |
log.info(this.action, 'was successfull'); | |
this.relayOrder(); | |
} | |
var handleCancelResult = function(alreadyFilled) { | |
if(alreadyFilled) | |
return; | |
if(this.exchangeMeta.forceReorderDelay) { | |
//We need to wait in case a canceled order has already reduced the amount | |
var wait = 10; | |
log.debug(`Waiting ${wait} seconds before starting a new trade on ${this.exchangeMeta.name}!`); | |
setTimeout( | |
() => this.trade(this.action, true), | |
+moment.duration(wait, 'seconds') | |
); | |
return; | |
} | |
this.trade(this.action, true); | |
} | |
this.exchange.checkOrder(_.last(this.orders), _.bind(handleCheckResult, this)); | |
} | |
// convert into the portfolio expected by the performanceAnalyzer | |
Manager.prototype.convertPortfolio = function(portfolio) { | |
var asset = _.find(portfolio, a => a.name === this.asset).amount; | |
var currency = _.find(portfolio, a => a.name === this.currency).amount; | |
return { | |
currency, | |
asset, | |
balance: currency + (asset * this.ticker.bid) | |
} | |
} | |
Manager.prototype.relayOrder = function(done) { | |
// look up all executed orders and relay average. | |
var relay = (err, res) => { | |
var price = 0; | |
var amount = 0; | |
var date = moment(0); | |
_.each(res.filter(o => !_.isUndefined(o) && o.amount), order => { | |
date = _.max([moment(order.date), date]); | |
price = ((price * amount) + (order.price * order.amount)) / (order.amount + amount); | |
amount += +order.amount; | |
}); | |
async.series([ | |
this.setPortfolio, | |
this.setTicker | |
], () => { | |
const portfolio = this.convertPortfolio(this.portfolio); | |
this.emit('trade', { | |
date, | |
price, | |
portfolio: portfolio, | |
balance: portfolio.balance, | |
// NOTE: within the portfolioManager | |
// this is in uppercase, everywhere else | |
// (UI, performanceAnalyzer, etc. it is | |
// lowercase) | |
action: this.action.toLowerCase() | |
}); | |
this.orders = []; | |
if(_.isFunction(done)) | |
done(); | |
}); | |
} | |
var getOrders = _.map( | |
this.orders, | |
order => next => this.exchange.getOrder(order, next) | |
); | |
async.series(getOrders, relay); | |
} | |
Manager.prototype.logPortfolio = function() { | |
log.info(this.exchange.name, 'portfolio:'); | |
_.each(this.portfolio, function(fund) { | |
log.info('\t', fund.name + ':', parseFloat(fund.amount).toFixed(12)); | |
}); | |
}; | |
module.exports = Manager; | |
var _ = require('lodash'); | |
var util = require('../../core/util.js'); | |
var config = util.getConfig(); | |
var dirs = util.dirs(); | |
var log = require(dirs.core + 'log'); | |
//----------------------------------------------------------------------------------- | |
//===== Choose true to activate pingpong trade, false for default gekko setting ===== | |
//----------------------------------------------------------------------------------- | |
var activatepingpong = true; | |
//----------------------------------------------------------------------------------- | |
if(activatepingpong = true){ | |
var Manager = require('./portfolioManager_pingpong'); | |
}else{ | |
var Manager = require('./portfolioManager'); | |
} | |
var Trader = function(next) { | |
_.bindAll(this); | |
this.manager = new Manager(_.extend(config.trader, config.watch)); | |
this.manager.init(next); | |
let sendPortfolio = false; | |
this.manager.on('trade', trade => { | |
if(!sendPortfolio && this.initialPortfolio) { | |
this.emit('portfolioUpdate', this.initialPortfolio); | |
sendPortfolio = true; | |
} | |
this.emit('trade', trade); | |
}); | |
this.manager.once('portfolioUpdate', portfolioUpdate => { | |
this.initialPortfolio = portfolioUpdate; | |
}) | |
} | |
// teach our trader events | |
util.makeEventEmitter(Trader); | |
Trader.prototype.processCandle = (candle, done) => done(); | |
Trader.prototype.processAdvice = function(advice) { | |
if(advice.recommendation == 'long') { | |
log.info( | |
'Trader', | |
'Received advice to go long.', | |
'Buying ', config.trader.asset | |
); | |
this.manager.trade('BUY'); | |
} else if(advice.recommendation == 'short') { | |
log.info( | |
'Trader', | |
'Received advice to go short.', | |
'Selling ', config.trader.asset | |
); | |
this.manager.trade('SELL'); | |
} | |
} | |
module.exports = Trader; |
This file contains hidden or 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
var _ = require('lodash'); | |
var util = require('../../core/util.js'); | |
var config = util.getConfig(); | |
var dirs = util.dirs(); | |
var log = require(dirs.core + 'log'); | |
//----------------------------------------------------------------------------------- | |
//===== Choose true to activate pingpong trade, false for default gekko setting ===== | |
//----------------------------------------------------------------------------------- | |
var activatepingpong = true; | |
//----------------------------------------------------------------------------------- | |
if(activatepingpong = true){ | |
var Manager = require('./portfolioManager_pingpong'); | |
}else{ | |
var Manager = require('./portfolioManager'); | |
} | |
var Trader = function(next) { | |
_.bindAll(this); | |
this.manager = new Manager(_.extend(config.trader, config.watch)); | |
this.manager.init(next); | |
let sendPortfolio = false; | |
this.manager.on('trade', trade => { | |
if(!sendPortfolio && this.initialPortfolio) { | |
this.emit('portfolioUpdate', this.initialPortfolio); | |
sendPortfolio = true; | |
} | |
this.emit('trade', trade); | |
}); | |
this.manager.once('portfolioUpdate', portfolioUpdate => { | |
this.initialPortfolio = portfolioUpdate; | |
}) | |
} | |
// teach our trader events | |
util.makeEventEmitter(Trader); | |
Trader.prototype.processCandle = (candle, done) => done(); | |
Trader.prototype.processAdvice = function(advice) { | |
if(advice.recommendation == 'long') { | |
log.info( | |
'Trader', | |
'Received advice to go long.', | |
'Buying ', config.trader.asset | |
); | |
this.manager.trade('BUY'); | |
} else if(advice.recommendation == 'short') { | |
log.info( | |
'Trader', | |
'Received advice to go short.', | |
'Selling ', config.trader.asset | |
); | |
this.manager.trade('SELL'); | |
} | |
} | |
module.exports = Trader; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment