Skip to content

Commit

Permalink
Support for Bitfinex margin trading
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin committed May 18, 2018
1 parent 3429baa commit 1940a3a
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 59 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ dwsync.xml
*.espressostorage

# Folders & files to ignore

strategies/custom*
node_modules
candles.csv
cexio.db
Expand All @@ -48,4 +48,4 @@ config.js
config-*.js
private-*.js
private-*.toml
SECRET-api-keys.json
SECRET-api-keys.json
8 changes: 4 additions & 4 deletions config/plugins/paperTrader.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
feeMaker = 0.25
feeTaker = 0.25
feeMaker = 0.1
feeTaker = 0.1
feeUsing = 'maker'
slippage = 0.05

[simulationBalance]
asset = 1
currency = 100
asset = 0
currency = 10000
6 changes: 3 additions & 3 deletions config/plugins/tradingAdvisor.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
enabled = true

candleSize = 60
candleSize = 120
historySize = 25
method = "MACD"
method = "RSI"

[talib]
enabled = false
version = "1.0.2"
version = "1.0.2"
40 changes: 28 additions & 12 deletions exchanges/bitfinex.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Trader.prototype.getPortfolio = function(callback) {
if (err) return callback(err);

// We are only interested in funds in the "exchange" wallet
data = data.filter(c => c.type === 'exchange');
data = data.filter(c => c.type === 'trading');

const asset = _.find(data, c => c.currency.toUpperCase() === this.asset);
const currency = _.find(data, c => c.currency.toUpperCase() === this.currency);
Expand All @@ -85,15 +85,30 @@ Trader.prototype.getPortfolio = function(callback) {
currencyAmount = 0;
}

const portfolio = [
{ name: this.asset, amount: assetAmount },
{ name: this.currency, amount: currencyAmount },
];

callback(undefined, portfolio);
let processPositions = (err, operations) => {
if(err) return callback(err);
if(operations == undefined) {
log.error("got undefined operations list", err);
return callback()
}

operations.forEach((operation) => {
if(operation.symbol.toUpperCase().substr(0,3) == this.asset) {
assetAmount += parseFloat(operation.amount)
}
})

const portfolio = [
{ name: this.asset, amount: assetAmount},
{ name: this.currency, amount: currencyAmount },
];

callback(undefined, portfolio);
};
this.bitfinex.active_positions(this.handleResponse('getPortfolio', processPositions))
};

let handler = (cb) => this.bitfinex.wallet_balances(this.handleResponse('getPortfolio', cb));
let handler = (cb) => this.bitfinex.wallet_balances(this.handleResponse('getPortfolio', cb))
util.retryCustom(retryForever, _.bind(handler, this), _.bind(process, this));
}

Expand All @@ -118,32 +133,33 @@ Trader.prototype.getFee = function(callback) {
callback(undefined, makerFee / 100);
}

Trader.prototype.submit_order = function(type, amount, price, callback) {
Trader.prototype.submit_order = function(side, amount, price, callback, type) {
let process = (err, data) => {
if (err) return callback(err);

callback(err, data.order_id);
}


amount = Math.floor(amount*100000000)/100000000;
let handler = (cb) => this.bitfinex.new_order(this.pair,
amount + '',
price + '',
this.name.toLowerCase(),
side,
type,
'exchange limit',
this.handleResponse('submitOrder', cb)
);

util.retryCustom(retryCritical, _.bind(handler, this), _.bind(process, this));
}

Trader.prototype.buy = function(amount, price, callback) {
this.submit_order('buy', amount, price, callback);
this.submit_order('buy', amount, price, callback, 'limit');
}

Trader.prototype.sell = function(amount, price, callback) {
this.submit_order('sell', amount, price, callback);
this.submit_order('sell', amount, price, callback, 'limit');
}

Trader.prototype.checkOrder = function(order_id, callback) {
Expand Down
2 changes: 1 addition & 1 deletion importers/exchanges/bitfinex.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ var fetch = () => {
// We need to slow this down to prevent hitting the rate limits
setTimeout(() => {
fetcher.getTrades(lastTimestamp, handleFetch);
}, 2500);
}, 2700);
} else {
lastTimestamp = from.valueOf();
batch_start = moment(from);
Expand Down
39 changes: 34 additions & 5 deletions plugins/paperTrader/paperTrader.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const _ = require('lodash');

const util = require('../../core/util');
var log = require('../../core/log.js');
const ENV = util.gekkoEnv();

const config = util.getConfig();
Expand Down Expand Up @@ -35,6 +36,8 @@ PaperTrader.prototype.relayTrade = function(advice) {
action = 'sell';
else if(what === 'long')
action = 'buy';
else if(what === 'close')
action = 'close';
else
return;

Expand Down Expand Up @@ -74,17 +77,43 @@ PaperTrader.prototype.updatePosition = function(advice) {
// virtually trade all {currency} to {asset}
// at the current price (minus fees)
if(what === 'long') {
// close short if exist
if(this.portfolio.asset < 0) {
this.portfolio.currency += this.portfolio.asset * price ;
this.portfolio.asset = 0;
this.trades++;
}
this.portfolio.asset += this.extractFee(this.portfolio.currency / price);
this.portfolio.currency = 0;
this.trades++;
}

// virtually trade all {currency} to {asset}
// virtually trade all {asset} to {asset}
// at the current price (minus fees)
else if(what === 'short') {
this.portfolio.currency += this.extractFee(this.portfolio.asset * price);
this.portfolio.asset = 0;
this.trades++;
// close long if exist
if(this.portfolio.asset > 0) {
this.portfolio.currency += this.extractFee(this.portfolio.asset * price);
this.portfolio.asset = 0;
}
if(this.portfolio.asset == 0) {
this.portfolio.asset = -this.portfolio.currency / price;
this.portfolio.currency += this.portfolio.currency;
}
}

else if (what === 'close') {
// close short if exist
if(this.portfolio.asset < 0) {
this.portfolio.currency += this.portfolio.asset * price ;
this.portfolio.asset = 0;
this.trades++;
//log.debug("SHORT", "end", this.portfolio.currency, this.portfolio.asset, price)
}
if(this.portfolio.asset > 0) {
this.portfolio.currency += this.extractFee(this.portfolio.asset * price);
this.portfolio.asset = 0;
//log.debug("LONG", "end", this.portfolio.currency, this.portfolio.asset, price)
}
}
}

Expand Down
45 changes: 32 additions & 13 deletions plugins/performanceAnalyzer/performanceAnalyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const moment = require('moment');
const stats = require('../../core/stats');
const util = require('../../core/util');
const ENV = util.gekkoEnv();

var log = require('../../core/log.js');
const config = util.getConfig();
const perfConfig = config.performanceAnalyzer;
const watchConfig = config.watch;
Expand Down Expand Up @@ -74,31 +74,47 @@ PerformanceAnalyzer.prototype.processTrade = function(trade) {
}

PerformanceAnalyzer.prototype.logRoundtripPart = function(trade) {
// this is not part of a valid roundtrip
if(!this.roundTrip.entry && trade.action === 'sell') {
return;
}

if(trade.action === 'buy') {
if (this.roundTrip.exit) {
this.roundTrip.id++;
this.roundTrip.exit = false
if(this.roundTrip.entry) {
this.roundTrip.exit = {
date: trade.date,
price: trade.price,
total: trade.portfolio.currency + (trade.portfolio.asset * trade.price),
}
this.handleRoundtrip();
}

this.roundTrip.entry = {
date: trade.date,
price: trade.price,
total: trade.portfolio.currency + (trade.portfolio.asset * trade.price),
}
} else if(trade.action === 'sell') {
}
else if(trade.action === 'sell') {
if(this.roundTrip.entry) {
this.roundTrip.exit = {
date: trade.date,
price: trade.price,
total: trade.portfolio.currency + (trade.portfolio.asset * trade.price),
}
this.handleRoundtrip();
}
this.roundTrip.exit = false
this.roundTrip.entry = {
date: trade.date,
price: trade.price,
total: trade.portfolio.currency + (trade.portfolio.asset * trade.price),
}
}
else if(trade.action === 'close') {
this.roundTrip.exit = {
date: trade.date,
price: trade.price,
total: trade.portfolio.currency + (trade.portfolio.asset * trade.price),
}

this.handleRoundtrip();
this.roundTrip.entry = false;
}

}

PerformanceAnalyzer.prototype.round = function(amount) {
Expand All @@ -123,6 +139,8 @@ PerformanceAnalyzer.prototype.handleRoundtrip = function() {
roundtrip.pnl = roundtrip.exitBalance - roundtrip.entryBalance;
roundtrip.profit = (100 * roundtrip.exitBalance / roundtrip.entryBalance) - 100;

log.debug("PNL", roundtrip.profit)

this.roundTrips[this.roundTrip.id] = roundtrip;

// this will keep resending roundtrips, that is not ideal.. what do we do about it?
Expand All @@ -142,7 +160,7 @@ PerformanceAnalyzer.prototype.calculateReportStatistics = function() {
// the portfolio's balance is measured in {currency}
let balance = this.current.currency + this.price * this.current.asset;
let profit = balance - this.start.balance;

let timespan = moment.duration(
this.dates.end.diff(this.dates.start)
);
Expand Down Expand Up @@ -178,6 +196,7 @@ PerformanceAnalyzer.prototype.calculateReportStatistics = function() {

PerformanceAnalyzer.prototype.finalize = function(done) {
const report = this.calculateReportStatistics();
log.debug("Total Profit", report);
this.handler.finalize(report);
done();
}
Expand Down
45 changes: 26 additions & 19 deletions plugins/trader/trade.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,25 @@ class Trade{
let act = () => {
var amount, price;
if(this.action === 'BUY') {
amount = this.portfolio.getBalance(this.currency) / this.portfolio.ticker.ask;
if(amount > 0){
price = this.portfolio.ticker.bid;
this.buy(amount, price);
}
const amount = this.portfolio.getBalance(this.currency) / this.portfolio.ticker.ask;
if(amount > 0)
this.buy(amount);
else
log.debug("no money for margin selling");
} else if(this.action === 'SELL') {
amount = this.portfolio.getBalance(this.asset) - this.keepAsset;
if(amount > 0){
price = this.portfolio.ticker.ask;
this.sell(amount, price);
}
const amount = this.portfolio.getBalance(this.currency) / this.portfolio.ticker.ask;
if(amount > 0)
this.sell(amount);
else
log.debug("no money for margin selling");
} else if(this.action === 'CLOSE') {
const asset_amount = this.portfolio.getBalance(this.asset);
if(asset_amount < 0)
this.buy(-asset_amount);
else if (asset_amount > 0)
this.sell(asset_amount);
else
log.debug("nothing to close");
}
}

Expand All @@ -101,9 +109,9 @@ class Trade{
// 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)
buy(amount, price) {
buy(amount) {
const price = this.portfolio.ticker.bid;
let minimum = 0;

let process = (err, order) => {
if(!this.isActive || this.isDeactivating){
return log.debug(this.action, "trade class is no longer active")
Expand All @@ -120,7 +128,6 @@ class Trade{
this.exchange.name
);
}

log.info(
'attempting to BUY',
order.amount,
Expand All @@ -130,7 +137,6 @@ class Trade{
'price:',
order.price
);

this.exchange.buy(order.amount, order.price, _.bind(this.noteOrder,this) );
}

Expand All @@ -145,7 +151,8 @@ class Trade{
// 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)
sell(amount, price) {
sell(amount) {
const price = this.portfolio.ticker.ask;
let minimum = 0;
let process = (err, order) => {

Expand All @@ -156,7 +163,7 @@ class Trade{
// if order to small
if (!order.amount || order.amount < minimum) {
return log.warn(
'wanted to buy',
'wanted to sell',
this.currency,
'but the amount is too small ',
'(' + parseFloat(amount).toFixed(8) + ' @',
Expand Down Expand Up @@ -307,12 +314,12 @@ class Trade{

getMinimum(price) {
if(this.minimalOrder.unit === 'currency')
return minimum = this.minimalOrder.amount / price;
return this.minimalOrder.amount / price;
else
return minimum = this.minimalOrder.amount;
return this.minimalOrder.amount;
}
}

util.makeEventEmitter(Trade)

module.exports = Trade
module.exports = Trade
Loading

0 comments on commit 1940a3a

Please sign in to comment.