ruff format: update ft_client

This commit is contained in:
Matthias 2024-05-12 16:16:26 +02:00
parent 4f5bf632fc
commit 15f32be176
4 changed files with 189 additions and 186 deletions

View File

@ -1,26 +1,37 @@
from freqtrade_client.ft_rest_client import FtRestClient from freqtrade_client.ft_rest_client import FtRestClient
__version__ = '2024.5-dev' __version__ = "2024.5-dev"
if 'dev' in __version__: if "dev" in __version__:
from pathlib import Path from pathlib import Path
try: try:
import subprocess import subprocess
freqtrade_basedir = Path(__file__).parent freqtrade_basedir = Path(__file__).parent
__version__ = __version__ + '-' + subprocess.check_output( __version__ = (
['git', 'log', '--format="%h"', '-n 1'], __version__
stderr=subprocess.DEVNULL, cwd=freqtrade_basedir).decode("utf-8").rstrip().strip('"') + "-"
+ subprocess.check_output(
["git", "log", '--format="%h"', "-n 1"],
stderr=subprocess.DEVNULL,
cwd=freqtrade_basedir,
)
.decode("utf-8")
.rstrip()
.strip('"')
)
except Exception: # pragma: no cover except Exception: # pragma: no cover
# git not available, ignore # git not available, ignore
try: try:
# Try Fallback to freqtrade_commit file (created by CI while building docker image) # Try Fallback to freqtrade_commit file (created by CI while building docker image)
versionfile = Path('./freqtrade_commit') versionfile = Path("./freqtrade_commit")
if versionfile.is_file(): if versionfile.is_file():
__version__ = f"docker-{__version__}-{versionfile.read_text()[:8]}" __version__ = f"docker-{__version__}-{versionfile.read_text()[:8]}"
except Exception: except Exception:
pass pass
__all__ = ['FtRestClient'] __all__ = ["FtRestClient"]

View File

@ -15,41 +15,44 @@ from freqtrade_client.ft_rest_client import FtRestClient
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
) )
logger = logging.getLogger("ft_rest_client") logger = logging.getLogger("ft_rest_client")
def add_arguments(args: Any = None): def add_arguments(args: Any = None):
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
prog="freqtrade-client", prog="freqtrade-client",
description="Client for the freqtrade REST API", description="Client for the freqtrade REST API",
)
parser.add_argument(
"command", help="Positional argument defining the command to execute.", nargs="?"
)
parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {__version__}")
parser.add_argument(
"--show",
help="Show possible methods with this client",
dest="show",
action="store_true",
default=False,
) )
parser.add_argument("command",
help="Positional argument defining the command to execute.",
nargs="?"
)
parser.add_argument('-V', '--version', action='version', version=f'%(prog)s {__version__}')
parser.add_argument('--show',
help='Show possible methods with this client',
dest='show',
action='store_true',
default=False
)
parser.add_argument('-c', '--config', parser.add_argument(
help='Specify configuration file (default: %(default)s). ', "-c",
dest='config', "--config",
type=str, help="Specify configuration file (default: %(default)s). ",
metavar='PATH', dest="config",
default='config.json' type=str,
) metavar="PATH",
default="config.json",
)
parser.add_argument("command_arguments", parser.add_argument(
help="Positional arguments for the parameters for [command]", "command_arguments",
nargs="*", help="Positional arguments for the parameters for [command]",
default=[] nargs="*",
) default=[],
)
pargs = parser.parse_args(args) pargs = parser.parse_args(args)
return vars(pargs) return vars(pargs)
@ -59,8 +62,9 @@ def load_config(configfile):
file = Path(configfile) file = Path(configfile)
if file.is_file(): if file.is_file():
with file.open("r") as f: with file.open("r") as f:
config = rapidjson.load(f, parse_mode=rapidjson.PM_COMMENTS | config = rapidjson.load(
rapidjson.PM_TRAILING_COMMAS) f, parse_mode=rapidjson.PM_COMMENTS | rapidjson.PM_TRAILING_COMMAS
)
return config return config
else: else:
logger.warning(f"Could not load config file {file}.") logger.warning(f"Could not load config file {file}.")
@ -72,27 +76,26 @@ def print_commands():
client = FtRestClient(None) client = FtRestClient(None)
print("Possible commands:\n") print("Possible commands:\n")
for x, _ in inspect.getmembers(client): for x, _ in inspect.getmembers(client):
if not x.startswith('_'): if not x.startswith("_"):
doc = re.sub(':return:.*', '', getattr(client, x).__doc__, flags=re.MULTILINE).rstrip() doc = re.sub(":return:.*", "", getattr(client, x).__doc__, flags=re.MULTILINE).rstrip()
print(f"{x}\n\t{doc}\n") print(f"{x}\n\t{doc}\n")
def main_exec(args: Dict[str, Any]): def main_exec(args: Dict[str, Any]):
if args.get("show"): if args.get("show"):
print_commands() print_commands()
sys.exit() sys.exit()
config = load_config(args['config']) config = load_config(args["config"])
url = config.get('api_server', {}).get('listen_ip_address', '127.0.0.1') url = config.get("api_server", {}).get("listen_ip_address", "127.0.0.1")
port = config.get('api_server', {}).get('listen_port', '8080') port = config.get("api_server", {}).get("listen_port", "8080")
username = config.get('api_server', {}).get('username') username = config.get("api_server", {}).get("username")
password = config.get('api_server', {}).get('password') password = config.get("api_server", {}).get("password")
server_url = f"http://{url}:{port}" server_url = f"http://{url}:{port}"
client = FtRestClient(server_url, username, password) client = FtRestClient(server_url, username, password)
m = [x for x, y in inspect.getmembers(client) if not x.startswith('_')] m = [x for x, y in inspect.getmembers(client) if not x.startswith("_")]
command = args["command"] command = args["command"]
if command not in m: if command not in m:
logger.error(f"Command {command} not defined") logger.error(f"Command {command} not defined")

View File

@ -21,31 +21,26 @@ PostDataT = Optional[Union[Dict[str, Any], List[Dict[str, Any]]]]
class FtRestClient: class FtRestClient:
def __init__(
def __init__(self, serverurl, username=None, password=None, *, self, serverurl, username=None, password=None, *, pool_connections=10, pool_maxsize=10
pool_connections=10, pool_maxsize=10): ):
self._serverurl = serverurl self._serverurl = serverurl
self._session = requests.Session() self._session = requests.Session()
# allow configuration of pool # allow configuration of pool
adapter = requests.adapters.HTTPAdapter( adapter = requests.adapters.HTTPAdapter(
pool_connections=pool_connections, pool_connections=pool_connections, pool_maxsize=pool_maxsize
pool_maxsize=pool_maxsize
) )
self._session.mount('http://', adapter) self._session.mount("http://", adapter)
self._session.auth = (username, password) self._session.auth = (username, password)
def _call(self, method, apipath, params: Optional[dict] = None, data=None, files=None): def _call(self, method, apipath, params: Optional[dict] = None, data=None, files=None):
if str(method).upper() not in ("GET", "POST", "PUT", "DELETE"):
if str(method).upper() not in ('GET', 'POST', 'PUT', 'DELETE'): raise ValueError(f"invalid method <{method}>")
raise ValueError(f'invalid method <{method}>')
basepath = f"{self._serverurl}/api/v1/{apipath}" basepath = f"{self._serverurl}/api/v1/{apipath}"
hd = {"Accept": "application/json", hd = {"Accept": "application/json", "Content-Type": "application/json"}
"Content-Type": "application/json"
}
# Split url # Split url
schema, netloc, path, par, query, fragment = urlparse(basepath) schema, netloc, path, par, query, fragment = urlparse(basepath)
@ -151,7 +146,7 @@ class FtRestClient:
""" """
return self._delete(f"locks/{lock_id}") return self._delete(f"locks/{lock_id}")
def lock_add(self, pair: str, until: str, side: str = '*', reason: str = ''): def lock_add(self, pair: str, until: str, side: str = "*", reason: str = ""):
"""Lock pair """Lock pair
:param pair: Pair to lock :param pair: Pair to lock
@ -160,14 +155,7 @@ class FtRestClient:
:param reason: Reason for the lock :param reason: Reason for the lock
:return: json object :return: json object
""" """
data = [ data = [{"pair": pair, "until": until, "side": side, "reason": reason}]
{
"pair": pair,
"until": until,
"side": side,
"reason": reason
}
]
return self._post("locks", data=data) return self._post("locks", data=data)
def daily(self, days=None): def daily(self, days=None):
@ -234,7 +222,7 @@ class FtRestClient:
return self._get("version") return self._get("version")
def show_config(self): def show_config(self):
""" Returns part of the configuration, relevant for trading operations. """Returns part of the configuration, relevant for trading operations.
:return: json object containing the version :return: json object containing the version
""" """
return self._get("show_config") return self._get("show_config")
@ -244,7 +232,7 @@ class FtRestClient:
configstatus = self.show_config() configstatus = self.show_config()
if not configstatus: if not configstatus:
return {"status": "not_running"} return {"status": "not_running"}
elif configstatus['state'] == "running": elif configstatus["state"] == "running":
return {"status": "pong"} return {"status": "pong"}
else: else:
return {"status": "not_running"} return {"status": "not_running"}
@ -266,9 +254,9 @@ class FtRestClient:
""" """
params = {} params = {}
if limit: if limit:
params['limit'] = limit params["limit"] = limit
if offset: if offset:
params['offset'] = offset params["offset"] = offset
return self._get("trades", params) return self._get("trades", params)
def trade(self, trade_id): def trade(self, trade_id):
@ -321,9 +309,7 @@ class FtRestClient:
:param price: Optional - price to buy :param price: Optional - price to buy
:return: json object of the trade :return: json object of the trade
""" """
data = {"pair": pair, data = {"pair": pair, "price": price}
"price": price
}
return self._post("forcebuy", data=data) return self._post("forcebuy", data=data)
def forceenter(self, pair, side, price=None): def forceenter(self, pair, side, price=None):
@ -334,11 +320,12 @@ class FtRestClient:
:param price: Optional - price to buy :param price: Optional - price to buy
:return: json object of the trade :return: json object of the trade
""" """
data = {"pair": pair, data = {
"side": side, "pair": pair,
} "side": side,
}
if price: if price:
data['price'] = price data["price"] = price
return self._post("forceenter", data=data) return self._post("forceenter", data=data)
def forceexit(self, tradeid, ordertype=None, amount=None): def forceexit(self, tradeid, ordertype=None, amount=None):
@ -350,11 +337,14 @@ class FtRestClient:
:return: json object :return: json object
""" """
return self._post("forceexit", data={ return self._post(
"tradeid": tradeid, "forceexit",
"ordertype": ordertype, data={
"amount": amount, "tradeid": tradeid,
}) "ordertype": ordertype,
"amount": amount,
},
)
def strategies(self): def strategies(self):
"""Lists available strategies """Lists available strategies
@ -392,10 +382,13 @@ class FtRestClient:
:param stake_currency: Only pairs that include this timeframe :param stake_currency: Only pairs that include this timeframe
:return: json object :return: json object
""" """
return self._get("available_pairs", params={ return self._get(
"stake_currency": stake_currency if timeframe else '', "available_pairs",
"timeframe": timeframe if timeframe else '', params={
}) "stake_currency": stake_currency if timeframe else "",
"timeframe": timeframe if timeframe else "",
},
)
def pair_candles(self, pair, timeframe, limit=None, columns=None): def pair_candles(self, pair, timeframe, limit=None, columns=None):
"""Return live dataframe for <pair><timeframe>. """Return live dataframe for <pair><timeframe>.
@ -411,14 +404,11 @@ class FtRestClient:
"timeframe": timeframe, "timeframe": timeframe,
} }
if limit: if limit:
params['limit'] = limit params["limit"] = limit
if columns is not None: if columns is not None:
params['columns'] = columns params["columns"] = columns
return self._post( return self._post("pair_candles", data=params)
"pair_candles",
data=params
)
return self._get("pair_candles", params=params) return self._get("pair_candles", params=params)
@ -432,13 +422,16 @@ class FtRestClient:
:param timerange: Timerange to get data for (same format than --timerange endpoints) :param timerange: Timerange to get data for (same format than --timerange endpoints)
:return: json object :return: json object
""" """
return self._get("pair_history", params={ return self._get(
"pair": pair, "pair_history",
"timeframe": timeframe, params={
"strategy": strategy, "pair": pair,
"freqaimodel": freqaimodel, "timeframe": timeframe,
"timerange": timerange if timerange else '', "strategy": strategy,
}) "freqaimodel": freqaimodel,
"timerange": timerange if timerange else "",
},
)
def sysinfo(self): def sysinfo(self):
"""Provides system information (CPU, RAM usage) """Provides system information (CPU, RAM usage)

View File

@ -14,7 +14,7 @@ def log_has_re(line, logs):
def get_rest_client(): def get_rest_client():
client = FtRestClient('http://localhost:8080', 'freqtrader', 'password') client = FtRestClient("http://localhost:8080", "freqtrader", "password")
client._session = MagicMock() client._session = MagicMock()
request_mock = MagicMock() request_mock = MagicMock()
client._session.request = request_mock client._session.request = request_mock
@ -22,93 +22,96 @@ def get_rest_client():
def test_FtRestClient_init(): def test_FtRestClient_init():
client = FtRestClient('http://localhost:8080', 'freqtrader', 'password') client = FtRestClient("http://localhost:8080", "freqtrader", "password")
assert client is not None assert client is not None
assert client._serverurl == 'http://localhost:8080' assert client._serverurl == "http://localhost:8080"
assert client._session is not None assert client._session is not None
assert client._session.auth is not None assert client._session.auth is not None
assert client._session.auth == ('freqtrader', 'password') assert client._session.auth == ("freqtrader", "password")
@pytest.mark.parametrize('method', ['GET', 'POST', 'DELETE']) @pytest.mark.parametrize("method", ["GET", "POST", "DELETE"])
def test_FtRestClient_call(method): def test_FtRestClient_call(method):
client, mock = get_rest_client() client, mock = get_rest_client()
client._call(method, '/dummytest') client._call(method, "/dummytest")
assert mock.call_count == 1 assert mock.call_count == 1
getattr(client, f"_{method.lower()}")('/dummytest') getattr(client, f"_{method.lower()}")("/dummytest")
assert mock.call_count == 2 assert mock.call_count == 2
def test_FtRestClient_call_invalid(caplog): def test_FtRestClient_call_invalid(caplog):
client, _ = get_rest_client() client, _ = get_rest_client()
with pytest.raises(ValueError): with pytest.raises(ValueError):
client._call('PUTTY', '/dummytest') client._call("PUTTY", "/dummytest")
client._session.request = MagicMock(side_effect=ConnectionError()) client._session.request = MagicMock(side_effect=ConnectionError())
client._call('GET', '/dummytest') client._call("GET", "/dummytest")
assert log_has_re('Connection error', caplog) assert log_has_re("Connection error", caplog)
@pytest.mark.parametrize('method,args', [ @pytest.mark.parametrize(
('start', []), "method,args",
('stop', []), [
('stopbuy', []), ("start", []),
('reload_config', []), ("stop", []),
('balance', []), ("stopbuy", []),
('count', []), ("reload_config", []),
('entries', []), ("balance", []),
('exits', []), ("count", []),
('mix_tags', []), ("entries", []),
('locks', []), ("exits", []),
('lock_add', ["XRP/USDT", '2024-01-01 20:00:00Z', '*', 'rand']), ("mix_tags", []),
('delete_lock', [2]), ("locks", []),
('daily', []), ("lock_add", ["XRP/USDT", "2024-01-01 20:00:00Z", "*", "rand"]),
('daily', [15]), ("delete_lock", [2]),
('weekly', []), ("daily", []),
('weekly', [15]), ("daily", [15]),
('monthly', []), ("weekly", []),
('monthly', [12]), ("weekly", [15]),
('edge', []), ("monthly", []),
('profit', []), ("monthly", [12]),
('stats', []), ("edge", []),
('performance', []), ("profit", []),
('status', []), ("stats", []),
('version', []), ("performance", []),
('show_config', []), ("status", []),
('ping', []), ("version", []),
('logs', []), ("show_config", []),
('logs', [55]), ("ping", []),
('trades', []), ("logs", []),
('trades', [5]), ("logs", [55]),
('trades', [5, 5]), # With offset ("trades", []),
('trade', [1]), ("trades", [5]),
('delete_trade', [1]), ("trades", [5, 5]), # With offset
('cancel_open_order', [1]), ("trade", [1]),
('whitelist', []), ("delete_trade", [1]),
('blacklist', []), ("cancel_open_order", [1]),
('blacklist', ['XRP/USDT']), ("whitelist", []),
('blacklist', ['XRP/USDT', 'BTC/USDT']), ("blacklist", []),
('forcebuy', ['XRP/USDT']), ("blacklist", ["XRP/USDT"]),
('forcebuy', ['XRP/USDT', 1.5]), ("blacklist", ["XRP/USDT", "BTC/USDT"]),
('forceenter', ['XRP/USDT', 'short']), ("forcebuy", ["XRP/USDT"]),
('forceenter', ['XRP/USDT', 'short', 1.5]), ("forcebuy", ["XRP/USDT", 1.5]),
('forceexit', [1]), ("forceenter", ["XRP/USDT", "short"]),
('forceexit', [1, 'limit']), ("forceenter", ["XRP/USDT", "short", 1.5]),
('forceexit', [1, 'limit', 100]), ("forceexit", [1]),
('strategies', []), ("forceexit", [1, "limit"]),
('strategy', ['sampleStrategy']), ("forceexit", [1, "limit", 100]),
('pairlists_available', []), ("strategies", []),
('plot_config', []), ("strategy", ["sampleStrategy"]),
('available_pairs', []), ("pairlists_available", []),
('available_pairs', ['5m']), ("plot_config", []),
('pair_candles', ['XRP/USDT', '5m']), ("available_pairs", []),
('pair_candles', ['XRP/USDT', '5m', 500]), ("available_pairs", ["5m"]),
('pair_history', ['XRP/USDT', '5m', 'SampleStrategy']), ("pair_candles", ["XRP/USDT", "5m"]),
('sysinfo', []), ("pair_candles", ["XRP/USDT", "5m", 500]),
('health', []), ("pair_history", ["XRP/USDT", "5m", "SampleStrategy"]),
]) ("sysinfo", []),
("health", []),
],
)
def test_FtRestClient_call_explicit_methods(method, args): def test_FtRestClient_call_explicit_methods(method, args):
client, mock = get_rest_client() client, mock = get_rest_client()
exec = getattr(client, method) exec = getattr(client, method)
@ -118,37 +121,30 @@ def test_FtRestClient_call_explicit_methods(method, args):
def test_ft_client(mocker, capsys, caplog): def test_ft_client(mocker, capsys, caplog):
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
args = add_arguments(['-V']) args = add_arguments(["-V"])
args = add_arguments(['--show']) args = add_arguments(["--show"])
assert isinstance(args, dict) assert isinstance(args, dict)
assert args['show'] is True assert args["show"] is True
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
main_exec(args) main_exec(args)
captured = capsys.readouterr() captured = capsys.readouterr()
assert 'Possible commands' in captured.out assert "Possible commands" in captured.out
mock = mocker.patch('freqtrade_client.ft_client.FtRestClient._call') mock = mocker.patch("freqtrade_client.ft_client.FtRestClient._call")
args = add_arguments([ args = add_arguments(["--config", "tests/testdata/testconfigs/main_test_config.json", "ping"])
'--config',
'tests/testdata/testconfigs/main_test_config.json',
'ping'
])
main_exec(args) main_exec(args)
captured = capsys.readouterr() captured = capsys.readouterr()
assert mock.call_count == 1 assert mock.call_count == 1
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
args = add_arguments(['--config', 'tests/testdata/testconfigs/nonexisting.json']) args = add_arguments(["--config", "tests/testdata/testconfigs/nonexisting.json"])
main_exec(args) main_exec(args)
assert log_has_re(r'Could not load config file .*nonexisting\.json\.', assert log_has_re(r"Could not load config file .*nonexisting\.json\.", caplog)
caplog)
args = add_arguments([ args = add_arguments(
'--config', ["--config", "tests/testdata/testconfigs/main_test_config.json", "whatever"]
'tests/testdata/testconfigs/main_test_config.json', )
'whatever'
])
main_exec(args) main_exec(args)
assert log_has_re('Command whatever not defined', caplog) assert log_has_re("Command whatever not defined", caplog)