stable/tests/rpc/test_rpc_apiserver.py

1831 lines
66 KiB
Python
Raw Normal View History

2019-04-04 05:12:58 +00:00
"""
Unit test file for rpc/api_server.py
"""
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
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
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
2020-10-25 09:54:30 +00:00
from freqtrade.persistence import PairLocks, 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 tests.conftest import (CURRENT_TEST_STRATEGY, 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)
default_conf['runmode'] = RunMode.DRY_RUN
2019-05-11 11:31:48 +00:00
default_conf.update({"api_server": {"enabled": True,
2019-05-11 11:40:30 +00:00
"listen_ip_address": "127.0.0.1",
2019-11-23 14:20:53 +00:00
"listen_port": 8080,
2020-06-24 18:32:35 +00:00
"CORS_origins": ['http://example.com'],
2019-05-25 12:13:59 +00:00
"username": _TEST_USER,
"password": _TEST_PASS,
2022-09-08 17:25:30 +00:00
"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)
2020-12-31 10:01:50 +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
def client_post(client: TestClient, url, data={}):
2019-05-25 12:13:59 +00:00
return client.post(url,
json=data,
2020-05-20 17:43:52 +00:00
headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS),
2020-12-26 15:33:13 +00:00
'Origin': 'http://example.com',
'content-type': 'application/json'
})
2019-05-25 12:13:59 +00:00
def client_get(client: TestClient, url):
2020-05-20 17:43:52 +00:00
# Add fake Origin to ensure CORS kicks in
return client.get(url, headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS),
2020-06-24 18:32:35 +00:00
'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
return client.delete(url, headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS),
'Origin': 'http://example.com'})
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
2020-12-26 15:33:13 +00:00
assert response.headers.get('content-type') == "application/json"
2020-05-20 17:43:52 +00:00
if needs_cors:
2020-12-26 15:33:13 +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):
2021-07-23 11:34:18 +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):
2021-07-23 11:34:18 +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
assert '`freqtrade install-ui`' in rc.text
# 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
2021-08-16 04:38:36 +00:00
rc = client_get(client, '%2F%2F%2Fetc/passwd')
assert rc.status_code == 200
2021-10-12 05:00:07 +00:00
# Allow both fallback or real UI
assert '`freqtrade install-ui`' in rc.text or '<!DOCTYPE html>' in rc.text
mocker.patch.object(Path, 'is_file', MagicMock(side_effect=[True, False]))
rc = client_get(client, '%2F%2F%2Fetc/passwd')
assert rc.status_code == 200
2021-08-16 04:38:36 +00:00
assert '`freqtrade install-ui`' in rc.text
2021-01-31 14:27:00 +00:00
2021-07-06 05:20:05 +00:00
def test_api_ui_version(botclient, mocker):
ftbot, client = botclient
mocker.patch('freqtrade.commands.deploy_commands.read_ui_version', return_value='0.1.2')
rc = client_get(client, "/ui_version")
assert rc.status_code == 200
assert rc.json()['version'] == '0.1.2'
2020-12-27 13:57:01 +00:00
def test_api_auth():
with pytest.raises(ValueError):
create_token({'identity': {'u': 'Freqtrade'}}, 'secret1234', token_type="NotATokenType")
2020-12-27 13:57:01 +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
2020-12-27 14:24:49 +00:00
u = get_user_from_token(token, 'secret1234')
2020-12-27 13:57:01 +00:00
assert u == 'Freqtrade'
with pytest.raises(HTTPException):
2020-12-27 14:24:49 +00:00
get_user_from_token(token, 'secret1234', token_type='refresh')
2020-12-27 13:57:01 +00:00
# Create invalid token
token = create_token({'identity': {'u1': 'Freqrade'}}, 'secret1234')
2020-12-27 13:57:01 +00:00
with pytest.raises(HTTPException):
2020-12-27 14:24:49 +00:00
get_user_from_token(token, 'secret1234')
2020-12-27 13:57:01 +00:00
with pytest.raises(HTTPException):
2020-12-27 14:24:49 +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
2022-09-09 17:38:42 +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
2022-09-09 17:38:42 +00:00
jwt_secret = ftbot.config['api_server'].get('jwt_secret_key', 'super-secret')
jwt_token = create_token({'identity': {'u': 'Freqtrade'}}, jwt_secret)
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)
2020-12-26 15:33:13 +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)
2020-12-27 13:57:01 +00:00
assert rc.json() == {'detail': 'Unauthorized'}
2019-05-25 12:13:59 +00:00
# Change only username
2021-07-23 11:34:18 +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)
2020-12-27 13:57:01 +00:00
assert rc.json() == {'detail': 'Unauthorized'}
2019-05-25 12:13:59 +00:00
# Change only password
2021-07-23 11:34:18 +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)
2020-12-27 13:57:01 +00:00
assert rc.json() == {'detail': 'Unauthorized'}
2019-05-25 12:13:59 +00:00
2021-07-23 11:34:18 +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)
2020-12-27 13:57:01 +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):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
2020-12-27 13:57:01 +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'})
assert_response(rc, 401)
2020-05-10 08:43:13 +00:00
rc = client_post(client, f"{BASE_URI}/token/login")
assert_response(rc)
2020-12-26 15:33:13 +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
rc = client.get(f"{BASE_URI}/count",
2020-12-26 15:33:13 +00:00
headers={'Authorization': f'Bearer {rc.json()["access_token"]}',
2020-06-24 18:32:35 +00:00
'Origin': 'http://example.com'})
2020-05-10 08:43:13 +00:00
assert_response(rc)
def test_api_token_refresh(botclient):
2021-07-23 11:34:18 +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)
rc = client.post(f"{BASE_URI}/token/refresh",
data=None,
2020-12-26 16:48:19 +00:00
headers={'Authorization': f'Bearer {rc.json()["refresh_token"]}',
2020-06-24 18:32:35 +00:00
'Origin': 'http://example.com'})
2020-05-10 08:43:13 +00:00
assert_response(rc)
2020-12-26 16:48:19 +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)
2020-12-26 16:48:19 +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)
2020-12-26 16:48:19 +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)
2020-12-26 16:48:19 +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)
2020-12-26 16:48:19 +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
"""
2020-05-10 18:00:19 +00:00
default_conf.update({"api_server": {"enabled": True,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"username": "TestUser",
"password": "testPass",
}})
2019-04-04 05:12:58 +00:00
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
2020-12-31 10:01:50 +00:00
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):
2020-12-31 10:01:50 +00:00
thread_mock = mocker.patch('freqtrade.rpc.api_server.uvicorn_threaded.threading.Thread')
2020-12-27 13:57:01 +00:00
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1'))
assert thread_mock.call_count == 0
s.install_signal_handlers()
# Original implementation starts a thread - make sure that's not the case
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):
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'))
assert serve_mock.call_count == 0
s.install_signal_handlers()
# Original implementation starts a thread - make sure that's not the case
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):
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'))
assert serve_mock.call_count == 0
s.install_signal_handlers()
# Original implementation starts a thread - make sure that's not the case
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):
default_conf.update({"api_server": {"enabled": True,
"listen_ip_address": "127.0.0.1",
2019-11-23 14:20:53 +00:00
"listen_port": 8080,
"username": "TestUser",
"password": "testPass",
}})
2019-05-14 05:07:51 +00:00
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
server_inst_mock = MagicMock()
server_inst_mock.run_in_thread = MagicMock()
server_inst_mock.run = MagicMock()
server_mock = MagicMock(return_value=server_inst_mock)
2020-12-31 10:01:50 +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()
2019-05-14 05:07:51 +00:00
apiserver._config.update({"api_server": {"enabled": True,
"listen_ip_address": "0.0.0.0",
2019-11-23 14:20:53 +00:00
"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)
2019-05-14 05:07:51 +00:00
assert log_has("SECURITY WARNING - Local Rest Server listening to external connections",
2019-08-11 18:17:22 +00:00
caplog)
2019-05-14 05:07:51 +00:00
assert log_has("SECURITY WARNING - This is insecure please set to your loopback,"
2019-08-11 18:17:22 +00:00
"e.g 127.0.0.1 in config.json", caplog)
assert log_has("SECURITY WARNING - No password for local REST Server defined. "
2019-08-11 18:17:22 +00:00
"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()
2020-12-31 10:01:50 +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):
default_conf.update({"api_server": {"enabled": True,
"listen_ip_address": "127.0.0.1",
2019-11-23 14:20:53 +00:00
"listen_port": 8080,
"username": "TestUser",
"password": "testPass",
}})
2019-05-18 07:54:36 +00:00
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
2020-12-27 09:56:19 +00:00
server_mock = MagicMock()
server_mock.cleanup = MagicMock()
2020-12-31 10:01:50 +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)
2020-12-26 15:33:13 +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
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() == {
'status': 'No more entries will occur from now. Run /reload_config to reset.'}
assert ftbot.config['max_open_trades'] == 0
rc = client_post(client, f"{BASE_URI}/stopentry")
assert_response(rc)
assert rc.json() == {
'status': 'No more entries will occur from now. Run /reload_config to reset.'}
2021-07-23 11:34:18 +00:00
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
2021-07-23 11:34:18 +00:00
ftbot.config['dry_run'] = False
2019-05-11 07:10:54 +00:00
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance)
2021-09-19 23:44:12 +00:00
mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers)
2019-07-03 18:07:26 +00:00
mocker.patch('freqtrade.exchange.Exchange.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
assert response['currencies'][0] == {
2019-05-11 07:10:54 +00:00
'currency': 'BTC',
'free': 12.0,
2019-05-11 07:10:54 +00:00
'balance': 12.0,
'used': 0.0,
2019-11-15 05:33:07 +00:00
'est_stake': 12.0,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
2019-05-11 07:10:54 +00:00
}
2021-09-19 23:44:12 +00:00
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
2021-09-20 02:24:22 +00:00
@pytest.mark.parametrize('is_short', [True, False])
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(
'freqtrade.exchange.Exchange',
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,
markets=PropertyMock(return_value=markets)
)
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
2021-07-23 11:34:18 +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):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
2020-10-17 15:58:07 +00:00
rc = client_get(client, f"{BASE_URI}/locks")
assert_response(rc)
2020-12-26 16:33:27 +00:00
assert 'locks' in rc.json()
2020-10-17 15:58:07 +00:00
2020-12-26 16:33:27 +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
2020-10-26 06:36:25 +00:00
PairLocks.lock_pair('ETH/BTC', datetime.now(timezone.utc) + timedelta(minutes=4), 'randreason')
PairLocks.lock_pair('XRP/BTC', datetime.now(timezone.utc) + timedelta(minutes=20), 'deadbeef')
2020-10-17 15:58:07 +00:00
rc = client_get(client, f"{BASE_URI}/locks")
assert_response(rc)
2020-12-26 16:33:27 +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)
assert rc.json()['lock_count'] == 1
rc = client_post(client, f"{BASE_URI}/locks/delete",
data={"pair": "XRP/BTC"})
2021-03-01 18:50:39 +00:00
assert_response(rc)
assert rc.json()['lock_count'] == 0
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()
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'
2021-12-09 05:34:07 +00:00
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
2021-11-23 06:35:26 +00:00
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(
'freqtrade.exchange.Exchange',
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,
markets=PropertyMock(return_value=markets)
)
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)
2020-12-26 16:33:27 +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.utcnow().date())
2019-05-11 12:05:25 +00:00
2021-09-20 02:24:22 +00:00
@pytest.mark.parametrize('is_short', [True, False])
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)
2020-04-07 17:50:13 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets)
)
rc = client_get(client, f"{BASE_URI}/trades")
assert_response(rc)
2022-06-18 15:44:15 +00:00
assert len(rc.json()) == 4
2020-12-26 16:33:27 +00:00
assert rc.json()['trades_count'] == 0
2021-04-20 19:28:13 +00:00
assert rc.json()['total_trades'] == 0
2022-06-18 15:44:15 +00:00
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)
Trade.query.session.flush()
2020-04-07 17:50:13 +00:00
rc = client_get(client, f"{BASE_URI}/trades")
assert_response(rc)
2020-12-26 16:33:27 +00:00
assert len(rc.json()['trades']) == 2
assert rc.json()['trades_count'] == 2
2021-04-20 19:28:13 +00:00
assert rc.json()['total_trades'] == 2
2021-11-14 08:52:38 +00:00
assert rc.json()['trades'][0]['is_short'] == is_short
rc = client_get(client, f"{BASE_URI}/trades?limit=1")
assert_response(rc)
2020-12-26 16:33:27 +00:00
assert len(rc.json()['trades']) == 1
assert rc.json()['trades_count'] == 1
2021-04-20 19:28:13 +00:00
assert rc.json()['total_trades'] == 2
2020-04-07 17:50:13 +00:00
2021-11-14 01:22:43 +00:00
@pytest.mark.parametrize('is_short', [True, False])
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(
'freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets),
fetch_ticker=ticker,
)
rc = client_get(client, f"{BASE_URI}/trade/3")
assert_response(rc, 404)
assert rc.json()['detail'] == 'Trade not found.'
2022-02-26 16:08:35 +00:00
Trade.query.session.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)
assert rc.json()['trade_id'] == 3
2021-11-14 08:52:38 +00:00
assert rc.json()['is_short'] == is_short
2021-11-14 01:22:43 +00:00
@pytest.mark.parametrize('is_short', [True, False])
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(
'freqtrade.exchange.Exchange',
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
2021-07-23 11:34:18 +00:00
ftbot.strategy.order_types['stoploss_on_exchange'] = True
2020-08-04 12:41:38 +00:00
trades = Trade.query.all()
2020-08-04 17:43:05 +00:00
trades[1].stoploss_order_id = '1234'
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)
2020-12-26 16:33:27 +00:00
assert rc.json()['result_msg'] == 'Deleted trade 1. Closed 1 open orders.'
2020-08-04 12:41:38 +00:00
assert len(trades) - 1 == len(Trade.query.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
2020-08-04 12:41:38 +00:00
assert len(trades) - 1 == len(Trade.query.all())
rc = client_delete(client, f"{BASE_URI}/trades/2")
assert_response(rc)
2020-12-26 16:33:27 +00:00
assert rc.json()['result_msg'] == 'Deleted trade 2. Closed 2 open orders.'
2020-08-04 12:41:38 +00:00
assert len(trades) - 2 == len(Trade.query.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
2020-08-14 17:36:12 +00:00
def test_api_logs(botclient):
2021-07-23 11:34:18 +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
assert 'logs' in rc.json()
2020-08-14 17:36:12 +00:00
# Using a fixed comparison here would make this test fail!
2020-12-26 16:33:27 +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
2020-12-26 16:33:27 +00:00
assert isinstance(rc.json()['logs'][0], list)
2020-08-14 17:36:12 +00:00
# date
2020-12-26 16:33:27 +00:00
assert isinstance(rc.json()['logs'][0][0], str)
2020-08-14 17:36:12 +00:00
# created_timestamp
2020-12-26 16:33:27 +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
assert 'logs' in rc1.json()
2020-08-14 18:12:59 +00:00
# Using a fixed comparison here would make this test fail!
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()}")
assert rc1.json()['log_count'] > 2
2021-01-16 09:01:31 +00:00
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(
'freqtrade.exchange.Exchange',
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,
markets=PropertyMock(return_value=markets)
)
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
2021-11-27 08:44:06 +00:00
@pytest.mark.parametrize('is_short,expected', [
(
2021-11-16 06:29:40 +00:00
True,
2021-11-18 19:20:01 +00:00
{'best_pair': 'ETC/BTC', 'best_rate': -0.5, 'best_pair_profit_ratio': -0.005,
'profit_all_coin': 45.561959,
'profit_all_fiat': 562462.39126200, 'profit_all_percent_mean': 66.41,
2021-11-16 06:29:40 +00:00
'profit_all_ratio_mean': 0.664109545, 'profit_all_percent_sum': 398.47,
'profit_all_ratio_sum': 3.98465727, 'profit_all_percent': 4.56,
'profit_all_ratio': 0.04556147, 'profit_closed_coin': -0.00673913,
2021-11-16 06:29:40 +00:00
'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,
2022-06-18 14:53:25 +00:00
'profit_factor': 0.0, 'trading_volume': 91.074,
}
),
2021-11-27 08:44:06 +00:00
(
2021-11-16 06:29:40 +00:00
False,
2021-11-18 19:20:01 +00:00
{'best_pair': 'XRP/BTC', 'best_rate': 1.0, 'best_pair_profit_ratio': 0.01,
'profit_all_coin': -45.79641127,
'profit_all_fiat': -565356.69712815, 'profit_all_percent_mean': -66.41,
2021-11-16 06:29:40 +00:00
'profit_all_ratio_mean': -0.6641100666666667, 'profit_all_percent_sum': -398.47,
'profit_all_ratio_sum': -3.9846604, 'profit_all_percent': -4.58,
'profit_all_ratio': -0.045796261934205953, 'profit_closed_coin': 0.00073913,
2021-11-16 06:29:40 +00:00
'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,
2022-06-18 14:53:25 +00:00
'profit_factor': None, 'trading_volume': 91.074,
}
),
2021-11-27 08:44:06 +00:00
(
2021-11-16 06:29:40 +00:00
None,
2021-11-18 19:20:01 +00:00
{'best_pair': 'XRP/BTC', 'best_rate': 1.0, 'best_pair_profit_ratio': 0.01,
'profit_all_coin': -14.94732578,
'profit_all_fiat': -184524.7367541, 'profit_all_percent_mean': 0.08,
2021-11-16 06:29:40 +00:00
'profit_all_ratio_mean': 0.000835751666666662, 'profit_all_percent_sum': 0.5,
'profit_all_ratio_sum': 0.005014509999999972, 'profit_all_percent': -1.49,
'profit_all_ratio': -0.014947184841095841, 'profit_closed_coin': -0.00542913,
2021-11-16 06:29:40 +00:00
'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,
2022-06-18 14:53:25 +00:00
'profit_factor': 0.02775724835771106, 'trading_volume': 91.074,
}
)
2021-11-27 08:44:06 +00:00
])
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(
'freqtrade.exchange.Exchange',
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,
markets=PropertyMock(return_value=markets)
)
2019-05-25 12:13:59 +00:00
rc = client_get(client, f"{BASE_URI}/profit")
assert_response(rc, 200)
2020-12-26 15:43:15 +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() == {
'avg_duration': ANY,
2021-11-16 06:29:40 +00:00
'best_pair': expected['best_pair'],
2021-11-18 19:20:01 +00:00
'best_pair_profit_ratio': expected['best_pair_profit_ratio'],
2021-11-16 06:29:40 +00:00
'best_rate': expected['best_rate'],
'first_trade_date': ANY,
'first_trade_timestamp': ANY,
'latest_trade_date': '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,
2021-11-16 06:29:40 +00:00
'winning_trades': expected['winning_trades'],
'losing_trades': expected['losing_trades'],
'profit_factor': expected['profit_factor'],
'max_drawdown': ANY,
'max_drawdown_abs': ANY,
2022-06-18 14:53:25 +00:00
'trading_volume': expected['trading_volume'],
2021-11-15 03:01:08 +00:00
}
2019-05-11 12:05:25 +00:00
2021-11-14 01:22:43 +00:00
@pytest.mark.parametrize('is_short', [True, False])
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(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
fetch_ticker=ticker,
get_fee=fee,
markets=PropertyMock(return_value=markets)
)
rc = client_get(client, f"{BASE_URI}/stats")
assert_response(rc, 200)
2020-12-26 15:43:15 +00:00
assert 'durations' in rc.json()
2022-03-24 19:33:47 +00:00
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)
2020-12-26 15:43:15 +00:00
assert 'durations' in rc.json()
2022-03-24 19:33:47 +00:00
assert 'exit_reasons' in rc.json()
2020-12-07 14:07:08 +00:00
2020-12-26 15:43:15 +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(
pair='LTC/ETH',
amount=1,
exchange='binance',
stake_amount=1,
open_rate=0.245441,
open_order_id="123456",
is_open=False,
fee_close=fee.return_value,
fee_open=fee.return_value,
close_rate=0.265441,
)
trade.close_profit = trade.calc_profit_ratio(trade.close_rate)
trade.close_profit_abs = trade.calc_profit(trade.close_rate)
Trade.query.session.add(trade)
2019-05-11 12:05:25 +00:00
trade = Trade(
pair='XRP/ETH',
amount=5,
stake_amount=1,
exchange='binance',
open_rate=0.412,
open_order_id="123456",
is_open=False,
fee_close=fee.return_value,
fee_open=fee.return_value,
close_rate=0.391
)
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
Trade.query.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
2021-10-31 09:42:42 +00:00
assert rc.json() == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61, 'profit_pct': 7.61,
2022-08-10 18:56:04 +00:00
'profit_ratio': 0.07609203, 'profit_abs': 0.0187228},
2021-10-31 09:42:42 +00:00
{'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
@pytest.mark.parametrize(
'is_short,current_rate,open_order_id,open_trade_value',
[(True, 1.098e-05, 'dry_run_buy_short_12345', 15.0911775),
(False, 1.099e-05, 'dry_run_buy_long_12345', 15.1668225)])
def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
current_rate, open_order_id, 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(
'freqtrade.exchange.Exchange',
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] == {
'amount': 123.0,
'amount_requested': 123.0,
2020-12-26 16:33:27 +00:00
'close_date': None,
'close_timestamp': None,
'close_profit': None,
'close_profit_pct': None,
'close_profit_abs': None,
'close_rate': None,
2021-04-07 15:10:20 +00:00
'current_profit': ANY,
'current_profit_pct': ANY,
'current_profit_abs': ANY,
'profit_ratio': ANY,
'profit_pct': ANY,
'profit_abs': ANY,
'profit_fiat': ANY,
'current_rate': current_rate,
2020-12-26 16:33:27 +00:00
'open_date': ANY,
'open_timestamp': ANY,
'open_order': None,
2021-04-07 15:10:20 +00:00
'open_rate': 0.123,
2020-12-26 16:33:27 +00:00
'pair': 'ETH/BTC',
2022-04-09 14:42:18 +00:00
'base_currency': 'ETH',
'quote_currency': 'BTC',
2020-12-26 16:33:27 +00:00
'stake_amount': 0.001,
2022-12-27 12:46:27 +00:00
'max_stake_amount': ANY,
2021-04-07 15:10:20 +00:00
'stop_loss_abs': ANY,
'stop_loss_pct': ANY,
'stop_loss_ratio': ANY,
2020-12-26 16:33:27 +00:00
'stoploss_order_id': None,
'stoploss_last_update': ANY,
'stoploss_last_update_timestamp': ANY,
2021-04-07 15:10:20 +00:00
'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,
2020-12-26 16:33:27 +00:00
'trade_id': 1,
2021-04-07 15:10:20 +00:00
'close_rate_requested': ANY,
2020-12-26 16:33:27 +00:00
'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,
2021-04-07 15:10:20 +00:00
'max_rate': ANY,
'min_rate': ANY,
'open_order_id': open_order_id,
2021-04-07 15:10:20 +00:00
'open_rate_requested': ANY,
'open_trade_value': open_trade_value,
2022-03-24 19:33:47 +00:00
'exit_reason': None,
2022-04-03 09:17:01 +00:00
'exit_order_status': None,
'strategy': CURRENT_TEST_STRATEGY,
2021-11-21 08:51:16 +00:00
'enter_tag': None,
2020-12-26 16:33:27 +00:00
'timeframe': 5,
2021-04-20 10:54:22 +00:00
'exchange': 'binance',
'leverage': 1.0,
'interest_rate': 0.0,
2022-05-22 06:54:27 +00:00
'liquidation_price': None,
'funding_fees': None,
'trading_mode': ANY,
'orders': [ANY],
2021-04-07 15:10:20 +00:00
}
2019-05-11 12:05:25 +00:00
2021-07-18 03:58:54 +00:00
mocker.patch('freqtrade.exchange.Exchange.get_rate',
2021-02-22 10:44:39 +00:00
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
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
2022-04-27 11:13:11 +00:00
assert resp_values[0]['profit_abs'] is None
2021-02-22 10:44:39 +00:00
2019-05-11 12:05:25 +00:00
def test_api_version(botclient):
2021-07-23 11:34:18 +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):
2021-07-23 11:34:18 +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!
2020-12-26 16:33:27 +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
2019-05-25 12:13:59 +00:00
rc = client_post(client, f"{BASE_URI}/blacklist",
data={"blacklist": ["ETH/BTC"]})
2019-05-11 12:05:25 +00:00
assert_response(rc)
2020-12-26 16:33:27 +00:00
assert rc.json() == {"blacklist": ["DOGE/BTC", "HOT/BTC", "ETH/BTC"],
"blacklist_expanded": ["ETH/BTC"],
"length": 3,
"method": ["StaticPairList"],
"errors": {},
}
2019-05-11 12:05:25 +00:00
rc = client_post(client, f"{BASE_URI}/blacklist",
data={"blacklist": ["XRP/.*"]})
assert_response(rc)
2020-12-26 16:33:27 +00:00
assert rc.json() == {"blacklist": ["DOGE/BTC", "HOT/BTC", "ETH/BTC", "XRP/.*"],
2021-10-01 09:10:43 +00:00
"blacklist_expanded": ["ETH/BTC", "XRP/BTC", "XRP/USDT"],
2020-12-26 16:33:27 +00:00
"length": 4,
"method": ["StaticPairList"],
"errors": {},
}
rc = client_delete(client, f"{BASE_URI}/blacklist?pairs_to_delete=DOGE/BTC")
assert_response(rc)
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)
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(
client,
f"{BASE_URI}/blacklist?pairs_to_delete=HOT/BTC&pairs_to_delete=ETH/BTC")
assert_response(rc)
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):
2021-07-23 11:34:18 +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() == {
"whitelist": ['ETH/BTC', 'LTC/BTC', 'XRP/BTC', 'NEO/BTC'],
"length": 4,
"method": ["StaticPairList"]
2021-08-06 22:19:36 +00:00
}
2019-05-15 04:51:23 +00:00
@pytest.mark.parametrize('endpoint', [
'forcebuy',
2022-04-07 18:33:54 +00:00
'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
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
2022-04-08 11:39:41 +00:00
ftbot.config['force_entry_enable'] = True
2019-05-15 04:51:23 +00:00
fbuy_mock = MagicMock(return_value=None)
2022-01-26 18:08:37 +00:00
mocker.patch("freqtrade.rpc.RPC._rpc_force_entry", fbuy_mock)
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
2019-05-15 04:51:23 +00:00
fbuy_mock = MagicMock(return_value=Trade(
2022-04-09 14:42:18 +00:00
pair='ETH/BTC',
2019-05-15 04:51:23 +00:00
amount=1,
amount_requested=1,
2021-04-20 10:54:22 +00:00
exchange='binance',
2019-05-15 04:51:23 +00:00
stake_amount=1,
open_rate=0.245441,
open_order_id="123456",
open_date=datetime.utcnow(),
is_open=False,
is_short=False,
2019-05-15 04:51:23 +00:00
fee_close=fee.return_value,
fee_open=fee.return_value,
close_rate=0.265441,
2021-01-01 18:38:28 +00:00
id=22,
timeframe=5,
strategy=CURRENT_TEST_STRATEGY,
trading_mode=TradingMode.SPOT
2019-05-15 04:51:23 +00:00
))
2022-01-26 18:08:37 +00:00
mocker.patch("freqtrade.rpc.RPC._rpc_force_entry", fbuy_mock)
2019-05-15 04:51:23 +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() == {
'amount': 1.0,
'amount_requested': 1.0,
2021-01-01 18:38:28 +00:00
'trade_id': 22,
2020-12-26 16:33:27 +00:00
'close_date': None,
'close_timestamp': None,
'close_rate': 0.265441,
'open_date': ANY,
'open_timestamp': ANY,
'open_rate': 0.245441,
2022-04-09 14:42:18 +00:00
'pair': 'ETH/BTC',
'base_currency': 'ETH',
'quote_currency': 'BTC',
2020-12-26 16:33:27 +00:00
'stake_amount': 1,
2022-12-27 12:46:27 +00:00
'max_stake_amount': ANY,
2020-12-26 16:33:27 +00:00
'stop_loss_abs': None,
'stop_loss_pct': None,
'stop_loss_ratio': None,
'stoploss_order_id': 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,
2021-04-02 12:35:19 +00:00
'profit_fiat': None,
2020-12-26 16:33:27 +00:00
'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,
2020-12-26 16:33:27 +00:00
'max_rate': None,
'min_rate': None,
'open_order_id': '123456',
'open_rate_requested': None,
'open_trade_value': 0.24605460,
2022-03-24 19:33:47 +00:00
'exit_reason': None,
2022-04-03 09:17:01 +00:00
'exit_order_status': None,
'strategy': CURRENT_TEST_STRATEGY,
2021-11-21 08:51:16 +00:00
'enter_tag': None,
2021-01-01 18:38:28 +00:00
'timeframe': 5,
2021-04-20 10:54:22 +00:00
'exchange': 'binance',
'leverage': None,
'interest_rate': None,
2022-05-22 06:54:27 +00:00
'liquidation_price': None,
'funding_fees': None,
'trading_mode': 'spot',
'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(
'freqtrade.exchange.Exchange',
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),
2022-08-04 14:28:36 +00:00
_is_dry_limit_order_filled=MagicMock(return_value=True),
2019-05-15 05:00:17 +00:00
)
patch_get_signal(ftbot)
2019-05-15 05:00:17 +00:00
2022-04-10 13:56:29 +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"}
Trade.query.session.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
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)
assert rc.json() == {'result': 'Created sell order for trade 5.'}
Trade.query.session.rollback()
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
2022-04-10 13:56:29 +00:00
rc = client_post(client, f"{BASE_URI}/forceexit",
data={"tradeid": "5"})
2019-05-15 05:00:17 +00:00
assert_response(rc)
2022-08-04 14:28:36 +00:00
assert rc.json() == {'result': 'Created sell order for trade 5.'}
Trade.query.session.rollback()
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
2020-06-15 06:47:43 +00:00
timeframe = '5m'
2020-12-15 19:49:46 +00:00
amount = 3
# No pair
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
rc = client_get(client,
f"{BASE_URI}/pair_candles?pair=XRP%2FBTC")
2020-12-26 19:05:27 +00:00
assert_response(rc, 422)
rc = client_get(client,
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
assert_response(rc)
2020-12-26 19:05:27 +00:00
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
2020-06-15 06:47:43 +00:00
ohlcv_history['sma'] = 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)
2020-06-15 06:47:43 +00:00
rc = client_get(client,
2020-07-02 05:10:56 +00:00
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
2020-06-15 06:47:43 +00:00
assert_response(rc)
2020-12-26 19:05:27 +00:00
assert 'strategy' in rc.json()
assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
2020-12-26 19:05:27 +00:00
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 rc.json()['data_start'] == '2017-11-26 08:50:00+00:00'
assert rc.json()['data_start_ts'] == 1511686200000
assert rc.json()['data_stop'] == '2017-11-26 09:00:00+00:00'
assert rc.json()['data_stop_ts'] == 1511686800000
assert isinstance(rc.json()['columns'], list)
assert set(rc.json()['columns']) == {
'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'
}
2020-12-26 19:05:27 +00:00
assert 'pair' in rc.json()
assert rc.json()['pair'] == 'XRP/BTC'
2020-06-15 06:47:43 +00:00
2020-12-26 19:05:27 +00:00
assert 'data' in rc.json()
assert len(rc.json()['data']) == amount
2020-06-15 06:47:43 +00:00
2020-12-26 19:05:27 +00:00
assert (rc.json()['data'] ==
[['2017-11-26 08:50:00', 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-26 08:55:00', 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],
2020-12-15 19:49:46 +00:00
['2017-11-26 09:00:00', 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]
2020-12-15 19:49:46 +00:00
2020-06-15 06:47:43 +00:00
])
2022-02-11 16:02:04 +00:00
ohlcv_history['exit_long'] = ohlcv_history['exit_long'].astype('float64')
ohlcv_history.at[0, 'exit_long'] = float('inf')
2022-02-09 05:36:17 +00:00
ohlcv_history['date1'] = ohlcv_history['date']
ohlcv_history.at[0, 'date1'] = pd.NaT
2022-02-11 16:02:04 +00:00
ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history, CandleType.SPOT)
2022-02-09 05:36:17 +00:00
rc = client_get(client,
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
assert_response(rc)
assert (rc.json()['data'] ==
[['2017-11-26 08:50:00', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, 0.0877869,
2022-02-11 16:02:04 +00:00
None, 0, None, 0, 0, None, 1511686200000, None, None, None, None],
2022-02-09 05:36:17 +00:00
['2017-11-26 08:55:00', 8.88e-05, 8.942e-05, 8.88e-05,
2022-02-11 16:02:04 +00:00
8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0.0, 0, 0, '2017-11-26 08:55:00',
1511686500000, 8.893e-05, None, None, None],
2022-02-09 05:36:17 +00:00
['2017-11-26 09:00:00', 8.891e-05, 8.893e-05, 8.875e-05, 8.877e-05,
2022-02-11 16:02:04 +00:00
0.7039405, 8.885e-05, 0, 0.0, 0, 0, '2017-11-26 09:00:00', 1511686800000,
None, None, None, None]
2022-02-09 05:36:17 +00:00
])
2020-06-23 04:49:53 +00:00
2020-09-11 18:45:59 +00:00
def test_api_pair_history(botclient, ohlcv_history):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
2020-09-11 18:45:59 +00:00
timeframe = '5m'
# No pair
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
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
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
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
# Working
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}")
2020-09-11 18:45:59 +00:00
assert_response(rc, 200)
2020-12-26 19:05:27 +00:00
assert rc.json()['length'] == 289
assert len(rc.json()['data']) == rc.json()['length']
assert 'columns' in rc.json()
assert 'data' in rc.json()
assert rc.json()['pair'] == 'UNITTEST/BTC'
assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
2020-12-26 19:05:27 +00:00
assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00'
assert rc.json()['data_start_ts'] == 1515628800000
assert rc.json()['data_stop'] == '2018-01-12 00:00:00+00:00'
assert rc.json()['data_stop_ts'] == 1515715200000
2020-09-11 18:45:59 +00:00
# No data found
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
f"&timerange=20200111-20200112&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 502)
assert rc.json()['error'] == ("Error querying /api/v1/pair_history: "
"No data for UNITTEST/BTC, 5m in 20200111-20200112 found.")
2020-09-11 18:45:59 +00:00
2020-06-23 04:49:53 +00:00
def test_api_plot_config(botclient):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
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 = {
2021-07-20 16:56:03 +00:00
'main_plot': {'sma': {}},
'subplots': {'RSI': {'rsi': {'color': 'red'}}}
}
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
2020-12-26 19:05:27 +00:00
assert isinstance(rc.json()['main_plot'], dict)
assert isinstance(rc.json()['subplots'], dict)
2021-07-23 11:34:18 +00:00
ftbot.strategy.plot_config = {'main_plot': {'sma': {}}}
rc = client_get(client, f"{BASE_URI}/plot_config")
assert_response(rc)
assert isinstance(rc.json()['main_plot'], dict)
assert isinstance(rc.json()['subplots'], dict)
2020-07-31 05:31:20 +00:00
2022-10-14 14:59:55 +00:00
def test_api_strategies(botclient, tmpdir):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
2022-10-14 14:59:55 +00:00
ftbot.config['user_data_dir'] = Path(tmpdir)
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
assert rc.json() == {'strategies': [
'HyperoptableStrategy',
'HyperoptableStrategyV2',
2021-09-19 23:44:12 +00:00
'InformativeDecoratorTest',
2021-08-26 17:38:41 +00:00
'StrategyTestV2',
2021-09-21 17:14:14 +00:00
'StrategyTestV3',
'StrategyTestV3CustomEntryPrice',
'StrategyTestV3Futures',
2022-09-17 15:46:47 +00:00
'freqai_rl_test_strat',
'freqai_test_classifier',
2022-10-30 17:08:10 +00:00
'freqai_test_multimodel_classifier_strat',
'freqai_test_multimodel_strat',
'freqai_test_strat'
2021-08-06 22:19:36 +00:00
]}
2020-08-01 15:49:59 +00:00
2020-09-17 05:53:22 +00:00
def test_api_strategy(botclient):
2021-07-23 11:34:18 +00:00
ftbot, client = botclient
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)
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()
2020-12-26 19:05:27 +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)
2020-09-17 05:53:22 +00:00
def test_api_freqaimodels(botclient, tmpdir, mocker):
2022-12-20 06:23:41 +00:00
ftbot, client = botclient
ftbot.config['user_data_dir'] = Path(tmpdir)
mocker.patch(
"freqtrade.resolvers.freqaimodel_resolver.FreqaiModelResolver.search_all_objects",
return_value=[
{'name': 'LightGBMClassifier'},
{'name': 'LightGBMClassifierMultiTarget'},
{'name': 'LightGBMRegressor'},
{'name': 'LightGBMRegressorMultiTarget'},
{'name': 'ReinforcementLearner'},
{'name': 'ReinforcementLearner_multiproc'},
{'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)
assert rc.json() == {'freqaimodels': [
'LightGBMClassifier',
'LightGBMClassifierMultiTarget',
'LightGBMRegressor',
'LightGBMRegressorMultiTarget',
'ReinforcementLearner',
'ReinforcementLearner_multiproc',
'XGBoostClassifier',
'XGBoostRFClassifier',
'XGBoostRFRegressor',
'XGBoostRegressor',
'XGBoostRegressorMultiTarget'
]}
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)
assert rc.json()['length'] == 13
2020-12-26 19:05:27 +00:00
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)
2020-12-26 19:05:27 +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)
2020-12-26 19:05:27 +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)
2020-12-26 19:05:27 +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
ftbot.config['trading_mode'] = 'futures'
rc = client_get(
2021-12-08 13:35:15 +00:00
client, f"{BASE_URI}/available_pairs?timeframe=1h")
assert_response(rc)
assert rc.json()['length'] == 1
2023-01-13 19:44:32 +00:00
assert rc.json()['pairs'] == ['XRP/USDT:USDT']
rc = client_get(
client, f"{BASE_URI}/available_pairs?timeframe=1h&candletype=mark")
assert_response(rc)
assert rc.json()['length'] == 2
2023-01-13 19:44:32 +00:00
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):
ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/sysinfo")
assert_response(rc)
result = rc.json()
assert 'cpu_pct' in result
assert 'ram_pct' in result
2022-01-20 06:11:48 +00:00
def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir):
2021-04-01 17:59:49 +00:00
ftbot, client = botclient
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
rc = client_get(client, f"{BASE_URI}/backtest")
# Backtest prevented in default mode
assert_response(rc, 502)
ftbot.config['runmode'] = RunMode.WEBSERVER
2021-04-01 17:59:49 +00:00
# Backtesting not started yet
rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
assert result['status'] == 'not_started'
assert not result['running']
assert result['status_msg'] == 'Backtest not yet executed'
assert result['progress'] == 0
# Reset backtesting
rc = client_delete(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
assert result['status'] == 'reset'
assert not result['running']
assert result['status_msg'] == 'Backtest reset'
2022-01-20 06:11:48 +00:00
ftbot.config['export'] = 'trades'
2022-04-30 18:08:40 +00:00
ftbot.config['backtest_cache'] = 'day'
2022-01-20 06:11:48 +00:00
ftbot.config['user_data_dir'] = Path(tmpdir)
ftbot.config['exportfilename'] = Path(tmpdir) / "backtest_results"
ftbot.config['exportfilename'].mkdir()
2021-04-01 17:59:49 +00:00
# start backtesting
data = {
"strategy": CURRENT_TEST_STRATEGY,
2021-04-01 17:59:49 +00:00
"timeframe": "5m",
"timerange": "20180110-20180111",
"max_open_trades": 3,
"stake_amount": 100,
"dry_run_wallet": 1000,
"enable_protections": False
}
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
2021-04-01 17:59:49 +00:00
assert_response(rc)
result = rc.json()
assert result['status'] == 'running'
assert result['progress'] == 0
assert result['running']
assert result['status_msg'] == 'Backtest started'
rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
assert result['status'] == 'ended'
assert not result['running']
assert result['status_msg'] == 'Backtest ended'
assert result['progress'] == 1
assert result['backtest_result']
rc = client_get(client, f"{BASE_URI}/backtest/abort")
assert_response(rc)
result = rc.json()
assert result['status'] == 'not_running'
assert not result['running']
assert result['status_msg'] == 'Backtest ended'
2021-07-06 19:04:52 +00:00
# Simulate running backtest
ApiServer._bgtask_running = True
rc = client_get(client, f"{BASE_URI}/backtest/abort")
assert_response(rc)
result = rc.json()
assert result['status'] == 'stopping'
assert not result['running']
assert result['status_msg'] == 'Backtest ended'
# Get running backtest...
rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
assert result['status'] == 'running'
assert result['running']
assert result['step'] == "backtest"
assert result['status_msg'] == "Backtest running"
# Try delete with task still running
rc = client_delete(client, f"{BASE_URI}/backtest")
assert_response(rc)
result = rc.json()
assert result['status'] == 'running'
# Post to backtest that's still running
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
2021-07-06 19:04:52 +00:00
assert_response(rc, 502)
result = rc.json()
assert 'Bot Background task already running' in result['error']
ApiServer._bgtask_running = False
2022-01-20 06:11:48 +00:00
# Rerun backtest (should get previous result)
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
2022-01-20 06:11:48 +00:00
assert_response(rc)
result = rc.json()
assert log_has_re('Reusing result of previous backtest.*', caplog)
2022-04-30 18:08:40 +00:00
data['stake_amount'] = 101
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy',
side_effect=DependencyException())
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
2022-04-30 18:08:40 +00:00
assert log_has("Backtesting caused an error: ", caplog)
2021-04-01 17:59:49 +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()
assert result['status'] == 'reset'
assert not result['running']
assert result['status_msg'] == 'Backtest reset'
# Disallow base64 strategies
data['strategy'] = "xx:cHJpbnQoImhlbGxvIHdvcmxkIik="
rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert_response(rc, 500)
2022-04-12 04:28:37 +00:00
def test_api_backtest_history(botclient, mocker, testdatadir):
ftbot, client = botclient
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, 502)
ftbot.config['user_data_dir'] = testdatadir
ftbot.config['runmode'] = RunMode.WEBSERVER
rc = client_get(client, f"{BASE_URI}/backtest/history")
assert_response(rc)
result = rc.json()
assert len(result) == 3
2022-04-12 04:28:37 +00:00
fn = result[0]['filename']
assert fn == "backtest-result_multistrat.json"
2022-04-13 04:47:39 +00:00
strategy = result[0]['strategy']
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
assert result2['status'] == 'ended'
assert not result2['running']
assert result2['progress'] == 1
# Only one strategy loaded - even though we use multiresult
assert len(result2['backtest_result']['strategy']) == 1
assert result2['backtest_result']['strategy'][strategy]
2022-04-12 04:28:37 +00:00
def test_health(botclient):
ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/health")
assert_response(rc)
ret = rc.json()
assert ret['last_process_ts'] == 0
assert ret['last_process'] == '1970-01-01T00:00:00+00:00'
2022-09-08 19:58:28 +00:00
def test_api_ws_subscribe(botclient, mocker):
ftbot, client = botclient
ws_url = f"/api/v1/message/ws?token={_TEST_WS_TOKEN}"
2022-09-13 18:42:32 +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:
ws.send_json({'type': 'subscribe', 'data': ['whitelist']})
2022-11-21 19:21:40 +00:00
time.sleep(1)
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:
ws.send_json({'type': 'subscribe', 'data': 'whitelist'})
2022-11-21 19:21:40 +00:00
time.sleep(1)
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)
ftbot, client = botclient
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)
assert response['type'] == "whitelist"
# 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)
assert response['type'] == "analyzed_df"
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)
assert response['type'] == "analyzed_df"
def test_api_ws_send_msg(default_conf, mocker, caplog):
try:
caplog.set_level(logging.DEBUG)
default_conf.update({"api_server": {"enabled": True,
"listen_ip_address": "127.0.0.1",
2022-09-13 18:42:32 +00:00
"listen_port": 8080,
"CORS_origins": ['http://example.com'],
"username": _TEST_USER,
"password": _TEST_PASS,
"ws_token": _TEST_WS_TOKEN
}})
2022-09-13 18:42:32 +00:00
mocker.patch('freqtrade.rpc.telegram.Updater')
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()