From 1940a3a3a3763ca2a5f5eef9c8840867b6bbc60d Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 18 May 2018 00:19:00 -0300 Subject: [PATCH] Support for Bitfinex margin trading --- .gitignore | 4 +- config/plugins/paperTrader.toml | 8 ++-- config/plugins/tradingAdvisor.toml | 6 +-- exchanges/bitfinex.js | 40 ++++++++++++----- importers/exchanges/bitfinex.js | 2 +- plugins/paperTrader/paperTrader.js | 39 +++++++++++++--- .../performanceAnalyzer.js | 45 +++++++++++++------ plugins/trader/trade.js | 45 +++++++++++-------- plugins/trader/trader.js | 9 ++++ 9 files changed, 139 insertions(+), 59 deletions(-) diff --git a/.gitignore b/.gitignore index b054f6d5b..e80cc24e9 100644 --- a/.gitignore +++ b/.gitignore @@ -36,7 +36,7 @@ dwsync.xml *.espressostorage # Folders & files to ignore - +strategies/custom* node_modules candles.csv cexio.db @@ -48,4 +48,4 @@ config.js config-*.js private-*.js private-*.toml -SECRET-api-keys.json \ No newline at end of file +SECRET-api-keys.json diff --git a/config/plugins/paperTrader.toml b/config/plugins/paperTrader.toml index 06abc1e1e..8dc57f5ec 100644 --- a/config/plugins/paperTrader.toml +++ b/config/plugins/paperTrader.toml @@ -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 diff --git a/config/plugins/tradingAdvisor.toml b/config/plugins/tradingAdvisor.toml index 54bc8e4ff..d53c628fa 100644 --- a/config/plugins/tradingAdvisor.toml +++ b/config/plugins/tradingAdvisor.toml @@ -1,9 +1,9 @@ enabled = true -candleSize = 60 +candleSize = 120 historySize = 25 -method = "MACD" +method = "RSI" [talib] enabled = false -version = "1.0.2" \ No newline at end of file +version = "1.0.2" diff --git a/exchanges/bitfinex.js b/exchanges/bitfinex.js index bd22bd4ff..f5b3d755d 100644 --- a/exchanges/bitfinex.js +++ b/exchanges/bitfinex.js @@ -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); @@ -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)); } @@ -118,20 +133,21 @@ 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) ); @@ -139,11 +155,11 @@ Trader.prototype.submit_order = function(type, amount, price, callback) { } 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) { diff --git a/importers/exchanges/bitfinex.js b/importers/exchanges/bitfinex.js index 0b4eaafd2..d60e7a37d 100644 --- a/importers/exchanges/bitfinex.js +++ b/importers/exchanges/bitfinex.js @@ -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); diff --git a/plugins/paperTrader/paperTrader.js b/plugins/paperTrader/paperTrader.js index 1bbb720c9..f33ec4e01 100644 --- a/plugins/paperTrader/paperTrader.js +++ b/plugins/paperTrader/paperTrader.js @@ -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(); @@ -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; @@ -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) + } } } diff --git a/plugins/performanceAnalyzer/performanceAnalyzer.js b/plugins/performanceAnalyzer/performanceAnalyzer.js index 5df48a2bf..b790e2764 100644 --- a/plugins/performanceAnalyzer/performanceAnalyzer.js +++ b/plugins/performanceAnalyzer/performanceAnalyzer.js @@ -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; @@ -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) { @@ -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? @@ -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) ); @@ -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(); } diff --git a/plugins/trader/trade.js b/plugins/trader/trade.js index baf59aaf8..d85ccc92d 100644 --- a/plugins/trader/trade.js +++ b/plugins/trader/trade.js @@ -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"); } } @@ -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") @@ -120,7 +128,6 @@ class Trade{ this.exchange.name ); } - log.info( 'attempting to BUY', order.amount, @@ -130,7 +137,6 @@ class Trade{ 'price:', order.price ); - this.exchange.buy(order.amount, order.price, _.bind(this.noteOrder,this) ); } @@ -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) => { @@ -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) + ' @', @@ -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 \ No newline at end of file +module.exports = Trade diff --git a/plugins/trader/trader.js b/plugins/trader/trader.js index 85a9962d3..f4e2aee8c 100644 --- a/plugins/trader/trader.js +++ b/plugins/trader/trader.js @@ -49,6 +49,15 @@ Trader.prototype.processAdvice = function(advice) { 'Selling ', config.trader.asset ); this.manager.trade('SELL'); + } else if(advice.recommendation == 'close') { + log.debug("CLOSES") + log.info( + 'Trader', + 'Received advice to close position', + 'Selling ', config.trader.asset + ); + this.manager.trade('CLOSE'); + } }