From 82aecc81f393e98b86115e9bdfa46dac1a143fad Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Aug 2022 19:53:10 +0200 Subject: [PATCH 1/8] Accept parameters to forceexit --- freqtrade/rpc/api_server/api_schemas.py | 1 + freqtrade/rpc/api_server/api_v1.py | 2 +- freqtrade/rpc/rpc.py | 22 +++++++++++++++++----- scripts/rest_client.py | 10 ++++++++-- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 333f2fe6e..d2a584136 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -330,6 +330,7 @@ class ForceEnterPayload(BaseModel): class ForceExitPayload(BaseModel): tradeid: str ordertype: Optional[OrderTypeValues] + amount: Optional[float] class BlacklistPayload(BaseModel): diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index b3506409d..29e4fb75a 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -161,7 +161,7 @@ def force_entry(payload: ForceEnterPayload, rpc: RPC = Depends(get_rpc)): @router.post('/forcesell', response_model=ResultMsg, tags=['trading']) def forceexit(payload: ForceExitPayload, rpc: RPC = Depends(get_rpc)): ordertype = payload.ordertype.value if payload.ordertype else None - return rpc._rpc_force_exit(payload.tradeid, ordertype) + return rpc._rpc_force_exit(payload.tradeid, ordertype, amount=payload.amount) @router.get('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist']) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index d848da546..340adea43 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -660,12 +660,15 @@ class RPC: return {'status': 'No more buy will occur from now. Run /reload_config to reset.'} - def _rpc_force_exit(self, trade_id: str, ordertype: Optional[str] = None) -> Dict[str, str]: + def _rpc_force_exit(self, trade_id: str, ordertype: Optional[str] = None, *, + amount: Optional[float]) -> Dict[str, str]: """ Handler for forceexit . Sells the given trade at current price """ - def _exec_force_exit(trade: Trade) -> None: + + def _exec_force_exit(trade: Trade, ordertype: Optional[str], + _amount: Optional[float] = None) -> None: # Check if there is there is an open order fully_canceled = False if trade.open_order_id: @@ -686,10 +689,19 @@ class RPC: exit_check = ExitCheckTuple(exit_type=ExitType.FORCE_EXIT) order_type = ordertype or self._freqtrade.strategy.order_types.get( "force_exit", self._freqtrade.strategy.order_types["exit"]) + sub_amount: float = None + if _amount and _amount < trade.amount: + # Partial exit ... + min_exit_stake = self._freqtrade.exchange.get_min_pair_stake_amount( + trade.pair, current_rate, trade.stop_loss_pct) + remaining = (trade.amount - _amount) * current_rate + if remaining < min_exit_stake: + raise RPCException(f'Remaining amount of {remaining} would be too small.') + sub_amount = _amount self._freqtrade.execute_trade_exit( - trade, current_rate, exit_check, ordertype=order_type) - # ---- EOF def _exec_forcesell ---- + trade, current_rate, exit_check, ordertype=order_type, + sub_trade_amt=sub_amount) if self._freqtrade.state != State.RUNNING: raise RPCException('trader is not running') @@ -711,7 +723,7 @@ class RPC: logger.warning('force_exit: Invalid argument received') raise RPCException('invalid argument') - _exec_force_exit(trade) + _exec_force_exit(trade, ordertype, amount) Trade.commit() self._freqtrade.wallets.update() return {'result': f'Created sell order for trade {trade_id}.'} diff --git a/scripts/rest_client.py b/scripts/rest_client.py index e5d358c98..989e6a50d 100755 --- a/scripts/rest_client.py +++ b/scripts/rest_client.py @@ -275,14 +275,20 @@ class FtRestClient(): } return self._post("forceenter", data=data) - def forceexit(self, tradeid): + def forceexit(self, tradeid, ordertype=None, amount=None): """Force-exit a trade. :param tradeid: Id of the trade (can be received via status command) + :param ordertype: Order type to use (must be market or limit) + :param amount: Amount to sell. Full sell if not given :return: json object """ - return self._post("forceexit", data={"tradeid": tradeid}) + return self._post("forceexit", data={ + "tradeid": tradeid, + "ordertype": ordertype, + "amount": amount, + }) def strategies(self): """Lists available strategies From daf015d0077736ba94c0b3cc3302942c425a9357 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Aug 2022 19:54:37 +0200 Subject: [PATCH 2/8] extract nested force_exit function to private instance function --- freqtrade/rpc/rpc.py | 76 ++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 340adea43..36327f091 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -660,6 +660,42 @@ class RPC: return {'status': 'No more buy will occur from now. Run /reload_config to reset.'} + def __exec_force_exit(self, trade: Trade, ordertype: Optional[str], + _amount: Optional[float] = None) -> None: + # Check if there is there is an open order + fully_canceled = False + if trade.open_order_id: + order = self._freqtrade.exchange.fetch_order(trade.open_order_id, trade.pair) + + if order['side'] == trade.entry_side: + fully_canceled = self._freqtrade.handle_cancel_enter( + trade, order, CANCEL_REASON['FORCE_EXIT']) + + if order['side'] == trade.exit_side: + # Cancel order - so it is placed anew with a fresh price. + self._freqtrade.handle_cancel_exit(trade, order, CANCEL_REASON['FORCE_EXIT']) + + if not fully_canceled: + # Get current rate and execute sell + current_rate = self._freqtrade.exchange.get_rate( + trade.pair, side='exit', is_short=trade.is_short, refresh=True) + exit_check = ExitCheckTuple(exit_type=ExitType.FORCE_EXIT) + order_type = ordertype or self._freqtrade.strategy.order_types.get( + "force_exit", self._freqtrade.strategy.order_types["exit"]) + sub_amount: float = None + if _amount and _amount < trade.amount: + # Partial exit ... + min_exit_stake = self._freqtrade.exchange.get_min_pair_stake_amount( + trade.pair, current_rate, trade.stop_loss_pct) + remaining = (trade.amount - _amount) * current_rate + if remaining < min_exit_stake: + raise RPCException(f'Remaining amount of {remaining} would be too small.') + sub_amount = _amount + + self._freqtrade.execute_trade_exit( + trade, current_rate, exit_check, ordertype=order_type, + sub_trade_amt=sub_amount) + def _rpc_force_exit(self, trade_id: str, ordertype: Optional[str] = None, *, amount: Optional[float]) -> Dict[str, str]: """ @@ -667,42 +703,6 @@ class RPC: Sells the given trade at current price """ - def _exec_force_exit(trade: Trade, ordertype: Optional[str], - _amount: Optional[float] = None) -> None: - # Check if there is there is an open order - fully_canceled = False - if trade.open_order_id: - order = self._freqtrade.exchange.fetch_order(trade.open_order_id, trade.pair) - - if order['side'] == trade.entry_side: - fully_canceled = self._freqtrade.handle_cancel_enter( - trade, order, CANCEL_REASON['FORCE_EXIT']) - - if order['side'] == trade.exit_side: - # Cancel order - so it is placed anew with a fresh price. - self._freqtrade.handle_cancel_exit(trade, order, CANCEL_REASON['FORCE_EXIT']) - - if not fully_canceled: - # Get current rate and execute sell - current_rate = self._freqtrade.exchange.get_rate( - trade.pair, side='exit', is_short=trade.is_short, refresh=True) - exit_check = ExitCheckTuple(exit_type=ExitType.FORCE_EXIT) - order_type = ordertype or self._freqtrade.strategy.order_types.get( - "force_exit", self._freqtrade.strategy.order_types["exit"]) - sub_amount: float = None - if _amount and _amount < trade.amount: - # Partial exit ... - min_exit_stake = self._freqtrade.exchange.get_min_pair_stake_amount( - trade.pair, current_rate, trade.stop_loss_pct) - remaining = (trade.amount - _amount) * current_rate - if remaining < min_exit_stake: - raise RPCException(f'Remaining amount of {remaining} would be too small.') - sub_amount = _amount - - self._freqtrade.execute_trade_exit( - trade, current_rate, exit_check, ordertype=order_type, - sub_trade_amt=sub_amount) - if self._freqtrade.state != State.RUNNING: raise RPCException('trader is not running') @@ -710,7 +710,7 @@ class RPC: if trade_id == 'all': # Execute sell for all open orders for trade in Trade.get_open_trades(): - _exec_force_exit(trade) + self.__exec_force_exit(trade) Trade.commit() self._freqtrade.wallets.update() return {'result': 'Created sell orders for all open trades.'} @@ -723,7 +723,7 @@ class RPC: logger.warning('force_exit: Invalid argument received') raise RPCException('invalid argument') - _exec_force_exit(trade, ordertype, amount) + self.__exec_force_exit(trade, ordertype, amount) Trade.commit() self._freqtrade.wallets.update() return {'result': f'Created sell order for trade {trade_id}.'} From eff8cd7ecbf2da8aea10a363a98aafa5f0bec480 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Aug 2022 20:15:47 +0200 Subject: [PATCH 3/8] Add leverage to force_entry --- freqtrade/freqtradebot.py | 27 +++++++++++++++---------- freqtrade/rpc/api_server/api_schemas.py | 1 + freqtrade/rpc/api_server/api_v1.py | 10 ++++----- freqtrade/rpc/rpc.py | 4 +++- 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 390c8e8f6..0dbeb2e44 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -624,7 +624,8 @@ class FreqtradeBot(LoggingMixin): ordertype: Optional[str] = None, enter_tag: Optional[str] = None, trade: Optional[Trade] = None, - order_adjust: bool = False + order_adjust: bool = False, + leverage_: Optional[float] = None, ) -> bool: """ Executes a limit buy for the given pair @@ -640,7 +641,7 @@ class FreqtradeBot(LoggingMixin): pos_adjust = trade is not None enter_limit_requested, stake_amount, leverage = self.get_valid_enter_price_and_stake( - pair, price, stake_amount, trade_side, enter_tag, trade, order_adjust) + pair, price, stake_amount, trade_side, enter_tag, trade, order_adjust, leverage_) if not stake_amount: return False @@ -787,6 +788,7 @@ class FreqtradeBot(LoggingMixin): entry_tag: Optional[str], trade: Optional[Trade], order_adjust: bool, + leverage_: Optional[float], ) -> Tuple[float, float, float]: if price: @@ -809,16 +811,19 @@ class FreqtradeBot(LoggingMixin): if not enter_limit_requested: raise PricingError('Could not determine entry price.') - if trade is None: + if self.trading_mode != TradingMode.SPOT and trade is None: max_leverage = self.exchange.get_max_leverage(pair, stake_amount) - leverage = strategy_safe_wrapper(self.strategy.leverage, default_retval=1.0)( - pair=pair, - current_time=datetime.now(timezone.utc), - current_rate=enter_limit_requested, - proposed_leverage=1.0, - max_leverage=max_leverage, - side=trade_side, entry_tag=entry_tag, - ) if self.trading_mode != TradingMode.SPOT else 1.0 + if leverage_: + leverage = leverage_ + else: + leverage = strategy_safe_wrapper(self.strategy.leverage, default_retval=1.0)( + pair=pair, + current_time=datetime.now(timezone.utc), + current_rate=enter_limit_requested, + proposed_leverage=1.0, + max_leverage=max_leverage, + side=trade_side, entry_tag=entry_tag, + ) # Cap leverage between 1.0 and max_leverage. leverage = min(max(leverage, 1.0), max_leverage) else: diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index d2a584136..1641df384 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -325,6 +325,7 @@ class ForceEnterPayload(BaseModel): ordertype: Optional[OrderTypeValues] stakeamount: Optional[float] entry_tag: Optional[str] + leverage: Optional[float] class ForceExitPayload(BaseModel): diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 29e4fb75a..54d7baf38 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -37,7 +37,8 @@ logger = logging.getLogger(__name__) # 2.14: Add entry/exit orders to trade response # 2.15: Add backtest history endpoints # 2.16: Additional daily metrics -API_VERSION = 2.16 +# 2.16: Forceentry - leverage, partial force_exit +API_VERSION = 2.17 # Public API, requires no auth. router_public = APIRouter() @@ -142,12 +143,11 @@ def show_config(rpc: Optional[RPC] = Depends(get_rpc_optional), config=Depends(g @router.post('/forcebuy', response_model=ForceEnterResponse, tags=['trading']) def force_entry(payload: ForceEnterPayload, rpc: RPC = Depends(get_rpc)): ordertype = payload.ordertype.value if payload.ordertype else None - stake_amount = payload.stakeamount if payload.stakeamount else None - entry_tag = payload.entry_tag if payload.entry_tag else 'force_entry' trade = rpc._rpc_force_entry(payload.pair, payload.price, order_side=payload.side, - order_type=ordertype, stake_amount=stake_amount, - enter_tag=entry_tag) + order_type=ordertype, stake_amount=payload.stakeamount, + enter_tag=payload.entry_tag or 'force_entry', + leverage=payload.leverage) if trade: return ForceEnterResponse.parse_obj(trade.to_json()) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 36327f091..22effb849 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -732,7 +732,8 @@ class RPC: order_type: Optional[str] = None, order_side: SignalDirection = SignalDirection.LONG, stake_amount: Optional[float] = None, - enter_tag: Optional[str] = 'force_entry') -> Optional[Trade]: + enter_tag: Optional[str] = 'force_entry', + leverage: Optional[float] = None) -> Optional[Trade]: """ Handler for forcebuy Buys a pair trade at the given or current price @@ -774,6 +775,7 @@ class RPC: ordertype=order_type, trade=trade, is_short=is_short, enter_tag=enter_tag, + leverage_=leverage, ): Trade.commit() trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() From d1998f7ed0bf2f58daf439d367fa25b7319f3aa1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Aug 2022 20:16:01 +0200 Subject: [PATCH 4/8] Fix forceexit calling --- freqtrade/rpc/rpc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 22effb849..7694b2414 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -697,7 +697,7 @@ class RPC: sub_trade_amt=sub_amount) def _rpc_force_exit(self, trade_id: str, ordertype: Optional[str] = None, *, - amount: Optional[float]) -> Dict[str, str]: + amount: Optional[float] = None) -> Dict[str, str]: """ Handler for forceexit . Sells the given trade at current price @@ -710,7 +710,7 @@ class RPC: if trade_id == 'all': # Execute sell for all open orders for trade in Trade.get_open_trades(): - self.__exec_force_exit(trade) + self.__exec_force_exit(trade, ordertype) Trade.commit() self._freqtrade.wallets.update() return {'result': 'Created sell orders for all open trades.'} From d3780b931c27a6cdede7d58d5814de7d1aa8ac05 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Aug 2022 20:21:02 +0200 Subject: [PATCH 5/8] Add test passing leverage to execute_entry --- tests/test_freqtradebot.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index f274e2119..fb5fd38d8 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -973,6 +973,14 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, trade.is_short = is_short assert pytest.approx(trade.stake_amount) == 500 + order['id'] = '55673' + + freqtrade.strategy.leverage.reset_mock() + assert freqtrade.execute_entry(pair, 200, leverage_=3) + assert freqtrade.strategy.leverage.call_count == 0 + trade = Trade.query.all()[10] + assert trade.leverage == 1 if trading_mode == 'spot' else 3 + @pytest.mark.parametrize("is_short", [False, True]) def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_order, is_short) -> None: From 6ded2d5b7c442a5fd69d56ee63361df4f4a3db7e Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 4 Aug 2022 16:28:36 +0200 Subject: [PATCH 6/8] Improve forceexit API test --- freqtrade/rpc/rpc.py | 2 +- tests/conftest_trades.py | 6 ++++-- tests/rpc/test_rpc_apiserver.py | 23 +++++++++++++++++++---- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 7694b2414..cda4fdc22 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -682,7 +682,7 @@ class RPC: exit_check = ExitCheckTuple(exit_type=ExitType.FORCE_EXIT) order_type = ordertype or self._freqtrade.strategy.order_types.get( "force_exit", self._freqtrade.strategy.order_types["exit"]) - sub_amount: float = None + sub_amount: Optional[float] = None if _amount and _amount < trade.amount: # Partial exit ... min_exit_stake = self._freqtrade.exchange.get_min_pair_stake_amount( diff --git a/tests/conftest_trades.py b/tests/conftest_trades.py index 1a8cf3183..9642435e5 100644 --- a/tests/conftest_trades.py +++ b/tests/conftest_trades.py @@ -214,7 +214,8 @@ def mock_trade_4(fee, is_short: bool): open_order_id=f'prod_buy_{direc(is_short)}_12345', strategy='StrategyTestV3', timeframe=5, - is_short=is_short + is_short=is_short, + stop_loss_pct=0.10 ) o = Order.parse_from_ccxt_object(mock_order_4(is_short), 'ETC/BTC', entry_side(is_short)) trade.orders.append(o) @@ -270,7 +271,8 @@ def mock_trade_5(fee, is_short: bool): enter_tag='TEST1', stoploss_order_id=f'prod_stoploss_{direc(is_short)}_3455', timeframe=5, - is_short=is_short + is_short=is_short, + stop_loss_pct=0.10, ) o = Order.parse_from_ccxt_object(mock_order_5(is_short), 'XRP/BTC', entry_side(is_short)) trade.orders.append(o) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index b7161e680..d2d9a73d5 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1202,7 +1202,7 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets): fetch_ticker=ticker, get_fee=fee, markets=PropertyMock(return_value=markets), - _is_dry_limit_order_filled=MagicMock(return_value=False), + _is_dry_limit_order_filled=MagicMock(return_value=True), ) patch_get_signal(ftbot) @@ -1212,12 +1212,27 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets): assert rc.json() == {"error": "Error querying /api/v1/forceexit: invalid argument"} Trade.query.session.rollback() - ftbot.enter_positions() + create_mock_trades(fee) + trade = Trade.get_trades([Trade.id == 5]).first() + assert pytest.approx(trade.amount) == 123 + rc = client_post(client, f"{BASE_URI}/forceexit", + data='{"tradeid": "5", "ordertype": "market", "amount": 23}') + assert_response(rc) + assert rc.json() == {'result': 'Created sell order for trade 5.'} + Trade.query.session.rollback() + + trade = Trade.get_trades([Trade.id == 5]).first() + assert pytest.approx(trade.amount) == 100 + assert trade.is_open is True rc = client_post(client, f"{BASE_URI}/forceexit", - data='{"tradeid": "1"}') + data='{"tradeid": "5"}') assert_response(rc) - assert rc.json() == {'result': 'Created sell order for trade 1.'} + assert rc.json() == {'result': 'Created sell order for trade 5.'} + Trade.query.session.rollback() + + trade = Trade.get_trades([Trade.id == 5]).first() + assert trade.is_open is False def test_api_pair_candles(botclient, ohlcv_history): From 0b2104fc7a13e8034e051cf456dce25b30097f4a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 7 Aug 2022 10:13:22 +0200 Subject: [PATCH 7/8] Properly increment the api version --- freqtrade/rpc/api_server/api_v1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 54d7baf38..e0fef7be8 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -37,7 +37,7 @@ logger = logging.getLogger(__name__) # 2.14: Add entry/exit orders to trade response # 2.15: Add backtest history endpoints # 2.16: Additional daily metrics -# 2.16: Forceentry - leverage, partial force_exit +# 2.17: Forceentry - leverage, partial force_exit API_VERSION = 2.17 # Public API, requires no auth. From ce2c9bf26dd54c5cee3864edf60fa496e5711922 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 10 Aug 2022 06:44:41 +0200 Subject: [PATCH 8/8] Slight renaming of variable --- freqtrade/rpc/rpc.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index cda4fdc22..9f2c8cf37 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -661,7 +661,7 @@ class RPC: return {'status': 'No more buy will occur from now. Run /reload_config to reset.'} def __exec_force_exit(self, trade: Trade, ordertype: Optional[str], - _amount: Optional[float] = None) -> None: + amount: Optional[float] = None) -> None: # Check if there is there is an open order fully_canceled = False if trade.open_order_id: @@ -683,14 +683,14 @@ class RPC: order_type = ordertype or self._freqtrade.strategy.order_types.get( "force_exit", self._freqtrade.strategy.order_types["exit"]) sub_amount: Optional[float] = None - if _amount and _amount < trade.amount: + if amount and amount < trade.amount: # Partial exit ... min_exit_stake = self._freqtrade.exchange.get_min_pair_stake_amount( trade.pair, current_rate, trade.stop_loss_pct) - remaining = (trade.amount - _amount) * current_rate + remaining = (trade.amount - amount) * current_rate if remaining < min_exit_stake: raise RPCException(f'Remaining amount of {remaining} would be too small.') - sub_amount = _amount + sub_amount = amount self._freqtrade.execute_trade_exit( trade, current_rate, exit_check, ordertype=order_type,