From 0a52d79208fa7514749c78a5cda8289173f7243f Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 26 Jan 2022 20:17:00 +0100 Subject: [PATCH] Update forcesell to work as forceexit --- freqtrade/rpc/api_server/api_schemas.py | 2 +- freqtrade/rpc/api_server/api_v1.py | 7 ++++--- freqtrade/rpc/rpc.py | 2 +- freqtrade/rpc/telegram.py | 21 ++++++++++++++------- tests/rpc/test_rpc.py | 22 +++++++++++----------- tests/rpc/test_rpc_telegram.py | 12 ++++++------ 6 files changed, 37 insertions(+), 29 deletions(-) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index efe107346..dee566cea 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -285,7 +285,7 @@ class ForceEnterPayload(BaseModel): stakeamount: Optional[float] -class ForceSellPayload(BaseModel): +class ForceExitPayload(BaseModel): tradeid: str ordertype: Optional[OrderTypeValues] diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 4797b38e0..93a160efb 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -15,7 +15,7 @@ from freqtrade.rpc import RPC from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, BlacklistPayload, BlacklistResponse, Count, Daily, DeleteLockRequest, DeleteTrade, ForceEnterPayload, - ForceEnterResponse, ForceSellPayload, Locks, Logs, + ForceEnterResponse, ForceExitPayload, Locks, Logs, OpenTradeSchema, PairHistory, PerformanceEntry, Ping, PlotConfig, Profit, ResultMsg, ShowConfig, Stats, StatusMsg, StrategyListResponse, @@ -152,10 +152,11 @@ def forceentry(payload: ForceEnterPayload, rpc: RPC = Depends(get_rpc)): {"status": f"Error entering {payload.side} trade for pair {payload.pair}."}) +@router.post('/forceexit', response_model=ResultMsg, tags=['trading']) @router.post('/forcesell', response_model=ResultMsg, tags=['trading']) -def forcesell(payload: ForceSellPayload, rpc: RPC = Depends(get_rpc)): +def forcesell(payload: ForceExitPayload, rpc: RPC = Depends(get_rpc)): ordertype = payload.ordertype.value if payload.ordertype else None - return rpc._rpc_forcesell(payload.tradeid, ordertype) + return rpc._rpc_forceexit(payload.tradeid, ordertype) @router.get('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist']) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 7eedd27db..b9d7cedd4 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -658,7 +658,7 @@ class RPC: return {'status': 'No more buy will occur from now. Run /reload_config to reset.'} - def _rpc_forcesell(self, trade_id: str, ordertype: Optional[str] = None) -> Dict[str, str]: + def _rpc_forceexit(self, trade_id: str, ordertype: Optional[str] = None) -> Dict[str, str]: """ Handler for forcesell . Sells the given trade at current price diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 6eac52bf0..23872286e 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -25,6 +25,7 @@ from freqtrade.__init__ import __version__ from freqtrade.constants import DUST_PER_COIN from freqtrade.enums import RPCMessageType from freqtrade.enums.signaltype import SignalDirection +from freqtrade.enums.tradingmode import TradingMode from freqtrade.exceptions import OperationalException from freqtrade.misc import chunks, plural, round_coin_value from freqtrade.persistence import Trade @@ -115,7 +116,8 @@ class Telegram(RPCHandler): r'/stopbuy$', r'/reload_config$', r'/show_config$', r'/logs$', r'/whitelist$', r'/blacklist$', r'/bl_delete$', r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$', - r'/forcebuy$', r'/edge$', r'/help$', r'/version$'] + r'/forcebuy$', r'/forcelong$', r'/forceshort$', + r'/edge$', r'/help$', r'/version$'] # Create keys for generation valid_keys_print = [k.replace('$', '') for k in valid_keys] @@ -152,7 +154,7 @@ class Telegram(RPCHandler): CommandHandler('balance', self._balance), CommandHandler('start', self._start), CommandHandler('stop', self._stop), - CommandHandler('forcesell', self._forcesell), + CommandHandler(['forcesell', 'forceexit'], self._forceexit), CommandHandler(['forcebuy', 'forcelong'], partial(self._forcebuy, order_side=SignalDirection.LONG)), CommandHandler('forceshort', partial(self._forcebuy, order_side=SignalDirection.SHORT)), CommandHandler('trades', self._trades), @@ -849,7 +851,7 @@ class Telegram(RPCHandler): self._send_msg('Status: `{status}`'.format(**msg)) @authorized_only - def _forcesell(self, update: Update, context: CallbackContext) -> None: + def _forceexit(self, update: Update, context: CallbackContext) -> None: """ Handler for /forcesell . Sells the given trade at current price @@ -863,7 +865,7 @@ class Telegram(RPCHandler): self._send_msg("You must specify a trade-id or 'all'.") return try: - msg = self._rpc._rpc_forcesell(trade_id) + msg = self._rpc._rpc_forceexit(trade_id) self._send_msg('Forcesell Result: `{result}`'.format(**msg)) except RPCException as e: @@ -1279,16 +1281,21 @@ class Telegram(RPCHandler): :param update: message update :return: None """ - forcebuy_text = ("*/forcebuy []:* `Instantly buys the given pair. " + forcebuy_text = ("*/forcelong []:* `Instantly buys the given pair. " "Optionally takes a rate at which to buy " - "(only applies to limit orders).` \n") + "(only applies to limit orders).` \n" + ) + if self._rpc_._freqtrade.trading_mode != TradingMode.SPOT: + forcebuy_text += ("*/forceshort []:* `Instantly shorts the given pair. " + "Optionally takes a rate at which to sell " + "(only applies to limit orders).` \n") message = ( "_BotControl_\n" "------------\n" "*/start:* `Starts the trader`\n" "*/stop:* Stops the trader\n" "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" - "*/forcesell |all:* `Instantly sells the given trade or all trades, " + "*/forceexit |all:* `Instantly exits the given trade or all trades, " "regardless of profit`\n" f"{forcebuy_text if self._config.get('forcebuy_enable', False) else ''}" "*/delete :* `Instantly delete the given trade in the database`\n" diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 26c98d6a1..1afda4b3d 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -687,7 +687,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None: assert freqtradebot.config['max_open_trades'] == 0 -def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: +def test_rpc_forceexit(default_conf, ticker, fee, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) cancel_order_mock = MagicMock() @@ -714,29 +714,29 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: freqtradebot.state = State.STOPPED with pytest.raises(RPCException, match=r'.*trader is not running*'): - rpc._rpc_forcesell(None) + rpc._rpc_forceexit(None) freqtradebot.state = State.RUNNING with pytest.raises(RPCException, match=r'.*invalid argument*'): - rpc._rpc_forcesell(None) + rpc._rpc_forceexit(None) - msg = rpc._rpc_forcesell('all') + msg = rpc._rpc_forceexit('all') assert msg == {'result': 'Created sell orders for all open trades.'} freqtradebot.enter_positions() - msg = rpc._rpc_forcesell('all') + msg = rpc._rpc_forceexit('all') assert msg == {'result': 'Created sell orders for all open trades.'} freqtradebot.enter_positions() - msg = rpc._rpc_forcesell('2') + msg = rpc._rpc_forceexit('2') assert msg == {'result': 'Created sell order for trade 2.'} freqtradebot.state = State.STOPPED with pytest.raises(RPCException, match=r'.*trader is not running*'): - rpc._rpc_forcesell(None) + rpc._rpc_forceexit(None) with pytest.raises(RPCException, match=r'.*trader is not running*'): - rpc._rpc_forcesell('all') + rpc._rpc_forceexit('all') freqtradebot.state = State.RUNNING assert cancel_order_mock.call_count == 0 @@ -765,7 +765,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: ) # check that the trade is called, which is done by ensuring exchange.cancel_order is called # and trade amount is updated - rpc._rpc_forcesell('3') + rpc._rpc_forceexit('3') assert cancel_order_mock.call_count == 1 assert trade.amount == filled_amount @@ -793,7 +793,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: } ) # check that the trade is called, which is done by ensuring exchange.cancel_order is called - msg = rpc._rpc_forcesell('4') + msg = rpc._rpc_forceexit('4') assert msg == {'result': 'Created sell order for trade 4.'} assert cancel_order_mock.call_count == 2 assert trade.amount == amount @@ -810,7 +810,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: 'filled': 0.0 } ) - msg = rpc._rpc_forcesell('3') + msg = rpc._rpc_forceexit('3') assert msg == {'result': 'Created sell order for trade 3.'} # status quo, no exchange calls assert cancel_order_mock.call_count == 3 diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index c44756ae7..7b83fd1a6 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -940,7 +940,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee, # /forcesell 1 context = MagicMock() context.args = ["1"] - telegram._forcesell(update=update, context=context) + telegram._forceexit(update=update, context=context) assert msg_mock.call_count == 4 last_msg = msg_mock.call_args_list[-2][0][0] @@ -1007,7 +1007,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee, # /forcesell 1 context = MagicMock() context.args = ["1"] - telegram._forcesell(update=update, context=context) + telegram._forceexit(update=update, context=context) assert msg_mock.call_count == 4 @@ -1065,7 +1065,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None # /forcesell all context = MagicMock() context.args = ["all"] - telegram._forcesell(update=update, context=context) + telegram._forceexit(update=update, context=context) # Called for each trade 2 times assert msg_mock.call_count == 8 @@ -1109,7 +1109,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: # /forcesell 1 context = MagicMock() context.args = ["1"] - telegram._forcesell(update=update, context=context) + telegram._forceexit(update=update, context=context) assert msg_mock.call_count == 1 assert 'not running' in msg_mock.call_args_list[0][0][0] @@ -1118,7 +1118,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: freqtradebot.state = State.RUNNING context = MagicMock() context.args = [] - telegram._forcesell(update=update, context=context) + telegram._forceexit(update=update, context=context) assert msg_mock.call_count == 1 assert "You must specify a trade-id or 'all'." in msg_mock.call_args_list[0][0][0] @@ -1128,7 +1128,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: # /forcesell 123456 context = MagicMock() context.args = ["123456"] - telegram._forcesell(update=update, context=context) + telegram._forceexit(update=update, context=context) assert msg_mock.call_count == 1 assert 'invalid argument' in msg_mock.call_args_list[0][0][0]