Merge pull request #5243 from freqtrade/feat/webservermode_progress

Introduce webserver mode subcommand
This commit is contained in:
Matthias
2021-07-18 10:48:55 +02:00
committed by GitHub
20 changed files with 595 additions and 34 deletions

View File

@@ -13,7 +13,7 @@ from freqtrade.commands import (start_convert_data, start_create_userdir, start_
start_list_data, start_list_exchanges, start_list_hyperopts,
start_list_markets, start_list_strategies, start_list_timeframes,
start_new_hyperopt, start_new_strategy, start_show_trades,
start_test_pairlist, start_trading)
start_test_pairlist, start_trading, start_webserver)
from freqtrade.commands.deploy_commands import (clean_ui_subdir, download_and_install_ui,
get_ui_download_url, read_ui_version)
from freqtrade.configuration import setup_utils_configuration
@@ -58,6 +58,18 @@ def test_start_trading_fail(mocker, caplog):
assert log_has('Fatal exception!', caplog)
def test_start_webserver(mocker, caplog):
api_server_mock = mocker.patch("freqtrade.rpc.api_server.ApiServer", )
args = [
'webserver',
'-c', 'config_bittrex.json.example'
]
start_webserver(get_args(args))
assert api_server_mock.call_count == 1
def test_list_exchanges(capsys):
args = [

View File

@@ -346,6 +346,20 @@ def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
assert processed['UNITTEST/BTC'].equals(processed2['UNITTEST/BTC'])
def test_backtest_abort(default_conf, mocker, testdatadir) -> None:
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
backtesting.check_abort()
backtesting.abort = True
with pytest.raises(DependencyException, match="Stop requested"):
backtesting.check_abort()
# abort flag resets
assert backtesting.abort is False
assert backtesting.progress.progress == 0
def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
def get_timerange(input1):
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)

View File

@@ -2,6 +2,7 @@
Unit test file for rpc/api_server.py
"""
import json
from datetime import datetime, timedelta, timezone
from pathlib import Path
from unittest.mock import ANY, MagicMock, PropertyMock
@@ -16,7 +17,7 @@ from requests.auth import _basic_auth_str
from freqtrade.__init__ import __version__
from freqtrade.enums import RunMode, State
from freqtrade.exceptions import ExchangeError
from freqtrade.exceptions import DependencyException, ExchangeError, OperationalException
from freqtrade.loggers import setup_logging, setup_logging_pre
from freqtrade.persistence import PairLocks, Trade
from freqtrade.rpc import RPC
@@ -48,9 +49,13 @@ def botclient(default_conf, mocker):
ftbot = get_patched_freqtradebot(mocker, default_conf)
rpc = RPC(ftbot)
mocker.patch('freqtrade.rpc.api_server.ApiServer.start_api', MagicMock())
apiserver = ApiServer(rpc, default_conf)
yield ftbot, TestClient(apiserver.app)
# Cleanup ... ?
try:
apiserver = ApiServer(default_conf)
apiserver.add_rpc_handler(rpc)
yield ftbot, TestClient(apiserver.app)
# Cleanup ... ?
finally:
ApiServer.shutdown()
def client_post(client, url, data={}):
@@ -235,8 +240,13 @@ def test_api__init__(default_conf, mocker):
}})
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
mocker.patch('freqtrade.rpc.api_server.webserver.ApiServer.start_api', MagicMock())
apiserver = ApiServer(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
apiserver = ApiServer(default_conf)
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
assert apiserver._config == default_conf
with pytest.raises(OperationalException, match="RPC Handler already attached."):
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
ApiServer.shutdown()
def test_api_UvicornServer(mocker):
@@ -298,15 +308,21 @@ def test_api_run(default_conf, mocker, caplog):
}})
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
server_mock = 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)
mocker.patch('freqtrade.rpc.api_server.webserver.UvicornServer', server_mock)
apiserver = ApiServer(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
apiserver = ApiServer(default_conf)
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
assert server_mock.call_count == 1
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
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)
@@ -325,6 +341,8 @@ def test_api_run(default_conf, mocker, caplog):
apiserver.start_api()
assert server_mock.call_count == 1
assert server_inst_mock.run_in_thread.call_count == 1
assert server_inst_mock.run.call_count == 0
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)
@@ -338,12 +356,24 @@ def test_api_run(default_conf, mocker, caplog):
"Please make sure that this is intentional!", caplog)
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
caplog.clear()
mocker.patch('freqtrade.rpc.api_server.webserver.UvicornServer',
MagicMock(side_effect=Exception))
apiserver.start_api()
assert log_has("Api server failed to start.", caplog)
ApiServer.shutdown()
def test_api_cleanup(default_conf, mocker, caplog):
@@ -359,11 +389,13 @@ def test_api_cleanup(default_conf, mocker, caplog):
server_mock.cleanup = MagicMock()
mocker.patch('freqtrade.rpc.api_server.webserver.UvicornServer', server_mock)
apiserver = ApiServer(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
apiserver = ApiServer(default_conf)
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
apiserver.cleanup()
assert apiserver._server.cleanup.call_count == 1
assert log_has("Stopping API Server", caplog)
ApiServer.shutdown()
def test_api_reloadconf(botclient):
@@ -1221,3 +1253,108 @@ def test_list_available_pairs(botclient):
assert rc.json()['length'] == 1
assert rc.json()['pairs'] == ['XRP/ETH']
assert len(rc.json()['pair_interval']) == 1
def test_api_backtesting(botclient, mocker, fee, caplog):
ftbot, client = botclient
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
# 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'
# start backtesting
data = {
"strategy": "DefaultStrategy",
"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=json.dumps(data))
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'
# 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=json.dumps(data))
assert_response(rc, 502)
result = rc.json()
assert 'Bot Background task already running' in result['error']
ApiServer._bgtask_running = False
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy',
side_effect=DependencyException())
rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data))
assert log_has("Backtesting caused an error: ", caplog)
# 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'

View File

@@ -5,6 +5,7 @@ from unittest.mock import MagicMock
from freqtrade.enums import RPCMessageType
from freqtrade.rpc import RPCManager
from freqtrade.rpc.api_server.webserver import ApiServer
from tests.conftest import get_patched_freqtradebot, log_has
@@ -190,3 +191,4 @@ def test_init_apiserver_enabled(mocker, default_conf, caplog) -> None:
assert len(rpc_manager.registered_modules) == 1
assert 'apiserver' in [mod.name for mod in rpc_manager.registered_modules]
assert run_mock.call_count == 1
ApiServer.shutdown()