Skip to content

Commit 1940a3a

Browse files
author
Martin
committed
Support for Bitfinex margin trading
1 parent 3429baa commit 1940a3a

File tree

9 files changed

+139
-59
lines changed

9 files changed

+139
-59
lines changed

.gitignore

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ dwsync.xml
3636
*.espressostorage
3737

3838
# Folders & files to ignore
39-
39+
strategies/custom*
4040
node_modules
4141
candles.csv
4242
cexio.db
@@ -48,4 +48,4 @@ config.js
4848
config-*.js
4949
private-*.js
5050
private-*.toml
51-
SECRET-api-keys.json
51+
SECRET-api-keys.json

config/plugins/paperTrader.toml

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
feeMaker = 0.25
2-
feeTaker = 0.25
1+
feeMaker = 0.1
2+
feeTaker = 0.1
33
feeUsing = 'maker'
44
slippage = 0.05
55

66
[simulationBalance]
7-
asset = 1
8-
currency = 100
7+
asset = 0
8+
currency = 10000

config/plugins/tradingAdvisor.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
enabled = true
22

3-
candleSize = 60
3+
candleSize = 120
44
historySize = 25
5-
method = "MACD"
5+
method = "RSI"
66

77
[talib]
88
enabled = false
9-
version = "1.0.2"
9+
version = "1.0.2"

exchanges/bitfinex.js

+28-12
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Trader.prototype.getPortfolio = function(callback) {
6464
if (err) return callback(err);
6565

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

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

88-
const portfolio = [
89-
{ name: this.asset, amount: assetAmount },
90-
{ name: this.currency, amount: currencyAmount },
91-
];
92-
93-
callback(undefined, portfolio);
88+
let processPositions = (err, operations) => {
89+
if(err) return callback(err);
90+
if(operations == undefined) {
91+
log.error("got undefined operations list", err);
92+
return callback()
93+
}
94+
95+
operations.forEach((operation) => {
96+
if(operation.symbol.toUpperCase().substr(0,3) == this.asset) {
97+
assetAmount += parseFloat(operation.amount)
98+
}
99+
})
100+
101+
const portfolio = [
102+
{ name: this.asset, amount: assetAmount},
103+
{ name: this.currency, amount: currencyAmount },
104+
];
105+
106+
callback(undefined, portfolio);
107+
};
108+
this.bitfinex.active_positions(this.handleResponse('getPortfolio', processPositions))
94109
};
95110

96-
let handler = (cb) => this.bitfinex.wallet_balances(this.handleResponse('getPortfolio', cb));
111+
let handler = (cb) => this.bitfinex.wallet_balances(this.handleResponse('getPortfolio', cb))
97112
util.retryCustom(retryForever, _.bind(handler, this), _.bind(process, this));
98113
}
99114

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

121-
Trader.prototype.submit_order = function(type, amount, price, callback) {
136+
Trader.prototype.submit_order = function(side, amount, price, callback, type) {
122137
let process = (err, data) => {
123138
if (err) return callback(err);
124139

125140
callback(err, data.order_id);
126141
}
127142

143+
128144
amount = Math.floor(amount*100000000)/100000000;
129145
let handler = (cb) => this.bitfinex.new_order(this.pair,
130146
amount + '',
131147
price + '',
132148
this.name.toLowerCase(),
149+
side,
133150
type,
134-
'exchange limit',
135151
this.handleResponse('submitOrder', cb)
136152
);
137153

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

141157
Trader.prototype.buy = function(amount, price, callback) {
142-
this.submit_order('buy', amount, price, callback);
158+
this.submit_order('buy', amount, price, callback, 'limit');
143159
}
144160

145161
Trader.prototype.sell = function(amount, price, callback) {
146-
this.submit_order('sell', amount, price, callback);
162+
this.submit_order('sell', amount, price, callback, 'limit');
147163
}
148164

149165
Trader.prototype.checkOrder = function(order_id, callback) {

importers/exchanges/bitfinex.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ var fetch = () => {
7676
// We need to slow this down to prevent hitting the rate limits
7777
setTimeout(() => {
7878
fetcher.getTrades(lastTimestamp, handleFetch);
79-
}, 2500);
79+
}, 2700);
8080
} else {
8181
lastTimestamp = from.valueOf();
8282
batch_start = moment(from);

plugins/paperTrader/paperTrader.js

+34-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const _ = require('lodash');
22

33
const util = require('../../core/util');
4+
var log = require('../../core/log.js');
45
const ENV = util.gekkoEnv();
56

67
const config = util.getConfig();
@@ -35,6 +36,8 @@ PaperTrader.prototype.relayTrade = function(advice) {
3536
action = 'sell';
3637
else if(what === 'long')
3738
action = 'buy';
39+
else if(what === 'close')
40+
action = 'close';
3841
else
3942
return;
4043

@@ -74,17 +77,43 @@ PaperTrader.prototype.updatePosition = function(advice) {
7477
// virtually trade all {currency} to {asset}
7578
// at the current price (minus fees)
7679
if(what === 'long') {
80+
// close short if exist
81+
if(this.portfolio.asset < 0) {
82+
this.portfolio.currency += this.portfolio.asset * price ;
83+
this.portfolio.asset = 0;
84+
this.trades++;
85+
}
7786
this.portfolio.asset += this.extractFee(this.portfolio.currency / price);
7887
this.portfolio.currency = 0;
79-
this.trades++;
8088
}
8189

82-
// virtually trade all {currency} to {asset}
90+
// virtually trade all {asset} to {asset}
8391
// at the current price (minus fees)
8492
else if(what === 'short') {
85-
this.portfolio.currency += this.extractFee(this.portfolio.asset * price);
86-
this.portfolio.asset = 0;
87-
this.trades++;
93+
// close long if exist
94+
if(this.portfolio.asset > 0) {
95+
this.portfolio.currency += this.extractFee(this.portfolio.asset * price);
96+
this.portfolio.asset = 0;
97+
}
98+
if(this.portfolio.asset == 0) {
99+
this.portfolio.asset = -this.portfolio.currency / price;
100+
this.portfolio.currency += this.portfolio.currency;
101+
}
102+
}
103+
104+
else if (what === 'close') {
105+
// close short if exist
106+
if(this.portfolio.asset < 0) {
107+
this.portfolio.currency += this.portfolio.asset * price ;
108+
this.portfolio.asset = 0;
109+
this.trades++;
110+
//log.debug("SHORT", "end", this.portfolio.currency, this.portfolio.asset, price)
111+
}
112+
if(this.portfolio.asset > 0) {
113+
this.portfolio.currency += this.extractFee(this.portfolio.asset * price);
114+
this.portfolio.asset = 0;
115+
//log.debug("LONG", "end", this.portfolio.currency, this.portfolio.asset, price)
116+
}
88117
}
89118
}
90119

plugins/performanceAnalyzer/performanceAnalyzer.js

+32-13
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const moment = require('moment');
44
const stats = require('../../core/stats');
55
const util = require('../../core/util');
66
const ENV = util.gekkoEnv();
7-
7+
var log = require('../../core/log.js');
88
const config = util.getConfig();
99
const perfConfig = config.performanceAnalyzer;
1010
const watchConfig = config.watch;
@@ -74,31 +74,47 @@ PerformanceAnalyzer.prototype.processTrade = function(trade) {
7474
}
7575

7676
PerformanceAnalyzer.prototype.logRoundtripPart = function(trade) {
77-
// this is not part of a valid roundtrip
78-
if(!this.roundTrip.entry && trade.action === 'sell') {
79-
return;
80-
}
81-
8277
if(trade.action === 'buy') {
83-
if (this.roundTrip.exit) {
84-
this.roundTrip.id++;
85-
this.roundTrip.exit = false
78+
if(this.roundTrip.entry) {
79+
this.roundTrip.exit = {
80+
date: trade.date,
81+
price: trade.price,
82+
total: trade.portfolio.currency + (trade.portfolio.asset * trade.price),
83+
}
84+
this.handleRoundtrip();
8685
}
87-
8886
this.roundTrip.entry = {
8987
date: trade.date,
9088
price: trade.price,
9189
total: trade.portfolio.currency + (trade.portfolio.asset * trade.price),
9290
}
93-
} else if(trade.action === 'sell') {
91+
}
92+
else if(trade.action === 'sell') {
93+
if(this.roundTrip.entry) {
94+
this.roundTrip.exit = {
95+
date: trade.date,
96+
price: trade.price,
97+
total: trade.portfolio.currency + (trade.portfolio.asset * trade.price),
98+
}
99+
this.handleRoundtrip();
100+
}
101+
this.roundTrip.exit = false
102+
this.roundTrip.entry = {
103+
date: trade.date,
104+
price: trade.price,
105+
total: trade.portfolio.currency + (trade.portfolio.asset * trade.price),
106+
}
107+
}
108+
else if(trade.action === 'close') {
94109
this.roundTrip.exit = {
95110
date: trade.date,
96111
price: trade.price,
97112
total: trade.portfolio.currency + (trade.portfolio.asset * trade.price),
98113
}
99-
100114
this.handleRoundtrip();
115+
this.roundTrip.entry = false;
101116
}
117+
102118
}
103119

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

142+
log.debug("PNL", roundtrip.profit)
143+
126144
this.roundTrips[this.roundTrip.id] = roundtrip;
127145

128146
// this will keep resending roundtrips, that is not ideal.. what do we do about it?
@@ -142,7 +160,7 @@ PerformanceAnalyzer.prototype.calculateReportStatistics = function() {
142160
// the portfolio's balance is measured in {currency}
143161
let balance = this.current.currency + this.price * this.current.asset;
144162
let profit = balance - this.start.balance;
145-
163+
146164
let timespan = moment.duration(
147165
this.dates.end.diff(this.dates.start)
148166
);
@@ -178,6 +196,7 @@ PerformanceAnalyzer.prototype.calculateReportStatistics = function() {
178196

179197
PerformanceAnalyzer.prototype.finalize = function(done) {
180198
const report = this.calculateReportStatistics();
199+
log.debug("Total Profit", report);
181200
this.handler.finalize(report);
182201
done();
183202
}

plugins/trader/trade.js

+26-19
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,25 @@ class Trade{
7777
let act = () => {
7878
var amount, price;
7979
if(this.action === 'BUY') {
80-
amount = this.portfolio.getBalance(this.currency) / this.portfolio.ticker.ask;
81-
if(amount > 0){
82-
price = this.portfolio.ticker.bid;
83-
this.buy(amount, price);
84-
}
80+
const amount = this.portfolio.getBalance(this.currency) / this.portfolio.ticker.ask;
81+
if(amount > 0)
82+
this.buy(amount);
83+
else
84+
log.debug("no money for margin selling");
8585
} else if(this.action === 'SELL') {
86-
amount = this.portfolio.getBalance(this.asset) - this.keepAsset;
87-
if(amount > 0){
88-
price = this.portfolio.ticker.ask;
89-
this.sell(amount, price);
90-
}
86+
const amount = this.portfolio.getBalance(this.currency) / this.portfolio.ticker.ask;
87+
if(amount > 0)
88+
this.sell(amount);
89+
else
90+
log.debug("no money for margin selling");
91+
} else if(this.action === 'CLOSE') {
92+
const asset_amount = this.portfolio.getBalance(this.asset);
93+
if(asset_amount < 0)
94+
this.buy(-asset_amount);
95+
else if (asset_amount > 0)
96+
this.sell(asset_amount);
97+
else
98+
log.debug("nothing to close");
9199
}
92100
}
93101

@@ -101,9 +109,9 @@ class Trade{
101109
// first do a quick check to see whether we can buy
102110
// the asset, if so BUY and keep track of the order
103111
// (amount is in asset quantity)
104-
buy(amount, price) {
112+
buy(amount) {
113+
const price = this.portfolio.ticker.bid;
105114
let minimum = 0;
106-
107115
let process = (err, order) => {
108116
if(!this.isActive || this.isDeactivating){
109117
return log.debug(this.action, "trade class is no longer active")
@@ -120,7 +128,6 @@ class Trade{
120128
this.exchange.name
121129
);
122130
}
123-
124131
log.info(
125132
'attempting to BUY',
126133
order.amount,
@@ -130,7 +137,6 @@ class Trade{
130137
'price:',
131138
order.price
132139
);
133-
134140
this.exchange.buy(order.amount, order.price, _.bind(this.noteOrder,this) );
135141
}
136142

@@ -145,7 +151,8 @@ class Trade{
145151
// first do a quick check to see whether we can sell
146152
// the asset, if so SELL and keep track of the order
147153
// (amount is in asset quantity)
148-
sell(amount, price) {
154+
sell(amount) {
155+
const price = this.portfolio.ticker.ask;
149156
let minimum = 0;
150157
let process = (err, order) => {
151158

@@ -156,7 +163,7 @@ class Trade{
156163
// if order to small
157164
if (!order.amount || order.amount < minimum) {
158165
return log.warn(
159-
'wanted to buy',
166+
'wanted to sell',
160167
this.currency,
161168
'but the amount is too small ',
162169
'(' + parseFloat(amount).toFixed(8) + ' @',
@@ -307,12 +314,12 @@ class Trade{
307314

308315
getMinimum(price) {
309316
if(this.minimalOrder.unit === 'currency')
310-
return minimum = this.minimalOrder.amount / price;
317+
return this.minimalOrder.amount / price;
311318
else
312-
return minimum = this.minimalOrder.amount;
319+
return this.minimalOrder.amount;
313320
}
314321
}
315322

316323
util.makeEventEmitter(Trade)
317324

318-
module.exports = Trade
325+
module.exports = Trade

0 commit comments

Comments
 (0)