From 821e299afb77181dcaf9c66c95beccbbaf3529e6 Mon Sep 17 00:00:00 2001 From: misagh Date: Sun, 6 Jan 2019 14:45:29 +0100 Subject: [PATCH 01/32] adjusting trailing stoploss on exchange --- freqtrade/freqtradebot.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 4febe9dd0..6d3f7db07 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -648,7 +648,17 @@ class FreqtradeBot(object): trade.update(order) result = True else: - result = False + # check if trailing stoploss is enabled and stoploss value has changed + # in which case we cancel stoploss order and put another one with new + # value immediately + if self.config.get('trailing_stop', False) and trade.stop_loss > order['price']: + if self.exchange.cancel_order(order['id'], trade.pair): + stoploss_order_id = self.exchange.stoploss_limit( + pair=trade.pair, amount=trade.amount, + stop_price=stop_price, rate=limit_price + )['id'] + trade.stoploss_order_id = str(stoploss_order_id) + return result def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool: From 16472535ebabe4bf0ba3f9de77f87ae4cd33a38f Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 8 Jan 2019 12:39:10 +0100 Subject: [PATCH 02/32] adding stoploss_last_update to persistence --- freqtrade/persistence.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index a14d22b98..c466f2fc1 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -83,7 +83,7 @@ def check_migrate(engine) -> None: logger.debug(f'trying {table_back_name}') # Check for latest column - if not has_column(cols, 'stoploss_order_id'): + if not has_column(cols, 'stoploss_last_update'): logger.info(f'Running database migration - backup available as {table_back_name}') fee_open = get_column_def(cols, 'fee_open', 'fee') @@ -93,6 +93,7 @@ def check_migrate(engine) -> None: stop_loss = get_column_def(cols, 'stop_loss', '0.0') initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0') stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null') + stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null') max_rate = get_column_def(cols, 'max_rate', '0.0') sell_reason = get_column_def(cols, 'sell_reason', 'null') strategy = get_column_def(cols, 'strategy', 'null') @@ -111,7 +112,8 @@ def check_migrate(engine) -> None: (id, exchange, pair, is_open, fee_open, fee_close, open_rate, open_rate_requested, close_rate, close_rate_requested, close_profit, stake_amount, amount, open_date, close_date, open_order_id, - stop_loss, initial_stop_loss, stoploss_order_id, max_rate, sell_reason, strategy, + stop_loss, initial_stop_loss, stoploss_order_id, stoploss_last_update, + max_rate, sell_reason, strategy, ticker_interval ) select id, lower(exchange), @@ -127,9 +129,9 @@ def check_migrate(engine) -> None: {close_rate_requested} close_rate_requested, close_profit, stake_amount, amount, open_date, close_date, open_order_id, {stop_loss} stop_loss, {initial_stop_loss} initial_stop_loss, - {stoploss_order_id} stoploss_order_id, {max_rate} max_rate, - {sell_reason} sell_reason, {strategy} strategy, - {ticker_interval} ticker_interval + {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, + {max_rate} max_rate, {sell_reason} sell_reason, + {strategy} strategy, {ticker_interval} ticker_interval from {table_back_name} """) @@ -185,6 +187,8 @@ class Trade(_DECL_BASE): initial_stop_loss = Column(Float, nullable=True, default=0.0) # stoploss order id which is on exchange stoploss_order_id = Column(String, nullable=True, index=True) + # last update time of the stoploss order on exchange + stoploss_last_update = Column(DateTime, nullable=True) # absolute value of the highest reached price max_rate = Column(Float, nullable=True, default=0.0) sell_reason = Column(String, nullable=True) From 4fbb9d4462d353414a99087cbe54562466fd0c7e Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 8 Jan 2019 12:39:53 +0100 Subject: [PATCH 03/32] adding stoploss_on_exchange_interval to order_types dict. default to 1 minute (60) --- freqtrade/strategy/interface.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 694430fd6..acf627c8a 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -75,7 +75,8 @@ class IStrategy(ABC): 'buy': 'limit', 'sell': 'limit', 'stoploss': 'limit', - 'stoploss_on_exchange': False + 'stoploss_on_exchange': False, + 'stoploss_on_exchange_interval': 60, } # Optional time in force From aed855284cb3707d2fc994914441779be3115636 Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 8 Jan 2019 13:44:51 +0100 Subject: [PATCH 04/32] comparing with stopPrice instead of price --- freqtrade/freqtradebot.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 6d3f7db07..02b55954e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -613,7 +613,7 @@ class FreqtradeBot(object): def handle_stoploss_on_exchange(self, trade: Trade) -> bool: """ Check if trade is fulfilled in which case the stoploss - on exchange should be added immediately if stoploss on exchnage + on exchange should be added immediately if stoploss on exchange is enabled. """ @@ -630,13 +630,14 @@ class FreqtradeBot(object): stop_price = trade.open_rate * (1 + stoploss) # limit price should be less than stop price. - # 0.98 is arbitrary here. - limit_price = stop_price * 0.98 + # 0.99 is arbitrary here. + limit_price = stop_price * 0.99 stoploss_order_id = self.exchange.stoploss_limit( pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price )['id'] trade.stoploss_order_id = str(stoploss_order_id) + trade.stoploss_last_update = datetime.now() # Or the trade open and there is already a stoploss on exchange. # so we check if it is hit ... @@ -647,17 +648,22 @@ class FreqtradeBot(object): trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value trade.update(order) result = True - else: + elif self.config.get('trailing_stop', False): # check if trailing stoploss is enabled and stoploss value has changed # in which case we cancel stoploss order and put another one with new # value immediately - if self.config.get('trailing_stop', False) and trade.stop_loss > order['price']: - if self.exchange.cancel_order(order['id'], trade.pair): - stoploss_order_id = self.exchange.stoploss_limit( - pair=trade.pair, amount=trade.amount, - stop_price=stop_price, rate=limit_price - )['id'] - trade.stoploss_order_id = str(stoploss_order_id) + if trade.stop_loss > order['info']['stopPrice']: + # we check also if the update is neccesary + update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] + if (datetime.now() - trade.stoploss_last_update).total_seconds > update_beat: + # cancelling the current stoploss on exchange first + if self.exchange.cancel_order(order['id'], trade.pair): + # creating the new one + stoploss_order_id = self.exchange.stoploss_limit( + pair=trade.pair, amount=trade.amount, + stop_price=stop_price, rate=limit_price + )['id'] + trade.stoploss_order_id = str(stoploss_order_id) return result From 9e133eb32e206c2d9c01da340f099c7cef191004 Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 8 Jan 2019 16:31:02 +0100 Subject: [PATCH 05/32] adding guard not to cancel the previous stop loss on exchange if market is falling quickly --- freqtrade/freqtradebot.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 02b55954e..513b27755 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -649,9 +649,19 @@ class FreqtradeBot(object): trade.update(order) result = True elif self.config.get('trailing_stop', False): - # check if trailing stoploss is enabled and stoploss value has changed + # if trailing stoploss is enabled we check if stoploss value has changed # in which case we cancel stoploss order and put another one with new # value immediately + + # This is a guard: there is a situation where market is going doing down fast + # the stoploss on exchange checked previously is not hit but + # it is too late and too risky to cancel the previous stoploss + if trade.stop_loss < self.exchange.get_ticker(trade.pair)['bid']: + logger.info('stoploss on exchange update: too risky to update stoploss as ' + 'current best bid price (%s) is higher than stoploss value (%s)', + self.exchange.get_ticker(trade.pair)['bid'], trade.stop_loss) + return result + if trade.stop_loss > order['info']['stopPrice']: # we check also if the update is neccesary update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] From 1a27258469cf9fd756203dacca721886ca5297b9 Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 8 Jan 2019 16:34:23 +0100 Subject: [PATCH 06/32] condition fixed --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 513b27755..a081d364a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -656,7 +656,7 @@ class FreqtradeBot(object): # This is a guard: there is a situation where market is going doing down fast # the stoploss on exchange checked previously is not hit but # it is too late and too risky to cancel the previous stoploss - if trade.stop_loss < self.exchange.get_ticker(trade.pair)['bid']: + if trade.stop_loss <= self.exchange.get_ticker(trade.pair)['bid']: logger.info('stoploss on exchange update: too risky to update stoploss as ' 'current best bid price (%s) is higher than stoploss value (%s)', self.exchange.get_ticker(trade.pair)['bid'], trade.stop_loss) From e025ad391871b2bfcd7b6584641cd0a66cc07953 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 9 Jan 2019 16:23:13 +0100 Subject: [PATCH 07/32] temp test commit --- freqtrade/freqtradebot.py | 4 +- freqtrade/tests/test_freqtradebot.py | 66 ++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a081d364a..5169d162c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -656,12 +656,14 @@ class FreqtradeBot(object): # This is a guard: there is a situation where market is going doing down fast # the stoploss on exchange checked previously is not hit but # it is too late and too risky to cancel the previous stoploss - if trade.stop_loss <= self.exchange.get_ticker(trade.pair)['bid']: + if trade.stop_loss > self.exchange.get_ticker(trade.pair)['bid']: logger.info('stoploss on exchange update: too risky to update stoploss as ' 'current best bid price (%s) is higher than stoploss value (%s)', self.exchange.get_ticker(trade.pair)['bid'], trade.stop_loss) return result + print(trade.stop_loss) + print(order['info']['stopPrice']) if trade.stop_loss > order['info']['stopPrice']: # we check also if the update is neccesary update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 2b14c586b..5aa8c55a9 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1014,6 +1014,72 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, assert trade.is_open is False +def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, + markets, limit_buy_order, limit_sell_order) -> None: + # When trailing stoploss is set + stoploss_limit = MagicMock(return_value={'id': 13434334}) + patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_ticker=MagicMock(return_value={ + 'bid': 0.00001172, + 'ask': 0.00001173, + 'last': 0.00001172 + }), + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + sell=MagicMock(return_value={'id': limit_sell_order['id']}), + get_fee=fee, + get_markets=markets, + stoploss_limit=stoploss_limit + ) + default_conf['trailing_stop'] = True + freqtrade = FreqtradeBot(default_conf) + freqtrade.strategy.stoploss = -0.05 + patch_get_signal(freqtrade) + + freqtrade.create_trade() + trade = Trade.query.first() + trade.is_open = True + trade.open_order_id = None + trade.stoploss_order_id = 100 + + stoploss_order_hanging = MagicMock(return_value={ + 'id': 100, + 'status': 'open', + 'type': 'stop_loss_limit', + 'price': 3, + 'average': 2, + 'info' : { + 'stopPrice': 1.113399999 + } + }) + + mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hanging) + + # stoploss initially at 5% + assert freqtrade.handle_trade(trade) is False + + # price jumped 2x + trade.max_rate = trade.open_rate * 2 + assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False + + assert trade.stop_loss == (trade.open_rate * 2) * 0.95 + + stoploss_order_hit = MagicMock(return_value={ + 'status': 'open', + 'type': 'stop_loss_limit', + 'price': 3, + 'average': 2 + }) + mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hit) + assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert log_has('STOP_LOSS_LIMIT is hit for {}.'.format(trade), caplog.record_tuples) + assert trade.stoploss_order_id is None + assert trade.is_open is False + + def test_process_maybe_execute_buy(mocker, default_conf) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) From cfe00c2f0c118c93e1870567eb75c195bfa91ddd Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 15 Jan 2019 11:04:32 +0100 Subject: [PATCH 08/32] initial test added for TSL on exchange --- freqtrade/freqtradebot.py | 6 ++--- freqtrade/persistence.py | 2 ++ freqtrade/tests/test_freqtradebot.py | 39 ++++++++++++++++++---------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5169d162c..477141782 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -662,18 +662,16 @@ class FreqtradeBot(object): self.exchange.get_ticker(trade.pair)['bid'], trade.stop_loss) return result - print(trade.stop_loss) - print(order['info']['stopPrice']) if trade.stop_loss > order['info']['stopPrice']: # we check also if the update is neccesary update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] - if (datetime.now() - trade.stoploss_last_update).total_seconds > update_beat: + if (datetime.now() - trade.stoploss_last_update).total_seconds() > update_beat: # cancelling the current stoploss on exchange first if self.exchange.cancel_order(order['id'], trade.pair): # creating the new one stoploss_order_id = self.exchange.stoploss_limit( pair=trade.pair, amount=trade.amount, - stop_price=stop_price, rate=limit_price + stop_price=trade.stop_loss, rate=trade.stop_loss * 0.99 )['id'] trade.stoploss_order_id = str(stoploss_order_id) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index c466f2fc1..f9b34fc64 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -222,11 +222,13 @@ class Trade(_DECL_BASE): logger.debug("assigning new stop loss") self.stop_loss = new_loss self.initial_stop_loss = new_loss + self.stoploss_last_update = datetime.utcnow() # evaluate if the stop loss needs to be updated else: if new_loss > self.stop_loss: # stop losses only walk up, never down! self.stop_loss = new_loss + self.stoploss_last_update = datetime.utcnow() logger.debug("adjusted stop loss") else: logger.debug("keeping current stop loss") diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 5aa8c55a9..6d6e5fe91 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1033,9 +1033,21 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, get_markets=markets, stoploss_limit=stoploss_limit ) + + # enabling TSL default_conf['trailing_stop'] = True + + # disabling ROI + default_conf['minimal_roi']['0'] = 999999999 + freqtrade = FreqtradeBot(default_conf) + + # setting stoploss freqtrade.strategy.stoploss = -0.05 + + # setting stoploss_on_exchange_interval + freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0 + patch_get_signal(freqtrade) freqtrade.create_trade() @@ -1051,7 +1063,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, 'price': 3, 'average': 2, 'info' : { - 'stopPrice': 1.113399999 + 'stopPrice': 0.000011134 } }) @@ -1061,23 +1073,22 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, assert freqtrade.handle_trade(trade) is False # price jumped 2x - trade.max_rate = trade.open_rate * 2 + mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(return_value={ + 'bid': 0.00002344, + 'ask': 0.00002346, + 'last': 0.00002344 + })) + assert freqtrade.handle_trade(trade) is False - assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert trade.stop_loss == 0.00002344 * 0.95 - assert trade.stop_loss == (trade.open_rate * 2) * 0.95 + mocker.patch('freqtrade.exchange.Exchange.cancel_order', MagicMock(return_value={ + "orderId": 100, + "status": "CANCELED", + })) - stoploss_order_hit = MagicMock(return_value={ - 'status': 'open', - 'type': 'stop_loss_limit', - 'price': 3, - 'average': 2 - }) - mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hit) assert freqtrade.handle_stoploss_on_exchange(trade) is False - assert log_has('STOP_LOSS_LIMIT is hit for {}.'.format(trade), caplog.record_tuples) - assert trade.stoploss_order_id is None - assert trade.is_open is False + assert freqtrade.exchange.cancel_order.call_count == 1 def test_process_maybe_execute_buy(mocker, default_conf) -> None: From f0cfab79404048366a225af2316365eb5fbdf433 Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 15 Jan 2019 11:10:28 +0100 Subject: [PATCH 09/32] flaking 8 --- freqtrade/tests/test_freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 6d6e5fe91..40f5f3327 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1062,7 +1062,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, 'type': 'stop_loss_limit', 'price': 3, 'average': 2, - 'info' : { + 'info': { 'stopPrice': 0.000011134 } }) From 12e81080159343025717803a2ec3a5c1d8d2fc0f Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 15 Jan 2019 15:36:41 +0100 Subject: [PATCH 10/32] checking params of cancel order and stop loss order --- freqtrade/freqtradebot.py | 8 +++++--- freqtrade/tests/test_freqtradebot.py | 15 ++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 477141782..23e1e422b 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -657,9 +657,11 @@ class FreqtradeBot(object): # the stoploss on exchange checked previously is not hit but # it is too late and too risky to cancel the previous stoploss if trade.stop_loss > self.exchange.get_ticker(trade.pair)['bid']: - logger.info('stoploss on exchange update: too risky to update stoploss as ' - 'current best bid price (%s) is higher than stoploss value (%s)', - self.exchange.get_ticker(trade.pair)['bid'], trade.stop_loss) + logger.warning( + 'stoploss on exchange update: too risky to update stoploss as ' + 'current best bid price (%s) is higher than stoploss value (%s).', + self.exchange.get_ticker(trade.pair)['bid'], trade.stop_loss + ) return result if trade.stop_loss > order['info']['stopPrice']: diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 40f5f3327..d0a708865 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1082,13 +1082,18 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, assert freqtrade.handle_trade(trade) is False assert trade.stop_loss == 0.00002344 * 0.95 - mocker.patch('freqtrade.exchange.Exchange.cancel_order', MagicMock(return_value={ - "orderId": 100, - "status": "CANCELED", - })) + cancel_order_mock = MagicMock() + stoploss_order_mock = MagicMock() + mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order_mock) + mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_order_mock) assert freqtrade.handle_stoploss_on_exchange(trade) is False - assert freqtrade.exchange.cancel_order.call_count == 1 + + cancel_order_mock.assert_called_once_with(100, 'ETH/BTC') + stoploss_order_mock.assert_called_once_with(amount=85.25149190110828, + pair='ETH/BTC', + rate=0.00002344 * 0.95 * 0.99, + stop_price=0.00002344 * 0.95) def test_process_maybe_execute_buy(mocker, default_conf) -> None: From 1cd5abde3789a79c7acb2182ff06f716e19f94a7 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 11:22:25 +0100 Subject: [PATCH 11/32] removing unnecessary guard --- freqtrade/freqtradebot.py | 12 ------------ freqtrade/tests/test_freqtradebot.py | 1 + 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 23e1e422b..2a1415e09 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -652,18 +652,6 @@ class FreqtradeBot(object): # if trailing stoploss is enabled we check if stoploss value has changed # in which case we cancel stoploss order and put another one with new # value immediately - - # This is a guard: there is a situation where market is going doing down fast - # the stoploss on exchange checked previously is not hit but - # it is too late and too risky to cancel the previous stoploss - if trade.stop_loss > self.exchange.get_ticker(trade.pair)['bid']: - logger.warning( - 'stoploss on exchange update: too risky to update stoploss as ' - 'current best bid price (%s) is higher than stoploss value (%s).', - self.exchange.get_ticker(trade.pair)['bid'], trade.stop_loss - ) - return result - if trade.stop_loss > order['info']['stopPrice']: # we check also if the update is neccesary update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index d0a708865..b86d76553 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1071,6 +1071,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, # stoploss initially at 5% assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False # price jumped 2x mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(return_value={ From 29439c05d66fd069b17e8d6b8f47dd1ca053ffdc Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 11:51:54 +0100 Subject: [PATCH 12/32] adding update beat test --- freqtrade/freqtradebot.py | 2 +- freqtrade/tests/test_freqtradebot.py | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 2a1415e09..6334aef04 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -655,7 +655,7 @@ class FreqtradeBot(object): if trade.stop_loss > order['info']['stopPrice']: # we check also if the update is neccesary update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] - if (datetime.now() - trade.stoploss_last_update).total_seconds() > update_beat: + if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() > update_beat: # cancelling the current stoploss on exchange first if self.exchange.cancel_order(order['id'], trade.pair): # creating the new one diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index b86d76553..dcdae16fe 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1045,8 +1045,8 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, # setting stoploss freqtrade.strategy.stoploss = -0.05 - # setting stoploss_on_exchange_interval - freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0 + # setting stoploss_on_exchange_interval to 60 seconds + freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60 patch_get_signal(freqtrade) @@ -1080,14 +1080,24 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, 'last': 0.00002344 })) - assert freqtrade.handle_trade(trade) is False - assert trade.stop_loss == 0.00002344 * 0.95 cancel_order_mock = MagicMock() stoploss_order_mock = MagicMock() mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order_mock) mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_order_mock) + # stoploss should not be updated as the interval is 60 seconds + assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False + cancel_order_mock.assert_not_called() + stoploss_order_mock.assert_not_called() + + assert freqtrade.handle_trade(trade) is False + assert trade.stop_loss == 0.00002344 * 0.95 + + # setting stoploss_on_exchange_interval to 0 seconds + freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0 + assert freqtrade.handle_stoploss_on_exchange(trade) is False cancel_order_mock.assert_called_once_with(100, 'ETH/BTC') From bfb71215834fcc817bba9e5478afc15c25cc915b Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 12:16:32 +0100 Subject: [PATCH 13/32] refactoring handle_stoploss_on_exchange --- freqtrade/freqtradebot.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 6334aef04..25951aba5 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -652,21 +652,24 @@ class FreqtradeBot(object): # if trailing stoploss is enabled we check if stoploss value has changed # in which case we cancel stoploss order and put another one with new # value immediately - if trade.stop_loss > order['info']['stopPrice']: - # we check also if the update is neccesary - update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] - if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() > update_beat: - # cancelling the current stoploss on exchange first - if self.exchange.cancel_order(order['id'], trade.pair): - # creating the new one - stoploss_order_id = self.exchange.stoploss_limit( - pair=trade.pair, amount=trade.amount, - stop_price=trade.stop_loss, rate=trade.stop_loss * 0.99 - )['id'] - trade.stoploss_order_id = str(stoploss_order_id) + handle_trailing_stoploss_on_exchange(trade, order) return result + def handle_trailing_stoploss_on_exchange(self, trade: Trade, order): + if trade.stop_loss > order['info']['stopPrice']: + # we check if the update is neccesary + update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] + if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() > update_beat: + # cancelling the current stoploss on exchange first + if self.exchange.cancel_order(order['id'], trade.pair): + # creating the new one + stoploss_order_id = self.exchange.stoploss_limit( + pair=trade.pair, amount=trade.amount, + stop_price=trade.stop_loss, rate=trade.stop_loss * 0.99 + )['id'] + trade.stoploss_order_id = str(stoploss_order_id) + def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool: if self.edge: stoploss = self.edge.stoploss(trade.pair) From 6d588b3b0bf0f464a5cd86bb435c4887773823f1 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 14:28:52 +0100 Subject: [PATCH 14/32] trailing stop loss on exchange extracted to a separate function --- freqtrade/freqtradebot.py | 2 +- freqtrade/tests/test_freqtradebot.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 25951aba5..544e4a997 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -652,7 +652,7 @@ class FreqtradeBot(object): # if trailing stoploss is enabled we check if stoploss value has changed # in which case we cancel stoploss order and put another one with new # value immediately - handle_trailing_stoploss_on_exchange(trade, order) + self.handle_trailing_stoploss_on_exchange(trade, order) return result diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index dcdae16fe..fe2dd0ec2 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1080,7 +1080,6 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, 'last': 0.00002344 })) - cancel_order_mock = MagicMock() stoploss_order_mock = MagicMock() mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order_mock) From cffc9ce890deae1db17d79ca4ad0cef5ce31f8e1 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 14:49:47 +0100 Subject: [PATCH 15/32] last step: stop loss on exchange added to trailing --- freqtrade/strategy/interface.py | 16 ++++++++-------- freqtrade/tests/test_freqtradebot.py | 3 +++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 7afb0c59f..f3ff9ba7a 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -234,12 +234,9 @@ class IStrategy(ABC): current_rate = low or rate current_profit = trade.calc_profit_percent(current_rate) - if self.order_types.get('stoploss_on_exchange'): - stoplossflag = SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) - else: - stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, - current_time=date, current_profit=current_profit, - force_stoploss=force_stoploss) + stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, + current_time=date, current_profit=current_profit, + force_stoploss=force_stoploss) if stoplossflag.sell_flag: return stoplossflag @@ -281,8 +278,11 @@ class IStrategy(ABC): trade.adjust_stop_loss(trade.open_rate, force_stoploss if force_stoploss else self.stoploss, initial=True) - # evaluate if the stoploss was hit - if self.stoploss is not None and trade.stop_loss >= current_rate: + # evaluate if the stoploss was hit if stoploss is not on exchange + if self.stoploss is not None and \ + trade.stop_loss >= current_rate and \ + not self.order_types.get('stoploss_on_exchange'): + selltype = SellType.STOP_LOSS # If Trailing stop (and max-rate did move above open rate) if trailing_stop and trade.open_rate != trade.max_rate: diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index fe2dd0ec2..569477fb1 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1042,6 +1042,9 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, freqtrade = FreqtradeBot(default_conf) + # enabling stoploss on exchange + freqtrade.strategy.order_types['stoploss_on_exchange'] = True + # setting stoploss freqtrade.strategy.stoploss = -0.05 From aa03a864f70b92320080abd444225d0a4fc734ba Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 15:00:35 +0100 Subject: [PATCH 16/32] comments added for TSL on exchange function --- freqtrade/freqtradebot.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 544e4a997..682a850b8 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -657,6 +657,14 @@ class FreqtradeBot(object): return result def handle_trailing_stoploss_on_exchange(self, trade: Trade, order): + """ + Check to see if stoploss on exchange should be updated + in case of trailing stoploss on exchange + :param Trade: Corresponding Trade + :param order: Current on exchange stoploss order + :return: None + """ + if trade.stop_loss > order['info']['stopPrice']: # we check if the update is neccesary update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] From baa5cc5b9e4ccff978a6ea100b70d63d409d6738 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 15:10:31 +0100 Subject: [PATCH 17/32] logs enriched --- freqtrade/exchange/__init__.py | 5 ++++- freqtrade/freqtradebot.py | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index c74b32ad2..588504894 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -402,8 +402,11 @@ class Exchange(object): return self._dry_run_open_orders[order_id] try: - return self._api.create_order(pair, 'stop_loss_limit', 'sell', + order = self._api.create_order(pair, 'stop_loss_limit', 'sell', amount, rate, {'stopPrice': stop_price}) + logger.info('stoploss limit order added for %s. ' + 'stop price: %s. limit: %s' % (pair,stop_price,rate)) + return order except ccxt.InsufficientFunds as e: raise DependencyException( diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 682a850b8..dc5f3d8ad 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -670,6 +670,8 @@ class FreqtradeBot(object): update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() > update_beat: # cancelling the current stoploss on exchange first + logger.info('Trailing stoploss: cancelling current stoploss on exchange ' + 'in order to add another one ...') if self.exchange.cancel_order(order['id'], trade.pair): # creating the new one stoploss_order_id = self.exchange.stoploss_limit( From 50bc20134f67fdc56c18352cd85849929f815c92 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 15:17:28 +0100 Subject: [PATCH 18/32] adding whitespace --- freqtrade/exchange/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 588504894..094d2cc29 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -403,7 +403,7 @@ class Exchange(object): try: order = self._api.create_order(pair, 'stop_loss_limit', 'sell', - amount, rate, {'stopPrice': stop_price}) + amount, rate, {'stopPrice': stop_price}) logger.info('stoploss limit order added for %s. ' 'stop price: %s. limit: %s' % (pair,stop_price,rate)) return order From 611b48dbb99e86a53b948a5e468767d046e1459d Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 16:15:36 +0100 Subject: [PATCH 19/32] fix return value from info hash: value is in string --- freqtrade/freqtradebot.py | 2 +- freqtrade/tests/test_freqtradebot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index dc5f3d8ad..9b5ab4bba 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -665,7 +665,7 @@ class FreqtradeBot(object): :return: None """ - if trade.stop_loss > order['info']['stopPrice']: + if trade.stop_loss > float(order['info']['stopPrice']): # we check if the update is neccesary update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() > update_beat: diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 569477fb1..6c25b1a7f 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1066,7 +1066,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, 'price': 3, 'average': 2, 'info': { - 'stopPrice': 0.000011134 + 'stopPrice': '0.000011134' } }) From 5e2e96acd2aa3e3cf01ae2a6291ebea917122734 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 18:38:20 +0100 Subject: [PATCH 20/32] compatibility with edge added --- freqtrade/exchange/__init__.py | 2 +- freqtrade/strategy/interface.py | 5 +- freqtrade/tests/test_freqtradebot.py | 110 +++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 094d2cc29..90a33a226 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -405,7 +405,7 @@ class Exchange(object): order = self._api.create_order(pair, 'stop_loss_limit', 'sell', amount, rate, {'stopPrice': stop_price}) logger.info('stoploss limit order added for %s. ' - 'stop price: %s. limit: %s' % (pair,stop_price,rate)) + 'stop price: %s. limit: %s' % (pair, stop_price, rate)) return order except ccxt.InsufficientFunds as e: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 6dbdf72e8..88029f4d4 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -274,7 +274,6 @@ class IStrategy(ABC): """ trailing_stop = self.config.get('trailing_stop', False) - trade.adjust_stop_loss(trade.open_rate, force_stoploss if force_stoploss else self.stoploss, initial=True) @@ -282,7 +281,6 @@ class IStrategy(ABC): if self.stoploss is not None and \ trade.stop_loss >= current_rate and \ not self.order_types.get('stoploss_on_exchange'): - selltype = SellType.STOP_LOSS # If Trailing stop (and max-rate did move above open rate) if trailing_stop and trade.open_rate != trade.max_rate: @@ -302,7 +300,8 @@ class IStrategy(ABC): # check if we have a special stop loss for positive condition # and if profit is positive - stop_loss_value = self.stoploss + stop_loss_value = force_stoploss if force_stoploss else self.stoploss + sl_offset = self.config.get('trailing_stop_positive_offset', 0.0) if 'trailing_stop_positive' in self.config and current_profit > sl_offset: diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 6c25b1a7f..1149a69e9 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1109,6 +1109,116 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, stop_price=0.00002344 * 0.95) +def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, + markets, limit_buy_order, limit_sell_order) -> None: + + # When trailing stoploss is set + stoploss_limit = MagicMock(return_value={'id': 13434334}) + patch_RPCManager(mocker) + patch_exchange(mocker) + patch_edge(mocker) + + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_ticker=MagicMock(return_value={ + 'bid': 0.00001172, + 'ask': 0.00001173, + 'last': 0.00001172 + }), + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + sell=MagicMock(return_value={'id': limit_sell_order['id']}), + get_fee=fee, + get_markets=markets, + stoploss_limit=stoploss_limit + ) + + # enabling TSL + edge_conf['trailing_stop'] = True + edge_conf['trailing_stop_positive'] = 0.01 + edge_conf['trailing_stop_positive_offset'] = 0.011 + + # disabling ROI + edge_conf['minimal_roi']['0'] = 999999999 + + freqtrade = FreqtradeBot(edge_conf) + + # enabling stoploss on exchange + freqtrade.strategy.order_types['stoploss_on_exchange'] = True + + # setting stoploss + freqtrade.strategy.stoploss = -0.02 + + # setting stoploss_on_exchange_interval to 0 second + freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0 + + patch_get_signal(freqtrade) + + freqtrade.active_pair_whitelist = freqtrade.edge.adjust(freqtrade.active_pair_whitelist) + + freqtrade.create_trade() + trade = Trade.query.first() + trade.is_open = True + trade.open_order_id = None + trade.stoploss_order_id = 100 + + stoploss_order_hanging = MagicMock(return_value={ + 'id': 100, + 'status': 'open', + 'type': 'stop_loss_limit', + 'price': 3, + 'average': 2, + 'info': { + 'stopPrice': '0.000009384' + } + }) + + mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hanging) + + # stoploss initially at 20% as edge dictated it. + assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert trade.stop_loss == 0.000009384 + + cancel_order_mock = MagicMock() + stoploss_order_mock = MagicMock() + mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order_mock) + mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_order_mock) + + # price goes down 5% + mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(return_value={ + 'bid': 0.00001172 * 0.95, + 'ask': 0.00001173 * 0.95, + 'last': 0.00001172 * 0.95 + })) + + assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False + + # stoploss should remain the same + assert trade.stop_loss == 0.000009384 + + # stoploss on exchange should not be canceled + cancel_order_mock.assert_not_called() + + # price jumped 2x + mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(return_value={ + 'bid': 0.00002344, + 'ask': 0.00002346, + 'last': 0.00002344 + })) + + assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False + + # stoploss should be set to 1% as trailing is on + assert trade.stop_loss == 0.00002344 * 0.99 + cancel_order_mock.assert_called_once_with(100, 'NEO/BTC') + stoploss_order_mock.assert_called_once_with(amount=2131074.168797954, + pair='NEO/BTC', + rate=0.00002344 * 0.99 * 0.99, + stop_price=0.00002344 * 0.99) + + def test_process_maybe_execute_buy(mocker, default_conf) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) From 91c714c7d17907afc9404075eb78ef87d634d984 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 18:58:12 +0100 Subject: [PATCH 21/32] stoploss_on_exchange_interval added to full config --- config_full.json.example | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config_full.json.example b/config_full.json.example index 0427f8700..234722f82 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -37,7 +37,8 @@ "buy": "limit", "sell": "limit", "stoploss": "market", - "stoploss_on_exchange": "false" + "stoploss_on_exchange": "false", + "stoploss_on_exchange_interval": 60 }, "order_time_in_force": { "buy": "gtc", From da51ef40f804db5cbd7e711f345e4459d3d87379 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 19:04:43 +0100 Subject: [PATCH 22/32] SL interval added to CONF_SCHEMA --- freqtrade/constants.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index b2393c2b7..9e5561ba3 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -112,7 +112,8 @@ CONF_SCHEMA = { 'buy': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, 'sell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, 'stoploss': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, - 'stoploss_on_exchange': {'type': 'boolean'} + 'stoploss_on_exchange': {'type': 'boolean'}, + 'stoploss_on_exchange_interval': {'type': 'boolean'} }, 'required': ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] }, @@ -137,7 +138,7 @@ CONF_SCHEMA = { 'pairlist': { 'type': 'object', 'properties': { - 'method': {'type': 'string', 'enum': AVAILABLE_PAIRLISTS}, + 'method': {'type': 'string', 'enum': AVAILABLE_PAIRLISTS}, 'config': {'type': 'object'} }, 'required': ['method'] From 08d98773f39503f01506214fd2d39485abefcbf0 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 19:10:13 +0100 Subject: [PATCH 23/32] added SL interval to configuration document --- docs/configuration.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index a7deaa60c..2f72da794 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -144,10 +144,10 @@ end up paying more then would probably have been necessary. ### Understand order_types -`order_types` contains a dict mapping order-types to market-types as well as stoploss on or off exchange type. This allows to buy using limit orders, sell using limit-orders, and create stoploss orders using market. It also allows to set the stoploss "on exchange" which means stoploss order would be placed immediately once the buy order is fulfilled. +`order_types` contains a dict mapping order-types to market-types as well as stoploss on or off exchange type and stoploss on exchange update interval in seconds. This allows to buy using limit orders, sell using limit-orders, and create stoploss orders using market. It also allows to set the stoploss "on exchange" which means stoploss order would be placed immediately once the buy order is fulfilled. In case stoploss on exchange is set then the bot will use `stoploss_on_exchange_interval` to check it periodically and update it if necessary (e.x. in case of trailing stoploss). This can be set in the configuration or in the strategy. Configuration overwrites strategy configurations. -If this is configured, all 4 values (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`) need to be present, otherwise the bot warn about it and will fail to start. +If this is configured, all 4 values (`"buy"`, `"sell"`, `"stoploss"` and `"stoploss_on_exchange"`) need to be present, otherwise the bot warn about it and will fail to start. The below is the default which is used if this is not configured in either Strategy or configuration. ```python @@ -155,7 +155,8 @@ The below is the default which is used if this is not configured in either Strat "buy": "limit", "sell": "limit", "stoploss": "market", - "stoploss_on_exchange": False + "stoploss_on_exchange": False, + "stoploss_on_exchange_interval": 60 }, ``` @@ -163,6 +164,9 @@ The below is the default which is used if this is not configured in either Strat Not all exchanges support "market" orders. The following message will be shown if your exchange does not support market orders: `"Exchange does not support market orders."` +!!! Note + stoploss on exchange interval is not mandatory. Do not change it's value if you are unsure of what you are doing. + ### Understand order_time_in_force `order_time_in_force` defines the policy by which the order is executed on the exchange. Three commonly used time in force are:
**GTC (Goog Till Canceled):** From 2533112254f1fe183f993630a3b1491eb4bd72ce Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 19:12:52 +0100 Subject: [PATCH 24/32] added referral to stop loss documentation --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 2f72da794..bad8d8b4e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -165,7 +165,7 @@ The below is the default which is used if this is not configured in either Strat The following message will be shown if your exchange does not support market orders: `"Exchange does not support market orders."` !!! Note - stoploss on exchange interval is not mandatory. Do not change it's value if you are unsure of what you are doing. + stoploss on exchange interval is not mandatory. Do not change it's value if you are unsure of what you are doing. For more information about how stoploss works please read [the stoploss documentation](stoploss.md). ### Understand order_time_in_force `order_time_in_force` defines the policy by which the order is executed on the exchange. Three commonly used time in force are:
From e682eceae4f5d7b89de1d98c5834132dd629cac6 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 19:22:06 +0100 Subject: [PATCH 25/32] stop loss documentation added --- docs/stoploss.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/stoploss.md b/docs/stoploss.md index 0278e7bbb..6ee7dd6e8 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -2,12 +2,17 @@ At this stage the bot contains the following stoploss support modes: -1. static stop loss, defined in either the strategy or configuration -2. trailing stop loss, defined in the configuration -3. trailing stop loss, custom positive loss, defined in configuration +1. static stop loss, defined in either the strategy or configuration. +2. trailing stop loss, defined in the configuration. +3. trailing stop loss, custom positive loss, defined in configuration. !!! Note - All stoploss properties can be configured in eihter Strategy or configuration. Configuration values override strategy values. + All stoploss properties can be configured in either Strategy or configuration. Configuration values override strategy values. + +Those stoploss modes can be `on exchange` or `off exchange`. If the stoploss is `on exchange` it means a stoploss limit order is placed on the exchange immediately after buy order happens successfuly. This will protect you against sudden crashes in market as the order will be in the queue immediately and if market goes down then the order has more chance of being fulfilled. + +In case of stoploss on exchange there is another parameter called `stoploss_on_exchange_interval`. That means the interval in seconds the bot will check the stoploss and update it if necessary. As an example in case of trailing stoploss if the order is on the exchange and the market is going up then the bot automatically cancels the previous stoploss order and put a new one with a stop value higher than previous one. It is clear that the bot cannot do it every 5 seconds otherwise it gets banned. So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute). + ## Static Stop Loss From 9d6c54791b5a1d8d2ee2b824c19368c322d03a84 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 19:23:55 +0100 Subject: [PATCH 26/32] added note for only binance --- docs/stoploss.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/stoploss.md b/docs/stoploss.md index 6ee7dd6e8..70ce6b0f6 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -13,6 +13,9 @@ Those stoploss modes can be `on exchange` or `off exchange`. If the stoploss is In case of stoploss on exchange there is another parameter called `stoploss_on_exchange_interval`. That means the interval in seconds the bot will check the stoploss and update it if necessary. As an example in case of trailing stoploss if the order is on the exchange and the market is going up then the bot automatically cancels the previous stoploss order and put a new one with a stop value higher than previous one. It is clear that the bot cannot do it every 5 seconds otherwise it gets banned. So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute). +!!! Note + Stoploss on exchange is only supported for Binance as of now. + ## Static Stop Loss From e41e45413faf518bc0cf0fb86b43640cb3e09a46 Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 18 Jan 2019 11:58:23 +0100 Subject: [PATCH 27/32] adding tailing_stop to docs --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index bad8d8b4e..c3843cc2a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -144,7 +144,7 @@ end up paying more then would probably have been necessary. ### Understand order_types -`order_types` contains a dict mapping order-types to market-types as well as stoploss on or off exchange type and stoploss on exchange update interval in seconds. This allows to buy using limit orders, sell using limit-orders, and create stoploss orders using market. It also allows to set the stoploss "on exchange" which means stoploss order would be placed immediately once the buy order is fulfilled. In case stoploss on exchange is set then the bot will use `stoploss_on_exchange_interval` to check it periodically and update it if necessary (e.x. in case of trailing stoploss). +`order_types` contains a dict mapping order-types to market-types as well as stoploss on or off exchange type and stoploss on exchange update interval in seconds. This allows to buy using limit orders, sell using limit-orders, and create stoploss orders using market. It also allows to set the stoploss "on exchange" which means stoploss order would be placed immediately once the buy order is fulfilled. In case stoploss on exchange and `trailing_stop` are both set, then the bot will use `stoploss_on_exchange_interval` to check it periodically and update it if necessary (e.x. in case of trailing stoploss). This can be set in the configuration or in the strategy. Configuration overwrites strategy configurations. If this is configured, all 4 values (`"buy"`, `"sell"`, `"stoploss"` and `"stoploss_on_exchange"`) need to be present, otherwise the bot warn about it and will fail to start. From 1c4ee35ecab03f4a4b1295f8b71bdd89788a4a20 Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 18 Jan 2019 12:00:02 +0100 Subject: [PATCH 28/32] =?UTF-8?q?using=20italic=20for=20=E2=80=9Coff=20exc?= =?UTF-8?q?hange=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/stoploss.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/stoploss.md b/docs/stoploss.md index 70ce6b0f6..78a486ab5 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -9,7 +9,7 @@ At this stage the bot contains the following stoploss support modes: !!! Note All stoploss properties can be configured in either Strategy or configuration. Configuration values override strategy values. -Those stoploss modes can be `on exchange` or `off exchange`. If the stoploss is `on exchange` it means a stoploss limit order is placed on the exchange immediately after buy order happens successfuly. This will protect you against sudden crashes in market as the order will be in the queue immediately and if market goes down then the order has more chance of being fulfilled. +Those stoploss modes can be *on exchange* or *off exchange*. If the stoploss is *on exchange* it means a stoploss limit order is placed on the exchange immediately after buy order happens successfuly. This will protect you against sudden crashes in market as the order will be in the queue immediately and if market goes down then the order has more chance of being fulfilled. In case of stoploss on exchange there is another parameter called `stoploss_on_exchange_interval`. That means the interval in seconds the bot will check the stoploss and update it if necessary. As an example in case of trailing stoploss if the order is on the exchange and the market is going up then the bot automatically cancels the previous stoploss order and put a new one with a stop value higher than previous one. It is clear that the bot cannot do it every 5 seconds otherwise it gets banned. So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute). From 89eddfd349d4ac7b04c519d61aed45f6e047273d Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 18 Jan 2019 12:00:53 +0100 Subject: [PATCH 29/32] =?UTF-8?q?refactoring=20english=20=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/stoploss.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/stoploss.md b/docs/stoploss.md index 78a486ab5..0726aebbc 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -11,7 +11,7 @@ At this stage the bot contains the following stoploss support modes: Those stoploss modes can be *on exchange* or *off exchange*. If the stoploss is *on exchange* it means a stoploss limit order is placed on the exchange immediately after buy order happens successfuly. This will protect you against sudden crashes in market as the order will be in the queue immediately and if market goes down then the order has more chance of being fulfilled. -In case of stoploss on exchange there is another parameter called `stoploss_on_exchange_interval`. That means the interval in seconds the bot will check the stoploss and update it if necessary. As an example in case of trailing stoploss if the order is on the exchange and the market is going up then the bot automatically cancels the previous stoploss order and put a new one with a stop value higher than previous one. It is clear that the bot cannot do it every 5 seconds otherwise it gets banned. So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute). +In case of stoploss on exchange there is another parameter called `stoploss_on_exchange_interval`. This configures the interval in seconds at which the bot will check the stoploss and update it if necessary. As an example in case of trailing stoploss if the order is on the exchange and the market is going up then the bot automatically cancels the previous stoploss order and put a new one with a stop value higher than previous one. It is clear that the bot cannot do it every 5 seconds otherwise it gets banned. So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute). !!! Note Stoploss on exchange is only supported for Binance as of now. From 70780bb01e491945a02f3ba73975a513a95fbad4 Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 18 Jan 2019 12:02:29 +0100 Subject: [PATCH 30/32] using dict.get to fetch interval --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 9b5ab4bba..56b518472 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -667,7 +667,7 @@ class FreqtradeBot(object): if trade.stop_loss > float(order['info']['stopPrice']): # we check if the update is neccesary - update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] + update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60) if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() > update_beat: # cancelling the current stoploss on exchange first logger.info('Trailing stoploss: cancelling current stoploss on exchange ' From a2618208ef78427fe334c90b9a875c67c6664ec4 Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 18 Jan 2019 12:07:51 +0100 Subject: [PATCH 31/32] wrapping in parantheses instead of line breaks --- freqtrade/strategy/interface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 88029f4d4..08a5cf1cd 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -278,9 +278,9 @@ class IStrategy(ABC): else self.stoploss, initial=True) # evaluate if the stoploss was hit if stoploss is not on exchange - if self.stoploss is not None and \ - trade.stop_loss >= current_rate and \ - not self.order_types.get('stoploss_on_exchange'): + if ((self.stoploss is not None) and + (trade.stop_loss >= current_rate) and + (not self.order_types.get('stoploss_on_exchange'))): selltype = SellType.STOP_LOSS # If Trailing stop (and max-rate did move above open rate) if trailing_stop and trade.open_rate != trade.max_rate: From 87329a393c444937dda6e0487f5b888e2cba62fc Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 18 Jan 2019 12:14:00 +0100 Subject: [PATCH 32/32] adding stop loss last update to test persistence --- freqtrade/tests/test_persistence.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index e64a08262..be6efc2ff 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -516,6 +516,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert trade.strategy is None assert trade.ticker_interval is None assert trade.stoploss_order_id is None + assert trade.stoploss_last_update is None assert log_has("trying trades_bak1", caplog.record_tuples) assert log_has("trying trades_bak2", caplog.record_tuples) assert log_has("Running database migration - backup available as trades_bak2",