diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 281b22bc5..1e5dfd175 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -572,6 +572,17 @@ class FreqtradeBot(object): trade.update(order) + # Check if stoploss on exchnage is hit first + if self.strategy.stoploss_on_exchange and trade.stoploss_order_id: + # Check if stoploss is hit + result = self.handle_stoploss_on_exchage(trade) + + # Updating wallets if stoploss is hit + if result: + self.wallets.update() + + return result + if trade.is_open and trade.open_order_id is None: # Check if we can sell our current pair result = self.handle_trade(trade) @@ -676,6 +687,19 @@ class FreqtradeBot(object): logger.info('Found no sell signals for whitelisted currencies. Trying again..') return False + def handle_stoploss_on_exchage(self, trade: Trade) -> bool: + if not trade.is_open: + raise ValueError(f'attempt to handle stoploss on exchnage for a closed trade: {trade}') + + logger.debug('Handling stoploss on exchange %s ...', trade) + order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) + if order['status'] == 'closed': + trade.sell_reason = SellType.STOPLOSS_ON_EXCHNAGE.value + trade.update(order) + return True + else: + return False + def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool: if self.edge: stoploss = self.edge.stoploss(trade.pair) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index db6d526c7..02caeeccd 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -178,7 +178,7 @@ class Trade(_DECL_BASE): # absolute value of the initial stop loss initial_stop_loss = Column(Float, nullable=True, default=0.0) # absolute value of the highest reached price - stoploss_order_id = Column(Integer, nullable=True, index=True) + stoploss_order_id = Column(String, nullable=True, index=True) max_rate = Column(Float, nullable=True, default=0.0) sell_reason = Column(String, nullable=True) strategy = Column(String, nullable=True) @@ -250,6 +250,9 @@ class Trade(_DECL_BASE): self.open_order_id = None elif order_type == 'limit' and order['side'] == 'sell': self.close(order['price']) + elif order_type == 'stop_loss_limit': + self.stoploss_order_id = None + self.close(order['price']) else: raise ValueError(f'Unknown order type: {order_type}') cleanup() diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 30fc62f42..d1e22850c 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -33,6 +33,7 @@ class SellType(Enum): """ ROI = "roi" STOP_LOSS = "stop_loss" + STOPLOSS_ON_EXCHNAGE = "stoploss_on_exchange" TRAILING_STOP_LOSS = "trailing_stop_loss" SELL_SIGNAL = "sell_signal" FORCE_SELL = "force_sell" diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index aad5c371f..48918645d 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -931,7 +931,7 @@ def test_execute_buy_with_stoploss_on_exchange(mocker, default_conf, trade = Trade.query.first() assert trade.is_open is True - assert trade.stoploss_order_id == 13434334 + assert trade.stoploss_order_id == '13434334' def test_process_maybe_execute_buy(mocker, default_conf) -> None: @@ -1572,7 +1572,8 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, get_ticker=ticker_sell_up ) - freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI) + freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], + sell_reason=SellType.SELL_SIGNAL) trade = Trade.query.first() assert trade @@ -1580,6 +1581,76 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, assert rpc_mock.call_count == 2 +def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, + ticker, fee, + limit_buy_order, + markets, mocker) -> None: + default_conf['exchange']['name'] = 'binance' + rpc_mock = patch_RPCManager(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + _load_markets=MagicMock(return_value={}), + get_ticker=ticker, + get_fee=fee, + get_markets=markets + ) + + stoploss_limit = MagicMock(return_value={ + 'id': 123, + 'info': { + 'foo': 'bar' + } + }) + + mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_limit) + + freqtrade = FreqtradeBot(default_conf) + freqtrade.strategy.stoploss_on_exchange = True + patch_get_signal(freqtrade) + + # Create some test data + freqtrade.create_trade() + + trade = Trade.query.first() + assert trade + assert trade.stoploss_order_id == '123' + assert trade.open_order_id is not None + + trade.update(limit_buy_order) + + # Assuming stoploss on exchnage is hit + # stoploss_order_id should become None + # and trade should be sold at the price of stoploss + stoploss_limit_executed = MagicMock(return_value={ + "id": "123", + "timestamp": 1542707426845, + "datetime": "2018-11-20T09:50:26.845Z", + "lastTradeTimestamp": None, + "symbol": "BTC/USDT", + "type": "stop_loss_limit", + "side": "sell", + "price": 1.08801, + "amount": 90.99181074, + "cost": 99.0000000032274, + "average": 1.08801, + "filled": 90.99181074, + "remaining": 0.0, + "status": "closed", + "fee": None, + "trades": None + }) + mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_limit_executed) + + freqtrade.process_maybe_execute_sell(trade) + assert trade.stoploss_order_id is None + assert trade.is_open is False + print(trade.sell_reason) + assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHNAGE.value + assert rpc_mock.call_count == 1 + + def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, mocker) -> None: rpc_mock = patch_RPCManager(mocker)