Merge branch 'develop' into pr/eatrisno/4308
This commit is contained in:
@@ -50,6 +50,10 @@ def test_start_new_config(mocker, caplog, exchange):
|
||||
'telegram': False,
|
||||
'telegram_token': 'asdf1244',
|
||||
'telegram_chat_id': '1144444',
|
||||
'api_server': False,
|
||||
'api_server_listen_addr': '127.0.0.1',
|
||||
'api_server_username': 'freqtrader',
|
||||
'api_server_password': 'MoneyMachine',
|
||||
}
|
||||
mocker.patch('freqtrade.commands.build_config_commands.ask_user_config',
|
||||
return_value=sample_selections)
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
import re
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
@@ -16,8 +17,8 @@ from freqtrade.commands import (start_convert_data, start_create_userdir, start_
|
||||
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
|
||||
from freqtrade.enums import RunMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.state import RunMode
|
||||
from tests.conftest import (create_mock_trades, get_args, log_has, log_has_re, patch_exchange,
|
||||
patched_configuration_load_config_file)
|
||||
from tests.conftest_trades import MOCK_TRADE_COUNT
|
||||
@@ -66,8 +67,8 @@ def test_list_exchanges(capsys):
|
||||
start_list_exchanges(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert re.match(r"Exchanges available for Freqtrade.*", captured.out)
|
||||
assert re.match(r".*binance,.*", captured.out)
|
||||
assert re.match(r".*bittrex,.*", captured.out)
|
||||
assert re.search(r".*binance.*", captured.out)
|
||||
assert re.search(r".*bittrex.*", captured.out)
|
||||
|
||||
# Test with --one-column
|
||||
args = [
|
||||
@@ -89,9 +90,9 @@ def test_list_exchanges(capsys):
|
||||
start_list_exchanges(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert re.match(r"All exchanges supported by the ccxt library.*", captured.out)
|
||||
assert re.match(r".*binance,.*", captured.out)
|
||||
assert re.match(r".*bittrex,.*", captured.out)
|
||||
assert re.match(r".*bitmex,.*", captured.out)
|
||||
assert re.search(r".*binance.*", captured.out)
|
||||
assert re.search(r".*bittrex.*", captured.out)
|
||||
assert re.search(r".*bitmex.*", captured.out)
|
||||
|
||||
# Test with --one-column --all
|
||||
args = [
|
||||
@@ -116,7 +117,7 @@ def test_list_timeframes(mocker, capsys):
|
||||
'1h': 'hour',
|
||||
'1d': 'day',
|
||||
}
|
||||
patch_exchange(mocker, api_mock=api_mock)
|
||||
patch_exchange(mocker, api_mock=api_mock, id='bittrex')
|
||||
args = [
|
||||
"list-timeframes",
|
||||
]
|
||||
@@ -201,7 +202,7 @@ def test_list_markets(mocker, markets, capsys):
|
||||
|
||||
api_mock = MagicMock()
|
||||
api_mock.markets = markets
|
||||
patch_exchange(mocker, api_mock=api_mock)
|
||||
patch_exchange(mocker, api_mock=api_mock, id='bittrex')
|
||||
|
||||
# Test with no --config
|
||||
args = [
|
||||
@@ -706,7 +707,7 @@ def test_download_data_timerange(mocker, caplog, markets):
|
||||
start_download_data(get_args(args))
|
||||
assert dl_mock.call_count == 1
|
||||
# 20days ago
|
||||
days_ago = arrow.get(arrow.utcnow().shift(days=-20).date()).int_timestamp
|
||||
days_ago = arrow.get(arrow.now().shift(days=-20).date()).int_timestamp
|
||||
assert dl_mock.call_args_list[0][1]['timerange'].startts == days_ago
|
||||
|
||||
dl_mock.reset_mock()
|
||||
@@ -914,246 +915,258 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
|
||||
]
|
||||
start_test_pairlist(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert re.match(r'Pairs for BTC: \n\["ETH/BTC","TKN/BTC","BLK/BTC","LTC/BTC","XRP/BTC"\]\n',
|
||||
captured.out)
|
||||
try:
|
||||
json_pairs = json.loads(captured.out)
|
||||
assert 'ETH/BTC' in json_pairs
|
||||
assert 'TKN/BTC' in json_pairs
|
||||
assert 'BLK/BTC' in json_pairs
|
||||
assert 'LTC/BTC' in json_pairs
|
||||
assert 'XRP/BTC' in json_pairs
|
||||
except json.decoder.JSONDecodeError:
|
||||
pytest.fail(f'Expected well formed JSON, but failed to parse: {captured.out}')
|
||||
|
||||
|
||||
def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
|
||||
def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results,
|
||||
saved_hyperopt_results_legacy, tmpdir):
|
||||
csv_file = Path(tmpdir) / "test.csv"
|
||||
for res in (saved_hyperopt_results, saved_hyperopt_results_legacy):
|
||||
mocker.patch(
|
||||
'freqtrade.optimize.hyperopt_tools.HyperoptTools.load_previous_results',
|
||||
MagicMock(return_value=res)
|
||||
)
|
||||
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12",
|
||||
" 6/12", " 7/12", " 8/12", " 9/12", " 10/12",
|
||||
" 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--best",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 1/12", " 5/12", " 10/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 2/12", " 3/12", " 4/12", " 6/12", " 7/12", " 8/12", " 9/12",
|
||||
" 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--profitable",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 2/12", " 10/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
|
||||
" 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--profitable",
|
||||
"--no-color",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 2/12", " 10/12", "Best result:", "Buy hyperspace params",
|
||||
"Sell hyperspace params", "ROI table", "Stoploss"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
|
||||
" 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--min-trades", "20",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 3/12", " 6/12", " 7/12", " 9/12", " 11/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 2/12", " 4/12", " 5/12", " 8/12", " 10/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--profitable",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--max-trades", "20",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 2/12", " 10/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
|
||||
" 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--profitable",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--min-avg-profit", "0.11",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 2/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
|
||||
" 10/12", " 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--max-avg-profit", "0.10",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 1/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
|
||||
" 11/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 2/12", " 4/12", " 10/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--min-total-profit", "0.4",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 10/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12",
|
||||
" 9/12", " 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--max-total-profit", "0.4",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 1/12", " 2/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12",
|
||||
" 9/12", " 11/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 4/12", " 10/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--min-objective", "0.1",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 10/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12",
|
||||
" 9/12", " 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--max-objective", "0.1",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 1/12", " 2/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12",
|
||||
" 9/12", " 11/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 4/12", " 10/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--profitable",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--min-avg-time", "2000",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 10/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12",
|
||||
" 8/12", " 9/12", " 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--max-avg-time", "1500",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 2/12", " 6/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 7/12", " 8/12"
|
||||
" 9/12", " 10/12", " 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--export-csv",
|
||||
str(csv_file),
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
log_has("CSV file created: test_file.csv", caplog)
|
||||
assert csv_file.is_file()
|
||||
line = csv_file.read_text()
|
||||
assert ('Best,1,2,-1.25%,-1.2222,-0.00125625,,-2.51,"3,930.0 m",0.43662' in line
|
||||
or "Best,1,2,-1.25%,-1.2222,-0.00125625,,-2.51,2 days 17:30:00,0.43662" in line)
|
||||
csv_file.unlink()
|
||||
|
||||
|
||||
def test_hyperopt_show(mocker, capsys, saved_hyperopt_results):
|
||||
mocker.patch(
|
||||
'freqtrade.optimize.hyperopt.Hyperopt.load_previous_results',
|
||||
MagicMock(return_value=hyperopt_results)
|
||||
)
|
||||
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12",
|
||||
" 6/12", " 7/12", " 8/12", " 9/12", " 10/12",
|
||||
" 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--best",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 1/12", " 5/12", " 10/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 2/12", " 3/12", " 4/12", " 6/12", " 7/12", " 8/12", " 9/12",
|
||||
" 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--profitable",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 2/12", " 10/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
|
||||
" 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--profitable",
|
||||
"--no-color",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 2/12", " 10/12", "Best result:", "Buy hyperspace params",
|
||||
"Sell hyperspace params", "ROI table", "Stoploss"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
|
||||
" 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--min-trades", "20",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 3/12", " 6/12", " 7/12", " 9/12", " 11/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 2/12", " 4/12", " 5/12", " 8/12", " 10/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--profitable",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--max-trades", "20",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 2/12", " 10/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
|
||||
" 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--profitable",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--min-avg-profit", "0.11",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 2/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
|
||||
" 10/12", " 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--max-avg-profit", "0.10",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 1/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
|
||||
" 11/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 2/12", " 4/12", " 10/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--min-total-profit", "0.4",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 10/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12",
|
||||
" 9/12", " 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--max-total-profit", "0.4",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 1/12", " 2/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12",
|
||||
" 9/12", " 11/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 4/12", " 10/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--min-objective", "0.1",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 10/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12",
|
||||
" 9/12", " 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--max-objective", "0.1",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 1/12", " 2/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12",
|
||||
" 9/12", " 11/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 4/12", " 10/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--profitable",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--min-avg-time", "2000",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 10/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12",
|
||||
" 8/12", " 9/12", " 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--max-avg-time", "1500",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert all(x in captured.out
|
||||
for x in [" 2/12", " 6/12"])
|
||||
assert all(x not in captured.out
|
||||
for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 7/12", " 8/12"
|
||||
" 9/12", " 10/12", " 11/12", " 12/12"])
|
||||
args = [
|
||||
"hyperopt-list",
|
||||
"--no-details",
|
||||
"--no-color",
|
||||
"--export-csv", "test_file.csv",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_hyperopt_list(pargs)
|
||||
captured = capsys.readouterr()
|
||||
log_has("CSV file created: test_file.csv", caplog)
|
||||
f = Path("test_file.csv")
|
||||
assert 'Best,1,2,-1.25%,-0.00125625,,-2.51,"3,930.0 m",0.43662' in f.read_text()
|
||||
assert f.is_file()
|
||||
f.unlink()
|
||||
|
||||
|
||||
def test_hyperopt_show(mocker, capsys, hyperopt_results):
|
||||
mocker.patch(
|
||||
'freqtrade.optimize.hyperopt.Hyperopt.load_previous_results',
|
||||
MagicMock(return_value=hyperopt_results)
|
||||
'freqtrade.optimize.hyperopt_tools.HyperoptTools.load_previous_results',
|
||||
MagicMock(return_value=saved_hyperopt_results)
|
||||
)
|
||||
|
||||
args = [
|
||||
|
@@ -59,7 +59,7 @@
|
||||
}
|
||||
},
|
||||
"exchange": {
|
||||
"name": "bittrex",
|
||||
"name": "binance",
|
||||
"sandbox": false,
|
||||
"key": "your_exchange_key",
|
||||
"secret": "your_exchange_secret",
|
||||
|
@@ -3,10 +3,10 @@ import json
|
||||
import logging
|
||||
import re
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from functools import reduce
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
from unittest.mock import MagicMock, Mock, PropertyMock
|
||||
|
||||
import arrow
|
||||
import numpy as np
|
||||
@@ -17,9 +17,10 @@ from freqtrade import constants
|
||||
from freqtrade.commands import Arguments
|
||||
from freqtrade.data.converter import ohlcv_to_dataframe
|
||||
from freqtrade.edge import Edge, PairInfo
|
||||
from freqtrade.enums import RunMode
|
||||
from freqtrade.exchange import Exchange
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
from freqtrade.persistence import Trade, init_db
|
||||
from freqtrade.persistence import LocalTrade, Trade, init_db
|
||||
from freqtrade.resolvers import ExchangeResolver
|
||||
from freqtrade.worker import Worker
|
||||
from tests.conftest_trades import (mock_trade_1, mock_trade_2, mock_trade_3, mock_trade_4,
|
||||
@@ -64,6 +65,14 @@ def get_args(args):
|
||||
return Arguments(args).get_parsed_arg()
|
||||
|
||||
|
||||
# Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines
|
||||
def get_mock_coro(return_value):
|
||||
async def mock_coro(*args, **kwargs):
|
||||
return return_value
|
||||
|
||||
return Mock(wraps=mock_coro)
|
||||
|
||||
|
||||
def patched_configuration_load_config_file(mocker, config) -> None:
|
||||
mocker.patch(
|
||||
'freqtrade.configuration.configuration.load_config_file',
|
||||
@@ -71,7 +80,7 @@ def patched_configuration_load_config_file(mocker, config) -> None:
|
||||
)
|
||||
|
||||
|
||||
def patch_exchange(mocker, api_mock=None, id='bittrex', mock_markets=True) -> None:
|
||||
def patch_exchange(mocker, api_mock=None, id='binance', mock_markets=True) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock(return_value={}))
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
|
||||
@@ -90,7 +99,7 @@ def patch_exchange(mocker, api_mock=None, id='bittrex', mock_markets=True) -> No
|
||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock())
|
||||
|
||||
|
||||
def get_patched_exchange(mocker, config, api_mock=None, id='bittrex',
|
||||
def get_patched_exchange(mocker, config, api_mock=None, id='binance',
|
||||
mock_markets=True) -> Exchange:
|
||||
patch_exchange(mocker, api_mock, id, mock_markets)
|
||||
config['exchange']['name'] = id
|
||||
@@ -183,28 +192,37 @@ def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None:
|
||||
freqtrade.exchange.refresh_latest_ohlcv = lambda p: None
|
||||
|
||||
|
||||
def create_mock_trades(fee):
|
||||
def create_mock_trades(fee, use_db: bool = True):
|
||||
"""
|
||||
Create some fake trades ...
|
||||
"""
|
||||
def add_trade(trade):
|
||||
if use_db:
|
||||
Trade.query.session.add(trade)
|
||||
else:
|
||||
LocalTrade.add_bt_trade(trade)
|
||||
|
||||
# Simulate dry_run entries
|
||||
trade = mock_trade_1(fee)
|
||||
Trade.session.add(trade)
|
||||
add_trade(trade)
|
||||
|
||||
trade = mock_trade_2(fee)
|
||||
Trade.session.add(trade)
|
||||
add_trade(trade)
|
||||
|
||||
trade = mock_trade_3(fee)
|
||||
Trade.session.add(trade)
|
||||
add_trade(trade)
|
||||
|
||||
trade = mock_trade_4(fee)
|
||||
Trade.session.add(trade)
|
||||
add_trade(trade)
|
||||
|
||||
trade = mock_trade_5(fee)
|
||||
Trade.session.add(trade)
|
||||
add_trade(trade)
|
||||
|
||||
trade = mock_trade_6(fee)
|
||||
Trade.session.add(trade)
|
||||
add_trade(trade)
|
||||
|
||||
if use_db:
|
||||
Trade.query.session.flush()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@@ -255,6 +273,7 @@ def get_default_conf(testdatadir):
|
||||
"20": 0.02,
|
||||
"0": 0.04
|
||||
},
|
||||
"dry_run_wallet": 1000,
|
||||
"stoploss": -0.10,
|
||||
"unfilledtimeout": {
|
||||
"buy": 10,
|
||||
@@ -275,7 +294,7 @@ def get_default_conf(testdatadir):
|
||||
"order_book_max": 1
|
||||
},
|
||||
"exchange": {
|
||||
"name": "bittrex",
|
||||
"name": "binance",
|
||||
"enabled": True,
|
||||
"key": "key",
|
||||
"secret": "secret",
|
||||
@@ -296,7 +315,8 @@ def get_default_conf(testdatadir):
|
||||
"telegram": {
|
||||
"enabled": True,
|
||||
"token": "token",
|
||||
"chat_id": "0"
|
||||
"chat_id": "0",
|
||||
"notification_settings": {},
|
||||
},
|
||||
"datadir": str(testdatadir),
|
||||
"initial_state": "running",
|
||||
@@ -1658,6 +1678,7 @@ def buy_order_fee():
|
||||
@pytest.fixture(scope="function")
|
||||
def edge_conf(default_conf):
|
||||
conf = deepcopy(default_conf)
|
||||
conf['runmode'] = RunMode.DRY_RUN
|
||||
conf['max_open_trades'] = -1
|
||||
conf['tradable_balance_ratio'] = 0.5
|
||||
conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
|
||||
@@ -1729,7 +1750,7 @@ def import_fails() -> None:
|
||||
realimport = builtins.__import__
|
||||
|
||||
def mockedimport(name, *args, **kwargs):
|
||||
if name in ["filelock", 'systemd.journal']:
|
||||
if name in ["filelock", 'systemd.journal', 'uvloop']:
|
||||
raise ImportError(f"No module named '{name}'")
|
||||
return realimport(name, *args, **kwargs)
|
||||
|
||||
@@ -1747,7 +1768,7 @@ def open_trade():
|
||||
return Trade(
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee_open=0.0,
|
||||
@@ -1759,14 +1780,14 @@ def open_trade():
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def hyperopt_results():
|
||||
def saved_hyperopt_results_legacy():
|
||||
return [
|
||||
{
|
||||
'loss': 0.4366182531161519,
|
||||
'params_dict': {
|
||||
'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1190, 'roi_t2': 541, 'roi_t3': 408, 'roi_p1': 0.026035863879169705, 'roi_p2': 0.12508730043628782, 'roi_p3': 0.27766427921605896, 'stoploss': -0.2562930402099556}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4287874435315165, 408: 0.15112316431545753, 949: 0.026035863879169705, 2139: 0}, 'stoploss': {'stoploss': -0.2562930402099556}}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 2, 'avg_profit': -1.254995, 'total_profit': -0.00125625, 'profit': -2.50999, 'duration': 3930.0}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 2, 'avg_profit': -1.254995, 'median_profit': -1.2222, 'total_profit': -0.00125625, 'profit': -2.50999, 'duration': 3930.0}, # noqa: E501
|
||||
'results_explanation': ' 2 trades. Avg profit -1.25%. Total profit -0.00125625 BTC ( -2.51Σ%). Avg duration 3930.0 min.', # noqa: E501
|
||||
'total_profit': -0.00125625,
|
||||
'current_epoch': 1,
|
||||
@@ -1781,7 +1802,7 @@ def hyperopt_results():
|
||||
'sell': {'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, # noqa: E501
|
||||
'roi': {0: 0.4449309386008759, 140: 0.11955965746663, 823: 0.06403981740598495, 1157: 0}, # noqa: E501
|
||||
'stoploss': {'stoploss': -0.338070047333259}},
|
||||
'results_metrics': {'trade_count': 1, 'avg_profit': 0.12357, 'total_profit': 6.185e-05, 'profit': 0.12357, 'duration': 1200.0}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 1, 'avg_profit': 0.12357, 'median_profit': -1.2222, 'total_profit': 6.185e-05, 'profit': 0.12357, 'duration': 1200.0}, # noqa: E501
|
||||
'results_explanation': ' 1 trades. Avg profit 0.12%. Total profit 0.00006185 BTC ( 0.12Σ%). Avg duration 1200.0 min.', # noqa: E501
|
||||
'total_profit': 6.185e-05,
|
||||
'current_epoch': 2,
|
||||
@@ -1791,7 +1812,7 @@ def hyperopt_results():
|
||||
'loss': 14.241196856510731,
|
||||
'params_dict': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 889, 'roi_t2': 533, 'roi_t3': 263, 'roi_p1': 0.04759065393663096, 'roi_p2': 0.1488819964638463, 'roi_p3': 0.4102801822104605, 'stoploss': -0.05394588767607611}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.6067528326109377, 263: 0.19647265040047726, 796: 0.04759065393663096, 1685: 0}, 'stoploss': {'stoploss': -0.05394588767607611}}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 621, 'avg_profit': -0.43883302093397747, 'total_profit': -0.13639474, 'profit': -272.515306, 'duration': 1691.207729468599}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 621, 'avg_profit': -0.43883302093397747, 'median_profit': -1.2222, 'total_profit': -0.13639474, 'profit': -272.515306, 'duration': 1691.207729468599}, # noqa: E501
|
||||
'results_explanation': ' 621 trades. Avg profit -0.44%. Total profit -0.13639474 BTC (-272.52Σ%). Avg duration 1691.2 min.', # noqa: E501
|
||||
'total_profit': -0.13639474,
|
||||
'current_epoch': 3,
|
||||
@@ -1801,14 +1822,14 @@ def hyperopt_results():
|
||||
'loss': 100000,
|
||||
'params_dict': {'mfi-value': 13, 'fastd-value': 35, 'adx-value': 39, 'rsi-value': 29, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 87, 'sell-fastd-value': 54, 'sell-adx-value': 63, 'sell-rsi-value': 93, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1402, 'roi_t2': 676, 'roi_t3': 215, 'roi_p1': 0.06264755784937427, 'roi_p2': 0.14258587851894644, 'roi_p3': 0.20671291201040828, 'stoploss': -0.11818343570194478}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 13, 'fastd-value': 35, 'adx-value': 39, 'rsi-value': 29, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 54, 'sell-adx-value': 63, 'sell-rsi-value': 93, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.411946348378729, 215: 0.2052334363683207, 891: 0.06264755784937427, 2293: 0}, 'stoploss': {'stoploss': -0.11818343570194478}}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 0, 'avg_profit': None, 'total_profit': 0, 'profit': 0.0, 'duration': None}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 0, 'avg_profit': None, 'median_profit': None, 'total_profit': 0, 'profit': 0.0, 'duration': None}, # noqa: E501
|
||||
'results_explanation': ' 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.', # noqa: E501
|
||||
'total_profit': 0, 'current_epoch': 4, 'is_initial_point': True, 'is_best': False
|
||||
}, {
|
||||
'loss': 0.22195522184191518,
|
||||
'params_dict': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 1269, 'roi_t2': 601, 'roi_t3': 444, 'roi_p1': 0.07280999507931168, 'roi_p2': 0.08946698095898986, 'roi_p3': 0.1454876733325284, 'stoploss': -0.18181041180901014}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3077646493708299, 444: 0.16227697603830155, 1045: 0.07280999507931168, 2314: 0}, 'stoploss': {'stoploss': -0.18181041180901014}}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 14, 'avg_profit': -0.3539515, 'total_profit': -0.002480140000000001, 'profit': -4.955321, 'duration': 3402.8571428571427}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 14, 'avg_profit': -0.3539515, 'median_profit': -1.2222, 'total_profit': -0.002480140000000001, 'profit': -4.955321, 'duration': 3402.8571428571427}, # noqa: E501
|
||||
'results_explanation': ' 14 trades. Avg profit -0.35%. Total profit -0.00248014 BTC ( -4.96Σ%). Avg duration 3402.9 min.', # noqa: E501
|
||||
'total_profit': -0.002480140000000001,
|
||||
'current_epoch': 5,
|
||||
@@ -1818,7 +1839,7 @@ def hyperopt_results():
|
||||
'loss': 0.545315889154162,
|
||||
'params_dict': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower', 'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 319, 'roi_t2': 556, 'roi_t3': 216, 'roi_p1': 0.06251955472249589, 'roi_p2': 0.11659519602202795, 'roi_p3': 0.0953744132197762, 'stoploss': -0.024551752215582423}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.2744891639643, 216: 0.17911475074452382, 772: 0.06251955472249589, 1091: 0}, 'stoploss': {'stoploss': -0.024551752215582423}}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 39, 'avg_profit': -0.21400679487179478, 'total_profit': -0.0041773, 'profit': -8.346264999999997, 'duration': 636.9230769230769}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 39, 'avg_profit': -0.21400679487179478, 'median_profit': -1.2222, 'total_profit': -0.0041773, 'profit': -8.346264999999997, 'duration': 636.9230769230769}, # noqa: E501
|
||||
'results_explanation': ' 39 trades. Avg profit -0.21%. Total profit -0.00417730 BTC ( -8.35Σ%). Avg duration 636.9 min.', # noqa: E501
|
||||
'total_profit': -0.0041773,
|
||||
'current_epoch': 6,
|
||||
@@ -1830,7 +1851,7 @@ def hyperopt_results():
|
||||
'params_details': {
|
||||
'buy': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.4837436938134452, 145: 0.10853310701097472, 765: 0.0586919200378493, 1536: 0}, # noqa: E501
|
||||
'stoploss': {'stoploss': -0.14613268022709905}}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 318, 'avg_profit': -0.39833954716981146, 'total_profit': -0.06339929, 'profit': -126.67197600000004, 'duration': 3140.377358490566}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 318, 'avg_profit': -0.39833954716981146, 'median_profit': -1.2222, 'total_profit': -0.06339929, 'profit': -126.67197600000004, 'duration': 3140.377358490566}, # noqa: E501
|
||||
'results_explanation': ' 318 trades. Avg profit -0.40%. Total profit -0.06339929 BTC (-126.67Σ%). Avg duration 3140.4 min.', # noqa: E501
|
||||
'total_profit': -0.06339929,
|
||||
'current_epoch': 7,
|
||||
@@ -1840,7 +1861,7 @@ def hyperopt_results():
|
||||
'loss': 20.0, # noqa: E501
|
||||
'params_dict': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal', 'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 1149, 'roi_t2': 375, 'roi_t3': 289, 'roi_p1': 0.05571820757172588, 'roi_p2': 0.0606240398618907, 'roi_p3': 0.1729012220156157, 'stoploss': -0.1588514289110401}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.2892434694492323, 289: 0.11634224743361658, 664: 0.05571820757172588, 1813: 0}, 'stoploss': {'stoploss': -0.1588514289110401}}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 1, 'avg_profit': 0.0, 'total_profit': 0.0, 'profit': 0.0, 'duration': 5340.0}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 1, 'avg_profit': 0.0, 'median_profit': 0.0, 'total_profit': 0.0, 'profit': 0.0, 'duration': 5340.0}, # noqa: E501
|
||||
'results_explanation': ' 1 trades. Avg profit 0.00%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration 5340.0 min.', # noqa: E501
|
||||
'total_profit': 0.0,
|
||||
'current_epoch': 8,
|
||||
@@ -1850,7 +1871,7 @@ def hyperopt_results():
|
||||
'loss': 2.4731817780991223,
|
||||
'params_dict': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1012, 'roi_t2': 584, 'roi_t3': 422, 'roi_p1': 0.036764323603472565, 'roi_p2': 0.10335480573205287, 'roi_p3': 0.10322347377503042, 'stoploss': -0.2780610808108503}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.2433426031105559, 422: 0.14011912933552545, 1006: 0.036764323603472565, 2018: 0}, 'stoploss': {'stoploss': -0.2780610808108503}}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 229, 'avg_profit': -0.38433433624454144, 'total_profit': -0.044050070000000004, 'profit': -88.01256299999999, 'duration': 6505.676855895196}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 229, 'avg_profit': -0.38433433624454144, 'median_profit': -1.2222, 'total_profit': -0.044050070000000004, 'profit': -88.01256299999999, 'duration': 6505.676855895196}, # noqa: E501
|
||||
'results_explanation': ' 229 trades. Avg profit -0.38%. Total profit -0.04405007 BTC ( -88.01Σ%). Avg duration 6505.7 min.', # noqa: E501
|
||||
'total_profit': -0.044050070000000004, # noqa: E501
|
||||
'current_epoch': 9,
|
||||
@@ -1860,7 +1881,7 @@ def hyperopt_results():
|
||||
'loss': -0.2604606005845212, # noqa: E501
|
||||
'params_dict': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 792, 'roi_t2': 464, 'roi_t3': 215, 'roi_p1': 0.04594053535385903, 'roi_p2': 0.09623192684243963, 'roi_p3': 0.04428219070850663, 'stoploss': -0.16992287161634415}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.18645465290480528, 215: 0.14217246219629864, 679: 0.04594053535385903, 1471: 0}, 'stoploss': {'stoploss': -0.16992287161634415}}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 4, 'avg_profit': 0.1080385, 'total_profit': 0.00021629, 'profit': 0.432154, 'duration': 2850.0}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 4, 'avg_profit': 0.1080385, 'median_profit': -1.2222, 'total_profit': 0.00021629, 'profit': 0.432154, 'duration': 2850.0}, # noqa: E501
|
||||
'results_explanation': ' 4 trades. Avg profit 0.11%. Total profit 0.00021629 BTC ( 0.43Σ%). Avg duration 2850.0 min.', # noqa: E501
|
||||
'total_profit': 0.00021629,
|
||||
'current_epoch': 10,
|
||||
@@ -1870,7 +1891,7 @@ def hyperopt_results():
|
||||
'loss': 4.876465945994304, # noqa: E501
|
||||
'params_dict': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower', 'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 579, 'roi_t2': 614, 'roi_t3': 273, 'roi_p1': 0.05307643172744114, 'roi_p2': 0.1352282078262871, 'roi_p3': 0.1913307406325751, 'stoploss': -0.25728526022513887}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3796353801863034, 273: 0.18830463955372825, 887: 0.05307643172744114, 1466: 0}, 'stoploss': {'stoploss': -0.25728526022513887}}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 117, 'avg_profit': -1.2698609145299145, 'total_profit': -0.07436117, 'profit': -148.573727, 'duration': 4282.5641025641025}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 117, 'avg_profit': -1.2698609145299145, 'median_profit': -1.2222, 'total_profit': -0.07436117, 'profit': -148.573727, 'duration': 4282.5641025641025}, # noqa: E501
|
||||
'results_explanation': ' 117 trades. Avg profit -1.27%. Total profit -0.07436117 BTC (-148.57Σ%). Avg duration 4282.6 min.', # noqa: E501
|
||||
'total_profit': -0.07436117,
|
||||
'current_epoch': 11,
|
||||
@@ -1880,7 +1901,7 @@ def hyperopt_results():
|
||||
'loss': 100000,
|
||||
'params_dict': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1156, 'roi_t2': 581, 'roi_t3': 408, 'roi_p1': 0.06860454019988212, 'roi_p2': 0.12473718444931989, 'roi_p3': 0.2896360635226823, 'stoploss': -0.30889015124682806}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4829777881718843, 408: 0.19334172464920202, 989: 0.06860454019988212, 2145: 0}, 'stoploss': {'stoploss': -0.30889015124682806}}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 0, 'avg_profit': None, 'total_profit': 0, 'profit': 0.0, 'duration': None}, # noqa: E501
|
||||
'results_metrics': {'trade_count': 0, 'avg_profit': None, 'median_profit': None, 'total_profit': 0, 'profit': 0.0, 'duration': None}, # noqa: E501
|
||||
'results_explanation': ' 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.', # noqa: E501
|
||||
'total_profit': 0,
|
||||
'current_epoch': 12,
|
||||
@@ -1888,3 +1909,142 @@ def hyperopt_results():
|
||||
'is_best': False
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def saved_hyperopt_results():
|
||||
hyperopt_res = [
|
||||
{
|
||||
'loss': 0.4366182531161519,
|
||||
'params_dict': {
|
||||
'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1190, 'roi_t2': 541, 'roi_t3': 408, 'roi_p1': 0.026035863879169705, 'roi_p2': 0.12508730043628782, 'roi_p3': 0.27766427921605896, 'stoploss': -0.2562930402099556}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4287874435315165, 408: 0.15112316431545753, 949: 0.026035863879169705, 2139: 0}, 'stoploss': {'stoploss': -0.2562930402099556}}, # noqa: E501
|
||||
'results_metrics': {'total_trades': 2, 'wins': 0, 'draws': 0, 'losses': 2, 'profit_mean': -0.01254995, 'profit_median': -0.012222, 'profit_total': -0.00125625, 'profit_total_abs': -2.50999, 'holding_avg': timedelta(minutes=3930.0)}, # noqa: E501
|
||||
'results_explanation': ' 2 trades. Avg profit -1.25%. Total profit -0.00125625 BTC ( -2.51Σ%). Avg duration 3930.0 min.', # noqa: E501
|
||||
'total_profit': -0.00125625,
|
||||
'current_epoch': 1,
|
||||
'is_initial_point': True,
|
||||
'is_best': True
|
||||
}, {
|
||||
'loss': 20.0,
|
||||
'params_dict': {
|
||||
'mfi-value': 17, 'fastd-value': 38, 'adx-value': 48, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 334, 'roi_t2': 683, 'roi_t3': 140, 'roi_p1': 0.06403981740598495, 'roi_p2': 0.055519840060645045, 'roi_p3': 0.3253712811342459, 'stoploss': -0.338070047333259}, # noqa: E501
|
||||
'params_details': {
|
||||
'buy': {'mfi-value': 17, 'fastd-value': 38, 'adx-value': 48, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, # noqa: E501
|
||||
'sell': {'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, # noqa: E501
|
||||
'roi': {0: 0.4449309386008759, 140: 0.11955965746663, 823: 0.06403981740598495, 1157: 0}, # noqa: E501
|
||||
'stoploss': {'stoploss': -0.338070047333259}},
|
||||
'results_metrics': {'total_trades': 1, 'wins': 0, 'draws': 0, 'losses': 1, 'profit_mean': 0.012357, 'profit_median': -0.012222, 'profit_total': 6.185e-05, 'profit_total_abs': 0.12357, 'holding_avg': timedelta(minutes=1200.0)}, # noqa: E501
|
||||
'results_explanation': ' 1 trades. Avg profit 0.12%. Total profit 0.00006185 BTC ( 0.12Σ%). Avg duration 1200.0 min.', # noqa: E501
|
||||
'total_profit': 6.185e-05,
|
||||
'current_epoch': 2,
|
||||
'is_initial_point': True,
|
||||
'is_best': False
|
||||
}, {
|
||||
'loss': 14.241196856510731,
|
||||
'params_dict': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 889, 'roi_t2': 533, 'roi_t3': 263, 'roi_p1': 0.04759065393663096, 'roi_p2': 0.1488819964638463, 'roi_p3': 0.4102801822104605, 'stoploss': -0.05394588767607611}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.6067528326109377, 263: 0.19647265040047726, 796: 0.04759065393663096, 1685: 0}, 'stoploss': {'stoploss': -0.05394588767607611}}, # noqa: E501
|
||||
'results_metrics': {'total_trades': 621, 'wins': 320, 'draws': 0, 'losses': 301, 'profit_mean': -0.043883302093397747, 'profit_median': -0.012222, 'profit_total': -0.13639474, 'profit_total_abs': -272.515306, 'holding_avg': timedelta(minutes=1691.207729468599)}, # noqa: E501
|
||||
'results_explanation': ' 621 trades. Avg profit -0.44%. Total profit -0.13639474 BTC (-272.52Σ%). Avg duration 1691.2 min.', # noqa: E501
|
||||
'total_profit': -0.13639474,
|
||||
'current_epoch': 3,
|
||||
'is_initial_point': True,
|
||||
'is_best': False
|
||||
}, {
|
||||
'loss': 100000,
|
||||
'params_dict': {'mfi-value': 13, 'fastd-value': 35, 'adx-value': 39, 'rsi-value': 29, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 87, 'sell-fastd-value': 54, 'sell-adx-value': 63, 'sell-rsi-value': 93, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1402, 'roi_t2': 676, 'roi_t3': 215, 'roi_p1': 0.06264755784937427, 'roi_p2': 0.14258587851894644, 'roi_p3': 0.20671291201040828, 'stoploss': -0.11818343570194478}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 13, 'fastd-value': 35, 'adx-value': 39, 'rsi-value': 29, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 54, 'sell-adx-value': 63, 'sell-rsi-value': 93, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.411946348378729, 215: 0.2052334363683207, 891: 0.06264755784937427, 2293: 0}, 'stoploss': {'stoploss': -0.11818343570194478}}, # noqa: E501
|
||||
'results_metrics': {'total_trades': 0, 'wins': 0, 'draws': 0, 'losses': 0, 'profit_mean': None, 'profit_median': None, 'profit_total': 0, 'profit': 0.0, 'holding_avg': timedelta()}, # noqa: E501
|
||||
'results_explanation': ' 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.', # noqa: E501
|
||||
'total_profit': 0, 'current_epoch': 4, 'is_initial_point': True, 'is_best': False
|
||||
}, {
|
||||
'loss': 0.22195522184191518,
|
||||
'params_dict': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 1269, 'roi_t2': 601, 'roi_t3': 444, 'roi_p1': 0.07280999507931168, 'roi_p2': 0.08946698095898986, 'roi_p3': 0.1454876733325284, 'stoploss': -0.18181041180901014}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3077646493708299, 444: 0.16227697603830155, 1045: 0.07280999507931168, 2314: 0}, 'stoploss': {'stoploss': -0.18181041180901014}}, # noqa: E501
|
||||
'results_metrics': {'total_trades': 14, 'wins': 6, 'draws': 0, 'losses': 8, 'profit_mean': -0.003539515, 'profit_median': -0.012222, 'profit_total': -0.002480140000000001, 'profit_total_abs': -4.955321, 'holding_avg': timedelta(minutes=3402.8571428571427)}, # noqa: E501
|
||||
'results_explanation': ' 14 trades. Avg profit -0.35%. Total profit -0.00248014 BTC ( -4.96Σ%). Avg duration 3402.9 min.', # noqa: E501
|
||||
'total_profit': -0.002480140000000001,
|
||||
'current_epoch': 5,
|
||||
'is_initial_point': True,
|
||||
'is_best': True
|
||||
}, {
|
||||
'loss': 0.545315889154162,
|
||||
'params_dict': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower', 'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 319, 'roi_t2': 556, 'roi_t3': 216, 'roi_p1': 0.06251955472249589, 'roi_p2': 0.11659519602202795, 'roi_p3': 0.0953744132197762, 'stoploss': -0.024551752215582423}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.2744891639643, 216: 0.17911475074452382, 772: 0.06251955472249589, 1091: 0}, 'stoploss': {'stoploss': -0.024551752215582423}}, # noqa: E501
|
||||
'results_metrics': {'total_trades': 39, 'wins': 20, 'draws': 0, 'losses': 19, 'profit_mean': -0.0021400679487179478, 'profit_median': -0.012222, 'profit_total': -0.0041773, 'profit_total_abs': -8.346264999999997, 'holding_avg': timedelta(minutes=636.9230769230769)}, # noqa: E501
|
||||
'results_explanation': ' 39 trades. Avg profit -0.21%. Total profit -0.00417730 BTC ( -8.35Σ%). Avg duration 636.9 min.', # noqa: E501
|
||||
'total_profit': -0.0041773,
|
||||
'current_epoch': 6,
|
||||
'is_initial_point': True,
|
||||
'is_best': False
|
||||
}, {
|
||||
'loss': 4.713497421432944,
|
||||
'params_dict': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower', 'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 771, 'roi_t2': 620, 'roi_t3': 145, 'roi_p1': 0.0586919200378493, 'roi_p2': 0.04984118697312542, 'roi_p3': 0.37521058680247044, 'stoploss': -0.14613268022709905}, # noqa: E501
|
||||
'params_details': {
|
||||
'buy': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.4837436938134452, 145: 0.10853310701097472, 765: 0.0586919200378493, 1536: 0}, # noqa: E501
|
||||
'stoploss': {'stoploss': -0.14613268022709905}}, # noqa: E501
|
||||
'results_metrics': {'total_trades': 318, 'wins': 100, 'draws': 0, 'losses': 218, 'profit_mean': -0.0039833954716981146, 'profit_median': -0.012222, 'profit_total': -0.06339929, 'profit_total_abs': -126.67197600000004, 'holding_avg': timedelta(minutes=3140.377358490566)}, # noqa: E501
|
||||
'results_explanation': ' 318 trades. Avg profit -0.40%. Total profit -0.06339929 BTC (-126.67Σ%). Avg duration 3140.4 min.', # noqa: E501
|
||||
'total_profit': -0.06339929,
|
||||
'current_epoch': 7,
|
||||
'is_initial_point': True,
|
||||
'is_best': False
|
||||
}, {
|
||||
'loss': 20.0, # noqa: E501
|
||||
'params_dict': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal', 'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 1149, 'roi_t2': 375, 'roi_t3': 289, 'roi_p1': 0.05571820757172588, 'roi_p2': 0.0606240398618907, 'roi_p3': 0.1729012220156157, 'stoploss': -0.1588514289110401}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.2892434694492323, 289: 0.11634224743361658, 664: 0.05571820757172588, 1813: 0}, 'stoploss': {'stoploss': -0.1588514289110401}}, # noqa: E501
|
||||
'results_metrics': {'total_trades': 1, 'wins': 0, 'draws': 1, 'losses': 0, 'profit_mean': 0.0, 'profit_median': 0.0, 'profit_total': 0.0, 'profit_total_abs': 0.0, 'holding_avg': timedelta(minutes=5340.0)}, # noqa: E501
|
||||
'results_explanation': ' 1 trades. Avg profit 0.00%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration 5340.0 min.', # noqa: E501
|
||||
'total_profit': 0.0,
|
||||
'current_epoch': 8,
|
||||
'is_initial_point': True,
|
||||
'is_best': False
|
||||
}, {
|
||||
'loss': 2.4731817780991223,
|
||||
'params_dict': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1012, 'roi_t2': 584, 'roi_t3': 422, 'roi_p1': 0.036764323603472565, 'roi_p2': 0.10335480573205287, 'roi_p3': 0.10322347377503042, 'stoploss': -0.2780610808108503}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.2433426031105559, 422: 0.14011912933552545, 1006: 0.036764323603472565, 2018: 0}, 'stoploss': {'stoploss': -0.2780610808108503}}, # noqa: E501
|
||||
'results_metrics': {'total_trades': 229, 'wins': 150, 'draws': 0, 'losses': 79, 'profit_mean': -0.0038433433624454144, 'profit_median': -0.012222, 'profit_total': -0.044050070000000004, 'profit_total_abs': -88.01256299999999, 'holding_avg': timedelta(minutes=6505.676855895196)}, # noqa: E501
|
||||
'results_explanation': ' 229 trades. Avg profit -0.38%. Total profit -0.04405007 BTC ( -88.01Σ%). Avg duration 6505.7 min.', # noqa: E501
|
||||
'total_profit': -0.044050070000000004, # noqa: E501
|
||||
'current_epoch': 9,
|
||||
'is_initial_point': True,
|
||||
'is_best': False
|
||||
}, {
|
||||
'loss': -0.2604606005845212, # noqa: E501
|
||||
'params_dict': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 792, 'roi_t2': 464, 'roi_t3': 215, 'roi_p1': 0.04594053535385903, 'roi_p2': 0.09623192684243963, 'roi_p3': 0.04428219070850663, 'stoploss': -0.16992287161634415}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.18645465290480528, 215: 0.14217246219629864, 679: 0.04594053535385903, 1471: 0}, 'stoploss': {'stoploss': -0.16992287161634415}}, # noqa: E501
|
||||
'results_metrics': {'total_trades': 4, 'wins': 0, 'draws': 0, 'losses': 4, 'profit_mean': 0.001080385, 'profit_median': -0.012222, 'profit_total': 0.00021629, 'profit_total_abs': 0.432154, 'holding_avg': timedelta(minutes=2850.0)}, # noqa: E501
|
||||
'results_explanation': ' 4 trades. Avg profit 0.11%. Total profit 0.00021629 BTC ( 0.43Σ%). Avg duration 2850.0 min.', # noqa: E501
|
||||
'total_profit': 0.00021629,
|
||||
'current_epoch': 10,
|
||||
'is_initial_point': True,
|
||||
'is_best': True
|
||||
}, {
|
||||
'loss': 4.876465945994304, # noqa: E501
|
||||
'params_dict': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower', 'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 579, 'roi_t2': 614, 'roi_t3': 273, 'roi_p1': 0.05307643172744114, 'roi_p2': 0.1352282078262871, 'roi_p3': 0.1913307406325751, 'stoploss': -0.25728526022513887}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3796353801863034, 273: 0.18830463955372825, 887: 0.05307643172744114, 1466: 0}, 'stoploss': {'stoploss': -0.25728526022513887}}, # noqa: E501
|
||||
# New Hyperopt mode!
|
||||
'results_metrics': {'total_trades': 117, 'wins': 67, 'draws': 0, 'losses': 50, 'profit_mean': -0.012698609145299145, 'profit_median': -0.012222, 'profit_total': -0.07436117, 'profit_total_abs': -148.573727, 'holding_avg': timedelta(minutes=4282.5641025641025)}, # noqa: E501
|
||||
'results_explanation': ' 117 trades. Avg profit -1.27%. Total profit -0.07436117 BTC (-148.57Σ%). Avg duration 4282.6 min.', # noqa: E501
|
||||
'total_profit': -0.07436117,
|
||||
'current_epoch': 11,
|
||||
'is_initial_point': True,
|
||||
'is_best': False
|
||||
}, {
|
||||
'loss': 100000,
|
||||
'params_dict': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1156, 'roi_t2': 581, 'roi_t3': 408, 'roi_p1': 0.06860454019988212, 'roi_p2': 0.12473718444931989, 'roi_p3': 0.2896360635226823, 'stoploss': -0.30889015124682806}, # noqa: E501
|
||||
'params_details': {'buy': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4829777881718843, 408: 0.19334172464920202, 989: 0.06860454019988212, 2145: 0}, 'stoploss': {'stoploss': -0.30889015124682806}}, # noqa: E501
|
||||
'results_metrics': {'total_trades': 0, 'wins': 0, 'draws': 0, 'losses': 0, 'profit_mean': None, 'profit_median': None, 'profit_total': 0, 'profit_total_abs': 0.0, 'holding_avg': timedelta()}, # noqa: E501
|
||||
'results_explanation': ' 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.', # noqa: E501
|
||||
'total_profit': 0,
|
||||
'current_epoch': 12,
|
||||
'is_initial_point': True,
|
||||
'is_best': False
|
||||
}
|
||||
]
|
||||
|
||||
for res in hyperopt_res:
|
||||
res['results_metrics']['holding_avg_s'] = res['results_metrics']['holding_avg'
|
||||
].total_seconds()
|
||||
|
||||
return hyperopt_res
|
||||
|
@@ -28,8 +28,10 @@ def mock_trade_1(fee):
|
||||
amount_requested=123.0,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
is_open=True,
|
||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=17),
|
||||
open_rate=0.123,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
open_order_id='dry_run_buy_12345',
|
||||
strategy='DefaultStrategy',
|
||||
timeframe=5,
|
||||
@@ -81,14 +83,15 @@ def mock_trade_2(fee):
|
||||
open_rate=0.123,
|
||||
close_rate=0.128,
|
||||
close_profit=0.005,
|
||||
exchange='bittrex',
|
||||
close_profit_abs=0.000584127,
|
||||
exchange='binance',
|
||||
is_open=False,
|
||||
open_order_id='dry_run_sell_12345',
|
||||
strategy='DefaultStrategy',
|
||||
timeframe=5,
|
||||
sell_reason='sell_signal',
|
||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
||||
close_date=datetime.now(tz=timezone.utc),
|
||||
close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
|
||||
)
|
||||
o = Order.parse_from_ccxt_object(mock_order_2(), 'ETC/BTC', 'buy')
|
||||
trade.orders.append(o)
|
||||
@@ -140,7 +143,8 @@ def mock_trade_3(fee):
|
||||
open_rate=0.05,
|
||||
close_rate=0.06,
|
||||
close_profit=0.01,
|
||||
exchange='bittrex',
|
||||
close_profit_abs=0.000155,
|
||||
exchange='binance',
|
||||
is_open=False,
|
||||
strategy='DefaultStrategy',
|
||||
timeframe=5,
|
||||
@@ -180,8 +184,10 @@ def mock_trade_4(fee):
|
||||
amount_requested=124.0,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=14),
|
||||
is_open=True,
|
||||
open_rate=0.123,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
open_order_id='prod_buy_12345',
|
||||
strategy='DefaultStrategy',
|
||||
timeframe=5,
|
||||
@@ -230,10 +236,13 @@ def mock_trade_5(fee):
|
||||
amount_requested=124.0,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=12),
|
||||
is_open=True,
|
||||
open_rate=0.123,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
strategy='SampleStrategy',
|
||||
stoploss_order_id='prod_stoploss_3455'
|
||||
stoploss_order_id='prod_stoploss_3455',
|
||||
timeframe=5,
|
||||
)
|
||||
o = Order.parse_from_ccxt_object(mock_order_5(), 'XRP/BTC', 'buy')
|
||||
trade.orders.append(o)
|
||||
@@ -279,12 +288,15 @@ def mock_trade_6(fee):
|
||||
stake_amount=0.001,
|
||||
amount=2.0,
|
||||
amount_requested=2.0,
|
||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
is_open=True,
|
||||
open_rate=0.15,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
strategy='SampleStrategy',
|
||||
open_order_id="prod_sell_6",
|
||||
timeframe=5,
|
||||
)
|
||||
o = Order.parse_from_ccxt_object(mock_order_6(), 'LTC/BTC', 'buy')
|
||||
trade.orders.append(o)
|
||||
|
@@ -1,3 +1,4 @@
|
||||
from math import isclose
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
@@ -8,11 +9,12 @@ from pandas import DataFrame, DateOffset, Timestamp, to_datetime
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.constants import LAST_BT_RESULT_FN
|
||||
from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, BT_DATA_COLUMNS_MID, BT_DATA_COLUMNS_OLD,
|
||||
analyze_trade_parallelism, calculate_market_change,
|
||||
calculate_max_drawdown, combine_dataframes_with_mean,
|
||||
create_cum_profit, extract_trades_of_period,
|
||||
get_latest_backtest_filename, get_latest_hyperopt_file,
|
||||
load_backtest_data, load_trades, load_trades_from_db)
|
||||
analyze_trade_parallelism, calculate_csum,
|
||||
calculate_market_change, calculate_max_drawdown,
|
||||
combine_dataframes_with_mean, create_cum_profit,
|
||||
extract_trades_of_period, get_latest_backtest_filename,
|
||||
get_latest_hyperopt_file, load_backtest_data, load_trades,
|
||||
load_trades_from_db)
|
||||
from freqtrade.data.history import load_data, load_pair_history
|
||||
from tests.conftest import create_mock_trades
|
||||
from tests.conftest_trades import MOCK_TRADE_COUNT
|
||||
@@ -245,7 +247,7 @@ def test_create_cum_profit(testdatadir):
|
||||
"cum_profits", timeframe="5m")
|
||||
assert "cum_profits" in cum_profits.columns
|
||||
assert cum_profits.iloc[0]['cum_profits'] == 0
|
||||
assert cum_profits.iloc[-1]['cum_profits'] == 0.0798005
|
||||
assert isclose(cum_profits.iloc[-1]['cum_profits'], 8.723007518796964e-06)
|
||||
|
||||
|
||||
def test_create_cum_profit1(testdatadir):
|
||||
@@ -263,7 +265,7 @@ def test_create_cum_profit1(testdatadir):
|
||||
"cum_profits", timeframe="5m")
|
||||
assert "cum_profits" in cum_profits.columns
|
||||
assert cum_profits.iloc[0]['cum_profits'] == 0
|
||||
assert cum_profits.iloc[-1]['cum_profits'] == 0.0798005
|
||||
assert isclose(cum_profits.iloc[-1]['cum_profits'], 8.723007518796964e-06)
|
||||
|
||||
with pytest.raises(ValueError, match='Trade dataframe empty.'):
|
||||
create_cum_profit(df.set_index('date'), bt_data[bt_data["pair"] == 'NOTAPAIR'],
|
||||
@@ -273,15 +275,35 @@ def test_create_cum_profit1(testdatadir):
|
||||
def test_calculate_max_drawdown(testdatadir):
|
||||
filename = testdatadir / "backtest-result_test.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
drawdown, h, low = calculate_max_drawdown(bt_data)
|
||||
drawdown, hdate, lowdate, hval, lval = calculate_max_drawdown(bt_data)
|
||||
assert isinstance(drawdown, float)
|
||||
assert pytest.approx(drawdown) == 0.21142322
|
||||
assert isinstance(h, Timestamp)
|
||||
assert isinstance(low, Timestamp)
|
||||
assert h == Timestamp('2018-01-24 14:25:00', tz='UTC')
|
||||
assert low == Timestamp('2018-01-30 04:45:00', tz='UTC')
|
||||
assert isinstance(hdate, Timestamp)
|
||||
assert isinstance(lowdate, Timestamp)
|
||||
assert isinstance(hval, float)
|
||||
assert isinstance(lval, float)
|
||||
assert hdate == Timestamp('2018-01-24 14:25:00', tz='UTC')
|
||||
assert lowdate == Timestamp('2018-01-30 04:45:00', tz='UTC')
|
||||
with pytest.raises(ValueError, match='Trade dataframe empty.'):
|
||||
drawdown, h, low = calculate_max_drawdown(DataFrame())
|
||||
drawdown, hdate, lowdate, hval, lval = calculate_max_drawdown(DataFrame())
|
||||
|
||||
|
||||
def test_calculate_csum(testdatadir):
|
||||
filename = testdatadir / "backtest-result_test.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
csum_min, csum_max = calculate_csum(bt_data)
|
||||
|
||||
assert isinstance(csum_min, float)
|
||||
assert isinstance(csum_max, float)
|
||||
assert csum_min < 0.01
|
||||
assert csum_max > 0.02
|
||||
csum_min1, csum_max1 = calculate_csum(bt_data, 5)
|
||||
|
||||
assert csum_min1 == csum_min + 5
|
||||
assert csum_max1 == csum_max + 5
|
||||
|
||||
with pytest.raises(ValueError, match='Trade dataframe empty.'):
|
||||
csum_min, csum_max = calculate_csum(DataFrame())
|
||||
|
||||
|
||||
def test_calculate_max_drawdown2():
|
||||
@@ -295,13 +317,16 @@ def test_calculate_max_drawdown2():
|
||||
# sort by profit and reset index
|
||||
df = df.sort_values('profit').reset_index(drop=True)
|
||||
df1 = df.copy()
|
||||
drawdown, h, low = calculate_max_drawdown(df, date_col='open_date', value_col='profit')
|
||||
drawdown, hdate, ldate, hval, lval = calculate_max_drawdown(
|
||||
df, date_col='open_date', value_col='profit')
|
||||
# Ensure df has not been altered.
|
||||
assert df.equals(df1)
|
||||
|
||||
assert isinstance(drawdown, float)
|
||||
# High must be before low
|
||||
assert h < low
|
||||
assert hdate < ldate
|
||||
# High value must be higher than low value
|
||||
assert hval > lval
|
||||
assert drawdown == 0.091755
|
||||
|
||||
df = DataFrame(zip(values[:5], dates[:5]), columns=['profit', 'open_date'])
|
||||
|
@@ -1,5 +1,7 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from shutil import copyfile
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -10,8 +12,8 @@ from freqtrade.data.converter import (convert_ohlcv_format, convert_trades_forma
|
||||
trades_to_ohlcv, trim_dataframe)
|
||||
from freqtrade.data.history import (get_timerange, load_data, load_pair_history,
|
||||
validate_backtest_data)
|
||||
from tests.conftest import log_has
|
||||
from tests.data.test_history import _backup_file, _clean_test_file
|
||||
from tests.conftest import log_has, log_has_re
|
||||
from tests.data.test_history import _clean_test_file
|
||||
|
||||
|
||||
def test_dataframe_correct_columns(result):
|
||||
@@ -62,8 +64,8 @@ def test_ohlcv_fill_up_missing_data(testdatadir, caplog):
|
||||
# Column names should not change
|
||||
assert (data.columns == data2.columns).all()
|
||||
|
||||
assert log_has(f"Missing data fillup for UNITTEST/BTC: before: "
|
||||
f"{len(data)} - after: {len(data2)}", caplog)
|
||||
assert log_has_re(f"Missing data fillup for UNITTEST/BTC: before: "
|
||||
f"{len(data)} - after: {len(data2)}.*", caplog)
|
||||
|
||||
# Test fillup actually fixes invalid backtest data
|
||||
min_date, max_date = get_timerange({'UNITTEST/BTC': data})
|
||||
@@ -125,8 +127,8 @@ def test_ohlcv_fill_up_missing_data2(caplog):
|
||||
# Column names should not change
|
||||
assert (data.columns == data2.columns).all()
|
||||
|
||||
assert log_has(f"Missing data fillup for UNITTEST/BTC: before: "
|
||||
f"{len(data)} - after: {len(data2)}", caplog)
|
||||
assert log_has_re(f"Missing data fillup for UNITTEST/BTC: before: "
|
||||
f"{len(data)} - after: {len(data2)}.*", caplog)
|
||||
|
||||
|
||||
def test_ohlcv_drop_incomplete(caplog):
|
||||
@@ -197,6 +199,16 @@ def test_trim_dataframe(testdatadir) -> None:
|
||||
assert all(data_modify.iloc[-1] == data.iloc[-1])
|
||||
assert all(data_modify.iloc[0] == data.iloc[30])
|
||||
|
||||
data_modify = data.copy()
|
||||
tr = TimeRange('date', None, min_date + 1800, 0)
|
||||
# Remove first 20 candles - ignores min date
|
||||
data_modify = trim_dataframe(data_modify, tr, startup_candles=20)
|
||||
assert not data_modify.equals(data)
|
||||
assert len(data_modify) < len(data)
|
||||
assert len(data_modify) == len(data) - 20
|
||||
assert all(data_modify.iloc[-1] == data.iloc[-1])
|
||||
assert all(data_modify.iloc[0] == data.iloc[20])
|
||||
|
||||
data_modify = data.copy()
|
||||
# Remove last 30 minutes (1800 s)
|
||||
tr = TimeRange(None, 'date', 0, max_date - 1800)
|
||||
@@ -241,17 +253,18 @@ def test_trades_dict_to_list(fetch_trades_result):
|
||||
assert t[6] == fetch_trades_result[i]['cost']
|
||||
|
||||
|
||||
def test_convert_trades_format(mocker, default_conf, testdatadir):
|
||||
files = [{'old': testdatadir / "XRP_ETH-trades.json.gz",
|
||||
'new': testdatadir / "XRP_ETH-trades.json"},
|
||||
{'old': testdatadir / "XRP_OLD-trades.json.gz",
|
||||
'new': testdatadir / "XRP_OLD-trades.json"},
|
||||
def test_convert_trades_format(default_conf, testdatadir, tmpdir):
|
||||
tmpdir1 = Path(tmpdir)
|
||||
files = [{'old': tmpdir1 / "XRP_ETH-trades.json.gz",
|
||||
'new': tmpdir1 / "XRP_ETH-trades.json"},
|
||||
{'old': tmpdir1 / "XRP_OLD-trades.json.gz",
|
||||
'new': tmpdir1 / "XRP_OLD-trades.json"},
|
||||
]
|
||||
for file in files:
|
||||
_backup_file(file['old'], copy_file=True)
|
||||
copyfile(testdatadir / file['old'].name, file['old'])
|
||||
assert not file['new'].exists()
|
||||
|
||||
default_conf['datadir'] = testdatadir
|
||||
default_conf['datadir'] = tmpdir1
|
||||
|
||||
convert_trades_format(default_conf, convert_from='jsongz',
|
||||
convert_to='json', erase=False)
|
||||
@@ -274,14 +287,20 @@ def test_convert_trades_format(mocker, default_conf, testdatadir):
|
||||
file['new'].unlink()
|
||||
|
||||
|
||||
def test_convert_ohlcv_format(mocker, default_conf, testdatadir):
|
||||
file1 = testdatadir / "XRP_ETH-5m.json"
|
||||
file1_new = testdatadir / "XRP_ETH-5m.json.gz"
|
||||
file2 = testdatadir / "XRP_ETH-1m.json"
|
||||
file2_new = testdatadir / "XRP_ETH-1m.json.gz"
|
||||
_backup_file(file1, copy_file=True)
|
||||
_backup_file(file2, copy_file=True)
|
||||
default_conf['datadir'] = testdatadir
|
||||
def test_convert_ohlcv_format(default_conf, testdatadir, tmpdir):
|
||||
tmpdir1 = Path(tmpdir)
|
||||
|
||||
file1_orig = testdatadir / "XRP_ETH-5m.json"
|
||||
file1 = tmpdir1 / "XRP_ETH-5m.json"
|
||||
file1_new = tmpdir1 / "XRP_ETH-5m.json.gz"
|
||||
file2_orig = testdatadir / "XRP_ETH-1m.json"
|
||||
file2 = tmpdir1 / "XRP_ETH-1m.json"
|
||||
file2_new = tmpdir1 / "XRP_ETH-1m.json.gz"
|
||||
|
||||
copyfile(file1_orig, file1)
|
||||
copyfile(file2_orig, file2)
|
||||
|
||||
default_conf['datadir'] = tmpdir1
|
||||
default_conf['pairs'] = ['XRP_ETH']
|
||||
default_conf['timeframes'] = ['1m', '5m']
|
||||
|
||||
@@ -307,10 +326,3 @@ def test_convert_ohlcv_format(mocker, default_conf, testdatadir):
|
||||
assert file2.exists()
|
||||
assert not file1_new.exists()
|
||||
assert not file2_new.exists()
|
||||
|
||||
_clean_test_file(file1)
|
||||
_clean_test_file(file2)
|
||||
if file1_new.exists():
|
||||
file1_new.unlink()
|
||||
if file2_new.exists():
|
||||
file2_new.unlink()
|
||||
|
@@ -5,9 +5,9 @@ import pytest
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.enums import RunMode
|
||||
from freqtrade.exceptions import ExchangeError, OperationalException
|
||||
from freqtrade.plugins.pairlistmanager import PairListManager
|
||||
from freqtrade.state import RunMode
|
||||
from tests.conftest import get_patched_exchange
|
||||
|
||||
|
||||
@@ -214,8 +214,8 @@ def test_current_whitelist(mocker, default_conf, tickers):
|
||||
pairlist.refresh_pairlist()
|
||||
|
||||
assert dp.current_whitelist() == pairlist._whitelist
|
||||
# The identity of the 2 lists should be identical
|
||||
assert dp.current_whitelist() is pairlist._whitelist
|
||||
# The identity of the 2 lists should not be identical, but a copy
|
||||
assert dp.current_whitelist() is not pairlist._whitelist
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
dp = DataProvider(default_conf, exchange)
|
||||
@@ -246,3 +246,46 @@ def test_get_analyzed_dataframe(mocker, default_conf, ohlcv_history):
|
||||
assert dataframe.empty
|
||||
assert isinstance(time, datetime)
|
||||
assert time == datetime(1970, 1, 1, tzinfo=timezone.utc)
|
||||
|
||||
# Test backtest mode
|
||||
default_conf["runmode"] = RunMode.BACKTEST
|
||||
dp._set_dataframe_max_index(1)
|
||||
dataframe, time = dp.get_analyzed_dataframe("XRP/BTC", timeframe)
|
||||
|
||||
assert len(dataframe) == 1
|
||||
|
||||
dp._set_dataframe_max_index(2)
|
||||
dataframe, time = dp.get_analyzed_dataframe("XRP/BTC", timeframe)
|
||||
assert len(dataframe) == 2
|
||||
|
||||
dp._set_dataframe_max_index(3)
|
||||
dataframe, time = dp.get_analyzed_dataframe("XRP/BTC", timeframe)
|
||||
assert len(dataframe) == 3
|
||||
|
||||
dp._set_dataframe_max_index(500)
|
||||
dataframe, time = dp.get_analyzed_dataframe("XRP/BTC", timeframe)
|
||||
assert len(dataframe) == len(ohlcv_history)
|
||||
|
||||
|
||||
def test_no_exchange_mode(default_conf):
|
||||
dp = DataProvider(default_conf, None)
|
||||
|
||||
message = "Exchange is not available to DataProvider."
|
||||
|
||||
with pytest.raises(OperationalException, match=message):
|
||||
dp.refresh([()])
|
||||
|
||||
with pytest.raises(OperationalException, match=message):
|
||||
dp.ohlcv('XRP/USDT', '5m')
|
||||
|
||||
with pytest.raises(OperationalException, match=message):
|
||||
dp.market('XRP/USDT')
|
||||
|
||||
with pytest.raises(OperationalException, match=message):
|
||||
dp.ticker('XRP/USDT')
|
||||
|
||||
with pytest.raises(OperationalException, match=message):
|
||||
dp.orderbook('XRP/USDT', 20)
|
||||
|
||||
with pytest.raises(OperationalException, match=message):
|
||||
dp.available_pairs()
|
||||
|
@@ -86,14 +86,12 @@ def test_load_data_7min_timeframe(mocker, caplog, default_conf, testdatadir) ->
|
||||
def test_load_data_1min_timeframe(ohlcv_history, mocker, caplog, testdatadir) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history)
|
||||
file = testdatadir / 'UNITTEST_BTC-1m.json'
|
||||
_backup_file(file, copy_file=True)
|
||||
load_data(datadir=testdatadir, timeframe='1m', pairs=['UNITTEST/BTC'])
|
||||
assert file.is_file()
|
||||
assert not log_has(
|
||||
'Download history data for pair: "UNITTEST/BTC", interval: 1m '
|
||||
'and store in None.', caplog
|
||||
)
|
||||
_clean_test_file(file)
|
||||
|
||||
|
||||
def test_load_data_startup_candles(mocker, caplog, default_conf, testdatadir) -> None:
|
||||
@@ -112,17 +110,17 @@ def test_load_data_startup_candles(mocker, caplog, default_conf, testdatadir) ->
|
||||
|
||||
|
||||
def test_load_data_with_new_pair_1min(ohlcv_history_list, mocker, caplog,
|
||||
default_conf, testdatadir) -> None:
|
||||
default_conf, tmpdir) -> None:
|
||||
"""
|
||||
Test load_pair_history() with 1 min timeframe
|
||||
"""
|
||||
tmpdir1 = Path(tmpdir)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history_list)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
file = testdatadir / 'MEME_BTC-1m.json'
|
||||
file = tmpdir1 / 'MEME_BTC-1m.json'
|
||||
|
||||
_backup_file(file)
|
||||
# do not download a new pair if refresh_pairs isn't set
|
||||
load_pair_history(datadir=testdatadir, timeframe='1m', pair='MEME/BTC')
|
||||
load_pair_history(datadir=tmpdir1, timeframe='1m', pair='MEME/BTC')
|
||||
assert not file.is_file()
|
||||
assert log_has(
|
||||
'No history data for pair: "MEME/BTC", timeframe: 1m. '
|
||||
@@ -130,15 +128,14 @@ def test_load_data_with_new_pair_1min(ohlcv_history_list, mocker, caplog,
|
||||
)
|
||||
|
||||
# download a new pair if refresh_pairs is set
|
||||
refresh_data(datadir=testdatadir, timeframe='1m', pairs=['MEME/BTC'],
|
||||
refresh_data(datadir=tmpdir1, timeframe='1m', pairs=['MEME/BTC'],
|
||||
exchange=exchange)
|
||||
load_pair_history(datadir=testdatadir, timeframe='1m', pair='MEME/BTC')
|
||||
load_pair_history(datadir=tmpdir1, timeframe='1m', pair='MEME/BTC')
|
||||
assert file.is_file()
|
||||
assert log_has_re(
|
||||
'Download history data for pair: "MEME/BTC", timeframe: 1m '
|
||||
'and store in .*', caplog
|
||||
)
|
||||
_clean_test_file(file)
|
||||
|
||||
|
||||
def test_testdata_path(testdatadir) -> None:
|
||||
@@ -231,26 +228,22 @@ def test_load_cached_data_for_updating(mocker, testdatadir) -> None:
|
||||
assert start_ts is None
|
||||
|
||||
|
||||
def test_download_pair_history(ohlcv_history_list, mocker, default_conf, testdatadir) -> None:
|
||||
def test_download_pair_history(ohlcv_history_list, mocker, default_conf, tmpdir) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history_list)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
file1_1 = testdatadir / 'MEME_BTC-1m.json'
|
||||
file1_5 = testdatadir / 'MEME_BTC-5m.json'
|
||||
file2_1 = testdatadir / 'CFI_BTC-1m.json'
|
||||
file2_5 = testdatadir / 'CFI_BTC-5m.json'
|
||||
|
||||
_backup_file(file1_1)
|
||||
_backup_file(file1_5)
|
||||
_backup_file(file2_1)
|
||||
_backup_file(file2_5)
|
||||
tmpdir1 = Path(tmpdir)
|
||||
file1_1 = tmpdir1 / 'MEME_BTC-1m.json'
|
||||
file1_5 = tmpdir1 / 'MEME_BTC-5m.json'
|
||||
file2_1 = tmpdir1 / 'CFI_BTC-1m.json'
|
||||
file2_5 = tmpdir1 / 'CFI_BTC-5m.json'
|
||||
|
||||
assert not file1_1.is_file()
|
||||
assert not file2_1.is_file()
|
||||
|
||||
assert _download_pair_history(datadir=testdatadir, exchange=exchange,
|
||||
assert _download_pair_history(datadir=tmpdir1, exchange=exchange,
|
||||
pair='MEME/BTC',
|
||||
timeframe='1m')
|
||||
assert _download_pair_history(datadir=testdatadir, exchange=exchange,
|
||||
assert _download_pair_history(datadir=tmpdir1, exchange=exchange,
|
||||
pair='CFI/BTC',
|
||||
timeframe='1m')
|
||||
assert not exchange._pairs_last_refresh_time
|
||||
@@ -264,20 +257,16 @@ def test_download_pair_history(ohlcv_history_list, mocker, default_conf, testdat
|
||||
assert not file1_5.is_file()
|
||||
assert not file2_5.is_file()
|
||||
|
||||
assert _download_pair_history(datadir=testdatadir, exchange=exchange,
|
||||
assert _download_pair_history(datadir=tmpdir1, exchange=exchange,
|
||||
pair='MEME/BTC',
|
||||
timeframe='5m')
|
||||
assert _download_pair_history(datadir=testdatadir, exchange=exchange,
|
||||
assert _download_pair_history(datadir=tmpdir1, exchange=exchange,
|
||||
pair='CFI/BTC',
|
||||
timeframe='5m')
|
||||
assert not exchange._pairs_last_refresh_time
|
||||
assert file1_5.is_file()
|
||||
assert file2_5.is_file()
|
||||
|
||||
# clean files freshly downloaded
|
||||
_clean_test_file(file1_5)
|
||||
_clean_test_file(file2_5)
|
||||
|
||||
|
||||
def test_download_pair_history2(mocker, default_conf, testdatadir) -> None:
|
||||
tick = [
|
||||
@@ -294,24 +283,15 @@ def test_download_pair_history2(mocker, default_conf, testdatadir) -> None:
|
||||
assert json_dump_mock.call_count == 2
|
||||
|
||||
|
||||
def test_download_backtesting_data_exception(ohlcv_history, mocker, caplog,
|
||||
default_conf, testdatadir) -> None:
|
||||
def test_download_backtesting_data_exception(mocker, caplog, default_conf, tmpdir) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv',
|
||||
side_effect=Exception('File Error'))
|
||||
|
||||
tmpdir1 = Path(tmpdir)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
|
||||
file1_1 = testdatadir / 'MEME_BTC-1m.json'
|
||||
file1_5 = testdatadir / 'MEME_BTC-5m.json'
|
||||
_backup_file(file1_1)
|
||||
_backup_file(file1_5)
|
||||
|
||||
assert not _download_pair_history(datadir=testdatadir, exchange=exchange,
|
||||
assert not _download_pair_history(datadir=tmpdir1, exchange=exchange,
|
||||
pair='MEME/BTC',
|
||||
timeframe='1m')
|
||||
# clean files freshly downloaded
|
||||
_clean_test_file(file1_1)
|
||||
_clean_test_file(file1_5)
|
||||
assert log_has('Failed to download history data for pair: "MEME/BTC", timeframe: 1m.', caplog)
|
||||
|
||||
|
||||
@@ -528,15 +508,15 @@ def test_refresh_backtest_trades_data(mocker, default_conf, markets, caplog, tes
|
||||
assert log_has("Skipping pair XRP/ETH...", caplog)
|
||||
|
||||
|
||||
def test_download_trades_history(trades_history, mocker, default_conf, testdatadir, caplog) -> None:
|
||||
|
||||
def test_download_trades_history(trades_history, mocker, default_conf, testdatadir, caplog,
|
||||
tmpdir) -> None:
|
||||
tmpdir1 = Path(tmpdir)
|
||||
ght_mock = MagicMock(side_effect=lambda pair, *args, **kwargs: (pair, trades_history))
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_historic_trades',
|
||||
ght_mock)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
file1 = testdatadir / 'ETH_BTC-trades.json.gz'
|
||||
data_handler = get_datahandler(testdatadir, data_format='jsongz')
|
||||
_backup_file(file1)
|
||||
file1 = tmpdir1 / 'ETH_BTC-trades.json.gz'
|
||||
data_handler = get_datahandler(tmpdir1, data_format='jsongz')
|
||||
|
||||
assert not file1.is_file()
|
||||
|
||||
@@ -557,8 +537,7 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad
|
||||
assert int(ght_mock.call_args_list[0][1]['since'] // 1000) == since_time2 - 5
|
||||
assert ght_mock.call_args_list[0][1]['from_id'] is not None
|
||||
|
||||
# clean files freshly downloaded
|
||||
_clean_test_file(file1)
|
||||
file1.unlink()
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_historic_trades',
|
||||
MagicMock(side_effect=ValueError))
|
||||
@@ -567,9 +546,8 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad
|
||||
pair='ETH/BTC')
|
||||
assert log_has_re('Failed to download historic trades for pair: "ETH/BTC".*', caplog)
|
||||
|
||||
file2 = testdatadir / 'XRP_ETH-trades.json.gz'
|
||||
|
||||
_backup_file(file2, True)
|
||||
file2 = tmpdir1 / 'XRP_ETH-trades.json.gz'
|
||||
copyfile(testdatadir / file2.name, file2)
|
||||
|
||||
ght_mock.reset_mock()
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_historic_trades',
|
||||
@@ -589,38 +567,37 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad
|
||||
_clean_test_file(file2)
|
||||
|
||||
|
||||
def test_convert_trades_to_ohlcv(mocker, default_conf, testdatadir, caplog):
|
||||
|
||||
def test_convert_trades_to_ohlcv(testdatadir, tmpdir, caplog):
|
||||
tmpdir1 = Path(tmpdir)
|
||||
pair = 'XRP/ETH'
|
||||
file1 = testdatadir / 'XRP_ETH-1m.json'
|
||||
file5 = testdatadir / 'XRP_ETH-5m.json'
|
||||
# Compare downloaded dataset with converted dataset
|
||||
dfbak_1m = load_pair_history(datadir=testdatadir, timeframe="1m", pair=pair)
|
||||
dfbak_5m = load_pair_history(datadir=testdatadir, timeframe="5m", pair=pair)
|
||||
file1 = tmpdir1 / 'XRP_ETH-1m.json'
|
||||
file5 = tmpdir1 / 'XRP_ETH-5m.json'
|
||||
filetrades = tmpdir1 / 'XRP_ETH-trades.json.gz'
|
||||
copyfile(testdatadir / file1.name, file1)
|
||||
copyfile(testdatadir / file5.name, file5)
|
||||
copyfile(testdatadir / filetrades.name, filetrades)
|
||||
|
||||
_backup_file(file1, copy_file=True)
|
||||
_backup_file(file5)
|
||||
# Compare downloaded dataset with converted dataset
|
||||
dfbak_1m = load_pair_history(datadir=tmpdir1, timeframe="1m", pair=pair)
|
||||
dfbak_5m = load_pair_history(datadir=tmpdir1, timeframe="5m", pair=pair)
|
||||
|
||||
tr = TimeRange.parse_timerange('20191011-20191012')
|
||||
|
||||
convert_trades_to_ohlcv([pair], timeframes=['1m', '5m'],
|
||||
datadir=testdatadir, timerange=tr, erase=True)
|
||||
datadir=tmpdir1, timerange=tr, erase=True)
|
||||
|
||||
assert log_has("Deleting existing data for pair XRP/ETH, interval 1m.", caplog)
|
||||
# Load new data
|
||||
df_1m = load_pair_history(datadir=testdatadir, timeframe="1m", pair=pair)
|
||||
df_5m = load_pair_history(datadir=testdatadir, timeframe="5m", pair=pair)
|
||||
df_1m = load_pair_history(datadir=tmpdir1, timeframe="1m", pair=pair)
|
||||
df_5m = load_pair_history(datadir=tmpdir1, timeframe="5m", pair=pair)
|
||||
|
||||
assert df_1m.equals(dfbak_1m)
|
||||
assert df_5m.equals(dfbak_5m)
|
||||
|
||||
_clean_test_file(file1)
|
||||
_clean_test_file(file5)
|
||||
|
||||
assert not log_has('Could not convert NoDatapair to OHLCV.', caplog)
|
||||
|
||||
convert_trades_to_ohlcv(['NoDatapair'], timeframes=['1m', '5m'],
|
||||
datadir=testdatadir, timerange=tr, erase=True)
|
||||
datadir=tmpdir1, timerange=tr, erase=True)
|
||||
assert log_has('Could not convert NoDatapair to OHLCV.', caplog)
|
||||
|
||||
|
||||
@@ -752,15 +729,17 @@ def test_hdf5datahandler_trades_load(testdatadir):
|
||||
assert len([t for t in trades2 if t[0] > timerange.stopts * 1000]) == 0
|
||||
|
||||
|
||||
def test_hdf5datahandler_trades_store(testdatadir):
|
||||
def test_hdf5datahandler_trades_store(testdatadir, tmpdir):
|
||||
tmpdir1 = Path(tmpdir)
|
||||
dh = HDF5DataHandler(testdatadir)
|
||||
trades = dh.trades_load('XRP/ETH')
|
||||
|
||||
dh.trades_store('XRP/NEW', trades)
|
||||
file = testdatadir / 'XRP_NEW-trades.h5'
|
||||
dh1 = HDF5DataHandler(tmpdir1)
|
||||
dh1.trades_store('XRP/NEW', trades)
|
||||
file = tmpdir1 / 'XRP_NEW-trades.h5'
|
||||
assert file.is_file()
|
||||
# Load trades back
|
||||
trades_new = dh.trades_load('XRP/NEW')
|
||||
trades_new = dh1.trades_load('XRP/NEW')
|
||||
|
||||
assert len(trades_new) == len(trades)
|
||||
assert trades[0][0] == trades_new[0][0]
|
||||
@@ -778,8 +757,6 @@ def test_hdf5datahandler_trades_store(testdatadir):
|
||||
assert trades[-1][5] == trades_new[-1][5]
|
||||
assert trades[-1][6] == trades_new[-1][6]
|
||||
|
||||
_clean_test_file(file)
|
||||
|
||||
|
||||
def test_hdf5datahandler_trades_purge(mocker, testdatadir):
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
|
||||
@@ -793,16 +770,18 @@ def test_hdf5datahandler_trades_purge(mocker, testdatadir):
|
||||
assert unlinkmock.call_count == 1
|
||||
|
||||
|
||||
def test_hdf5datahandler_ohlcv_load_and_resave(testdatadir):
|
||||
def test_hdf5datahandler_ohlcv_load_and_resave(testdatadir, tmpdir):
|
||||
tmpdir1 = Path(tmpdir)
|
||||
dh = HDF5DataHandler(testdatadir)
|
||||
ohlcv = dh.ohlcv_load('UNITTEST/BTC', '5m')
|
||||
assert isinstance(ohlcv, DataFrame)
|
||||
assert len(ohlcv) > 0
|
||||
|
||||
file = testdatadir / 'UNITTEST_NEW-5m.h5'
|
||||
file = tmpdir1 / 'UNITTEST_NEW-5m.h5'
|
||||
assert not file.is_file()
|
||||
|
||||
dh.ohlcv_store('UNITTEST/NEW', '5m', ohlcv)
|
||||
dh1 = HDF5DataHandler(tmpdir1)
|
||||
dh1.ohlcv_store('UNITTEST/NEW', '5m', ohlcv)
|
||||
assert file.is_file()
|
||||
|
||||
assert not ohlcv[ohlcv['date'] < '2018-01-15'].empty
|
||||
@@ -812,14 +791,12 @@ def test_hdf5datahandler_ohlcv_load_and_resave(testdatadir):
|
||||
|
||||
# Call private function to ensure timerange is filtered in hdf5
|
||||
ohlcv = dh._ohlcv_load('UNITTEST/BTC', '5m', timerange)
|
||||
ohlcv1 = dh._ohlcv_load('UNITTEST/NEW', '5m', timerange)
|
||||
ohlcv1 = dh1._ohlcv_load('UNITTEST/NEW', '5m', timerange)
|
||||
assert len(ohlcv) == len(ohlcv1)
|
||||
assert ohlcv.equals(ohlcv1)
|
||||
assert ohlcv[ohlcv['date'] < '2018-01-15'].empty
|
||||
assert ohlcv[ohlcv['date'] > '2018-01-19'].empty
|
||||
|
||||
_clean_test_file(file)
|
||||
|
||||
# Try loading inexisting file
|
||||
ohlcv = dh.ohlcv_load('UNITTEST/NONEXIST', '5m')
|
||||
assert ohlcv.empty
|
||||
|
@@ -12,8 +12,8 @@ from pandas import DataFrame, to_datetime
|
||||
|
||||
from freqtrade.data.converter import ohlcv_to_dataframe
|
||||
from freqtrade.edge import Edge, PairInfo
|
||||
from freqtrade.enums import SellType
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.strategy.interface import SellType
|
||||
from tests.conftest import get_patched_freqtradebot, log_has
|
||||
from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe,
|
||||
_get_frame_time_from_offset)
|
||||
@@ -266,7 +266,7 @@ def test_edge_heartbeat_calculate(mocker, edge_conf):
|
||||
# should not recalculate if heartbeat not reached
|
||||
edge._last_updated = arrow.utcnow().int_timestamp - heartbeat + 1
|
||||
|
||||
assert edge.calculate() is False
|
||||
assert edge.calculate(edge_conf['exchange']['pair_whitelist']) is False
|
||||
|
||||
|
||||
def mocked_load_data(datadir, pairs=[], timeframe='0m',
|
||||
@@ -310,7 +310,7 @@ def test_edge_process_downloaded_data(mocker, edge_conf):
|
||||
mocker.patch('freqtrade.edge.edge_positioning.load_data', mocked_load_data)
|
||||
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||
|
||||
assert edge.calculate()
|
||||
assert edge.calculate(edge_conf['exchange']['pair_whitelist'])
|
||||
assert len(edge._cached_pairs) == 2
|
||||
assert edge._last_updated <= arrow.utcnow().int_timestamp + 2
|
||||
|
||||
@@ -322,7 +322,7 @@ def test_edge_process_no_data(mocker, edge_conf, caplog):
|
||||
mocker.patch('freqtrade.edge.edge_positioning.load_data', MagicMock(return_value={}))
|
||||
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||
|
||||
assert not edge.calculate()
|
||||
assert not edge.calculate(edge_conf['exchange']['pair_whitelist'])
|
||||
assert len(edge._cached_pairs) == 0
|
||||
assert log_has("No data found. Edge is stopped ...", caplog)
|
||||
assert edge._last_updated == 0
|
||||
@@ -330,18 +330,37 @@ def test_edge_process_no_data(mocker, edge_conf, caplog):
|
||||
|
||||
def test_edge_process_no_trades(mocker, edge_conf, caplog):
|
||||
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
|
||||
mocker.patch('freqtrade.edge.edge_positioning.refresh_data', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', return_value=0.001)
|
||||
mocker.patch('freqtrade.edge.edge_positioning.refresh_data', )
|
||||
mocker.patch('freqtrade.edge.edge_positioning.load_data', mocked_load_data)
|
||||
# Return empty
|
||||
mocker.patch('freqtrade.edge.Edge._find_trades_for_stoploss_range', MagicMock(return_value=[]))
|
||||
mocker.patch('freqtrade.edge.Edge._find_trades_for_stoploss_range', return_value=[])
|
||||
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||
|
||||
assert not edge.calculate()
|
||||
assert not edge.calculate(edge_conf['exchange']['pair_whitelist'])
|
||||
assert len(edge._cached_pairs) == 0
|
||||
assert log_has("No trades found.", caplog)
|
||||
|
||||
|
||||
def test_edge_process_no_pairs(mocker, edge_conf, caplog):
|
||||
edge_conf['exchange']['pair_whitelist'] = []
|
||||
mocker.patch('freqtrade.freqtradebot.validate_config_consistency')
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||
fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', return_value=0.001)
|
||||
mocker.patch('freqtrade.edge.edge_positioning.refresh_data')
|
||||
mocker.patch('freqtrade.edge.edge_positioning.load_data', mocked_load_data)
|
||||
# Return empty
|
||||
mocker.patch('freqtrade.edge.Edge._find_trades_for_stoploss_range', return_value=[])
|
||||
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||
assert fee_mock.call_count == 0
|
||||
assert edge.fee is None
|
||||
|
||||
assert not edge.calculate(['XRP/USDT'])
|
||||
assert fee_mock.call_count == 1
|
||||
assert edge.fee == 0.001
|
||||
|
||||
|
||||
def test_edge_init_error(mocker, edge_conf,):
|
||||
edge_conf['stake_amount'] = 0.5
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
|
||||
|
@@ -5,10 +5,12 @@ However, these tests should give a good idea to determine if a new exchange is
|
||||
suitable to run with freqtrade.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date
|
||||
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
|
||||
from tests.conftest import get_default_conf
|
||||
|
||||
@@ -34,7 +36,12 @@ EXCHANGES = {
|
||||
'pair': 'BTC/USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
}
|
||||
},
|
||||
'kucoin': {
|
||||
'pair': 'BTC/USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +49,7 @@ EXCHANGES = {
|
||||
def exchange_conf():
|
||||
config = get_default_conf((Path(__file__).parent / "testdata").resolve())
|
||||
config['exchange']['pair_whitelist'] = []
|
||||
config['dry_run'] = False
|
||||
return config
|
||||
|
||||
|
||||
@@ -97,14 +105,16 @@ class TestCCXTExchange():
|
||||
assert 'asks' in l2
|
||||
assert 'bids' in l2
|
||||
l2_limit_range = exchange._ft_has['l2_limit_range']
|
||||
l2_limit_range_required = exchange._ft_has['l2_limit_range_required']
|
||||
for val in [1, 2, 5, 25, 100]:
|
||||
l2 = exchange.fetch_l2_order_book(pair, val)
|
||||
if not l2_limit_range or val in l2_limit_range:
|
||||
assert len(l2['asks']) == val
|
||||
assert len(l2['bids']) == val
|
||||
else:
|
||||
next_limit = exchange.get_next_limit_in_list(val, l2_limit_range)
|
||||
if next_limit > 200:
|
||||
next_limit = exchange.get_next_limit_in_list(
|
||||
val, l2_limit_range, l2_limit_range_required)
|
||||
if next_limit is None or next_limit > 200:
|
||||
# Large orderbook sizes can be a problem for some exchanges (bitrex ...)
|
||||
assert len(l2['asks']) > 200
|
||||
assert len(l2['asks']) > 200
|
||||
@@ -122,7 +132,10 @@ class TestCCXTExchange():
|
||||
assert len(ohlcv[pair_tf]) == len(exchange.klines(pair_tf))
|
||||
# assert len(exchange.klines(pair_tf)) > 200
|
||||
# Assume 90% uptime ...
|
||||
assert len(exchange.klines(pair_tf)) > exchange._ohlcv_candle_limit * 0.90
|
||||
assert len(exchange.klines(pair_tf)) > exchange.ohlcv_candle_limit(timeframe) * 0.90
|
||||
# Check if last-timeframe is within the last 2 intervals
|
||||
now = datetime.now(timezone.utc) - timedelta(minutes=(timeframe_to_minutes(timeframe) * 2))
|
||||
assert exchange.klines(pair_tf).iloc[-1]['date'] >= timeframe_to_prev_date(timeframe, now)
|
||||
|
||||
# TODO: tests fetch_trades (?)
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import copy
|
||||
import logging
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from math import isclose
|
||||
from random import randint
|
||||
from unittest.mock import MagicMock, Mock, PropertyMock, patch
|
||||
|
||||
@@ -10,7 +11,7 @@ import pytest
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOrderException,
|
||||
OperationalException, TemporaryError)
|
||||
OperationalException, PricingError, TemporaryError)
|
||||
from freqtrade.exchange import Binance, Bittrex, Exchange, Kraken
|
||||
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_COUNT,
|
||||
calculate_backoff)
|
||||
@@ -18,21 +19,13 @@ from freqtrade.exchange.exchange import (market_is_active, timeframe_to_minutes,
|
||||
timeframe_to_next_date, timeframe_to_prev_date,
|
||||
timeframe_to_seconds)
|
||||
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
|
||||
from tests.conftest import get_patched_exchange, log_has, log_has_re
|
||||
from tests.conftest import get_mock_coro, get_patched_exchange, log_has, log_has_re
|
||||
|
||||
|
||||
# Make sure to always keep one exchange here which is NOT subclassed!!
|
||||
EXCHANGES = ['bittrex', 'binance', 'kraken', 'ftx']
|
||||
|
||||
|
||||
# Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines
|
||||
def get_mock_coro(return_value):
|
||||
async def mock_coro(*args, **kwargs):
|
||||
return return_value
|
||||
|
||||
return Mock(wraps=mock_coro)
|
||||
|
||||
|
||||
def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
|
||||
fun, mock_ccxt_fun, retries=API_RETRY_COUNT + 1, **kwargs):
|
||||
|
||||
@@ -378,7 +371,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
|
||||
PropertyMock(return_value=markets)
|
||||
)
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss)
|
||||
assert result == 2 / 0.9
|
||||
assert isclose(result, 2 * (1+0.05) / (1-abs(stoploss)))
|
||||
|
||||
# min amount is set
|
||||
markets["ETH/BTC"]["limits"] = {
|
||||
@@ -390,7 +383,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
|
||||
PropertyMock(return_value=markets)
|
||||
)
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
||||
assert result == 2 * 2 / 0.9
|
||||
assert isclose(result, 2 * 2 * (1+0.05) / (1-abs(stoploss)))
|
||||
|
||||
# min amount and cost are set (cost is minimal)
|
||||
markets["ETH/BTC"]["limits"] = {
|
||||
@@ -402,7 +395,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
|
||||
PropertyMock(return_value=markets)
|
||||
)
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
||||
assert result == max(2, 2 * 2) / 0.9
|
||||
assert isclose(result, max(2, 2 * 2) * (1+0.05) / (1-abs(stoploss)))
|
||||
|
||||
# min amount and cost are set (amount is minial)
|
||||
markets["ETH/BTC"]["limits"] = {
|
||||
@@ -414,7 +407,14 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
|
||||
PropertyMock(return_value=markets)
|
||||
)
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
||||
assert result == max(8, 2 * 2) / 0.9
|
||||
assert isclose(result, max(8, 2 * 2) * (1+0.05) / (1-abs(stoploss)))
|
||||
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4)
|
||||
assert isclose(result, max(8, 2 * 2) * 1.5)
|
||||
|
||||
# Really big stoploss
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1)
|
||||
assert isclose(result, max(8, 2 * 2) * 1.5)
|
||||
|
||||
|
||||
def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None:
|
||||
@@ -432,7 +432,10 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None:
|
||||
PropertyMock(return_value=markets)
|
||||
)
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss)
|
||||
assert round(result, 8) == round(max(0.0001, 0.001 * 0.020405) / 0.9, 8)
|
||||
assert round(result, 8) == round(
|
||||
max(0.0001, 0.001 * 0.020405) * (1+0.05) / (1-abs(stoploss)),
|
||||
8
|
||||
)
|
||||
|
||||
|
||||
def test_set_sandbox(default_conf, mocker):
|
||||
@@ -498,7 +501,7 @@ def test__load_markets(default_conf, mocker, caplog):
|
||||
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||
Exchange(default_conf)
|
||||
assert log_has('Unable to initialize markets. Reason: SomeError', caplog)
|
||||
assert log_has('Unable to initialize markets.', caplog)
|
||||
|
||||
expected_return = {'ETH/BTC': 'available'}
|
||||
api_mock = MagicMock()
|
||||
@@ -931,11 +934,11 @@ def test_exchange_has(default_conf, mocker):
|
||||
("sell")
|
||||
])
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_dry_run_order(default_conf, mocker, side, exchange_name):
|
||||
def test_create_dry_run_order(default_conf, mocker, side, exchange_name):
|
||||
default_conf['dry_run'] = True
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
|
||||
order = exchange.dry_run_order(
|
||||
order = exchange.create_dry_run_order(
|
||||
pair='ETH/BTC', ordertype='limit', side=side, amount=1, rate=200)
|
||||
assert 'id' in order
|
||||
assert f'dry_run_{side}_' in order["id"]
|
||||
@@ -1245,44 +1248,6 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
|
||||
assert "timeInForce" not in api_mock.create_order.call_args[0][5]
|
||||
|
||||
|
||||
def test_get_balance_dry_run(default_conf, mocker):
|
||||
default_conf['dry_run'] = True
|
||||
default_conf['dry_run_wallet'] = 999.9
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
assert exchange.get_balance(currency='BTC') == 999.9
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_get_balance_prod(default_conf, mocker, exchange_name):
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_balance = MagicMock(return_value={'BTC': {'free': 123.4, 'total': 123.4}})
|
||||
default_conf['dry_run'] = False
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
|
||||
assert exchange.get_balance(currency='BTC') == 123.4
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
|
||||
exchange.get_balance(currency='BTC')
|
||||
|
||||
with pytest.raises(TemporaryError, match=r'.*balance due to malformed exchange response:.*'):
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_balances', MagicMock(return_value={}))
|
||||
mocker.patch('freqtrade.exchange.Kraken.get_balances', MagicMock(return_value={}))
|
||||
exchange.get_balance(currency='BTC')
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_get_balances_dry_run(default_conf, mocker, exchange_name):
|
||||
default_conf['dry_run'] = True
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
assert exchange.get_balances() == {}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_get_balances_prod(default_conf, mocker, exchange_name):
|
||||
balance_item = {
|
||||
@@ -1334,6 +1299,16 @@ def test_get_tickers(default_conf, mocker, exchange_name):
|
||||
assert tickers['ETH/BTC']['ask'] == 1
|
||||
assert tickers['BCH/BTC']['bid'] == 0.6
|
||||
assert tickers['BCH/BTC']['ask'] == 0.5
|
||||
assert api_mock.fetch_tickers.call_count == 1
|
||||
|
||||
api_mock.fetch_tickers.reset_mock()
|
||||
|
||||
# Cached ticker should not call api again
|
||||
tickers2 = exchange.get_tickers(cached=True)
|
||||
assert tickers2 == tickers
|
||||
assert api_mock.fetch_tickers.call_count == 0
|
||||
tickers2 = exchange.get_tickers(cached=False)
|
||||
assert api_mock.fetch_tickers.call_count == 1
|
||||
|
||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
|
||||
"get_tickers", "fetch_tickers")
|
||||
@@ -1417,7 +1392,7 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name):
|
||||
exchange._async_get_candle_history = Mock(wraps=mock_candle_hist)
|
||||
# one_call calculation * 1.8 should do 2 calls
|
||||
|
||||
since = 5 * 60 * exchange._ft_has['ohlcv_candle_limit'] * 1.8
|
||||
since = 5 * 60 * exchange.ohlcv_candle_limit('5m') * 1.8
|
||||
ret = exchange.get_historic_ohlcv(pair, "5m", int((
|
||||
arrow.utcnow().int_timestamp - since) * 1000))
|
||||
|
||||
@@ -1473,7 +1448,7 @@ def test_get_historic_ohlcv_as_df(default_conf, mocker, exchange_name):
|
||||
exchange._async_get_candle_history = Mock(wraps=mock_candle_hist)
|
||||
# one_call calculation * 1.8 should do 2 calls
|
||||
|
||||
since = 5 * 60 * exchange._ft_has['ohlcv_candle_limit'] * 1.8
|
||||
since = 5 * 60 * exchange.ohlcv_candle_limit('5m') * 1.8
|
||||
ret = exchange.get_historic_ohlcv_as_df(pair, "5m", int((
|
||||
arrow.utcnow().int_timestamp - since) * 1000))
|
||||
|
||||
@@ -1656,6 +1631,9 @@ def test_get_next_limit_in_list():
|
||||
# Going over the limit ...
|
||||
assert Exchange.get_next_limit_in_list(1001, limit_range) == 1000
|
||||
assert Exchange.get_next_limit_in_list(2000, limit_range) == 1000
|
||||
# Without required range
|
||||
assert Exchange.get_next_limit_in_list(2000, limit_range, False) is None
|
||||
assert Exchange.get_next_limit_in_list(15, limit_range, False) == 20
|
||||
|
||||
assert Exchange.get_next_limit_in_list(21, None) == 21
|
||||
assert Exchange.get_next_limit_in_list(100, None) == 100
|
||||
@@ -1706,6 +1684,152 @@ def test_fetch_l2_order_book_exception(default_conf, mocker, exchange_name):
|
||||
exchange.fetch_l2_order_book(pair='ETH/BTC', limit=50)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", [
|
||||
('ask', 20, 19, 10, 0.0, 20), # Full ask side
|
||||
('ask', 20, 19, 10, 1.0, 10), # Full last side
|
||||
('ask', 20, 19, 10, 0.5, 15), # Between ask and last
|
||||
('ask', 20, 19, 10, 0.7, 13), # Between ask and last
|
||||
('ask', 20, 19, 10, 0.3, 17), # Between ask and last
|
||||
('ask', 5, 6, 10, 1.0, 5), # last bigger than ask
|
||||
('ask', 5, 6, 10, 0.5, 5), # last bigger than ask
|
||||
('ask', 10, 20, None, 0.5, 10), # last not available - uses ask
|
||||
('ask', 4, 5, None, 0.5, 4), # last not available - uses ask
|
||||
('ask', 4, 5, None, 1, 4), # last not available - uses ask
|
||||
('ask', 4, 5, None, 0, 4), # last not available - uses ask
|
||||
('bid', 21, 20, 10, 0.0, 20), # Full bid side
|
||||
('bid', 21, 20, 10, 1.0, 10), # Full last side
|
||||
('bid', 21, 20, 10, 0.5, 15), # Between bid and last
|
||||
('bid', 21, 20, 10, 0.7, 13), # Between bid and last
|
||||
('bid', 21, 20, 10, 0.3, 17), # Between bid and last
|
||||
('bid', 6, 5, 10, 1.0, 5), # last bigger than bid
|
||||
('bid', 6, 5, 10, 0.5, 5), # last bigger than bid
|
||||
('bid', 21, 20, None, 0.5, 20), # last not available - uses bid
|
||||
('bid', 6, 5, None, 0.5, 5), # last not available - uses bid
|
||||
('bid', 6, 5, None, 1, 5), # last not available - uses bid
|
||||
('bid', 6, 5, None, 0, 5), # last not available - uses bid
|
||||
])
|
||||
def test_get_buy_rate(mocker, default_conf, caplog, side, ask, bid,
|
||||
last, last_ab, expected) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
default_conf['bid_strategy']['ask_last_balance'] = last_ab
|
||||
default_conf['bid_strategy']['price_side'] = side
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
return_value={'ask': ask, 'last': last, 'bid': bid})
|
||||
|
||||
assert exchange.get_buy_rate('ETH/BTC', True) == expected
|
||||
assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
|
||||
|
||||
assert exchange.get_buy_rate('ETH/BTC', False) == expected
|
||||
assert log_has("Using cached buy rate for ETH/BTC.", caplog)
|
||||
# Running a 2nd time with Refresh on!
|
||||
caplog.clear()
|
||||
assert exchange.get_buy_rate('ETH/BTC', True) == expected
|
||||
assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', [
|
||||
('bid', 12.0, 11.0, 11.5, 0.0, 11.0), # full bid side
|
||||
('bid', 12.0, 11.0, 11.5, 1.0, 11.5), # full last side
|
||||
('bid', 12.0, 11.0, 11.5, 0.5, 11.25), # between bid and lat
|
||||
('bid', 12.0, 11.2, 10.5, 0.0, 11.2), # Last smaller than bid
|
||||
('bid', 12.0, 11.2, 10.5, 1.0, 11.2), # Last smaller than bid - uses bid
|
||||
('bid', 12.0, 11.2, 10.5, 0.5, 11.2), # Last smaller than bid - uses bid
|
||||
('bid', 0.003, 0.002, 0.005, 0.0, 0.002),
|
||||
('ask', 12.0, 11.0, 12.5, 0.0, 12.0), # full ask side
|
||||
('ask', 12.0, 11.0, 12.5, 1.0, 12.5), # full last side
|
||||
('ask', 12.0, 11.0, 12.5, 0.5, 12.25), # between bid and lat
|
||||
('ask', 12.2, 11.2, 10.5, 0.0, 12.2), # Last smaller than ask
|
||||
('ask', 12.0, 11.0, 10.5, 1.0, 12.0), # Last smaller than ask - uses ask
|
||||
('ask', 12.0, 11.2, 10.5, 0.5, 12.0), # Last smaller than ask - uses ask
|
||||
('ask', 10.0, 11.0, 11.0, 0.0, 10.0),
|
||||
('ask', 10.11, 11.2, 11.0, 0.0, 10.11),
|
||||
('ask', 0.001, 0.002, 11.0, 0.0, 0.001),
|
||||
('ask', 0.006, 1.0, 11.0, 0.0, 0.006),
|
||||
])
|
||||
def test_get_sell_rate(default_conf, mocker, caplog, side, bid, ask,
|
||||
last, last_ab, expected) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
default_conf['ask_strategy']['price_side'] = side
|
||||
default_conf['ask_strategy']['bid_last_balance'] = last_ab
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
return_value={'ask': ask, 'bid': bid, 'last': last})
|
||||
pair = "ETH/BTC"
|
||||
|
||||
# Test regular mode
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
rate = exchange.get_sell_rate(pair, True)
|
||||
assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
|
||||
assert isinstance(rate, float)
|
||||
assert rate == expected
|
||||
# Use caching
|
||||
rate = exchange.get_sell_rate(pair, False)
|
||||
assert rate == expected
|
||||
assert log_has("Using cached sell rate for ETH/BTC.", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('side,expected', [
|
||||
('bid', 0.043936), # Value from order_book_l2 fiture - bids side
|
||||
('ask', 0.043949), # Value from order_book_l2 fiture - asks side
|
||||
])
|
||||
def test_get_sell_rate_orderbook(default_conf, mocker, caplog, side, expected, order_book_l2):
|
||||
caplog.set_level(logging.DEBUG)
|
||||
# Test orderbook mode
|
||||
default_conf['ask_strategy']['price_side'] = side
|
||||
default_conf['ask_strategy']['use_order_book'] = True
|
||||
default_conf['ask_strategy']['order_book_min'] = 1
|
||||
default_conf['ask_strategy']['order_book_max'] = 2
|
||||
pair = "ETH/BTC"
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
rate = exchange.get_sell_rate(pair, True)
|
||||
assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
|
||||
assert isinstance(rate, float)
|
||||
assert rate == expected
|
||||
rate = exchange.get_sell_rate(pair, False)
|
||||
assert rate == expected
|
||||
assert log_has("Using cached sell rate for ETH/BTC.", caplog)
|
||||
|
||||
|
||||
def test_get_sell_rate_orderbook_exception(default_conf, mocker, caplog):
|
||||
# Test orderbook mode
|
||||
default_conf['ask_strategy']['price_side'] = 'ask'
|
||||
default_conf['ask_strategy']['use_order_book'] = True
|
||||
default_conf['ask_strategy']['order_book_min'] = 1
|
||||
default_conf['ask_strategy']['order_book_max'] = 2
|
||||
pair = "ETH/BTC"
|
||||
# Test What happens if the exchange returns an empty orderbook.
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book',
|
||||
return_value={'bids': [[]], 'asks': [[]]})
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
with pytest.raises(PricingError):
|
||||
exchange.get_sell_rate(pair, True)
|
||||
assert log_has("Sell Price at location from orderbook could not be determined.", caplog)
|
||||
|
||||
|
||||
def test_get_sell_rate_exception(default_conf, mocker, caplog):
|
||||
# Ticker on one side can be empty in certain circumstances.
|
||||
default_conf['ask_strategy']['price_side'] = 'ask'
|
||||
pair = "ETH/BTC"
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
return_value={'ask': None, 'bid': 0.12, 'last': None})
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."):
|
||||
exchange.get_sell_rate(pair, True)
|
||||
|
||||
exchange._config['ask_strategy']['price_side'] = 'bid'
|
||||
assert exchange.get_sell_rate(pair, True) == 0.12
|
||||
# Reverse sides
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
return_value={'ask': 0.13, 'bid': None, 'last': None})
|
||||
with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."):
|
||||
exchange.get_sell_rate(pair, True)
|
||||
|
||||
exchange._config['ask_strategy']['price_side'] = 'ask'
|
||||
assert exchange.get_sell_rate(pair, True) == 0.13
|
||||
|
||||
|
||||
def make_fetch_ohlcv_mock(data):
|
||||
def fetch_ohlcv_mock(pair, timeframe, since):
|
||||
if since:
|
||||
@@ -2106,6 +2230,46 @@ def test_cancel_stoploss_order(default_conf, mocker, exchange_name):
|
||||
order_id='_', pair='TKN/BTC')
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
|
||||
default_conf['dry_run'] = False
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', return_value={'for': 123})
|
||||
mocker.patch('freqtrade.exchange.Ftx.fetch_stoploss_order', return_value={'for': 123})
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
|
||||
return_value={'fee': {}, 'status': 'canceled', 'amount': 1234})
|
||||
mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order',
|
||||
return_value={'fee': {}, 'status': 'canceled', 'amount': 1234})
|
||||
co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
|
||||
assert co == {'fee': {}, 'status': 'canceled', 'amount': 1234}
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
|
||||
return_value='canceled')
|
||||
mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order',
|
||||
return_value='canceled')
|
||||
# Fall back to fetch_stoploss_order
|
||||
co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
|
||||
assert co == {'for': 123}
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order',
|
||||
side_effect=InvalidOrderException(""))
|
||||
mocker.patch('freqtrade.exchange.Ftx.fetch_stoploss_order',
|
||||
side_effect=InvalidOrderException(""))
|
||||
|
||||
co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
|
||||
assert co['amount'] == 555
|
||||
assert co == {'fee': {}, 'status': 'canceled', 'amount': 555, 'info': {}}
|
||||
|
||||
with pytest.raises(InvalidOrderException):
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
|
||||
side_effect=InvalidOrderException("Did not find order"))
|
||||
mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order',
|
||||
side_effect=InvalidOrderException("Did not find order"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=123)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_fetch_order(default_conf, mocker, exchange_name):
|
||||
default_conf['dry_run'] = True
|
||||
@@ -2276,12 +2440,20 @@ def test_get_fee(default_conf, mocker, exchange_name):
|
||||
'cost': 0.05
|
||||
})
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
exchange._config.pop('fee', None)
|
||||
|
||||
assert exchange.get_fee('ETH/BTC') == 0.025
|
||||
assert api_mock.calculate_fee.call_count == 1
|
||||
|
||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
|
||||
'get_fee', 'calculate_fee', symbol="ETH/BTC")
|
||||
|
||||
api_mock.calculate_fee.reset_mock()
|
||||
exchange._config['fee'] = 0.001
|
||||
|
||||
assert exchange.get_fee('ETH/BTC') == 0.001
|
||||
assert api_mock.calculate_fee.call_count == 0
|
||||
|
||||
|
||||
def test_stoploss_order_unsupported_exchange(default_conf, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='bittrex')
|
||||
@@ -2418,6 +2590,19 @@ def test_get_markets_error(default_conf, mocker):
|
||||
ex.get_markets('LTC', 'USDT', True, False)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_ohlcv_candle_limit(default_conf, mocker, exchange_name):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
timeframes = ('1m', '5m', '1h')
|
||||
expected = exchange._ft_has['ohlcv_candle_limit']
|
||||
for timeframe in timeframes:
|
||||
if 'ohlcv_candle_limit_per_timeframe' in exchange._ft_has:
|
||||
expected = exchange._ft_has['ohlcv_candle_limit_per_timeframe'][timeframe]
|
||||
# This should only run for bittrex
|
||||
assert exchange_name == 'bittrex'
|
||||
assert exchange.ohlcv_candle_limit(timeframe) == expected
|
||||
|
||||
|
||||
def test_timeframe_to_minutes():
|
||||
assert timeframe_to_minutes("5m") == 5
|
||||
assert timeframe_to_minutes("10m") == 10
|
||||
|
@@ -39,8 +39,9 @@ def test_stoploss_order_ftx(default_conf, mocker):
|
||||
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
|
||||
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
|
||||
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
|
||||
assert api_mock.create_order.call_args_list[0][1]['price'] == 190
|
||||
assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params']
|
||||
assert 'stopPrice' in api_mock.create_order.call_args_list[0][1]['params']
|
||||
assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 190
|
||||
|
||||
assert api_mock.create_order.call_count == 1
|
||||
|
||||
@@ -55,8 +56,8 @@ def test_stoploss_order_ftx(default_conf, mocker):
|
||||
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
|
||||
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
|
||||
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
|
||||
assert api_mock.create_order.call_args_list[0][1]['price'] == 220
|
||||
assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params']
|
||||
assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
||||
@@ -69,9 +70,9 @@ def test_stoploss_order_ftx(default_conf, mocker):
|
||||
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
|
||||
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
|
||||
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
|
||||
assert api_mock.create_order.call_args_list[0][1]['price'] == 220
|
||||
assert 'orderPrice' in api_mock.create_order.call_args_list[0][1]['params']
|
||||
assert api_mock.create_order.call_args_list[0][1]['params']['orderPrice'] == 217.8
|
||||
assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220
|
||||
|
||||
# test exception handling
|
||||
with pytest.raises(DependencyException):
|
||||
@@ -124,7 +125,7 @@ def test_stoploss_adjust_ftx(mocker, default_conf):
|
||||
assert not exchange.stoploss_adjust(1501, order)
|
||||
|
||||
|
||||
def test_fetch_stoploss_order(default_conf, mocker):
|
||||
def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order):
|
||||
default_conf['dry_run'] = True
|
||||
order = MagicMock()
|
||||
order.myid = 123
|
||||
@@ -146,6 +147,17 @@ def test_fetch_stoploss_order(default_conf, mocker):
|
||||
with pytest.raises(InvalidOrderException, match=r"Could not get stoploss order for id X"):
|
||||
exchange.fetch_stoploss_order('X', 'TKN/BTC')['status']
|
||||
|
||||
api_mock.fetch_orders = MagicMock(return_value=[{'id': 'X', 'status': 'closed'}])
|
||||
api_mock.fetch_order = MagicMock(return_value=limit_sell_order)
|
||||
|
||||
resp = exchange.fetch_stoploss_order('X', 'TKN/BTC')
|
||||
assert resp
|
||||
assert api_mock.fetch_order.call_count == 1
|
||||
assert resp['id_stop'] == 'mocked_limit_sell'
|
||||
assert resp['id'] == 'X'
|
||||
assert resp['type'] == 'stop'
|
||||
assert resp['status_stop'] == 'triggered'
|
||||
|
||||
with pytest.raises(InvalidOrderException):
|
||||
api_mock.fetch_orders = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx')
|
||||
@@ -156,3 +168,26 @@ def test_fetch_stoploss_order(default_conf, mocker):
|
||||
'fetch_stoploss_order', 'fetch_orders',
|
||||
retries=API_FETCH_ORDER_RETRY_COUNT + 1,
|
||||
order_id='_', pair='TKN/BTC')
|
||||
|
||||
|
||||
def test_get_order_id(mocker, default_conf):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='ftx')
|
||||
order = {
|
||||
'type': STOPLOSS_ORDERTYPE,
|
||||
'price': 1500,
|
||||
'id': '1111',
|
||||
'id_stop': '1234',
|
||||
'info': {
|
||||
}
|
||||
}
|
||||
assert exchange.get_order_id_conditional(order) == '1234'
|
||||
|
||||
order = {
|
||||
'type': 'limit',
|
||||
'price': 1500,
|
||||
'id': '1111',
|
||||
'id_stop': '1234',
|
||||
'info': {
|
||||
}
|
||||
}
|
||||
assert exchange.get_order_id_conditional(order) == '1111'
|
||||
|
@@ -90,6 +90,7 @@ def test_get_balances_prod(default_conf, mocker):
|
||||
'3ST': balance_item.copy(),
|
||||
'4ST': balance_item.copy(),
|
||||
'EUR': balance_item.copy(),
|
||||
'timestamp': 123123
|
||||
})
|
||||
kraken_open_orders = [{'symbol': '1ST/EUR',
|
||||
'type': 'limit',
|
||||
@@ -138,7 +139,7 @@ def test_get_balances_prod(default_conf, mocker):
|
||||
default_conf['dry_run'] = False
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
|
||||
balances = exchange.get_balances()
|
||||
assert len(balances) == 5
|
||||
assert len(balances) == 6
|
||||
|
||||
assert balances['1ST']['free'] == 9.0
|
||||
assert balances['1ST']['total'] == 10.0
|
||||
|
@@ -3,8 +3,8 @@ from typing import Dict, List, NamedTuple, Optional
|
||||
import arrow
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.enums import SellType
|
||||
from freqtrade.exchange import timeframe_to_minutes
|
||||
from freqtrade.strategy.interface import SellType
|
||||
|
||||
|
||||
tests_start_time = arrow.get(2018, 10, 3)
|
||||
|
@@ -5,8 +5,8 @@ from pathlib import Path
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from freqtrade.enums import RunMode, SellType
|
||||
from freqtrade.optimize.hyperopt import Hyperopt
|
||||
from freqtrade.strategy.interface import SellType
|
||||
from tests.conftest import patch_exchange
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ from tests.conftest import patch_exchange
|
||||
def hyperopt_conf(default_conf):
|
||||
hyperconf = deepcopy(default_conf)
|
||||
hyperconf.update({
|
||||
'datadir': Path(default_conf['datadir']),
|
||||
'runmode': RunMode.HYPEROPT,
|
||||
'hyperopt': 'DefaultHyperOpt',
|
||||
'hyperopt_loss': 'ShortTradeDurHyperOptLoss',
|
||||
'hyperopt_path': str(Path(__file__).parent / 'hyperopts'),
|
||||
@@ -21,6 +23,7 @@ def hyperopt_conf(default_conf):
|
||||
'timerange': None,
|
||||
'spaces': ['default'],
|
||||
'hyperopt_jobs': 1,
|
||||
'hyperopt_min_trades': 1,
|
||||
})
|
||||
return hyperconf
|
||||
|
||||
|
@@ -1,12 +1,11 @@
|
||||
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, C0330, unused-argument
|
||||
import logging
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.data.history import get_timerange
|
||||
from freqtrade.enums import SellType
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
from freqtrade.strategy.interface import SellType
|
||||
from tests.conftest import patch_exchange
|
||||
from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe,
|
||||
_get_frame_time_from_offset, tests_timeframe)
|
||||
@@ -186,7 +185,7 @@ tc11 = BTContainer(data=[
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
|
||||
[1, 5000, 5050, 4950, 5100, 6172, 0, 0],
|
||||
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
|
||||
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
|
||||
[3, 5000, 5150, 4650, 4750, 6172, 0, 0],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True,
|
||||
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
|
||||
@@ -269,7 +268,7 @@ tc16 = BTContainer(data=[
|
||||
# Test 17: Buy, hold for 120 mins, then forcesell using roi=-1
|
||||
# Causes negative profit even though sell-reason is ROI.
|
||||
# stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration)
|
||||
# Uses open as sell-rate (special case) - since the roi-time is a multiple of the ticker interval.
|
||||
# Uses open as sell-rate (special case) - since the roi-time is a multiple of the timeframe.
|
||||
tc17 = BTContainer(data=[
|
||||
# D O H L C V B S
|
||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||
@@ -441,6 +440,23 @@ tc27 = BTContainer(data=[
|
||||
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=4)]
|
||||
)
|
||||
|
||||
# Test 28: trailing_stop should raise so candle 3 causes a stoploss
|
||||
# Same case than tc11 - but candle 3 "gaps down" - the stoploss will be above the candle,
|
||||
# therefore "open" will be used
|
||||
# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2
|
||||
tc28 = BTContainer(data=[
|
||||
# D O H L C V B S
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
|
||||
[1, 5000, 5050, 4950, 5100, 6172, 0, 0],
|
||||
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
|
||||
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.03, trailing_stop=True,
|
||||
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
|
||||
trailing_stop_positive=0.03,
|
||||
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
|
||||
)
|
||||
|
||||
TESTS = [
|
||||
tc0,
|
||||
tc1,
|
||||
@@ -470,6 +486,7 @@ TESTS = [
|
||||
tc25,
|
||||
tc26,
|
||||
tc27,
|
||||
tc28,
|
||||
]
|
||||
|
||||
|
||||
@@ -489,10 +506,12 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
|
||||
default_conf["trailing_stop_positive_offset"] = data.trailing_stop_positive_offset
|
||||
default_conf["ask_strategy"] = {"use_sell_signal": data.use_sell_signal}
|
||||
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_fee", MagicMock(return_value=0.0))
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_fee", return_value=0.0)
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
|
||||
patch_exchange(mocker)
|
||||
frame = _build_backtest_dataframe(data.data)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
backtesting.strategy.advise_buy = lambda a, m: frame
|
||||
backtesting.strategy.advise_sell = lambda a, m: frame
|
||||
caplog.set_level(logging.DEBUG)
|
||||
@@ -501,19 +520,19 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
|
||||
# Dummy data as we mock the analyze functions
|
||||
data_processed = {pair: frame.copy()}
|
||||
min_date, max_date = get_timerange({pair: frame})
|
||||
results = backtesting.backtest(
|
||||
result = backtesting.backtest(
|
||||
processed=data_processed,
|
||||
stake_amount=default_conf['stake_amount'],
|
||||
start_date=min_date,
|
||||
end_date=max_date,
|
||||
max_open_trades=10,
|
||||
)
|
||||
|
||||
results = result['results']
|
||||
assert len(results) == len(data.trades)
|
||||
assert round(results["profit_ratio"].sum(), 3) == round(data.profit_perc, 3)
|
||||
|
||||
for c, trade in enumerate(data.trades):
|
||||
res = results.iloc[c]
|
||||
assert res.sell_reason == trade.sell_reason
|
||||
assert res.sell_reason == trade.sell_reason.value
|
||||
assert res.open_date == _get_frame_time_from_offset(trade.open_tick)
|
||||
assert res.close_date == _get_frame_time_from_offset(trade.close_tick)
|
||||
|
@@ -9,7 +9,6 @@ import pandas as pd
|
||||
import pytest
|
||||
from arrow import Arrow
|
||||
|
||||
from freqtrade import constants
|
||||
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_backtesting
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.data import history
|
||||
@@ -17,11 +16,11 @@ from freqtrade.data.btanalysis import BT_DATA_COLUMNS, evaluate_result_multi
|
||||
from freqtrade.data.converter import clean_ohlcv_dataframe
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.data.history import get_timerange
|
||||
from freqtrade.enums import RunMode, SellType
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
from freqtrade.persistence import LocalTrade
|
||||
from freqtrade.resolvers import StrategyResolver
|
||||
from freqtrade.state import RunMode
|
||||
from freqtrade.strategy.interface import SellType
|
||||
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
||||
patched_configuration_load_config_file)
|
||||
|
||||
@@ -83,6 +82,7 @@ def simple_backtest(config, contour, mocker, testdatadir) -> None:
|
||||
patch_exchange(mocker)
|
||||
config['timeframe'] = '1m'
|
||||
backtesting = Backtesting(config)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
|
||||
data = load_data_test(contour, testdatadir)
|
||||
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
|
||||
@@ -90,7 +90,6 @@ def simple_backtest(config, contour, mocker, testdatadir) -> None:
|
||||
assert isinstance(processed, dict)
|
||||
results = backtesting.backtest(
|
||||
processed=processed,
|
||||
stake_amount=config['stake_amount'],
|
||||
start_date=min_date,
|
||||
end_date=max_date,
|
||||
max_open_trades=1,
|
||||
@@ -107,11 +106,11 @@ def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'):
|
||||
data = trim_dictlist(data, -201)
|
||||
patch_exchange(mocker)
|
||||
backtesting = Backtesting(conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
|
||||
min_date, max_date = get_timerange(processed)
|
||||
return {
|
||||
'processed': processed,
|
||||
'stake_amount': conf['stake_amount'],
|
||||
'start_date': min_date,
|
||||
'end_date': max_date,
|
||||
'max_open_trades': 10,
|
||||
@@ -233,8 +232,7 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
|
||||
assert log_has('Parameter --fee detected, setting fee to: {} ...'.format(config['fee']), caplog)
|
||||
|
||||
|
||||
def test_setup_optimize_configuration_unlimited_stake_amount(mocker, default_conf, caplog) -> None:
|
||||
default_conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
|
||||
def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog) -> None:
|
||||
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
@@ -242,9 +240,21 @@ def test_setup_optimize_configuration_unlimited_stake_amount(mocker, default_con
|
||||
'backtesting',
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--stake-amount', '1',
|
||||
'--starting-balance', '2'
|
||||
]
|
||||
|
||||
with pytest.raises(DependencyException, match=r'.`stake_amount`.*'):
|
||||
conf = setup_optimize_configuration(get_args(args), RunMode.BACKTEST)
|
||||
assert isinstance(conf, dict)
|
||||
|
||||
args = [
|
||||
'backtesting',
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--stake-amount', '1',
|
||||
'--starting-balance', '0.5'
|
||||
]
|
||||
with pytest.raises(OperationalException, match=r"Starting balance .* smaller .*"):
|
||||
setup_optimize_configuration(get_args(args), RunMode.BACKTEST)
|
||||
|
||||
|
||||
@@ -276,6 +286,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None:
|
||||
patch_exchange(mocker)
|
||||
get_fee = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
assert backtesting.config == default_conf
|
||||
assert backtesting.timeframe == '5m'
|
||||
assert callable(backtesting.strategy.ohlcvdata_to_dataframe)
|
||||
@@ -306,11 +317,13 @@ def test_data_with_fee(default_conf, mocker, testdatadir) -> None:
|
||||
|
||||
fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
assert backtesting.fee == 0.1234
|
||||
assert fee_mock.call_count == 0
|
||||
|
||||
default_conf['fee'] = 0.0
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
assert backtesting.fee == 0.0
|
||||
assert fee_mock.call_count == 0
|
||||
|
||||
@@ -321,6 +334,7 @@ def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
|
||||
data = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange,
|
||||
fill_up_missing=True)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
|
||||
assert len(processed['UNITTEST/BTC']) == 102
|
||||
|
||||
@@ -352,12 +366,13 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
|
||||
default_conf['timerange'] = '-1510694220'
|
||||
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
backtesting.strategy.bot_loop_start = MagicMock()
|
||||
backtesting.start()
|
||||
# check the logs, that will contain the backtest result
|
||||
exists = [
|
||||
'Backtesting with data from 2017-11-14 21:17:00 '
|
||||
'up to 2017-11-14 22:59:00 (0 days)..'
|
||||
'up to 2017-11-14 22:59:00 (0 days).'
|
||||
]
|
||||
for line in exists:
|
||||
assert log_has(line, caplog)
|
||||
@@ -384,6 +399,7 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) ->
|
||||
default_conf['timerange'] = '20180101-20180102'
|
||||
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
with pytest.raises(OperationalException, match='No data found. Terminating.'):
|
||||
backtesting.start()
|
||||
|
||||
@@ -448,25 +464,73 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti
|
||||
Backtesting(default_conf)
|
||||
|
||||
|
||||
def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
|
||||
default_conf['ask_strategy']['use_sell_signal'] = False
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
|
||||
patch_exchange(mocker)
|
||||
default_conf['stake_amount'] = 'unlimited'
|
||||
default_conf['max_open_trades'] = 2
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
pair = 'UNITTEST/BTC'
|
||||
row = [
|
||||
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0),
|
||||
1, # Sell
|
||||
0.001, # Open
|
||||
0.0011, # Close
|
||||
0, # Sell
|
||||
0.00099, # Low
|
||||
0.0012, # High
|
||||
]
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
assert isinstance(trade, LocalTrade)
|
||||
assert trade.stake_amount == 495
|
||||
|
||||
# Fake 2 trades, so there's not enough amount for the next trade left.
|
||||
LocalTrade.trades_open.append(trade)
|
||||
LocalTrade.trades_open.append(trade)
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
assert trade is None
|
||||
LocalTrade.trades_open.pop()
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
assert trade is not None
|
||||
|
||||
# Stake-amount too high!
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=600.0)
|
||||
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
assert trade is None
|
||||
|
||||
# Stake-amount throwing error
|
||||
mocker.patch("freqtrade.wallets.Wallets.get_trade_stake_amount",
|
||||
side_effect=DependencyException)
|
||||
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
assert trade is None
|
||||
|
||||
|
||||
def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||
default_conf['ask_strategy']['use_sell_signal'] = False
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
|
||||
patch_exchange(mocker)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
pair = 'UNITTEST/BTC'
|
||||
timerange = TimeRange('date', None, 1517227800, 0)
|
||||
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'],
|
||||
timerange=timerange)
|
||||
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
|
||||
min_date, max_date = get_timerange(processed)
|
||||
results = backtesting.backtest(
|
||||
result = backtesting.backtest(
|
||||
processed=processed,
|
||||
stake_amount=default_conf['stake_amount'],
|
||||
start_date=min_date,
|
||||
end_date=max_date,
|
||||
max_open_trades=10,
|
||||
position_stacking=False,
|
||||
)
|
||||
results = result['results']
|
||||
assert not results.empty
|
||||
assert len(results) == 2
|
||||
|
||||
@@ -486,7 +550,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||
'trade_duration': [235, 40],
|
||||
'profit_ratio': [0.0, 0.0],
|
||||
'profit_abs': [0.0, 0.0],
|
||||
'sell_reason': [SellType.ROI, SellType.ROI],
|
||||
'sell_reason': [SellType.ROI.value, SellType.ROI.value],
|
||||
'initial_stop_loss_abs': [0.0940005, 0.09272236],
|
||||
'initial_stop_loss_ratio': [-0.1, -0.1],
|
||||
'stop_loss_abs': [0.0940005, 0.09272236],
|
||||
@@ -512,8 +576,10 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||
def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None:
|
||||
default_conf['ask_strategy']['use_sell_signal'] = False
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
|
||||
patch_exchange(mocker)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
|
||||
# Run a backtesting for an exiting 1min timeframe
|
||||
timerange = TimeRange.parse_timerange('1510688220-1510700340')
|
||||
@@ -523,19 +589,19 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
|
||||
min_date, max_date = get_timerange(processed)
|
||||
results = backtesting.backtest(
|
||||
processed=processed,
|
||||
stake_amount=default_conf['stake_amount'],
|
||||
start_date=min_date,
|
||||
end_date=max_date,
|
||||
max_open_trades=1,
|
||||
position_stacking=False,
|
||||
)
|
||||
assert not results.empty
|
||||
assert len(results) == 1
|
||||
assert not results['results'].empty
|
||||
assert len(results['results']) == 1
|
||||
|
||||
|
||||
def test_processed(default_conf, mocker, testdatadir) -> None:
|
||||
patch_exchange(mocker)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
|
||||
dict_of_tickerrows = load_data_test('raise', testdatadir)
|
||||
dataframes = backtesting.strategy.ohlcvdata_to_dataframe(dict_of_tickerrows)
|
||||
@@ -558,6 +624,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
|
||||
|
||||
default_conf['enable_protections'] = True
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
|
||||
tests = [
|
||||
['sine', 9],
|
||||
['raise', 10],
|
||||
@@ -568,7 +635,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
|
||||
# While buy-signals are unrealistic, running backtesting
|
||||
# over and over again should not cause different results
|
||||
for [contour, numres] in tests:
|
||||
assert len(simple_backtest(default_conf, contour, mocker, testdatadir)) == numres
|
||||
assert len(simple_backtest(default_conf, contour, mocker, testdatadir)['results']) == numres
|
||||
|
||||
|
||||
@pytest.mark.parametrize('protections,contour,expected', [
|
||||
@@ -589,10 +656,11 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
|
||||
default_conf['protections'] = protections
|
||||
default_conf['enable_protections'] = True
|
||||
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
# While buy-signals are unrealistic, running backtesting
|
||||
# over and over again should not cause different results
|
||||
assert len(simple_backtest(default_conf, contour, mocker, testdatadir)) == expected
|
||||
assert len(simple_backtest(default_conf, contour, mocker, testdatadir)['results']) == expected
|
||||
|
||||
|
||||
def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
|
||||
@@ -604,10 +672,11 @@ def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
|
||||
|
||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
backtesting.strategy.advise_buy = fun # Override
|
||||
backtesting.strategy.advise_sell = fun # Override
|
||||
results = backtesting.backtest(**backtest_conf)
|
||||
assert results.empty
|
||||
result = backtesting.backtest(**backtest_conf)
|
||||
assert result['results'].empty
|
||||
|
||||
|
||||
def test_backtest_only_sell(mocker, default_conf, testdatadir):
|
||||
@@ -619,24 +688,28 @@ def test_backtest_only_sell(mocker, default_conf, testdatadir):
|
||||
|
||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
backtesting.strategy.advise_buy = fun # Override
|
||||
backtesting.strategy.advise_sell = fun # Override
|
||||
results = backtesting.backtest(**backtest_conf)
|
||||
assert results.empty
|
||||
result = backtesting.backtest(**backtest_conf)
|
||||
assert result['results'].empty
|
||||
|
||||
|
||||
def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf,
|
||||
pair='UNITTEST/BTC', datadir=testdatadir)
|
||||
default_conf['timeframe'] = '1m'
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
backtesting.strategy.advise_buy = _trend_alternate # Override
|
||||
backtesting.strategy.advise_sell = _trend_alternate # Override
|
||||
results = backtesting.backtest(**backtest_conf)
|
||||
result = backtesting.backtest(**backtest_conf)
|
||||
# 200 candles in backtest data
|
||||
# won't buy on first (shifted by 1)
|
||||
# 100 buys signals
|
||||
results = result['results']
|
||||
assert len(results) == 100
|
||||
# One trade was force-closed at the end
|
||||
assert len(results.loc[results['is_open']]) == 0
|
||||
@@ -658,6 +731,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
|
||||
dataframe['sell'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0)
|
||||
return dataframe
|
||||
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
patch_exchange(mocker)
|
||||
|
||||
@@ -671,6 +745,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
|
||||
default_conf['timeframe'] = '5m'
|
||||
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
backtesting.strategy.advise_buy = _trend_alternate_hold # Override
|
||||
backtesting.strategy.advise_sell = _trend_alternate_hold # Override
|
||||
|
||||
@@ -678,7 +753,6 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
|
||||
min_date, max_date = get_timerange(processed)
|
||||
backtest_conf = {
|
||||
'processed': processed,
|
||||
'stake_amount': default_conf['stake_amount'],
|
||||
'start_date': min_date,
|
||||
'end_date': max_date,
|
||||
'max_open_trades': 3,
|
||||
@@ -688,20 +762,19 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
|
||||
results = backtesting.backtest(**backtest_conf)
|
||||
|
||||
# Make sure we have parallel trades
|
||||
assert len(evaluate_result_multi(results, '5m', 2)) > 0
|
||||
assert len(evaluate_result_multi(results['results'], '5m', 2)) > 0
|
||||
# make sure we don't have trades with more than configured max_open_trades
|
||||
assert len(evaluate_result_multi(results, '5m', 3)) == 0
|
||||
assert len(evaluate_result_multi(results['results'], '5m', 3)) == 0
|
||||
|
||||
backtest_conf = {
|
||||
'processed': processed,
|
||||
'stake_amount': default_conf['stake_amount'],
|
||||
'start_date': min_date,
|
||||
'end_date': max_date,
|
||||
'max_open_trades': 1,
|
||||
'position_stacking': False,
|
||||
}
|
||||
results = backtesting.backtest(**backtest_conf)
|
||||
assert len(evaluate_result_multi(results, '5m', 1)) == 0
|
||||
assert len(evaluate_result_multi(results['results'], '5m', 1)) == 0
|
||||
|
||||
|
||||
def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
||||
@@ -733,9 +806,9 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
||||
'Parameter --timerange detected: 1510694220-1510700340 ...',
|
||||
f'Using data directory: {testdatadir} ...',
|
||||
'Loading data from 2017-11-14 20:57:00 '
|
||||
'up to 2017-11-14 22:58:00 (0 days)..',
|
||||
'up to 2017-11-14 22:58:00 (0 days).',
|
||||
'Backtesting with data from 2017-11-14 21:17:00 '
|
||||
'up to 2017-11-14 22:58:00 (0 days)..',
|
||||
'up to 2017-11-14 22:58:00 (0 days).',
|
||||
'Parameter --enable-position-stacking detected ...'
|
||||
]
|
||||
|
||||
@@ -746,8 +819,20 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
||||
@pytest.mark.filterwarnings("ignore:deprecated")
|
||||
def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
|
||||
default_conf['ask_strategy'].update({
|
||||
"use_sell_signal": True,
|
||||
"sell_profit_only": False,
|
||||
"sell_profit_offset": 0.0,
|
||||
"ignore_roi_if_buy_signal": False,
|
||||
})
|
||||
patch_exchange(mocker)
|
||||
backtestmock = MagicMock(return_value=pd.DataFrame(columns=BT_DATA_COLUMNS))
|
||||
backtestmock = MagicMock(return_value={
|
||||
'results': pd.DataFrame(columns=BT_DATA_COLUMNS),
|
||||
'config': default_conf,
|
||||
'locks': [],
|
||||
'rejected_signals': 20,
|
||||
'final_balance': 1000,
|
||||
})
|
||||
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
||||
PropertyMock(return_value=['UNITTEST/BTC']))
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
|
||||
@@ -761,7 +846,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
text_table_strategy=strattable_mock,
|
||||
generate_pair_metrics=MagicMock(),
|
||||
generate_sell_reason_stats=sell_reason_mock,
|
||||
generate_strategy_metrics=strat_summary,
|
||||
generate_strategy_comparison=strat_summary,
|
||||
generate_daily_stats=MagicMock(),
|
||||
)
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
@@ -795,9 +880,9 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
'Parameter --timerange detected: 1510694220-1510700340 ...',
|
||||
f'Using data directory: {testdatadir} ...',
|
||||
'Loading data from 2017-11-14 20:57:00 '
|
||||
'up to 2017-11-14 22:58:00 (0 days)..',
|
||||
'up to 2017-11-14 22:58:00 (0 days).',
|
||||
'Backtesting with data from 2017-11-14 21:17:00 '
|
||||
'up to 2017-11-14 22:58:00 (0 days)..',
|
||||
'up to 2017-11-14 22:58:00 (0 days).',
|
||||
'Parameter --enable-position-stacking detected ...',
|
||||
'Running backtesting for Strategy DefaultStrategy',
|
||||
'Running backtesting for Strategy TestStrategyLegacy',
|
||||
@@ -809,39 +894,60 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:deprecated")
|
||||
def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdatadir, capsys):
|
||||
|
||||
default_conf['ask_strategy'].update({
|
||||
"use_sell_signal": True,
|
||||
"sell_profit_only": False,
|
||||
"sell_profit_offset": 0.0,
|
||||
"ignore_roi_if_buy_signal": False,
|
||||
})
|
||||
patch_exchange(mocker)
|
||||
result1 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
|
||||
'profit_ratio': [0.0, 0.0],
|
||||
'profit_abs': [0.0, 0.0],
|
||||
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
|
||||
'2018-01-30 03:30:00', ], utc=True
|
||||
),
|
||||
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
|
||||
'2018-01-30 05:35:00', ], utc=True),
|
||||
'trade_duration': [235, 40],
|
||||
'is_open': [False, False],
|
||||
'stake_amount': [0.01, 0.01],
|
||||
'open_rate': [0.104445, 0.10302485],
|
||||
'close_rate': [0.104969, 0.103541],
|
||||
'sell_reason': [SellType.ROI, SellType.ROI]
|
||||
})
|
||||
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
|
||||
'profit_ratio': [0.03, 0.01, 0.1],
|
||||
'profit_abs': [0.01, 0.02, 0.2],
|
||||
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
|
||||
'2018-01-30 03:30:00',
|
||||
'2018-01-30 05:30:00'], utc=True
|
||||
),
|
||||
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
|
||||
'2018-01-30 05:35:00',
|
||||
'2018-01-30 08:30:00'], utc=True),
|
||||
'trade_duration': [47, 40, 20],
|
||||
'is_open': [False, False, False],
|
||||
'stake_amount': [0.01, 0.01, 0.01],
|
||||
'open_rate': [0.104445, 0.10302485, 0.122541],
|
||||
'close_rate': [0.104969, 0.103541, 0.123541],
|
||||
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
||||
})
|
||||
backtestmock = MagicMock(side_effect=[
|
||||
pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
|
||||
'profit_ratio': [0.0, 0.0],
|
||||
'profit_abs': [0.0, 0.0],
|
||||
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
|
||||
'2018-01-30 03:30:00', ], utc=True
|
||||
),
|
||||
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
|
||||
'2018-01-30 05:35:00', ], utc=True),
|
||||
'trade_duration': [235, 40],
|
||||
'is_open': [False, False],
|
||||
'open_rate': [0.104445, 0.10302485],
|
||||
'close_rate': [0.104969, 0.103541],
|
||||
'sell_reason': [SellType.ROI, SellType.ROI]
|
||||
}),
|
||||
pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
|
||||
'profit_ratio': [0.03, 0.01, 0.1],
|
||||
'profit_abs': [0.01, 0.02, 0.2],
|
||||
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
|
||||
'2018-01-30 03:30:00',
|
||||
'2018-01-30 05:30:00'], utc=True
|
||||
),
|
||||
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
|
||||
'2018-01-30 05:35:00',
|
||||
'2018-01-30 08:30:00'], utc=True),
|
||||
'trade_duration': [47, 40, 20],
|
||||
'is_open': [False, False, False],
|
||||
'open_rate': [0.104445, 0.10302485, 0.122541],
|
||||
'close_rate': [0.104969, 0.103541, 0.123541],
|
||||
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
||||
}),
|
||||
{
|
||||
'results': result1,
|
||||
'config': default_conf,
|
||||
'locks': [],
|
||||
'rejected_signals': 20,
|
||||
'final_balance': 1000,
|
||||
},
|
||||
{
|
||||
'results': result2,
|
||||
'config': default_conf,
|
||||
'locks': [],
|
||||
'rejected_signals': 20,
|
||||
'final_balance': 1000,
|
||||
}
|
||||
])
|
||||
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
||||
PropertyMock(return_value=['UNITTEST/BTC']))
|
||||
@@ -872,9 +978,9 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
||||
'Parameter --timerange detected: 1510694220-1510700340 ...',
|
||||
f'Using data directory: {testdatadir} ...',
|
||||
'Loading data from 2017-11-14 20:57:00 '
|
||||
'up to 2017-11-14 22:58:00 (0 days)..',
|
||||
'up to 2017-11-14 22:58:00 (0 days).',
|
||||
'Backtesting with data from 2017-11-14 21:17:00 '
|
||||
'up to 2017-11-14 22:58:00 (0 days)..',
|
||||
'up to 2017-11-14 22:58:00 (0 days).',
|
||||
'Parameter --enable-position-stacking detected ...',
|
||||
'Running backtesting for Strategy DefaultStrategy',
|
||||
'Running backtesting for Strategy TestStrategyLegacy',
|
||||
|
@@ -4,8 +4,8 @@
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_edge
|
||||
from freqtrade.enums import RunMode
|
||||
from freqtrade.optimize.edge_cli import EdgeCli
|
||||
from freqtrade.state import RunMode
|
||||
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
||||
patched_configuration_load_config_file)
|
||||
|
||||
|
@@ -1,24 +1,27 @@
|
||||
# pragma pylint: disable=missing-docstring,W0212,C0103
|
||||
import locale
|
||||
import logging
|
||||
import re
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import ANY, MagicMock
|
||||
|
||||
import pandas as pd
|
||||
import pytest
|
||||
from arrow import Arrow
|
||||
from filelock import Timeout
|
||||
|
||||
from freqtrade import constants
|
||||
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_hyperopt
|
||||
from freqtrade.data.history import load_data
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
from freqtrade.enums import RunMode, SellType
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.optimize.hyperopt import Hyperopt
|
||||
from freqtrade.optimize.hyperopt_auto import HyperOptAuto
|
||||
from freqtrade.optimize.hyperopt_tools import HyperoptTools
|
||||
from freqtrade.optimize.optimize_reports import generate_strategy_stats
|
||||
from freqtrade.optimize.space import SKDecimal
|
||||
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver
|
||||
from freqtrade.state import RunMode
|
||||
from freqtrade.strategy.hyper import IntParameter
|
||||
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
||||
patched_configuration_load_config_file)
|
||||
|
||||
@@ -26,23 +29,7 @@ from .hyperopts.default_hyperopt import DefaultHyperOpt
|
||||
|
||||
|
||||
# Functions for recurrent object patching
|
||||
def create_results(mocker, hyperopt, testdatadir) -> List[Dict]:
|
||||
"""
|
||||
When creating results, mock the hyperopt so that *by default*
|
||||
- we don't create any pickle'd files in the filesystem
|
||||
- we might have a pickle'd file so make sure that we return
|
||||
false when looking for it
|
||||
"""
|
||||
hyperopt.results_file = testdatadir / 'optimize/ut_results.pickle'
|
||||
|
||||
mocker.patch.object(Path, "is_file", MagicMock(return_value=False))
|
||||
stat_mock = MagicMock()
|
||||
stat_mock.st_size = 1
|
||||
mocker.patch.object(Path, "stat", MagicMock(return_value=stat_mock))
|
||||
|
||||
mocker.patch.object(Path, "unlink", MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.optimize.hyperopt.dump', return_value=None)
|
||||
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
|
||||
def create_results() -> List[Dict]:
|
||||
|
||||
return [{'loss': 1, 'result': 'foo', 'params': {}, 'is_best': True}]
|
||||
|
||||
@@ -130,8 +117,7 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
|
||||
assert log_has('Parameter --print-all detected ...', caplog)
|
||||
|
||||
|
||||
def test_setup_hyperopt_configuration_unlimited_stake_amount(mocker, default_conf) -> None:
|
||||
default_conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
|
||||
def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None:
|
||||
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
@@ -139,9 +125,20 @@ def test_setup_hyperopt_configuration_unlimited_stake_amount(mocker, default_con
|
||||
'hyperopt',
|
||||
'--config', 'config.json',
|
||||
'--hyperopt', 'DefaultHyperOpt',
|
||||
'--stake-amount', '1',
|
||||
'--starting-balance', '2'
|
||||
]
|
||||
conf = setup_optimize_configuration(get_args(args), RunMode.HYPEROPT)
|
||||
assert isinstance(conf, dict)
|
||||
|
||||
with pytest.raises(DependencyException, match=r'.`stake_amount`.*'):
|
||||
args = [
|
||||
'hyperopt',
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--stake-amount', '1',
|
||||
'--starting-balance', '0.5'
|
||||
]
|
||||
with pytest.raises(OperationalException, match=r"Starting balance .* smaller .*"):
|
||||
setup_optimize_configuration(get_args(args), RunMode.HYPEROPT)
|
||||
|
||||
|
||||
@@ -306,55 +303,50 @@ def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None:
|
||||
assert caplog.record_tuples == []
|
||||
|
||||
|
||||
def test_save_results_saves_epochs(mocker, hyperopt, testdatadir, caplog) -> None:
|
||||
epochs = create_results(mocker, hyperopt, testdatadir)
|
||||
mock_dump = mocker.patch('freqtrade.optimize.hyperopt.dump', return_value=None)
|
||||
mock_dump_json = mocker.patch('freqtrade.optimize.hyperopt.file_dump_json', return_value=None)
|
||||
results_file = testdatadir / 'optimize' / 'ut_results.pickle'
|
||||
def test_save_results_saves_epochs(mocker, hyperopt, tmpdir, caplog) -> None:
|
||||
# Test writing to temp dir and reading again
|
||||
epochs = create_results()
|
||||
hyperopt.results_file = Path(tmpdir / 'ut_results.fthypt')
|
||||
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
hyperopt.epochs = epochs
|
||||
hyperopt._save_results()
|
||||
assert log_has(f"1 epoch saved to '{results_file}'.", caplog)
|
||||
mock_dump.assert_called_once()
|
||||
mock_dump_json.assert_called_once()
|
||||
for epoch in epochs:
|
||||
hyperopt._save_result(epoch)
|
||||
assert log_has(f"1 epoch saved to '{hyperopt.results_file}'.", caplog)
|
||||
|
||||
hyperopt.epochs = epochs + epochs
|
||||
hyperopt._save_results()
|
||||
assert log_has(f"2 epochs saved to '{results_file}'.", caplog)
|
||||
hyperopt._save_result(epochs[0])
|
||||
assert log_has(f"2 epochs saved to '{hyperopt.results_file}'.", caplog)
|
||||
|
||||
hyperopt_epochs = HyperoptTools.load_previous_results(hyperopt.results_file)
|
||||
assert len(hyperopt_epochs) == 2
|
||||
|
||||
|
||||
def test_read_results_returns_epochs(mocker, hyperopt, testdatadir, caplog) -> None:
|
||||
epochs = create_results(mocker, hyperopt, testdatadir)
|
||||
mock_load = mocker.patch('freqtrade.optimize.hyperopt.load', return_value=epochs)
|
||||
results_file = testdatadir / 'optimize' / 'ut_results.pickle'
|
||||
hyperopt_epochs = hyperopt._read_results(results_file)
|
||||
assert log_has(f"Reading epochs from '{results_file}'", caplog)
|
||||
assert hyperopt_epochs == epochs
|
||||
mock_load.assert_called_once()
|
||||
def test_load_previous_results(testdatadir, caplog) -> None:
|
||||
|
||||
results_file = testdatadir / 'hyperopt_results_SampleStrategy.pickle'
|
||||
|
||||
hyperopt_epochs = HyperoptTools.load_previous_results(results_file)
|
||||
|
||||
assert len(hyperopt_epochs) == 5
|
||||
assert log_has_re(r"Reading pickled epochs from .*", caplog)
|
||||
|
||||
caplog.clear()
|
||||
|
||||
# Modern version
|
||||
results_file = testdatadir / 'strategy_SampleStrategy.fthypt'
|
||||
|
||||
hyperopt_epochs = HyperoptTools.load_previous_results(results_file)
|
||||
|
||||
assert len(hyperopt_epochs) == 5
|
||||
assert log_has_re(r"Reading epochs from .*", caplog)
|
||||
|
||||
|
||||
def test_load_previous_results(mocker, hyperopt, testdatadir, caplog) -> None:
|
||||
epochs = create_results(mocker, hyperopt, testdatadir)
|
||||
mock_load = mocker.patch('freqtrade.optimize.hyperopt.load', return_value=epochs)
|
||||
mocker.patch.object(Path, 'is_file', MagicMock(return_value=True))
|
||||
statmock = MagicMock()
|
||||
statmock.st_size = 5
|
||||
# mocker.patch.object(Path, 'stat', MagicMock(return_value=statmock))
|
||||
|
||||
results_file = testdatadir / 'optimize' / 'ut_results.pickle'
|
||||
|
||||
hyperopt_epochs = hyperopt.load_previous_results(results_file)
|
||||
|
||||
assert hyperopt_epochs == epochs
|
||||
mock_load.assert_called_once()
|
||||
|
||||
del epochs[0]['is_best']
|
||||
mock_load = mocker.patch('freqtrade.optimize.hyperopt.load', return_value=epochs)
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
hyperopt.load_previous_results(results_file)
|
||||
def test_load_previous_results2(mocker, testdatadir, caplog) -> None:
|
||||
mocker.patch('freqtrade.optimize.hyperopt_tools.HyperoptTools._read_results_pickle',
|
||||
return_value=[{'asdf': '222'}])
|
||||
results_file = testdatadir / 'hyperopt_results_SampleStrategy.pickle'
|
||||
with pytest.raises(OperationalException, match=r"The file .* incompatible.*"):
|
||||
HyperoptTools.load_previous_results(results_file)
|
||||
|
||||
|
||||
def test_roi_table_generation(hyperopt) -> None:
|
||||
@@ -371,7 +363,8 @@ def test_roi_table_generation(hyperopt) -> None:
|
||||
|
||||
|
||||
def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
|
||||
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
|
||||
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
|
||||
dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
|
||||
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
|
||||
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
|
||||
@@ -410,9 +403,9 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
|
||||
assert dumper.called
|
||||
# Should be called twice, once for historical candle data, once to save evaluations
|
||||
assert dumper.call_count == 2
|
||||
# Should be called for historical candle data
|
||||
assert dumper.call_count == 1
|
||||
assert dumper2.call_count == 1
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
|
||||
assert hasattr(hyperopt, "max_open_trades")
|
||||
@@ -420,18 +413,42 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
|
||||
assert hasattr(hyperopt, "position_stacking")
|
||||
|
||||
|
||||
def test_format_results(hyperopt):
|
||||
# Test with BTC as stake_currency
|
||||
trades = [
|
||||
('ETH/BTC', 2, 2, 123),
|
||||
('LTC/BTC', 1, 1, 123),
|
||||
('XPR/BTC', -1, -2, -246)
|
||||
]
|
||||
labels = ['currency', 'profit_ratio', 'profit_abs', 'trade_duration']
|
||||
df = pd.DataFrame.from_records(trades, columns=labels)
|
||||
results_metrics = hyperopt._calculate_results_metrics(df)
|
||||
results_explanation = hyperopt._format_results_explanation_string(results_metrics)
|
||||
total_profit = results_metrics['total_profit']
|
||||
def test_hyperopt_format_results(hyperopt):
|
||||
|
||||
bt_result = {
|
||||
'results': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC",
|
||||
"UNITTEST/BTC", "UNITTEST/BTC"],
|
||||
"profit_ratio": [0.003312, 0.010801, 0.013803, 0.002780],
|
||||
"profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
|
||||
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
|
||||
Arrow(2017, 11, 14, 21, 36, 00).datetime,
|
||||
Arrow(2017, 11, 14, 22, 12, 00).datetime,
|
||||
Arrow(2017, 11, 14, 22, 44, 00).datetime],
|
||||
"close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime,
|
||||
Arrow(2017, 11, 14, 22, 10, 00).datetime,
|
||||
Arrow(2017, 11, 14, 22, 43, 00).datetime,
|
||||
Arrow(2017, 11, 14, 22, 58, 00).datetime],
|
||||
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
|
||||
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
|
||||
"trade_duration": [123, 34, 31, 14],
|
||||
"is_open": [False, False, False, True],
|
||||
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
||||
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
|
||||
SellType.ROI, SellType.FORCE_SELL]
|
||||
}),
|
||||
'config': hyperopt.config,
|
||||
'locks': [],
|
||||
'final_balance': 0.02,
|
||||
'rejected_signals': 2,
|
||||
'backtest_start_time': 1619718665,
|
||||
'backtest_end_time': 1619718665,
|
||||
}
|
||||
results_metrics = generate_strategy_stats({'XRP/BTC': None}, '', bt_result,
|
||||
Arrow(2017, 11, 14, 19, 32, 00),
|
||||
Arrow(2017, 12, 14, 19, 32, 00), market_change=0)
|
||||
|
||||
results_explanation = HyperoptTools.format_results_explanation_string(results_metrics, 'BTC')
|
||||
total_profit = results_metrics['profit_total_abs']
|
||||
|
||||
results = {
|
||||
'loss': 0.0,
|
||||
@@ -444,22 +461,10 @@ def test_format_results(hyperopt):
|
||||
'is_initial_point': True,
|
||||
}
|
||||
|
||||
result = hyperopt._format_explanation_string(results, 1)
|
||||
assert result.find(' 66.67%')
|
||||
assert result.find('Total profit 1.00000000 BTC')
|
||||
assert result.find('2.0000Σ %')
|
||||
|
||||
# Test with EUR as stake_currency
|
||||
trades = [
|
||||
('ETH/EUR', 2, 2, 123),
|
||||
('LTC/EUR', 1, 1, 123),
|
||||
('XPR/EUR', -1, -2, -246)
|
||||
]
|
||||
df = pd.DataFrame.from_records(trades, columns=labels)
|
||||
results_metrics = hyperopt._calculate_results_metrics(df)
|
||||
results['total_profit'] = results_metrics['total_profit']
|
||||
result = hyperopt._format_explanation_string(results, 1)
|
||||
assert result.find('Total profit 1.00000000 EUR')
|
||||
result = HyperoptTools._format_explanation_string(results, 1)
|
||||
assert ' 0.71%' in result
|
||||
assert 'Total profit 0.00003100 BTC' in result
|
||||
assert '0:50:00 min' in result
|
||||
|
||||
|
||||
@pytest.mark.parametrize("spaces, expected_results", [
|
||||
@@ -490,10 +495,10 @@ def test_format_results(hyperopt):
|
||||
(['default', 'buy'],
|
||||
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False}),
|
||||
])
|
||||
def test_has_space(hyperopt, spaces, expected_results):
|
||||
def test_has_space(hyperopt_conf, spaces, expected_results):
|
||||
for s in ['buy', 'sell', 'roi', 'stoploss', 'trailing']:
|
||||
hyperopt.config.update({'spaces': spaces})
|
||||
assert hyperopt.has_space(s) == expected_results[s]
|
||||
hyperopt_conf.update({'spaces': spaces})
|
||||
assert HyperoptTools.has_space(hyperopt_conf, s) == expected_results[s]
|
||||
|
||||
|
||||
def test_populate_indicators(hyperopt, testdatadir) -> None:
|
||||
@@ -564,22 +569,39 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
|
||||
'hyperopt_min_trades': 1,
|
||||
})
|
||||
|
||||
trades = [
|
||||
('TRX/BTC', 0.023117, 0.000233, 100)
|
||||
]
|
||||
labels = ['currency', 'profit_ratio', 'profit_abs', 'trade_duration']
|
||||
backtest_result = pd.DataFrame.from_records(trades, columns=labels)
|
||||
backtest_result = {
|
||||
'results': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC",
|
||||
"UNITTEST/BTC", "UNITTEST/BTC"],
|
||||
"profit_ratio": [0.003312, 0.010801, 0.013803, 0.002780],
|
||||
"profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
|
||||
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
|
||||
Arrow(2017, 11, 14, 21, 36, 00).datetime,
|
||||
Arrow(2017, 11, 14, 22, 12, 00).datetime,
|
||||
Arrow(2017, 11, 14, 22, 44, 00).datetime],
|
||||
"close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime,
|
||||
Arrow(2017, 11, 14, 22, 10, 00).datetime,
|
||||
Arrow(2017, 11, 14, 22, 43, 00).datetime,
|
||||
Arrow(2017, 11, 14, 22, 58, 00).datetime],
|
||||
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
|
||||
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
|
||||
"trade_duration": [123, 34, 31, 14],
|
||||
"is_open": [False, False, False, True],
|
||||
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
||||
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
|
||||
SellType.ROI, SellType.FORCE_SELL]
|
||||
}),
|
||||
'config': hyperopt_conf,
|
||||
'locks': [],
|
||||
'rejected_signals': 20,
|
||||
'final_balance': 1000,
|
||||
}
|
||||
|
||||
mocker.patch(
|
||||
'freqtrade.optimize.hyperopt.Backtesting.backtest',
|
||||
MagicMock(return_value=backtest_result)
|
||||
)
|
||||
mocker.patch(
|
||||
'freqtrade.optimize.hyperopt.get_timerange',
|
||||
MagicMock(return_value=(Arrow(2017, 12, 10), Arrow(2017, 12, 13)))
|
||||
)
|
||||
mocker.patch('freqtrade.optimize.hyperopt.Backtesting.backtest', return_value=backtest_result)
|
||||
mocker.patch('freqtrade.optimize.hyperopt.get_timerange',
|
||||
return_value=(Arrow(2017, 12, 10), Arrow(2017, 12, 13)))
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.optimize.hyperopt.load', MagicMock())
|
||||
mocker.patch.object(Path, 'open')
|
||||
mocker.patch('freqtrade.optimize.hyperopt.load', return_value={'XRP/BTC': None})
|
||||
|
||||
optimizer_param = {
|
||||
'adx-value': 0,
|
||||
@@ -613,12 +635,12 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
|
||||
'trailing_only_offset_is_reached': False,
|
||||
}
|
||||
response_expected = {
|
||||
'loss': 1.9840569076926293,
|
||||
'results_explanation': (' 1 trades. 1/0/0 Wins/Draws/Losses. '
|
||||
'Avg profit 2.31%. Median profit 2.31%. Total profit '
|
||||
'0.00023300 BTC ( 2.31\N{GREEK CAPITAL LETTER SIGMA}%). '
|
||||
'Avg duration 100.0 min.'
|
||||
).encode(locale.getpreferredencoding(), 'replace').decode('utf-8'),
|
||||
'loss': 1.9147239021396234,
|
||||
'results_explanation': (' 4 trades. 4/0/0 Wins/Draws/Losses. '
|
||||
'Avg profit 0.77%. Median profit 0.71%. Total profit '
|
||||
'0.00003100 BTC ( 0.00%). '
|
||||
'Avg duration 0:50:00 min.'
|
||||
),
|
||||
'params_details': {'buy': {'adx-enabled': False,
|
||||
'adx-value': 0,
|
||||
'fastd-enabled': True,
|
||||
@@ -628,10 +650,10 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
|
||||
'rsi-enabled': False,
|
||||
'rsi-value': 0,
|
||||
'trigger': 'macd_cross_signal'},
|
||||
'roi': {0: 0.12000000000000001,
|
||||
20.0: 0.02,
|
||||
50.0: 0.01,
|
||||
110.0: 0},
|
||||
'roi': {"0": 0.12000000000000001,
|
||||
"20.0": 0.02,
|
||||
"50.0": 0.01,
|
||||
"110.0": 0},
|
||||
'sell': {'sell-adx-enabled': False,
|
||||
'sell-adx-value': 0,
|
||||
'sell-fastd-enabled': True,
|
||||
@@ -647,21 +669,16 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
|
||||
'trailing_stop_positive': 0.02,
|
||||
'trailing_stop_positive_offset': 0.07}},
|
||||
'params_dict': optimizer_param,
|
||||
'results_metrics': {'avg_profit': 2.3117,
|
||||
'draws': 0,
|
||||
'duration': 100.0,
|
||||
'losses': 0,
|
||||
'winsdrawslosses': ' 1 0 0',
|
||||
'median_profit': 2.3117,
|
||||
'profit': 2.3117,
|
||||
'total_profit': 0.000233,
|
||||
'trade_count': 1,
|
||||
'wins': 1},
|
||||
'total_profit': 0.00023300
|
||||
'params_not_optimized': {'buy': {}, 'sell': {}},
|
||||
'results_metrics': ANY,
|
||||
'total_profit': 3.1e-08
|
||||
}
|
||||
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
hyperopt.dimensions = hyperopt.hyperopt_space()
|
||||
hyperopt.min_date = Arrow(2017, 12, 10)
|
||||
hyperopt.max_date = Arrow(2017, 12, 13)
|
||||
hyperopt.init_spaces()
|
||||
hyperopt.dimensions = hyperopt.dimensions
|
||||
generate_optimizer_value = hyperopt.generate_optimizer(list(optimizer_param.values()))
|
||||
assert generate_optimizer_value == response_expected
|
||||
|
||||
@@ -678,7 +695,8 @@ def test_clean_hyperopt(mocker, hyperopt_conf, caplog):
|
||||
|
||||
|
||||
def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
|
||||
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
|
||||
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
|
||||
dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
|
||||
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
|
||||
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
|
||||
@@ -729,13 +747,14 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
|
||||
':{},"stoploss":null,"trailing_stop":null}'
|
||||
)
|
||||
assert result_str in out # noqa: E501
|
||||
assert dumper.called
|
||||
# Should be called twice, once for historical candle data, once to save evaluations
|
||||
assert dumper.call_count == 2
|
||||
# Should be called for historical candle data
|
||||
assert dumper.call_count == 1
|
||||
assert dumper2.call_count == 1
|
||||
|
||||
|
||||
def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
|
||||
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
|
||||
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
|
||||
dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
|
||||
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
|
||||
MagicMock(return_value=(MagicMock(), None)))
|
||||
@@ -777,13 +796,14 @@ def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
assert '{"params":{"mfi-value":null,"sell-mfi-value":null},"minimal_roi":{},"stoploss":null}' in out # noqa: E501
|
||||
assert dumper.called
|
||||
# Should be called twice, once for historical candle data, once to save evaluations
|
||||
assert dumper.call_count == 2
|
||||
# Should be called for historical candle data
|
||||
assert dumper.call_count == 1
|
||||
assert dumper2.call_count == 1
|
||||
|
||||
|
||||
def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
|
||||
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
|
||||
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
|
||||
dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
|
||||
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
|
||||
MagicMock(return_value=(MagicMock(), None)))
|
||||
@@ -824,13 +844,14 @@ def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
assert '{"minimal_roi":{},"stoploss":null}' in out
|
||||
assert dumper.called
|
||||
# Should be called twice, once for historical candle data, once to save evaluations
|
||||
assert dumper.call_count == 2
|
||||
|
||||
assert dumper.call_count == 1
|
||||
assert dumper2.call_count == 1
|
||||
|
||||
|
||||
def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
|
||||
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
|
||||
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
|
||||
dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
|
||||
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
|
||||
MagicMock(return_value=(MagicMock(), None)))
|
||||
@@ -872,9 +893,9 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
|
||||
assert dumper.called
|
||||
# Should be called twice, once for historical candle data, once to save evaluations
|
||||
assert dumper.call_count == 2
|
||||
assert dumper.call_count == 1
|
||||
assert dumper2.call_count == 1
|
||||
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
|
||||
assert hasattr(hyperopt, "max_open_trades")
|
||||
@@ -910,7 +931,8 @@ def test_simplified_interface_all_failed(mocker, hyperopt_conf) -> None:
|
||||
|
||||
|
||||
def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
|
||||
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
|
||||
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
|
||||
dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
|
||||
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
|
||||
MagicMock(return_value=(MagicMock(), None)))
|
||||
@@ -953,8 +975,8 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
|
||||
out, err = capsys.readouterr()
|
||||
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
|
||||
assert dumper.called
|
||||
# Should be called twice, once for historical candle data, once to save evaluations
|
||||
assert dumper.call_count == 2
|
||||
assert dumper.call_count == 1
|
||||
assert dumper2.call_count == 1
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
|
||||
assert hasattr(hyperopt, "max_open_trades")
|
||||
@@ -963,7 +985,8 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
|
||||
|
||||
|
||||
def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
|
||||
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
|
||||
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
|
||||
dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
|
||||
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
|
||||
MagicMock(return_value=(MagicMock(), None)))
|
||||
@@ -1006,8 +1029,8 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
|
||||
out, err = capsys.readouterr()
|
||||
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
|
||||
assert dumper.called
|
||||
# Should be called twice, once for historical candle data, once to save evaluations
|
||||
assert dumper.call_count == 2
|
||||
assert dumper.call_count == 1
|
||||
assert dumper2.call_count == 1
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
|
||||
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
|
||||
assert hasattr(hyperopt, "max_open_trades")
|
||||
@@ -1045,7 +1068,7 @@ def test_simplified_interface_failed(mocker, hyperopt_conf, method, space) -> No
|
||||
hyperopt.start()
|
||||
|
||||
|
||||
def test_print_epoch_details(capsys):
|
||||
def test_show_epoch_details(capsys):
|
||||
test_result = {
|
||||
'params_details': {
|
||||
'trailing': {
|
||||
@@ -1067,7 +1090,7 @@ def test_print_epoch_details(capsys):
|
||||
'is_best': True
|
||||
}
|
||||
|
||||
Hyperopt.print_epoch_details(test_result, 5, False, no_header=True)
|
||||
HyperoptTools.show_epoch_details(test_result, 5, False, no_header=True)
|
||||
captured = capsys.readouterr()
|
||||
assert '# Trailing stop:' in captured.out
|
||||
# re.match(r"Pairs for .*", captured.out)
|
||||
@@ -1079,3 +1102,58 @@ def test_print_epoch_details(capsys):
|
||||
assert '# ROI table:' in captured.out
|
||||
assert re.search(r'^\s+minimal_roi = \{$', captured.out, re.MULTILINE)
|
||||
assert re.search(r'^\s+\"90\"\:\s0.14,\s*$', captured.out, re.MULTILINE)
|
||||
|
||||
|
||||
def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
(Path(tmpdir) / 'hyperopt_results').mkdir(parents=True)
|
||||
# No hyperopt needed
|
||||
del hyperopt_conf['hyperopt']
|
||||
hyperopt_conf.update({
|
||||
'strategy': 'HyperoptableStrategy',
|
||||
'user_data_dir': Path(tmpdir),
|
||||
})
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
||||
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
||||
buy_rsi_range = hyperopt.backtesting.strategy.buy_rsi.range
|
||||
assert isinstance(buy_rsi_range, range)
|
||||
# Range from 0 - 50 (inclusive)
|
||||
assert len(list(buy_rsi_range)) == 51
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
|
||||
def test_SKDecimal():
|
||||
space = SKDecimal(1, 2, decimals=2)
|
||||
assert 1.5 in space
|
||||
assert 2.5 not in space
|
||||
assert space.low == 100
|
||||
assert space.high == 200
|
||||
|
||||
assert space.inverse_transform([200]) == [2.0]
|
||||
assert space.inverse_transform([100]) == [1.0]
|
||||
assert space.inverse_transform([150, 160]) == [1.5, 1.6]
|
||||
|
||||
assert space.transform([1.5]) == [150]
|
||||
assert space.transform([2.0]) == [200]
|
||||
assert space.transform([1.0]) == [100]
|
||||
assert space.transform([1.5, 1.6]) == [150, 160]
|
||||
|
||||
|
||||
def test___pprint():
|
||||
params = {'buy_std': 1.2, 'buy_rsi': 31, 'buy_enable': True, 'buy_what': 'asdf'}
|
||||
non_params = {'buy_notoptimied': 55}
|
||||
|
||||
x = HyperoptTools._pprint(params, non_params)
|
||||
assert x == """{
|
||||
"buy_std": 1.2,
|
||||
"buy_rsi": 31,
|
||||
"buy_enable": True,
|
||||
"buy_what": "asdf",
|
||||
"buy_notoptimied": 55, # value loaded from strategy
|
||||
}"""
|
||||
|
@@ -149,9 +149,9 @@ def test_sortino_loss_daily_prefers_higher_profits(default_conf, hyperopt_result
|
||||
|
||||
def test_onlyprofit_loss_prefers_higher_profits(default_conf, hyperopt_results) -> None:
|
||||
results_over = hyperopt_results.copy()
|
||||
results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2
|
||||
results_over['profit_abs'] = hyperopt_results['profit_abs'] * 2
|
||||
results_under = hyperopt_results.copy()
|
||||
results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2
|
||||
results_under['profit_abs'] = hyperopt_results['profit_abs'] / 2
|
||||
|
||||
default_conf.update({'hyperopt_loss': 'OnlyProfitHyperOptLoss'})
|
||||
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import datetime
|
||||
import re
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
@@ -7,18 +8,19 @@ import pytest
|
||||
from arrow import Arrow
|
||||
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.constants import LAST_BT_RESULT_FN
|
||||
from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN
|
||||
from freqtrade.data import history
|
||||
from freqtrade.data.btanalysis import get_latest_backtest_filename, load_backtest_data
|
||||
from freqtrade.edge import PairInfo
|
||||
from freqtrade.enums import SellType
|
||||
from freqtrade.optimize.optimize_reports import (generate_backtest_stats, generate_daily_stats,
|
||||
generate_edge_table, generate_pair_metrics,
|
||||
generate_sell_reason_stats,
|
||||
generate_strategy_metrics, store_backtest_stats,
|
||||
generate_strategy_comparison,
|
||||
generate_trading_stats, store_backtest_stats,
|
||||
text_table_bt_results, text_table_sell_reason,
|
||||
text_table_strategy)
|
||||
from freqtrade.resolvers.strategy_resolver import StrategyResolver
|
||||
from freqtrade.strategy.interface import SellType
|
||||
from tests.data.test_history import _backup_file, _clean_test_file
|
||||
|
||||
|
||||
@@ -26,33 +28,30 @@ def test_text_table_bt_results():
|
||||
|
||||
results = pd.DataFrame(
|
||||
{
|
||||
'pair': ['ETH/BTC', 'ETH/BTC'],
|
||||
'profit_ratio': [0.1, 0.2],
|
||||
'profit_abs': [0.2, 0.4],
|
||||
'trade_duration': [10, 30],
|
||||
'wins': [2, 0],
|
||||
'draws': [0, 0],
|
||||
'losses': [0, 0]
|
||||
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
||||
'profit_ratio': [0.1, 0.2, -0.05],
|
||||
'profit_abs': [0.2, 0.4, -0.1],
|
||||
'trade_duration': [10, 30, 20],
|
||||
}
|
||||
)
|
||||
|
||||
result_str = (
|
||||
'| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |'
|
||||
' Tot Profit % | Avg Duration | Wins | Draws | Losses |\n'
|
||||
'|---------+--------+----------------+----------------+------------------+'
|
||||
'----------------+----------------+--------+---------+----------|\n'
|
||||
'| ETH/BTC | 2 | 15.00 | 30.00 | 0.60000000 |'
|
||||
' 15.00 | 0:20:00 | 2 | 0 | 0 |\n'
|
||||
'| TOTAL | 2 | 15.00 | 30.00 | 0.60000000 |'
|
||||
' 15.00 | 0:20:00 | 2 | 0 | 0 |'
|
||||
'| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % |'
|
||||
' Avg Duration | Win Draw Loss Win% |\n'
|
||||
'|---------+--------+----------------+----------------+------------------+----------------+'
|
||||
'----------------+-------------------------|\n'
|
||||
'| ETH/BTC | 3 | 8.33 | 25.00 | 0.50000000 | 12.50 |'
|
||||
' 0:20:00 | 2 0 1 66.7 |\n'
|
||||
'| TOTAL | 3 | 8.33 | 25.00 | 0.50000000 | 12.50 |'
|
||||
' 0:20:00 | 2 0 1 66.7 |'
|
||||
)
|
||||
|
||||
pair_results = generate_pair_metrics(data={'ETH/BTC': {}}, stake_currency='BTC',
|
||||
max_open_trades=2, results=results)
|
||||
starting_balance=4, results=results)
|
||||
assert text_table_bt_results(pair_results, stake_currency='BTC') == result_str
|
||||
|
||||
|
||||
def test_generate_backtest_stats(default_conf, testdatadir):
|
||||
def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
@@ -73,11 +72,14 @@ def test_generate_backtest_stats(default_conf, testdatadir):
|
||||
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
|
||||
"trade_duration": [123, 34, 31, 14],
|
||||
"is_open": [False, False, False, True],
|
||||
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
||||
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
|
||||
SellType.ROI, SellType.FORCE_SELL]
|
||||
}),
|
||||
'config': default_conf,
|
||||
'locks': [],
|
||||
'final_balance': 1000.02,
|
||||
'rejected_signals': 20,
|
||||
'backtest_start_time': Arrow.utcnow().int_timestamp,
|
||||
'backtest_end_time': Arrow.utcnow().int_timestamp,
|
||||
}
|
||||
@@ -94,12 +96,13 @@ def test_generate_backtest_stats(default_conf, testdatadir):
|
||||
assert 'DefStrat' in stats['strategy']
|
||||
assert 'strategy_comparison' in stats
|
||||
strat_stats = stats['strategy']['DefStrat']
|
||||
assert strat_stats['backtest_start'] == min_date.datetime
|
||||
assert strat_stats['backtest_end'] == max_date.datetime
|
||||
assert strat_stats['backtest_start'] == min_date.strftime(DATETIME_PRINT_FORMAT)
|
||||
assert strat_stats['backtest_end'] == max_date.strftime(DATETIME_PRINT_FORMAT)
|
||||
assert strat_stats['total_trades'] == len(results['DefStrat']['results'])
|
||||
# Above sample had no loosing trade
|
||||
assert strat_stats['max_drawdown'] == 0.0
|
||||
|
||||
# Retry with losing trade
|
||||
results = {'DefStrat': {
|
||||
'results': pd.DataFrame(
|
||||
{"pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"],
|
||||
@@ -116,23 +119,37 @@ def test_generate_backtest_stats(default_conf, testdatadir):
|
||||
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
|
||||
"close_rate": [0.002546, 0.003014, 0.0032903, 0.003217],
|
||||
"trade_duration": [123, 34, 31, 14],
|
||||
"open_at_end": [False, False, False, True],
|
||||
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
|
||||
SellType.ROI, SellType.FORCE_SELL]
|
||||
"is_open": [False, False, False, True],
|
||||
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
||||
"sell_reason": [SellType.ROI, SellType.ROI,
|
||||
SellType.STOP_LOSS, SellType.FORCE_SELL]
|
||||
}),
|
||||
'config': default_conf}
|
||||
'config': default_conf,
|
||||
'locks': [],
|
||||
'final_balance': 1000.02,
|
||||
'rejected_signals': 20,
|
||||
'backtest_start_time': Arrow.utcnow().int_timestamp,
|
||||
'backtest_end_time': Arrow.utcnow().int_timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
assert strat_stats['max_drawdown'] == 0.0
|
||||
assert strat_stats['drawdown_start'] == datetime(1970, 1, 1, tzinfo=timezone.utc)
|
||||
assert strat_stats['drawdown_end'] == datetime(1970, 1, 1, tzinfo=timezone.utc)
|
||||
assert strat_stats['drawdown_end_ts'] == 0
|
||||
assert strat_stats['drawdown_start_ts'] == 0
|
||||
stats = generate_backtest_stats(btdata, results, min_date, max_date)
|
||||
assert isinstance(stats, dict)
|
||||
assert 'strategy' in stats
|
||||
assert 'DefStrat' in stats['strategy']
|
||||
assert 'strategy_comparison' in stats
|
||||
strat_stats = stats['strategy']['DefStrat']
|
||||
|
||||
assert strat_stats['max_drawdown'] == 0.013803
|
||||
assert strat_stats['drawdown_start'] == '2017-11-14 22:10:00'
|
||||
assert strat_stats['drawdown_end'] == '2017-11-14 22:43:00'
|
||||
assert strat_stats['drawdown_end_ts'] == 1510699380000
|
||||
assert strat_stats['drawdown_start_ts'] == 1510697400000
|
||||
assert strat_stats['pairlist'] == ['UNITTEST/BTC']
|
||||
|
||||
# Test storing stats
|
||||
filename = Path(testdatadir / 'btresult.json')
|
||||
filename_last = Path(testdatadir / LAST_BT_RESULT_FN)
|
||||
filename = Path(tmpdir / 'btresult.json')
|
||||
filename_last = Path(tmpdir / LAST_BT_RESULT_FN)
|
||||
_backup_file(filename_last, copy_file=True)
|
||||
assert not filename.is_file()
|
||||
|
||||
@@ -142,7 +159,7 @@ def test_generate_backtest_stats(default_conf, testdatadir):
|
||||
last_fn = get_latest_backtest_filename(filename_last.parent)
|
||||
assert re.match(r"btresult-.*\.json", last_fn)
|
||||
|
||||
filename1 = (testdatadir / last_fn)
|
||||
filename1 = Path(tmpdir / last_fn)
|
||||
assert filename1.is_file()
|
||||
content = filename1.read_text()
|
||||
assert 'max_drawdown' in content
|
||||
@@ -189,7 +206,7 @@ def test_generate_pair_metrics():
|
||||
)
|
||||
|
||||
pair_results = generate_pair_metrics(data={'ETH/BTC': {}}, stake_currency='BTC',
|
||||
max_open_trades=2, results=results)
|
||||
starting_balance=2, results=results)
|
||||
assert isinstance(pair_results, list)
|
||||
assert len(pair_results) == 2
|
||||
assert pair_results[-1]['key'] == 'TOTAL'
|
||||
@@ -210,8 +227,6 @@ def test_generate_daily_stats(testdatadir):
|
||||
assert res['winning_days'] == 14
|
||||
assert res['draw_days'] == 4
|
||||
assert res['losing_days'] == 3
|
||||
assert res['winner_holding_avg'] == timedelta(seconds=1440)
|
||||
assert res['loser_holding_avg'] == timedelta(days=1, seconds=21420)
|
||||
|
||||
# Select empty dataframe!
|
||||
res = generate_daily_stats(bt_data.loc[bt_data['open_date'] == '2000-01-01', :])
|
||||
@@ -222,6 +237,23 @@ def test_generate_daily_stats(testdatadir):
|
||||
assert res['losing_days'] == 0
|
||||
|
||||
|
||||
def test_generate_trading_stats(testdatadir):
|
||||
filename = testdatadir / "backtest-result_new.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
res = generate_trading_stats(bt_data)
|
||||
assert isinstance(res, dict)
|
||||
assert res['winner_holding_avg'] == timedelta(seconds=1440)
|
||||
assert res['loser_holding_avg'] == timedelta(days=1, seconds=21420)
|
||||
assert 'wins' in res
|
||||
assert 'losses' in res
|
||||
assert 'draws' in res
|
||||
|
||||
# Select empty dataframe!
|
||||
res = generate_trading_stats(bt_data.loc[bt_data['open_date'] == '2000-01-01', :])
|
||||
assert res['wins'] == 0
|
||||
assert res['losses'] == 0
|
||||
|
||||
|
||||
def test_text_table_sell_reason():
|
||||
|
||||
results = pd.DataFrame(
|
||||
@@ -238,14 +270,14 @@ def test_text_table_sell_reason():
|
||||
)
|
||||
|
||||
result_str = (
|
||||
'| Sell Reason | Sells | Wins | Draws | Losses |'
|
||||
' Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % |\n'
|
||||
'|---------------+---------+--------+---------+----------+'
|
||||
'----------------+----------------+------------------+----------------|\n'
|
||||
'| roi | 2 | 2 | 0 | 0 |'
|
||||
' 15 | 30 | 0.6 | 15 |\n'
|
||||
'| stop_loss | 1 | 0 | 0 | 1 |'
|
||||
' -10 | -10 | -0.2 | -5 |'
|
||||
'| Sell Reason | Sells | Win Draws Loss Win% | Avg Profit % | Cum Profit % |'
|
||||
' Tot Profit BTC | Tot Profit % |\n'
|
||||
'|---------------+---------+--------------------------+----------------+----------------+'
|
||||
'------------------+----------------|\n'
|
||||
'| roi | 2 | 2 0 0 100 | 15 | 30 |'
|
||||
' 0.6 | 15 |\n'
|
||||
'| stop_loss | 1 | 0 0 1 0 | -10 | -10 |'
|
||||
' -0.2 | -5 |'
|
||||
)
|
||||
|
||||
sell_reason_stats = generate_sell_reason_stats(max_open_trades=2,
|
||||
@@ -265,7 +297,7 @@ def test_generate_sell_reason_stats():
|
||||
'wins': [2, 0, 0],
|
||||
'draws': [0, 0, 0],
|
||||
'losses': [0, 0, 1],
|
||||
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
||||
'sell_reason': [SellType.ROI.value, SellType.ROI.value, SellType.STOP_LOSS.value]
|
||||
}
|
||||
)
|
||||
|
||||
@@ -291,10 +323,14 @@ def test_generate_sell_reason_stats():
|
||||
|
||||
def test_text_table_strategy(default_conf):
|
||||
default_conf['max_open_trades'] = 2
|
||||
default_conf['dry_run_wallet'] = 3
|
||||
results = {}
|
||||
date = datetime.datetime(year=2020, month=1, day=1, hour=12, minute=30)
|
||||
delta = datetime.timedelta(days=1)
|
||||
results['TestStrategy1'] = {'results': pd.DataFrame(
|
||||
{
|
||||
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
||||
'close_date': [date, date + delta, date + delta * 2],
|
||||
'profit_ratio': [0.1, 0.2, 0.3],
|
||||
'profit_abs': [0.2, 0.4, 0.5],
|
||||
'trade_duration': [10, 30, 10],
|
||||
@@ -307,6 +343,7 @@ def test_text_table_strategy(default_conf):
|
||||
results['TestStrategy2'] = {'results': pd.DataFrame(
|
||||
{
|
||||
'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'],
|
||||
'close_date': [date, date + delta, date + delta * 2],
|
||||
'profit_ratio': [0.4, 0.2, 0.3],
|
||||
'profit_abs': [0.4, 0.4, 0.5],
|
||||
'trade_duration': [15, 30, 15],
|
||||
@@ -318,18 +355,17 @@ def test_text_table_strategy(default_conf):
|
||||
), 'config': default_conf}
|
||||
|
||||
result_str = (
|
||||
'| Strategy | Buys | Avg Profit % | Cum Profit % | Tot'
|
||||
' Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |\n'
|
||||
'| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |'
|
||||
' Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n'
|
||||
'|---------------+--------+----------------+----------------+------------------+'
|
||||
'----------------+----------------+--------+---------+----------|\n'
|
||||
'----------------+----------------+-------------------------+-----------------------|\n'
|
||||
'| TestStrategy1 | 3 | 20.00 | 60.00 | 1.10000000 |'
|
||||
' 30.00 | 0:17:00 | 3 | 0 | 0 |\n'
|
||||
' 36.67 | 0:17:00 | 3 0 0 100 | 0.00000000 BTC 0.00% |\n'
|
||||
'| TestStrategy2 | 3 | 30.00 | 90.00 | 1.30000000 |'
|
||||
' 45.00 | 0:20:00 | 3 | 0 | 0 |'
|
||||
' 43.33 | 0:20:00 | 3 0 0 100 | 0.00000000 BTC 0.00% |'
|
||||
)
|
||||
|
||||
strategy_results = generate_strategy_metrics(all_results=results)
|
||||
|
||||
strategy_results = generate_strategy_comparison(all_results=results)
|
||||
assert text_table_strategy(strategy_results, 'BTC') == result_str
|
||||
|
||||
|
||||
|
@@ -1,15 +1,17 @@
|
||||
# pragma pylint: disable=missing-docstring,C0103,protected-access
|
||||
|
||||
import time
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.constants import AVAILABLE_PAIRLISTS
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
||||
from freqtrade.plugins.pairlistmanager import PairListManager
|
||||
from freqtrade.resolvers import PairListResolver
|
||||
from tests.conftest import get_patched_freqtradebot, log_has, log_has_re
|
||||
from tests.conftest import get_patched_exchange, get_patched_freqtradebot, log_has, log_has_re
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@@ -260,6 +262,8 @@ def test_refresh_pairlist_dynamic_2(mocker, shitcoinmarkets, tickers, whitelist_
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert whitelist == freqtrade.pairlists.whitelist
|
||||
|
||||
# Delay to allow 0 TTL cache to expire...
|
||||
time.sleep(1)
|
||||
whitelist = ['FUEL/BTC', 'ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']
|
||||
tickers_dict['FUEL/BTC']['quoteVolume'] = 10000.0
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
@@ -403,10 +407,17 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
|
||||
([{"method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume"},
|
||||
{"method": "PriceFilter", "low_price_ratio": 0.02}],
|
||||
"USDT", ['ETH/USDT', 'NANO/USDT']),
|
||||
([{"method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume"},
|
||||
{"method": "PriceFilter", "max_value": 0.000001}],
|
||||
"USDT", ['NANO/USDT']),
|
||||
([{"method": "StaticPairList"},
|
||||
{"method": "RangeStabilityFilter", "lookback_days": 10,
|
||||
"min_rate_of_change": 0.01, "refresh_period": 1440}],
|
||||
"BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']),
|
||||
([{"method": "StaticPairList"},
|
||||
{"method": "VolatilityFilter", "lookback_days": 3,
|
||||
"min_volatility": 0.002, "max_volatility": 0.004, "refresh_period": 1440}],
|
||||
"BTC", ['ETH/BTC', 'TKN/BTC'])
|
||||
])
|
||||
def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers,
|
||||
ohlcv_history, pairlists, base_currency,
|
||||
@@ -414,12 +425,15 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
|
||||
whitelist_conf['pairlists'] = pairlists
|
||||
whitelist_conf['stake_currency'] = base_currency
|
||||
|
||||
ohlcv_history_high_vola = ohlcv_history.copy()
|
||||
ohlcv_history_high_vola.loc[ohlcv_history_high_vola.index == 1, 'close'] = 0.00090
|
||||
|
||||
ohlcv_data = {
|
||||
('ETH/BTC', '1d'): ohlcv_history,
|
||||
('TKN/BTC', '1d'): ohlcv_history,
|
||||
('LTC/BTC', '1d'): ohlcv_history,
|
||||
('XRP/BTC', '1d'): ohlcv_history,
|
||||
('HOT/BTC', '1d'): ohlcv_history,
|
||||
('HOT/BTC', '1d'): ohlcv_history_high_vola,
|
||||
}
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
||||
@@ -478,6 +492,8 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
|
||||
r'because last price < .*%$', caplog) or
|
||||
log_has_re(r'^Removed .* from whitelist, '
|
||||
r'because last price > .*%$', caplog) or
|
||||
log_has_re(r'^Removed .* from whitelist, '
|
||||
r'because min value change of .*', caplog) or
|
||||
log_has_re(r"^Removed .* from whitelist, because ticker\['last'\] "
|
||||
r"is empty.*", caplog))
|
||||
if pairlist['method'] == 'VolumePairList':
|
||||
@@ -487,6 +503,8 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
|
||||
assert log_has(logmsg, caplog)
|
||||
else:
|
||||
assert not log_has(logmsg, caplog)
|
||||
if pairlist["method"] == 'VolatilityFilter':
|
||||
assert log_has_re(r'^Removed .* from whitelist, because volatility.*$', caplog)
|
||||
|
||||
|
||||
def test_PrecisionFilter_error(mocker, whitelist_conf) -> None:
|
||||
@@ -500,6 +518,18 @@ def test_PrecisionFilter_error(mocker, whitelist_conf) -> None:
|
||||
PairListManager(MagicMock, whitelist_conf)
|
||||
|
||||
|
||||
def test_PerformanceFilter_error(mocker, whitelist_conf, caplog) -> None:
|
||||
whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PerformanceFilter"}]
|
||||
if hasattr(Trade, 'query'):
|
||||
del Trade.query
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
||||
exchange = get_patched_exchange(mocker, whitelist_conf)
|
||||
pm = PairListManager(exchange, whitelist_conf)
|
||||
pm.refresh_pairlist()
|
||||
|
||||
assert log_has("PerformanceFilter is not available in this mode.", caplog)
|
||||
|
||||
|
||||
def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None:
|
||||
default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}]
|
||||
|
||||
@@ -595,17 +625,14 @@ def test_volumepairlist_caching(mocker, markets, whitelist_conf, tickers):
|
||||
get_tickers=tickers
|
||||
)
|
||||
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
|
||||
assert freqtrade.pairlists._pairlist_handlers[0]._last_refresh == 0
|
||||
assert len(freqtrade.pairlists._pairlist_handlers[0]._pair_cache) == 0
|
||||
assert tickers.call_count == 0
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert tickers.call_count == 1
|
||||
|
||||
assert freqtrade.pairlists._pairlist_handlers[0]._last_refresh != 0
|
||||
lrf = freqtrade.pairlists._pairlist_handlers[0]._last_refresh
|
||||
assert len(freqtrade.pairlists._pairlist_handlers[0]._pair_cache) == 1
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert tickers.call_count == 1
|
||||
# Time should not be updated.
|
||||
assert freqtrade.pairlists._pairlist_handlers[0]._last_refresh == lrf
|
||||
|
||||
|
||||
def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tickers):
|
||||
@@ -778,6 +805,10 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo
|
||||
"[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.00002000.'}]",
|
||||
None
|
||||
),
|
||||
({"method": "PriceFilter", "max_value": 0.00002000},
|
||||
"[{'PriceFilter': 'PriceFilter - Filtering pairs priced Value above 0.00002000.'}]",
|
||||
None
|
||||
),
|
||||
({"method": "PriceFilter"},
|
||||
"[{'PriceFilter': 'PriceFilter - No price filters configured.'}]",
|
||||
None
|
||||
@@ -794,6 +825,10 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo
|
||||
None,
|
||||
"PriceFilter requires max_price to be >= 0"
|
||||
), # OperationalException expected
|
||||
({"method": "PriceFilter", "max_value": -1.00010000},
|
||||
None,
|
||||
"PriceFilter requires max_value to be >= 0"
|
||||
), # OperationalException expected
|
||||
({"method": "RangeStabilityFilter", "lookback_days": 10, "min_rate_of_change": 0.01},
|
||||
"[{'RangeStabilityFilter': 'RangeStabilityFilter - Filtering pairs with rate of change below "
|
||||
"0.01 over the last days.'}]",
|
||||
|
@@ -73,9 +73,13 @@ def test_PairLocks(use_db):
|
||||
assert PairLocks.is_pair_locked('XRP/USDT', lock_time + timedelta(minutes=-50))
|
||||
|
||||
if use_db:
|
||||
assert len(PairLock.query.all()) > 0
|
||||
locks = PairLocks.get_all_locks()
|
||||
locks_db = PairLock.query.all()
|
||||
assert len(locks) == len(locks_db)
|
||||
assert len(locks_db) > 0
|
||||
else:
|
||||
# Nothing was pushed to the database
|
||||
assert len(PairLocks.get_all_locks()) > 0
|
||||
assert len(PairLock.query.all()) == 0
|
||||
# Reset use-db variable
|
||||
PairLocks.reset_locks()
|
||||
|
@@ -4,9 +4,9 @@ from datetime import datetime, timedelta
|
||||
import pytest
|
||||
|
||||
from freqtrade import constants
|
||||
from freqtrade.enums import SellType
|
||||
from freqtrade.persistence import PairLocks, Trade
|
||||
from freqtrade.plugins.protectionmanager import ProtectionManager
|
||||
from freqtrade.strategy.interface import SellType
|
||||
from tests.conftest import get_patched_freqtradebot, log_has_re
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool,
|
||||
open_rate=open_rate,
|
||||
is_open=is_open,
|
||||
amount=0.01 / open_rate,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
)
|
||||
trade.recalc_open_trade_value()
|
||||
if not is_open:
|
||||
@@ -83,7 +83,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
|
||||
"method": "StoplossGuard",
|
||||
"lookback_period": 60,
|
||||
"stop_duration": 40,
|
||||
"trade_limit": 2
|
||||
"trade_limit": 3
|
||||
}]
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
message = r"Trading stopped due to .*"
|
||||
@@ -91,7 +91,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
|
||||
assert not log_has_re(message, caplog)
|
||||
caplog.clear()
|
||||
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||
min_ago_open=200, min_ago_close=30,
|
||||
))
|
||||
@@ -100,12 +100,12 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
|
||||
assert not log_has_re(message, caplog)
|
||||
caplog.clear()
|
||||
# This trade does not count, as it's closed too long ago
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'BCH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||
min_ago_open=250, min_ago_close=100,
|
||||
))
|
||||
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||
min_ago_open=240, min_ago_close=30,
|
||||
))
|
||||
@@ -114,7 +114,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
|
||||
assert not log_has_re(message, caplog)
|
||||
caplog.clear()
|
||||
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'LTC/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||
min_ago_open=180, min_ago_close=30,
|
||||
))
|
||||
@@ -136,7 +136,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
|
||||
default_conf['protections'] = [{
|
||||
"method": "StoplossGuard",
|
||||
"lookback_period": 60,
|
||||
"trade_limit": 1,
|
||||
"trade_limit": 2,
|
||||
"stop_duration": 60,
|
||||
"only_per_pair": only_per_pair
|
||||
}]
|
||||
@@ -148,7 +148,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
|
||||
assert not log_has_re(message, caplog)
|
||||
caplog.clear()
|
||||
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||
min_ago_open=200, min_ago_close=30, profit_rate=0.9,
|
||||
))
|
||||
@@ -158,12 +158,12 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
|
||||
assert not log_has_re(message, caplog)
|
||||
caplog.clear()
|
||||
# This trade does not count, as it's closed too long ago
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||
min_ago_open=250, min_ago_close=100, profit_rate=0.9,
|
||||
))
|
||||
# Trade does not count for per pair stop as it's the wrong pair.
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||
min_ago_open=240, min_ago_close=30, profit_rate=0.9,
|
||||
))
|
||||
@@ -178,7 +178,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
|
||||
caplog.clear()
|
||||
|
||||
# 2nd Trade that counts with correct pair
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||
min_ago_open=180, min_ago_close=30, profit_rate=0.9,
|
||||
))
|
||||
@@ -203,7 +203,7 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog):
|
||||
assert not log_has_re(message, caplog)
|
||||
caplog.clear()
|
||||
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||
min_ago_open=200, min_ago_close=30,
|
||||
))
|
||||
@@ -213,7 +213,7 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog):
|
||||
assert PairLocks.is_pair_locked('XRP/BTC')
|
||||
assert not PairLocks.is_global_lock()
|
||||
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'ETH/BTC', fee.return_value, False, sell_reason=SellType.ROI.value,
|
||||
min_ago_open=205, min_ago_close=35,
|
||||
))
|
||||
@@ -242,7 +242,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
|
||||
assert not log_has_re(message, caplog)
|
||||
caplog.clear()
|
||||
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||
min_ago_open=800, min_ago_close=450, profit_rate=0.9,
|
||||
))
|
||||
@@ -253,7 +253,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
|
||||
assert not PairLocks.is_pair_locked('XRP/BTC')
|
||||
assert not PairLocks.is_global_lock()
|
||||
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||
min_ago_open=200, min_ago_close=120, profit_rate=0.9,
|
||||
))
|
||||
@@ -265,14 +265,14 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
|
||||
assert not PairLocks.is_global_lock()
|
||||
|
||||
# Add positive trade
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value,
|
||||
min_ago_open=20, min_ago_close=10, profit_rate=1.15,
|
||||
))
|
||||
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
||||
assert not PairLocks.is_pair_locked('XRP/BTC')
|
||||
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||
min_ago_open=110, min_ago_close=20, profit_rate=0.8,
|
||||
))
|
||||
@@ -300,15 +300,15 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
|
||||
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
||||
caplog.clear()
|
||||
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||
min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
|
||||
))
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||
min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
|
||||
))
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'NEO/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||
min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
|
||||
))
|
||||
@@ -316,7 +316,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
|
||||
assert not freqtrade.protections.global_stop()
|
||||
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
||||
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||
min_ago_open=500, min_ago_close=400, profit_rate=0.9,
|
||||
))
|
||||
@@ -326,7 +326,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
|
||||
assert not PairLocks.is_pair_locked('XRP/BTC')
|
||||
assert not PairLocks.is_global_lock()
|
||||
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||
min_ago_open=1200, min_ago_close=1100, profit_rate=0.5,
|
||||
))
|
||||
@@ -339,7 +339,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
|
||||
assert not log_has_re(message, caplog)
|
||||
|
||||
# Winning trade ... (should not lock, does not change drawdown!)
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value,
|
||||
min_ago_open=320, min_ago_close=410, profit_rate=1.5,
|
||||
))
|
||||
@@ -349,7 +349,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
|
||||
caplog.clear()
|
||||
|
||||
# Add additional negative trade, causing a loss of > 15%
|
||||
Trade.session.add(generate_mock_trade(
|
||||
Trade.query.session.add(generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value,
|
||||
min_ago_open=20, min_ago_close=10, profit_rate=0.8,
|
||||
))
|
||||
|
@@ -1,44 +1,16 @@
|
||||
# pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors,
|
||||
# pragma pylint: disable=protected-access, C0103
|
||||
|
||||
import time
|
||||
import datetime
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from freqtrade.rpc.fiat_convert import CryptoFiat, CryptoToFiatConverter
|
||||
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
||||
from tests.conftest import log_has, log_has_re
|
||||
|
||||
|
||||
def test_pair_convertion_object():
|
||||
pair_convertion = CryptoFiat(
|
||||
crypto_symbol='btc',
|
||||
fiat_symbol='usd',
|
||||
price=12345.0
|
||||
)
|
||||
|
||||
# Check the cache duration is 6 hours
|
||||
assert pair_convertion.CACHE_DURATION == 6 * 60 * 60
|
||||
|
||||
# Check a regular usage
|
||||
assert pair_convertion.crypto_symbol == 'btc'
|
||||
assert pair_convertion.fiat_symbol == 'usd'
|
||||
assert pair_convertion.price == 12345.0
|
||||
assert pair_convertion.is_expired() is False
|
||||
|
||||
# Update the expiration time (- 2 hours) and check the behavior
|
||||
pair_convertion._expiration = time.time() - 2 * 60 * 60
|
||||
assert pair_convertion.is_expired() is True
|
||||
|
||||
# Check set price behaviour
|
||||
time_reference = time.time() + pair_convertion.CACHE_DURATION
|
||||
pair_convertion.set_price(price=30000.123)
|
||||
assert pair_convertion.is_expired() is False
|
||||
assert pair_convertion._expiration >= time_reference
|
||||
assert pair_convertion.price == 30000.123
|
||||
|
||||
|
||||
def test_fiat_convert_is_supported(mocker):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
assert fiat_convert._is_supported_fiat(fiat='USD') is True
|
||||
@@ -47,31 +19,15 @@ def test_fiat_convert_is_supported(mocker):
|
||||
assert fiat_convert._is_supported_fiat(fiat='ABC') is False
|
||||
|
||||
|
||||
def test_fiat_convert_add_pair(mocker):
|
||||
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
|
||||
pair_len = len(fiat_convert._pairs)
|
||||
assert pair_len == 0
|
||||
|
||||
fiat_convert._add_pair(crypto_symbol='btc', fiat_symbol='usd', price=12345.0)
|
||||
pair_len = len(fiat_convert._pairs)
|
||||
assert pair_len == 1
|
||||
assert fiat_convert._pairs[0].crypto_symbol == 'btc'
|
||||
assert fiat_convert._pairs[0].fiat_symbol == 'usd'
|
||||
assert fiat_convert._pairs[0].price == 12345.0
|
||||
|
||||
fiat_convert._add_pair(crypto_symbol='btc', fiat_symbol='Eur', price=13000.2)
|
||||
pair_len = len(fiat_convert._pairs)
|
||||
assert pair_len == 2
|
||||
assert fiat_convert._pairs[1].crypto_symbol == 'btc'
|
||||
assert fiat_convert._pairs[1].fiat_symbol == 'eur'
|
||||
assert fiat_convert._pairs[1].price == 13000.2
|
||||
|
||||
|
||||
def test_fiat_convert_find_price(mocker):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
|
||||
fiat_convert._cryptomap = {}
|
||||
fiat_convert._backoff = 0
|
||||
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._load_cryptomap',
|
||||
return_value=None)
|
||||
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='EUR') == 0.0
|
||||
|
||||
with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'):
|
||||
fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='ABC')
|
||||
|
||||
@@ -95,8 +51,8 @@ def test_fiat_convert_unsupported_crypto(mocker, caplog):
|
||||
|
||||
|
||||
def test_fiat_convert_get_price(mocker):
|
||||
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
|
||||
return_value=28000.0)
|
||||
find_price = mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
|
||||
return_value=28000.0)
|
||||
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
|
||||
@@ -104,26 +60,17 @@ def test_fiat_convert_get_price(mocker):
|
||||
fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='US Dollar')
|
||||
|
||||
# Check the value return by the method
|
||||
pair_len = len(fiat_convert._pairs)
|
||||
pair_len = len(fiat_convert._pair_price)
|
||||
assert pair_len == 0
|
||||
assert fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='usd') == 28000.0
|
||||
assert fiat_convert._pairs[0].crypto_symbol == 'btc'
|
||||
assert fiat_convert._pairs[0].fiat_symbol == 'usd'
|
||||
assert fiat_convert._pairs[0].price == 28000.0
|
||||
assert fiat_convert._pairs[0]._expiration != 0
|
||||
assert len(fiat_convert._pairs) == 1
|
||||
assert fiat_convert._pair_price['btc/usd'] == 28000.0
|
||||
assert len(fiat_convert._pair_price) == 1
|
||||
assert find_price.call_count == 1
|
||||
|
||||
# Verify the cached is used
|
||||
fiat_convert._pairs[0].price = 9867.543
|
||||
expiration = fiat_convert._pairs[0]._expiration
|
||||
fiat_convert._pair_price['btc/usd'] = 9867.543
|
||||
assert fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='usd') == 9867.543
|
||||
assert fiat_convert._pairs[0]._expiration == expiration
|
||||
|
||||
# Verify the cache expiration
|
||||
expiration = time.time() - 2 * 60 * 60
|
||||
fiat_convert._pairs[0]._expiration = expiration
|
||||
assert fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='usd') == 28000.0
|
||||
assert fiat_convert._pairs[0]._expiration is not expiration
|
||||
assert find_price.call_count == 1
|
||||
|
||||
|
||||
def test_fiat_convert_same_currencies(mocker):
|
||||
@@ -175,6 +122,28 @@ def test_fiat_convert_without_network(mocker):
|
||||
CryptoToFiatConverter._coingekko = cmc_temp
|
||||
|
||||
|
||||
def test_fiat_too_many_requests_response(mocker, caplog):
|
||||
# Because CryptoToFiatConverter is a Singleton we reset the listings
|
||||
req_exception = "429 Too Many Requests"
|
||||
listmock = MagicMock(return_value="{}", side_effect=RequestException(req_exception))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
|
||||
get_coins_list=listmock,
|
||||
)
|
||||
# with pytest.raises(RequestEsxception):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
fiat_convert._cryptomap = {}
|
||||
fiat_convert._load_cryptomap()
|
||||
|
||||
length_cryptomap = len(fiat_convert._cryptomap)
|
||||
assert length_cryptomap == 0
|
||||
assert fiat_convert._backoff > datetime.datetime.now().timestamp()
|
||||
assert log_has(
|
||||
'Too many requests for Coingecko API, backing off and trying again later.',
|
||||
caplog
|
||||
)
|
||||
|
||||
|
||||
def test_fiat_invalid_response(mocker, caplog):
|
||||
# Because CryptoToFiatConverter is a Singleton we reset the listings
|
||||
listmock = MagicMock(return_value="{'novalidjson':DEADBEEFf}")
|
||||
|
@@ -1,18 +1,19 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
# pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from unittest.mock import ANY, MagicMock, PropertyMock
|
||||
|
||||
import pytest
|
||||
from numpy import isnan
|
||||
|
||||
from freqtrade.edge import PairInfo
|
||||
from freqtrade.enums import State
|
||||
from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.persistence.pairlock_middleware import PairLocks
|
||||
from freqtrade.rpc import RPC, RPCException
|
||||
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
||||
from freqtrade.state import State
|
||||
from tests.conftest import create_mock_trades, get_patched_freqtradebot, patch_get_signal
|
||||
|
||||
|
||||
@@ -52,7 +53,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'pair': 'ETH/BTC',
|
||||
'base_currency': 'BTC',
|
||||
'open_date': ANY,
|
||||
'open_date_hum': ANY,
|
||||
'open_timestamp': ANY,
|
||||
'is_open': ANY,
|
||||
'fee_open': ANY,
|
||||
@@ -72,7 +72,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'timeframe': 5,
|
||||
'open_order_id': ANY,
|
||||
'close_date': None,
|
||||
'close_date_hum': None,
|
||||
'close_timestamp': None,
|
||||
'open_rate': 1.098e-05,
|
||||
'close_rate': None,
|
||||
@@ -91,6 +90,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'profit_ratio': -0.00408133,
|
||||
'profit_pct': -0.41,
|
||||
'profit_abs': -4.09e-06,
|
||||
'profit_fiat': ANY,
|
||||
'stop_loss_abs': 9.882e-06,
|
||||
'stop_loss_pct': -10.0,
|
||||
'stop_loss_ratio': -0.1,
|
||||
@@ -106,10 +106,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'stoploss_entry_dist': -0.00010475,
|
||||
'stoploss_entry_dist_ratio': -0.10448878,
|
||||
'open_order': None,
|
||||
'exchange': 'bittrex',
|
||||
'exchange': 'binance',
|
||||
}
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate',
|
||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||
results = rpc._rpc_trade_status()
|
||||
assert isnan(results[0]['current_profit'])
|
||||
@@ -119,7 +119,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'pair': 'ETH/BTC',
|
||||
'base_currency': 'BTC',
|
||||
'open_date': ANY,
|
||||
'open_date_hum': ANY,
|
||||
'open_timestamp': ANY,
|
||||
'is_open': ANY,
|
||||
'fee_open': ANY,
|
||||
@@ -139,7 +138,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'timeframe': ANY,
|
||||
'open_order_id': ANY,
|
||||
'close_date': None,
|
||||
'close_date_hum': None,
|
||||
'close_timestamp': None,
|
||||
'open_rate': 1.098e-05,
|
||||
'close_rate': None,
|
||||
@@ -158,6 +156,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'profit_ratio': ANY,
|
||||
'profit_pct': ANY,
|
||||
'profit_abs': ANY,
|
||||
'profit_fiat': ANY,
|
||||
'stop_loss_abs': 9.882e-06,
|
||||
'stop_loss_pct': -10.0,
|
||||
'stop_loss_ratio': -0.1,
|
||||
@@ -173,7 +172,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'stoploss_entry_dist': -0.00010475,
|
||||
'stoploss_entry_dist_ratio': -0.10448878,
|
||||
'open_order': None,
|
||||
'exchange': 'bittrex',
|
||||
'exchange': 'binance',
|
||||
}
|
||||
|
||||
|
||||
@@ -200,28 +199,31 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||
|
||||
freqtradebot.enter_positions()
|
||||
|
||||
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||
assert "Since" in headers
|
||||
assert "Pair" in headers
|
||||
assert 'instantly' == result[0][2]
|
||||
assert 'ETH/BTC' in result[0][1]
|
||||
assert '-0.41%' == result[0][3]
|
||||
assert isnan(fiat_profit_sum)
|
||||
# Test with fiatconvert
|
||||
|
||||
rpc._fiat_converter = CryptoToFiatConverter()
|
||||
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||
assert "Since" in headers
|
||||
assert "Pair" in headers
|
||||
assert 'instantly' == result[0][2]
|
||||
assert 'ETH/BTC' in result[0][1]
|
||||
assert '-0.41% (-0.06)' == result[0][3]
|
||||
assert '-0.06' == f'{fiat_profit_sum:.2f}'
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate',
|
||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||
assert 'instantly' == result[0][2]
|
||||
assert 'ETH/BTC' in result[0][1]
|
||||
assert 'nan%' == result[0][3]
|
||||
assert isnan(fiat_profit_sum)
|
||||
|
||||
|
||||
def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
||||
@@ -412,26 +414,26 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
||||
|
||||
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
|
||||
assert prec_satoshi(stats['profit_closed_coin'], 6.217e-05)
|
||||
assert prec_satoshi(stats['profit_closed_percent'], 6.2)
|
||||
assert prec_satoshi(stats['profit_closed_percent_mean'], 6.2)
|
||||
assert prec_satoshi(stats['profit_closed_fiat'], 0.93255)
|
||||
assert prec_satoshi(stats['profit_all_coin'], 5.802e-05)
|
||||
assert prec_satoshi(stats['profit_all_percent'], 2.89)
|
||||
assert prec_satoshi(stats['profit_all_percent_mean'], 2.89)
|
||||
assert prec_satoshi(stats['profit_all_fiat'], 0.8703)
|
||||
assert stats['trade_count'] == 2
|
||||
assert stats['first_trade_date'] == 'just now'
|
||||
assert stats['latest_trade_date'] == 'just now'
|
||||
assert stats['avg_duration'] == '0:00:00'
|
||||
assert stats['avg_duration'] in ('0:00:00', '0:00:01')
|
||||
assert stats['best_pair'] == 'ETH/BTC'
|
||||
assert prec_satoshi(stats['best_rate'], 6.2)
|
||||
|
||||
# Test non-available pair
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate',
|
||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
|
||||
assert stats['trade_count'] == 2
|
||||
assert stats['first_trade_date'] == 'just now'
|
||||
assert stats['latest_trade_date'] == 'just now'
|
||||
assert stats['avg_duration'] == '0:00:00'
|
||||
assert stats['avg_duration'] in ('0:00:00', '0:00:01')
|
||||
assert stats['best_pair'] == 'ETH/BTC'
|
||||
assert prec_satoshi(stats['best_rate'], 6.2)
|
||||
assert isnan(stats['profit_all_coin'])
|
||||
@@ -481,10 +483,10 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
|
||||
|
||||
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
|
||||
assert prec_satoshi(stats['profit_closed_coin'], 0)
|
||||
assert prec_satoshi(stats['profit_closed_percent'], 0)
|
||||
assert prec_satoshi(stats['profit_closed_percent_mean'], 0)
|
||||
assert prec_satoshi(stats['profit_closed_fiat'], 0)
|
||||
assert prec_satoshi(stats['profit_all_coin'], 0)
|
||||
assert prec_satoshi(stats['profit_all_percent'], 0)
|
||||
assert prec_satoshi(stats['profit_all_percent_mean'], 0)
|
||||
assert prec_satoshi(stats['profit_all_fiat'], 0)
|
||||
assert stats['trade_count'] == 1
|
||||
assert stats['first_trade_date'] == 'just now'
|
||||
@@ -570,6 +572,8 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
|
||||
result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency'])
|
||||
assert prec_satoshi(result['total'], 12.309096315)
|
||||
assert prec_satoshi(result['value'], 184636.44472997)
|
||||
assert tickers.call_count == 1
|
||||
assert tickers.call_args_list[0][1]['cached'] is True
|
||||
assert 'USD' == result['symbol']
|
||||
assert result['currencies'] == [
|
||||
{'currency': 'BTC',
|
||||
@@ -911,6 +915,24 @@ def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
|
||||
rpc._rpc_forcebuy(pair, None)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_rpc_delete_lock(mocker, default_conf):
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc = RPC(freqtradebot)
|
||||
pair = 'ETH/BTC'
|
||||
|
||||
PairLocks.lock_pair(pair, datetime.now(timezone.utc) + timedelta(minutes=4))
|
||||
PairLocks.lock_pair(pair, datetime.now(timezone.utc) + timedelta(minutes=5))
|
||||
PairLocks.lock_pair(pair, datetime.now(timezone.utc) + timedelta(minutes=10))
|
||||
locks = rpc._rpc_locks()
|
||||
assert locks['lock_count'] == 3
|
||||
locks1 = rpc._rpc_delete_lock(lockid=locks['locks'][0]['id'])
|
||||
assert locks1['lock_count'] == 2
|
||||
|
||||
locks2 = rpc._rpc_delete_lock(pair=pair)
|
||||
assert locks2['lock_count'] == 0
|
||||
|
||||
|
||||
def test_rpc_whitelist(mocker, default_conf) -> None:
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
|
||||
|
@@ -11,18 +11,20 @@ import uvicorn
|
||||
from fastapi import FastAPI
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi.testclient import TestClient
|
||||
from numpy import isnan
|
||||
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.loggers import setup_logging, setup_logging_pre
|
||||
from freqtrade.persistence import PairLocks, Trade
|
||||
from freqtrade.rpc import RPC
|
||||
from freqtrade.rpc.api_server import ApiServer
|
||||
from freqtrade.rpc.api_server.api_auth import create_token, get_user_from_token
|
||||
from freqtrade.rpc.api_server.uvicorn_threaded import UvicornServer
|
||||
from freqtrade.state import RunMode, State
|
||||
from tests.conftest import (create_mock_trades, get_patched_freqtradebot, log_has, log_has_re,
|
||||
patch_get_signal)
|
||||
from tests.conftest import (create_mock_trades, get_mock_coro, get_patched_freqtradebot, log_has,
|
||||
log_has_re, patch_get_signal)
|
||||
|
||||
|
||||
BASE_URI = "/api/v1"
|
||||
@@ -228,7 +230,7 @@ def test_api__init__(default_conf, mocker):
|
||||
assert apiserver._config == default_conf
|
||||
|
||||
|
||||
def test_api_UvicornServer(default_conf, mocker):
|
||||
def test_api_UvicornServer(mocker):
|
||||
thread_mock = mocker.patch('freqtrade.rpc.api_server.uvicorn_threaded.threading.Thread')
|
||||
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1'))
|
||||
assert thread_mock.call_count == 0
|
||||
@@ -246,6 +248,38 @@ def test_api_UvicornServer(default_conf, mocker):
|
||||
assert s.should_exit is True
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def test_api_run(default_conf, mocker, caplog):
|
||||
default_conf.update({"api_server": {"enabled": True,
|
||||
"listen_ip_address": "127.0.0.1",
|
||||
@@ -295,7 +329,7 @@ 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)
|
||||
|
||||
# Test crashing flask
|
||||
# Test crashing API server
|
||||
caplog.clear()
|
||||
mocker.patch('freqtrade.rpc.api_server.webserver.UvicornServer',
|
||||
MagicMock(side_effect=Exception))
|
||||
@@ -382,10 +416,10 @@ def test_api_count(botclient, mocker, ticker, fee, markets):
|
||||
assert rc.json()["max"] == 1
|
||||
|
||||
# Create some test data
|
||||
ftbot.enter_positions()
|
||||
create_mock_trades(fee)
|
||||
rc = client_get(client, f"{BASE_URI}/count")
|
||||
assert_response(rc)
|
||||
assert rc.json()["current"] == 1
|
||||
assert rc.json()["current"] == 4
|
||||
assert rc.json()["max"] == 1
|
||||
|
||||
ftbot.config['max_open_trades'] = float('inf')
|
||||
@@ -416,6 +450,16 @@ def test_api_locks(botclient):
|
||||
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'])
|
||||
|
||||
# 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"}')
|
||||
assert_response(rc)
|
||||
assert rc.json()['lock_count'] == 0
|
||||
|
||||
|
||||
def test_api_show_config(botclient, mocker):
|
||||
ftbot, client = botclient
|
||||
@@ -424,7 +468,7 @@ def test_api_show_config(botclient, mocker):
|
||||
rc = client_get(client, f"{BASE_URI}/show_config")
|
||||
assert_response(rc)
|
||||
assert 'dry_run' in rc.json()
|
||||
assert rc.json()['exchange'] == 'bittrex'
|
||||
assert rc.json()['exchange'] == 'binance'
|
||||
assert rc.json()['timeframe'] == '5m'
|
||||
assert rc.json()['timeframe_ms'] == 300000
|
||||
assert rc.json()['timeframe_min'] == 5
|
||||
@@ -462,20 +506,43 @@ def test_api_trades(botclient, mocker, fee, markets):
|
||||
)
|
||||
rc = client_get(client, f"{BASE_URI}/trades")
|
||||
assert_response(rc)
|
||||
assert len(rc.json()) == 2
|
||||
assert len(rc.json()) == 3
|
||||
assert rc.json()['trades_count'] == 0
|
||||
assert rc.json()['total_trades'] == 0
|
||||
|
||||
create_mock_trades(fee)
|
||||
Trade.session.flush()
|
||||
Trade.query.session.flush()
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/trades")
|
||||
assert_response(rc)
|
||||
assert len(rc.json()['trades']) == 2
|
||||
assert rc.json()['trades_count'] == 2
|
||||
assert rc.json()['total_trades'] == 2
|
||||
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
|
||||
assert rc.json()['total_trades'] == 2
|
||||
|
||||
|
||||
def test_api_trade_single(botclient, mocker, fee, ticker, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
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.'
|
||||
|
||||
create_mock_trades(fee)
|
||||
Trade.query.session.flush()
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/trade/3")
|
||||
assert_response(rc)
|
||||
assert rc.json()['trade_id'] == 3
|
||||
|
||||
|
||||
def test_api_delete_trade(botclient, mocker, fee, markets):
|
||||
@@ -494,7 +561,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets):
|
||||
assert_response(rc, 502)
|
||||
|
||||
create_mock_trades(fee)
|
||||
Trade.session.flush()
|
||||
Trade.query.session.flush()
|
||||
ftbot.strategy.order_types['stoploss_on_exchange'] = True
|
||||
trades = Trade.query.all()
|
||||
trades[1].stoploss_order_id = '1234'
|
||||
@@ -568,7 +635,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, limit_sell_order):
|
||||
def test_api_profit(botclient, mocker, ticker, fee, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
mocker.patch.multiple(
|
||||
@@ -583,50 +650,33 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, li
|
||||
assert_response(rc, 200)
|
||||
assert rc.json()['trade_count'] == 0
|
||||
|
||||
ftbot.enter_positions()
|
||||
trade = Trade.query.first()
|
||||
|
||||
create_mock_trades(fee)
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
trade.update(limit_buy_order)
|
||||
rc = client_get(client, f"{BASE_URI}/profit")
|
||||
assert_response(rc, 200)
|
||||
# One open trade
|
||||
assert rc.json()['trade_count'] == 1
|
||||
assert rc.json()['best_pair'] == ''
|
||||
assert rc.json()['best_rate'] == 0
|
||||
|
||||
trade = Trade.query.first()
|
||||
trade.update(limit_sell_order)
|
||||
|
||||
trade.close_date = datetime.utcnow()
|
||||
trade.is_open = False
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/profit")
|
||||
assert_response(rc)
|
||||
assert rc.json() == {'avg_duration': ANY,
|
||||
'best_pair': 'ETH/BTC',
|
||||
'best_rate': 6.2,
|
||||
'first_trade_date': 'just now',
|
||||
'best_pair': 'XRP/BTC',
|
||||
'best_rate': 1.0,
|
||||
'first_trade_date': ANY,
|
||||
'first_trade_timestamp': ANY,
|
||||
'latest_trade_date': 'just now',
|
||||
'latest_trade_date': '5 minutes ago',
|
||||
'latest_trade_timestamp': ANY,
|
||||
'profit_all_coin': 6.217e-05,
|
||||
'profit_all_fiat': 0.76748865,
|
||||
'profit_all_percent': 6.2,
|
||||
'profit_all_percent_mean': 6.2,
|
||||
'profit_all_ratio_mean': 0.06201058,
|
||||
'profit_all_percent_sum': 6.2,
|
||||
'profit_all_ratio_sum': 0.06201058,
|
||||
'profit_closed_coin': 6.217e-05,
|
||||
'profit_closed_fiat': 0.76748865,
|
||||
'profit_closed_percent': 6.2,
|
||||
'profit_closed_ratio_mean': 0.06201058,
|
||||
'profit_closed_percent_mean': 6.2,
|
||||
'profit_closed_ratio_sum': 0.06201058,
|
||||
'profit_closed_percent_sum': 6.2,
|
||||
'trade_count': 1,
|
||||
'closed_trade_count': 1,
|
||||
'winning_trades': 1,
|
||||
'profit_all_coin': -44.0631579,
|
||||
'profit_all_fiat': -543959.6842755,
|
||||
'profit_all_percent_mean': -66.41,
|
||||
'profit_all_ratio_mean': -0.6641100666666667,
|
||||
'profit_all_percent_sum': -398.47,
|
||||
'profit_all_ratio_sum': -3.9846604,
|
||||
'profit_closed_coin': 0.00073913,
|
||||
'profit_closed_fiat': 9.124559849999999,
|
||||
'profit_closed_ratio_mean': 0.0075,
|
||||
'profit_closed_percent_mean': 0.75,
|
||||
'profit_closed_ratio_sum': 0.015,
|
||||
'profit_closed_percent_sum': 1.5,
|
||||
'trade_count': 6,
|
||||
'closed_trade_count': 2,
|
||||
'winning_trades': 2,
|
||||
'losing_trades': 0,
|
||||
}
|
||||
|
||||
@@ -660,7 +710,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,):
|
||||
assert 'draws' in rc.json()['durations']
|
||||
|
||||
|
||||
def test_api_performance(botclient, mocker, ticker, fee):
|
||||
def test_api_performance(botclient, fee):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
|
||||
@@ -678,7 +728,8 @@ def test_api_performance(botclient, mocker, ticker, fee):
|
||||
|
||||
)
|
||||
trade.close_profit = trade.calc_profit_ratio()
|
||||
Trade.session.add(trade)
|
||||
trade.close_profit_abs = trade.calc_profit()
|
||||
Trade.query.session.add(trade)
|
||||
|
||||
trade = Trade(
|
||||
pair='XRP/ETH',
|
||||
@@ -693,14 +744,16 @@ def test_api_performance(botclient, mocker, ticker, fee):
|
||||
close_rate=0.391
|
||||
)
|
||||
trade.close_profit = trade.calc_profit_ratio()
|
||||
Trade.session.add(trade)
|
||||
Trade.session.flush()
|
||||
trade.close_profit_abs = trade.calc_profit()
|
||||
|
||||
Trade.query.session.add(trade)
|
||||
Trade.query.session.flush()
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/performance")
|
||||
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}]
|
||||
assert rc.json() == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61, 'profit_abs': 0.01872279},
|
||||
{'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57, 'profit_abs': -0.1150375}]
|
||||
|
||||
|
||||
def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||
@@ -711,83 +764,84 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
fetch_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
markets=PropertyMock(return_value=markets),
|
||||
fetch_order=MagicMock(return_value={}),
|
||||
)
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/status")
|
||||
assert_response(rc, 200)
|
||||
assert rc.json() == []
|
||||
|
||||
ftbot.enter_positions()
|
||||
trades = Trade.get_open_trades()
|
||||
trades[0].open_order_id = None
|
||||
ftbot.exit_positions(trades)
|
||||
Trade.session.flush()
|
||||
create_mock_trades(fee)
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/status")
|
||||
assert_response(rc)
|
||||
assert len(rc.json()) == 1
|
||||
assert rc.json() == [{
|
||||
'amount': 91.07468123,
|
||||
'amount_requested': 91.07468123,
|
||||
'base_currency': 'BTC',
|
||||
assert len(rc.json()) == 4
|
||||
assert rc.json()[0] == {
|
||||
'amount': 123.0,
|
||||
'amount_requested': 123.0,
|
||||
'close_date': None,
|
||||
'close_date_hum': None,
|
||||
'close_timestamp': None,
|
||||
'close_profit': None,
|
||||
'close_profit_pct': None,
|
||||
'close_profit_abs': None,
|
||||
'close_rate': None,
|
||||
'current_profit': -0.00408133,
|
||||
'current_profit_pct': -0.41,
|
||||
'current_profit_abs': -4.09e-06,
|
||||
'profit_ratio': -0.00408133,
|
||||
'profit_pct': -0.41,
|
||||
'profit_abs': -4.09e-06,
|
||||
'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': 1.099e-05,
|
||||
'open_date': ANY,
|
||||
'open_date_hum': 'just now',
|
||||
'open_timestamp': ANY,
|
||||
'open_order': None,
|
||||
'open_rate': 1.098e-05,
|
||||
'open_rate': 0.123,
|
||||
'pair': 'ETH/BTC',
|
||||
'stake_amount': 0.001,
|
||||
'stop_loss_abs': 9.882e-06,
|
||||
'stop_loss_pct': -10.0,
|
||||
'stop_loss_ratio': -0.1,
|
||||
'stop_loss_abs': ANY,
|
||||
'stop_loss_pct': ANY,
|
||||
'stop_loss_ratio': ANY,
|
||||
'stoploss_order_id': None,
|
||||
'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,
|
||||
'stoploss_current_dist': -1.1080000000000002e-06,
|
||||
'stoploss_current_dist_ratio': -0.10081893,
|
||||
'stoploss_current_dist_pct': -10.08,
|
||||
'stoploss_entry_dist': -0.00010475,
|
||||
'stoploss_entry_dist_ratio': -0.10448878,
|
||||
'initial_stop_loss_abs': 0.0,
|
||||
'initial_stop_loss_pct': ANY,
|
||||
'initial_stop_loss_ratio': ANY,
|
||||
'stoploss_current_dist': ANY,
|
||||
'stoploss_current_dist_ratio': ANY,
|
||||
'stoploss_current_dist_pct': ANY,
|
||||
'stoploss_entry_dist': ANY,
|
||||
'stoploss_entry_dist_ratio': ANY,
|
||||
'trade_id': 1,
|
||||
'close_rate_requested': None,
|
||||
'current_rate': 1.099e-05,
|
||||
'close_rate_requested': ANY,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
'fee_open': 0.0025,
|
||||
'fee_open_cost': None,
|
||||
'fee_open_currency': None,
|
||||
'open_date': ANY,
|
||||
'is_open': True,
|
||||
'max_rate': 1.099e-05,
|
||||
'min_rate': 1.098e-05,
|
||||
'open_order_id': None,
|
||||
'open_rate_requested': 1.098e-05,
|
||||
'open_trade_value': 0.0010025,
|
||||
'max_rate': ANY,
|
||||
'min_rate': ANY,
|
||||
'open_order_id': 'dry_run_buy_12345',
|
||||
'open_rate_requested': ANY,
|
||||
'open_trade_value': 15.1668225,
|
||||
'sell_reason': None,
|
||||
'sell_order_status': None,
|
||||
'strategy': 'DefaultStrategy',
|
||||
'timeframe': 5,
|
||||
'exchange': 'bittrex',
|
||||
}]
|
||||
'exchange': 'binance',
|
||||
}
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate',
|
||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/status")
|
||||
assert_response(rc)
|
||||
resp_values = rc.json()
|
||||
assert len(resp_values) == 4
|
||||
assert isnan(resp_values[0]['profit_abs'])
|
||||
|
||||
|
||||
def test_api_version(botclient):
|
||||
@@ -868,7 +922,7 @@ def test_api_forcebuy(botclient, mocker, fee):
|
||||
pair='ETH/ETH',
|
||||
amount=1,
|
||||
amount_requested=1,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
stake_amount=1,
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456",
|
||||
@@ -891,11 +945,9 @@ def test_api_forcebuy(botclient, mocker, fee):
|
||||
'amount_requested': 1,
|
||||
'trade_id': 22,
|
||||
'close_date': None,
|
||||
'close_date_hum': None,
|
||||
'close_timestamp': None,
|
||||
'close_rate': 0.265441,
|
||||
'open_date': ANY,
|
||||
'open_date_hum': 'just now',
|
||||
'open_timestamp': ANY,
|
||||
'open_rate': 0.245441,
|
||||
'pair': 'ETH/ETH',
|
||||
@@ -916,6 +968,7 @@ def test_api_forcebuy(botclient, mocker, fee):
|
||||
'profit_ratio': None,
|
||||
'profit_pct': None,
|
||||
'profit_abs': None,
|
||||
'profit_fiat': None,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
@@ -932,7 +985,7 @@ def test_api_forcebuy(botclient, mocker, fee):
|
||||
'sell_order_status': None,
|
||||
'strategy': 'DefaultStrategy',
|
||||
'timeframe': 5,
|
||||
'exchange': 'bittrex',
|
||||
'exchange': 'binance',
|
||||
}
|
||||
|
||||
|
||||
@@ -1092,6 +1145,14 @@ def test_api_plot_config(botclient):
|
||||
assert_response(rc)
|
||||
assert rc.json() == ftbot.strategy.plot_config
|
||||
assert isinstance(rc.json()['main_plot'], dict)
|
||||
assert isinstance(rc.json()['subplots'], dict)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def test_api_strategies(botclient):
|
||||
@@ -1100,7 +1161,11 @@ def test_api_strategies(botclient):
|
||||
rc = client_get(client, f"{BASE_URI}/strategies")
|
||||
|
||||
assert_response(rc)
|
||||
assert rc.json() == {'strategies': ['DefaultStrategy', 'TestStrategyLegacy']}
|
||||
assert rc.json() == {'strategies': [
|
||||
'DefaultStrategy',
|
||||
'HyperoptableStrategy',
|
||||
'TestStrategyLegacy'
|
||||
]}
|
||||
|
||||
|
||||
def test_api_strategy(botclient):
|
||||
|
@@ -3,7 +3,8 @@ import logging
|
||||
import time
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from freqtrade.rpc import RPCManager, RPCMessageType
|
||||
from freqtrade.enums import RPCMessageType
|
||||
from freqtrade.rpc import RPCManager
|
||||
from tests.conftest import get_patched_freqtradebot, log_has
|
||||
|
||||
|
||||
@@ -71,7 +72,7 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc_manager = RPCManager(freqtradebot)
|
||||
rpc_manager.send_msg({
|
||||
'type': RPCMessageType.STATUS_NOTIFICATION,
|
||||
'type': RPCMessageType.STATUS,
|
||||
'status': 'test'
|
||||
})
|
||||
|
||||
@@ -86,7 +87,7 @@ def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None:
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc_manager = RPCManager(freqtradebot)
|
||||
rpc_manager.send_msg({
|
||||
'type': RPCMessageType.STATUS_NOTIFICATION,
|
||||
'type': RPCMessageType.STATUS,
|
||||
'status': 'test'
|
||||
})
|
||||
|
||||
@@ -124,7 +125,7 @@ def test_send_msg_webhook_CustomMessagetype(mocker, default_conf, caplog) -> Non
|
||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
||||
|
||||
assert 'webhook' in [mod.name for mod in rpc_manager.registered_modules]
|
||||
rpc_manager.send_msg({'type': RPCMessageType.STARTUP_NOTIFICATION,
|
||||
rpc_manager.send_msg({'type': RPCMessageType.STARTUP,
|
||||
'status': 'TestMessage'})
|
||||
assert log_has(
|
||||
"Message type 'startup' not implemented by handler webhook.",
|
||||
@@ -140,7 +141,7 @@ def test_startupmessages_telegram_enabled(mocker, default_conf, caplog) -> None:
|
||||
rpc_manager.startup_messages(default_conf, freqtradebot.pairlists, freqtradebot.protections)
|
||||
|
||||
assert telegram_mock.call_count == 3
|
||||
assert "*Exchange:* `bittrex`" in telegram_mock.call_args_list[1][0][0]['status']
|
||||
assert "*Exchange:* `binance`" in telegram_mock.call_args_list[1][0][0]['status']
|
||||
|
||||
telegram_mock.reset_mock()
|
||||
default_conf['dry_run'] = True
|
||||
|
@@ -4,6 +4,7 @@
|
||||
|
||||
import re
|
||||
from datetime import datetime
|
||||
from functools import reduce
|
||||
from random import choice, randint
|
||||
from string import ascii_uppercase
|
||||
from unittest.mock import ANY, MagicMock
|
||||
@@ -16,14 +17,13 @@ from telegram.error import NetworkError
|
||||
from freqtrade import __version__
|
||||
from freqtrade.constants import CANCEL_REASON
|
||||
from freqtrade.edge import PairInfo
|
||||
from freqtrade.enums import RPCMessageType, RunMode, SellType, State
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
from freqtrade.loggers import setup_logging
|
||||
from freqtrade.persistence import PairLocks, Trade
|
||||
from freqtrade.rpc import RPC, RPCMessageType
|
||||
from freqtrade.rpc import RPC
|
||||
from freqtrade.rpc.telegram import Telegram, authorized_only
|
||||
from freqtrade.state import RunMode, State
|
||||
from freqtrade.strategy.interface import SellType
|
||||
from tests.conftest import (create_mock_trades, get_patched_freqtradebot, log_has, patch_exchange,
|
||||
patch_get_signal, patch_whitelist)
|
||||
|
||||
@@ -92,7 +92,8 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
|
||||
message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], "
|
||||
"['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], "
|
||||
"['delete'], ['performance'], ['stats'], ['daily'], ['count'], ['locks'], "
|
||||
"['reload_config', 'reload_conf'], ['show_config', 'show_conf'], ['stopbuy'], "
|
||||
"['unlock', 'delete_locks'], ['reload_config', 'reload_conf'], "
|
||||
"['show_config', 'show_conf'], ['stopbuy'], "
|
||||
"['whitelist'], ['blacklist'], ['logs'], ['edge'], ['help'], ['version']"
|
||||
"]")
|
||||
|
||||
@@ -176,9 +177,7 @@ def test_telegram_status(default_conf, update, mocker) -> None:
|
||||
'pair': 'ETH/BTC',
|
||||
'base_currency': 'BTC',
|
||||
'open_date': arrow.utcnow(),
|
||||
'open_date_hum': arrow.utcnow().humanize,
|
||||
'close_date': None,
|
||||
'close_date_hum': None,
|
||||
'open_rate': 1.099e-05,
|
||||
'close_rate': None,
|
||||
'current_rate': 1.098e-05,
|
||||
@@ -443,8 +442,10 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
||||
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
trade.update(limit_buy_order)
|
||||
|
||||
telegram._profit(update=update, context=MagicMock())
|
||||
context = MagicMock()
|
||||
# Test with invalid 2nd argument (should silently pass)
|
||||
context.args = ["aaa"]
|
||||
telegram._profit(update=update, context=context)
|
||||
assert msg_mock.call_count == 1
|
||||
assert 'No closed trade' in msg_mock.call_args_list[-1][0][0]
|
||||
assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0]
|
||||
@@ -520,7 +521,7 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick
|
||||
assert 'Balance:' in result
|
||||
assert 'Est. BTC:' in result
|
||||
assert 'BTC: 12.00000000' in result
|
||||
assert '*XRP:* not showing <1$ amount' in result
|
||||
assert '*XRP:* not showing <0.0001 BTC amount' in result
|
||||
|
||||
|
||||
def test_balance_handle_empty_response(default_conf, update, mocker) -> None:
|
||||
@@ -684,12 +685,12 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
|
||||
context.args = ["1"]
|
||||
telegram._forcesell(update=update, context=context)
|
||||
|
||||
assert msg_mock.call_count == 3
|
||||
assert msg_mock.call_count == 4
|
||||
last_msg = msg_mock.call_args_list[-1][0][0]
|
||||
assert {
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'type': RPCMessageType.SELL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Bittrex',
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'gain': 'profit',
|
||||
'limit': 1.173e-05,
|
||||
@@ -704,6 +705,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
|
||||
'sell_reason': SellType.FORCE_SELL.value,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
'close_rate': ANY,
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -744,13 +746,13 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
|
||||
context.args = ["1"]
|
||||
telegram._forcesell(update=update, context=context)
|
||||
|
||||
assert msg_mock.call_count == 3
|
||||
assert msg_mock.call_count == 4
|
||||
|
||||
last_msg = msg_mock.call_args_list[-1][0][0]
|
||||
assert {
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'type': RPCMessageType.SELL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Bittrex',
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'gain': 'loss',
|
||||
'limit': 1.043e-05,
|
||||
@@ -765,6 +767,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
|
||||
'sell_reason': SellType.FORCE_SELL.value,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
'close_rate': ANY,
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -795,13 +798,13 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
|
||||
context.args = ["all"]
|
||||
telegram._forcesell(update=update, context=context)
|
||||
|
||||
# Called for each trade 3 times
|
||||
assert msg_mock.call_count == 8
|
||||
msg = msg_mock.call_args_list[1][0][0]
|
||||
# Called for each trade 4 times
|
||||
assert msg_mock.call_count == 12
|
||||
msg = msg_mock.call_args_list[2][0][0]
|
||||
assert {
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'type': RPCMessageType.SELL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Bittrex',
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'gain': 'loss',
|
||||
'limit': 1.099e-05,
|
||||
@@ -816,6 +819,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
|
||||
'sell_reason': SellType.FORCE_SELL.value,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
'close_rate': ANY,
|
||||
} == msg
|
||||
|
||||
|
||||
@@ -900,6 +904,33 @@ def test_forcebuy_handle_exception(default_conf, update, mocker) -> None:
|
||||
assert msg_mock.call_args_list[0][0][0] == 'Forcebuy not enabled.'
|
||||
|
||||
|
||||
def test_forcebuy_no_pair(default_conf, update, mocker) -> None:
|
||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||
|
||||
fbuy_mock = MagicMock(return_value=None)
|
||||
mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock)
|
||||
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
|
||||
context = MagicMock()
|
||||
context.args = []
|
||||
telegram._forcebuy(update=update, context=context)
|
||||
|
||||
assert fbuy_mock.call_count == 0
|
||||
assert msg_mock.call_count == 1
|
||||
assert msg_mock.call_args_list[0][1]['msg'] == 'Which pair?'
|
||||
# assert msg_mock.call_args_list[0][1]['callback_query_handler'] == 'forcebuy'
|
||||
keyboard = msg_mock.call_args_list[0][1]['keyboard']
|
||||
assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 4
|
||||
update = MagicMock()
|
||||
update.callback_query = MagicMock()
|
||||
update.callback_query.data = 'XRP/USDT'
|
||||
telegram._forcebuy_inline(update, None)
|
||||
assert fbuy_mock.call_count == 1
|
||||
|
||||
|
||||
def test_performance_handle(default_conf, update, ticker, fee,
|
||||
limit_buy_order, limit_sell_order, mocker) -> None:
|
||||
|
||||
@@ -927,7 +958,7 @@ def test_performance_handle(default_conf, update, ticker, fee,
|
||||
telegram._performance(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
assert 'Performance' in msg_mock.call_args_list[0][0][0]
|
||||
assert '<code>ETH/BTC\t6.20% (1)</code>' in msg_mock.call_args_list[0][0][0]
|
||||
assert '<code>ETH/BTC\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
@@ -967,6 +998,11 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None
|
||||
)
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
telegram._locks(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
assert 'No active locks.' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
msg_mock.reset_mock()
|
||||
|
||||
PairLocks.lock_pair('ETH/BTC', arrow.utcnow().shift(minutes=4).datetime, 'randreason')
|
||||
PairLocks.lock_pair('XRP/BTC', arrow.utcnow().shift(minutes=20).datetime, 'deadbeef')
|
||||
@@ -981,6 +1017,16 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None
|
||||
assert 'deadbeef' in msg_mock.call_args_list[0][0][0]
|
||||
assert 'randreason' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
context = MagicMock()
|
||||
context.args = ['XRP/BTC']
|
||||
msg_mock.reset_mock()
|
||||
telegram._delete_locks(update=update, context=context)
|
||||
|
||||
assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0]
|
||||
assert 'randreason' in msg_mock.call_args_list[0][0][0]
|
||||
assert 'XRP/BTC' not in msg_mock.call_args_list[0][0][0]
|
||||
assert 'deadbeef' not in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_whitelist_static(default_conf, update, mocker) -> None:
|
||||
|
||||
@@ -1090,6 +1136,15 @@ def test_edge_enabled(edge_conf, update, mocker) -> None:
|
||||
assert '<b>Edge only validated following pairs:</b>\n<pre>' in msg_mock.call_args_list[0][0][0]
|
||||
assert 'Pair Winrate Expectancy Stoploss' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
msg_mock.reset_mock()
|
||||
|
||||
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||
return_value={}))
|
||||
telegram._edge(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
assert '<b>Edge only validated following pairs:</b>' in msg_mock.call_args_list[0][0][0]
|
||||
assert 'Winrate' not in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_telegram_trades(mocker, update, default_conf, fee):
|
||||
|
||||
@@ -1117,8 +1172,10 @@ def test_telegram_trades(mocker, update, default_conf, fee):
|
||||
msg_mock.call_count == 1
|
||||
assert "2 recent trades</b>:" in msg_mock.call_args_list[0][0][0]
|
||||
assert "Profit (" in msg_mock.call_args_list[0][0][0]
|
||||
assert "Open Date" in msg_mock.call_args_list[0][0][0]
|
||||
assert "Close Date" in msg_mock.call_args_list[0][0][0]
|
||||
assert "<pre>" in msg_mock.call_args_list[0][0][0]
|
||||
assert bool(re.search(r"just now[ ]*XRP\/BTC \(#3\) 1.00% \(",
|
||||
msg_mock.call_args_list[0][0][0]))
|
||||
|
||||
|
||||
def test_telegram_delete_trade(mocker, update, default_conf, fee):
|
||||
@@ -1167,7 +1224,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
|
||||
telegram._show_config(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
|
||||
assert '*Exchange:* `bittrex`' in msg_mock.call_args_list[0][0][0]
|
||||
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
|
||||
assert '*Strategy:* `DefaultStrategy`' in msg_mock.call_args_list[0][0][0]
|
||||
assert '*Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
@@ -1176,7 +1233,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
|
||||
telegram._show_config(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
|
||||
assert '*Exchange:* `bittrex`' in msg_mock.call_args_list[0][0][0]
|
||||
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
|
||||
assert '*Strategy:* `DefaultStrategy`' in msg_mock.call_args_list[0][0][0]
|
||||
assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
@@ -1184,8 +1241,9 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
|
||||
def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
|
||||
|
||||
msg = {
|
||||
'type': RPCMessageType.BUY_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'type': RPCMessageType.BUY,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'limit': 1.099e-05,
|
||||
'order_type': 'limit',
|
||||
@@ -1201,7 +1259,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
|
||||
|
||||
telegram.send_msg(msg)
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== '\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC\n' \
|
||||
== '\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' \
|
||||
'*Amount:* `1333.33333333`\n' \
|
||||
'*Open Rate:* `0.00001099`\n' \
|
||||
'*Current Rate:* `0.00001099`\n' \
|
||||
@@ -1228,13 +1286,34 @@ def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None:
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.BUY_CANCEL_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'type': RPCMessageType.BUY_CANCEL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'reason': CANCEL_REASON['TIMEOUT']
|
||||
})
|
||||
assert (msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Bittrex:* '
|
||||
'Cancelling open buy Order for ETH/BTC. Reason: cancelled due to timeout.')
|
||||
assert (msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Binance:* '
|
||||
'Cancelling open buy Order for ETH/BTC (#1). '
|
||||
'Reason: cancelled due to timeout.')
|
||||
|
||||
|
||||
def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
|
||||
|
||||
default_conf['telegram']['notification_settings']['buy_fill'] = 'on'
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.BUY_FILL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/USDT',
|
||||
'open_rate': 200,
|
||||
'stake_amount': 100,
|
||||
'amount': 0.5,
|
||||
'open_date': arrow.utcnow().datetime
|
||||
})
|
||||
assert (msg_mock.call_args[0][0] == '\N{LARGE CIRCLE} *Binance:* '
|
||||
'Buy order for ETH/USDT (#1) filled for 200.')
|
||||
|
||||
|
||||
def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
@@ -1244,7 +1323,8 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
old_convamount = telegram._rpc._fiat_converter.convert_amount
|
||||
telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'type': RPCMessageType.SELL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'KEY/ETH',
|
||||
'gain': 'loss',
|
||||
@@ -1262,18 +1342,20 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
'close_date': arrow.utcnow(),
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n'
|
||||
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
|
||||
'*Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
|
||||
'*Sell Reason:* `stop_loss`\n'
|
||||
'*Duration:* `1:00:00 (60.0 min)`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Current Rate:* `0.00003201`\n'
|
||||
'*Close Rate:* `0.00003201`\n'
|
||||
'*Sell Reason:* `stop_loss`\n'
|
||||
'*Duration:* `1:00:00 (60.0 min)`\n'
|
||||
'*Profit:* `-57.41%` `(loss: -0.05746268 ETH / -24.812 USD)`')
|
||||
'*Close Rate:* `0.00003201`'
|
||||
)
|
||||
|
||||
msg_mock.reset_mock()
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'type': RPCMessageType.SELL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'KEY/ETH',
|
||||
'gain': 'loss',
|
||||
@@ -1290,14 +1372,15 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
'close_date': arrow.utcnow(),
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n'
|
||||
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
|
||||
'*Profit:* `-57.41%`\n'
|
||||
'*Sell Reason:* `stop_loss`\n'
|
||||
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Current Rate:* `0.00003201`\n'
|
||||
'*Close Rate:* `0.00003201`\n'
|
||||
'*Sell Reason:* `stop_loss`\n'
|
||||
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
|
||||
'*Profit:* `-57.41%`')
|
||||
'*Close Rate:* `0.00003201`'
|
||||
)
|
||||
# Reset singleton function to avoid random breaks
|
||||
telegram._rpc._fiat_converter.convert_amount = old_convamount
|
||||
|
||||
@@ -1309,33 +1392,65 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
|
||||
old_convamount = telegram._rpc._fiat_converter.convert_amount
|
||||
telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
|
||||
'type': RPCMessageType.SELL_CANCEL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'KEY/ETH',
|
||||
'reason': 'Cancelled on exchange'
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH. '
|
||||
'Reason: Cancelled on exchange')
|
||||
== ('\N{WARNING SIGN} *Binance:* Cancelling open sell Order for KEY/ETH (#1).'
|
||||
' Reason: Cancelled on exchange.')
|
||||
|
||||
msg_mock.reset_mock()
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
|
||||
'type': RPCMessageType.SELL_CANCEL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'KEY/ETH',
|
||||
'reason': 'timeout'
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH. Reason: timeout')
|
||||
== ('\N{WARNING SIGN} *Binance:* Cancelling open sell Order for KEY/ETH (#1).'
|
||||
' Reason: timeout.')
|
||||
# Reset singleton function to avoid random breaks
|
||||
telegram._rpc._fiat_converter.convert_amount = old_convamount
|
||||
|
||||
|
||||
def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
|
||||
|
||||
default_conf['telegram']['notification_settings']['sell_fill'] = 'on'
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.SELL_FILL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/USDT',
|
||||
'gain': 'loss',
|
||||
'limit': 3.201e-05,
|
||||
'amount': 0.1,
|
||||
'order_type': 'market',
|
||||
'open_rate': 500,
|
||||
'close_rate': 550,
|
||||
'current_rate': 3.201e-05,
|
||||
'profit_amount': -0.05746268,
|
||||
'profit_ratio': -0.57405275,
|
||||
'stake_currency': 'ETH',
|
||||
'fiat_currency': 'USD',
|
||||
'sell_reason': SellType.STOP_LOSS.value,
|
||||
'open_date': arrow.utcnow().shift(hours=-1),
|
||||
'close_date': arrow.utcnow(),
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== ('\N{LARGE CIRCLE} *Binance:* Sell order for ETH/USDT (#1) filled for 550.')
|
||||
|
||||
|
||||
def test_send_msg_status_notification(default_conf, mocker) -> None:
|
||||
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.STATUS_NOTIFICATION,
|
||||
'type': RPCMessageType.STATUS,
|
||||
'status': 'running'
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == '*Status:* `running`'
|
||||
@@ -1344,7 +1459,7 @@ def test_send_msg_status_notification(default_conf, mocker) -> None:
|
||||
def test_warning_notification(default_conf, mocker) -> None:
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.WARNING_NOTIFICATION,
|
||||
'type': RPCMessageType.WARNING,
|
||||
'status': 'message'
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Warning:* `message`'
|
||||
@@ -1353,7 +1468,7 @@ def test_warning_notification(default_conf, mocker) -> None:
|
||||
def test_startup_notification(default_conf, mocker) -> None:
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.STARTUP_NOTIFICATION,
|
||||
'type': RPCMessageType.STARTUP,
|
||||
'status': '*Custom:* `Hello World`'
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == '*Custom:* `Hello World`'
|
||||
@@ -1372,8 +1487,9 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.BUY_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'type': RPCMessageType.BUY,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'limit': 1.099e-05,
|
||||
'order_type': 'limit',
|
||||
@@ -1385,7 +1501,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
|
||||
'amount': 1333.3333333333335,
|
||||
'open_date': arrow.utcnow().shift(hours=-1)
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == ('\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC\n'
|
||||
assert msg_mock.call_args[0][0] == ('\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00001099`\n'
|
||||
'*Current Rate:* `0.00001099`\n'
|
||||
@@ -1397,7 +1513,8 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'type': RPCMessageType.SELL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'KEY/ETH',
|
||||
'gain': 'loss',
|
||||
@@ -1414,14 +1531,15 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
|
||||
'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3),
|
||||
'close_date': arrow.utcnow(),
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n'
|
||||
assert msg_mock.call_args[0][0] == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
|
||||
'*Profit:* `-57.41%`\n'
|
||||
'*Sell Reason:* `stop_loss`\n'
|
||||
'*Duration:* `2:35:03 (155.1 min)`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Current Rate:* `0.00003201`\n'
|
||||
'*Close Rate:* `0.00003201`\n'
|
||||
'*Sell Reason:* `stop_loss`\n'
|
||||
'*Duration:* `2:35:03 (155.1 min)`\n'
|
||||
'*Profit:* `-57.41%`')
|
||||
'*Close Rate:* `0.00003201`'
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('msg,expected', [
|
||||
@@ -1482,7 +1600,7 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
|
||||
['/count', '/start', '/stop', '/help']]
|
||||
default_keyboard = ReplyKeyboardMarkup(default_keys_list)
|
||||
|
||||
custom_keys_list = [['/daily', '/stats', '/balance', '/profit'],
|
||||
custom_keys_list = [['/daily', '/stats', '/balance', '/profit', '/profit 5'],
|
||||
['/count', '/start', '/reload_config', '/help']]
|
||||
custom_keyboard = ReplyKeyboardMarkup(custom_keys_list)
|
||||
|
||||
@@ -1516,5 +1634,5 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
|
||||
used_keyboard = bot.send_message.call_args[1]['reply_markup']
|
||||
assert used_keyboard == custom_keyboard
|
||||
assert log_has("using custom keyboard from config.json: "
|
||||
"[['/daily', '/stats', '/balance', '/profit'], ['/count', "
|
||||
"[['/daily', '/stats', '/balance', '/profit', '/profit 5'], ['/count', "
|
||||
"'/start', '/reload_config', '/help']]", caplog)
|
||||
|
@@ -5,9 +5,9 @@ from unittest.mock import MagicMock
|
||||
import pytest
|
||||
from requests import RequestException
|
||||
|
||||
from freqtrade.rpc import RPC, RPCMessageType
|
||||
from freqtrade.enums import RPCMessageType, SellType
|
||||
from freqtrade.rpc import RPC
|
||||
from freqtrade.rpc.webhook import Webhook
|
||||
from freqtrade.strategy.interface import SellType
|
||||
from tests.conftest import get_patched_freqtradebot, log_has
|
||||
|
||||
|
||||
@@ -25,6 +25,11 @@ def get_webhook_dict() -> dict:
|
||||
"value2": "limit {limit:8f}",
|
||||
"value3": "{stake_amount:8f} {stake_currency}"
|
||||
},
|
||||
"webhookbuyfill": {
|
||||
"value1": "Buy Order for {pair} filled",
|
||||
"value2": "at {open_rate:8f}",
|
||||
"value3": "{stake_amount:8f} {stake_currency}"
|
||||
},
|
||||
"webhooksell": {
|
||||
"value1": "Selling {pair}",
|
||||
"value2": "limit {limit:8f}",
|
||||
@@ -35,6 +40,11 @@ def get_webhook_dict() -> dict:
|
||||
"value2": "limit {limit:8f}",
|
||||
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
|
||||
},
|
||||
"webhooksellfill": {
|
||||
"value1": "Sell Order for {pair} filled",
|
||||
"value2": "at {close_rate:8f}",
|
||||
"value3": ""
|
||||
},
|
||||
"webhookstatus": {
|
||||
"value1": "Status: {status}",
|
||||
"value2": "",
|
||||
@@ -49,7 +59,7 @@ def test__init__(mocker, default_conf):
|
||||
assert webhook._config == default_conf
|
||||
|
||||
|
||||
def test_send_msg(default_conf, mocker):
|
||||
def test_send_msg_webhook(default_conf, mocker):
|
||||
default_conf["webhook"] = get_webhook_dict()
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
@@ -58,8 +68,8 @@ def test_send_msg(default_conf, mocker):
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
msg = {
|
||||
'type': RPCMessageType.BUY_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'type': RPCMessageType.BUY,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'limit': 0.005,
|
||||
'stake_amount': 0.8,
|
||||
@@ -76,11 +86,11 @@ def test_send_msg(default_conf, mocker):
|
||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||
default_conf["webhook"]["webhookbuy"]["value3"].format(**msg))
|
||||
# Test buy cancel
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
msg_mock.reset_mock()
|
||||
|
||||
msg = {
|
||||
'type': RPCMessageType.BUY_CANCEL_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'type': RPCMessageType.BUY_CANCEL,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'limit': 0.005,
|
||||
'stake_amount': 0.8,
|
||||
@@ -96,12 +106,32 @@ def test_send_msg(default_conf, mocker):
|
||||
default_conf["webhook"]["webhookbuycancel"]["value2"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||
default_conf["webhook"]["webhookbuycancel"]["value3"].format(**msg))
|
||||
# Test sell
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
# Test buy fill
|
||||
msg_mock.reset_mock()
|
||||
|
||||
msg = {
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'type': RPCMessageType.BUY_FILL,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'open_rate': 0.005,
|
||||
'stake_amount': 0.8,
|
||||
'stake_amount_fiat': 500,
|
||||
'stake_currency': 'BTC',
|
||||
'fiat_currency': 'EUR'
|
||||
}
|
||||
webhook.send_msg(msg=msg)
|
||||
assert msg_mock.call_count == 1
|
||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||
default_conf["webhook"]["webhookbuyfill"]["value1"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||
default_conf["webhook"]["webhookbuyfill"]["value2"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||
default_conf["webhook"]["webhookbuyfill"]["value3"].format(**msg))
|
||||
# Test sell
|
||||
msg_mock.reset_mock()
|
||||
msg = {
|
||||
'type': RPCMessageType.SELL,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'gain': "profit",
|
||||
'limit': 0.005,
|
||||
@@ -123,11 +153,10 @@ def test_send_msg(default_conf, mocker):
|
||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||
default_conf["webhook"]["webhooksell"]["value3"].format(**msg))
|
||||
# Test sell cancel
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
msg_mock.reset_mock()
|
||||
msg = {
|
||||
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'type': RPCMessageType.SELL_CANCEL,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'gain': "profit",
|
||||
'limit': 0.005,
|
||||
@@ -148,9 +177,35 @@ def test_send_msg(default_conf, mocker):
|
||||
default_conf["webhook"]["webhooksellcancel"]["value2"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||
default_conf["webhook"]["webhooksellcancel"]["value3"].format(**msg))
|
||||
for msgtype in [RPCMessageType.STATUS_NOTIFICATION,
|
||||
RPCMessageType.WARNING_NOTIFICATION,
|
||||
RPCMessageType.STARTUP_NOTIFICATION]:
|
||||
# Test Sell fill
|
||||
msg_mock.reset_mock()
|
||||
msg = {
|
||||
'type': RPCMessageType.SELL_FILL,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'gain': "profit",
|
||||
'close_rate': 0.005,
|
||||
'amount': 0.8,
|
||||
'order_type': 'limit',
|
||||
'open_rate': 0.004,
|
||||
'current_rate': 0.005,
|
||||
'profit_amount': 0.001,
|
||||
'profit_ratio': 0.20,
|
||||
'stake_currency': 'BTC',
|
||||
'sell_reason': SellType.STOP_LOSS.value
|
||||
}
|
||||
webhook.send_msg(msg=msg)
|
||||
assert msg_mock.call_count == 1
|
||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||
default_conf["webhook"]["webhooksellfill"]["value1"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||
default_conf["webhook"]["webhooksellfill"]["value2"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||
default_conf["webhook"]["webhooksellfill"]["value3"].format(**msg))
|
||||
|
||||
for msgtype in [RPCMessageType.STATUS,
|
||||
RPCMessageType.WARNING,
|
||||
RPCMessageType.STARTUP]:
|
||||
# Test notification
|
||||
msg = {
|
||||
'type': msgtype,
|
||||
@@ -173,8 +228,8 @@ def test_exception_send_msg(default_conf, mocker, caplog):
|
||||
del default_conf["webhook"]["webhookbuy"]
|
||||
|
||||
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
||||
webhook.send_msg({'type': RPCMessageType.BUY_NOTIFICATION})
|
||||
assert log_has(f"Message type '{RPCMessageType.BUY_NOTIFICATION}' not configured for webhooks",
|
||||
webhook.send_msg({'type': RPCMessageType.BUY})
|
||||
assert log_has(f"Message type '{RPCMessageType.BUY}' not configured for webhooks",
|
||||
caplog)
|
||||
|
||||
default_conf["webhook"] = get_webhook_dict()
|
||||
@@ -183,8 +238,8 @@ def test_exception_send_msg(default_conf, mocker, caplog):
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
||||
msg = {
|
||||
'type': RPCMessageType.BUY_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'type': RPCMessageType.BUY,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'limit': 0.005,
|
||||
'order_type': 'limit',
|
||||
@@ -225,3 +280,15 @@ def test__send_msg(default_conf, mocker, caplog):
|
||||
mocker.patch("freqtrade.rpc.webhook.post", post)
|
||||
webhook._send_msg(msg)
|
||||
assert log_has('Could not call webhook url. Exception: ', caplog)
|
||||
|
||||
|
||||
def test__send_msg_with_json_format(default_conf, mocker, caplog):
|
||||
default_conf["webhook"] = get_webhook_dict()
|
||||
default_conf["webhook"]["format"] = "json"
|
||||
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
||||
msg = {'text': 'Hello'}
|
||||
post = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.post", post)
|
||||
webhook._send_msg(msg)
|
||||
|
||||
assert post.call_args[1] == {'json': msg}
|
||||
|
@@ -28,7 +28,7 @@ class DefaultStrategy(IStrategy):
|
||||
# Optimal stoploss designed for the strategy
|
||||
stoploss = -0.10
|
||||
|
||||
# Optimal ticker interval for the strategy
|
||||
# Optimal timeframe for the strategy
|
||||
timeframe = '5m'
|
||||
|
||||
# Optional order type mapping
|
||||
|
173
tests/strategy/strats/hyperoptable_strategy.py
Normal file
173
tests/strategy/strats/hyperoptable_strategy.py
Normal file
@@ -0,0 +1,173 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
from freqtrade.strategy import DecimalParameter, IntParameter, IStrategy, RealParameter
|
||||
|
||||
|
||||
class HyperoptableStrategy(IStrategy):
|
||||
"""
|
||||
Default Strategy provided by freqtrade bot.
|
||||
Please do not modify this strategy, it's intended for internal use only.
|
||||
Please look at the SampleStrategy in the user_data/strategy directory
|
||||
or strategy repository https://github.com/freqtrade/freqtrade-strategies
|
||||
for samples and inspiration.
|
||||
"""
|
||||
INTERFACE_VERSION = 2
|
||||
|
||||
# Minimal ROI designed for the strategy
|
||||
minimal_roi = {
|
||||
"40": 0.0,
|
||||
"30": 0.01,
|
||||
"20": 0.02,
|
||||
"0": 0.04
|
||||
}
|
||||
|
||||
# Optimal stoploss designed for the strategy
|
||||
stoploss = -0.10
|
||||
|
||||
# Optimal ticker interval for the strategy
|
||||
timeframe = '5m'
|
||||
|
||||
# Optional order type mapping
|
||||
order_types = {
|
||||
'buy': 'limit',
|
||||
'sell': 'limit',
|
||||
'stoploss': 'limit',
|
||||
'stoploss_on_exchange': False
|
||||
}
|
||||
|
||||
# Number of candles the strategy requires before producing valid signals
|
||||
startup_candle_count: int = 20
|
||||
|
||||
# Optional time in force for orders
|
||||
order_time_in_force = {
|
||||
'buy': 'gtc',
|
||||
'sell': 'gtc',
|
||||
}
|
||||
|
||||
buy_params = {
|
||||
'buy_rsi': 35,
|
||||
# Intentionally not specified, so "default" is tested
|
||||
# 'buy_plusdi': 0.4
|
||||
}
|
||||
|
||||
sell_params = {
|
||||
'sell_rsi': 74,
|
||||
'sell_minusdi': 0.4
|
||||
}
|
||||
|
||||
buy_rsi = IntParameter([0, 50], default=30, space='buy')
|
||||
buy_plusdi = RealParameter(low=0, high=1, default=0.5, space='buy')
|
||||
sell_rsi = IntParameter(low=50, high=100, default=70, space='sell')
|
||||
sell_minusdi = DecimalParameter(low=0, high=1, default=0.5001, decimals=3, space='sell',
|
||||
load=False)
|
||||
|
||||
def informative_pairs(self):
|
||||
"""
|
||||
Define additional, informative pair/interval combinations to be cached from the exchange.
|
||||
These pair/interval combinations are non-tradeable, unless they are part
|
||||
of the whitelist as well.
|
||||
For more information, please consult the documentation
|
||||
:return: List of tuples in the format (pair, interval)
|
||||
Sample: return [("ETH/USDT", "5m"),
|
||||
("BTC/USDT", "15m"),
|
||||
]
|
||||
"""
|
||||
return []
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
Adds several different TA indicators to the given DataFrame
|
||||
|
||||
Performance Note: For the best performance be frugal on the number of indicators
|
||||
you are using. Let uncomment only the indicator you are using in your strategies
|
||||
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
||||
:param dataframe: Dataframe with data from the exchange
|
||||
:param metadata: Additional information, like the currently traded pair
|
||||
:return: a Dataframe with all mandatory indicators for the strategies
|
||||
"""
|
||||
|
||||
# Momentum Indicator
|
||||
# ------------------------------------
|
||||
|
||||
# ADX
|
||||
dataframe['adx'] = ta.ADX(dataframe)
|
||||
|
||||
# MACD
|
||||
macd = ta.MACD(dataframe)
|
||||
dataframe['macd'] = macd['macd']
|
||||
dataframe['macdsignal'] = macd['macdsignal']
|
||||
dataframe['macdhist'] = macd['macdhist']
|
||||
|
||||
# Minus Directional Indicator / Movement
|
||||
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||
|
||||
# Plus Directional Indicator / Movement
|
||||
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
|
||||
|
||||
# RSI
|
||||
dataframe['rsi'] = ta.RSI(dataframe)
|
||||
|
||||
# Stoch fast
|
||||
stoch_fast = ta.STOCHF(dataframe)
|
||||
dataframe['fastd'] = stoch_fast['fastd']
|
||||
dataframe['fastk'] = stoch_fast['fastk']
|
||||
|
||||
# Bollinger bands
|
||||
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||
dataframe['bb_lowerband'] = bollinger['lower']
|
||||
dataframe['bb_middleband'] = bollinger['mid']
|
||||
dataframe['bb_upperband'] = bollinger['upper']
|
||||
|
||||
# EMA - Exponential Moving Average
|
||||
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
Based on TA indicators, populates the buy signal for the given dataframe
|
||||
:param dataframe: DataFrame
|
||||
:param metadata: Additional information, like the currently traded pair
|
||||
:return: DataFrame with buy column
|
||||
"""
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['rsi'] < self.buy_rsi.value) &
|
||||
(dataframe['fastd'] < 35) &
|
||||
(dataframe['adx'] > 30) &
|
||||
(dataframe['plus_di'] > self.buy_plusdi.value)
|
||||
) |
|
||||
(
|
||||
(dataframe['adx'] > 65) &
|
||||
(dataframe['plus_di'] > self.buy_plusdi.value)
|
||||
),
|
||||
'buy'] = 1
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
Based on TA indicators, populates the sell signal for the given dataframe
|
||||
:param dataframe: DataFrame
|
||||
:param metadata: Additional information, like the currently traded pair
|
||||
:return: DataFrame with buy column
|
||||
"""
|
||||
dataframe.loc[
|
||||
(
|
||||
(
|
||||
(qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) |
|
||||
(qtpylib.crossed_above(dataframe['fastd'], 70))
|
||||
) &
|
||||
(dataframe['adx'] > 10) &
|
||||
(dataframe['minus_di'] > 0)
|
||||
) |
|
||||
(
|
||||
(dataframe['adx'] > 70) &
|
||||
(dataframe['minus_di'] > self.sell_minusdi.value)
|
||||
),
|
||||
'sell'] = 1
|
||||
return dataframe
|
@@ -31,7 +31,7 @@ class TestStrategyLegacy(IStrategy):
|
||||
# This attribute will be overridden if the config file contains "stoploss"
|
||||
stoploss = -0.10
|
||||
|
||||
# Optimal ticker interval for the strategy
|
||||
# Optimal timeframe for the strategy
|
||||
# Keep the legacy value here to test compatibility
|
||||
ticker_interval = '5m'
|
||||
|
||||
|
@@ -36,9 +36,11 @@ def test_default_strategy(result, fee):
|
||||
)
|
||||
|
||||
assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1,
|
||||
rate=20000, time_in_force='gtc') is True
|
||||
rate=20000, time_in_force='gtc',
|
||||
current_time=datetime.utcnow()) is True
|
||||
assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1,
|
||||
rate=20000, time_in_force='gtc', sell_reason='roi') is True
|
||||
rate=20000, time_in_force='gtc', sell_reason='roi',
|
||||
current_time=datetime.utcnow()) is True
|
||||
|
||||
assert strategy.custom_stoploss(pair='ETH/BTC', trade=trade, current_time=datetime.now(),
|
||||
current_rate=20_000, current_profit=0.05) == strategy.stoploss
|
||||
|
@@ -10,10 +10,13 @@ from pandas import DataFrame
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.data.history import load_data
|
||||
from freqtrade.exceptions import StrategyError
|
||||
from freqtrade.enums import SellType
|
||||
from freqtrade.exceptions import OperationalException, StrategyError
|
||||
from freqtrade.persistence import PairLocks, Trade
|
||||
from freqtrade.resolvers import StrategyResolver
|
||||
from freqtrade.strategy.interface import SellCheckTuple, SellType
|
||||
from freqtrade.strategy.hyper import (BaseParameter, CategoricalParameter, DecimalParameter,
|
||||
IntParameter, RealParameter)
|
||||
from freqtrade.strategy.interface import SellCheckTuple
|
||||
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
||||
from tests.conftest import log_has, log_has_re
|
||||
|
||||
@@ -217,7 +220,7 @@ def test_min_roi_reached(default_conf, fee) -> None:
|
||||
open_date=arrow.utcnow().shift(hours=-1).datetime,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
open_rate=1,
|
||||
)
|
||||
|
||||
@@ -256,7 +259,7 @@ def test_min_roi_reached2(default_conf, fee) -> None:
|
||||
open_date=arrow.utcnow().shift(hours=-1).datetime,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
open_rate=1,
|
||||
)
|
||||
|
||||
@@ -291,7 +294,7 @@ def test_min_roi_reached3(default_conf, fee) -> None:
|
||||
open_date=arrow.utcnow().shift(hours=-1).datetime,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
open_rate=1,
|
||||
)
|
||||
|
||||
@@ -344,7 +347,7 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
|
||||
open_date=arrow.utcnow().shift(hours=-1).datetime,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
open_rate=1,
|
||||
)
|
||||
trade.adjust_min_max_rates(trade.open_rate)
|
||||
@@ -380,6 +383,50 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
|
||||
strategy.custom_stoploss = original_stopvalue
|
||||
|
||||
|
||||
def test_custom_sell(default_conf, fee, caplog) -> None:
|
||||
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.01,
|
||||
amount=1,
|
||||
open_date=arrow.utcnow().shift(hours=-1).datetime,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
open_rate=1,
|
||||
)
|
||||
|
||||
now = arrow.utcnow().datetime
|
||||
res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
|
||||
|
||||
assert res.sell_flag is False
|
||||
assert res.sell_type == SellType.NONE
|
||||
|
||||
strategy.custom_sell = MagicMock(return_value=True)
|
||||
res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
|
||||
assert res.sell_flag is True
|
||||
assert res.sell_type == SellType.CUSTOM_SELL
|
||||
assert res.sell_reason == 'custom_sell'
|
||||
|
||||
strategy.custom_sell = MagicMock(return_value='hello world')
|
||||
|
||||
res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
|
||||
assert res.sell_type == SellType.CUSTOM_SELL
|
||||
assert res.sell_flag is True
|
||||
assert res.sell_reason == 'hello world'
|
||||
|
||||
caplog.clear()
|
||||
strategy.custom_sell = MagicMock(return_value='h' * 100)
|
||||
res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
|
||||
assert res.sell_type == SellType.CUSTOM_SELL
|
||||
assert res.sell_flag is True
|
||||
assert res.sell_reason == 'h' * 64
|
||||
assert log_has_re('Custom sell reason returned from custom_sell is too long.*', caplog)
|
||||
|
||||
|
||||
def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
ind_mock = MagicMock(side_effect=lambda x, meta: x)
|
||||
@@ -552,3 +599,82 @@ def test_strategy_safe_wrapper(value):
|
||||
|
||||
assert type(ret) == type(value)
|
||||
assert ret == value
|
||||
|
||||
|
||||
def test_hyperopt_parameters():
|
||||
from skopt.space import Categorical, Integer, Real
|
||||
with pytest.raises(OperationalException, match=r"Name is determined.*"):
|
||||
IntParameter(low=0, high=5, default=1, name='hello')
|
||||
|
||||
with pytest.raises(OperationalException, match=r"IntParameter space must be.*"):
|
||||
IntParameter(low=0, default=5, space='buy')
|
||||
|
||||
with pytest.raises(OperationalException, match=r"RealParameter space must be.*"):
|
||||
RealParameter(low=0, default=5, space='buy')
|
||||
|
||||
with pytest.raises(OperationalException, match=r"DecimalParameter space must be.*"):
|
||||
DecimalParameter(low=0, default=5, space='buy')
|
||||
|
||||
with pytest.raises(OperationalException, match=r"IntParameter space invalid\."):
|
||||
IntParameter([0, 10], high=7, default=5, space='buy')
|
||||
|
||||
with pytest.raises(OperationalException, match=r"RealParameter space invalid\."):
|
||||
RealParameter([0, 10], high=7, default=5, space='buy')
|
||||
|
||||
with pytest.raises(OperationalException, match=r"DecimalParameter space invalid\."):
|
||||
DecimalParameter([0, 10], high=7, default=5, space='buy')
|
||||
|
||||
with pytest.raises(OperationalException, match=r"CategoricalParameter space must.*"):
|
||||
CategoricalParameter(['aa'], default='aa', space='buy')
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
BaseParameter(opt_range=[0, 1], default=1, space='buy')
|
||||
|
||||
intpar = IntParameter(low=0, high=5, default=1, space='buy')
|
||||
assert intpar.value == 1
|
||||
assert isinstance(intpar.get_space(''), Integer)
|
||||
assert isinstance(intpar.range, range)
|
||||
assert len(list(intpar.range)) == 1
|
||||
# Range contains ONLY the default / value.
|
||||
assert list(intpar.range) == [intpar.value]
|
||||
intpar.in_space = True
|
||||
|
||||
assert len(list(intpar.range)) == 6
|
||||
assert list(intpar.range) == [0, 1, 2, 3, 4, 5]
|
||||
|
||||
fltpar = RealParameter(low=0.0, high=5.5, default=1.0, space='buy')
|
||||
assert isinstance(fltpar.get_space(''), Real)
|
||||
assert fltpar.value == 1
|
||||
|
||||
fltpar = DecimalParameter(low=0.0, high=5.5, default=1.0004, decimals=3, space='buy')
|
||||
assert isinstance(fltpar.get_space(''), Integer)
|
||||
assert fltpar.value == 1
|
||||
|
||||
catpar = CategoricalParameter(['buy_rsi', 'buy_macd', 'buy_none'],
|
||||
default='buy_macd', space='buy')
|
||||
assert isinstance(catpar.get_space(''), Categorical)
|
||||
assert catpar.value == 'buy_macd'
|
||||
|
||||
|
||||
def test_auto_hyperopt_interface(default_conf):
|
||||
default_conf.update({'strategy': 'HyperoptableStrategy'})
|
||||
PairLocks.timeframe = default_conf['timeframe']
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
assert strategy.buy_rsi.value == strategy.buy_params['buy_rsi']
|
||||
# PlusDI is NOT in the buy-params, so default should be used
|
||||
assert strategy.buy_plusdi.value == 0.5
|
||||
assert strategy.sell_rsi.value == strategy.sell_params['sell_rsi']
|
||||
|
||||
# Parameter is disabled - so value from sell_param dict will NOT be used.
|
||||
assert strategy.sell_minusdi.value == 0.5
|
||||
all_params = strategy.detect_all_parameters()
|
||||
assert isinstance(all_params, dict)
|
||||
assert len(all_params['buy']) == 2
|
||||
assert len(all_params['sell']) == 2
|
||||
assert all_params['count'] == 4
|
||||
|
||||
strategy.__class__.sell_rsi = IntParameter([0, 10], default=5, space='buy')
|
||||
|
||||
with pytest.raises(OperationalException, match=r"Inconclusive parameter.*"):
|
||||
[x for x in strategy.detect_parameters('sell')]
|
||||
|
@@ -1,8 +1,10 @@
|
||||
from math import isclose
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from freqtrade.strategy import merge_informative_pair, timeframe_to_minutes
|
||||
from freqtrade.strategy import merge_informative_pair, stoploss_from_open, timeframe_to_minutes
|
||||
|
||||
|
||||
def generate_test_data(timeframe: str, size: int):
|
||||
@@ -95,3 +97,38 @@ def test_merge_informative_pair_lower():
|
||||
|
||||
with pytest.raises(ValueError, match=r"Tried to merge a faster timeframe .*"):
|
||||
merge_informative_pair(data, informative, '1h', '15m', ffill=True)
|
||||
|
||||
|
||||
def test_stoploss_from_open():
|
||||
open_price_ranges = [
|
||||
[0.01, 1.00, 30],
|
||||
[1, 100, 30],
|
||||
[100, 10000, 30],
|
||||
]
|
||||
current_profit_range = [-0.99, 2, 30]
|
||||
desired_stop_range = [-0.50, 0.50, 30]
|
||||
|
||||
for open_range in open_price_ranges:
|
||||
for open_price in np.linspace(*open_range):
|
||||
for desired_stop in np.linspace(*desired_stop_range):
|
||||
|
||||
# -1 is not a valid current_profit, should return 1
|
||||
assert stoploss_from_open(desired_stop, -1) == 1
|
||||
|
||||
for current_profit in np.linspace(*current_profit_range):
|
||||
current_price = open_price * (1 + current_profit)
|
||||
expected_stop_price = open_price * (1 + desired_stop)
|
||||
|
||||
stoploss = stoploss_from_open(desired_stop, current_profit)
|
||||
|
||||
assert stoploss >= 0
|
||||
assert stoploss <= 1
|
||||
|
||||
stop_price = current_price * (1 - stoploss)
|
||||
|
||||
# there is no correct answer if the expected stop price is above
|
||||
# the current price
|
||||
if expected_stop_price > current_price:
|
||||
assert stoploss == 0
|
||||
else:
|
||||
assert isclose(stop_price, expected_stop_price, rel_tol=0.00001)
|
||||
|
@@ -35,7 +35,7 @@ def test_search_all_strategies_no_failed():
|
||||
directory = Path(__file__).parent / "strats"
|
||||
strategies = StrategyResolver.search_all_objects(directory, enum_failed=False)
|
||||
assert isinstance(strategies, list)
|
||||
assert len(strategies) == 2
|
||||
assert len(strategies) == 3
|
||||
assert isinstance(strategies[0], dict)
|
||||
|
||||
|
||||
@@ -43,10 +43,10 @@ def test_search_all_strategies_with_failed():
|
||||
directory = Path(__file__).parent / "strats"
|
||||
strategies = StrategyResolver.search_all_objects(directory, enum_failed=True)
|
||||
assert isinstance(strategies, list)
|
||||
assert len(strategies) == 3
|
||||
assert len(strategies) == 4
|
||||
# with enum_failed=True search_all_objects() shall find 2 good strategies
|
||||
# and 1 which fails to load
|
||||
assert len([x for x in strategies if x['class'] is not None]) == 2
|
||||
assert len([x for x in strategies if x['class'] is not None]) == 3
|
||||
assert len([x for x in strategies if x['class'] is None]) == 1
|
||||
|
||||
|
||||
@@ -129,13 +129,16 @@ def test_strategy_override_minimal_roi(caplog, default_conf):
|
||||
default_conf.update({
|
||||
'strategy': 'DefaultStrategy',
|
||||
'minimal_roi': {
|
||||
"20": 0.1,
|
||||
"0": 0.5
|
||||
}
|
||||
})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
assert strategy.minimal_roi[0] == 0.5
|
||||
assert log_has("Override strategy 'minimal_roi' with value in config file: {'0': 0.5}.", caplog)
|
||||
assert log_has(
|
||||
"Override strategy 'minimal_roi' with value in config file: {'20': 0.1, '0': 0.5}.",
|
||||
caplog)
|
||||
|
||||
|
||||
def test_strategy_override_stoploss(caplog, default_conf):
|
||||
|
@@ -186,18 +186,22 @@ def test_plot_dataframe_options() -> None:
|
||||
assert pargs['pairs'] == ['UNITTEST/BTC']
|
||||
|
||||
|
||||
def test_plot_profit_options() -> None:
|
||||
@pytest.mark.parametrize('auto_open_arg', [True, False])
|
||||
def test_plot_profit_options(auto_open_arg: bool) -> None:
|
||||
args = [
|
||||
'plot-profit',
|
||||
'-p', 'UNITTEST/BTC',
|
||||
'--trade-source', 'DB',
|
||||
'--db-url', 'sqlite:///whatever.sqlite',
|
||||
]
|
||||
if auto_open_arg:
|
||||
args.append('--auto-open')
|
||||
pargs = Arguments(args).get_parsed_arg()
|
||||
|
||||
assert pargs['trade_source'] == 'DB'
|
||||
assert pargs['pairs'] == ['UNITTEST/BTC']
|
||||
assert pargs['db_url'] == 'sqlite:///whatever.sqlite'
|
||||
assert pargs['plot_auto_open'] == auto_open_arg
|
||||
|
||||
|
||||
def test_config_notallowed(mocker) -> None:
|
||||
|
@@ -18,11 +18,11 @@ from freqtrade.configuration.deprecated_settings import (check_conflicting_setti
|
||||
process_deprecated_setting,
|
||||
process_removed_setting,
|
||||
process_temporary_deprecated_settings)
|
||||
from freqtrade.configuration.load_config import load_config_file, log_config_error_range
|
||||
from freqtrade.configuration.load_config import load_config_file, load_file, log_config_error_range
|
||||
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL
|
||||
from freqtrade.enums import RunMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.loggers import _set_loggers, setup_logging, setup_logging_pre
|
||||
from freqtrade.state import RunMode
|
||||
from tests.conftest import log_has, log_has_re, patched_configuration_load_config_file
|
||||
|
||||
|
||||
@@ -88,6 +88,24 @@ def test_load_config_file_error_range(default_conf, mocker, caplog) -> None:
|
||||
'"stake_amount": .001, "fiat_display_currency": "USD", '
|
||||
'"timeframe": "5m", "dry_run": true, "cance')
|
||||
|
||||
filedata = json.dumps(default_conf, indent=2).replace(
|
||||
'"stake_amount": 0.001,', '"stake_amount": .001,')
|
||||
mocker.patch.object(Path, "read_text", MagicMock(return_value=filedata))
|
||||
|
||||
x = log_config_error_range('somefile', 'Parse error at offset 4: Invalid value.')
|
||||
assert isinstance(x, str)
|
||||
assert (x == ' "max_open_trades": 1,\n "stake_currency": "BTC",\n'
|
||||
' "stake_amount": .001,')
|
||||
|
||||
x = log_config_error_range('-', '')
|
||||
assert x == ''
|
||||
|
||||
|
||||
def test_load_file_error(tmpdir):
|
||||
testpath = Path(tmpdir) / 'config.json'
|
||||
with pytest.raises(OperationalException, match=r"File .* not found!"):
|
||||
load_file(testpath)
|
||||
|
||||
|
||||
def test__args_to_config(caplog):
|
||||
|
||||
@@ -430,7 +448,8 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
||||
'--enable-position-stacking',
|
||||
'--disable-max-market-positions',
|
||||
'--timerange', ':100',
|
||||
'--export', '/bar/foo'
|
||||
'--export', '/bar/foo',
|
||||
'--stake-amount', 'unlimited'
|
||||
]
|
||||
|
||||
args = Arguments(arglist).get_parsed_arg()
|
||||
@@ -463,6 +482,8 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
||||
|
||||
assert 'export' in config
|
||||
assert log_has('Parameter --export detected: {} ...'.format(config['export']), caplog)
|
||||
assert 'stake_amount' in config
|
||||
assert config['stake_amount'] == 'unlimited'
|
||||
|
||||
|
||||
def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> None:
|
||||
@@ -562,7 +583,7 @@ def test_check_exchange(default_conf, caplog) -> None:
|
||||
# Test a 'bad' exchange, which known to have serious problems
|
||||
default_conf.get('exchange').update({'name': 'bitmex'})
|
||||
with pytest.raises(OperationalException,
|
||||
match=r"Exchange .* is known to not work with the bot yet.*"):
|
||||
match=r"Exchange .* will not work with Freqtrade\..*"):
|
||||
check_exchange(default_conf)
|
||||
caplog.clear()
|
||||
|
||||
@@ -787,6 +808,38 @@ def test_validate_max_open_trades(default_conf):
|
||||
validate_config_consistency(default_conf)
|
||||
|
||||
|
||||
def test_validate_price_side(default_conf):
|
||||
default_conf['order_types'] = {
|
||||
"buy": "limit",
|
||||
"sell": "limit",
|
||||
"stoploss": "limit",
|
||||
"stoploss_on_exchange": False,
|
||||
}
|
||||
# Default should pass
|
||||
validate_config_consistency(default_conf)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
conf['order_types']['buy'] = 'market'
|
||||
with pytest.raises(OperationalException,
|
||||
match='Market buy orders require bid_strategy.price_side = "ask".'):
|
||||
validate_config_consistency(conf)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
conf['order_types']['sell'] = 'market'
|
||||
with pytest.raises(OperationalException,
|
||||
match='Market sell orders require ask_strategy.price_side = "bid".'):
|
||||
validate_config_consistency(conf)
|
||||
|
||||
# Validate inversed case
|
||||
conf = deepcopy(default_conf)
|
||||
conf['order_types']['sell'] = 'market'
|
||||
conf['order_types']['buy'] = 'market'
|
||||
conf['ask_strategy']['price_side'] = 'bid'
|
||||
conf['bid_strategy']['price_side'] = 'ask'
|
||||
|
||||
validate_config_consistency(conf)
|
||||
|
||||
|
||||
def test_validate_tsl(default_conf):
|
||||
default_conf['stoploss'] = 0.0
|
||||
with pytest.raises(OperationalException, match='The config stoploss needs to be different '
|
||||
@@ -825,22 +878,6 @@ def test_validate_tsl(default_conf):
|
||||
validate_config_consistency(default_conf)
|
||||
|
||||
|
||||
def test_validate_edge(edge_conf):
|
||||
edge_conf.update({"pairlist": {
|
||||
"method": "VolumePairList",
|
||||
}})
|
||||
|
||||
with pytest.raises(OperationalException,
|
||||
match="Edge and VolumePairList are incompatible, "
|
||||
"Edge will override whatever pairs VolumePairlist selects."):
|
||||
validate_config_consistency(edge_conf)
|
||||
|
||||
edge_conf.update({"pairlist": {
|
||||
"method": "StaticPairList",
|
||||
}})
|
||||
validate_config_consistency(edge_conf)
|
||||
|
||||
|
||||
def test_validate_edge2(edge_conf):
|
||||
edge_conf.update({"ask_strategy": {
|
||||
"use_sell_signal": True,
|
||||
@@ -983,6 +1020,7 @@ def test_pairlist_resolving():
|
||||
config = configuration.get_config()
|
||||
|
||||
assert config['pairs'] == ['ETH/BTC', 'XRP/BTC']
|
||||
assert config['exchange']['pair_whitelist'] == ['ETH/BTC', 'XRP/BTC']
|
||||
assert config['exchange']['name'] == 'binance'
|
||||
|
||||
|
||||
@@ -1019,37 +1057,30 @@ def test_pairlist_resolving_with_config(mocker, default_conf):
|
||||
|
||||
def test_pairlist_resolving_with_config_pl(mocker, default_conf):
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
load_mock = mocker.patch("freqtrade.configuration.configuration.json_load",
|
||||
MagicMock(return_value=['XRP/BTC', 'ETH/BTC']))
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||
mocker.patch.object(Path, "open", MagicMock(return_value=MagicMock()))
|
||||
|
||||
arglist = [
|
||||
'download-data',
|
||||
'--config', 'config.json',
|
||||
'--pairs-file', 'pairs.json',
|
||||
'--pairs-file', 'tests/testdata/pairs.json',
|
||||
]
|
||||
|
||||
args = Arguments(arglist).get_parsed_arg()
|
||||
|
||||
configuration = Configuration(args)
|
||||
config = configuration.get_config()
|
||||
|
||||
assert load_mock.call_count == 1
|
||||
assert config['pairs'] == ['ETH/BTC', 'XRP/BTC']
|
||||
assert len(config['pairs']) == 23
|
||||
assert 'ETH/BTC' in config['pairs']
|
||||
assert 'XRP/BTC' in config['pairs']
|
||||
assert config['exchange']['name'] == default_conf['exchange']['name']
|
||||
|
||||
|
||||
def test_pairlist_resolving_with_config_pl_not_exists(mocker, default_conf):
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
mocker.patch("freqtrade.configuration.configuration.json_load",
|
||||
MagicMock(return_value=['XRP/BTC', 'ETH/BTC']))
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
|
||||
|
||||
arglist = [
|
||||
'download-data',
|
||||
'--config', 'config.json',
|
||||
'--pairs-file', 'pairs.json',
|
||||
'--pairs-file', 'tests/testdata/pairs_doesnotexist.json',
|
||||
]
|
||||
|
||||
args = Arguments(arglist).get_parsed_arg()
|
||||
@@ -1062,7 +1093,7 @@ def test_pairlist_resolving_with_config_pl_not_exists(mocker, default_conf):
|
||||
def test_pairlist_resolving_fallback(mocker):
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||
mocker.patch.object(Path, "open", MagicMock(return_value=MagicMock()))
|
||||
mocker.patch("freqtrade.configuration.configuration.json_load",
|
||||
mocker.patch("freqtrade.configuration.configuration.load_file",
|
||||
MagicMock(return_value=['XRP/BTC', 'ETH/BTC']))
|
||||
arglist = [
|
||||
'download-data',
|
||||
|
@@ -1,11 +1,12 @@
|
||||
# pragma pylint: disable=missing-docstring, protected-access, invalid-name
|
||||
import os
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.configuration.directory_operations import (copy_sample_files, create_datadir,
|
||||
create_userdata_dir)
|
||||
from freqtrade.configuration.directory_operations import (chown_user_directory, copy_sample_files,
|
||||
create_datadir, create_userdata_dir)
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from tests.conftest import log_has, log_has_re
|
||||
|
||||
@@ -31,6 +32,24 @@ def test_create_userdata_dir(mocker, default_conf, caplog) -> None:
|
||||
assert str(x) == str(Path("/tmp/bar"))
|
||||
|
||||
|
||||
def test_create_userdata_dir_and_chown(mocker, tmpdir, caplog) -> None:
|
||||
sp_mock = mocker.patch('subprocess.check_output')
|
||||
path = Path(tmpdir / 'bar')
|
||||
assert not path.is_dir()
|
||||
|
||||
x = create_userdata_dir(str(path), create_dir=True)
|
||||
assert sp_mock.call_count == 0
|
||||
assert log_has(f'Created user-data directory: {path}', caplog)
|
||||
assert isinstance(x, Path)
|
||||
assert path.is_dir()
|
||||
assert (path / 'data').is_dir()
|
||||
|
||||
os.environ['FT_APP_ENV'] = 'docker'
|
||||
chown_user_directory(path / 'data')
|
||||
assert sp_mock.call_count == 1
|
||||
del os.environ['FT_APP_ENV']
|
||||
|
||||
|
||||
def test_create_userdata_dir_exists(mocker, default_conf, caplog) -> None:
|
||||
mocker.patch.object(Path, "is_dir", MagicMock(return_value=True))
|
||||
md = mocker.patch.object(Path, 'mkdir', MagicMock())
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -2,9 +2,10 @@ from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.enums import SellType
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.rpc.rpc import RPC
|
||||
from freqtrade.strategy.interface import SellCheckTuple, SellType
|
||||
from freqtrade.strategy.interface import SellCheckTuple
|
||||
from tests.conftest import get_patched_freqtradebot, patch_get_signal
|
||||
|
||||
|
||||
@@ -51,8 +52,8 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open])
|
||||
# Sell 3rd trade (not called for the first trade)
|
||||
should_sell_mock = MagicMock(side_effect=[
|
||||
SellCheckTuple(sell_flag=False, sell_type=SellType.NONE),
|
||||
SellCheckTuple(sell_flag=True, sell_type=SellType.SELL_SIGNAL)]
|
||||
SellCheckTuple(sell_type=SellType.NONE),
|
||||
SellCheckTuple(sell_type=SellType.SELL_SIGNAL)]
|
||||
)
|
||||
cancel_order_mock = MagicMock()
|
||||
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
|
||||
@@ -63,7 +64,7 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
amount_to_precision=lambda s, x, y: y,
|
||||
price_to_precision=lambda s, x, y: y,
|
||||
fetch_stoploss_order=stoploss_order_mock,
|
||||
cancel_stoploss_order=cancel_order_mock,
|
||||
cancel_stoploss_order_with_result=cancel_order_mock,
|
||||
)
|
||||
|
||||
mocker.patch.multiple(
|
||||
@@ -89,7 +90,6 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
freqtrade.strategy.confirm_trade_entry.reset_mock()
|
||||
assert freqtrade.strategy.confirm_trade_exit.call_count == 0
|
||||
wallets_mock.reset_mock()
|
||||
Trade.session = MagicMock()
|
||||
|
||||
trades = Trade.query.all()
|
||||
# Make sure stoploss-order is open and trade is bought (since we mock update_trade_state)
|
||||
@@ -157,11 +157,11 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
|
||||
_notify_sell=MagicMock(),
|
||||
)
|
||||
should_sell_mock = MagicMock(side_effect=[
|
||||
SellCheckTuple(sell_flag=False, sell_type=SellType.NONE),
|
||||
SellCheckTuple(sell_flag=True, sell_type=SellType.SELL_SIGNAL),
|
||||
SellCheckTuple(sell_flag=False, sell_type=SellType.NONE),
|
||||
SellCheckTuple(sell_flag=False, sell_type=SellType.NONE),
|
||||
SellCheckTuple(sell_flag=None, sell_type=SellType.NONE)]
|
||||
SellCheckTuple(sell_type=SellType.NONE),
|
||||
SellCheckTuple(sell_type=SellType.SELL_SIGNAL),
|
||||
SellCheckTuple(sell_type=SellType.NONE),
|
||||
SellCheckTuple(sell_type=SellType.NONE),
|
||||
SellCheckTuple(sell_type=SellType.NONE)]
|
||||
)
|
||||
mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock)
|
||||
|
||||
@@ -178,8 +178,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
|
||||
|
||||
trades = Trade.query.all()
|
||||
assert len(trades) == 4
|
||||
assert freqtrade.wallets.get_trade_stake_amount(
|
||||
'XRP/BTC', freqtrade.get_free_open_trades()) == result1
|
||||
assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC') == result1
|
||||
|
||||
rpc._rpc_forcebuy('TKN/BTC', None)
|
||||
|
||||
@@ -200,8 +199,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
|
||||
# One trade sold
|
||||
assert len(trades) == 4
|
||||
# stake-amount should now be reduced, since one trade was sold at a loss.
|
||||
assert freqtrade.wallets.get_trade_stake_amount(
|
||||
'XRP/BTC', freqtrade.get_free_open_trades()) < result1
|
||||
assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC') < result1
|
||||
# Validate that balance of sold trade is not in dry-run balances anymore.
|
||||
bals2 = freqtrade.wallets.get_all_balances()
|
||||
assert bals != bals2
|
||||
|
@@ -7,10 +7,10 @@ from unittest.mock import MagicMock, PropertyMock
|
||||
import pytest
|
||||
|
||||
from freqtrade.commands import Arguments
|
||||
from freqtrade.enums import State
|
||||
from freqtrade.exceptions import FreqtradeException, OperationalException
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
from freqtrade.main import main
|
||||
from freqtrade.state import State
|
||||
from freqtrade.worker import Worker
|
||||
from tests.conftest import (log_has, log_has_re, patch_exchange,
|
||||
patched_configuration_load_config_file)
|
||||
@@ -118,7 +118,7 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None:
|
||||
def test_main_operational_exception1(mocker, default_conf, caplog) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch(
|
||||
'freqtrade.commands.list_commands.available_exchanges',
|
||||
'freqtrade.commands.list_commands.validate_exchanges',
|
||||
MagicMock(side_effect=ValueError('Oh snap!'))
|
||||
)
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
@@ -132,7 +132,7 @@ def test_main_operational_exception1(mocker, default_conf, caplog) -> None:
|
||||
assert log_has('Fatal exception!', caplog)
|
||||
assert not log_has_re(r'SIGINT.*', caplog)
|
||||
mocker.patch(
|
||||
'freqtrade.commands.list_commands.available_exchanges',
|
||||
'freqtrade.commands.list_commands.validate_exchanges',
|
||||
MagicMock(side_effect=KeyboardInterrupt)
|
||||
)
|
||||
with pytest.raises(SystemExit):
|
||||
|
@@ -1,32 +1,36 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
import logging
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from types import FunctionType
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import arrow
|
||||
import pytest
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy import create_engine, inspect, text
|
||||
|
||||
from freqtrade import constants
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
from freqtrade.persistence import Order, Trade, clean_dry_run_db, init_db
|
||||
from freqtrade.persistence import LocalTrade, Order, Trade, clean_dry_run_db, init_db
|
||||
from tests.conftest import create_mock_trades, log_has, log_has_re
|
||||
|
||||
|
||||
def test_init_create_session(default_conf):
|
||||
# Check if init create a session
|
||||
init_db(default_conf['db_url'], default_conf['dry_run'])
|
||||
assert hasattr(Trade, 'session')
|
||||
assert 'scoped_session' in type(Trade.session).__name__
|
||||
assert hasattr(Trade, '_session')
|
||||
assert 'scoped_session' in type(Trade._session).__name__
|
||||
|
||||
|
||||
def test_init_custom_db_url(default_conf, mocker):
|
||||
def test_init_custom_db_url(default_conf, tmpdir):
|
||||
# Update path to a value other than default, but still in-memory
|
||||
default_conf.update({'db_url': 'sqlite:///tmp/freqtrade2_test.sqlite'})
|
||||
create_engine_mock = mocker.patch('freqtrade.persistence.models.create_engine', MagicMock())
|
||||
filename = f"{tmpdir}/freqtrade2_test.sqlite"
|
||||
assert not Path(filename).is_file()
|
||||
|
||||
default_conf.update({'db_url': f'sqlite:///{filename}'})
|
||||
|
||||
init_db(default_conf['db_url'], default_conf['dry_run'])
|
||||
assert create_engine_mock.call_count == 1
|
||||
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tmp/freqtrade2_test.sqlite'
|
||||
assert Path(filename).is_file()
|
||||
|
||||
|
||||
def test_init_invalid_db_url(default_conf):
|
||||
@@ -47,19 +51,20 @@ def test_init_prod_db(default_conf, mocker):
|
||||
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tradesv3.sqlite'
|
||||
|
||||
|
||||
def test_init_dryrun_db(default_conf, mocker):
|
||||
default_conf.update({'dry_run': True})
|
||||
default_conf.update({'db_url': constants.DEFAULT_DB_DRYRUN_URL})
|
||||
|
||||
create_engine_mock = mocker.patch('freqtrade.persistence.models.create_engine', MagicMock())
|
||||
def test_init_dryrun_db(default_conf, tmpdir):
|
||||
filename = f"{tmpdir}/freqtrade2_prod.sqlite"
|
||||
assert not Path(filename).is_file()
|
||||
default_conf.update({
|
||||
'dry_run': True,
|
||||
'db_url': f'sqlite:///{filename}'
|
||||
})
|
||||
|
||||
init_db(default_conf['db_url'], default_conf['dry_run'])
|
||||
assert create_engine_mock.call_count == 1
|
||||
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tradesv3.dryrun.sqlite'
|
||||
assert Path(filename).is_file()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee, caplog):
|
||||
def test_update_with_binance(limit_buy_order, limit_sell_order, fee, caplog):
|
||||
"""
|
||||
On this test we will buy and sell a crypto currency.
|
||||
|
||||
@@ -97,7 +102,7 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee, caplog):
|
||||
open_date=arrow.utcnow().datetime,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
)
|
||||
assert trade.open_order_id is None
|
||||
assert trade.close_profit is None
|
||||
@@ -137,7 +142,7 @@ def test_update_market_order(market_buy_order, market_sell_order, fee, caplog):
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_date=arrow.utcnow().datetime,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
)
|
||||
|
||||
trade.open_order_id = 'something'
|
||||
@@ -172,7 +177,7 @@ def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee):
|
||||
amount=5,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
)
|
||||
|
||||
trade.open_order_id = 'something'
|
||||
@@ -200,7 +205,7 @@ def test_trade_close(limit_buy_order, limit_sell_order, fee):
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_date=arrow.Arrow(2020, 2, 1, 15, 5, 1).datetime,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
)
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
@@ -228,7 +233,7 @@ def test_calc_close_trade_price_exception(limit_buy_order, fee):
|
||||
amount=5,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
)
|
||||
|
||||
trade.open_order_id = 'something'
|
||||
@@ -245,7 +250,7 @@ def test_update_open_order(limit_buy_order):
|
||||
amount=5,
|
||||
fee_open=0.1,
|
||||
fee_close=0.1,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
)
|
||||
|
||||
assert trade.open_order_id is None
|
||||
@@ -269,7 +274,7 @@ def test_update_invalid_order(limit_buy_order):
|
||||
open_rate=0.001,
|
||||
fee_open=0.1,
|
||||
fee_close=0.1,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
)
|
||||
limit_buy_order['type'] = 'invalid'
|
||||
with pytest.raises(ValueError, match=r'Unknown order type'):
|
||||
@@ -285,7 +290,7 @@ def test_calc_open_trade_value(limit_buy_order, fee):
|
||||
open_rate=0.00001099,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
)
|
||||
trade.open_order_id = 'open_trade'
|
||||
trade.update(limit_buy_order) # Buy @ 0.00001099
|
||||
@@ -306,7 +311,7 @@ def test_calc_close_trade_price(limit_buy_order, limit_sell_order, fee):
|
||||
open_rate=0.00001099,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
)
|
||||
trade.open_order_id = 'close_trade'
|
||||
trade.update(limit_buy_order) # Buy @ 0.00001099
|
||||
@@ -331,7 +336,7 @@ def test_calc_profit(limit_buy_order, limit_sell_order, fee):
|
||||
open_rate=0.00001099,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
)
|
||||
trade.open_order_id = 'something'
|
||||
trade.update(limit_buy_order) # Buy @ 0.00001099
|
||||
@@ -365,7 +370,7 @@ def test_calc_profit_ratio(limit_buy_order, limit_sell_order, fee):
|
||||
open_rate=0.00001099,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
)
|
||||
trade.open_order_id = 'something'
|
||||
trade.update(limit_buy_order) # Buy @ 0.00001099
|
||||
@@ -383,6 +388,9 @@ def test_calc_profit_ratio(limit_buy_order, limit_sell_order, fee):
|
||||
# Test with a custom fee rate on the close trade
|
||||
assert trade.calc_profit_ratio(fee=0.003) == 0.06147824
|
||||
|
||||
trade.open_trade_value = 0.0
|
||||
assert trade.calc_profit_ratio(fee=0.003) == 0.0
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_clean_dry_run_db(default_conf, fee):
|
||||
@@ -395,10 +403,10 @@ def test_clean_dry_run_db(default_conf, fee):
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.123,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
open_order_id='dry_run_buy_12345'
|
||||
)
|
||||
Trade.session.add(trade)
|
||||
Trade.query.session.add(trade)
|
||||
|
||||
trade = Trade(
|
||||
pair='ETC/BTC',
|
||||
@@ -407,10 +415,10 @@ def test_clean_dry_run_db(default_conf, fee):
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.123,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
open_order_id='dry_run_sell_12345'
|
||||
)
|
||||
Trade.session.add(trade)
|
||||
Trade.query.session.add(trade)
|
||||
|
||||
# Simulate prod entry
|
||||
trade = Trade(
|
||||
@@ -420,10 +428,10 @@ def test_clean_dry_run_db(default_conf, fee):
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.123,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
open_order_id='prod_buy_12345'
|
||||
)
|
||||
Trade.session.add(trade)
|
||||
Trade.query.session.add(trade)
|
||||
|
||||
# We have 3 entries: 2 dry_run, 1 prod
|
||||
assert len(Trade.query.filter(Trade.open_order_id.isnot(None)).all()) == 3
|
||||
@@ -458,7 +466,7 @@ def test_migrate_old(mocker, default_conf, fee):
|
||||
);"""
|
||||
insert_table_old = """INSERT INTO trades (exchange, pair, is_open, open_order_id, fee,
|
||||
open_rate, stake_amount, amount, open_date)
|
||||
VALUES ('BITTREX', 'BTC_ETC', 1, '123123', {fee},
|
||||
VALUES ('binance', 'BTC_ETC', 1, '123123', {fee},
|
||||
0.00258580, {stake}, {amount},
|
||||
'2017-11-28 12:44:24.000000')
|
||||
""".format(fee=fee.return_value,
|
||||
@@ -467,7 +475,7 @@ def test_migrate_old(mocker, default_conf, fee):
|
||||
)
|
||||
insert_table_old2 = """INSERT INTO trades (exchange, pair, is_open, fee,
|
||||
open_rate, close_rate, stake_amount, amount, open_date)
|
||||
VALUES ('BITTREX', 'BTC_ETC', 0, {fee},
|
||||
VALUES ('binance', 'BTC_ETC', 0, {fee},
|
||||
0.00258580, 0.00268580, {stake}, {amount},
|
||||
'2017-11-28 12:44:24.000000')
|
||||
""".format(fee=fee.return_value,
|
||||
@@ -478,9 +486,10 @@ def test_migrate_old(mocker, default_conf, fee):
|
||||
mocker.patch('freqtrade.persistence.models.create_engine', lambda *args, **kwargs: engine)
|
||||
|
||||
# Create table using the old format
|
||||
engine.execute(create_table_old)
|
||||
engine.execute(insert_table_old)
|
||||
engine.execute(insert_table_old2)
|
||||
with engine.begin() as connection:
|
||||
connection.execute(text(create_table_old))
|
||||
connection.execute(text(insert_table_old))
|
||||
connection.execute(text(insert_table_old2))
|
||||
# Run init to test migration
|
||||
init_db(default_conf['db_url'], default_conf['dry_run'])
|
||||
|
||||
@@ -495,7 +504,7 @@ def test_migrate_old(mocker, default_conf, fee):
|
||||
assert trade.amount_requested == amount
|
||||
assert trade.stake_amount == default_conf.get("stake_amount")
|
||||
assert trade.pair == "ETC/BTC"
|
||||
assert trade.exchange == "bittrex"
|
||||
assert trade.exchange == "binance"
|
||||
assert trade.max_rate == 0.0
|
||||
assert trade.stop_loss == 0.0
|
||||
assert trade.initial_stop_loss == 0.0
|
||||
@@ -571,15 +580,16 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
mocker.patch('freqtrade.persistence.models.create_engine', lambda *args, **kwargs: engine)
|
||||
|
||||
# Create table using the old format
|
||||
engine.execute(create_table_old)
|
||||
engine.execute("create index ix_trades_is_open on trades(is_open)")
|
||||
engine.execute("create index ix_trades_pair on trades(pair)")
|
||||
engine.execute(insert_table_old)
|
||||
with engine.begin() as connection:
|
||||
connection.execute(text(create_table_old))
|
||||
connection.execute(text("create index ix_trades_is_open on trades(is_open)"))
|
||||
connection.execute(text("create index ix_trades_pair on trades(pair)"))
|
||||
connection.execute(text(insert_table_old))
|
||||
|
||||
# fake previous backup
|
||||
engine.execute("create table trades_bak as select * from trades")
|
||||
# fake previous backup
|
||||
connection.execute(text("create table trades_bak as select * from trades"))
|
||||
|
||||
engine.execute("create table trades_bak1 as select * from trades")
|
||||
connection.execute(text("create table trades_bak1 as select * from trades"))
|
||||
# Run init to test migration
|
||||
init_db(default_conf['db_url'], default_conf['dry_run'])
|
||||
|
||||
@@ -619,6 +629,65 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
assert orders[1].order_id == 'stop_order_id222'
|
||||
assert orders[1].ft_order_side == 'stoploss'
|
||||
|
||||
caplog.clear()
|
||||
# Drop latest column
|
||||
with engine.begin() as connection:
|
||||
connection.execute(text("alter table orders rename to orders_bak"))
|
||||
inspector = inspect(engine)
|
||||
|
||||
with engine.begin() as connection:
|
||||
for index in inspector.get_indexes('orders_bak'):
|
||||
connection.execute(text(f"drop index {index['name']}"))
|
||||
# Recreate table
|
||||
connection.execute(text("""
|
||||
CREATE TABLE orders (
|
||||
id INTEGER NOT NULL,
|
||||
ft_trade_id INTEGER,
|
||||
ft_order_side VARCHAR NOT NULL,
|
||||
ft_pair VARCHAR NOT NULL,
|
||||
ft_is_open BOOLEAN NOT NULL,
|
||||
order_id VARCHAR NOT NULL,
|
||||
status VARCHAR,
|
||||
symbol VARCHAR,
|
||||
order_type VARCHAR,
|
||||
side VARCHAR,
|
||||
price FLOAT,
|
||||
amount FLOAT,
|
||||
filled FLOAT,
|
||||
remaining FLOAT,
|
||||
cost FLOAT,
|
||||
order_date DATETIME,
|
||||
order_filled_date DATETIME,
|
||||
order_update_date DATETIME,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT _order_pair_order_id UNIQUE (ft_pair, order_id),
|
||||
FOREIGN KEY(ft_trade_id) REFERENCES trades (id)
|
||||
)
|
||||
"""))
|
||||
|
||||
connection.execute(text("""
|
||||
insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status,
|
||||
symbol, order_type, side, price, amount, filled, remaining, cost, order_date,
|
||||
order_filled_date, order_update_date)
|
||||
select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status,
|
||||
symbol, order_type, side, price, amount, filled, remaining, cost, order_date,
|
||||
order_filled_date, order_update_date
|
||||
from orders_bak
|
||||
"""))
|
||||
|
||||
# Run init to test migration
|
||||
init_db(default_conf['db_url'], default_conf['dry_run'])
|
||||
|
||||
assert log_has("trying orders_bak1", caplog)
|
||||
|
||||
orders = Order.query.all()
|
||||
assert len(orders) == 2
|
||||
assert orders[0].order_id == 'buy_order'
|
||||
assert orders[0].ft_order_side == 'buy'
|
||||
|
||||
assert orders[1].order_id == 'stop_order_id222'
|
||||
assert orders[1].ft_order_side == 'stoploss'
|
||||
|
||||
|
||||
def test_migrate_mid_state(mocker, default_conf, fee, caplog):
|
||||
"""
|
||||
@@ -657,8 +726,9 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog):
|
||||
mocker.patch('freqtrade.persistence.models.create_engine', lambda *args, **kwargs: engine)
|
||||
|
||||
# Create table using the old format
|
||||
engine.execute(create_table_old)
|
||||
engine.execute(insert_table_old)
|
||||
with engine.begin() as connection:
|
||||
connection.execute(text(create_table_old))
|
||||
connection.execute(text(insert_table_old))
|
||||
|
||||
# Run init to test migration
|
||||
init_db(default_conf['db_url'], default_conf['dry_run'])
|
||||
@@ -689,7 +759,7 @@ def test_adjust_stop_loss(fee):
|
||||
amount=5,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
open_rate=1,
|
||||
max_rate=1,
|
||||
)
|
||||
@@ -741,7 +811,7 @@ def test_adjust_min_max_rates(fee):
|
||||
amount=5,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
open_rate=1,
|
||||
)
|
||||
|
||||
@@ -766,11 +836,16 @@ def test_adjust_min_max_rates(fee):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_get_open(fee):
|
||||
@pytest.mark.parametrize('use_db', [True, False])
|
||||
def test_get_open(fee, use_db):
|
||||
Trade.use_db = use_db
|
||||
Trade.reset_trades()
|
||||
|
||||
create_mock_trades(fee)
|
||||
create_mock_trades(fee, use_db)
|
||||
assert len(Trade.get_open_trades()) == 4
|
||||
|
||||
Trade.use_db = True
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_to_json(default_conf, fee):
|
||||
@@ -785,7 +860,7 @@ def test_to_json(default_conf, fee):
|
||||
fee_close=fee.return_value,
|
||||
open_date=arrow.utcnow().shift(hours=-2).datetime,
|
||||
open_rate=0.123,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
open_order_id='dry_run_buy_12345'
|
||||
)
|
||||
result = trade.to_json()
|
||||
@@ -794,11 +869,9 @@ def test_to_json(default_conf, fee):
|
||||
assert result == {'trade_id': None,
|
||||
'pair': 'ETH/BTC',
|
||||
'is_open': None,
|
||||
'open_date_hum': '2 hours ago',
|
||||
'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
'open_timestamp': int(trade.open_date.timestamp() * 1000),
|
||||
'open_order_id': 'dry_run_buy_12345',
|
||||
'close_date_hum': None,
|
||||
'close_date': None,
|
||||
'close_timestamp': None,
|
||||
'open_rate': 0.123,
|
||||
@@ -838,7 +911,7 @@ def test_to_json(default_conf, fee):
|
||||
'max_rate': None,
|
||||
'strategy': None,
|
||||
'timeframe': None,
|
||||
'exchange': 'bittrex',
|
||||
'exchange': 'binance',
|
||||
}
|
||||
|
||||
# Simulate dry_run entries
|
||||
@@ -853,17 +926,15 @@ def test_to_json(default_conf, fee):
|
||||
close_date=arrow.utcnow().shift(hours=-1).datetime,
|
||||
open_rate=0.123,
|
||||
close_rate=0.125,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
)
|
||||
result = trade.to_json()
|
||||
assert isinstance(result, dict)
|
||||
|
||||
assert result == {'trade_id': None,
|
||||
'pair': 'XRP/BTC',
|
||||
'open_date_hum': '2 hours ago',
|
||||
'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
'open_timestamp': int(trade.open_date.timestamp() * 1000),
|
||||
'close_date_hum': 'an hour ago',
|
||||
'close_date': trade.close_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
'close_timestamp': int(trade.close_date.timestamp() * 1000),
|
||||
'open_rate': 0.123,
|
||||
@@ -905,7 +976,7 @@ def test_to_json(default_conf, fee):
|
||||
'sell_order_status': None,
|
||||
'strategy': None,
|
||||
'timeframe': None,
|
||||
'exchange': 'bittrex',
|
||||
'exchange': 'binance',
|
||||
}
|
||||
|
||||
|
||||
@@ -918,7 +989,7 @@ def test_stoploss_reinitialization(default_conf, fee):
|
||||
open_date=arrow.utcnow().shift(hours=-2).datetime,
|
||||
amount=10,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
open_rate=1,
|
||||
max_rate=1,
|
||||
)
|
||||
@@ -928,7 +999,7 @@ def test_stoploss_reinitialization(default_conf, fee):
|
||||
assert trade.stop_loss_pct == -0.05
|
||||
assert trade.initial_stop_loss == 0.95
|
||||
assert trade.initial_stop_loss_pct == -0.05
|
||||
Trade.session.add(trade)
|
||||
Trade.query.session.add(trade)
|
||||
|
||||
# Lower stoploss
|
||||
Trade.stoploss_reinitialization(0.06)
|
||||
@@ -977,7 +1048,7 @@ def test_update_fee(fee):
|
||||
open_date=arrow.utcnow().shift(hours=-2).datetime,
|
||||
amount=10,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
open_rate=1,
|
||||
max_rate=1,
|
||||
)
|
||||
@@ -1016,7 +1087,7 @@ def test_fee_updated(fee):
|
||||
open_date=arrow.utcnow().shift(hours=-2).datetime,
|
||||
amount=10,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
open_rate=1,
|
||||
max_rate=1,
|
||||
)
|
||||
@@ -1039,14 +1110,52 @@ def test_fee_updated(fee):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_total_open_trades_stakes(fee):
|
||||
@pytest.mark.parametrize('use_db', [True, False])
|
||||
def test_total_open_trades_stakes(fee, use_db):
|
||||
|
||||
Trade.use_db = use_db
|
||||
Trade.reset_trades()
|
||||
res = Trade.total_open_trades_stakes()
|
||||
assert res == 0
|
||||
create_mock_trades(fee)
|
||||
create_mock_trades(fee, use_db)
|
||||
res = Trade.total_open_trades_stakes()
|
||||
assert res == 0.004
|
||||
|
||||
Trade.use_db = True
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
@pytest.mark.parametrize('use_db', [True, False])
|
||||
def test_get_trades_proxy(fee, use_db):
|
||||
Trade.use_db = use_db
|
||||
Trade.reset_trades()
|
||||
create_mock_trades(fee, use_db)
|
||||
trades = Trade.get_trades_proxy()
|
||||
assert len(trades) == 6
|
||||
|
||||
assert isinstance(trades[0], Trade)
|
||||
|
||||
trades = Trade.get_trades_proxy(is_open=True)
|
||||
assert len(trades) == 4
|
||||
assert trades[0].is_open
|
||||
trades = Trade.get_trades_proxy(is_open=False)
|
||||
|
||||
assert len(trades) == 2
|
||||
assert not trades[0].is_open
|
||||
|
||||
opendate = datetime.now(tz=timezone.utc) - timedelta(minutes=15)
|
||||
|
||||
assert len(Trade.get_trades_proxy(open_date=opendate)) == 3
|
||||
|
||||
Trade.use_db = True
|
||||
|
||||
|
||||
def test_get_trades_backtest():
|
||||
Trade.use_db = False
|
||||
with pytest.raises(NotImplementedError, match=r"`Trade.get_trades\(\)` not .*"):
|
||||
Trade.get_trades([])
|
||||
Trade.use_db = True
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_get_overall_performance(fee):
|
||||
@@ -1172,3 +1281,39 @@ def test_select_order(fee):
|
||||
assert order.ft_order_side == 'stoploss'
|
||||
order = trades[4].select_order('sell', False)
|
||||
assert order is None
|
||||
|
||||
|
||||
def test_Trade_object_idem():
|
||||
|
||||
assert issubclass(Trade, LocalTrade)
|
||||
|
||||
trade = vars(Trade)
|
||||
localtrade = vars(LocalTrade)
|
||||
|
||||
excludes = (
|
||||
'delete',
|
||||
'session',
|
||||
'commit',
|
||||
'query',
|
||||
'open_date',
|
||||
'get_best_pair',
|
||||
'get_overall_performance',
|
||||
'total_open_trades_stakes',
|
||||
'get_sold_trades_without_assigned_fees',
|
||||
'get_open_trades_without_assigned_fees',
|
||||
'get_open_order_trades',
|
||||
'get_trades',
|
||||
)
|
||||
|
||||
# Parent (LocalTrade) should have the same attributes
|
||||
for item in trade:
|
||||
# Exclude private attributes and open_date (as it's not assigned a default)
|
||||
if (not item.startswith('_') and item not in excludes):
|
||||
assert item in localtrade
|
||||
|
||||
# Fails if only a column is added without corresponding parent field
|
||||
for item in localtrade:
|
||||
if (not item.startswith('__')
|
||||
and item not in ('trades', 'trades_open', 'total_profit')
|
||||
and type(getattr(LocalTrade, item)) not in (property, FunctionType)):
|
||||
assert item in trade
|
||||
|
@@ -331,13 +331,13 @@ def test_generate_profit_graph(testdatadir):
|
||||
|
||||
trades = trades[trades['pair'].isin(pairs)]
|
||||
|
||||
fig = generate_profit_graph(pairs, data, trades, timeframe="5m")
|
||||
fig = generate_profit_graph(pairs, data, trades, timeframe="5m", stake_currency='BTC')
|
||||
assert isinstance(fig, go.Figure)
|
||||
|
||||
assert fig.layout.title.text == "Freqtrade Profit plot"
|
||||
assert fig.layout.yaxis.title.text == "Price"
|
||||
assert fig.layout.yaxis2.title.text == "Profit"
|
||||
assert fig.layout.yaxis3.title.text == "Profit"
|
||||
assert fig.layout.yaxis2.title.text == "Profit BTC"
|
||||
assert fig.layout.yaxis3.title.text == "Profit BTC"
|
||||
|
||||
figure = fig.layout.figure
|
||||
assert len(figure.data) == 5
|
||||
@@ -356,7 +356,8 @@ def test_generate_profit_graph(testdatadir):
|
||||
|
||||
with pytest.raises(OperationalException, match=r"No trades found.*"):
|
||||
# Pair cannot be empty - so it's an empty dataframe.
|
||||
generate_profit_graph(pairs, data, trades.loc[trades['pair'].isnull()], timeframe="5m")
|
||||
generate_profit_graph(pairs, data, trades.loc[trades['pair'].isnull()], timeframe="5m",
|
||||
stake_currency='BTC')
|
||||
|
||||
|
||||
def test_start_plot_dataframe(mocker):
|
||||
@@ -459,7 +460,11 @@ def test_plot_profit(default_conf, mocker, testdatadir):
|
||||
assert store_mock.call_count == 1
|
||||
|
||||
assert profit_mock.call_args_list[0][0][0] == default_conf['pairs']
|
||||
assert store_mock.call_args_list[0][1]['auto_open'] is True
|
||||
assert store_mock.call_args_list[0][1]['auto_open'] is False
|
||||
|
||||
del default_conf['timeframe']
|
||||
with pytest.raises(OperationalException, match=r"Timeframe must be set.*--timeframe.*"):
|
||||
plot_profit(default_conf)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ind1,ind2,plot_conf,exp", [
|
||||
|
@@ -3,6 +3,7 @@ import arrow
|
||||
import pytest
|
||||
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.exceptions import OperationalException
|
||||
|
||||
|
||||
def test_parse_timerange_incorrect():
|
||||
@@ -27,9 +28,13 @@ def test_parse_timerange_incorrect():
|
||||
timerange = TimeRange.parse_timerange('-1231006505000')
|
||||
assert TimeRange(None, 'date', 0, 1231006505) == timerange
|
||||
|
||||
with pytest.raises(Exception, match=r'Incorrect syntax.*'):
|
||||
with pytest.raises(OperationalException, match=r'Incorrect syntax.*'):
|
||||
TimeRange.parse_timerange('-')
|
||||
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'Start date is after stop date for timerange.*'):
|
||||
TimeRange.parse_timerange('20100523-20100522')
|
||||
|
||||
|
||||
def test_subtract_start():
|
||||
x = TimeRange('date', 'date', 1274486400, 1438214400)
|
||||
|
@@ -1,7 +1,12 @@
|
||||
# pragma pylint: disable=missing-docstring
|
||||
from copy import deepcopy
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from tests.conftest import get_patched_freqtradebot
|
||||
import pytest
|
||||
|
||||
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
|
||||
from freqtrade.exceptions import DependencyException
|
||||
from tests.conftest import get_patched_freqtradebot, patch_wallet
|
||||
|
||||
|
||||
def test_sync_wallet_at_boot(mocker, default_conf):
|
||||
@@ -106,3 +111,62 @@ def test_sync_wallet_missing_data(mocker, default_conf):
|
||||
assert freqtrade.wallets._wallets['GAS'].used is None
|
||||
assert freqtrade.wallets._wallets['GAS'].total == 0.260739
|
||||
assert freqtrade.wallets.get_free('GAS') == 0.260739
|
||||
|
||||
|
||||
def test_get_trade_stake_amount_no_stake_amount(default_conf, mocker) -> None:
|
||||
patch_wallet(mocker, free=default_conf['stake_amount'] * 0.5)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
|
||||
freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
|
||||
|
||||
|
||||
@pytest.mark.parametrize("balance_ratio,result1,result2", [
|
||||
(1, 50, 66.66666),
|
||||
(0.99, 49.5, 66.0),
|
||||
(0.50, 25, 33.3333),
|
||||
])
|
||||
def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_ratio, result1,
|
||||
result2, limit_buy_order_open,
|
||||
fee, mocker) -> None:
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
conf['stake_amount'] = UNLIMITED_STAKE_AMOUNT
|
||||
conf['dry_run_wallet'] = 100
|
||||
conf['max_open_trades'] = 2
|
||||
conf['tradable_balance_ratio'] = balance_ratio
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, conf)
|
||||
|
||||
# no open trades, order amount should be 'balance / max_open_trades'
|
||||
result = freqtrade.wallets.get_trade_stake_amount('ETH/USDT')
|
||||
assert result == result1
|
||||
|
||||
# create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)'
|
||||
freqtrade.execute_buy('ETH/USDT', result)
|
||||
|
||||
result = freqtrade.wallets.get_trade_stake_amount('LTC/USDT')
|
||||
assert result == result1
|
||||
|
||||
# create 2 trades, order amount should be None
|
||||
freqtrade.execute_buy('LTC/BTC', result)
|
||||
|
||||
result = freqtrade.wallets.get_trade_stake_amount('XRP/USDT')
|
||||
assert result == 0
|
||||
|
||||
freqtrade.config['max_open_trades'] = 3
|
||||
freqtrade.config['dry_run_wallet'] = 200
|
||||
freqtrade.wallets.start_cap = 200
|
||||
result = freqtrade.wallets.get_trade_stake_amount('XRP/USDT')
|
||||
assert round(result, 4) == round(result2, 4)
|
||||
|
||||
# set max_open_trades = None, so do not trade
|
||||
freqtrade.config['max_open_trades'] = 0
|
||||
result = freqtrade.wallets.get_trade_stake_amount('NEO/USDT')
|
||||
assert result == 0
|
||||
|
@@ -3,7 +3,7 @@ import time
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.state import State
|
||||
from freqtrade.enums import State
|
||||
from freqtrade.worker import Worker
|
||||
from tests.conftest import get_patched_worker, log_has, log_has_re
|
||||
|
||||
|
BIN
tests/testdata/hyperopt_results_SampleStrategy.pickle
vendored
Normal file
BIN
tests/testdata/hyperopt_results_SampleStrategy.pickle
vendored
Normal file
Binary file not shown.
5
tests/testdata/strategy_SampleStrategy.fthypt
vendored
Normal file
5
tests/testdata/strategy_SampleStrategy.fthypt
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user