From 6bbe63eb62bafea5a7c2abd76d49df5946f2c04f Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 26 Jul 2021 00:01:57 -0600 Subject: [PATCH] updated exit_positions, handle_trade, create_stoploss_order. Changed execute_sell, _check_and_execute_sell to execute_exit --- freqtrade/exchange/binance.py | 8 +- freqtrade/exchange/exchange.py | 2 +- freqtrade/exchange/ftx.py | 7 +- freqtrade/exchange/kraken.py | 6 +- freqtrade/freqtradebot.py | 179 ++++++++++++++++---------------- freqtrade/rpc/rpc.py | 4 +- tests/exchange/test_binance.py | 21 ++-- tests/exchange/test_exchange.py | 2 +- tests/exchange/test_ftx.py | 20 ++-- tests/exchange/test_kraken.py | 16 +-- tests/test_freqtradebot.py | 81 ++++++++------- tests/test_integration.py | 2 +- 12 files changed, 183 insertions(+), 165 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 1ca1661c6..7243c1cb6 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -28,20 +28,24 @@ class Binance(Exchange): maintenance_margin_formula = LiqFormula.BINANCE - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. + :param side: "buy" or "sell" """ + # TODO-mg: Short support return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice']) @retrier(retries=0) - def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: + def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict, side: str) -> Dict: """ creates a stoploss limit order. this stoploss-limit is binance-specific. It may work with a limited number of other exchanges, but this has not been tested yet. + :param side: "buy" or "sell" """ + # TODO-mg: Short support # Limit price threshold: As limit price should always be below stop-price limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) rate = stop_price * limit_price_pct diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 9f5d9a0a6..9ec5af0f1 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -784,7 +784,7 @@ class Exchange: """ raise OperationalException(f"stoploss is not implemented for {self.name}.") - def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: + def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict, side: str) -> Dict: """ creates a stoploss order. The precise ordertype is determined by the order_types dict or exchange default. diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index cc090dcfc..5e9a58d35 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -34,21 +34,24 @@ class Ftx(Exchange): return (parent_check and market.get('spot', False) is True) - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ + # TODO-mg: Short support return order['type'] == 'stop' and stop_loss > float(order['price']) @retrier(retries=0) - def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: + def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict, side: str) -> Dict: """ Creates a stoploss order. depending on order_types.stoploss configuration, uses 'market' or limit order. Limit orders are defined by having orderPrice set, otherwise a market order is used. """ + # TODO-mg: Short support + limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) limit_rate = stop_price * limit_price_pct diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 904b41512..e6b3de99c 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -71,20 +71,22 @@ class Kraken(Exchange): except ccxt.BaseError as e: raise OperationalException(e) from e - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ + # TODO-mg: Short support return (order['type'] in ('stop-loss', 'stop-loss-limit') and stop_loss > float(order['price'])) @retrier(retries=0) - def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: + def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict, side: str) -> Dict: """ Creates a stoploss market order. Stoploss market orders is the only stoploss type supported by kraken. """ + # TODO-mg: Short support params = self._params.copy() if order_types.get('stoploss', 'market') == 'limit': diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 2af9ab5ae..8f00668ed 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -715,7 +715,7 @@ class FreqtradeBot(LoggingMixin): """ Sends rpc notification when a buy/short cancel occurred. """ - current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="buy") + current_rate = self.exchange.get_rate(trade.pair, refresh=False, side=trade.enter_side) msg_type = RPCMessageType.SHORT_CANCEL if trade.is_short else RPCMessageType.BUY_CANCEL msg = { 'trade_id': trade.id, @@ -795,33 +795,29 @@ class FreqtradeBot(LoggingMixin): logger.debug('Handling %s ...', trade) (enter_trade, exit_trade) = (False, False) + exit_signal_type = "exit_short" if trade.is_short else "sell" - if trade.is_short: - # enter_signal = "short" - exit_signal = "exit_short" - signal_config = (self.config.get('use_exit_short_signal', True) - or self.config.get('ignore_roi_if_short_signal', False)) - else: - # enter_signal = "buy" - exit_signal = "sell" - signal_config = (self.config.get('use_sell_signal', True) - or self.config.get('ignore_roi_if_buy_signal', False)) + # TODO-mg: change to use_exit_signal, ignore_roi_if_enter_signal + signal_config = (self.config.get('use_sell_signal', True) + or self.config.get('ignore_roi_if_buy_signal', False)) if (signal_config): analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair, self.strategy.timeframe) + + # TODO-mg: Call get_short_signal for shorts (enter_trade, exit_trade, _) = self.strategy.get_signal( trade.pair, self.strategy.timeframe, analyzed_df) - sell_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell") + logger.debug(f'checking {exit_signal_type}') - # TODO-mg: This will not works for shorts, - # TODO-mg: update exchange.get_exit_rate and _check_and_execute_sell - if self._check_and_execute_sell(trade, sell_rate, enter_trade, exit_trade): + logger.debug(f'checking {exit_signal_type}') + exit_rate = self.exchange.get_rate(trade.pair, refresh=True, side=trade.exit_side) + + if self._check_and_execute_exit(trade, exit_rate, enter_trade, exit_trade): return True - # TODO-mg: end of non-working section - logger.debug(f'Found no {exit_signal} signal for %s.', trade) + logger.debug(f'Found no {exit_signal_type} signal for %s.', trade) return False def create_stoploss_order(self, trade: Trade, stop_price: float) -> bool: @@ -834,7 +830,8 @@ class FreqtradeBot(LoggingMixin): try: stoploss_order = self.exchange.stoploss(pair=trade.pair, amount=trade.amount, stop_price=stop_price, - order_types=self.strategy.order_types) + order_types=self.strategy.order_types, + side=trade.exit_side) order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss') trade.orders.append(order_obj) @@ -848,14 +845,9 @@ class FreqtradeBot(LoggingMixin): except InvalidOrderException as e: trade.stoploss_order_id = None logger.error(f'Unable to place a stoploss order on exchange. {e}') - if trade.is_short: - logger.warning('Exiting(Buying) the trade forcefully') - # self.execute_exit_short(trade, trade.stop_loss, sell_reason=SellCheckTuple( - # sell_type=SellType.EMERGENCY_SELL)) - else: - logger.warning('Selling the trade forcefully') - self.execute_sell(trade, trade.stop_loss, sell_reason=SellCheckTuple( - sell_type=SellType.EMERGENCY_SELL)) + logger.warning('Exiting the trade forcefully') + self.execute_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple( + sell_type=SellType.EMERGENCY_SELL), side=trade.exit_side) except ExchangeError: trade.stoploss_order_id = None @@ -947,7 +939,7 @@ class FreqtradeBot(LoggingMixin): :param order: Current on exchange stoploss order :return: None """ - if self.exchange.stoploss_adjust(trade.stop_loss, order): + if self.exchange.stoploss_adjust(trade.stop_loss, order, side=trade.exit_side): # we check if the update is necessary update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60) if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat: @@ -967,20 +959,25 @@ class FreqtradeBot(LoggingMixin): logger.warning(f"Could not create trailing stoploss order " f"for pair {trade.pair}.") - def _check_and_execute_sell(self, trade: Trade, sell_rate: float, + def _check_and_execute_exit(self, trade: Trade, exit_rate: float, buy: bool, sell: bool) -> bool: """ Check and execute sell # TODO-mg: Update this for shorts """ - should_sell = self.strategy.should_sell( - trade, sell_rate, datetime.now(timezone.utc), buy, sell, + exit = getattr(self.strategy, "should_exit_short") if trade.is_short else getattr( + self.strategy, "should_sell") + + should_exit = exit( + trade, exit_rate, datetime.now(timezone.utc), buy, sell, force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0 ) - if should_sell.sell_flag: - logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}') - self.execute_sell(trade, sell_rate, should_sell) + if should_exit.sell_flag: + # TODO-mg: Update to exit_type + logger.info( + f'Executing {trade.exit_side} for {trade.pair}. Reason: {should_exit.sell_type}') + self.execute_exit(trade, exit_rate, should_exit, side="sell") return True return False @@ -988,6 +985,7 @@ class FreqtradeBot(LoggingMixin): """ Check if timeout is active, and if the order is still open and timed out """ + # TODO-mg: Maybe update side in the next line timeout = self.config.get('unfilledtimeout', {}).get(side) ordertime = arrow.get(order['datetime']).datetime if timeout is not None: @@ -1020,20 +1018,20 @@ class FreqtradeBot(LoggingMixin): order['status'] == 'open' or fully_cancelled) and ( fully_cancelled or self._check_timed_out(trade.enter_side, order) - or strategy_safe_wrapper(self.strategy.check_buy_timeout, + or strategy_safe_wrapper(self.strategy.check_buy_timeout, # TODO-mg: maybe change to check_enter_timeout default_retval=False)(pair=trade.pair, trade=trade, order=order))): self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT']) - elif (order['side'] == 'sell' and (order['status'] == 'open' or fully_cancelled) and ( + elif (order['side'] == trade.exit_side and (order['status'] == 'open' or fully_cancelled) and ( fully_cancelled - or self._check_timed_out('sell', order) - or strategy_safe_wrapper(self.strategy.check_sell_timeout, + or self._check_timed_out(trade.exit_side, order) + or strategy_safe_wrapper(self.strategy.check_sell_timeout, # TODO-mg: maybe change to check_exit_timeout default_retval=False)(pair=trade.pair, trade=trade, order=order))): - self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['TIMEOUT']) + self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['TIMEOUT']) def cancel_all_open_orders(self) -> None: """ @@ -1048,11 +1046,11 @@ class FreqtradeBot(LoggingMixin): logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc()) continue - if order['side'] == 'buy': + if order['side'] == trade.enter_side: self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['ALL_CANCELLED']) - elif order['side'] == 'sell': - self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['ALL_CANCELLED']) + elif order['side'] == trade.exit_side: + self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['ALL_CANCELLED']) Trade.commit() def handle_cancel_enter(self, trade: Trade, order: Dict, reason: str) -> bool: @@ -1087,18 +1085,15 @@ class FreqtradeBot(LoggingMixin): corder = order reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE'] - # TODO-mg: get this side variable working, fails in tests - # side = trade.enter_side[0].upper() + trade.enter_side[1:].lower() - logger.info('Buy order %s for %s.', reason, trade) - # logger.info(f'{side} order %s for %s.', reason, trade) + side = trade.enter_side.capitalize() + logger.info('%s order %s for %s.', side, reason, trade) # Using filled to determine the filled amount filled_amount = safe_value_fallback2(corder, order, 'filled', 'filled') if isclose(filled_amount, 0.0, abs_tol=constants.MATH_CLOSE_PREC): logger.info( - 'Buy order fully cancelled. Removing %s from database.', - # f'{side} order fully cancelled. Removing %s from database.', - trade + '%s order fully cancelled. Removing %s from database.', + side, trade ) # if trade is not partially completed, just delete the trade trade.delete() @@ -1116,20 +1111,18 @@ class FreqtradeBot(LoggingMixin): self.update_trade_state(trade, trade.open_order_id, corder) trade.open_order_id = None - # TODO-mg change to buy or short order - logger.info('Partial buy order timeout for %s.', trade) + logger.info('Partial %s order timeout for %s.', trade.enter_side, trade) reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}" self.wallets.update() - # TODO-mg change to buy or short order - # TODO-mg: Or should it be self.strategy.order_types['short'] or strategy.order_types['buy'] - self._notify_open_cancel(trade, order_type=self.strategy.order_types['buy'], + # TODO-mg: Should short and exit_short be an order type? + self._notify_open_cancel(trade, order_type=self.strategy.order_types[trade.enter_side], reason=reason) return was_trade_fully_canceled - def handle_cancel_sell(self, trade: Trade, order: Dict, reason: str) -> str: + def handle_cancel_exit(self, trade: Trade, order: Dict, reason: str) -> str: """ - Sell cancel - cancel order and update trade + Sell/exit_short cancel - cancel order and update trade :return: Reason for cancel """ # if trade is not partially completed, just cancel the order @@ -1141,12 +1134,13 @@ class FreqtradeBot(LoggingMixin): trade.amount) trade.update_order(co) except InvalidOrderException: - logger.exception(f"Could not cancel sell order {trade.open_order_id}") + logger.exception( + f"Could not cancel {trade.exit_side} order {trade.open_order_id}") return 'error cancelling order' - logger.info('Sell order %s for %s.', reason, trade) + logger.info('%s order %s for %s.', trade.exit_side.capitalize(), reason, trade) else: reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE'] - logger.info('Sell order %s for %s.', reason, trade) + logger.info('%s order %s for %s.', trade.exit_side.capitalize(), reason, trade) trade.update_order(order) trade.close_rate = None @@ -1163,12 +1157,12 @@ class FreqtradeBot(LoggingMixin): self.wallets.update() self._notify_close_cancel( trade, - order_type=self.strategy.order_types['sell'], + order_type=self.strategy.order_types[trade.exit_side], reason=reason ) return reason - def _safe_sell_amount(self, pair: str, amount: float) -> float: + def _safe_exit_amount(self, pair: str, amount: float) -> float: """ Get sellable amount. Should be trade.amount - but will fall back to the available amount if necessary. @@ -1179,6 +1173,7 @@ class FreqtradeBot(LoggingMixin): :return: amount to sell :raise: DependencyException: if available balance is not within 2% of the available amount. """ + # TODO-mg Maybe update? # Update wallets to ensure amounts tied up in a stoploss is now free! self.wallets.update() trade_base_currency = self.exchange.get_pair_base_currency(pair) @@ -1191,9 +1186,15 @@ class FreqtradeBot(LoggingMixin): return wallet_amount else: raise DependencyException( - f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}") + f"Not enough amount to exit trade. Trade-amount: {amount}, Wallet: {wallet_amount}") - def execute_sell(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool: + def execute_exit( + self, + trade: Trade, + limit: float, + sell_reason: SellCheckTuple, + side: str + ) -> bool: """ Executes a limit sell for the given trade and limit :param trade: Trade instance @@ -1201,14 +1202,14 @@ class FreqtradeBot(LoggingMixin): :param sell_reason: Reason the sell was triggered :return: True if it succeeds (supported) False (not supported) """ - # TODO-mg: account for leverage and make this work for shorts - sell_type = 'sell' + + exit_type = 'sell' # TODO-mg: Update to exit if sell_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): - sell_type = 'stoploss' + exit_type = 'stoploss' # if stoploss is on exchange and we are on dry_run mode, # we consider the sell price stop price - if self.config['dry_run'] and sell_type == 'stoploss' \ + if self.config['dry_run'] and exit_type == 'stoploss' \ and self.strategy.order_types['stoploss_on_exchange']: limit = trade.stop_loss @@ -1221,8 +1222,8 @@ class FreqtradeBot(LoggingMixin): except InvalidOrderException: logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}") - order_type = self.strategy.order_types[sell_type] - if sell_reason.sell_type == SellType.EMERGENCY_SELL: + order_type = self.strategy.order_types[exit_type] + if sell_reason.sell_type == SellType.EMERGENCY_SELL: # TODO-mg update to exit_reason # Emergency sells (default to market!) order_type = self.strategy.order_types.get("emergencysell", "market") if sell_reason.sell_type == SellType.FORCE_SELL: @@ -1230,42 +1231,44 @@ class FreqtradeBot(LoggingMixin): # but we allow this value to be changed) order_type = self.strategy.order_types.get("forcesell", order_type) - amount = self._safe_sell_amount(trade.pair, trade.amount) - time_in_force = self.strategy.order_time_in_force['sell'] + amount = self._safe_exit_amount(trade.pair, trade.amount) + time_in_force = self.strategy.order_time_in_force['sell'] # TODO-mg update to exit if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)( pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit, time_in_force=time_in_force, sell_reason=sell_reason.sell_reason, - current_time=datetime.now(timezone.utc)): - logger.info(f"User requested abortion of selling {trade.pair}") + current_time=datetime.now(timezone.utc)): # TODO-mg: Update to exit + logger.info(f"User requested abortion of exiting {trade.pair}") return False try: # Execute sell and update trade record - order = self.exchange.create_order(pair=trade.pair, - ordertype=order_type, side="sell", - amount=amount, rate=limit, - time_in_force=time_in_force - ) + order = self.exchange.create_order( + pair=trade.pair, + ordertype=order_type, + amount=amount, rate=limit, + time_in_force=time_in_force, + side=trade.exit_side + ) except InsufficientFundsError as e: logger.warning(f"Unable to place order {e}.") # Try to figure out what went wrong self.handle_insufficient_funds(trade) return False - order_obj = Order.parse_from_ccxt_object(order, trade.pair, 'sell') + order_obj = Order.parse_from_ccxt_object(order, trade.pair, trade.exit_side) trade.orders.append(order_obj) trade.open_order_id = order['id'] - trade.sell_order_status = '' + trade.sell_order_status = '' # TODO-mg: Update to exit_order_status trade.close_rate_requested = limit - trade.sell_reason = sell_reason.sell_reason - # In case of market sell orders the order can be closed immediately + trade.sell_reason = sell_reason.sell_reason # TODO-mg: Update to exit_reason + # In case of market exit orders the order can be closed immediately if order.get('status', 'unknown') == 'closed': self.update_trade_state(trade, trade.open_order_id, order) Trade.commit() - # Lock pair for one candle to prevent immediate re-buys + # Lock pair for one candle to prevent immediate re-trading self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc), reason='Auto lock') @@ -1281,7 +1284,7 @@ class FreqtradeBot(LoggingMixin): profit_trade = trade.calc_profit(rate=profit_rate) # Use cached rates here - it was updated seconds ago. current_rate = self.exchange.get_rate( - trade.pair, refresh=False, side="sell") if not fill else None + trade.pair, refresh=False, side=trade.exit_side) if not fill else None profit_ratio = trade.calc_profit_ratio(profit_rate) gain = "profit" if profit_ratio > 0 else "loss" @@ -1300,7 +1303,7 @@ class FreqtradeBot(LoggingMixin): 'current_rate': current_rate, 'profit_amount': profit_trade, 'profit_ratio': profit_ratio, - 'sell_reason': trade.sell_reason, + 'sell_reason': trade.sell_reason, # TODO-mg: trade to exit_reason 'open_date': trade.open_date, 'close_date': trade.close_date or datetime.utcnow(), 'stake_currency': self.config['stake_currency'], @@ -1319,14 +1322,14 @@ class FreqtradeBot(LoggingMixin): """ Sends rpc notification when a sell cancel occurred. """ - if trade.sell_order_status == reason: + if trade.sell_order_status == reason: # TODO-mg: Update to exit_order_status return else: - trade.sell_order_status = reason + trade.sell_order_status = reason # TODO-mg: Update to exit_order_status profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested profit_trade = trade.calc_profit(rate=profit_rate) - current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="sell") + current_rate = self.exchange.get_rate(trade.pair, refresh=False, side=trade.exit_side) profit_ratio = trade.calc_profit_ratio(profit_rate) gain = "profit" if profit_ratio > 0 else "loss" @@ -1343,7 +1346,7 @@ class FreqtradeBot(LoggingMixin): 'current_rate': current_rate, 'profit_amount': profit_trade, 'profit_ratio': profit_ratio, - 'sell_reason': trade.sell_reason, + 'sell_reason': trade.sell_reason, # TODO-mg: trade to exit_reason 'open_date': trade.open_date, 'close_date': trade.close_date, 'stake_currency': self.config['stake_currency'], diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 8bcc1044e..ea060658a 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -551,14 +551,14 @@ class RPC: if order['side'] == 'sell': # Cancel order - so it is placed anew with a fresh price. - self._freqtrade.handle_cancel_sell(trade, order, CANCEL_REASON['FORCE_SELL']) + self._freqtrade.handle_cancel_exit(trade, order, CANCEL_REASON['FORCE_SELL']) if not fully_canceled: # Get current rate and execute sell current_rate = self._freqtrade.exchange.get_rate( trade.pair, refresh=False, side="sell") sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL) - self._freqtrade.execute_sell(trade, current_rate, sell_reason) + self._freqtrade.execute_exit(trade, current_rate, sell_reason, side="sell") # ---- EOF def _exec_forcesell ---- if self._freqtrade.state != State.RUNNING: diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index f2b508761..7b324efa2 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -32,12 +32,13 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') with pytest.raises(OperationalException): - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side="sell", order_types={'stoploss_on_exchange_limit_ratio': 1.05}) api_mock.create_order.reset_mock() order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio} - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types=order_types, side="sell") assert 'id' in order assert 'info' in order @@ -54,17 +55,17 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("binance Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance", "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") def test_stoploss_order_dry_run_binance(default_conf, mocker): @@ -77,12 +78,12 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') with pytest.raises(OperationalException): - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side="sell", order_types={'stoploss_on_exchange_limit_ratio': 1.05}) api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") assert 'id' in order assert 'info' in order @@ -100,8 +101,8 @@ def test_stoploss_adjust_binance(mocker, default_conf): 'price': 1500, 'info': {'stopPrice': 1500}, } - assert exchange.stoploss_adjust(1501, order) - assert not exchange.stoploss_adjust(1499, order) + assert exchange.stoploss_adjust(1501, order, side="sell") + assert not exchange.stoploss_adjust(1499, order, side="sell") # Test with invalid order case order['type'] = 'stop_loss' - assert not exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1501, order, side="sell") diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 935775477..0fb89ad4a 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2530,7 +2530,7 @@ def test_get_fee(default_conf, mocker, exchange_name): def test_stoploss_order_unsupported_exchange(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, id='bittrex') with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): exchange.stoploss_adjust(1, {}) diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index 3794bb79c..3887e2b08 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -32,7 +32,7 @@ def test_stoploss_order_ftx(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') # stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side="sell", order_types={'stoploss_on_exchange_limit_ratio': 1.05}) assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' @@ -47,7 +47,7 @@ def test_stoploss_order_ftx(default_conf, mocker): api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") assert 'id' in order assert 'info' in order @@ -61,7 +61,7 @@ def test_stoploss_order_ftx(default_conf, mocker): api_mock.create_order.reset_mock() order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, - order_types={'stoploss': 'limit'}) + order_types={'stoploss': 'limit'}, side="sell") assert 'id' in order assert 'info' in order @@ -78,17 +78,17 @@ def test_stoploss_order_ftx(default_conf, mocker): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx", "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") def test_stoploss_order_dry_run_ftx(default_conf, mocker): @@ -101,7 +101,7 @@ def test_stoploss_order_dry_run_ftx(default_conf, mocker): api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") assert 'id' in order assert 'info' in order @@ -118,11 +118,11 @@ def test_stoploss_adjust_ftx(mocker, default_conf): 'type': STOPLOSS_ORDERTYPE, 'price': 1500, } - assert exchange.stoploss_adjust(1501, order) - assert not exchange.stoploss_adjust(1499, order) + assert exchange.stoploss_adjust(1501, order, side="sell") + assert not exchange.stoploss_adjust(1499, order, side="sell") # Test with invalid order case ... order['type'] = 'stop_loss_limit' - assert not exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1501, order, side="sell") def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order): diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index eb79dfc10..c2b96cf17 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -183,7 +183,7 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, side="sell", order_types={'stoploss': ordertype, 'stoploss_on_exchange_limit_ratio': 0.99 }) @@ -208,17 +208,17 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken", "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") def test_stoploss_order_dry_run_kraken(default_conf, mocker): @@ -231,7 +231,7 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker): api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") assert 'id' in order assert 'info' in order @@ -248,8 +248,8 @@ def test_stoploss_adjust_kraken(mocker, default_conf): 'type': STOPLOSS_ORDERTYPE, 'price': 1500, } - assert exchange.stoploss_adjust(1501, order) - assert not exchange.stoploss_adjust(1499, order) + assert exchange.stoploss_adjust(1501, order, side="sell") + assert not exchange.stoploss_adjust(1499, order, side="sell") # Test with invalid order case ... order['type'] = 'stop_loss_limit' - assert not exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1501, order, side="sell") diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index db049241a..0f41c37bb 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -254,7 +254,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker, edge_conf # stoploss shoud be hit assert freqtrade.handle_trade(trade) is True - assert log_has('Executing Sell for NEO/BTC. Reason: stop_loss', caplog) + assert log_has('Executing sell for NEO/BTC. Reason: stop_loss', caplog) assert trade.sell_reason == SellType.STOP_LOSS.value @@ -1158,7 +1158,7 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee, assert trade.stoploss_order_id is None assert trade.sell_reason == SellType.EMERGENCY_SELL.value assert log_has("Unable to place a stoploss order on exchange. ", caplog) - assert log_has("Selling the trade forcefully", caplog) + assert log_has("Exiting the trade forcefully", caplog) # Should call a market sell assert create_order_mock.call_count == 2 @@ -1312,7 +1312,8 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, stoploss_order_mock.assert_called_once_with(amount=85.32423208, pair='ETH/BTC', order_types=freqtrade.strategy.order_types, - stop_price=0.00002346 * 0.95) + stop_price=0.00002346 * 0.95, + side="sell") # price fell below stoploss, so dry-run sells trade. mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={ @@ -1495,7 +1496,8 @@ def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee, stoploss_order_mock.assert_called_once_with(amount=85.32423208, pair='ETH/BTC', order_types=freqtrade.strategy.order_types, - stop_price=0.00002346 * 0.96) + stop_price=0.00002346 * 0.96, + side="sell") # price fell below stoploss, so dry-run sells trade. mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={ @@ -1616,7 +1618,8 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, stoploss_order_mock.assert_called_once_with(amount=2132892.49146757, pair='NEO/BTC', order_types=freqtrade.strategy.order_types, - stop_price=0.00002346 * 0.99) + stop_price=0.00002346 * 0.99, + side="sell") def test_enter_positions(mocker, default_conf, caplog) -> None: @@ -1975,7 +1978,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open, trade = Trade.query.first() trade.is_open = True - # FIX: sniffing logs, suggest handle_trade should not execute_sell + # FIX: sniffing logs, suggest handle_trade should not execute_exit # instead that responsibility should be moved out of handle_trade(), # we might just want to check if we are in a sell condition without # executing @@ -2422,7 +2425,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke mocker.patch.multiple( 'freqtrade.freqtradebot.FreqtradeBot', handle_cancel_enter=MagicMock(), - handle_cancel_sell=MagicMock(), + handle_cancel_exit=MagicMock(), ) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -2503,6 +2506,7 @@ def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf, reason = CANCEL_REASON['TIMEOUT'] trade = MagicMock() trade.pair = 'LTC/ETH' + trade.enter_side = "buy" assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason) assert cancel_order_mock.call_count == 0 assert log_has_re(r'Buy order fully cancelled. Removing .* from database\.', caplog) @@ -2531,6 +2535,7 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf, limit_buy_order, trade = MagicMock() trade.pair = 'LTC/USDT' trade.open_rate = 200 + trade.enter_side = "buy" limit_buy_order['filled'] = 0.0 limit_buy_order['status'] = 'open' reason = CANCEL_REASON['TIMEOUT'] @@ -2543,7 +2548,7 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf, limit_buy_order, assert cancel_order_mock.call_count == 1 -def test_handle_cancel_sell_limit(mocker, default_conf, fee) -> None: +def test_handle_cancel_exit_limit(mocker, default_conf, fee) -> None: send_msg_mock = patch_RPCManager(mocker) patch_exchange(mocker) cancel_order_mock = MagicMock() @@ -2569,26 +2574,26 @@ def test_handle_cancel_sell_limit(mocker, default_conf, fee) -> None: 'amount': 1, 'status': "open"} reason = CANCEL_REASON['TIMEOUT'] - assert freqtrade.handle_cancel_sell(trade, order, reason) + assert freqtrade.handle_cancel_exit(trade, order, reason) assert cancel_order_mock.call_count == 1 assert send_msg_mock.call_count == 1 send_msg_mock.reset_mock() order['amount'] = 2 - assert freqtrade.handle_cancel_sell(trade, order, reason + assert freqtrade.handle_cancel_exit(trade, order, reason ) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'] # Assert cancel_order was not called (callcount remains unchanged) assert cancel_order_mock.call_count == 1 assert send_msg_mock.call_count == 1 - assert freqtrade.handle_cancel_sell(trade, order, reason + assert freqtrade.handle_cancel_exit(trade, order, reason ) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'] # Message should not be iterated again assert trade.sell_order_status == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'] assert send_msg_mock.call_count == 1 -def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None: +def test_handle_cancel_exit_cancel_exception(mocker, default_conf) -> None: patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch( @@ -2601,10 +2606,10 @@ def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None: order = {'remaining': 1, 'amount': 1, 'status': "open"} - assert freqtrade.handle_cancel_sell(trade, order, reason) == 'error cancelling order' + assert freqtrade.handle_cancel_exit(trade, order, reason) == 'error cancelling order' -def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None: +def test_execute_exit_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None: rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -2632,7 +2637,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N fetch_ticker=ticker_sell_up ) # Prevented sell ... - freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], + freqtrade.execute_exit(trade=trade, limit=ticker_sell_up()['bid'], side="sell", sell_reason=SellCheckTuple(sell_type=SellType.ROI)) assert rpc_mock.call_count == 0 assert freqtrade.strategy.confirm_trade_exit.call_count == 1 @@ -2640,7 +2645,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N # Repatch with true freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True) - freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], + freqtrade.execute_exit(trade=trade, limit=ticker_sell_up()['bid'], side="sell", sell_reason=SellCheckTuple(sell_type=SellType.ROI)) assert freqtrade.strategy.confirm_trade_exit.call_count == 1 @@ -2668,7 +2673,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N } == last_msg -def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) -> None: +def test_execute_exit_down(default_conf, ticker, fee, ticker_sell_down, mocker) -> None: rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -2693,7 +2698,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) fetch_ticker=ticker_sell_down ) - freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], + freqtrade.execute_exit(trade=trade, limit=ticker_sell_down()['bid'], side="sell", sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) assert rpc_mock.call_count == 2 @@ -2720,7 +2725,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) } == last_msg -def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee, +def test_execute_exit_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee, ticker_sell_down, mocker) -> None: rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) @@ -2751,7 +2756,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe # Setting trade stoploss to 0.01 trade.stop_loss = 0.00001099 * 0.99 - freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], + freqtrade.execute_exit(trade=trade, limit=ticker_sell_down()['bid'], side="sell", sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) assert rpc_mock.call_count == 2 @@ -2779,7 +2784,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe } == last_msg -def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, caplog) -> None: +def test_execute_exit_sloe_cancel_exception(mocker, default_conf, ticker, fee, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', side_effect=InvalidOrderException()) @@ -2806,13 +2811,13 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, c freqtrade.config['dry_run'] = False trade.stoploss_order_id = "abcd" - freqtrade.execute_sell(trade=trade, limit=1234, + freqtrade.execute_exit(trade=trade, limit=1234, side="sell", sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) assert create_order_mock.call_count == 2 assert log_has('Could not cancel stoploss order abcd', caplog) -def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticker_sell_up, +def test_execute_exit_with_stoploss_on_exchange(default_conf, ticker, fee, ticker_sell_up, mocker) -> None: default_conf['exchange']['name'] = 'binance' @@ -2857,7 +2862,7 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke fetch_ticker=ticker_sell_up ) - freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], + freqtrade.execute_exit(trade=trade, limit=ticker_sell_up()['bid'], side="sell", sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) trade = Trade.query.first() @@ -2866,7 +2871,7 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke assert rpc_mock.call_count == 3 -def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, fee, +def test_may_execute_exit_after_stoploss_on_exchange_hit(default_conf, ticker, fee, mocker) -> None: default_conf['exchange']['name'] = 'binance' rpc_mock = patch_RPCManager(mocker) @@ -2938,7 +2943,7 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, f assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL -def test_execute_sell_market_order(default_conf, ticker, fee, +def test_execute_exit_market_order(default_conf, ticker, fee, ticker_sell_up, mocker) -> None: rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) @@ -2965,7 +2970,7 @@ def test_execute_sell_market_order(default_conf, ticker, fee, ) freqtrade.config['order_types']['sell'] = 'market' - freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], + freqtrade.execute_exit(trade=trade, limit=ticker_sell_up()['bid'], side="sell", sell_reason=SellCheckTuple(sell_type=SellType.ROI)) assert not trade.is_open @@ -2996,7 +3001,7 @@ def test_execute_sell_market_order(default_conf, ticker, fee, } == last_msg -def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee, +def test_execute_exit_insufficient_funds_error(default_conf, ticker, fee, ticker_sell_up, mocker) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds') @@ -3024,8 +3029,8 @@ def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee, ) sell_reason = SellCheckTuple(sell_type=SellType.ROI) - assert not freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], - sell_reason=sell_reason) + assert not freqtrade.execute_exit(trade=trade, limit=ticker_sell_up()['bid'], + sell_reason=sell_reason, side="sell") assert mock_insuf.call_count == 1 @@ -3207,7 +3212,7 @@ def test_sell_not_enough_balance(default_conf, limit_buy_order, limit_buy_order_ assert trade.amount != amnt -def test__safe_sell_amount(default_conf, fee, caplog, mocker): +def test__safe_exit_amount(default_conf, fee, caplog, mocker): patch_RPCManager(mocker) patch_exchange(mocker) amount = 95.33 @@ -3227,17 +3232,17 @@ def test__safe_sell_amount(default_conf, fee, caplog, mocker): patch_get_signal(freqtrade) wallet_update.reset_mock() - assert freqtrade._safe_sell_amount(trade.pair, trade.amount) == amount_wallet + assert freqtrade._safe_exit_amount(trade.pair, trade.amount) == amount_wallet assert log_has_re(r'.*Falling back to wallet-amount.', caplog) assert wallet_update.call_count == 1 caplog.clear() wallet_update.reset_mock() - assert freqtrade._safe_sell_amount(trade.pair, amount_wallet) == amount_wallet + assert freqtrade._safe_exit_amount(trade.pair, amount_wallet) == amount_wallet assert not log_has_re(r'.*Falling back to wallet-amount.', caplog) assert wallet_update.call_count == 1 -def test__safe_sell_amount_error(default_conf, fee, caplog, mocker): +def test__safe_exit_amount_error(default_conf, fee, caplog, mocker): patch_RPCManager(mocker) patch_exchange(mocker) amount = 95.33 @@ -3254,8 +3259,8 @@ def test__safe_sell_amount_error(default_conf, fee, caplog, mocker): ) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - with pytest.raises(DependencyException, match=r"Not enough amount to sell."): - assert freqtrade._safe_sell_amount(trade.pair, trade.amount) + with pytest.raises(DependencyException, match=r"Not enough amount to exit trade."): + assert freqtrade._safe_exit_amount(trade.pair, trade.amount) def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplog) -> None: @@ -3281,7 +3286,7 @@ def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplo fetch_ticker=ticker_sell_down ) - freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], + freqtrade.execute_exit(trade=trade, limit=ticker_sell_down()['bid'], side="sell", sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) trade.close(ticker_sell_down()['bid']) assert freqtrade.strategy.is_pair_locked(trade.pair) @@ -4221,7 +4226,7 @@ def test_cancel_all_open_orders(mocker, default_conf, fee, limit_buy_order, limi side_effect=[ ExchangeError(), limit_sell_order, limit_buy_order, limit_sell_order]) buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_enter') - sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_sell') + sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit') freqtrade = get_patched_freqtradebot(mocker, default_conf) create_mock_trades(fee) diff --git a/tests/test_integration.py b/tests/test_integration.py index d89448833..f5da5f0a6 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -9,7 +9,7 @@ from freqtrade.strategy.interface import SellCheckTuple from tests.conftest import get_patched_freqtradebot, patch_get_signal -def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee, +def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, limit_buy_order, mocker) -> None: """ Tests workflow of selling stoploss_on_exchange.