Merge pull request #10269 from freqtrade/frog-rest-client-1

Add force_enter optional args and tests
This commit is contained in:
Matthias 2024-06-18 12:19:38 +02:00 committed by GitHub
commit a20abfc3c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 170 additions and 67 deletions

View File

@ -118,6 +118,14 @@ By default, the script assumes `127.0.0.1` (localhost) and port `8080` to be use
freqtrade-client --config rest_config.json <command> [optional parameters]
```
Commands with many arguments may require keyword arguments (for clarity) - which can be provided as follows:
``` bash
freqtrade-client --config rest_config.json forceenter BTC/USDT long enter_tag=GutFeeling
```
This method will work for all arguments - check the "show" command for a list of available parameters.
??? Note "Programmatic use"
The `freqtrade-client` package (installable independent of freqtrade) can be used in your own scripts to interact with the freqtrade API.
to do so, please use the following:

View File

@ -81,12 +81,12 @@ def print_commands():
print(f"{x}\n\t{doc}\n")
def main_exec(args: Dict[str, Any]):
if args.get("show"):
def main_exec(parsed: Dict[str, Any]):
if parsed.get("show"):
print_commands()
sys.exit()
config = load_config(args["config"])
config = load_config(parsed["config"])
url = config.get("api_server", {}).get("listen_ip_address", "127.0.0.1")
port = config.get("api_server", {}).get("listen_port", "8080")
username = config.get("api_server", {}).get("username")
@ -96,13 +96,24 @@ def main_exec(args: Dict[str, Any]):
client = FtRestClient(server_url, username, password)
m = [x for x, y in inspect.getmembers(client) if not x.startswith("_")]
command = args["command"]
command = parsed["command"]
if command not in m:
logger.error(f"Command {command} not defined")
print_commands()
return
print(json.dumps(getattr(client, command)(*args["command_arguments"])))
# Split arguments with = into key/value pairs
kwargs = {x.split("=")[0]: x.split("=")[1] for x in parsed["command_arguments"] if "=" in x}
args = [x for x in parsed["command_arguments"] if "=" not in x]
try:
res = getattr(client, command)(*args, **kwargs)
print(json.dumps(res))
except TypeError as e:
logger.error(f"Error executing command {command}: {e}")
sys.exit(1)
except Exception as e:
logger.error(f"Fatal Error executing command {command}: {e}")
sys.exit(1)
def main():

View File

@ -54,7 +54,7 @@ class FtRestClient:
# return resp.text
return resp.json()
except ConnectionError:
logger.warning("Connection error")
logger.warning(f"Connection error - could not connect to {netloc}.")
def _get(self, apipath, params: ParamsT = None):
return self._call("GET", apipath, params=params)
@ -312,20 +312,48 @@ class FtRestClient:
data = {"pair": pair, "price": price}
return self._post("forcebuy", data=data)
def forceenter(self, pair, side, price=None):
def forceenter(
self,
pair,
side,
price=None,
*,
order_type=None,
stake_amount=None,
leverage=None,
enter_tag=None,
):
"""Force entering a trade
:param pair: Pair to buy (ETH/BTC)
:param side: 'long' or 'short'
:param price: Optional - price to buy
:param order_type: Optional keyword argument - 'limit' or 'market'
:param stake_amount: Optional keyword argument - stake amount (as float)
:param leverage: Optional keyword argument - leverage (as float)
:param enter_tag: Optional keyword argument - entry tag (as string, default: 'force_enter')
:return: json object of the trade
"""
data = {
"pair": pair,
"side": side,
}
if price:
data["price"] = price
if order_type:
data["ordertype"] = order_type
if stake_amount:
data["stakeamount"] = stake_amount
if leverage:
data["leverage"] = leverage
if enter_tag:
data["entry_tag"] = enter_tag
return self._post("forceenter", data=data)
def forceexit(self, tradeid, ordertype=None, amount=None):

View File

@ -1,5 +1,5 @@
import re
from unittest.mock import MagicMock
from unittest.mock import ANY, MagicMock
import pytest
from requests.exceptions import ConnectionError
@ -52,70 +52,89 @@ def test_FtRestClient_call_invalid(caplog):
@pytest.mark.parametrize(
"method,args",
"method,args,kwargs",
[
("start", []),
("stop", []),
("stopbuy", []),
("reload_config", []),
("balance", []),
("count", []),
("entries", []),
("exits", []),
("mix_tags", []),
("locks", []),
("lock_add", ["XRP/USDT", "2024-01-01 20:00:00Z", "*", "rand"]),
("delete_lock", [2]),
("daily", []),
("daily", [15]),
("weekly", []),
("weekly", [15]),
("monthly", []),
("monthly", [12]),
("edge", []),
("profit", []),
("stats", []),
("performance", []),
("status", []),
("version", []),
("show_config", []),
("ping", []),
("logs", []),
("logs", [55]),
("trades", []),
("trades", [5]),
("trades", [5, 5]), # With offset
("trade", [1]),
("delete_trade", [1]),
("cancel_open_order", [1]),
("whitelist", []),
("blacklist", []),
("blacklist", ["XRP/USDT"]),
("blacklist", ["XRP/USDT", "BTC/USDT"]),
("forcebuy", ["XRP/USDT"]),
("forcebuy", ["XRP/USDT", 1.5]),
("forceenter", ["XRP/USDT", "short"]),
("forceenter", ["XRP/USDT", "short", 1.5]),
("forceexit", [1]),
("forceexit", [1, "limit"]),
("forceexit", [1, "limit", 100]),
("strategies", []),
("strategy", ["sampleStrategy"]),
("pairlists_available", []),
("plot_config", []),
("available_pairs", []),
("available_pairs", ["5m"]),
("pair_candles", ["XRP/USDT", "5m"]),
("pair_candles", ["XRP/USDT", "5m", 500]),
("pair_history", ["XRP/USDT", "5m", "SampleStrategy"]),
("sysinfo", []),
("health", []),
("start", [], {}),
("stop", [], {}),
("stopbuy", [], {}),
("reload_config", [], {}),
("balance", [], {}),
("count", [], {}),
("entries", [], {}),
("exits", [], {}),
("mix_tags", [], {}),
("locks", [], {}),
("lock_add", ["XRP/USDT", "2024-01-01 20:00:00Z", "*", "rand"], {}),
("delete_lock", [2], {}),
("daily", [], {}),
("daily", [15], {}),
("weekly", [], {}),
("weekly", [15], {}),
("monthly", [], {}),
("monthly", [12], {}),
("edge", [], {}),
("profit", [], {}),
("stats", [], {}),
("performance", [], {}),
("status", [], {}),
("version", [], {}),
("show_config", [], {}),
("ping", [], {}),
("logs", [], {}),
("logs", [55], {}),
("trades", [], {}),
("trades", [5], {}),
("trades", [5, 5], {}), # With offset
("trade", [1], {}),
("delete_trade", [1], {}),
("cancel_open_order", [1], {}),
("whitelist", [], {}),
("blacklist", [], {}),
("blacklist", ["XRP/USDT"], {}),
("blacklist", ["XRP/USDT", "BTC/USDT"], {}),
("forcebuy", ["XRP/USDT"], {}),
("forcebuy", ["XRP/USDT", 1.5], {}),
("forceenter", ["XRP/USDT", "short"], {}),
("forceenter", ["XRP/USDT", "short", 1.5], {}),
("forceenter", ["XRP/USDT", "short", 1.5], {"order_type": "market"}),
("forceenter", ["XRP/USDT", "short", 1.5], {"order_type": "market", "stake_amount": 100}),
(
"forceenter",
["XRP/USDT", "short", 1.5],
{"order_type": "market", "stake_amount": 100, "leverage": 10.0},
),
(
"forceenter",
["XRP/USDT", "short", 1.5],
{
"order_type": "market",
"stake_amount": 100,
"leverage": 10.0,
"enter_tag": "test_force_enter",
},
),
("forceexit", [1], {}),
("forceexit", [1, "limit"], {}),
("forceexit", [1, "limit", 100], {}),
("strategies", [], {}),
("strategy", ["sampleStrategy"], {}),
("pairlists_available", [], {}),
("plot_config", [], {}),
("available_pairs", [], {}),
("available_pairs", ["5m"], {}),
("pair_candles", ["XRP/USDT", "5m"], {}),
("pair_candles", ["XRP/USDT", "5m", 500], {}),
("pair_candles", ["XRP/USDT", "5m", 500], {"columns": ["close_time,close"]}),
("pair_history", ["XRP/USDT", "5m", "SampleStrategy"], {}),
("pair_history", ["XRP/USDT", "5m"], {"strategy": "SampleStrategy"}),
("sysinfo", [], {}),
("health", [], {}),
],
)
def test_FtRestClient_call_explicit_methods(method, args):
def test_FtRestClient_call_explicit_methods(method, args, kwargs):
client, mock = get_rest_client()
exec = getattr(client, method)
exec(*args)
exec(*args, **kwargs)
assert mock.call_count == 1
@ -148,3 +167,40 @@ def test_ft_client(mocker, capsys, caplog):
)
main_exec(args)
assert log_has_re("Command whatever not defined", caplog)
@pytest.mark.parametrize(
"params, expected_args, expected_kwargs",
[
("forceenter BTC/USDT long", ["BTC/USDT", "long"], {}),
("forceenter BTC/USDT long limit", ["BTC/USDT", "long", "limit"], {}),
(
# Skip most parameters, only providing enter_tag
"forceenter BTC/USDT long enter_tag=deadBeef",
["BTC/USDT", "long"],
{"enter_tag": "deadBeef"},
),
(
"forceenter BTC/USDT long invalid_key=123",
[],
SystemExit,
# {"invalid_key": "deadBeef"},
),
],
)
def test_ft_client_argparsing(mocker, params, expected_args, expected_kwargs, caplog):
mocked_method = params.split(" ")[0]
mocker.patch("freqtrade_client.ft_client.load_config", return_value={}, autospec=True)
mm = mocker.patch(
f"freqtrade_client.ft_client.FtRestClient.{mocked_method}", return_value={}, autospec=True
)
args = add_arguments(params.split(" "))
if isinstance(expected_kwargs, dict):
main_exec(args)
mm.assert_called_once_with(ANY, *expected_args, **expected_kwargs)
else:
with pytest.raises(expected_kwargs):
main_exec(args)
assert log_has_re(f"Error executing command {mocked_method}: got an unexpected .*", caplog)
mm.assert_not_called()