Skip to content

Instantly share code, notes, and snippets.

@bitkominer
Created October 28, 2017 07:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bitkominer/e9421e3d73ea2dd793afe453a638fc75 to your computer and use it in GitHub Desktop.
Save bitkominer/e9421e3d73ea2dd793afe453a638fc75 to your computer and use it in GitHub Desktop.
To be saved inside folder "/plugins/trader/" as supplemental files for pingpong strategy
/*
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;
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