Repository: freqtrade/freqtrade

Issue:
A command to prevent the bot to execute buy (/stopbuy)
Comes handy when you encounter some reason you want to stop the bot but dealing with currently open trades. sell should be activated but no more buy ...


Discussion:
you can set max_open_trades to 0 and reload the configuration ... so the bot will "gracefully" stop by selling all open trades according to the respective rules ..
you can also remove all pairs from whitelist then reload the config ;) 
those solutions are not practical when you are outside not having access to your server :/
There is another related enhancement to this, also as a safety measure in case when something is wrong with the trades initiated by the bot or as a general safety measure -- close all open or partially filled orders and/or close the positions at graceful exit. So simply command the bot to exit and it will return all positions to zeros.

I'm not saying this command would not be usefull - just pointing out alternatives we currently would have.

@hroff-1902 
well that's `/forcesell all` ... and then /stop
> I'm not saying this command would not be usefull - just pointing out alternatives we currently would have.
> 
> @hroff-1902
> well that's `/forcesell all` ... and then /stop

Have not known of /forcesell command yet, still studying all the capabilities implemented in the bot... :-)

It should be better */stopbuy*, /forcesell all, /stop in order to prevent opening new trades between /forcesell and /stop, if /forcesell does not stop new buys by itself...


Resolving patch:
diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -328,6 +328,16 @@ def _rpc_reload_conf(self) -> Dict[str, str]:
         self._freqtrade.state = State.RELOAD_CONF
         return {'status': 'reloading config ...'}
 
+    def _rpc_stopbuy(self) -> Dict[str, str]:
+        """
+        Handler to stop buying, but handle open trades gracefully.
+        """
+        if self._freqtrade.state == State.RUNNING:
+            # Set 'max_open_trades' to 0
+            self._freqtrade.config['max_open_trades'] = 0
+
+        return {'status': 'No more buy will occur from now. Run /reload_conf to reset.'}
+
     def _rpc_forcesell(self, trade_id) -> None:
         """
         Handler for forcesell <id>.
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -91,6 +91,7 @@ def _init(self) -> None:
             CommandHandler('daily', self._daily),
             CommandHandler('count', self._count),
             CommandHandler('reload_conf', self._reload_conf),
+            CommandHandler('stopbuy', self._stopbuy),
             CommandHandler('whitelist', self._whitelist),
             CommandHandler('help', self._help),
             CommandHandler('version', self._version),
@@ -362,6 +363,18 @@ def _reload_conf(self, bot: Bot, update: Update) -> None:
         msg = self._rpc_reload_conf()
         self._send_msg('Status: `{status}`'.format(**msg), bot=bot)
 
+    @authorized_only
+    def _stopbuy(self, bot: Bot, update: Update) -> None:
+        """
+        Handler for /stop_buy.
+        Sets max_open_trades to 0 and gracefully sells all open trades
+        :param bot: telegram bot
+        :param update: message update
+        :return: None
+        """
+        msg = self._rpc_stopbuy()
+        self._send_msg('Status: `{status}`'.format(**msg), bot=bot)
+
     @authorized_only
     def _forcesell(self, bot: Bot, update: Update) -> None:
         """
@@ -481,6 +494,7 @@ def _help(self, bot: Bot, update: Update) -> None:
                   "*/count:* `Show number of trades running compared to allowed number of trades`" \
                   "\n" \
                   "*/balance:* `Show account balance per currency`\n" \
+                  "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" \
                   "*/reload_conf:* `Reload configuration file` \n" \
                   "*/whitelist:* `Show current whitelist` \n" \
                   "*/help:* `This help message`\n" \


Test patch:
diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py
--- a/freqtrade/tests/rpc/test_rpc.py
+++ b/freqtrade/tests/rpc/test_rpc.py
@@ -406,6 +406,26 @@ def test_rpc_stop(mocker, default_conf) -> None:
     assert freqtradebot.state == State.STOPPED
 
 
+def test_rpc_stopbuy(mocker, default_conf) -> None:
+    patch_coinmarketcap(mocker)
+    patch_exchange(mocker)
+    mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
+    mocker.patch.multiple(
+        'freqtrade.exchange.Exchange',
+        get_ticker=MagicMock()
+    )
+
+    freqtradebot = FreqtradeBot(default_conf)
+    patch_get_signal(freqtradebot, (True, False))
+    rpc = RPC(freqtradebot)
+    freqtradebot.state = State.RUNNING
+
+    assert freqtradebot.config['max_open_trades'] != 0
+    result = rpc._rpc_stopbuy()
+    assert {'status': 'No more buy will occur from now. Run /reload_conf to reset.'} == result
+    assert freqtradebot.config['max_open_trades'] == 0
+
+
 def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
     patch_coinmarketcap(mocker)
     patch_exchange(mocker)
diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py
--- a/freqtrade/tests/rpc/test_rpc_telegram.py
+++ b/freqtrade/tests/rpc/test_rpc_telegram.py
@@ -74,7 +74,7 @@ def test_init(default_conf, mocker, caplog) -> None:
     message_str = "rpc.telegram is listening for following commands: [['status'], ['profit'], " \
                   "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], " \
                   "['performance'], ['daily'], ['count'], ['reload_conf'], " \
-                  "['whitelist'], ['help'], ['version']]"
+                  "['stopbuy'], ['whitelist'], ['help'], ['version']]"
 
     assert log_has(message_str, caplog.record_tuples)
 
@@ -662,6 +662,26 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
     assert 'already stopped' in msg_mock.call_args_list[0][0][0]
 
 
+def test_stopbuy_handle(default_conf, update, mocker) -> None:
+    patch_coinmarketcap(mocker)
+    msg_mock = MagicMock()
+    mocker.patch.multiple(
+        'freqtrade.rpc.telegram.Telegram',
+        _init=MagicMock(),
+        _send_msg=msg_mock
+    )
+
+    freqtradebot = get_patched_freqtradebot(mocker, default_conf)
+    telegram = Telegram(freqtradebot)
+
+    assert freqtradebot.config['max_open_trades'] != 0
+    telegram._stopbuy(bot=MagicMock(), update=update)
+    assert freqtradebot.config['max_open_trades'] == 0
+    assert msg_mock.call_count == 1
+    assert 'No more buy will occur from now. Run /reload_conf to reset.' \
+        in msg_mock.call_args_list[0][0][0]
+
+
 def test_reload_conf_handle(default_conf, update, mocker) -> None:
     patch_coinmarketcap(mocker)
     msg_mock = MagicMock()