2019-04-04 05:12:58 +00:00
|
|
|
"""
|
|
|
|
Unit test file for rpc/api_server.py
|
|
|
|
"""
|
|
|
|
|
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
|
|
|
|
2019-05-10 05:07:38 +00:00
|
|
|
import pytest
|
2019-05-18 07:50:29 +00:00
|
|
|
from flask import Flask
|
2019-05-25 12:25:36 +00:00
|
|
|
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__
|
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
|
2019-05-18 07:50:29 +00:00
|
|
|
from freqtrade.rpc.api_server import BASE_URI, ApiServer
|
2020-11-08 10:26:02 +00:00
|
|
|
from freqtrade.state import RunMode, State
|
2020-09-28 17:43:15 +00:00
|
|
|
from tests.conftest import create_mock_trades, get_patched_freqtradebot, log_has, patch_get_signal
|
|
|
|
|
2019-04-04 05:12:58 +00:00
|
|
|
|
2019-05-25 12:13:59 +00:00
|
|
|
_TEST_USER = "FreqTrader"
|
|
|
|
_TEST_PASS = "SuperSecurePassword1!"
|
|
|
|
|
|
|
|
|
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)
|
2020-11-08 10:26:02 +00:00
|
|
|
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,
|
|
|
|
}})
|
|
|
|
|
2019-05-11 06:55:21 +00:00
|
|
|
ftbot = get_patched_freqtradebot(mocker, default_conf)
|
2019-05-11 07:44:39 +00:00
|
|
|
mocker.patch('freqtrade.rpc.api_server.ApiServer.run', MagicMock())
|
2019-05-11 06:55:21 +00:00
|
|
|
apiserver = ApiServer(ftbot)
|
|
|
|
yield ftbot, apiserver.app.test_client()
|
2019-05-10 05:07:38 +00:00
|
|
|
# Cleanup ... ?
|
|
|
|
|
|
|
|
|
2019-05-25 12:13:59 +00:00
|
|
|
def client_post(client, url, data={}):
|
|
|
|
return client.post(url,
|
|
|
|
content_type="application/json",
|
|
|
|
data=data,
|
2020-05-20 17:43:52 +00:00
|
|
|
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_get(client, 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
|
|
|
|
|
|
|
|
2020-08-04 12:41:38 +00:00
|
|
|
def client_delete(client, url):
|
|
|
|
# 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
|
2019-05-10 05:07:38 +00:00
|
|
|
assert response.content_type == "application/json"
|
2020-05-20 17:43:52 +00:00
|
|
|
if needs_cors:
|
|
|
|
assert ('Access-Control-Allow-Credentials', 'true') in response.headers._list
|
2020-06-24 18:32:35 +00:00
|
|
|
assert ('Access-Control-Allow-Origin', 'http://example.com') in response.headers._list
|
2019-05-10 05:07:38 +00:00
|
|
|
|
|
|
|
|
2019-05-11 11:18:11 +00:00
|
|
|
def test_api_not_found(botclient):
|
|
|
|
ftbot, client = botclient
|
|
|
|
|
2019-05-25 12:13:59 +00:00
|
|
|
rc = client_post(client, f"{BASE_URI}/invalid_url")
|
2019-05-11 12:05:25 +00:00
|
|
|
assert_response(rc, 404)
|
2019-05-15 05:12:33 +00:00
|
|
|
assert rc.json == {"status": "error",
|
|
|
|
"reason": f"There's no API call for http://localhost{BASE_URI}/invalid_url.",
|
|
|
|
"code": 404
|
2019-05-11 11:18:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-05-25 12:13:59 +00:00
|
|
|
def test_api_unauthorized(botclient):
|
|
|
|
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)
|
2019-11-11 19:09:58 +00:00
|
|
|
assert rc.json == {'status': 'pong'}
|
|
|
|
|
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)
|
2019-05-25 12:13:59 +00:00
|
|
|
assert rc.json == {'error': 'Unauthorized'}
|
|
|
|
|
|
|
|
# Change only username
|
2020-08-26 18:52:09 +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)
|
|
|
|
assert rc.json == {'error': 'Unauthorized'}
|
|
|
|
|
|
|
|
# Change only password
|
|
|
|
ftbot.config['api_server']['username'] = _TEST_USER
|
2020-08-26 18:52:09 +00:00
|
|
|
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)
|
|
|
|
assert rc.json == {'error': 'Unauthorized'}
|
|
|
|
|
2020-08-26 18:52:09 +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)
|
|
|
|
assert rc.json == {'error': 'Unauthorized'}
|
|
|
|
|
|
|
|
|
2020-05-10 08:43:13 +00:00
|
|
|
def test_api_token_login(botclient):
|
|
|
|
ftbot, client = botclient
|
|
|
|
rc = client_post(client, f"{BASE_URI}/token/login")
|
|
|
|
assert_response(rc)
|
|
|
|
assert 'access_token' in rc.json
|
|
|
|
assert 'refresh_token' in rc.json
|
|
|
|
|
|
|
|
# test Authentication is working with JWT tokens too
|
|
|
|
rc = client.get(f"{BASE_URI}/count",
|
|
|
|
content_type="application/json",
|
2020-05-20 17:43:52 +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):
|
|
|
|
ftbot, client = botclient
|
|
|
|
rc = client_post(client, f"{BASE_URI}/token/login")
|
|
|
|
assert_response(rc)
|
|
|
|
rc = client.post(f"{BASE_URI}/token/refresh",
|
|
|
|
content_type="application/json",
|
|
|
|
data=None,
|
2020-05-20 17:43:52 +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)
|
|
|
|
assert 'access_token' in rc.json
|
|
|
|
assert 'refresh_token' not in rc.json
|
|
|
|
|
|
|
|
|
2019-05-11 06:55:21 +00:00
|
|
|
def test_api_stop_workflow(botclient):
|
|
|
|
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)
|
2019-05-11 06:55:21 +00:00
|
|
|
assert rc.json == {'status': 'stopping trader ...'}
|
|
|
|
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)
|
2019-05-11 06:55:21 +00:00
|
|
|
assert rc.json == {'status': 'already stopped'}
|
|
|
|
|
|
|
|
# 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)
|
2019-05-11 06:55:21 +00:00
|
|
|
assert rc.json == {'status': 'starting trader ...'}
|
|
|
|
assert ftbot.state == State.RUNNING
|
|
|
|
|
|
|
|
# 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)
|
2019-05-11 06:55:21 +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())
|
|
|
|
mocker.patch('freqtrade.rpc.api_server.ApiServer.run', MagicMock())
|
|
|
|
|
|
|
|
apiserver = ApiServer(get_patched_freqtradebot(mocker, default_conf))
|
|
|
|
assert apiserver._config == default_conf
|
|
|
|
|
|
|
|
|
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())
|
|
|
|
mocker.patch('freqtrade.rpc.api_server.threading.Thread', MagicMock())
|
|
|
|
|
2019-05-18 07:54:36 +00:00
|
|
|
server_mock = MagicMock()
|
|
|
|
mocker.patch('freqtrade.rpc.api_server.make_server', server_mock)
|
2019-05-18 07:50:29 +00:00
|
|
|
|
|
|
|
apiserver = ApiServer(get_patched_freqtradebot(mocker, default_conf))
|
2019-05-14 05:07:51 +00:00
|
|
|
|
|
|
|
assert apiserver._config == default_conf
|
|
|
|
apiserver.run()
|
2019-05-18 07:54:36 +00:00
|
|
|
assert server_mock.call_count == 1
|
|
|
|
assert server_mock.call_args_list[0][0][0] == "127.0.0.1"
|
2019-11-23 14:20:53 +00:00
|
|
|
assert server_mock.call_args_list[0][0][1] == 8080
|
2019-05-18 07:54:36 +00:00
|
|
|
assert isinstance(server_mock.call_args_list[0][0][2], Flask)
|
|
|
|
assert hasattr(apiserver, "srv")
|
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,
|
2019-05-26 12:40:03 +00:00
|
|
|
"password": "",
|
|
|
|
}})
|
2019-05-14 05:07:51 +00:00
|
|
|
apiserver.run()
|
|
|
|
|
2019-05-18 07:54:36 +00:00
|
|
|
assert server_mock.call_count == 1
|
|
|
|
assert server_mock.call_args_list[0][0][0] == "0.0.0.0"
|
2019-11-23 14:20:53 +00:00
|
|
|
assert server_mock.call_args_list[0][0][1] == 8089
|
2019-05-18 07:54:36 +00:00
|
|
|
assert isinstance(server_mock.call_args_list[0][0][2], Flask)
|
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)
|
2019-05-26 12:40:03 +00:00
|
|
|
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)
|
2019-05-14 05:07:51 +00:00
|
|
|
|
2019-05-15 04:24:22 +00:00
|
|
|
# Test crashing flask
|
|
|
|
caplog.clear()
|
2019-05-18 07:50:29 +00:00
|
|
|
mocker.patch('freqtrade.rpc.api_server.make_server', MagicMock(side_effect=Exception))
|
2019-05-15 04:24:22 +00:00
|
|
|
apiserver.run()
|
2019-08-11 18:17:22 +00:00
|
|
|
assert log_has("Api server failed to start.", caplog)
|
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())
|
|
|
|
mocker.patch('freqtrade.rpc.api_server.threading.Thread', MagicMock())
|
|
|
|
mocker.patch('freqtrade.rpc.api_server.make_server', MagicMock())
|
|
|
|
|
|
|
|
apiserver = ApiServer(get_patched_freqtradebot(mocker, default_conf))
|
|
|
|
apiserver.run()
|
|
|
|
stop_mock = MagicMock()
|
|
|
|
stop_mock.shutdown = MagicMock()
|
|
|
|
apiserver.srv = stop_mock
|
|
|
|
|
|
|
|
apiserver.cleanup()
|
|
|
|
assert stop_mock.shutdown.call_count == 1
|
2019-08-11 18:17:22 +00:00
|
|
|
assert log_has("Stopping API Server", caplog)
|
2019-05-18 07:54:36 +00:00
|
|
|
|
|
|
|
|
2019-05-11 06:55:21 +00:00
|
|
|
def test_api_reloadconf(botclient):
|
|
|
|
ftbot, client = botclient
|
2019-04-04 05:12:58 +00:00
|
|
|
|
2020-06-09 21:03:55 +00:00
|
|
|
rc = client_post(client, f"{BASE_URI}/reload_config")
|
2019-05-11 12:05:25 +00:00
|
|
|
assert_response(rc)
|
2020-09-05 14:44:23 +00:00
|
|
|
assert rc.json == {'status': 'Reloading config ...'}
|
2020-06-09 21:03:55 +00:00
|
|
|
assert ftbot.state == State.RELOAD_CONFIG
|
2019-04-04 05:12:58 +00:00
|
|
|
|
|
|
|
|
2019-05-11 06:55:21 +00:00
|
|
|
def test_api_stopbuy(botclient):
|
|
|
|
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)
|
2020-06-09 21:03:55 +00:00
|
|
|
assert rc.json == {'status': 'No more buy will occur from now. Run /reload_config to reset.'}
|
2019-05-11 06:55:21 +00:00
|
|
|
assert ftbot.config['max_open_trades'] == 0
|
2019-05-11 07:10:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_api_balance(botclient, mocker, rpc_balance):
|
|
|
|
ftbot, client = botclient
|
|
|
|
|
2019-12-15 09:39:52 +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)
|
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}")
|
2019-11-24 18:41:51 +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)
|
2019-05-11 07:10:54 +00:00
|
|
|
assert "currencies" in rc.json
|
|
|
|
assert len(rc.json["currencies"]) == 5
|
|
|
|
assert rc.json['currencies'][0] == {
|
|
|
|
'currency': 'BTC',
|
2019-09-05 18:02:01 +00:00
|
|
|
'free': 12.0,
|
2019-05-11 07:10:54 +00:00
|
|
|
'balance': 12.0,
|
2019-09-05 18:02:01 +00:00
|
|
|
'used': 0.0,
|
2019-11-15 05:33:07 +00:00
|
|
|
'est_stake': 12.0,
|
|
|
|
'stake': 'BTC',
|
2019-05-11 07:10:54 +00:00
|
|
|
}
|
2019-05-11 07:44:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_api_count(botclient, mocker, ticker, fee, markets):
|
|
|
|
ftbot, client = botclient
|
|
|
|
patch_get_signal(ftbot, (True, False))
|
|
|
|
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
|
|
|
|
|
|
|
assert rc.json["current"] == 0
|
|
|
|
assert rc.json["max"] == 1.0
|
|
|
|
|
|
|
|
# Create some test data
|
2019-12-30 19:50:56 +00:00
|
|
|
ftbot.enter_positions()
|
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
|
|
|
assert rc.json["current"] == 1.0
|
|
|
|
assert rc.json["max"] == 1.0
|
2019-05-11 11:31:48 +00:00
|
|
|
|
|
|
|
|
2020-10-17 15:58:07 +00:00
|
|
|
def test_api_locks(botclient):
|
|
|
|
ftbot, client = botclient
|
|
|
|
|
|
|
|
rc = client_get(client, f"{BASE_URI}/locks")
|
|
|
|
assert_response(rc)
|
|
|
|
|
|
|
|
assert 'locks' in rc.json
|
|
|
|
|
|
|
|
assert rc.json['lock_count'] == 0
|
|
|
|
assert rc.json['lock_count'] == len(rc.json['locks'])
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
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'])
|
|
|
|
|
|
|
|
|
2019-11-17 13:56:08 +00:00
|
|
|
def test_api_show_config(botclient, mocker):
|
|
|
|
ftbot, client = botclient
|
|
|
|
patch_get_signal(ftbot, (True, False))
|
|
|
|
|
|
|
|
rc = client_get(client, f"{BASE_URI}/show_config")
|
|
|
|
assert_response(rc)
|
|
|
|
assert 'dry_run' in rc.json
|
|
|
|
assert rc.json['exchange'] == 'bittrex'
|
2020-06-02 17:43:15 +00:00
|
|
|
assert rc.json['timeframe'] == '5m'
|
2020-07-13 19:15:33 +00:00
|
|
|
assert rc.json['timeframe_ms'] == 300000
|
|
|
|
assert rc.json['timeframe_min'] == 5
|
2020-05-05 19:19:35 +00:00
|
|
|
assert rc.json['state'] == 'running'
|
2019-11-17 13:56:08 +00:00
|
|
|
assert not rc.json['trailing_stop']
|
2020-06-05 18:31:40 +00:00
|
|
|
assert 'bid_strategy' in rc.json
|
|
|
|
assert 'ask_strategy' in rc.json
|
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):
|
|
|
|
ftbot, client = botclient
|
|
|
|
patch_get_signal(ftbot, (True, False))
|
|
|
|
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-05-17 18:12:01 +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
|
|
|
|
|
|
|
|
2020-08-04 12:41:38 +00:00
|
|
|
def test_api_trades(botclient, mocker, fee, markets):
|
2020-04-07 17:50:13 +00:00
|
|
|
ftbot, client = botclient
|
|
|
|
patch_get_signal(ftbot, (True, False))
|
|
|
|
mocker.patch.multiple(
|
|
|
|
'freqtrade.exchange.Exchange',
|
|
|
|
markets=PropertyMock(return_value=markets)
|
|
|
|
)
|
|
|
|
rc = client_get(client, f"{BASE_URI}/trades")
|
|
|
|
assert_response(rc)
|
|
|
|
assert len(rc.json) == 2
|
|
|
|
assert rc.json['trades_count'] == 0
|
|
|
|
|
|
|
|
create_mock_trades(fee)
|
|
|
|
|
|
|
|
rc = client_get(client, f"{BASE_URI}/trades")
|
|
|
|
assert_response(rc)
|
|
|
|
assert len(rc.json['trades']) == 2
|
|
|
|
assert rc.json['trades_count'] == 2
|
2020-07-23 05:50:45 +00:00
|
|
|
rc = client_get(client, f"{BASE_URI}/trades?limit=1")
|
|
|
|
assert_response(rc)
|
|
|
|
assert len(rc.json['trades']) == 1
|
|
|
|
assert rc.json['trades_count'] == 1
|
2020-04-07 17:50:13 +00:00
|
|
|
|
|
|
|
|
2020-08-04 12:41:38 +00:00
|
|
|
def test_api_delete_trade(botclient, mocker, fee, markets):
|
|
|
|
ftbot, client = botclient
|
|
|
|
patch_get_signal(ftbot, (True, False))
|
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
|
|
|
)
|
|
|
|
rc = client_delete(client, f"{BASE_URI}/trades/1")
|
|
|
|
# Error - trade won't exist yet.
|
|
|
|
assert_response(rc, 502)
|
|
|
|
|
|
|
|
create_mock_trades(fee)
|
2020-08-04 17:43:05 +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'
|
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-08-04 17:43:05 +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-08-04 17:43:05 +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
|
|
|
|
|
|
|
|
2020-08-14 17:36:12 +00:00
|
|
|
def test_api_logs(botclient):
|
|
|
|
ftbot, client = botclient
|
|
|
|
rc = client_get(client, f"{BASE_URI}/logs")
|
|
|
|
assert_response(rc)
|
|
|
|
assert len(rc.json) == 2
|
|
|
|
assert 'logs' in rc.json
|
|
|
|
# Using a fixed comparison here would make this test fail!
|
2020-09-11 18:00:36 +00:00
|
|
|
assert rc.json['log_count'] > 1
|
2020-08-14 17:36:12 +00:00
|
|
|
assert len(rc.json['logs']) == rc.json['log_count']
|
|
|
|
|
|
|
|
assert isinstance(rc.json['logs'][0], list)
|
|
|
|
# date
|
|
|
|
assert isinstance(rc.json['logs'][0][0], str)
|
|
|
|
# created_timestamp
|
|
|
|
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 18:12:59 +00:00
|
|
|
rc = client_get(client, f"{BASE_URI}/logs?limit=5")
|
|
|
|
assert_response(rc)
|
|
|
|
assert len(rc.json) == 2
|
|
|
|
assert 'logs' in rc.json
|
|
|
|
# Using a fixed comparison here would make this test fail!
|
|
|
|
assert rc.json['log_count'] == 5
|
|
|
|
assert len(rc.json['logs']) == rc.json['log_count']
|
|
|
|
|
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):
|
|
|
|
ftbot, client = botclient
|
|
|
|
patch_get_signal(ftbot, (True, False))
|
|
|
|
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)
|
|
|
|
assert rc.json == {"error": "Error querying _edge: Edge is not enabled."}
|
|
|
|
|
|
|
|
|
2020-09-11 04:44:20 +00:00
|
|
|
@pytest.mark.usefixtures("init_persistence")
|
2019-05-11 12:05:25 +00:00
|
|
|
def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, limit_sell_order):
|
|
|
|
ftbot, client = botclient
|
|
|
|
patch_get_signal(ftbot, (True, False))
|
|
|
|
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")
|
2020-05-29 07:03:48 +00:00
|
|
|
assert_response(rc, 200)
|
|
|
|
assert rc.json['trade_count'] == 0
|
2019-05-11 12:05:25 +00:00
|
|
|
|
2019-12-30 19:50:56 +00:00
|
|
|
ftbot.enter_positions()
|
2019-05-11 12:05:25 +00:00
|
|
|
trade = Trade.query.first()
|
|
|
|
|
|
|
|
# Simulate fulfilled LIMIT_BUY order for trade
|
|
|
|
trade.update(limit_buy_order)
|
2019-05-25 12:13:59 +00:00
|
|
|
rc = client_get(client, f"{BASE_URI}/profit")
|
2020-05-29 07:03:48 +00:00
|
|
|
assert_response(rc, 200)
|
|
|
|
# One open trade
|
|
|
|
assert rc.json['trade_count'] == 1
|
|
|
|
assert rc.json['best_pair'] == ''
|
|
|
|
assert rc.json['best_rate'] == 0
|
2019-05-11 12:05:25 +00:00
|
|
|
|
2020-09-11 04:44:20 +00:00
|
|
|
trade = Trade.query.first()
|
2019-05-11 12:05:25 +00:00
|
|
|
trade.update(limit_sell_order)
|
|
|
|
|
|
|
|
trade.close_date = datetime.utcnow()
|
|
|
|
trade.is_open = False
|
|
|
|
|
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)
|
|
|
|
assert rc.json == {'avg_duration': '0:00:00',
|
|
|
|
'best_pair': 'ETH/BTC',
|
|
|
|
'best_rate': 6.2,
|
|
|
|
'first_trade_date': 'just now',
|
2020-05-24 07:07:24 +00:00
|
|
|
'first_trade_timestamp': ANY,
|
2019-05-11 12:05:25 +00:00
|
|
|
'latest_trade_date': 'just now',
|
2020-05-24 07:07:24 +00:00
|
|
|
'latest_trade_timestamp': ANY,
|
2019-05-11 12:05:25 +00:00
|
|
|
'profit_all_coin': 6.217e-05,
|
2020-07-02 18:03:15 +00:00
|
|
|
'profit_all_fiat': 0.76748865,
|
2019-05-11 12:05:25 +00:00
|
|
|
'profit_all_percent': 6.2,
|
2020-06-03 17:40:49 +00:00
|
|
|
'profit_all_percent_mean': 6.2,
|
|
|
|
'profit_all_ratio_mean': 0.06201058,
|
|
|
|
'profit_all_percent_sum': 6.2,
|
|
|
|
'profit_all_ratio_sum': 0.06201058,
|
2019-05-11 12:05:25 +00:00
|
|
|
'profit_closed_coin': 6.217e-05,
|
2020-07-02 18:03:15 +00:00
|
|
|
'profit_closed_fiat': 0.76748865,
|
2019-05-11 12:05:25 +00:00
|
|
|
'profit_closed_percent': 6.2,
|
2020-06-03 17:40:49 +00:00
|
|
|
'profit_closed_ratio_mean': 0.06201058,
|
|
|
|
'profit_closed_percent_mean': 6.2,
|
|
|
|
'profit_closed_ratio_sum': 0.06201058,
|
|
|
|
'profit_closed_percent_sum': 6.2,
|
2020-05-29 07:38:12 +00:00
|
|
|
'trade_count': 1,
|
|
|
|
'closed_trade_count': 1,
|
2020-06-24 04:43:19 +00:00
|
|
|
'winning_trades': 1,
|
|
|
|
'losing_trades': 0,
|
2019-05-11 12:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-05-15 04:51:23 +00:00
|
|
|
def test_api_performance(botclient, mocker, ticker, fee):
|
2019-05-11 12:05:25 +00:00
|
|
|
ftbot, client = botclient
|
|
|
|
patch_get_signal(ftbot, (True, False))
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
|
|
)
|
2019-12-17 07:53:30 +00:00
|
|
|
trade.close_profit = trade.calc_profit_ratio()
|
2019-05-11 12:05:25 +00:00
|
|
|
Trade.session.add(trade)
|
|
|
|
|
|
|
|
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
|
|
|
|
)
|
2019-12-17 07:53:30 +00:00
|
|
|
trade.close_profit = trade.calc_profit_ratio()
|
2019-05-11 12:05:25 +00:00
|
|
|
Trade.session.add(trade)
|
|
|
|
Trade.session.flush()
|
|
|
|
|
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)
|
|
|
|
assert len(rc.json) == 2
|
|
|
|
assert rc.json == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61},
|
|
|
|
{'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57}]
|
|
|
|
|
|
|
|
|
2019-05-15 04:51:23 +00:00
|
|
|
def test_api_status(botclient, mocker, ticker, fee, markets):
|
2019-05-11 12:05:25 +00:00
|
|
|
ftbot, client = botclient
|
|
|
|
patch_get_signal(ftbot, (True, False))
|
|
|
|
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}/status")
|
2019-11-17 13:40:59 +00:00
|
|
|
assert_response(rc, 200)
|
|
|
|
assert rc.json == []
|
2019-05-11 12:05:25 +00:00
|
|
|
|
2019-12-30 19:50:56 +00:00
|
|
|
ftbot.enter_positions()
|
2020-06-04 04:56:30 +00:00
|
|
|
trades = Trade.get_open_trades()
|
|
|
|
trades[0].open_order_id = None
|
|
|
|
ftbot.exit_positions(trades)
|
|
|
|
|
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)
|
|
|
|
assert len(rc.json) == 1
|
2020-07-15 18:51:52 +00:00
|
|
|
assert rc.json == [{'amount': 91.07468123,
|
|
|
|
'amount_requested': 91.07468123,
|
2019-05-11 12:05:25 +00:00
|
|
|
'base_currency': 'BTC',
|
|
|
|
'close_date': None,
|
|
|
|
'close_date_hum': None,
|
2020-05-24 06:47:10 +00:00
|
|
|
'close_timestamp': None,
|
2019-05-11 12:05:25 +00:00
|
|
|
'close_profit': None,
|
2020-05-26 17:25:03 +00:00
|
|
|
'close_profit_pct': None,
|
2020-05-30 09:34:39 +00:00
|
|
|
'close_profit_abs': None,
|
2019-05-11 12:05:25 +00:00
|
|
|
'close_rate': None,
|
2020-05-24 06:47:10 +00:00
|
|
|
'current_profit': -0.00408133,
|
2020-05-26 17:25:03 +00:00
|
|
|
'current_profit_pct': -0.41,
|
2020-06-04 04:56:30 +00:00
|
|
|
'current_profit_abs': -4.09e-06,
|
2020-11-03 06:34:21 +00:00
|
|
|
'profit_ratio': -0.00408133,
|
|
|
|
'profit_pct': -0.41,
|
|
|
|
'profit_abs': -4.09e-06,
|
2020-02-23 13:27:03 +00:00
|
|
|
'current_rate': 1.099e-05,
|
2019-05-11 12:05:25 +00:00
|
|
|
'open_date': ANY,
|
|
|
|
'open_date_hum': 'just now',
|
2020-05-24 06:47:10 +00:00
|
|
|
'open_timestamp': ANY,
|
2020-06-04 04:56:30 +00:00
|
|
|
'open_order': None,
|
2020-02-23 13:27:03 +00:00
|
|
|
'open_rate': 1.098e-05,
|
2019-05-11 12:05:25 +00:00
|
|
|
'pair': 'ETH/BTC',
|
|
|
|
'stake_amount': 0.001,
|
2020-06-04 04:56:30 +00:00
|
|
|
'stop_loss_abs': 9.882e-06,
|
|
|
|
'stop_loss_pct': -10.0,
|
|
|
|
'stop_loss_ratio': -0.1,
|
2020-05-30 09:34:39 +00:00
|
|
|
'stoploss_order_id': None,
|
2020-06-04 04:56:30 +00:00
|
|
|
'stoploss_last_update': ANY,
|
|
|
|
'stoploss_last_update_timestamp': ANY,
|
|
|
|
'initial_stop_loss_abs': 9.882e-06,
|
|
|
|
'initial_stop_loss_pct': -10.0,
|
|
|
|
'initial_stop_loss_ratio': -0.1,
|
2020-06-04 04:56:59 +00:00
|
|
|
'stoploss_current_dist': -1.1080000000000002e-06,
|
|
|
|
'stoploss_current_dist_ratio': -0.10081893,
|
2020-07-14 18:16:18 +00:00
|
|
|
'stoploss_current_dist_pct': -10.08,
|
2020-06-04 05:04:32 +00:00
|
|
|
'stoploss_entry_dist': -0.00010475,
|
|
|
|
'stoploss_entry_dist_ratio': -0.10448878,
|
2020-04-06 13:49:24 +00:00
|
|
|
'trade_id': 1,
|
|
|
|
'close_rate_requested': None,
|
|
|
|
'current_rate': 1.099e-05,
|
|
|
|
'fee_close': 0.0025,
|
2020-04-30 04:58:43 +00:00
|
|
|
'fee_close_cost': None,
|
|
|
|
'fee_close_currency': None,
|
2020-04-06 13:49:24 +00:00
|
|
|
'fee_open': 0.0025,
|
2020-04-30 04:58:43 +00:00
|
|
|
'fee_open_cost': None,
|
|
|
|
'fee_open_currency': None,
|
2020-04-06 13:49:24 +00:00
|
|
|
'open_date': ANY,
|
|
|
|
'is_open': True,
|
2020-06-04 04:56:30 +00:00
|
|
|
'max_rate': 1.099e-05,
|
|
|
|
'min_rate': 1.098e-05,
|
|
|
|
'open_order_id': None,
|
2020-04-06 13:49:24 +00:00
|
|
|
'open_rate_requested': 1.098e-05,
|
2020-12-10 18:21:20 +00:00
|
|
|
'open_trade_value': 0.0010025,
|
2020-04-06 13:49:24 +00:00
|
|
|
'sell_reason': None,
|
2020-05-17 08:52:20 +00:00
|
|
|
'sell_order_status': None,
|
2020-04-06 13:49:24 +00:00
|
|
|
'strategy': 'DefaultStrategy',
|
2020-06-01 18:43:20 +00:00
|
|
|
'timeframe': 5,
|
2020-05-30 09:34:39 +00:00
|
|
|
'exchange': 'bittrex',
|
|
|
|
}]
|
2019-05-11 12:05:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_api_version(botclient):
|
|
|
|
ftbot, client = botclient
|
|
|
|
|
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)
|
|
|
|
assert rc.json == {"version": __version__}
|
|
|
|
|
|
|
|
|
2019-05-15 04:51:23 +00:00
|
|
|
def test_api_blacklist(botclient, mocker):
|
2019-05-11 12:05:25 +00:00
|
|
|
ftbot, client = botclient
|
|
|
|
|
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)
|
|
|
|
assert rc.json == {"blacklist": ["DOGE/BTC", "HOT/BTC"],
|
|
|
|
"length": 2,
|
2020-05-28 04:51:53 +00:00
|
|
|
"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)
|
|
|
|
assert rc.json == {"blacklist": ["DOGE/BTC", "HOT/BTC", "ETH/BTC"],
|
|
|
|
"length": 3,
|
2020-05-28 04:51:53 +00:00
|
|
|
"method": ["StaticPairList"],
|
|
|
|
"errors": {},
|
|
|
|
}
|
2019-05-11 12:05:25 +00:00
|
|
|
|
|
|
|
|
2019-05-15 04:51:23 +00:00
|
|
|
def test_api_whitelist(botclient):
|
2019-05-11 12:05:25 +00:00
|
|
|
ftbot, client = botclient
|
|
|
|
|
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)
|
|
|
|
assert rc.json == {"whitelist": ['ETH/BTC', 'LTC/BTC', 'XRP/BTC', 'NEO/BTC'],
|
|
|
|
"length": 4,
|
2019-11-09 13:00:32 +00:00
|
|
|
"method": ["StaticPairList"]}
|
2019-05-15 04:51:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_api_forcebuy(botclient, mocker, fee):
|
|
|
|
ftbot, client = botclient
|
|
|
|
|
2019-05-25 12:13:59 +00:00
|
|
|
rc = client_post(client, f"{BASE_URI}/forcebuy",
|
2019-05-15 04:51:23 +00:00
|
|
|
data='{"pair": "ETH/BTC"}')
|
|
|
|
assert_response(rc, 502)
|
|
|
|
assert rc.json == {"error": "Error querying _forcebuy: Forcebuy not enabled."}
|
|
|
|
|
|
|
|
# enable forcebuy
|
2020-08-26 18:52:09 +00:00
|
|
|
ftbot.config['forcebuy_enable'] = True
|
2019-05-15 04:51:23 +00:00
|
|
|
|
|
|
|
fbuy_mock = MagicMock(return_value=None)
|
|
|
|
mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock)
|
2019-05-25 12:13:59 +00:00
|
|
|
rc = client_post(client, f"{BASE_URI}/forcebuy",
|
2019-05-15 04:51:23 +00:00
|
|
|
data='{"pair": "ETH/BTC"}')
|
|
|
|
assert_response(rc)
|
|
|
|
assert rc.json == {"status": "Error buying pair ETH/BTC."}
|
|
|
|
|
|
|
|
# Test creating trae
|
|
|
|
fbuy_mock = MagicMock(return_value=Trade(
|
|
|
|
pair='ETH/ETH',
|
|
|
|
amount=1,
|
2020-07-15 18:27:00 +00:00
|
|
|
amount_requested=1,
|
2019-05-15 04:51:23 +00:00
|
|
|
exchange='bittrex',
|
|
|
|
stake_amount=1,
|
|
|
|
open_rate=0.245441,
|
|
|
|
open_order_id="123456",
|
|
|
|
open_date=datetime.utcnow(),
|
|
|
|
is_open=False,
|
|
|
|
fee_close=fee.return_value,
|
|
|
|
fee_open=fee.return_value,
|
|
|
|
close_rate=0.265441,
|
|
|
|
))
|
|
|
|
mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock)
|
|
|
|
|
2019-05-25 12:13:59 +00:00
|
|
|
rc = client_post(client, f"{BASE_URI}/forcebuy",
|
2019-05-15 04:51:23 +00:00
|
|
|
data='{"pair": "ETH/BTC"}')
|
|
|
|
assert_response(rc)
|
|
|
|
assert rc.json == {'amount': 1,
|
2020-07-15 18:27:00 +00:00
|
|
|
'amount_requested': 1,
|
2020-06-01 08:56:05 +00:00
|
|
|
'trade_id': None,
|
2019-05-15 04:51:23 +00:00
|
|
|
'close_date': None,
|
|
|
|
'close_date_hum': None,
|
2020-05-24 06:47:10 +00:00
|
|
|
'close_timestamp': None,
|
2019-05-15 04:51:23 +00:00
|
|
|
'close_rate': 0.265441,
|
|
|
|
'open_date': ANY,
|
|
|
|
'open_date_hum': 'just now',
|
2020-05-24 06:47:10 +00:00
|
|
|
'open_timestamp': ANY,
|
2019-05-15 04:51:23 +00:00
|
|
|
'open_rate': 0.245441,
|
|
|
|
'pair': 'ETH/ETH',
|
|
|
|
'stake_amount': 1,
|
2020-06-01 09:05:37 +00:00
|
|
|
'stop_loss_abs': None,
|
2019-05-15 04:51:23 +00:00
|
|
|
'stop_loss_pct': None,
|
2020-06-01 08:56:05 +00:00
|
|
|
'stop_loss_ratio': None,
|
2020-05-30 09:34:39 +00:00
|
|
|
'stoploss_order_id': None,
|
|
|
|
'stoploss_last_update': None,
|
|
|
|
'stoploss_last_update_timestamp': None,
|
2020-06-01 09:05:37 +00:00
|
|
|
'initial_stop_loss_abs': None,
|
2020-06-01 08:56:05 +00:00
|
|
|
'initial_stop_loss_pct': None,
|
|
|
|
'initial_stop_loss_ratio': None,
|
2020-04-06 13:49:24 +00:00
|
|
|
'close_profit': None,
|
2020-11-03 06:34:21 +00:00
|
|
|
'close_profit_pct': None,
|
2020-05-30 09:34:39 +00:00
|
|
|
'close_profit_abs': None,
|
2020-04-06 13:49:24 +00:00
|
|
|
'close_rate_requested': None,
|
2020-11-03 06:34:21 +00:00
|
|
|
'profit_ratio': None,
|
|
|
|
'profit_pct': None,
|
|
|
|
'profit_abs': None,
|
2020-04-06 13:49:24 +00:00
|
|
|
'fee_close': 0.0025,
|
2020-04-30 04:58:43 +00:00
|
|
|
'fee_close_cost': None,
|
|
|
|
'fee_close_currency': None,
|
2020-04-06 13:49:24 +00:00
|
|
|
'fee_open': 0.0025,
|
2020-04-30 04:58:43 +00:00
|
|
|
'fee_open_cost': None,
|
|
|
|
'fee_open_currency': None,
|
2020-04-06 13:49:24 +00:00
|
|
|
'is_open': False,
|
|
|
|
'max_rate': None,
|
|
|
|
'min_rate': None,
|
|
|
|
'open_order_id': '123456',
|
|
|
|
'open_rate_requested': None,
|
2020-12-10 18:21:20 +00:00
|
|
|
'open_trade_value': 0.24605460,
|
2020-04-06 13:49:24 +00:00
|
|
|
'sell_reason': None,
|
2020-05-17 08:52:20 +00:00
|
|
|
'sell_order_status': None,
|
2020-04-06 13:49:24 +00:00
|
|
|
'strategy': None,
|
2020-06-01 18:43:20 +00:00
|
|
|
'timeframe': None,
|
2020-05-30 09:34:39 +00:00
|
|
|
'exchange': 'bittrex',
|
2020-04-06 13:49:24 +00:00
|
|
|
}
|
2019-05-15 04:51:23 +00:00
|
|
|
|
|
|
|
|
2019-05-15 05:00:17 +00:00
|
|
|
def test_api_forcesell(botclient, mocker, ticker, fee, markets):
|
|
|
|
ftbot, client = botclient
|
|
|
|
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)
|
|
|
|
)
|
|
|
|
patch_get_signal(ftbot, (True, False))
|
|
|
|
|
2019-05-25 12:13:59 +00:00
|
|
|
rc = client_post(client, f"{BASE_URI}/forcesell",
|
2019-05-15 05:00:17 +00:00
|
|
|
data='{"tradeid": "1"}')
|
|
|
|
assert_response(rc, 502)
|
|
|
|
assert rc.json == {"error": "Error querying _forcesell: invalid argument"}
|
2019-05-15 04:51:23 +00:00
|
|
|
|
2019-12-30 19:50:56 +00:00
|
|
|
ftbot.enter_positions()
|
2019-05-15 05:00:17 +00:00
|
|
|
|
2019-05-25 12:13:59 +00:00
|
|
|
rc = client_post(client, f"{BASE_URI}/forcesell",
|
2019-05-15 05:00:17 +00:00
|
|
|
data='{"tradeid": "1"}')
|
|
|
|
assert_response(rc)
|
|
|
|
assert rc.json == {'result': 'Created sell order for trade 1.'}
|
2020-06-15 06:47:43 +00:00
|
|
|
|
|
|
|
|
2020-07-02 05:10:56 +00:00
|
|
|
def test_api_pair_candles(botclient, ohlcv_history):
|
2020-06-15 06:47:43 +00:00
|
|
|
ftbot, client = botclient
|
|
|
|
timeframe = '5m'
|
|
|
|
amount = 2
|
2020-08-24 18:38:01 +00:00
|
|
|
|
2020-09-11 18:31:02 +00:00
|
|
|
# No pair
|
|
|
|
rc = client_get(client,
|
|
|
|
f"{BASE_URI}/pair_candles?limit={amount}&timeframe={timeframe}")
|
|
|
|
assert_response(rc, 400)
|
|
|
|
|
|
|
|
# No timeframe
|
|
|
|
rc = client_get(client,
|
|
|
|
f"{BASE_URI}/pair_candles?pair=XRP%2FBTC")
|
|
|
|
assert_response(rc, 400)
|
|
|
|
|
2020-08-24 18:38:01 +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
|
2020-06-15 06:47:43 +00:00
|
|
|
ohlcv_history['sma'] = ohlcv_history['close'].rolling(2).mean()
|
2020-07-02 06:39:07 +00:00
|
|
|
ohlcv_history['buy'] = 0
|
2020-10-02 04:41:28 +00:00
|
|
|
ohlcv_history.loc[1, 'buy'] = 1
|
2020-07-02 06:39:07 +00:00
|
|
|
ohlcv_history['sell'] = 0
|
|
|
|
|
2020-06-15 06:47:43 +00:00
|
|
|
ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history)
|
|
|
|
|
|
|
|
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-07-31 05:32:27 +00:00
|
|
|
assert 'strategy' in rc.json
|
|
|
|
assert rc.json['strategy'] == 'DefaultStrategy'
|
2020-06-15 06:47:43 +00:00
|
|
|
assert 'columns' in rc.json
|
2020-07-13 19:06:10 +00:00
|
|
|
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 08:55:00+00:00'
|
|
|
|
assert rc.json['data_stop_ts'] == 1511686500000
|
2020-06-15 06:47:43 +00:00
|
|
|
assert isinstance(rc.json['columns'], list)
|
2020-07-02 06:39:07 +00:00
|
|
|
assert rc.json['columns'] == ['date', 'open', 'high',
|
|
|
|
'low', 'close', 'volume', 'sma', 'buy', 'sell',
|
2020-07-13 19:06:10 +00:00
|
|
|
'__date_ts', '_buy_signal_open', '_sell_signal_open']
|
2020-07-02 05:10:56 +00:00
|
|
|
assert 'pair' in rc.json
|
|
|
|
assert rc.json['pair'] == 'XRP/BTC'
|
2020-06-15 06:47:43 +00:00
|
|
|
|
|
|
|
assert 'data' in rc.json
|
|
|
|
assert len(rc.json['data']) == amount
|
|
|
|
|
|
|
|
assert (rc.json['data'] ==
|
2020-07-13 19:06:10 +00:00
|
|
|
[['2017-11-26 08:50:00', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, 0.0877869,
|
|
|
|
None, 0, 0, 1511686200000, None, None],
|
|
|
|
['2017-11-26 08:55:00', 8.88e-05, 8.942e-05, 8.88e-05,
|
2020-10-02 04:41:28 +00:00
|
|
|
8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0, 1511686500000, 8.88e-05, None]
|
2020-06-15 06:47:43 +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):
|
|
|
|
ftbot, client = botclient
|
|
|
|
timeframe = '5m'
|
|
|
|
|
|
|
|
# No pair
|
|
|
|
rc = client_get(client,
|
|
|
|
f"{BASE_URI}/pair_history?timeframe={timeframe}"
|
|
|
|
"&timerange=20180111-20180112&strategy=DefaultStrategy")
|
|
|
|
assert_response(rc, 400)
|
|
|
|
|
|
|
|
# No Timeframe
|
|
|
|
rc = client_get(client,
|
|
|
|
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC"
|
|
|
|
"&timerange=20180111-20180112&strategy=DefaultStrategy")
|
|
|
|
assert_response(rc, 400)
|
|
|
|
|
|
|
|
# No timerange
|
|
|
|
rc = client_get(client,
|
|
|
|
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
|
|
|
"&strategy=DefaultStrategy")
|
|
|
|
assert_response(rc, 400)
|
|
|
|
|
|
|
|
# No strategy
|
|
|
|
rc = client_get(client,
|
|
|
|
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
|
|
|
"&timerange=20180111-20180112")
|
|
|
|
assert_response(rc, 400)
|
|
|
|
|
|
|
|
# Working
|
|
|
|
rc = client_get(client,
|
|
|
|
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
|
|
|
"&timerange=20180111-20180112&strategy=DefaultStrategy")
|
|
|
|
assert_response(rc, 200)
|
|
|
|
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'] == 'DefaultStrategy'
|
|
|
|
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-06-23 04:49:53 +00:00
|
|
|
def test_api_plot_config(botclient):
|
|
|
|
ftbot, client = botclient
|
|
|
|
|
|
|
|
rc = client_get(client, f"{BASE_URI}/plot_config")
|
|
|
|
assert_response(rc)
|
|
|
|
assert rc.json == {}
|
|
|
|
|
|
|
|
ftbot.strategy.plot_config = {'main_plot': {'sma': {}},
|
|
|
|
'subplots': {'RSI': {'rsi': {'color': 'red'}}}}
|
|
|
|
rc = client_get(client, f"{BASE_URI}/plot_config")
|
|
|
|
assert_response(rc)
|
|
|
|
assert rc.json == ftbot.strategy.plot_config
|
|
|
|
assert isinstance(rc.json['main_plot'], dict)
|
2020-07-31 05:31:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_api_strategies(botclient):
|
|
|
|
ftbot, client = botclient
|
|
|
|
|
|
|
|
rc = client_get(client, f"{BASE_URI}/strategies")
|
|
|
|
|
|
|
|
assert_response(rc)
|
2020-08-01 15:12:32 +00:00
|
|
|
assert rc.json == {'strategies': ['DefaultStrategy', 'TestStrategyLegacy']}
|
2020-08-01 15:49:59 +00:00
|
|
|
|
|
|
|
|
2020-09-17 05:53:22 +00:00
|
|
|
def test_api_strategy(botclient):
|
|
|
|
ftbot, client = botclient
|
|
|
|
|
|
|
|
rc = client_get(client, f"{BASE_URI}/strategy/DefaultStrategy")
|
|
|
|
|
|
|
|
assert_response(rc)
|
|
|
|
assert rc.json['strategy'] == 'DefaultStrategy'
|
|
|
|
|
|
|
|
data = (Path(__file__).parents[1] / "strategy/strats/default_strategy.py").read_text()
|
|
|
|
assert rc.json['code'] == data
|
|
|
|
|
|
|
|
rc = client_get(client, f"{BASE_URI}/strategy/NoStrat")
|
|
|
|
assert_response(rc, 404)
|
|
|
|
|
|
|
|
|
2020-08-01 15:49:59 +00:00
|
|
|
def test_list_available_pairs(botclient):
|
|
|
|
ftbot, client = botclient
|
|
|
|
|
|
|
|
rc = client_get(client, f"{BASE_URI}/available_pairs")
|
|
|
|
|
|
|
|
assert_response(rc)
|
|
|
|
assert rc.json['length'] == 12
|
|
|
|
assert isinstance(rc.json['pairs'], list)
|
|
|
|
|
|
|
|
rc = client_get(client, f"{BASE_URI}/available_pairs?timeframe=5m")
|
|
|
|
assert_response(rc)
|
|
|
|
assert rc.json['length'] == 12
|
|
|
|
|
|
|
|
rc = client_get(client, f"{BASE_URI}/available_pairs?stake_currency=ETH")
|
|
|
|
assert_response(rc)
|
|
|
|
assert rc.json['length'] == 1
|
|
|
|
assert rc.json['pairs'] == ['XRP/ETH']
|
|
|
|
assert len(rc.json['pair_interval']) == 2
|
|
|
|
|
|
|
|
rc = client_get(client, f"{BASE_URI}/available_pairs?stake_currency=ETH&timeframe=5m")
|
|
|
|
assert_response(rc)
|
|
|
|
assert rc.json['length'] == 1
|
|
|
|
assert rc.json['pairs'] == ['XRP/ETH']
|
|
|
|
assert len(rc.json['pair_interval']) == 1
|