freqtrade_origin/tests/rpc/test_rpc_apiserver.py

2828 lines
91 KiB
Python
Raw Normal View History

2019-04-04 05:12:58 +00:00
"""
Unit test file for rpc/api_server.py
"""
2024-05-12 13:44:42 +00:00
2023-03-26 16:18:52 +00:00
import asyncio
import logging
import time
2020-10-26 06:36:25 +00:00
from datetime import datetime, timedelta, timezone
2020-09-17 05:53:22 +00:00
from pathlib import Path
2019-05-11 12:05:25 +00:00
from unittest.mock import ANY, MagicMock, PropertyMock
2019-04-04 05:12:58 +00:00
2022-02-09 05:36:17 +00:00
import pandas as pd
2019-05-10 05:07:38 +00:00
import pytest
2023-08-03 04:39:27 +00:00
import rapidjson
2020-12-31 10:01:50 +00:00
import uvicorn
2022-09-08 17:25:30 +00:00
from fastapi import FastAPI, WebSocketDisconnect
2020-12-31 10:01:50 +00:00
from fastapi.exceptions import HTTPException
2020-12-27 09:59:17 +00:00
from fastapi.testclient import TestClient
from requests.auth import _basic_auth_str
2023-03-16 06:25:04 +00:00
from sqlalchemy import select
2019-05-10 05:07:38 +00:00
2019-05-11 07:44:39 +00:00
from freqtrade.__init__ import __version__
2022-03-03 06:07:33 +00:00
from freqtrade.enums import CandleType, RunMode, State, TradingMode
from freqtrade.exceptions import DependencyException, ExchangeError, OperationalException
2020-08-14 17:36:12 +00:00
from freqtrade.loggers import setup_logging, setup_logging_pre
2023-05-21 07:14:00 +00:00
from freqtrade.optimize.backtesting import Backtesting
2024-03-30 17:14:51 +00:00
from freqtrade.persistence import Trade
from freqtrade.rpc import RPC
2020-12-31 10:01:50 +00:00
from freqtrade.rpc.api_server import ApiServer
from freqtrade.rpc.api_server.api_auth import create_token, get_user_from_token
from freqtrade.rpc.api_server.uvicorn_threaded import UvicornServer
from freqtrade.rpc.api_server.webserver_bgwork import ApiBG
2024-03-30 13:30:00 +00:00
from freqtrade.util.datetime_helpers import format_date
2024-05-12 13:08:40 +00:00
from tests.conftest import (
CURRENT_TEST_STRATEGY,
EXMS,
create_mock_trades,
get_mock_coro,
get_patched_freqtradebot,
log_has,
log_has_re,
patch_get_signal,
)
2020-09-28 17:43:15 +00:00
2019-04-04 05:12:58 +00:00
2020-12-27 09:59:17 +00:00
BASE_URI = "/api/v1"
2019-05-25 12:13:59 +00:00
_TEST_USER = "FreqTrader"
_TEST_PASS = "SuperSecurePassword1!"
2022-09-08 17:25:30 +00:00
_TEST_WS_TOKEN = "secret_Ws_t0ken"
2019-05-25 12:13:59 +00:00
2019-05-10 05:07:38 +00:00
@pytest.fixture
2019-05-11 06:55:21 +00:00
def botclient(default_conf, mocker):
2020-08-14 17:36:12 +00:00
setup_logging_pre()
setup_logging(default_conf)
2024-05-12 13:44:42 +00:00
default_conf["runmode"] = RunMode.DRY_RUN
default_conf.update(
{
"api_server": {
"enabled": True,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"CORS_origins": ["http://example.com"],
"username": _TEST_USER,
"password": _TEST_PASS,
"ws_token": _TEST_WS_TOKEN,
}
}
)
2019-05-25 12:13:59 +00:00
2021-07-23 11:34:18 +00:00
ftbot = get_patched_freqtradebot(mocker, default_conf)
rpc = RPC(ftbot)
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.api_server.ApiServer.start_api", MagicMock())
2022-09-08 20:00:22 +00:00
apiserver = None
2021-01-02 15:12:10 +00:00
try:
apiserver = ApiServer(default_conf)
apiserver.add_rpc_handler(rpc)
2022-11-21 19:21:40 +00:00
# We need to use the TestClient as a context manager to
# handle lifespan events correctly
with TestClient(apiserver.app) as client:
yield ftbot, client
2021-01-02 15:12:10 +00:00
# Cleanup ... ?
finally:
2022-09-08 20:00:22 +00:00
if apiserver:
apiserver.cleanup()
2021-01-02 15:12:10 +00:00
ApiServer.shutdown()
2019-05-10 05:07:38 +00:00
2024-04-19 05:25:41 +00:00
def client_post(client: TestClient, url, data=None):
if data is None:
data = {}
2024-05-12 13:44:42 +00:00
return client.post(
url,
json=data,
headers={
"Authorization": _basic_auth_str(_TEST_USER, _TEST_PASS),
"Origin": "http://example.com",
"content-type": "application/json",
},
)
2019-05-25 12:13:59 +00:00
2024-04-19 05:25:41 +00:00
def client_patch(client: TestClient, url, data=None):
if data is None:
data = {}
2024-05-12 13:44:42 +00:00
return client.patch(
url,
json=data,
headers={
"Authorization": _basic_auth_str(_TEST_USER, _TEST_PASS),
"Origin": "http://example.com",
"content-type": "application/json",
},
)
2019-05-25 12:13:59 +00:00
2023-08-03 04:42:15 +00:00
def client_get(client: TestClient, url):
2020-05-20 17:43:52 +00:00
# Add fake Origin to ensure CORS kicks in
2024-05-12 13:44:42 +00:00
return client.get(
url,
headers={
"Authorization": _basic_auth_str(_TEST_USER, _TEST_PASS),
"Origin": "http://example.com",
},
)
2019-05-25 12:13:59 +00:00
def client_delete(client: TestClient, url):
2020-08-04 12:41:38 +00:00
# Add fake Origin to ensure CORS kicks in
2024-05-12 13:44:42 +00:00
return client.delete(
url,
headers={
"Authorization": _basic_auth_str(_TEST_USER, _TEST_PASS),
"Origin": "http://example.com",
},
)
2020-08-04 12:41:38 +00:00
2020-05-20 17:43:52 +00:00
def assert_response(response, expected_code=200, needs_cors=True):
2019-05-11 12:05:25 +00:00
assert response.status_code == expected_code
2024-05-12 13:44:42 +00:00
assert response.headers.get("content-type") == "application/json"
2020-05-20 17:43:52 +00:00
if needs_cors:
2024-05-12 13:44:42 +00:00
assert ("access-control-allow-credentials", "true") in response.headers.items()
assert ("access-control-allow-origin", "http://example.com") in response.headers.items()
2019-05-10 05:07:38 +00:00
2019-05-11 11:18:11 +00:00
def test_api_not_found(botclient):
2024-01-24 19:31:38 +00:00
_ftbot, client = botclient
2019-05-11 11:18:11 +00:00
2021-01-10 09:31:05 +00:00
rc = client_get(client, f"{BASE_URI}/invalid_url")
2019-05-11 12:05:25 +00:00
assert_response(rc, 404)
2020-12-26 16:48:19 +00:00
assert rc.json() == {"detail": "Not Found"}
2019-05-11 11:18:11 +00:00
2021-10-12 05:00:07 +00:00
def test_api_ui_fallback(botclient, mocker):
2024-01-24 19:31:38 +00:00
_ftbot, client = botclient
2021-01-31 14:27:00 +00:00
rc = client_get(client, "/favicon.ico")
assert rc.status_code == 200
rc = client_get(client, "/fallback_file.html")
assert rc.status_code == 200
2024-05-12 13:44:42 +00:00
assert "`freqtrade install-ui`" in rc.text
2021-01-31 14:27:00 +00:00
# Forwarded to fallback_html or index.html (depending if it's installed or not)
rc = client_get(client, "/something")
assert rc.status_code == 200
rc = client_get(client, "/something.js")
assert rc.status_code == 200
2021-10-12 05:00:07 +00:00
# Test directory traversal without mock
2024-05-12 13:44:42 +00:00
rc = client_get(client, "%2F%2F%2Fetc/passwd")
2021-08-16 04:38:36 +00:00
assert rc.status_code == 200
2021-10-12 05:00:07 +00:00
# Allow both fallback or real UI
2024-05-12 13:44:42 +00:00
assert "`freqtrade install-ui`" in rc.text or "<!DOCTYPE html>" in rc.text
2021-10-12 05:00:07 +00:00
2024-05-12 13:44:42 +00:00
mocker.patch.object(Path, "is_file", MagicMock(side_effect=[True, False]))
rc = client_get(client, "%2F%2F%2Fetc/passwd")
2021-10-12 05:00:07 +00:00
assert rc.status_code == 200
2024-05-12 13:44:42 +00:00
assert "`freqtrade install-ui`" in rc.text
2021-08-16 04:38:36 +00:00
2021-01-31 14:27:00 +00:00
2021-07-06 05:20:05 +00:00
def test_api_ui_version(botclient, mocker):
2024-01-24 19:31:38 +00:00
_ftbot, client = botclient
2021-07-06 05:20:05 +00:00
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.commands.deploy_commands.read_ui_version", return_value="0.1.2")
2021-07-06 05:20:05 +00:00
rc = client_get(client, "/ui_version")
assert rc.status_code == 200
2024-05-12 13:44:42 +00:00
assert rc.json()["version"] == "0.1.2"
2021-07-06 05:20:05 +00:00
2020-12-27 13:57:01 +00:00
def test_api_auth():
with pytest.raises(ValueError):
2024-05-12 13:44:42 +00:00
create_token({"identity": {"u": "Freqtrade"}}, "secret1234", token_type="NotATokenType")
2020-12-27 13:57:01 +00:00
2024-05-12 13:44:42 +00:00
token = create_token({"identity": {"u": "Freqtrade"}}, "secret1234")
2021-01-08 18:27:51 +00:00
assert isinstance(token, str)
2020-12-27 13:57:01 +00:00
2024-05-12 13:44:42 +00:00
u = get_user_from_token(token, "secret1234")
assert u == "Freqtrade"
2020-12-27 13:57:01 +00:00
with pytest.raises(HTTPException):
2024-05-12 13:44:42 +00:00
get_user_from_token(token, "secret1234", token_type="refresh")
2020-12-27 13:57:01 +00:00
# Create invalid token
2024-05-12 13:44:42 +00:00
token = create_token({"identity": {"u1": "Freqrade"}}, "secret1234")
2020-12-27 13:57:01 +00:00
with pytest.raises(HTTPException):
2024-05-12 13:44:42 +00:00
get_user_from_token(token, "secret1234")
2020-12-27 13:57:01 +00:00
with pytest.raises(HTTPException):
2024-05-12 13:44:42 +00:00
get_user_from_token(b"not_a_token", "secret1234")
2020-12-27 13:57:01 +00:00
2022-09-08 17:25:30 +00:00
def test_api_ws_auth(botclient):
ftbot, client = botclient
2024-02-19 18:14:44 +00:00
def url(token):
return f"/api/v1/message/ws?token={token}"
2022-09-08 17:25:30 +00:00
bad_token = "bad-ws_token"
with pytest.raises(WebSocketDisconnect):
2022-09-09 17:38:42 +00:00
with client.websocket_connect(url(bad_token)) as websocket:
2022-09-08 17:25:30 +00:00
websocket.receive()
good_token = _TEST_WS_TOKEN
2022-09-09 17:38:42 +00:00
with client.websocket_connect(url(good_token)) as websocket:
pass
2022-09-08 17:25:30 +00:00
2024-05-12 13:44:42 +00:00
jwt_secret = ftbot.config["api_server"].get("jwt_secret_key", "super-secret")
jwt_token = create_token({"identity": {"u": "Freqtrade"}}, jwt_secret)
2022-09-09 17:38:42 +00:00
with client.websocket_connect(url(jwt_token)) as websocket:
2022-09-08 19:58:28 +00:00
pass
2022-09-08 17:25:30 +00:00
2019-05-25 12:13:59 +00:00
def test_api_unauthorized(botclient):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
2019-11-11 19:09:58 +00:00
rc = client.get(f"{BASE_URI}/ping")
2020-05-20 17:43:52 +00:00
assert_response(rc, needs_cors=False)
2024-05-12 13:44:42 +00:00
assert rc.json() == {"status": "pong"}
2019-11-11 19:09:58 +00:00
2019-05-25 12:13:59 +00:00
# Don't send user/pass information
rc = client.get(f"{BASE_URI}/version")
2020-05-20 17:43:52 +00:00
assert_response(rc, 401, needs_cors=False)
2024-05-12 13:44:42 +00:00
assert rc.json() == {"detail": "Unauthorized"}
2019-05-25 12:13:59 +00:00
# Change only username
2024-05-12 13:44:42 +00:00
ftbot.config["api_server"]["username"] = "Ftrader"
2019-05-25 12:13:59 +00:00
rc = client_get(client, f"{BASE_URI}/version")
assert_response(rc, 401)
2024-05-12 13:44:42 +00:00
assert rc.json() == {"detail": "Unauthorized"}
2019-05-25 12:13:59 +00:00
# Change only password
2024-05-12 13:44:42 +00:00
ftbot.config["api_server"]["username"] = _TEST_USER
ftbot.config["api_server"]["password"] = "WrongPassword"
2019-05-25 12:13:59 +00:00
rc = client_get(client, f"{BASE_URI}/version")
assert_response(rc, 401)
2024-05-12 13:44:42 +00:00
assert rc.json() == {"detail": "Unauthorized"}
2019-05-25 12:13:59 +00:00
2024-05-12 13:44:42 +00:00
ftbot.config["api_server"]["username"] = "Ftrader"
ftbot.config["api_server"]["password"] = "WrongPassword"
2019-05-25 12:13:59 +00:00
rc = client_get(client, f"{BASE_URI}/version")
assert_response(rc, 401)
2024-05-12 13:44:42 +00:00
assert rc.json() == {"detail": "Unauthorized"}
2019-05-25 12:13:59 +00:00
2020-05-10 08:43:13 +00:00
def test_api_token_login(botclient):
2024-01-24 19:31:38 +00:00
_ftbot, client = botclient
2024-05-12 13:44:42 +00:00
rc = client.post(
f"{BASE_URI}/token/login",
data=None,
headers={
"Authorization": _basic_auth_str("WRONG_USER", "WRONG_PASS"),
"Origin": "http://example.com",
},
)
2020-12-27 13:57:01 +00:00
assert_response(rc, 401)
2020-05-10 08:43:13 +00:00
rc = client_post(client, f"{BASE_URI}/token/login")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert "access_token" in rc.json()
assert "refresh_token" in rc.json()
2020-05-10 08:43:13 +00:00
# test Authentication is working with JWT tokens too
2024-05-12 13:44:42 +00:00
rc = client.get(
f"{BASE_URI}/count",
headers={
"Authorization": f'Bearer {rc.json()["access_token"]}',
"Origin": "http://example.com",
},
)
2020-05-10 08:43:13 +00:00
assert_response(rc)
def test_api_token_refresh(botclient):
2024-01-24 19:31:38 +00:00
_ftbot, client = botclient
2020-05-10 08:43:13 +00:00
rc = client_post(client, f"{BASE_URI}/token/login")
assert_response(rc)
2024-05-12 13:44:42 +00:00
rc = client.post(
f"{BASE_URI}/token/refresh",
data=None,
headers={
"Authorization": f'Bearer {rc.json()["refresh_token"]}',
"Origin": "http://example.com",
},
)
2020-05-10 08:43:13 +00:00
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert "access_token" in rc.json()
assert "refresh_token" not in rc.json()
2020-05-10 08:43:13 +00:00
2019-05-11 06:55:21 +00:00
def test_api_stop_workflow(botclient):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
assert ftbot.state == State.RUNNING
2019-05-25 12:13:59 +00:00
rc = client_post(client, f"{BASE_URI}/stop")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json() == {"status": "stopping trader ..."}
2021-07-23 11:34:18 +00:00
assert ftbot.state == State.STOPPED
2019-05-10 05:07:38 +00:00
2019-05-11 06:55:21 +00:00
# Stop bot again
2019-05-25 12:13:59 +00:00
rc = client_post(client, f"{BASE_URI}/stop")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json() == {"status": "already stopped"}
2019-05-11 06:55:21 +00:00
# Start bot
2019-05-25 12:13:59 +00:00
rc = client_post(client, f"{BASE_URI}/start")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json() == {"status": "starting trader ..."}
2021-07-23 11:34:18 +00:00
assert ftbot.state == State.RUNNING
2019-05-11 06:55:21 +00:00
# Call start again
2019-05-25 12:13:59 +00:00
rc = client_post(client, f"{BASE_URI}/start")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json() == {"status": "already running"}
2019-05-10 05:07:38 +00:00
2019-05-11 06:55:21 +00:00
def test_api__init__(default_conf, mocker):
2019-04-04 05:12:58 +00:00
"""
Test __init__() method
"""
2024-05-12 13:44:42 +00:00
default_conf.update(
{
"api_server": {
"enabled": True,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"username": "TestUser",
"password": "testPass",
}
}
)
mocker.patch("freqtrade.rpc.telegram.Telegram._init")
mocker.patch("freqtrade.rpc.api_server.webserver.ApiServer.start_api", MagicMock())
2021-01-02 15:12:10 +00:00
apiserver = ApiServer(default_conf)
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
2019-04-04 05:12:58 +00:00
assert apiserver._config == default_conf
2021-07-06 19:04:52 +00:00
with pytest.raises(OperationalException, match="RPC Handler already attached."):
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
2022-09-08 16:14:30 +00:00
apiserver.cleanup()
2021-01-02 15:12:10 +00:00
ApiServer.shutdown()
2019-04-04 05:12:58 +00:00
2021-03-13 14:36:34 +00:00
def test_api_UvicornServer(mocker):
2024-05-12 13:44:42 +00:00
thread_mock = mocker.patch("freqtrade.rpc.api_server.uvicorn_threaded.threading.Thread")
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host="127.0.0.1"))
2020-12-27 13:57:01 +00:00
assert thread_mock.call_count == 0
# Fake started to avoid sleeping forever
s.started = True
s.run_in_thread()
assert thread_mock.call_count == 1
s.cleanup()
assert s.should_exit is True
2021-03-13 14:36:34 +00:00
def test_api_UvicornServer_run(mocker):
2024-05-12 13:44:42 +00:00
serve_mock = mocker.patch(
"freqtrade.rpc.api_server.uvicorn_threaded.UvicornServer.serve", get_mock_coro(None)
)
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host="127.0.0.1"))
2021-03-13 14:36:34 +00:00
assert serve_mock.call_count == 0
# Fake started to avoid sleeping forever
s.started = True
s.run()
assert serve_mock.call_count == 1
def test_api_UvicornServer_run_no_uvloop(mocker, import_fails):
2024-05-12 13:44:42 +00:00
serve_mock = mocker.patch(
"freqtrade.rpc.api_server.uvicorn_threaded.UvicornServer.serve", get_mock_coro(None)
)
2023-03-26 16:18:52 +00:00
asyncio.set_event_loop(asyncio.new_event_loop())
2024-05-12 13:44:42 +00:00
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host="127.0.0.1"))
2021-03-13 14:36:34 +00:00
assert serve_mock.call_count == 0
# Fake started to avoid sleeping forever
s.started = True
s.run()
assert serve_mock.call_count == 1
2019-05-14 05:07:51 +00:00
def test_api_run(default_conf, mocker, caplog):
2024-05-12 13:44:42 +00:00
default_conf.update(
{
"api_server": {
"enabled": True,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"username": "TestUser",
"password": "testPass",
}
}
)
mocker.patch("freqtrade.rpc.telegram.Telegram._init")
2019-05-14 05:07:51 +00:00
server_inst_mock = MagicMock()
server_inst_mock.run_in_thread = MagicMock()
server_inst_mock.run = MagicMock()
server_mock = MagicMock(return_value=server_inst_mock)
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.api_server.webserver.UvicornServer", server_mock)
2021-01-02 15:12:10 +00:00
apiserver = ApiServer(default_conf)
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
2019-05-14 05:07:51 +00:00
2019-05-18 07:54:36 +00:00
assert server_mock.call_count == 1
2020-12-27 09:56:19 +00:00
assert apiserver._config == default_conf
apiserver.start_api()
assert server_mock.call_count == 2
assert server_inst_mock.run_in_thread.call_count == 2
assert server_inst_mock.run.call_count == 0
2020-12-27 09:56:19 +00:00
assert server_mock.call_args_list[0][0][0].host == "127.0.0.1"
assert server_mock.call_args_list[0][0][0].port == 8080
assert isinstance(server_mock.call_args_list[0][0][0].app, FastAPI)
2019-05-14 05:07:51 +00:00
2019-08-11 18:17:22 +00:00
assert log_has("Starting HTTP Server at 127.0.0.1:8080", caplog)
assert log_has("Starting Local Rest Server.", caplog)
2019-05-14 05:07:51 +00:00
# Test binding to public
caplog.clear()
2019-05-18 07:54:36 +00:00
server_mock.reset_mock()
2024-05-12 13:44:42 +00:00
apiserver._config.update(
{
"api_server": {
"enabled": True,
"listen_ip_address": "0.0.0.0",
"listen_port": 8089,
"password": "",
}
}
)
2020-12-27 09:56:19 +00:00
apiserver.start_api()
2019-05-14 05:07:51 +00:00
2019-05-18 07:54:36 +00:00
assert server_mock.call_count == 1
assert server_inst_mock.run_in_thread.call_count == 1
assert server_inst_mock.run.call_count == 0
2020-12-27 09:56:19 +00:00
assert server_mock.call_args_list[0][0][0].host == "0.0.0.0"
assert server_mock.call_args_list[0][0][0].port == 8089
assert isinstance(server_mock.call_args_list[0][0][0].app, FastAPI)
2019-08-11 18:17:22 +00:00
assert log_has("Starting HTTP Server at 0.0.0.0:8089", caplog)
assert log_has("Starting Local Rest Server.", caplog)
2024-05-12 13:44:42 +00:00
assert log_has("SECURITY WARNING - Local Rest Server listening to external connections", caplog)
assert log_has(
"SECURITY WARNING - This is insecure please set to your loopback,"
"e.g 127.0.0.1 in config.json",
caplog,
)
assert log_has(
"SECURITY WARNING - No password for local REST Server defined. "
"Please make sure that this is intentional!",
caplog,
)
2020-12-27 14:24:49 +00:00
assert log_has_re("SECURITY WARNING - `jwt_secret_key` seems to be default.*", caplog)
server_mock.reset_mock()
apiserver._standalone = True
apiserver.start_api()
assert server_inst_mock.run_in_thread.call_count == 0
assert server_inst_mock.run.call_count == 1
apiserver1 = ApiServer(default_conf)
assert id(apiserver1) == id(apiserver)
apiserver._standalone = False
# Test crashing API server
2019-05-15 04:24:22 +00:00
caplog.clear()
2024-05-12 13:44:42 +00:00
mocker.patch(
"freqtrade.rpc.api_server.webserver.UvicornServer", MagicMock(side_effect=Exception)
)
2020-12-27 09:56:19 +00:00
apiserver.start_api()
2019-08-11 18:17:22 +00:00
assert log_has("Api server failed to start.", caplog)
2022-09-08 16:14:30 +00:00
apiserver.cleanup()
2021-01-02 15:12:10 +00:00
ApiServer.shutdown()
2019-05-15 04:24:22 +00:00
2019-05-14 05:07:51 +00:00
2019-05-18 07:54:36 +00:00
def test_api_cleanup(default_conf, mocker, caplog):
2024-05-12 13:44:42 +00:00
default_conf.update(
{
"api_server": {
"enabled": True,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"username": "TestUser",
"password": "testPass",
}
}
)
mocker.patch("freqtrade.rpc.telegram.Telegram._init")
2020-12-27 09:56:19 +00:00
server_mock = MagicMock()
server_mock.cleanup = MagicMock()
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.api_server.webserver.UvicornServer", server_mock)
2019-05-18 07:54:36 +00:00
2021-01-02 15:12:10 +00:00
apiserver = ApiServer(default_conf)
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
2019-05-18 07:54:36 +00:00
apiserver.cleanup()
2020-12-27 09:56:19 +00:00
assert apiserver._server.cleanup.call_count == 1
2019-08-11 18:17:22 +00:00
assert log_has("Stopping API Server", caplog)
2021-01-02 15:12:10 +00:00
ApiServer.shutdown()
2019-05-18 07:54:36 +00:00
2019-05-11 06:55:21 +00:00
def test_api_reloadconf(botclient):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
2019-04-04 05:12:58 +00:00
rc = client_post(client, f"{BASE_URI}/reload_config")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json() == {"status": "Reloading config ..."}
2021-07-23 11:34:18 +00:00
assert ftbot.state == State.RELOAD_CONFIG
2019-04-04 05:12:58 +00:00
2022-08-28 09:32:53 +00:00
def test_api_stopentry(botclient):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
2024-05-12 13:44:42 +00:00
assert ftbot.config["max_open_trades"] != 0
2019-04-04 05:12:58 +00:00
2019-05-25 12:13:59 +00:00
rc = client_post(client, f"{BASE_URI}/stopbuy")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2022-08-28 09:32:53 +00:00
assert rc.json() == {
2024-05-12 13:44:42 +00:00
"status": "No more entries will occur from now. Run /reload_config to reset."
}
assert ftbot.config["max_open_trades"] == 0
2022-08-28 09:32:53 +00:00
rc = client_post(client, f"{BASE_URI}/stopentry")
assert_response(rc)
assert rc.json() == {
2024-05-12 13:44:42 +00:00
"status": "No more entries will occur from now. Run /reload_config to reset."
}
assert ftbot.config["max_open_trades"] == 0
2019-05-11 07:10:54 +00:00
2021-09-19 23:44:12 +00:00
def test_api_balance(botclient, mocker, rpc_balance, tickers):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
2019-05-11 07:10:54 +00:00
2024-05-12 13:44:42 +00:00
ftbot.config["dry_run"] = False
mocker.patch(f"{EXMS}.get_balances", return_value=rpc_balance)
mocker.patch(f"{EXMS}.get_tickers", tickers)
mocker.patch(f"{EXMS}.get_valid_pair_combination", side_effect=lambda a, b: f"{a}/{b}")
2021-07-23 11:34:18 +00:00
ftbot.wallets.update()
2019-05-11 07:10:54 +00:00
2019-05-25 12:13:59 +00:00
rc = client_get(client, f"{BASE_URI}/balance")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2021-09-19 23:44:12 +00:00
response = rc.json()
assert "currencies" in response
assert len(response["currencies"]) == 5
2024-05-12 13:44:42 +00:00
assert response["currencies"][0] == {
"currency": "BTC",
"free": 12.0,
"balance": 12.0,
"used": 0.0,
"bot_owned": pytest.approx(11.879999),
"est_stake": 12.0,
"est_stake_bot": pytest.approx(11.879999),
"stake": "BTC",
"is_position": False,
"leverage": 1.0,
"position": 0.0,
"side": "long",
"is_bot_managed": True,
2019-05-11 07:10:54 +00:00
}
2024-05-12 13:44:42 +00:00
assert response["total"] == 12.159513094
assert response["total_bot"] == pytest.approx(11.879999)
assert "starting_capital" in response
assert "starting_capital_fiat" in response
assert "starting_capital_pct" in response
assert "starting_capital_ratio" in response
2019-05-11 07:44:39 +00:00
2024-05-12 13:44:42 +00:00
@pytest.mark.parametrize("is_short", [True, False])
2021-09-20 02:24:22 +00:00
def test_api_count(botclient, mocker, ticker, fee, markets, is_short):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
patch_get_signal(ftbot)
2019-05-11 07:44:39 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2019-05-11 07:44:39 +00:00
get_balances=MagicMock(return_value=ticker),
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2019-05-11 07:44:39 +00:00
get_fee=fee,
2024-05-12 13:44:42 +00:00
markets=PropertyMock(return_value=markets),
2019-05-11 07:44:39 +00:00
)
2019-05-25 12:13:59 +00:00
rc = client_get(client, f"{BASE_URI}/count")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2019-05-11 07:44:39 +00:00
2020-12-26 15:33:13 +00:00
assert rc.json()["current"] == 0
assert rc.json()["max"] == 1
2019-05-11 07:44:39 +00:00
# Create some test data
create_mock_trades(fee, is_short=is_short)
2019-05-25 12:13:59 +00:00
rc = client_get(client, f"{BASE_URI}/count")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2021-04-10 17:53:00 +00:00
assert rc.json()["current"] == 4
assert rc.json()["max"] == 1
2024-05-12 13:44:42 +00:00
ftbot.config["max_open_trades"] = float("inf")
rc = client_get(client, f"{BASE_URI}/count")
assert rc.json()["max"] == -1
2019-05-11 11:31:48 +00:00
2020-10-17 15:58:07 +00:00
def test_api_locks(botclient):
2024-01-24 19:31:38 +00:00
_ftbot, client = botclient
2020-10-17 15:58:07 +00:00
rc = client_get(client, f"{BASE_URI}/locks")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert "locks" in rc.json()
2020-10-17 15:58:07 +00:00
2024-05-12 13:44:42 +00:00
assert rc.json()["lock_count"] == 0
assert rc.json()["lock_count"] == len(rc.json()["locks"])
2020-10-17 15:58:07 +00:00
2024-05-12 13:44:42 +00:00
rc = client_post(
client,
f"{BASE_URI}/locks",
[
{
"pair": "ETH/BTC",
"until": f"{format_date(datetime.now(timezone.utc) + timedelta(minutes=4))}Z",
"reason": "randreason",
},
{
"pair": "XRP/BTC",
"until": f"{format_date(datetime.now(timezone.utc) + timedelta(minutes=20))}Z",
"reason": "deadbeef",
},
],
)
2024-03-30 13:30:00 +00:00
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["lock_count"] == 2
2020-10-17 15:58:07 +00:00
rc = client_get(client, f"{BASE_URI}/locks")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["lock_count"] == 2
assert rc.json()["lock_count"] == len(rc.json()["locks"])
assert "ETH/BTC" in (rc.json()["locks"][0]["pair"], rc.json()["locks"][1]["pair"])
assert "randreason" in (rc.json()["locks"][0]["reason"], rc.json()["locks"][1]["reason"])
assert "deadbeef" in (rc.json()["locks"][0]["reason"], rc.json()["locks"][1]["reason"])
2020-10-17 15:58:07 +00:00
2021-03-01 18:50:39 +00:00
# Test deletions
rc = client_delete(client, f"{BASE_URI}/locks/1")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["lock_count"] == 1
2021-03-01 18:50:39 +00:00
2024-05-12 13:44:42 +00:00
rc = client_post(client, f"{BASE_URI}/locks/delete", data={"pair": "XRP/BTC"})
2021-03-01 18:50:39 +00:00
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["lock_count"] == 0
2021-03-01 18:50:39 +00:00
2020-10-17 15:58:07 +00:00
2021-11-06 15:12:25 +00:00
def test_api_show_config(botclient):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
patch_get_signal(ftbot)
2019-11-17 13:56:08 +00:00
rc = client_get(client, f"{BASE_URI}/show_config")
assert_response(rc)
response = rc.json()
2024-05-12 13:44:42 +00:00
assert "dry_run" in response
assert response["exchange"] == "binance"
assert response["timeframe"] == "5m"
assert response["timeframe_ms"] == 300000
assert response["timeframe_min"] == 5
assert response["state"] == "running"
assert response["bot_name"] == "freqtrade"
assert response["trading_mode"] == "spot"
assert response["strategy_version"] is None
assert not response["trailing_stop"]
assert "entry_pricing" in response
assert "exit_pricing" in response
assert "unfilledtimeout" in response
assert "version" in response
assert "api_version" in response
assert 2.1 <= response["api_version"] < 3.0
2019-11-17 13:56:08 +00:00
2019-05-11 11:31:48 +00:00
def test_api_daily(botclient, mocker, ticker, fee, markets):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
patch_get_signal(ftbot)
2019-05-11 11:31:48 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2019-05-11 11:31:48 +00:00
get_balances=MagicMock(return_value=ticker),
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2019-05-11 11:31:48 +00:00
get_fee=fee,
2024-05-12 13:44:42 +00:00
markets=PropertyMock(return_value=markets),
2019-05-11 11:31:48 +00:00
)
2019-05-25 12:13:59 +00:00
rc = client_get(client, f"{BASE_URI}/daily")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert len(rc.json()["data"]) == 7
assert rc.json()["stake_currency"] == "BTC"
assert rc.json()["fiat_display_currency"] == "USD"
assert rc.json()["data"][0]["date"] == str(datetime.now(timezone.utc).date())
2019-05-11 12:05:25 +00:00
2023-09-02 18:15:12 +00:00
def test_api_weekly(botclient, mocker, ticker, fee, markets, time_machine):
2023-09-02 15:06:23 +00:00
ftbot, client = botclient
patch_get_signal(ftbot)
mocker.patch.multiple(
EXMS,
get_balances=MagicMock(return_value=ticker),
fetch_ticker=ticker,
get_fee=fee,
2024-05-12 13:44:42 +00:00
markets=PropertyMock(return_value=markets),
2023-09-02 15:06:23 +00:00
)
2023-09-02 18:15:12 +00:00
time_machine.move_to("2023-03-31 21:45:05 +00:00")
2023-09-02 15:06:23 +00:00
rc = client_get(client, f"{BASE_URI}/weekly")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert len(rc.json()["data"]) == 4
assert rc.json()["stake_currency"] == "BTC"
assert rc.json()["fiat_display_currency"] == "USD"
2023-09-02 18:15:12 +00:00
# Moved to monday
2024-05-12 13:44:42 +00:00
assert rc.json()["data"][0]["date"] == "2023-03-27"
assert rc.json()["data"][1]["date"] == "2023-03-20"
2023-09-02 15:06:23 +00:00
2023-09-02 18:15:12 +00:00
def test_api_monthly(botclient, mocker, ticker, fee, markets, time_machine):
2023-09-02 15:06:23 +00:00
ftbot, client = botclient
patch_get_signal(ftbot)
mocker.patch.multiple(
EXMS,
get_balances=MagicMock(return_value=ticker),
fetch_ticker=ticker,
get_fee=fee,
2024-05-12 13:44:42 +00:00
markets=PropertyMock(return_value=markets),
2023-09-02 15:06:23 +00:00
)
2023-09-02 18:15:12 +00:00
time_machine.move_to("2023-03-31 21:45:05 +00:00")
2023-09-02 15:06:23 +00:00
rc = client_get(client, f"{BASE_URI}/monthly")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert len(rc.json()["data"]) == 3
assert rc.json()["stake_currency"] == "BTC"
assert rc.json()["fiat_display_currency"] == "USD"
assert rc.json()["data"][0]["date"] == "2023-03-01"
assert rc.json()["data"][1]["date"] == "2023-02-01"
2023-09-02 15:06:23 +00:00
2024-05-12 13:44:42 +00:00
@pytest.mark.parametrize("is_short", [True, False])
2021-09-20 02:24:22 +00:00
def test_api_trades(botclient, mocker, fee, markets, is_short):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
patch_get_signal(ftbot)
2024-05-12 13:44:42 +00:00
mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets))
2020-04-07 17:50:13 +00:00
rc = client_get(client, f"{BASE_URI}/trades")
assert_response(rc)
2022-06-18 15:44:15 +00:00
assert len(rc.json()) == 4
2024-05-12 13:44:42 +00:00
assert rc.json()["trades_count"] == 0
assert rc.json()["total_trades"] == 0
assert rc.json()["offset"] == 0
2020-04-07 17:50:13 +00:00
2021-11-14 08:52:38 +00:00
create_mock_trades(fee, is_short=is_short)
2023-03-16 06:25:04 +00:00
Trade.session.flush()
2020-04-07 17:50:13 +00:00
rc = client_get(client, f"{BASE_URI}/trades")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert len(rc.json()["trades"]) == 2
assert rc.json()["trades_count"] == 2
assert rc.json()["total_trades"] == 2
assert rc.json()["trades"][0]["is_short"] == is_short
rc = client_get(client, f"{BASE_URI}/trades?limit=1")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert len(rc.json()["trades"]) == 1
assert rc.json()["trades_count"] == 1
assert rc.json()["total_trades"] == 2
2020-04-07 17:50:13 +00:00
2024-05-12 13:44:42 +00:00
@pytest.mark.parametrize("is_short", [True, False])
2021-11-14 01:22:43 +00:00
def test_api_trade_single(botclient, mocker, fee, ticker, markets, is_short):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
2021-11-14 01:22:43 +00:00
patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
markets=PropertyMock(return_value=markets),
fetch_ticker=ticker,
)
rc = client_get(client, f"{BASE_URI}/trade/3")
assert_response(rc, 404)
2024-05-12 13:44:42 +00:00
assert rc.json()["detail"] == "Trade not found."
2023-03-16 06:04:15 +00:00
Trade.rollback()
2021-11-14 01:22:43 +00:00
create_mock_trades(fee, is_short=is_short)
2022-02-26 14:53:01 +00:00
rc = client_get(client, f"{BASE_URI}/trade/3")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["trade_id"] == 3
assert rc.json()["is_short"] == is_short
2024-05-12 13:44:42 +00:00
@pytest.mark.parametrize("is_short", [True, False])
2021-11-14 01:22:43 +00:00
def test_api_delete_trade(botclient, mocker, fee, markets, is_short):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
2021-11-14 01:22:43 +00:00
patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
2020-08-04 17:43:05 +00:00
stoploss_mock = MagicMock()
cancel_mock = MagicMock()
2020-08-04 12:41:38 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2020-08-04 17:43:05 +00:00
markets=PropertyMock(return_value=markets),
cancel_order=cancel_mock,
cancel_stoploss_order=stoploss_mock,
2020-08-04 12:41:38 +00:00
)
2021-11-14 08:52:38 +00:00
create_mock_trades(fee, is_short=is_short)
2021-10-12 05:11:34 +00:00
2024-05-12 13:44:42 +00:00
ftbot.strategy.order_types["stoploss_on_exchange"] = True
2023-03-16 06:25:04 +00:00
trades = Trade.session.scalars(select(Trade)).all()
2021-10-12 05:11:34 +00:00
Trade.commit()
2020-08-04 12:41:38 +00:00
assert len(trades) > 2
rc = client_delete(client, f"{BASE_URI}/trades/1")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["result_msg"] == "Deleted trade 1. Closed 1 open orders."
2023-03-16 06:25:04 +00:00
assert len(trades) - 1 == len(Trade.session.scalars(select(Trade)).all())
2020-08-04 17:43:05 +00:00
assert cancel_mock.call_count == 1
2020-08-04 12:41:38 +00:00
2020-08-04 17:43:05 +00:00
cancel_mock.reset_mock()
2020-08-04 12:41:38 +00:00
rc = client_delete(client, f"{BASE_URI}/trades/1")
# Trade is gone now.
assert_response(rc, 502)
2020-08-04 17:43:05 +00:00
assert cancel_mock.call_count == 0
2023-03-16 06:25:04 +00:00
assert len(trades) - 1 == len(Trade.session.scalars(select(Trade)).all())
rc = client_delete(client, f"{BASE_URI}/trades/5")
2020-08-04 12:41:38 +00:00
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["result_msg"] == "Deleted trade 5. Closed 1 open orders."
2023-03-16 06:25:04 +00:00
assert len(trades) - 2 == len(Trade.session.scalars(select(Trade)).all())
2020-08-04 17:43:05 +00:00
assert stoploss_mock.call_count == 1
2020-04-07 17:50:13 +00:00
2022-03-08 06:10:59 +00:00
rc = client_delete(client, f"{BASE_URI}/trades/502")
# Error - trade won't exist.
assert_response(rc, 502)
2020-04-07 17:50:13 +00:00
2024-05-12 13:44:42 +00:00
@pytest.mark.parametrize("is_short", [True, False])
2023-01-31 17:26:51 +00:00
def test_api_delete_open_order(botclient, mocker, fee, markets, ticker, is_short):
ftbot, client = botclient
patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
stoploss_mock = MagicMock()
cancel_mock = MagicMock()
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2023-01-31 17:26:51 +00:00
markets=PropertyMock(return_value=markets),
fetch_ticker=ticker,
cancel_order=cancel_mock,
cancel_stoploss_order=stoploss_mock,
)
2023-01-31 18:38:43 +00:00
rc = client_delete(client, f"{BASE_URI}/trades/10/open-order")
assert_response(rc, 502)
2024-05-12 13:44:42 +00:00
assert "Invalid trade_id." in rc.json()["error"]
2023-01-31 18:38:43 +00:00
2023-01-31 17:26:51 +00:00
create_mock_trades(fee, is_short=is_short)
Trade.commit()
rc = client_delete(client, f"{BASE_URI}/trades/5/open-order")
assert_response(rc, 502)
2024-05-12 13:44:42 +00:00
assert "No open order for trade_id" in rc.json()["error"]
2023-01-31 18:40:42 +00:00
trade = Trade.get_trades([Trade.id == 6]).first()
2024-05-12 13:44:42 +00:00
mocker.patch(f"{EXMS}.fetch_order", side_effect=ExchangeError)
2023-01-31 18:40:42 +00:00
rc = client_delete(client, f"{BASE_URI}/trades/6/open-order")
assert_response(rc, 502)
2024-05-12 13:44:42 +00:00
assert "Order not found." in rc.json()["error"]
2023-01-31 18:40:42 +00:00
trade = Trade.get_trades([Trade.id == 6]).first()
2024-05-12 13:44:42 +00:00
mocker.patch(f"{EXMS}.fetch_order", return_value=trade.orders[-1].to_ccxt_object())
2023-01-31 17:26:51 +00:00
rc = client_delete(client, f"{BASE_URI}/trades/6/open-order")
assert_response(rc)
assert cancel_mock.call_count == 1
2024-05-12 13:44:42 +00:00
@pytest.mark.parametrize("is_short", [True, False])
def test_api_trade_reload_trade(botclient, mocker, fee, markets, ticker, is_short):
ftbot, client = botclient
patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
stoploss_mock = MagicMock()
cancel_mock = MagicMock()
ftbot.handle_onexchange_order = MagicMock()
mocker.patch.multiple(
EXMS,
markets=PropertyMock(return_value=markets),
fetch_ticker=ticker,
cancel_order=cancel_mock,
cancel_stoploss_order=stoploss_mock,
)
2023-05-16 18:27:07 +00:00
rc = client_post(client, f"{BASE_URI}/trades/10/reload")
assert_response(rc, 502)
2024-05-12 13:44:42 +00:00
assert "Could not find trade with id 10." in rc.json()["error"]
assert ftbot.handle_onexchange_order.call_count == 0
create_mock_trades(fee, is_short=is_short)
Trade.commit()
2023-05-16 18:27:07 +00:00
rc = client_post(client, f"{BASE_URI}/trades/5/reload")
assert ftbot.handle_onexchange_order.call_count == 1
2020-08-14 17:36:12 +00:00
def test_api_logs(botclient):
2024-01-24 19:31:38 +00:00
_ftbot, client = botclient
2020-08-14 17:36:12 +00:00
rc = client_get(client, f"{BASE_URI}/logs")
assert_response(rc)
2020-12-26 16:33:27 +00:00
assert len(rc.json()) == 2
2024-05-12 13:44:42 +00:00
assert "logs" in rc.json()
2020-08-14 17:36:12 +00:00
# Using a fixed comparison here would make this test fail!
2024-05-12 13:44:42 +00:00
assert rc.json()["log_count"] > 1
assert len(rc.json()["logs"]) == rc.json()["log_count"]
2020-08-14 17:36:12 +00:00
2024-05-12 13:44:42 +00:00
assert isinstance(rc.json()["logs"][0], list)
2020-08-14 17:36:12 +00:00
# date
2024-05-12 13:44:42 +00:00
assert isinstance(rc.json()["logs"][0][0], str)
2020-08-14 17:36:12 +00:00
# created_timestamp
2024-05-12 13:44:42 +00:00
assert isinstance(rc.json()["logs"][0][1], float)
assert isinstance(rc.json()["logs"][0][2], str)
assert isinstance(rc.json()["logs"][0][3], str)
assert isinstance(rc.json()["logs"][0][4], str)
2020-08-14 17:36:12 +00:00
2021-01-16 09:01:31 +00:00
rc1 = client_get(client, f"{BASE_URI}/logs?limit=5")
assert_response(rc1)
assert len(rc1.json()) == 2
2024-05-12 13:44:42 +00:00
assert "logs" in rc1.json()
2020-08-14 18:12:59 +00:00
# Using a fixed comparison here would make this test fail!
2024-05-12 13:44:42 +00:00
if rc1.json()["log_count"] < 5:
2021-01-16 09:01:31 +00:00
# Help debugging random test failure
2021-01-16 09:05:47 +00:00
print(f"rc={rc.json()}")
print(f"rc1={rc1.json()}")
2024-05-12 13:44:42 +00:00
assert rc1.json()["log_count"] > 2
assert len(rc1.json()["logs"]) == rc1.json()["log_count"]
2020-08-14 18:12:59 +00:00
2020-08-14 17:36:12 +00:00
2019-05-11 12:05:25 +00:00
def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
patch_get_signal(ftbot)
2019-05-11 12:05:25 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2019-05-11 12:05:25 +00:00
get_balances=MagicMock(return_value=ticker),
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2019-05-11 12:05:25 +00:00
get_fee=fee,
2024-05-12 13:44:42 +00:00
markets=PropertyMock(return_value=markets),
2019-05-11 12:05:25 +00:00
)
2019-05-25 12:13:59 +00:00
rc = client_get(client, f"{BASE_URI}/edge")
2019-05-11 12:05:25 +00:00
assert_response(rc, 502)
2020-12-26 16:33:27 +00:00
assert rc.json() == {"error": "Error querying /api/v1/edge: Edge is not enabled."}
2019-05-11 12:05:25 +00:00
2024-05-12 13:44:42 +00:00
@pytest.mark.parametrize(
"is_short,expected",
[
(
True,
{
"best_pair": "ETC/BTC",
"best_rate": -0.5,
"best_pair_profit_ratio": -0.005,
"profit_all_coin": 15.382312,
"profit_all_fiat": 189894.6470718,
"profit_all_percent_mean": 49.62,
"profit_all_ratio_mean": 0.49620917,
"profit_all_percent_sum": 198.48,
"profit_all_ratio_sum": 1.98483671,
"profit_all_percent": 1.54,
"profit_all_ratio": 0.01538214,
"profit_closed_coin": -0.00673913,
"profit_closed_fiat": -83.19455985,
"profit_closed_ratio_mean": -0.0075,
"profit_closed_percent_mean": -0.75,
"profit_closed_ratio_sum": -0.015,
"profit_closed_percent_sum": -1.5,
"profit_closed_ratio": -6.739057628404269e-06,
"profit_closed_percent": -0.0,
"winning_trades": 0,
"losing_trades": 2,
"profit_factor": 0.0,
"winrate": 0.0,
"expectancy": -0.0033695635,
"expectancy_ratio": -1.0,
"trading_volume": 75.945,
},
),
(
False,
{
"best_pair": "XRP/BTC",
"best_rate": 1.0,
"best_pair_profit_ratio": 0.01,
"profit_all_coin": -15.46546305,
"profit_all_fiat": -190921.14135225,
"profit_all_percent_mean": -49.62,
"profit_all_ratio_mean": -0.49620955,
"profit_all_percent_sum": -198.48,
"profit_all_ratio_sum": -1.9848382,
"profit_all_percent": -1.55,
"profit_all_ratio": -0.0154654126,
"profit_closed_coin": 0.00073913,
"profit_closed_fiat": 9.124559849999999,
"profit_closed_ratio_mean": 0.0075,
"profit_closed_percent_mean": 0.75,
"profit_closed_ratio_sum": 0.015,
"profit_closed_percent_sum": 1.5,
"profit_closed_ratio": 7.391275897987988e-07,
"profit_closed_percent": 0.0,
"winning_trades": 2,
"losing_trades": 0,
"profit_factor": None,
"winrate": 1.0,
"expectancy": 0.0003695635,
"expectancy_ratio": 100,
"trading_volume": 75.945,
},
),
(
None,
{
"best_pair": "XRP/BTC",
"best_rate": 1.0,
"best_pair_profit_ratio": 0.01,
"profit_all_coin": -14.87167525,
"profit_all_fiat": -183590.83096125,
"profit_all_percent_mean": 0.13,
"profit_all_ratio_mean": 0.0012538324,
"profit_all_percent_sum": 0.5,
"profit_all_ratio_sum": 0.005015329,
"profit_all_percent": -1.49,
"profit_all_ratio": -0.0148715350,
"profit_closed_coin": -0.00542913,
"profit_closed_fiat": -67.02260985,
"profit_closed_ratio_mean": 0.0025,
"profit_closed_percent_mean": 0.25,
"profit_closed_ratio_sum": 0.005,
"profit_closed_percent_sum": 0.5,
"profit_closed_ratio": -5.429078808526421e-06,
"profit_closed_percent": -0.0,
"winning_trades": 1,
"losing_trades": 1,
"profit_factor": 0.02775724835771106,
"winrate": 0.5,
"expectancy": -0.0027145635000000003,
"expectancy_ratio": -0.48612137582114445,
"trading_volume": 75.945,
},
),
],
)
2021-11-16 06:29:40 +00:00
def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
patch_get_signal(ftbot)
2019-05-11 12:05:25 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2019-05-11 12:05:25 +00:00
get_balances=MagicMock(return_value=ticker),
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2019-05-11 12:05:25 +00:00
get_fee=fee,
2024-05-12 13:44:42 +00:00
markets=PropertyMock(return_value=markets),
2019-05-11 12:05:25 +00:00
)
2019-05-25 12:13:59 +00:00
rc = client_get(client, f"{BASE_URI}/profit")
assert_response(rc, 200)
2024-05-12 13:44:42 +00:00
assert rc.json()["trade_count"] == 0
2019-05-11 12:05:25 +00:00
2021-11-14 08:52:38 +00:00
create_mock_trades(fee, is_short=is_short)
2019-05-11 12:05:25 +00:00
# Simulate fulfilled LIMIT_BUY order for trade
2019-05-25 12:13:59 +00:00
rc = client_get(client, f"{BASE_URI}/profit")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2021-11-14 08:52:38 +00:00
# raise ValueError(rc.json())
assert rc.json() == {
2024-05-12 13:44:42 +00:00
"avg_duration": ANY,
"best_pair": expected["best_pair"],
"best_pair_profit_ratio": expected["best_pair_profit_ratio"],
"best_rate": expected["best_rate"],
"first_trade_date": ANY,
"first_trade_humanized": ANY,
"first_trade_timestamp": ANY,
"latest_trade_date": ANY,
"latest_trade_humanized": "5 minutes ago",
"latest_trade_timestamp": ANY,
"profit_all_coin": pytest.approx(expected["profit_all_coin"]),
"profit_all_fiat": pytest.approx(expected["profit_all_fiat"]),
"profit_all_percent_mean": pytest.approx(expected["profit_all_percent_mean"]),
"profit_all_ratio_mean": pytest.approx(expected["profit_all_ratio_mean"]),
"profit_all_percent_sum": pytest.approx(expected["profit_all_percent_sum"]),
"profit_all_ratio_sum": pytest.approx(expected["profit_all_ratio_sum"]),
"profit_all_percent": pytest.approx(expected["profit_all_percent"]),
"profit_all_ratio": pytest.approx(expected["profit_all_ratio"]),
"profit_closed_coin": pytest.approx(expected["profit_closed_coin"]),
"profit_closed_fiat": pytest.approx(expected["profit_closed_fiat"]),
"profit_closed_ratio_mean": pytest.approx(expected["profit_closed_ratio_mean"]),
"profit_closed_percent_mean": pytest.approx(expected["profit_closed_percent_mean"]),
"profit_closed_ratio_sum": pytest.approx(expected["profit_closed_ratio_sum"]),
"profit_closed_percent_sum": pytest.approx(expected["profit_closed_percent_sum"]),
"profit_closed_ratio": pytest.approx(expected["profit_closed_ratio"]),
"profit_closed_percent": pytest.approx(expected["profit_closed_percent"]),
"trade_count": 6,
"closed_trade_count": 2,
"winning_trades": expected["winning_trades"],
"losing_trades": expected["losing_trades"],
"profit_factor": expected["profit_factor"],
"winrate": expected["winrate"],
"expectancy": expected["expectancy"],
"expectancy_ratio": expected["expectancy_ratio"],
"max_drawdown": ANY,
"max_drawdown_abs": ANY,
"max_drawdown_start": ANY,
"max_drawdown_start_timestamp": ANY,
"max_drawdown_end": ANY,
"max_drawdown_end_timestamp": ANY,
"trading_volume": expected["trading_volume"],
"bot_start_timestamp": 0,
"bot_start_date": "",
2021-11-15 03:01:08 +00:00
}
2019-05-11 12:05:25 +00:00
2024-05-12 13:44:42 +00:00
@pytest.mark.parametrize("is_short", [True, False])
2021-11-14 01:22:43 +00:00
def test_api_stats(botclient, mocker, ticker, fee, markets, is_short):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
2021-11-14 01:22:43 +00:00
patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
2020-12-07 14:07:08 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2020-12-07 14:07:08 +00:00
get_balances=MagicMock(return_value=ticker),
fetch_ticker=ticker,
get_fee=fee,
2024-05-12 13:44:42 +00:00
markets=PropertyMock(return_value=markets),
2020-12-07 14:07:08 +00:00
)
rc = client_get(client, f"{BASE_URI}/stats")
assert_response(rc, 200)
2024-05-12 13:44:42 +00:00
assert "durations" in rc.json()
assert "exit_reasons" in rc.json()
2020-12-07 14:07:08 +00:00
2021-11-14 01:22:43 +00:00
create_mock_trades(fee, is_short=is_short)
2020-12-07 14:07:08 +00:00
rc = client_get(client, f"{BASE_URI}/stats")
assert_response(rc, 200)
2024-05-12 13:44:42 +00:00
assert "durations" in rc.json()
assert "exit_reasons" in rc.json()
2020-12-07 14:07:08 +00:00
2024-05-12 13:44:42 +00:00
assert "wins" in rc.json()["durations"]
assert "losses" in rc.json()["durations"]
assert "draws" in rc.json()["durations"]
2020-12-07 14:07:08 +00:00
2021-05-15 17:39:46 +00:00
def test_api_performance(botclient, fee):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
patch_get_signal(ftbot)
2019-05-11 12:05:25 +00:00
trade = Trade(
2024-05-12 13:44:42 +00:00
pair="LTC/ETH",
2019-05-11 12:05:25 +00:00
amount=1,
2024-05-12 13:44:42 +00:00
exchange="binance",
2019-05-11 12:05:25 +00:00
stake_amount=1,
open_rate=0.245441,
is_open=False,
fee_close=fee.return_value,
fee_open=fee.return_value,
close_rate=0.265441,
leverage=1.0,
2019-05-11 12:05:25 +00:00
)
trade.close_profit = trade.calc_profit_ratio(trade.close_rate)
trade.close_profit_abs = trade.calc_profit(trade.close_rate)
2023-03-16 06:04:15 +00:00
Trade.session.add(trade)
2019-05-11 12:05:25 +00:00
trade = Trade(
2024-05-12 13:44:42 +00:00
pair="XRP/ETH",
2019-05-11 12:05:25 +00:00
amount=5,
stake_amount=1,
2024-05-12 13:44:42 +00:00
exchange="binance",
2019-05-11 12:05:25 +00:00
open_rate=0.412,
is_open=False,
fee_close=fee.return_value,
fee_open=fee.return_value,
close_rate=0.391,
leverage=1.0,
2019-05-11 12:05:25 +00:00
)
trade.close_profit = trade.calc_profit_ratio(trade.close_rate)
trade.close_profit_abs = trade.calc_profit(trade.close_rate)
2021-05-15 17:39:46 +00:00
2023-03-16 06:04:15 +00:00
Trade.session.add(trade)
Trade.commit()
2019-05-11 12:05:25 +00:00
2019-05-25 12:13:59 +00:00
rc = client_get(client, f"{BASE_URI}/performance")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2020-12-26 15:43:15 +00:00
assert len(rc.json()) == 2
2024-05-12 13:44:42 +00:00
assert rc.json() == [
{
"count": 1,
"pair": "LTC/ETH",
"profit": 7.61,
"profit_pct": 7.61,
"profit_ratio": 0.07609203,
"profit_abs": 0.0187228,
},
{
"count": 1,
"pair": "XRP/ETH",
"profit": -5.57,
"profit_pct": -5.57,
"profit_ratio": -0.05570419,
"profit_abs": -0.1150375,
},
]
2019-05-11 12:05:25 +00:00
def test_api_entries(botclient, fee):
ftbot, client = botclient
patch_get_signal(ftbot)
# Empty
rc = client_get(client, f"{BASE_URI}/entries")
assert_response(rc)
assert len(rc.json()) == 0
create_mock_trades(fee)
rc = client_get(client, f"{BASE_URI}/entries")
assert_response(rc)
response = rc.json()
assert len(response) == 2
resp = response[0]
2024-05-12 13:44:42 +00:00
assert resp["enter_tag"] == "TEST1"
assert resp["count"] == 1
assert resp["profit_pct"] == 0.5
def test_api_exits(botclient, fee):
ftbot, client = botclient
patch_get_signal(ftbot)
# Empty
rc = client_get(client, f"{BASE_URI}/exits")
assert_response(rc)
assert len(rc.json()) == 0
create_mock_trades(fee)
rc = client_get(client, f"{BASE_URI}/exits")
assert_response(rc)
response = rc.json()
assert len(response) == 2
resp = response[0]
2024-05-12 13:44:42 +00:00
assert resp["exit_reason"] == "sell_signal"
assert resp["count"] == 1
assert resp["profit_pct"] == 0.5
def test_api_mix_tag(botclient, fee):
ftbot, client = botclient
patch_get_signal(ftbot)
# Empty
rc = client_get(client, f"{BASE_URI}/mix_tags")
assert_response(rc)
assert len(rc.json()) == 0
create_mock_trades(fee)
rc = client_get(client, f"{BASE_URI}/mix_tags")
assert_response(rc)
response = rc.json()
assert len(response) == 2
resp = response[0]
2024-05-12 13:44:42 +00:00
assert resp["mix_tag"] == "TEST1 sell_signal"
assert resp["count"] == 1
assert resp["profit_pct"] == 0.5
@pytest.mark.parametrize(
2024-05-12 13:44:42 +00:00
"is_short,current_rate,open_trade_value",
[(True, 1.098e-05, 15.0911775), (False, 1.099e-05, 15.1668225)],
)
def test_api_status(
botclient, mocker, ticker, fee, markets, is_short, current_rate, open_trade_value
):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
patch_get_signal(ftbot)
2019-05-11 12:05:25 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2019-05-11 12:05:25 +00:00
get_balances=MagicMock(return_value=ticker),
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2019-05-11 12:05:25 +00:00
get_fee=fee,
2021-04-07 15:10:20 +00:00
markets=PropertyMock(return_value=markets),
fetch_order=MagicMock(return_value={}),
2019-05-11 12:05:25 +00:00
)
2019-05-25 12:13:59 +00:00
rc = client_get(client, f"{BASE_URI}/status")
assert_response(rc, 200)
2020-12-26 16:33:27 +00:00
assert rc.json() == []
create_mock_trades(fee, is_short=is_short)
2019-05-25 12:13:59 +00:00
rc = client_get(client, f"{BASE_URI}/status")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2021-04-07 15:10:20 +00:00
assert len(rc.json()) == 4
assert rc.json()[0] == {
2024-05-12 13:44:42 +00:00
"amount": 123.0,
"amount_requested": 123.0,
"close_date": None,
"close_timestamp": None,
"close_profit": None,
"close_profit_pct": None,
"close_profit_abs": None,
"close_rate": None,
"profit_ratio": ANY,
"profit_pct": ANY,
"profit_abs": ANY,
"profit_fiat": ANY,
"total_profit_abs": ANY,
"total_profit_fiat": ANY,
"total_profit_ratio": ANY,
"realized_profit": 0.0,
"realized_profit_ratio": None,
"current_rate": current_rate,
"open_date": ANY,
"open_timestamp": ANY,
"open_fill_date": ANY,
"open_fill_timestamp": ANY,
"open_rate": 0.123,
"pair": "ETH/BTC",
"base_currency": "ETH",
"quote_currency": "BTC",
"stake_amount": 0.001,
"max_stake_amount": ANY,
"stop_loss_abs": ANY,
"stop_loss_pct": ANY,
"stop_loss_ratio": ANY,
"stoploss_last_update": ANY,
"stoploss_last_update_timestamp": ANY,
"initial_stop_loss_abs": 0.0,
"initial_stop_loss_pct": ANY,
"initial_stop_loss_ratio": ANY,
"stoploss_current_dist": ANY,
"stoploss_current_dist_ratio": ANY,
"stoploss_current_dist_pct": ANY,
"stoploss_entry_dist": ANY,
"stoploss_entry_dist_ratio": ANY,
"trade_id": 1,
"close_rate_requested": ANY,
"fee_close": 0.0025,
"fee_close_cost": None,
"fee_close_currency": None,
"fee_open": 0.0025,
"fee_open_cost": None,
"fee_open_currency": None,
"is_open": True,
2021-11-14 13:04:24 +00:00
"is_short": is_short,
2024-05-12 13:44:42 +00:00
"max_rate": ANY,
"min_rate": ANY,
"open_rate_requested": ANY,
"open_trade_value": open_trade_value,
"exit_reason": None,
"exit_order_status": None,
"strategy": CURRENT_TEST_STRATEGY,
"enter_tag": None,
"timeframe": 5,
"exchange": "binance",
"leverage": 1.0,
"interest_rate": 0.0,
"liquidation_price": None,
"funding_fees": None,
"trading_mode": ANY,
"amount_precision": None,
"price_precision": None,
"precision_mode": None,
"orders": [ANY],
"has_open_orders": True,
2021-04-07 15:10:20 +00:00
}
2019-05-11 12:05:25 +00:00
2024-05-12 13:44:42 +00:00
mocker.patch(
f"{EXMS}.get_rate", MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available"))
)
2021-02-22 10:44:39 +00:00
rc = client_get(client, f"{BASE_URI}/status")
assert_response(rc)
resp_values = rc.json()
2021-04-07 15:10:20 +00:00
assert len(resp_values) == 4
2024-05-12 13:44:42 +00:00
assert resp_values[0]["profit_abs"] == 0.0
2021-02-22 10:44:39 +00:00
2019-05-11 12:05:25 +00:00
def test_api_version(botclient):
2024-01-24 19:31:38 +00:00
_ftbot, client = botclient
2019-05-11 12:05:25 +00:00
2019-05-25 12:13:59 +00:00
rc = client_get(client, f"{BASE_URI}/version")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2020-12-26 15:33:13 +00:00
assert rc.json() == {"version": __version__}
2019-05-11 12:05:25 +00:00
2019-05-15 04:51:23 +00:00
def test_api_blacklist(botclient, mocker):
2024-01-24 19:31:38 +00:00
_ftbot, client = botclient
2019-05-11 12:05:25 +00:00
2019-05-25 12:13:59 +00:00
rc = client_get(client, f"{BASE_URI}/blacklist")
2019-05-11 12:05:25 +00:00
assert_response(rc)
# DOGE and HOT are not in the markets mock!
2024-05-12 13:44:42 +00:00
assert rc.json() == {
"blacklist": ["DOGE/BTC", "HOT/BTC"],
"blacklist_expanded": [],
"length": 2,
"method": ["StaticPairList"],
"errors": {},
}
2019-05-11 12:05:25 +00:00
# Add ETH/BTC to blacklist
2024-05-12 13:44:42 +00:00
rc = client_post(client, f"{BASE_URI}/blacklist", data={"blacklist": ["ETH/BTC"]})
assert_response(rc)
assert rc.json() == {
"blacklist": ["DOGE/BTC", "HOT/BTC", "ETH/BTC"],
"blacklist_expanded": ["ETH/BTC"],
"length": 3,
"method": ["StaticPairList"],
"errors": {},
}
rc = client_post(client, f"{BASE_URI}/blacklist", data={"blacklist": ["XRP/.*"]})
assert_response(rc)
assert rc.json() == {
"blacklist": ["DOGE/BTC", "HOT/BTC", "ETH/BTC", "XRP/.*"],
"blacklist_expanded": ["ETH/BTC", "XRP/BTC", "XRP/USDT"],
"length": 4,
"method": ["StaticPairList"],
"errors": {},
}
rc = client_delete(client, f"{BASE_URI}/blacklist?pairs_to_delete=DOGE/BTC")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json() == {
"blacklist": ["HOT/BTC", "ETH/BTC", "XRP/.*"],
"blacklist_expanded": ["ETH/BTC", "XRP/BTC", "XRP/USDT"],
"length": 3,
"method": ["StaticPairList"],
"errors": {},
}
rc = client_delete(client, f"{BASE_URI}/blacklist?pairs_to_delete=NOTHING/BTC")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json() == {
"blacklist": ["HOT/BTC", "ETH/BTC", "XRP/.*"],
"blacklist_expanded": ["ETH/BTC", "XRP/BTC", "XRP/USDT"],
"length": 3,
"method": ["StaticPairList"],
"errors": {
"NOTHING/BTC": {"error_msg": "Pair NOTHING/BTC is not in the current blacklist."}
},
2022-03-03 19:51:52 +00:00
}
rc = client_delete(
2024-05-12 13:44:42 +00:00
client, f"{BASE_URI}/blacklist?pairs_to_delete=HOT/BTC&pairs_to_delete=ETH/BTC"
)
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json() == {
"blacklist": ["XRP/.*"],
"blacklist_expanded": ["XRP/BTC", "XRP/USDT"],
"length": 1,
"method": ["StaticPairList"],
"errors": {},
}
2019-05-11 12:05:25 +00:00
2019-05-15 04:51:23 +00:00
def test_api_whitelist(botclient):
2024-01-24 19:31:38 +00:00
_ftbot, client = botclient
2019-05-11 12:05:25 +00:00
2019-05-25 12:13:59 +00:00
rc = client_get(client, f"{BASE_URI}/whitelist")
2019-05-11 12:05:25 +00:00
assert_response(rc)
2020-12-26 16:33:27 +00:00
assert rc.json() == {
2024-05-12 13:44:42 +00:00
"whitelist": ["ETH/BTC", "LTC/BTC", "XRP/BTC", "NEO/BTC"],
2020-12-26 16:33:27 +00:00
"length": 4,
2024-05-12 13:44:42 +00:00
"method": ["StaticPairList"],
2021-08-06 22:19:36 +00:00
}
2019-05-15 04:51:23 +00:00
2024-05-12 13:44:42 +00:00
@pytest.mark.parametrize(
"endpoint",
[
"forcebuy",
"forceenter",
],
)
2022-04-05 10:31:53 +00:00
def test_api_force_entry(botclient, mocker, fee, endpoint):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
2019-05-15 04:51:23 +00:00
2024-05-12 13:44:42 +00:00
rc = client_post(client, f"{BASE_URI}/{endpoint}", data={"pair": "ETH/BTC"})
2019-05-15 04:51:23 +00:00
assert_response(rc, 502)
2022-04-05 10:31:53 +00:00
assert rc.json() == {"error": f"Error querying /api/v1/{endpoint}: Force_entry not enabled."}
2019-05-15 04:51:23 +00:00
# enable forcebuy
2024-05-12 13:44:42 +00:00
ftbot.config["force_entry_enable"] = True
2019-05-15 04:51:23 +00:00
fbuy_mock = MagicMock(return_value=None)
2023-03-02 06:07:09 +00:00
mocker.patch("freqtrade.rpc.rpc.RPC._rpc_force_entry", fbuy_mock)
2024-05-12 13:44:42 +00:00
rc = client_post(client, f"{BASE_URI}/{endpoint}", data={"pair": "ETH/BTC"})
2019-05-15 04:51:23 +00:00
assert_response(rc)
assert rc.json() == {"status": "Error entering long trade for pair ETH/BTC."}
2019-05-15 04:51:23 +00:00
2020-12-26 16:33:27 +00:00
# Test creating trade
2024-05-12 13:44:42 +00:00
fbuy_mock = MagicMock(
return_value=Trade(
pair="ETH/BTC",
amount=1,
amount_requested=1,
exchange="binance",
stake_amount=1,
open_rate=0.245441,
open_date=datetime.now(timezone.utc),
is_open=False,
is_short=False,
fee_close=fee.return_value,
fee_open=fee.return_value,
close_rate=0.265441,
id=22,
timeframe=5,
strategy=CURRENT_TEST_STRATEGY,
trading_mode=TradingMode.SPOT,
)
)
2023-03-02 06:07:09 +00:00
mocker.patch("freqtrade.rpc.rpc.RPC._rpc_force_entry", fbuy_mock)
2019-05-15 04:51:23 +00:00
2024-05-12 13:44:42 +00:00
rc = client_post(client, f"{BASE_URI}/{endpoint}", data={"pair": "ETH/BTC"})
2019-05-15 04:51:23 +00:00
assert_response(rc)
2020-12-26 16:33:27 +00:00
assert rc.json() == {
2024-05-12 13:44:42 +00:00
"amount": 1.0,
"amount_requested": 1.0,
"trade_id": 22,
"close_date": None,
"close_timestamp": None,
"close_rate": 0.265441,
"open_date": ANY,
"open_timestamp": ANY,
"open_fill_date": ANY,
"open_fill_timestamp": ANY,
"open_rate": 0.245441,
"pair": "ETH/BTC",
"base_currency": "ETH",
"quote_currency": "BTC",
"stake_amount": 1,
"max_stake_amount": ANY,
"stop_loss_abs": None,
"stop_loss_pct": None,
"stop_loss_ratio": None,
"stoploss_last_update": None,
"stoploss_last_update_timestamp": None,
"initial_stop_loss_abs": None,
"initial_stop_loss_pct": None,
"initial_stop_loss_ratio": None,
"close_profit": None,
"close_profit_pct": None,
"close_profit_abs": None,
"close_rate_requested": None,
"profit_ratio": None,
"profit_pct": None,
"profit_abs": None,
"profit_fiat": None,
"realized_profit": 0.0,
"realized_profit_ratio": None,
"fee_close": 0.0025,
"fee_close_cost": None,
"fee_close_currency": None,
"fee_open": 0.0025,
"fee_open_cost": None,
"fee_open_currency": None,
"is_open": False,
"is_short": False,
"max_rate": None,
"min_rate": None,
"open_rate_requested": None,
"open_trade_value": 0.24605460,
"exit_reason": None,
"exit_order_status": None,
"strategy": CURRENT_TEST_STRATEGY,
"enter_tag": None,
"timeframe": 5,
"exchange": "binance",
"leverage": None,
"interest_rate": None,
"liquidation_price": None,
"funding_fees": None,
"trading_mode": "spot",
"amount_precision": None,
"price_precision": None,
"precision_mode": None,
"has_open_orders": False,
"orders": [],
2021-08-06 22:19:36 +00:00
}
2019-05-15 04:51:23 +00:00
2022-04-10 13:56:29 +00:00
def test_api_forceexit(botclient, mocker, ticker, fee, markets):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
2019-05-15 05:00:17 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2019-05-15 05:00:17 +00:00
get_balances=MagicMock(return_value=ticker),
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2019-05-15 05:00:17 +00:00
get_fee=fee,
markets=PropertyMock(return_value=markets),
2023-02-16 18:47:12 +00:00
_dry_is_price_crossed=MagicMock(return_value=True),
2019-05-15 05:00:17 +00:00
)
patch_get_signal(ftbot)
2019-05-15 05:00:17 +00:00
2024-05-12 13:44:42 +00:00
rc = client_post(client, f"{BASE_URI}/forceexit", data={"tradeid": "1"})
2019-05-15 05:00:17 +00:00
assert_response(rc, 502)
2022-04-10 13:56:29 +00:00
assert rc.json() == {"error": "Error querying /api/v1/forceexit: invalid argument"}
2023-03-16 06:04:15 +00:00
Trade.rollback()
2019-05-15 04:51:23 +00:00
2022-08-04 14:28:36 +00:00
create_mock_trades(fee)
trade = Trade.get_trades([Trade.id == 5]).first()
assert pytest.approx(trade.amount) == 123
2024-05-12 13:44:42 +00:00
rc = client_post(
client, f"{BASE_URI}/forceexit", data={"tradeid": "5", "ordertype": "market", "amount": 23}
)
2022-08-04 14:28:36 +00:00
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json() == {"result": "Created exit order for trade 5."}
2023-03-16 06:04:15 +00:00
Trade.rollback()
2022-08-04 14:28:36 +00:00
trade = Trade.get_trades([Trade.id == 5]).first()
assert pytest.approx(trade.amount) == 100
assert trade.is_open is True
2019-05-15 05:00:17 +00:00
2024-05-12 13:44:42 +00:00
rc = client_post(client, f"{BASE_URI}/forceexit", data={"tradeid": "5"})
2019-05-15 05:00:17 +00:00
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json() == {"result": "Created exit order for trade 5."}
2023-03-16 06:04:15 +00:00
Trade.rollback()
2022-08-04 14:28:36 +00:00
trade = Trade.get_trades([Trade.id == 5]).first()
assert trade.is_open is False
2020-06-15 06:47:43 +00:00
2020-07-02 05:10:56 +00:00
def test_api_pair_candles(botclient, ohlcv_history):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
2024-05-12 13:44:42 +00:00
timeframe = "5m"
2020-12-15 19:49:46 +00:00
amount = 3
# No pair
2024-05-12 13:44:42 +00:00
rc = client_get(client, f"{BASE_URI}/pair_candles?limit={amount}&timeframe={timeframe}")
2020-12-26 19:05:27 +00:00
assert_response(rc, 422)
# No timeframe
2024-05-12 13:44:42 +00:00
rc = client_get(client, f"{BASE_URI}/pair_candles?pair=XRP%2FBTC")
2020-12-26 19:05:27 +00:00
assert_response(rc, 422)
2024-05-12 13:44:42 +00:00
rc = client_get(
client, f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}"
)
assert_response(rc)
assert "columns" in rc.json()
assert "data_start_ts" in rc.json()
assert "data_start" in rc.json()
assert "data_stop" in rc.json()
assert "data_stop_ts" in rc.json()
assert len(rc.json()["data"]) == 0
ohlcv_history["sma"] = ohlcv_history["close"].rolling(2).mean()
ohlcv_history["sma2"] = ohlcv_history["close"].rolling(2).mean()
ohlcv_history["enter_long"] = 0
ohlcv_history.loc[1, "enter_long"] = 1
ohlcv_history["exit_long"] = 0
ohlcv_history["enter_short"] = 0
ohlcv_history["exit_short"] = 0
2020-07-02 06:39:07 +00:00
2021-12-03 12:04:31 +00:00
ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history, CandleType.SPOT)
2024-05-12 13:44:42 +00:00
for call in ("get", "post"):
if call == "get":
rc = client_get(
client,
2024-05-12 13:44:42 +00:00
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}",
)
else:
rc = client_post(
client,
f"{BASE_URI}/pair_candles",
data={
"pair": "XRP/BTC",
"timeframe": timeframe,
"limit": amount,
2024-05-12 13:44:42 +00:00
"columns": ["sma"],
},
)
assert_response(rc)
resp = rc.json()
2024-05-12 13:44:42 +00:00
assert "strategy" in resp
assert resp["strategy"] == CURRENT_TEST_STRATEGY
assert "columns" in resp
assert "data_start_ts" in resp
assert "data_start" in resp
assert "data_stop" in resp
assert "data_stop_ts" in resp
assert resp["data_start"] == "2017-11-26 08:50:00+00:00"
assert resp["data_start_ts"] == 1511686200000
assert resp["data_stop"] == "2017-11-26 09:00:00+00:00"
assert resp["data_stop_ts"] == 1511686800000
assert isinstance(resp["columns"], list)
2024-04-28 08:51:53 +00:00
base_cols = {
2024-05-12 13:44:42 +00:00
"date",
"open",
"high",
"low",
"close",
"volume",
"sma",
"enter_long",
"exit_long",
"enter_short",
"exit_short",
"__date_ts",
"_enter_long_signal_close",
"_exit_long_signal_close",
"_enter_short_signal_close",
"_exit_short_signal_close",
}
if call == "get":
assert set(resp["columns"]) == base_cols.union({"sma2"})
2024-04-28 08:51:53 +00:00
else:
2024-05-12 13:44:42 +00:00
assert set(resp["columns"]) == base_cols
2024-04-28 08:51:53 +00:00
# All columns doesn't include the internal columns
2024-05-12 13:44:42 +00:00
assert set(resp["all_columns"]) == {
"date",
"open",
"high",
"low",
"close",
"volume",
"sma",
"sma2",
"enter_long",
"exit_long",
"enter_short",
"exit_short",
}
2024-05-12 13:44:42 +00:00
assert "pair" in resp
assert resp["pair"] == "XRP/BTC"
assert "data" in resp
assert len(resp["data"]) == amount
if call == "get":
assert len(resp["data"][0]) == 17
assert resp["data"] == [
[
"2017-11-26T08:50:00Z",
8.794e-05,
8.948e-05,
8.794e-05,
8.88e-05,
0.0877869,
None,
None,
0,
0,
0,
0,
1511686200000,
None,
None,
None,
None,
],
[
"2017-11-26T08:55:00Z",
8.88e-05,
8.942e-05,
8.88e-05,
8.893e-05,
0.05874751,
8.886500000000001e-05,
8.886500000000001e-05,
1,
0,
0,
0,
1511686500000,
8.893e-05,
None,
None,
None,
],
[
"2017-11-26T09:00:00Z",
8.891e-05,
8.893e-05,
8.875e-05,
8.877e-05,
0.7039405,
8.885e-05,
8.885e-05,
0,
0,
0,
0,
1511686800000,
None,
None,
None,
None,
],
]
2024-04-28 08:51:53 +00:00
else:
2024-05-12 13:44:42 +00:00
assert len(resp["data"][0]) == 16
assert resp["data"] == [
[
"2017-11-26T08:50:00Z",
8.794e-05,
8.948e-05,
8.794e-05,
8.88e-05,
0.0877869,
None,
0,
0,
0,
0,
1511686200000,
None,
None,
None,
None,
],
[
"2017-11-26T08:55:00Z",
8.88e-05,
8.942e-05,
8.88e-05,
8.893e-05,
0.05874751,
8.886500000000001e-05,
1,
0,
0,
0,
1511686500000,
8.893e-05,
None,
None,
None,
],
[
"2017-11-26T09:00:00Z",
8.891e-05,
8.893e-05,
8.875e-05,
8.877e-05,
0.7039405,
8.885e-05,
0,
0,
0,
0,
1511686800000,
None,
None,
None,
None,
],
2024-04-28 08:51:53 +00:00
]
# prep for next test
2024-05-12 13:44:42 +00:00
ohlcv_history["exit_long"] = ohlcv_history["exit_long"].astype("float64")
ohlcv_history.at[0, "exit_long"] = float("inf")
ohlcv_history["date1"] = ohlcv_history["date"]
ohlcv_history.at[0, "date1"] = pd.NaT
2022-02-09 05:36:17 +00:00
2022-02-11 16:02:04 +00:00
ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history, CandleType.SPOT)
2024-05-12 13:44:42 +00:00
rc = client_get(
client, f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}"
)
2022-02-09 05:36:17 +00:00
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["data"] == [
[
"2017-11-26T08:50:00Z",
8.794e-05,
8.948e-05,
8.794e-05,
8.88e-05,
0.0877869,
None,
None,
0,
None,
0,
0,
None,
1511686200000,
None,
None,
None,
None,
],
[
"2017-11-26T08:55:00Z",
8.88e-05,
8.942e-05,
8.88e-05,
8.893e-05,
0.05874751,
8.886500000000001e-05,
8.886500000000001e-05,
1,
0.0,
0,
0,
"2017-11-26T08:55:00Z",
1511686500000,
8.893e-05,
None,
None,
None,
],
[
"2017-11-26T09:00:00Z",
8.891e-05,
8.893e-05,
8.875e-05,
8.877e-05,
0.7039405,
8.885e-05,
8.885e-05,
0,
0.0,
0,
0,
"2017-11-26T09:00:00Z",
1511686800000,
None,
None,
None,
None,
],
]
2020-06-23 04:49:53 +00:00
2024-04-25 09:02:34 +00:00
def test_api_pair_history(botclient, tmp_path, mocker):
2024-01-24 19:31:38 +00:00
_ftbot, client = botclient
2024-05-12 13:44:42 +00:00
_ftbot.config["user_data_dir"] = tmp_path
2024-04-25 09:02:34 +00:00
2024-05-12 13:44:42 +00:00
timeframe = "5m"
lfm = mocker.patch("freqtrade.strategy.interface.IStrategy.load_freqAI_model")
2020-09-11 18:45:59 +00:00
# No pair
2024-05-12 13:44:42 +00:00
rc = client_get(
client,
f"{BASE_URI}/pair_history?timeframe={timeframe}"
f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}",
)
2020-12-26 19:05:27 +00:00
assert_response(rc, 422)
2020-09-11 18:45:59 +00:00
# No Timeframe
2024-05-12 13:44:42 +00:00
rc = client_get(
client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC"
f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}",
)
2020-12-26 19:05:27 +00:00
assert_response(rc, 422)
2020-09-11 18:45:59 +00:00
# No timerange
2024-05-12 13:44:42 +00:00
rc = client_get(
client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
f"&strategy={CURRENT_TEST_STRATEGY}",
)
2020-12-26 19:05:27 +00:00
assert_response(rc, 422)
2020-09-11 18:45:59 +00:00
# No strategy
2024-05-12 13:44:42 +00:00
rc = client_get(
client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&timerange=20180111-20180112",
)
2020-12-26 19:05:27 +00:00
assert_response(rc, 422)
2020-09-11 18:45:59 +00:00
# Invalid strategy
2024-05-12 13:44:42 +00:00
rc = client_get(
client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}11",
)
assert_response(rc, 502)
2020-09-11 18:45:59 +00:00
# Working
2024-05-12 13:44:42 +00:00
for call in ("get", "post"):
if call == "get":
rc = client_get(
client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}",
)
2024-04-28 09:44:21 +00:00
else:
rc = client_post(
client,
f"{BASE_URI}/pair_history",
data={
"pair": "UNITTEST/BTC",
"timeframe": timeframe,
"timerange": "20180111-20180112",
"strategy": CURRENT_TEST_STRATEGY,
2024-05-12 13:44:42 +00:00
"columns": ["rsi", "fastd", "fastk"],
},
)
2024-04-28 09:44:21 +00:00
assert_response(rc, 200)
result = rc.json()
2024-05-12 13:44:42 +00:00
assert result["length"] == 289
assert len(result["data"]) == result["length"]
assert "columns" in result
assert "data" in result
data = result["data"]
2024-04-28 09:44:21 +00:00
assert len(data) == 289
2024-05-12 13:44:42 +00:00
col_count = 30 if call == "get" else 18
2024-04-28 09:44:21 +00:00
# analyzed DF has 30 columns
2024-05-12 13:44:42 +00:00
assert len(result["columns"]) == col_count
assert len(result["all_columns"]) == 25
2024-04-28 09:44:21 +00:00
assert len(data[0]) == col_count
2024-05-12 13:44:42 +00:00
date_col_idx = [idx for idx, c in enumerate(result["columns"]) if c == "date"][0]
rsi_col_idx = [idx for idx, c in enumerate(result["columns"]) if c == "rsi"][0]
2024-04-28 09:44:21 +00:00
2024-05-12 13:44:42 +00:00
assert data[0][date_col_idx] == "2018-01-11T00:00:00Z"
2024-04-28 09:44:21 +00:00
assert data[0][rsi_col_idx] is not None
assert data[0][rsi_col_idx] > 0
assert lfm.call_count == 1
2024-05-12 13:44:42 +00:00
assert result["pair"] == "UNITTEST/BTC"
assert result["strategy"] == CURRENT_TEST_STRATEGY
assert result["data_start"] == "2018-01-11 00:00:00+00:00"
assert result["data_start_ts"] == 1515628800000
assert result["data_stop"] == "2018-01-12 00:00:00+00:00"
assert result["data_stop_ts"] == 1515715200000
2024-04-28 09:44:21 +00:00
lfm.reset_mock()
2020-09-11 18:45:59 +00:00
2024-04-28 15:01:16 +00:00
# No data found
2024-05-12 13:44:42 +00:00
if call == "get":
rc = client_get(
client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
f"&timerange=20200111-20200112&strategy={CURRENT_TEST_STRATEGY}",
)
2024-04-28 15:01:16 +00:00
else:
rc = client_post(
client,
f"{BASE_URI}/pair_history",
data={
"pair": "UNITTEST/BTC",
"timeframe": timeframe,
"timerange": "20200111-20200112",
"strategy": CURRENT_TEST_STRATEGY,
2024-05-12 13:44:42 +00:00
"columns": ["rsi", "fastd", "fastk"],
},
)
2024-04-28 15:01:16 +00:00
assert_response(rc, 502)
2024-05-12 13:44:42 +00:00
assert rc.json()["detail"] == ("No data for UNITTEST/BTC, 5m in 20200111-20200112 found.")
2020-09-11 18:45:59 +00:00
def test_api_plot_config(botclient, mocker, tmp_path):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
2024-05-12 13:44:42 +00:00
ftbot.config["user_data_dir"] = tmp_path
2020-06-23 04:49:53 +00:00
rc = client_get(client, f"{BASE_URI}/plot_config")
assert_response(rc)
2020-12-26 19:05:27 +00:00
assert rc.json() == {}
2020-06-23 04:49:53 +00:00
2021-07-23 11:34:18 +00:00
ftbot.strategy.plot_config = {
2024-05-12 13:44:42 +00:00
"main_plot": {"sma": {}},
"subplots": {"RSI": {"rsi": {"color": "red"}}},
2021-07-20 16:56:03 +00:00
}
2020-06-23 04:49:53 +00:00
rc = client_get(client, f"{BASE_URI}/plot_config")
assert_response(rc)
2021-07-23 11:34:18 +00:00
assert rc.json() == ftbot.strategy.plot_config
2024-05-12 13:44:42 +00:00
assert isinstance(rc.json()["main_plot"], dict)
assert isinstance(rc.json()["subplots"], dict)
2024-05-12 13:44:42 +00:00
ftbot.strategy.plot_config = {"main_plot": {"sma": {}}}
rc = client_get(client, f"{BASE_URI}/plot_config")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert isinstance(rc.json()["main_plot"], dict)
assert isinstance(rc.json()["subplots"], dict)
2020-07-31 05:31:20 +00:00
2023-01-18 17:15:35 +00:00
rc = client_get(client, f"{BASE_URI}/plot_config?strategy=freqai_test_classifier")
assert_response(rc)
res = rc.json()
2024-05-12 13:44:42 +00:00
assert "target_roi" in res["subplots"]
assert "do_predict" in res["subplots"]
2023-01-18 17:15:35 +00:00
rc = client_get(client, f"{BASE_URI}/plot_config?strategy=HyperoptableStrategy")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["subplots"] == {}
2023-01-18 17:15:35 +00:00
rc = client_get(client, f"{BASE_URI}/plot_config?strategy=NotAStrategy")
assert_response(rc, 502)
2024-05-12 13:44:42 +00:00
assert rc.json()["detail"] is not None
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.api_server.api_v1.get_rpc_optional", return_value=None)
2023-01-18 17:15:35 +00:00
rc = client_get(client, f"{BASE_URI}/plot_config")
assert_response(rc)
2020-07-31 05:31:20 +00:00
2023-11-05 15:18:28 +00:00
def test_api_strategies(botclient, tmp_path):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
2024-05-12 13:44:42 +00:00
ftbot.config["user_data_dir"] = tmp_path
2020-07-31 05:31:20 +00:00
rc = client_get(client, f"{BASE_URI}/strategies")
assert_response(rc)
2022-05-24 20:04:23 +00:00
2024-05-12 13:44:42 +00:00
assert rc.json() == {
"strategies": [
"HyperoptableStrategy",
"HyperoptableStrategyV2",
"InformativeDecoratorTest",
"StrategyTestV2",
"StrategyTestV3",
"StrategyTestV3CustomEntryPrice",
"StrategyTestV3Futures",
"freqai_rl_test_strat",
"freqai_test_classifier",
"freqai_test_multimodel_classifier_strat",
"freqai_test_multimodel_strat",
"freqai_test_strat",
"strategy_test_v3_recursive_issue",
]
}
2020-08-01 15:49:59 +00:00
2024-04-28 15:06:36 +00:00
def test_api_strategy(botclient, tmp_path, mocker):
2024-01-24 19:31:38 +00:00
_ftbot, client = botclient
2024-05-12 13:44:42 +00:00
_ftbot.config["user_data_dir"] = tmp_path
2020-09-17 05:53:22 +00:00
rc = client_get(client, f"{BASE_URI}/strategy/{CURRENT_TEST_STRATEGY}")
2020-09-17 05:53:22 +00:00
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["strategy"] == CURRENT_TEST_STRATEGY
2020-09-17 05:53:22 +00:00
data = (Path(__file__).parents[1] / "strategy/strats/strategy_test_v3.py").read_text()
2024-05-12 13:44:42 +00:00
assert rc.json()["code"] == data
2020-09-17 05:53:22 +00:00
rc = client_get(client, f"{BASE_URI}/strategy/NoStrat")
assert_response(rc, 404)
# Disallow base64 strategies
rc = client_get(client, f"{BASE_URI}/strategy/xx:cHJpbnQoImhlbGxvIHdvcmxkIik=")
assert_response(rc, 500)
2024-05-12 13:44:42 +00:00
mocker.patch(
"freqtrade.resolvers.strategy_resolver.StrategyResolver._load_strategy",
side_effect=Exception("Test"),
)
2024-04-28 15:06:36 +00:00
rc = client_get(client, f"{BASE_URI}/strategy/NoStrat")
assert_response(rc, 502)
2020-09-17 05:53:22 +00:00
def test_api_exchanges(botclient):
2024-01-24 19:31:38 +00:00
_ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/exchanges")
assert_response(rc)
response = rc.json()
2024-05-12 13:44:42 +00:00
assert isinstance(response["exchanges"], list)
assert len(response["exchanges"]) > 20
okx = [x for x in response["exchanges"] if x["name"] == "okx"][0]
assert okx == {
"name": "okx",
"valid": True,
"supported": True,
"comment": "",
"trade_modes": [
2024-05-12 13:44:42 +00:00
{"trading_mode": "spot", "margin_mode": ""},
{"trading_mode": "futures", "margin_mode": "isolated"},
],
}
2024-05-12 13:44:42 +00:00
mexc = [x for x in response["exchanges"] if x["name"] == "mexc"][0]
assert mexc == {
"name": "mexc",
"valid": True,
"supported": False,
"comment": "",
2024-05-12 13:44:42 +00:00
"trade_modes": [{"trading_mode": "spot", "margin_mode": ""}],
}
2023-11-05 15:18:28 +00:00
def test_api_freqaimodels(botclient, tmp_path, mocker):
2022-12-20 06:23:41 +00:00
ftbot, client = botclient
2024-05-12 13:44:42 +00:00
ftbot.config["user_data_dir"] = tmp_path
mocker.patch(
"freqtrade.resolvers.freqaimodel_resolver.FreqaiModelResolver.search_all_objects",
return_value=[
2024-05-12 13:44:42 +00:00
{"name": "LightGBMClassifier"},
{"name": "LightGBMClassifierMultiTarget"},
{"name": "LightGBMRegressor"},
{"name": "LightGBMRegressorMultiTarget"},
{"name": "ReinforcementLearner"},
{"name": "ReinforcementLearner_multiproc"},
{"name": "SKlearnRandomForestClassifier"},
{"name": "XGBoostClassifier"},
{"name": "XGBoostRFClassifier"},
{"name": "XGBoostRFRegressor"},
{"name": "XGBoostRegressor"},
{"name": "XGBoostRegressorMultiTarget"},
],
)
2022-12-20 06:23:41 +00:00
rc = client_get(client, f"{BASE_URI}/freqaimodels")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json() == {
"freqaimodels": [
"LightGBMClassifier",
"LightGBMClassifierMultiTarget",
"LightGBMRegressor",
"LightGBMRegressorMultiTarget",
"ReinforcementLearner",
"ReinforcementLearner_multiproc",
"SKlearnRandomForestClassifier",
"XGBoostClassifier",
"XGBoostRFClassifier",
"XGBoostRFRegressor",
"XGBoostRegressor",
"XGBoostRegressorMultiTarget",
]
}
2022-12-20 06:23:41 +00:00
2023-11-05 15:18:28 +00:00
def test_api_pairlists_available(botclient, tmp_path):
2023-04-19 16:35:52 +00:00
ftbot, client = botclient
2024-05-12 13:44:42 +00:00
ftbot.config["user_data_dir"] = tmp_path
2023-04-19 16:35:52 +00:00
2023-05-21 07:56:46 +00:00
rc = client_get(client, f"{BASE_URI}/pairlists/available")
2023-04-19 16:35:52 +00:00
2023-06-01 18:46:28 +00:00
assert_response(rc, 503)
2024-05-12 13:44:42 +00:00
assert rc.json()["detail"] == "Bot is not in the correct state."
2023-06-01 18:46:28 +00:00
2024-05-12 13:44:42 +00:00
ftbot.config["runmode"] = RunMode.WEBSERVER
2023-06-01 18:46:28 +00:00
rc = client_get(client, f"{BASE_URI}/pairlists/available")
2023-04-19 16:35:52 +00:00
assert_response(rc)
response = rc.json()
2024-05-12 13:44:42 +00:00
assert isinstance(response["pairlists"], list)
assert len(response["pairlists"]) > 0
2023-04-19 16:35:52 +00:00
2024-05-12 13:44:42 +00:00
assert len([r for r in response["pairlists"] if r["name"] == "AgeFilter"]) == 1
assert len([r for r in response["pairlists"] if r["name"] == "VolumePairList"]) == 1
assert len([r for r in response["pairlists"] if r["name"] == "StaticPairList"]) == 1
2023-04-19 16:35:52 +00:00
2024-05-12 13:44:42 +00:00
volumepl = [r for r in response["pairlists"] if r["name"] == "VolumePairList"][0]
assert volumepl["is_pairlist_generator"] is True
assert len(volumepl["params"]) > 1
age_pl = [r for r in response["pairlists"] if r["name"] == "AgeFilter"][0]
assert age_pl["is_pairlist_generator"] is False
assert len(volumepl["params"]) > 2
2023-04-20 16:13:43 +00:00
2023-04-19 16:35:52 +00:00
2023-11-05 15:18:28 +00:00
def test_api_pairlists_evaluate(botclient, tmp_path, mocker):
2023-05-21 08:08:32 +00:00
ftbot, client = botclient
2024-05-12 13:44:42 +00:00
ftbot.config["user_data_dir"] = tmp_path
2023-05-21 08:08:32 +00:00
2023-05-31 05:08:27 +00:00
rc = client_get(client, f"{BASE_URI}/pairlists/evaluate/randomJob")
2023-05-21 08:08:32 +00:00
2023-06-01 18:46:28 +00:00
assert_response(rc, 503)
2024-05-12 13:44:42 +00:00
assert rc.json()["detail"] == "Bot is not in the correct state."
2023-06-01 18:46:28 +00:00
2024-05-12 13:44:42 +00:00
ftbot.config["runmode"] = RunMode.WEBSERVER
2023-06-01 18:46:28 +00:00
rc = client_get(client, f"{BASE_URI}/pairlists/evaluate/randomJob")
2023-05-31 05:08:27 +00:00
assert_response(rc, 404)
2024-05-12 13:44:42 +00:00
assert rc.json()["detail"] == "Job not found."
2023-05-21 08:08:32 +00:00
body = {
"pairlists": [
2024-05-12 13:44:42 +00:00
{
"method": "StaticPairList",
},
2023-05-21 08:08:32 +00:00
],
2024-05-12 13:44:42 +00:00
"blacklist": [],
"stake_currency": "BTC",
2023-05-21 08:08:32 +00:00
}
# Fail, already running
2023-05-31 05:08:27 +00:00
ApiBG.pairlist_running = True
2023-05-21 08:08:32 +00:00
rc = client_post(client, f"{BASE_URI}/pairlists/evaluate", body)
assert_response(rc, 400)
2024-05-12 13:44:42 +00:00
assert rc.json()["detail"] == "Pairlist evaluation is already running."
2023-05-21 08:08:32 +00:00
# should start the run
ApiBG.pairlist_running = False
rc = client_post(client, f"{BASE_URI}/pairlists/evaluate", body)
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["status"] == "Pairlist evaluation started in background."
job_id = rc.json()["job_id"]
2023-05-21 08:08:32 +00:00
2023-05-31 05:08:27 +00:00
rc = client_get(client, f"{BASE_URI}/background/RandomJob")
assert_response(rc, 404)
2024-05-12 13:44:42 +00:00
assert rc.json()["detail"] == "Job not found."
2023-05-31 05:08:27 +00:00
2024-05-12 07:12:53 +00:00
# Background list
rc = client_get(client, f"{BASE_URI}/background")
assert_response(rc)
response = rc.json()
assert isinstance(response, list)
assert len(response) == 1
2024-05-12 13:44:42 +00:00
assert response[0]["job_id"] == job_id
2024-05-12 07:12:53 +00:00
# Get individual job
2023-05-31 05:08:27 +00:00
rc = client_get(client, f"{BASE_URI}/background/{job_id}")
assert_response(rc)
response = rc.json()
2024-05-12 13:44:42 +00:00
assert response["job_id"] == job_id
assert response["job_category"] == "pairlist"
2023-05-31 05:08:27 +00:00
rc = client_get(client, f"{BASE_URI}/pairlists/evaluate/{job_id}")
2023-05-21 08:08:32 +00:00
assert_response(rc)
response = rc.json()
2024-05-12 13:44:42 +00:00
assert response["result"]["whitelist"] == ["ETH/BTC", "LTC/BTC", "XRP/BTC", "NEO/BTC"]
assert response["result"]["length"] == 4
2023-05-21 08:08:32 +00:00
# Restart with additional filter, reducing the list to 2
2024-05-12 13:44:42 +00:00
body["pairlists"].append({"method": "OffsetFilter", "number_assets": 2})
2023-05-21 08:08:32 +00:00
rc = client_post(client, f"{BASE_URI}/pairlists/evaluate", body)
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["status"] == "Pairlist evaluation started in background."
job_id = rc.json()["job_id"]
2023-05-31 05:08:27 +00:00
rc = client_get(client, f"{BASE_URI}/pairlists/evaluate/{job_id}")
2023-05-21 08:08:32 +00:00
assert_response(rc)
response = rc.json()
2024-05-12 13:44:42 +00:00
assert response["result"]["whitelist"] == [
"ETH/BTC",
"LTC/BTC",
]
assert response["result"]["length"] == 2
2023-06-02 08:14:11 +00:00
# Patch __run_pairlists
2024-05-12 13:44:42 +00:00
plm = mocker.patch(
"freqtrade.rpc.api_server.api_background_tasks.__run_pairlist", return_value=None
)
2023-06-02 08:14:11 +00:00
body = {
"pairlists": [
2024-05-12 13:44:42 +00:00
{
"method": "StaticPairList",
},
2023-06-02 08:14:11 +00:00
],
2024-05-12 13:44:42 +00:00
"blacklist": [],
2023-06-02 08:14:11 +00:00
"stake_currency": "BTC",
"exchange": "randomExchange",
"trading_mode": "futures",
2023-06-04 11:25:39 +00:00
"margin_mode": "isolated",
2023-06-02 08:14:11 +00:00
}
rc = client_post(client, f"{BASE_URI}/pairlists/evaluate", body)
assert_response(rc)
assert plm.call_count == 1
call_config = plm.call_args_list[0][0][1]
2024-05-12 13:44:42 +00:00
assert call_config["exchange"]["name"] == "randomExchange"
assert call_config["trading_mode"] == "futures"
assert call_config["margin_mode"] == "isolated"
2023-05-21 08:08:32 +00:00
2020-08-01 15:49:59 +00:00
def test_list_available_pairs(botclient):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
2020-08-01 15:49:59 +00:00
rc = client_get(client, f"{BASE_URI}/available_pairs")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["length"] == 12
assert isinstance(rc.json()["pairs"], list)
2020-08-01 15:49:59 +00:00
rc = client_get(client, f"{BASE_URI}/available_pairs?timeframe=5m")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["length"] == 12
2020-08-01 15:49:59 +00:00
rc = client_get(client, f"{BASE_URI}/available_pairs?stake_currency=ETH")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["length"] == 1
assert rc.json()["pairs"] == ["XRP/ETH"]
assert len(rc.json()["pair_interval"]) == 2
2020-08-01 15:49:59 +00:00
rc = client_get(client, f"{BASE_URI}/available_pairs?stake_currency=ETH&timeframe=5m")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["length"] == 1
assert rc.json()["pairs"] == ["XRP/ETH"]
assert len(rc.json()["pair_interval"]) == 1
2021-04-01 17:59:49 +00:00
2024-05-12 13:44:42 +00:00
ftbot.config["trading_mode"] = "futures"
rc = client_get(client, f"{BASE_URI}/available_pairs?timeframe=1h")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["length"] == 1
assert rc.json()["pairs"] == ["XRP/USDT:USDT"]
2024-05-12 13:44:42 +00:00
rc = client_get(client, f"{BASE_URI}/available_pairs?timeframe=1h&candletype=mark")
assert_response(rc)
2024-05-12 13:44:42 +00:00
assert rc.json()["length"] == 2
assert rc.json()["pairs"] == ["UNITTEST/USDT:USDT", "XRP/USDT:USDT"]
assert len(rc.json()["pair_interval"]) == 2
2021-04-01 17:59:49 +00:00
2021-10-06 17:36:51 +00:00
def test_sysinfo(botclient):
2024-01-24 19:31:38 +00:00
_ftbot, client = botclient
2021-10-06 17:36:51 +00:00
rc = client_get(client, f"{BASE_URI}/sysinfo")
assert_response(rc)
result = rc.json()
2024-05-12 13:44:42 +00:00
assert "cpu_pct" in result
assert "ram_pct" in result
2021-10-06 17:36:51 +00:00
2023-11-05 15:18:28 +00:00
def test_api_backtesting(botclient, mocker, fee, caplog, tmp_path):
2023-05-21 07:14:00 +00:00
try:
ftbot, client = botclient
2024-05-12 13:44:42 +00:00
mocker.patch(f"{EXMS}.get_fee", fee)
2023-05-21 07:14:00 +00:00
rc = client_get(client, f"{BASE_URI}/backtest")
# Backtest prevented in default mode
assert_response(rc, 503)
2024-05-12 13:44:42 +00:00
assert rc.json()["detail"] == "Bot is not in the correct state."
2023-05-21 07:14:00 +00:00
2024-05-12 13:44:42 +00:00
ftbot.config["runmode"] = RunMode.WEBSERVER
2023-05-21 07:14:00 +00:00
# Backtesting not started yet
rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
2024-05-12 13:44:42 +00:00
assert result["status"] == "not_started"
assert not result["running"]
assert result["status_msg"] == "Backtest not yet executed"
assert result["progress"] == 0
2023-05-21 07:14:00 +00:00
# Reset backtesting
rc = client_delete(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
2024-05-12 13:44:42 +00:00
assert result["status"] == "reset"
assert not result["running"]
assert result["status_msg"] == "Backtest reset"
ftbot.config["export"] = "trades"
ftbot.config["backtest_cache"] = "day"
ftbot.config["user_data_dir"] = tmp_path
ftbot.config["exportfilename"] = tmp_path / "backtest_results"
ftbot.config["exportfilename"].mkdir()
2023-05-21 07:14:00 +00:00
# start backtesting
data = {
"strategy": CURRENT_TEST_STRATEGY,
"timeframe": "5m",
"timerange": "20180110-20180111",
"max_open_trades": 3,
"stake_amount": 100,
"dry_run_wallet": 1000,
2024-05-12 13:44:42 +00:00
"enable_protections": False,
2023-05-21 07:14:00 +00:00
}
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert_response(rc)
result = rc.json()
2024-05-12 13:44:42 +00:00
assert result["status"] == "running"
assert result["progress"] == 0
assert result["running"]
assert result["status_msg"] == "Backtest started"
2023-05-21 07:14:00 +00:00
rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
2024-05-12 13:44:42 +00:00
assert result["status"] == "ended"
assert not result["running"]
assert result["status_msg"] == "Backtest ended"
assert result["progress"] == 1
assert result["backtest_result"]
2023-05-21 07:14:00 +00:00
rc = client_get(client, f"{BASE_URI}/backtest/abort")
assert_response(rc)
result = rc.json()
2024-05-12 13:44:42 +00:00
assert result["status"] == "not_running"
assert not result["running"]
assert result["status_msg"] == "Backtest ended"
2023-05-21 07:14:00 +00:00
# Simulate running backtest
ApiBG.bgtask_running = True
rc = client_get(client, f"{BASE_URI}/backtest/abort")
assert_response(rc)
result = rc.json()
2024-05-12 13:44:42 +00:00
assert result["status"] == "stopping"
assert not result["running"]
assert result["status_msg"] == "Backtest ended"
2023-05-21 07:14:00 +00:00
# Get running backtest...
rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
2024-05-12 13:44:42 +00:00
assert result["status"] == "running"
assert result["running"]
assert result["step"] == "backtest"
assert result["status_msg"] == "Backtest running"
2023-05-21 07:14:00 +00:00
# Try delete with task still running
rc = client_delete(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
2024-05-12 13:44:42 +00:00
assert result["status"] == "running"
2023-05-21 07:14:00 +00:00
# Post to backtest that's still running
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert_response(rc, 502)
result = rc.json()
2024-05-12 13:44:42 +00:00
assert "Bot Background task already running" in result["error"]
2023-05-21 07:14:00 +00:00
ApiBG.bgtask_running = False
# Rerun backtest (should get previous result)
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert_response(rc)
result = rc.json()
2024-05-12 13:44:42 +00:00
assert log_has_re("Reusing result of previous backtest.*", caplog)
2023-05-21 07:14:00 +00:00
2024-05-12 13:44:42 +00:00
data["stake_amount"] = 101
2023-05-21 07:14:00 +00:00
2024-05-12 13:44:42 +00:00
mocker.patch(
"freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy",
side_effect=DependencyException("DeadBeef"),
)
2023-05-21 07:14:00 +00:00
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert log_has("Backtesting caused an error: DeadBeef", caplog)
rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
2024-05-12 13:44:42 +00:00
assert result["status"] == "error"
assert "Backtest failed" in result["status_msg"]
2023-05-21 07:14:00 +00:00
# Delete backtesting to avoid leakage since the backtest-object may stick around.
rc = client_delete(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
2024-05-12 13:44:42 +00:00
assert result["status"] == "reset"
assert not result["running"]
assert result["status_msg"] == "Backtest reset"
2023-05-21 07:14:00 +00:00
# Disallow base64 strategies
2024-05-12 13:44:42 +00:00
data["strategy"] = "xx:cHJpbnQoImhlbGxvIHdvcmxkIik="
2023-05-21 07:14:00 +00:00
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert_response(rc, 500)
finally:
Backtesting.cleanup()
2022-04-12 04:28:37 +00:00
def test_api_backtest_history(botclient, mocker, testdatadir):
ftbot, client = botclient
2024-05-12 13:44:42 +00:00
mocker.patch(
"freqtrade.data.btanalysis._get_backtest_files",
return_value=[
testdatadir / "backtest_results/backtest-result_multistrat.json",
testdatadir / "backtest_results/backtest-result.json",
],
)
2022-04-12 04:28:37 +00:00
rc = client_get(client, f"{BASE_URI}/backtest/history")
assert_response(rc, 503)
2024-05-12 13:44:42 +00:00
assert rc.json()["detail"] == "Bot is not in the correct state."
2024-05-12 13:44:42 +00:00
ftbot.config["user_data_dir"] = testdatadir
ftbot.config["runmode"] = RunMode.WEBSERVER
2022-04-12 04:28:37 +00:00
rc = client_get(client, f"{BASE_URI}/backtest/history")
assert_response(rc)
result = rc.json()
assert len(result) == 3
2024-05-12 13:44:42 +00:00
fn = result[0]["filename"]
2023-07-25 18:38:49 +00:00
assert fn == "backtest-result_multistrat"
2024-05-12 13:44:42 +00:00
assert result[0]["notes"] == ""
strategy = result[0]["strategy"]
2022-04-13 04:47:39 +00:00
rc = client_get(client, f"{BASE_URI}/backtest/history/result?filename={fn}&strategy={strategy}")
2022-04-12 04:28:37 +00:00
assert_response(rc)
result2 = rc.json()
assert result2
2024-05-12 13:44:42 +00:00
assert result2["status"] == "ended"
assert not result2["running"]
assert result2["progress"] == 1
# Only one strategy loaded - even though we use multiresult
2024-05-12 13:44:42 +00:00
assert len(result2["backtest_result"]["strategy"]) == 1
assert result2["backtest_result"]["strategy"][strategy]
2022-04-12 04:28:37 +00:00
2023-08-03 04:39:27 +00:00
def test_api_delete_backtest_history_entry(botclient, tmp_path: Path):
2023-07-25 18:34:45 +00:00
ftbot, client = botclient
# Create a temporary directory and file
bt_results_base = tmp_path / "backtest_results"
bt_results_base.mkdir()
file_path = bt_results_base / "test.json"
file_path.touch()
2024-05-12 13:44:42 +00:00
meta_path = file_path.with_suffix(".meta.json")
2023-07-25 18:34:45 +00:00
meta_path.touch()
rc = client_delete(client, f"{BASE_URI}/backtest/history/randomFile.json")
assert_response(rc, 503)
2024-05-12 13:44:42 +00:00
assert rc.json()["detail"] == "Bot is not in the correct state."
2023-07-25 18:34:45 +00:00
2024-05-12 13:44:42 +00:00
ftbot.config["user_data_dir"] = tmp_path
ftbot.config["runmode"] = RunMode.WEBSERVER
2023-07-25 18:34:45 +00:00
rc = client_delete(client, f"{BASE_URI}/backtest/history/randomFile.json")
assert rc.status_code == 404
2024-05-12 13:44:42 +00:00
assert rc.json()["detail"] == "File not found."
2023-07-25 18:34:45 +00:00
rc = client_delete(client, f"{BASE_URI}/backtest/history/{file_path.name}")
assert rc.status_code == 200
assert not file_path.exists()
assert not meta_path.exists()
2023-08-03 04:39:27 +00:00
def test_api_patch_backtest_history_entry(botclient, tmp_path: Path):
ftbot, client = botclient
# Create a temporary directory and file
bt_results_base = tmp_path / "backtest_results"
bt_results_base.mkdir()
file_path = bt_results_base / "test.json"
file_path.touch()
2024-05-12 13:44:42 +00:00
meta_path = file_path.with_suffix(".meta.json")
with meta_path.open("w") as metafile:
rapidjson.dump(
{
CURRENT_TEST_STRATEGY: {
"run_id": "6e542efc8d5e62cef6e5be0ffbc29be81a6e751d",
"backtest_start_time": 1690176003,
}
},
metafile,
)
2023-08-03 04:39:27 +00:00
def read_metadata():
2024-05-12 13:44:42 +00:00
with meta_path.open("r") as metafile:
2023-08-03 04:39:27 +00:00
return rapidjson.load(metafile)
rc = client_patch(client, f"{BASE_URI}/backtest/history/randomFile.json")
assert_response(rc, 503)
2024-05-12 13:44:42 +00:00
ftbot.config["user_data_dir"] = tmp_path
ftbot.config["runmode"] = RunMode.WEBSERVER
2023-08-03 04:39:27 +00:00
2024-05-12 13:44:42 +00:00
rc = client_patch(
client,
f"{BASE_URI}/backtest/history/randomFile.json",
{
"strategy": CURRENT_TEST_STRATEGY,
},
)
2023-08-03 04:39:27 +00:00
assert rc.status_code == 404
# Nonexisting strategy
2024-05-12 13:44:42 +00:00
rc = client_patch(
client,
f"{BASE_URI}/backtest/history/{file_path.name}",
{
"strategy": f"{CURRENT_TEST_STRATEGY}xxx",
},
)
2023-08-03 04:39:27 +00:00
assert rc.status_code == 400
2024-05-12 13:44:42 +00:00
assert rc.json()["detail"] == "Strategy not in metadata."
2023-08-03 04:39:27 +00:00
# no Notes
2024-05-12 13:44:42 +00:00
rc = client_patch(
client,
f"{BASE_URI}/backtest/history/{file_path.name}",
{
"strategy": CURRENT_TEST_STRATEGY,
},
)
2023-08-03 04:39:27 +00:00
assert rc.status_code == 200
res = rc.json()
assert isinstance(res, list)
assert len(res) == 1
2024-05-12 13:44:42 +00:00
assert res[0]["strategy"] == CURRENT_TEST_STRATEGY
assert res[0]["notes"] == ""
2023-08-03 04:39:27 +00:00
fileres = read_metadata()
2024-05-12 13:44:42 +00:00
assert fileres[CURRENT_TEST_STRATEGY]["run_id"] == res[0]["run_id"]
assert fileres[CURRENT_TEST_STRATEGY]["notes"] == ""
2023-08-03 04:39:27 +00:00
2024-05-12 13:44:42 +00:00
rc = client_patch(
client,
f"{BASE_URI}/backtest/history/{file_path.name}",
{
"strategy": CURRENT_TEST_STRATEGY,
"notes": "FooBar",
},
)
2023-08-03 04:39:27 +00:00
assert rc.status_code == 200
res = rc.json()
assert isinstance(res, list)
assert len(res) == 1
2024-05-12 13:44:42 +00:00
assert res[0]["strategy"] == CURRENT_TEST_STRATEGY
assert res[0]["notes"] == "FooBar"
2023-08-03 04:39:27 +00:00
fileres = read_metadata()
2024-05-12 13:44:42 +00:00
assert fileres[CURRENT_TEST_STRATEGY]["run_id"] == res[0]["run_id"]
assert fileres[CURRENT_TEST_STRATEGY]["notes"] == "FooBar"
2023-08-03 04:39:27 +00:00
2024-04-17 05:22:36 +00:00
def test_api_patch_backtest_market_change(botclient, tmp_path: Path):
ftbot, client = botclient
# Create a temporary directory and file
bt_results_base = tmp_path / "backtest_results"
bt_results_base.mkdir()
file_path = bt_results_base / "test_22_market_change.feather"
2024-05-12 13:44:42 +00:00
df = pd.DataFrame(
{
"date": ["2018-01-01T00:00:00Z", "2018-01-01T00:05:00Z"],
"count": [2, 4],
"mean": [2555, 2556],
"rel_mean": [0, 0.022],
}
)
df["date"] = pd.to_datetime(df["date"])
df.to_feather(file_path, compression_level=9, compression="lz4")
2024-04-17 05:22:36 +00:00
# Nonexisting file
rc = client_get(client, f"{BASE_URI}/backtest/history/randomFile.json/market_change")
assert_response(rc, 503)
2024-05-12 13:44:42 +00:00
ftbot.config["user_data_dir"] = tmp_path
ftbot.config["runmode"] = RunMode.WEBSERVER
2024-04-17 05:22:36 +00:00
rc = client_get(client, f"{BASE_URI}/backtest/history/randomFile.json/market_change")
assert_response(rc, 404)
rc = client_get(client, f"{BASE_URI}/backtest/history/test_22/market_change")
assert_response(rc, 200)
result = rc.json()
2024-05-12 13:44:42 +00:00
assert result["length"] == 2
assert result["columns"] == ["date", "count", "mean", "rel_mean", "__date_ts"]
assert result["data"] == [
["2018-01-01T00:00:00Z", 2, 2555, 0.0, 1514764800000],
["2018-01-01T00:05:00Z", 4, 2556, 0.022, 1514765100000],
2024-04-17 05:22:36 +00:00
]
def test_health(botclient):
2024-01-24 19:31:38 +00:00
_ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/health")
assert_response(rc)
ret = rc.json()
assert ret["last_process_ts"] is None
assert ret["last_process"] is None
2022-09-08 19:58:28 +00:00
def test_api_ws_subscribe(botclient, mocker):
2024-01-24 19:31:38 +00:00
_ftbot, client = botclient
2022-09-08 19:58:28 +00:00
ws_url = f"/api/v1/message/ws?token={_TEST_WS_TOKEN}"
2024-05-12 13:44:42 +00:00
sub_mock = mocker.patch("freqtrade.rpc.api_server.ws.WebSocketChannel.set_subscriptions")
2022-09-08 19:58:28 +00:00
with client.websocket_connect(ws_url) as ws:
2024-05-12 13:44:42 +00:00
ws.send_json({"type": "subscribe", "data": ["whitelist"]})
2023-11-05 15:47:55 +00:00
time.sleep(0.2)
2022-09-08 19:58:28 +00:00
# Check call count is now 1 as we sent a valid subscribe request
assert sub_mock.call_count == 1
with client.websocket_connect(ws_url) as ws:
2024-05-12 13:44:42 +00:00
ws.send_json({"type": "subscribe", "data": "whitelist"})
2023-11-05 15:47:55 +00:00
time.sleep(0.2)
2022-09-08 19:58:28 +00:00
# Call count hasn't changed as the subscribe request was invalid
assert sub_mock.call_count == 1
def test_api_ws_requests(botclient, caplog):
2022-09-10 22:08:05 +00:00
caplog.set_level(logging.DEBUG)
2024-01-24 19:31:38 +00:00
_ftbot, client = botclient
2022-09-10 22:08:05 +00:00
ws_url = f"/api/v1/message/ws?token={_TEST_WS_TOKEN}"
# Test whitelist request
with client.websocket_connect(ws_url) as ws:
ws.send_json({"type": "whitelist", "data": None})
response = ws.receive_json()
assert log_has_re(r"Request of type whitelist from.+", caplog)
2024-05-12 13:44:42 +00:00
assert response["type"] == "whitelist"
2022-09-10 22:08:05 +00:00
# Test analyzed_df request
with client.websocket_connect(ws_url) as ws:
ws.send_json({"type": "analyzed_df", "data": {}})
response = ws.receive_json()
assert log_has_re(r"Request of type analyzed_df from.+", caplog)
2024-05-12 13:44:42 +00:00
assert response["type"] == "analyzed_df"
2022-09-10 22:08:05 +00:00
caplog.clear()
# Test analyzed_df request with data
with client.websocket_connect(ws_url) as ws:
ws.send_json({"type": "analyzed_df", "data": {"limit": 100}})
response = ws.receive_json()
assert log_has_re(r"Request of type analyzed_df from.+", caplog)
2024-05-12 13:44:42 +00:00
assert response["type"] == "analyzed_df"
2022-09-10 22:08:05 +00:00
def test_api_ws_send_msg(default_conf, mocker, caplog):
try:
caplog.set_level(logging.DEBUG)
2024-05-12 13:44:42 +00:00
default_conf.update(
{
"api_server": {
"enabled": True,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"CORS_origins": ["http://example.com"],
"username": _TEST_USER,
"password": _TEST_PASS,
"ws_token": _TEST_WS_TOKEN,
}
}
)
mocker.patch("freqtrade.rpc.telegram.Telegram._init")
mocker.patch("freqtrade.rpc.api_server.ApiServer.start_api")
apiserver = ApiServer(default_conf)
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
2022-11-21 19:21:40 +00:00
# Start test client context manager to run lifespan events
with TestClient(apiserver.app):
# Test message is published on the Message Stream
test_message = {"type": "status", "data": "test"}
first_waiter = apiserver._message_stream._waiter
apiserver.send_msg(test_message)
assert first_waiter.result()[0] == test_message
2022-11-21 19:21:40 +00:00
second_waiter = apiserver._message_stream._waiter
apiserver.send_msg(test_message)
assert first_waiter != second_waiter
finally:
ApiServer.shutdown()
ApiServer.shutdown()