Repository: freqtrade/freqtrade

Issue:
Bugs in the rest-client
(From Slack)

The rest-client: 
1) does not understand 'extended' syntax of config (with extra trailing commas in the lists, for example), validation fails;

2) does not fail if config specified does not exist (for instance, if `-c /config.json` was typed by an error instead of `-c ./config.json`).

3) In the following output:
```
$ python3 scripts/rest_client.py --c config.json status
{'error': 'Error querying _status: no active trade'}
```
the "Error querying" message is misleading. Why fetching status returns an error?



Discussion:

Resolving patch:
diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py
--- a/freqtrade/rpc/api_server.py
+++ b/freqtrade/rpc/api_server.py
@@ -330,8 +330,11 @@ def _status(self):
 
         Returns the current status of the trades in json format
         """
-        results = self._rpc_trade_status()
-        return self.rest_dump(results)
+        try:
+            results = self._rpc_trade_status()
+            return self.rest_dump(results)
+        except RPCException:
+            return self.rest_dump([])
 
     @require_login
     @rpc_catch_errors
diff --git a/scripts/rest_client.py b/scripts/rest_client.py
--- a/scripts/rest_client.py
+++ b/scripts/rest_client.py
@@ -8,12 +8,15 @@
 """
 
 import argparse
+import inspect
 import json
+import re
 import logging
-import inspect
-from urllib.parse import urlencode, urlparse, urlunparse
+import sys
 from pathlib import Path
+from urllib.parse import urlencode, urlparse, urlunparse
 
+import rapidjson
 import requests
 from requests.exceptions import ConnectionError
 
@@ -63,100 +66,99 @@ def _post(self, apipath, params: dict = None, data: dict = None):
         return self._call("POST", apipath, params=params, data=data)
 
     def start(self):
-        """
-        Start the bot if it's in stopped state.
+        """Start the bot if it's in the stopped state.
+
         :return: json object
         """
         return self._post("start")
 
     def stop(self):
-        """
-        Stop the bot. Use start to restart
+        """Stop the bot. Use `start` to restart.
+
         :return: json object
         """
         return self._post("stop")
 
     def stopbuy(self):
-        """
-        Stop buying (but handle sells gracefully).
-        use reload_conf to reset
+        """Stop buying (but handle sells gracefully). Use `reload_conf` to reset.
+
         :return: json object
         """
         return self._post("stopbuy")
 
     def reload_conf(self):
-        """
-        Reload configuration
+        """Reload configuration.
+
         :return: json object
         """
         return self._post("reload_conf")
 
     def balance(self):
-        """
-        Get the account balance
+        """Get the account balance.
+
         :return: json object
         """
         return self._get("balance")
 
     def count(self):
-        """
-        Returns the amount of open trades
+        """Return the amount of open trades.
+
         :return: json object
         """
         return self._get("count")
 
     def daily(self, days=None):
-        """
-        Returns the amount of open trades
+        """Return the amount of open trades.
+
         :return: json object
         """
         return self._get("daily", params={"timescale": days} if days else None)
 
     def edge(self):
-        """
-        Returns information about edge
+        """Return information about edge.
+
         :return: json object
         """
         return self._get("edge")
 
     def profit(self):
-        """
-        Returns the profit summary
+        """Return the profit summary.
+
         :return: json object
         """
         return self._get("profit")
 
     def performance(self):
-        """
-        Returns the performance of the different coins
+        """Return the performance of the different coins.
+
         :return: json object
         """
         return self._get("performance")
 
     def status(self):
-        """
-        Get the status of open trades
+        """Get the status of open trades.
+
         :return: json object
         """
         return self._get("status")
 
     def version(self):
-        """
-        Returns the version of the bot
+        """Return the version of the bot.
+
         :return: json object containing the version
         """
         return self._get("version")
 
     def whitelist(self):
-        """
-        Show the current whitelist
+        """Show the current whitelist.
+
         :return: json object
         """
         return self._get("whitelist")
 
     def blacklist(self, *args):
-        """
-        Show the current blacklist
+        """Show the current blacklist.
+
         :param add: List of coins to add (example: "BNB/BTC")
         :return: json object
         """
@@ -166,8 +168,8 @@ def blacklist(self, *args):
             return self._post("blacklist", data={"blacklist": args})
 
     def forcebuy(self, pair, price=None):
-        """
-        Buy an asset
+        """Buy an asset.
+
         :param pair: Pair to buy (ETH/BTC)
         :param price: Optional - price to buy
         :return: json object of the trade
@@ -178,8 +180,8 @@ def forcebuy(self, pair, price=None):
         return self._post("forcebuy", data=data)
 
     def forcesell(self, tradeid):
-        """
-        Force-sell a trade
+        """Force-sell a trade.
+
         :param tradeid: Id of the trade (can be received via status command)
         :return: json object
         """
@@ -190,7 +192,9 @@ def forcesell(self, tradeid):
 def add_arguments():
     parser = argparse.ArgumentParser()
     parser.add_argument("command",
-                        help="Positional argument defining the command to execute.")
+                        help="Positional argument defining the command to execute.",
+                        nargs="?"
+                        )
 
     parser.add_argument('--show',
                         help='Show possible methods with this client',
@@ -221,24 +225,29 @@ def load_config(configfile):
     file = Path(configfile)
     if file.is_file():
         with file.open("r") as f:
-            config = json.load(f)
+            config = rapidjson.load(f, parse_mode=rapidjson.PM_COMMENTS |
+                                    rapidjson.PM_TRAILING_COMMAS)
         return config
-    return {}
+    else:
+        logger.warning(f"Could not load config file {file}.")
+        sys.exit(1)
 
 
 def print_commands():
     # Print dynamic help for the different commands using the commands doc-strings
     client = FtRestClient(None)
-    print("Possible commands:")
+    print("Possible commands:\n")
     for x, y in inspect.getmembers(client):
         if not x.startswith('_'):
-            print(f"{x} {getattr(client, x).__doc__}")
+            doc = re.sub(':return:.*', '', getattr(client, x).__doc__, flags=re.MULTILINE).rstrip()
+            print(f"{x}\n\t{doc}\n")
 
 
 def main(args):
 
-    if args.get("help"):
+    if args.get("show"):
         print_commands()
+        sys.exit()
 
     config = load_config(args["config"])
     url = config.get("api_server", {}).get("server_url", "127.0.0.1")


Test patch:
diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py
--- a/tests/rpc/test_rpc_apiserver.py
+++ b/tests/rpc/test_rpc_apiserver.py
@@ -417,8 +417,8 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
     )
 
     rc = client_get(client, f"{BASE_URI}/status")
-    assert_response(rc, 502)
-    assert rc.json == {'error': 'Error querying _status: no active trade'}
+    assert_response(rc, 200)
+    assert rc.json == []
 
     ftbot.create_trades()
     rc = client_get(client, f"{BASE_URI}/status")