Merge branch 'develop' into ci/ccxt.pro
This commit is contained in:
@@ -533,12 +533,12 @@ jobs:
|
||||
|
||||
|
||||
- name: Publish to PyPI (Test)
|
||||
uses: pypa/gh-action-pypi-publish@v1.8.14
|
||||
uses: pypa/gh-action-pypi-publish@v1.9.0
|
||||
with:
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@v1.8.14
|
||||
uses: pypa/gh-action-pypi-publish@v1.9.0
|
||||
|
||||
|
||||
deploy-docker:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: "7.0.0"
|
||||
rev: "7.1.0"
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies: [Flake8-pyproject]
|
||||
@@ -31,7 +31,7 @@ repos:
|
||||
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.4.8'
|
||||
rev: 'v0.4.9'
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
markdown==3.6
|
||||
mkdocs==1.6.0
|
||||
mkdocs-material==9.5.26
|
||||
mkdocs-material==9.5.27
|
||||
mdx_truly_sane_lists==1.3
|
||||
pymdown-extensions==10.8.1
|
||||
jinja2==3.1.4
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -279,7 +279,7 @@ class Exchange:
|
||||
self._exchange_ws.cleanup()
|
||||
logger.debug("Exchange object destroyed, closing async loop")
|
||||
if (
|
||||
self._api_async
|
||||
getattr(self, "_api_async", None)
|
||||
and inspect.iscoroutinefunction(self._api_async.close)
|
||||
and self._api_async.session
|
||||
):
|
||||
|
||||
@@ -1466,6 +1466,8 @@ class RPC:
|
||||
from freqtrade.resolvers.strategy_resolver import StrategyResolver
|
||||
|
||||
strategy = StrategyResolver.load_strategy(config)
|
||||
# Manually load hyperparameters, as we don't call the bot-start callback.
|
||||
strategy.ft_load_hyper_params(False)
|
||||
|
||||
if strategy.plot_config and "subplots" not in strategy.plot_config:
|
||||
strategy.plot_config["subplots"] = {}
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-r docs/requirements-docs.txt
|
||||
|
||||
coveralls==4.0.1
|
||||
ruff==0.4.8
|
||||
ruff==0.4.9
|
||||
mypy==1.10.0
|
||||
pre-commit==3.7.1
|
||||
pytest==8.2.2
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
-r requirements-freqai.txt
|
||||
|
||||
# Required for freqai-rl
|
||||
torch==2.2.2
|
||||
torch==2.3.1; sys_platform != 'darwin' or platform_machine != 'x86_64'
|
||||
torch==2.2.2; sys_platform == 'darwin' and platform_machine == 'x86_64'
|
||||
gymnasium==0.29.1
|
||||
stable_baselines3==2.3.2
|
||||
sb3_contrib>=2.2.1
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
scikit-learn==1.5.0
|
||||
joblib==1.4.2
|
||||
catboost==1.2.5; 'arm' not in platform_machine
|
||||
lightgbm==4.3.0
|
||||
lightgbm==4.4.0
|
||||
xgboost==2.0.3
|
||||
tensorboard==2.17.0
|
||||
datasieve==0.1.7
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
scipy==1.13.1
|
||||
scikit-learn==1.5.0
|
||||
ft-scikit-optimize==0.9.2
|
||||
filelock==3.14.0
|
||||
filelock==3.15.1
|
||||
|
||||
+5
-5
@@ -4,7 +4,7 @@ bottleneck==1.3.8
|
||||
numexpr==2.10.0
|
||||
pandas-ta==0.3.14b
|
||||
|
||||
ccxt==4.3.42
|
||||
ccxt==4.3.46
|
||||
cryptography==42.0.8
|
||||
aiohttp==3.9.5
|
||||
SQLAlchemy==2.0.30
|
||||
@@ -14,9 +14,9 @@ httpx>=0.24.1
|
||||
humanize==4.9.0
|
||||
cachetools==5.3.3
|
||||
requests==2.32.3
|
||||
urllib3==2.2.1
|
||||
urllib3==2.2.2
|
||||
jsonschema==4.22.0
|
||||
TA-Lib==0.4.30
|
||||
TA-Lib==0.4.31
|
||||
technical==1.4.3
|
||||
tabulate==0.9.0
|
||||
pycoingecko==3.1.0
|
||||
@@ -32,14 +32,14 @@ py_find_1st==1.1.6
|
||||
# Load ticker files 30% faster
|
||||
python-rapidjson==1.17
|
||||
# Properly format api responses
|
||||
orjson==3.10.3
|
||||
orjson==3.10.5
|
||||
|
||||
# Notify systemd
|
||||
sdnotify==0.3.2
|
||||
|
||||
# API Server
|
||||
fastapi==0.111.0
|
||||
pydantic==2.7.3
|
||||
pydantic==2.7.4
|
||||
uvicorn==0.30.1
|
||||
pyjwt==2.8.0
|
||||
aiofiles==23.2.1
|
||||
|
||||
Reference in New Issue
Block a user