Merge branch 'develop' into pr/gmatheu/4746
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)
|
||||
|
@@ -66,8 +66,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 +89,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 +116,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 +201,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 = [
|
||||
@@ -918,242 +918,244 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
|
||||
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):
|
||||
for _ in (saved_hyperopt_results, saved_hyperopt_results_legacy):
|
||||
mocker.patch(
|
||||
'freqtrade.optimize.hyperopt_tools.HyperoptTools.load_previous_results',
|
||||
MagicMock(return_value=saved_hyperopt_results_legacy)
|
||||
)
|
||||
|
||||
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%,-1.2222,-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, saved_hyperopt_results):
|
||||
mocker.patch(
|
||||
'freqtrade.optimize.hyperopt_tools.HyperoptTools.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%,-1.2222,-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_tools.HyperoptTools.load_previous_results',
|
||||
MagicMock(return_value=hyperopt_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,7 +3,7 @@ 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, Mock, PropertyMock
|
||||
@@ -21,6 +21,7 @@ from freqtrade.exchange import Exchange
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
from freqtrade.persistence import LocalTrade, Trade, init_db
|
||||
from freqtrade.resolvers import ExchangeResolver
|
||||
from freqtrade.state import RunMode
|
||||
from freqtrade.worker import Worker
|
||||
from tests.conftest_trades import (mock_trade_1, mock_trade_2, mock_trade_3, mock_trade_4,
|
||||
mock_trade_5, mock_trade_6)
|
||||
@@ -79,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())
|
||||
@@ -98,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
|
||||
@@ -197,7 +198,7 @@ def create_mock_trades(fee, use_db: bool = True):
|
||||
"""
|
||||
def add_trade(trade):
|
||||
if use_db:
|
||||
Trade.session.add(trade)
|
||||
Trade.query.session.add(trade)
|
||||
else:
|
||||
LocalTrade.add_bt_trade(trade)
|
||||
|
||||
@@ -220,6 +221,9 @@ def create_mock_trades(fee, use_db: bool = True):
|
||||
trade = mock_trade_6(fee)
|
||||
add_trade(trade)
|
||||
|
||||
if use_db:
|
||||
Trade.query.session.flush()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def patch_coingekko(mocker) -> None:
|
||||
@@ -290,7 +294,7 @@ def get_default_conf(testdatadir):
|
||||
"order_book_max": 1
|
||||
},
|
||||
"exchange": {
|
||||
"name": "bittrex",
|
||||
"name": "binance",
|
||||
"enabled": True,
|
||||
"key": "key",
|
||||
"secret": "secret",
|
||||
@@ -311,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",
|
||||
@@ -1673,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
|
||||
@@ -1762,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,
|
||||
@@ -1774,7 +1780,7 @@ def open_trade():
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def hyperopt_results():
|
||||
def saved_hyperopt_results_legacy():
|
||||
return [
|
||||
{
|
||||
'loss': 0.4366182531161519,
|
||||
@@ -1903,3 +1909,136 @@ def hyperopt_results():
|
||||
'is_best': False
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def saved_hyperopt_results():
|
||||
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': {'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
|
||||
}
|
||||
]
|
||||
|
@@ -31,7 +31,7 @@ def mock_trade_1(fee):
|
||||
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,
|
||||
@@ -84,7 +84,7 @@ def mock_trade_2(fee):
|
||||
close_rate=0.128,
|
||||
close_profit=0.005,
|
||||
close_profit_abs=0.000584127,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
is_open=False,
|
||||
open_order_id='dry_run_sell_12345',
|
||||
strategy='DefaultStrategy',
|
||||
@@ -144,7 +144,7 @@ def mock_trade_3(fee):
|
||||
close_rate=0.06,
|
||||
close_profit=0.01,
|
||||
close_profit_abs=0.000155,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
is_open=False,
|
||||
strategy='DefaultStrategy',
|
||||
timeframe=5,
|
||||
@@ -187,7 +187,7 @@ def mock_trade_4(fee):
|
||||
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,
|
||||
@@ -239,9 +239,10 @@ def mock_trade_5(fee):
|
||||
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)
|
||||
@@ -292,9 +293,10 @@ def mock_trade_6(fee):
|
||||
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
|
||||
|
||||
@@ -246,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):
|
||||
@@ -264,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'],
|
||||
|
@@ -10,7 +10,7 @@ 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.conftest import log_has, log_has_re
|
||||
from tests.data.test_history import _backup_file, _clean_test_file
|
||||
|
||||
|
||||
@@ -62,8 +62,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 +125,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 +197,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)
|
||||
|
@@ -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()
|
||||
|
@@ -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))
|
||||
|
@@ -36,7 +36,12 @@ EXCHANGES = {
|
||||
'pair': 'BTC/USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
}
|
||||
},
|
||||
'kucoin': {
|
||||
'pair': 'BTC/USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -100,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
|
||||
|
@@ -371,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 isclose(result, 2 * 1.1)
|
||||
assert isclose(result, 2 * (1+0.05) / (1-abs(stoploss)))
|
||||
|
||||
# min amount is set
|
||||
markets["ETH/BTC"]["limits"] = {
|
||||
@@ -383,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 isclose(result, 2 * 2 * 1.1)
|
||||
assert isclose(result, 2 * 2 * (1+0.05) / (1-abs(stoploss)))
|
||||
|
||||
# min amount and cost are set (cost is minimal)
|
||||
markets["ETH/BTC"]["limits"] = {
|
||||
@@ -395,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 isclose(result, max(2, 2 * 2) * 1.1)
|
||||
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"] = {
|
||||
@@ -407,10 +407,10 @@ 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 isclose(result, max(8, 2 * 2) * 1.1)
|
||||
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.45)
|
||||
assert isclose(result, max(8, 2 * 2) * 1.5)
|
||||
|
||||
# Really big stoploss
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1)
|
||||
@@ -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) * 1.1, 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):
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -2106,6 +2084,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
|
||||
|
@@ -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):
|
||||
@@ -156,3 +157,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',
|
||||
'info': {
|
||||
'orderId': '1234'
|
||||
}
|
||||
}
|
||||
assert exchange.get_order_id_conditional(order) == '1234'
|
||||
|
||||
order = {
|
||||
'type': 'limit',
|
||||
'price': 1500,
|
||||
'id': '1111',
|
||||
'info': {
|
||||
'orderId': '1234'
|
||||
}
|
||||
}
|
||||
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
|
||||
|
@@ -6,6 +6,7 @@ import pandas as pd
|
||||
import pytest
|
||||
|
||||
from freqtrade.optimize.hyperopt import Hyperopt
|
||||
from freqtrade.state import RunMode
|
||||
from freqtrade.strategy.interface import SellType
|
||||
from tests.conftest import patch_exchange
|
||||
|
||||
@@ -14,6 +15,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 +24,7 @@ def hyperopt_conf(default_conf):
|
||||
'timerange': None,
|
||||
'spaces': ['default'],
|
||||
'hyperopt_jobs': 1,
|
||||
'hyperopt_min_trades': 1,
|
||||
})
|
||||
return hyperconf
|
||||
|
||||
|
@@ -185,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,
|
||||
@@ -268,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],
|
||||
@@ -440,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,
|
||||
@@ -469,6 +486,7 @@ TESTS = [
|
||||
tc25,
|
||||
tc26,
|
||||
tc27,
|
||||
tc28,
|
||||
]
|
||||
|
||||
|
||||
@@ -493,6 +511,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
|
||||
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,13 +520,14 @@ 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,
|
||||
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)
|
||||
|
||||
|
@@ -83,6 +83,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)
|
||||
@@ -106,6 +107,7 @@ 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 {
|
||||
@@ -285,6 +287,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)
|
||||
@@ -315,11 +318,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
|
||||
|
||||
@@ -330,6 +335,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
|
||||
|
||||
@@ -361,12 +367,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)
|
||||
@@ -393,6 +400,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()
|
||||
|
||||
@@ -457,13 +465,15 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti
|
||||
Backtesting(default_conf)
|
||||
|
||||
|
||||
def test_backtest__enter_trade(default_conf, fee, mocker, testdatadir) -> None:
|
||||
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),
|
||||
@@ -474,24 +484,30 @@ def test_backtest__enter_trade(default_conf, fee, mocker, testdatadir) -> None:
|
||||
0.00099, # Low
|
||||
0.0012, # High
|
||||
]
|
||||
trade = backtesting._enter_trade(pair, row=row, max_open_trades=2, open_trade_count=0)
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
assert isinstance(trade, LocalTrade)
|
||||
assert trade.stake_amount == 495
|
||||
|
||||
trade = backtesting._enter_trade(pair, row=row, max_open_trades=2, open_trade_count=2)
|
||||
# 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, max_open_trades=2, open_trade_count=0)
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
assert trade is None
|
||||
|
||||
# Stake-amount too high!
|
||||
# Stake-amount throwing error
|
||||
mocker.patch("freqtrade.wallets.Wallets.get_trade_stake_amount",
|
||||
side_effect=DependencyException)
|
||||
|
||||
trade = backtesting._enter_trade(pair, row=row, max_open_trades=2, open_trade_count=0)
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
assert trade is None
|
||||
|
||||
|
||||
@@ -501,19 +517,21 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||
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,
|
||||
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
|
||||
|
||||
@@ -562,6 +580,7 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
|
||||
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')
|
||||
@@ -576,13 +595,14 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
|
||||
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)
|
||||
@@ -616,7 +636,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', [
|
||||
@@ -641,7 +661,7 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
|
||||
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):
|
||||
@@ -653,10 +673,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):
|
||||
@@ -668,10 +689,11 @@ 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):
|
||||
@@ -681,12 +703,14 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
|
||||
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
|
||||
@@ -722,6 +746,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
|
||||
|
||||
@@ -738,9 +763,9 @@ 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,
|
||||
@@ -750,7 +775,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
|
||||
'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):
|
||||
@@ -782,9 +807,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 ...'
|
||||
]
|
||||
|
||||
@@ -795,8 +820,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)
|
||||
@@ -810,7 +847,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)
|
||||
@@ -844,9 +881,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',
|
||||
@@ -858,41 +895,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],
|
||||
'stake_amount': [0.01, 0.01],
|
||||
'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],
|
||||
'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]
|
||||
}),
|
||||
{
|
||||
'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']))
|
||||
@@ -923,9 +979,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',
|
||||
|
@@ -5,7 +5,7 @@ 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
|
||||
@@ -16,9 +16,14 @@ from freqtrade.commands.optimize_commands import setup_optimize_configuration, s
|
||||
from freqtrade.data.history import load_data
|
||||
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 freqtrade.strategy.interface import SellType
|
||||
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
||||
patched_configuration_load_config_file)
|
||||
|
||||
@@ -26,23 +31,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}]
|
||||
|
||||
@@ -316,54 +305,49 @@ 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_tools.load', return_value=epochs)
|
||||
results_file = testdatadir / 'optimize' / 'ut_results.pickle'
|
||||
hyperopt_epochs = HyperoptTools._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:
|
||||
|
||||
|
||||
def test_load_previous_results(mocker, hyperopt, testdatadir, caplog) -> None:
|
||||
epochs = create_results(mocker, hyperopt, testdatadir)
|
||||
mock_load = mocker.patch('freqtrade.optimize.hyperopt_tools.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'
|
||||
results_file = testdatadir / 'hyperopt_results_SampleStrategy.pickle'
|
||||
|
||||
hyperopt_epochs = HyperoptTools.load_previous_results(results_file)
|
||||
|
||||
assert hyperopt_epochs == epochs
|
||||
mock_load.assert_called_once()
|
||||
assert len(hyperopt_epochs) == 5
|
||||
assert log_has_re(r"Reading pickled epochs from .*", caplog)
|
||||
|
||||
del epochs[0]['is_best']
|
||||
mock_load = mocker.patch('freqtrade.optimize.hyperopt_tools.load', return_value=epochs)
|
||||
caplog.clear()
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
# 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_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)
|
||||
|
||||
|
||||
@@ -381,7 +365,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',
|
||||
@@ -420,9 +405,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")
|
||||
@@ -430,18 +415,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,
|
||||
@@ -455,21 +464,9 @@ def test_format_results(hyperopt):
|
||||
}
|
||||
|
||||
result = HyperoptTools._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 = HyperoptTools._format_explanation_string(results, 1)
|
||||
assert result.find('Total profit 1.00000000 EUR')
|
||||
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", [
|
||||
@@ -500,10 +497,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:
|
||||
@@ -574,22 +571,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,
|
||||
@@ -623,11 +637,11 @@ 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.'
|
||||
'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\N{GREEK CAPITAL LETTER SIGMA}%). '
|
||||
'Avg duration 0:50:00 min.'
|
||||
).encode(locale.getpreferredencoding(), 'replace').decode('utf-8'),
|
||||
'params_details': {'buy': {'adx-enabled': False,
|
||||
'adx-value': 0,
|
||||
@@ -638,10 +652,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,
|
||||
@@ -657,21 +671,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
|
||||
|
||||
@@ -688,7 +697,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',
|
||||
@@ -739,13 +749,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)))
|
||||
@@ -787,13 +798,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)))
|
||||
@@ -834,13 +846,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)))
|
||||
@@ -882,9 +895,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")
|
||||
@@ -920,7 +933,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)))
|
||||
@@ -963,8 +977,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")
|
||||
@@ -973,7 +987,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)))
|
||||
@@ -1016,8 +1031,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")
|
||||
@@ -1089,3 +1104,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
|
||||
}"""
|
||||
|
@@ -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,14 +8,15 @@ 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.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
|
||||
@@ -26,25 +28,22 @@ 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',
|
||||
@@ -80,6 +79,7 @@ def test_generate_backtest_stats(default_conf, testdatadir):
|
||||
'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,
|
||||
}
|
||||
@@ -96,8 +96,8 @@ 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
|
||||
@@ -127,6 +127,7 @@ def test_generate_backtest_stats(default_conf, testdatadir):
|
||||
'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,
|
||||
}
|
||||
@@ -140,8 +141,8 @@ def test_generate_backtest_stats(default_conf, testdatadir):
|
||||
strat_stats = stats['strategy']['DefStrat']
|
||||
|
||||
assert strat_stats['max_drawdown'] == 0.013803
|
||||
assert strat_stats['drawdown_start'] == datetime(2017, 11, 14, 22, 10, tzinfo=timezone.utc)
|
||||
assert strat_stats['drawdown_end'] == datetime(2017, 11, 14, 22, 43, tzinfo=timezone.utc)
|
||||
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']
|
||||
@@ -226,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', :])
|
||||
@@ -238,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(
|
||||
@@ -254,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,
|
||||
@@ -309,9 +325,12 @@ 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],
|
||||
@@ -324,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],
|
||||
@@ -335,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 |'
|
||||
' 36.67 | 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 |'
|
||||
' 43.33 | 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.'}]",
|
||||
|
@@ -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:
|
||||
@@ -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,
|
||||
))
|
||||
@@ -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}")
|
||||
|
@@ -53,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,
|
||||
@@ -73,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,
|
||||
@@ -92,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,
|
||||
@@ -107,7 +106,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',
|
||||
}
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
|
||||
@@ -120,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,
|
||||
@@ -140,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,
|
||||
@@ -159,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,
|
||||
@@ -174,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',
|
||||
}
|
||||
|
||||
|
||||
@@ -201,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',
|
||||
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,
|
||||
@@ -571,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',
|
||||
|
@@ -416,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')
|
||||
@@ -468,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
|
||||
@@ -506,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):
|
||||
@@ -538,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'
|
||||
@@ -612,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(
|
||||
@@ -627,48 +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_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_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,
|
||||
}
|
||||
|
||||
@@ -702,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))
|
||||
|
||||
@@ -720,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',
|
||||
@@ -735,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):
|
||||
@@ -753,63 +764,57 @@ 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,
|
||||
'close_rate_requested': ANY,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
@@ -817,17 +822,17 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||
'fee_open_cost': None,
|
||||
'fee_open_currency': None,
|
||||
'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.freqtradebot.FreqtradeBot.get_sell_rate',
|
||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||
@@ -835,7 +840,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||
rc = client_get(client, f"{BASE_URI}/status")
|
||||
assert_response(rc)
|
||||
resp_values = rc.json()
|
||||
assert len(resp_values) == 1
|
||||
assert len(resp_values) == 4
|
||||
assert isnan(resp_values[0]['profit_abs'])
|
||||
|
||||
|
||||
@@ -917,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",
|
||||
@@ -940,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',
|
||||
@@ -965,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,
|
||||
@@ -981,7 +985,7 @@ def test_api_forcebuy(botclient, mocker, fee):
|
||||
'sell_order_status': None,
|
||||
'strategy': 'DefaultStrategy',
|
||||
'timeframe': 5,
|
||||
'exchange': 'bittrex',
|
||||
'exchange': 'binance',
|
||||
}
|
||||
|
||||
|
||||
@@ -1141,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):
|
||||
@@ -1149,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):
|
||||
|
@@ -71,7 +71,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 +86,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 +124,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 +140,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
|
||||
|
@@ -186,9 +186,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,
|
||||
@@ -694,12 +692,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,
|
||||
@@ -714,6 +712,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
|
||||
|
||||
|
||||
@@ -754,13 +753,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,
|
||||
@@ -775,6 +774,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
|
||||
|
||||
|
||||
@@ -805,13 +805,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,
|
||||
@@ -826,6 +826,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
|
||||
|
||||
|
||||
@@ -964,7 +965,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:
|
||||
@@ -1004,6 +1005,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')
|
||||
@@ -1137,6 +1143,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):
|
||||
|
||||
@@ -1216,7 +1231,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]
|
||||
|
||||
@@ -1225,7 +1240,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]
|
||||
|
||||
@@ -1233,9 +1248,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,
|
||||
'type': RPCMessageType.BUY,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Bittrex',
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'limit': 1.099e-05,
|
||||
'order_type': 'limit',
|
||||
@@ -1251,7 +1266,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 (#1)\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' \
|
||||
@@ -1278,17 +1293,36 @@ 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,
|
||||
'type': RPCMessageType.BUY_CANCEL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Bittrex',
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'reason': CANCEL_REASON['TIMEOUT']
|
||||
})
|
||||
assert (msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Bittrex:* '
|
||||
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:
|
||||
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
@@ -1296,7 +1330,7 @@ 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',
|
||||
@@ -1326,7 +1360,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
|
||||
msg_mock.reset_mock()
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'type': RPCMessageType.SELL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'KEY/ETH',
|
||||
@@ -1363,36 +1397,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 (#1).'
|
||||
' 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 (#1).'
|
||||
' 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`'
|
||||
@@ -1401,7 +1464,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`'
|
||||
@@ -1410,7 +1473,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`'
|
||||
@@ -1429,9 +1492,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,
|
||||
'type': RPCMessageType.BUY,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Bittrex',
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'limit': 1.099e-05,
|
||||
'order_type': 'limit',
|
||||
@@ -1443,7 +1506,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 (#1)\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'
|
||||
@@ -1455,7 +1518,7 @@ 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',
|
||||
|
@@ -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',
|
||||
|
@@ -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,9 +10,11 @@ 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.exceptions import OperationalException, StrategyError
|
||||
from freqtrade.persistence import PairLocks, Trade
|
||||
from freqtrade.resolvers import StrategyResolver
|
||||
from freqtrade.strategy.hyper import (BaseParameter, CategoricalParameter, DecimalParameter,
|
||||
IntParameter, RealParameter)
|
||||
from freqtrade.strategy.interface import SellCheckTuple, SellType
|
||||
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
||||
from tests.conftest import log_has, log_has_re
|
||||
@@ -217,7 +219,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 +258,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 +293,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 +346,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 +382,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 +598,77 @@ 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
|
||||
|
||||
strategy.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')]
|
||||
|
@@ -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
|
||||
|
||||
|
||||
|
@@ -565,7 +565,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()
|
||||
|
||||
@@ -860,22 +860,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,
|
||||
@@ -1018,6 +1002,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'
|
||||
|
||||
|
||||
@@ -1054,37 +1039,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()
|
||||
@@ -1097,7 +1075,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())
|
||||
|
@@ -82,10 +82,6 @@ def test_bot_cleanup(mocker, default_conf, caplog) -> None:
|
||||
def test_order_dict_dry_run(default_conf, mocker, caplog) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2)
|
||||
)
|
||||
conf = default_conf.copy()
|
||||
conf['runmode'] = RunMode.DRY_RUN
|
||||
conf['order_types'] = {
|
||||
@@ -117,10 +113,7 @@ def test_order_dict_dry_run(default_conf, mocker, caplog) -> None:
|
||||
def test_order_dict_live(default_conf, mocker, caplog) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2)
|
||||
)
|
||||
|
||||
conf = default_conf.copy()
|
||||
conf['runmode'] = RunMode.LIVE
|
||||
conf['order_types'] = {
|
||||
@@ -153,15 +146,10 @@ def test_order_dict_live(default_conf, mocker, caplog) -> None:
|
||||
def test_get_trade_stake_amount(default_conf, ticker, mocker) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2)
|
||||
)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
result = freqtrade.wallets.get_trade_stake_amount(
|
||||
'ETH/BTC', freqtrade.get_free_open_trades())
|
||||
result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
|
||||
assert result == default_conf['stake_amount']
|
||||
|
||||
|
||||
@@ -182,7 +170,6 @@ def test_check_available_stake_amount(default_conf, ticker, mocker, fee, limit_b
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2),
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
get_fee=fee
|
||||
)
|
||||
@@ -197,73 +184,12 @@ def test_check_available_stake_amount(default_conf, ticker, mocker, fee, limit_b
|
||||
|
||||
if expected[i] is not None:
|
||||
limit_buy_order_open['id'] = str(i)
|
||||
result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC',
|
||||
freqtrade.get_free_open_trades())
|
||||
result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
|
||||
assert pytest.approx(result) == expected[i]
|
||||
freqtrade.execute_buy('ETH/BTC', result)
|
||||
else:
|
||||
with pytest.raises(DependencyException):
|
||||
freqtrade.wallets.get_trade_stake_amount('ETH/BTC',
|
||||
freqtrade.get_free_open_trades())
|
||||
|
||||
|
||||
def test_get_trade_stake_amount_no_stake_amount(default_conf, mocker) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
patch_wallet(mocker, free=default_conf['stake_amount'] * 0.5)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
|
||||
freqtrade.wallets.get_trade_stake_amount('ETH/BTC', freqtrade.get_free_open_trades())
|
||||
|
||||
|
||||
@pytest.mark.parametrize("balance_ratio,result1", [
|
||||
(1, 0.005),
|
||||
(0.99, 0.00495),
|
||||
(0.50, 0.0025),
|
||||
])
|
||||
def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_ratio, result1,
|
||||
limit_buy_order_open, fee, mocker) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
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'] = 0.01
|
||||
conf['max_open_trades'] = 2
|
||||
conf['tradable_balance_ratio'] = balance_ratio
|
||||
|
||||
freqtrade = FreqtradeBot(conf)
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
# no open trades, order amount should be 'balance / max_open_trades'
|
||||
result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC', freqtrade.get_free_open_trades())
|
||||
assert result == result1
|
||||
|
||||
# create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)'
|
||||
freqtrade.execute_buy('ETH/BTC', result)
|
||||
|
||||
result = freqtrade.wallets.get_trade_stake_amount('LTC/BTC', freqtrade.get_free_open_trades())
|
||||
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/BTC', freqtrade.get_free_open_trades())
|
||||
assert result == 0
|
||||
|
||||
# set max_open_trades = None, so do not trade
|
||||
conf['max_open_trades'] = 0
|
||||
freqtrade = FreqtradeBot(conf)
|
||||
result = freqtrade.wallets.get_trade_stake_amount('NEO/BTC', freqtrade.get_free_open_trades())
|
||||
assert result == 0
|
||||
freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
|
||||
|
||||
|
||||
def test_edge_called_in_process(mocker, edge_conf) -> None:
|
||||
@@ -289,9 +215,9 @@ def test_edge_overrides_stake_amount(mocker, edge_conf) -> None:
|
||||
freqtrade = FreqtradeBot(edge_conf)
|
||||
|
||||
assert freqtrade.wallets.get_trade_stake_amount(
|
||||
'NEO/BTC', freqtrade.get_free_open_trades(), freqtrade.edge) == (999.9 * 0.5 * 0.01) / 0.20
|
||||
'NEO/BTC', freqtrade.edge) == (999.9 * 0.5 * 0.01) / 0.20
|
||||
assert freqtrade.wallets.get_trade_stake_amount(
|
||||
'LTC/BTC', freqtrade.get_free_open_trades(), freqtrade.edge) == (999.9 * 0.5 * 0.01) / 0.21
|
||||
'LTC/BTC', freqtrade.edge) == (999.9 * 0.5 * 0.01) / 0.21
|
||||
|
||||
|
||||
def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker, edge_conf) -> None:
|
||||
@@ -421,7 +347,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, fee, mocker) -> Non
|
||||
assert trade.stake_amount == 0.001
|
||||
assert trade.is_open
|
||||
assert trade.open_date is not None
|
||||
assert trade.exchange == 'bittrex'
|
||||
assert trade.exchange == 'binance'
|
||||
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
trade.update(limit_buy_order)
|
||||
@@ -497,7 +423,6 @@ def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order_open,
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
get_balance=MagicMock(return_value=default_conf['stake_amount']),
|
||||
get_fee=fee,
|
||||
)
|
||||
default_conf['max_open_trades'] = 0
|
||||
@@ -507,8 +432,7 @@ def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order_open,
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
assert not freqtrade.create_trade('ETH/BTC')
|
||||
assert freqtrade.wallets.get_trade_stake_amount('ETH/BTC', freqtrade.get_free_open_trades(),
|
||||
freqtrade.edge) == 0
|
||||
assert freqtrade.wallets.get_trade_stake_amount('ETH/BTC', freqtrade.edge) == 0
|
||||
|
||||
|
||||
def test_enter_positions_no_pairs_left(default_conf, ticker, limit_buy_order_open, fee,
|
||||
@@ -586,7 +510,6 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balance=MagicMock(return_value=20),
|
||||
get_fee=fee,
|
||||
)
|
||||
default_conf['stake_amount'] = 10
|
||||
@@ -680,12 +603,13 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, limit_buy
|
||||
assert trade.stake_amount == default_conf['stake_amount']
|
||||
assert trade.is_open
|
||||
assert trade.open_date is not None
|
||||
assert trade.exchange == 'bittrex'
|
||||
assert trade.exchange == 'binance'
|
||||
assert trade.open_rate == 0.00001098
|
||||
assert trade.amount == 91.07468123
|
||||
|
||||
assert log_has(
|
||||
'Buy signal found: about create a new trade with stake_amount: 0.001 ...', caplog
|
||||
'Buy signal found: about create a new trade for ETH/BTC with stake_amount: 0.001 ...',
|
||||
caplog
|
||||
)
|
||||
|
||||
|
||||
@@ -768,7 +692,7 @@ def test_process_trade_no_whitelist_pair(default_conf, ticker, limit_buy_order,
|
||||
assert pair not in default_conf['exchange']['pair_whitelist']
|
||||
|
||||
# create open trade not in whitelist
|
||||
Trade.session.add(Trade(
|
||||
Trade.query.session.add(Trade(
|
||||
pair=pair,
|
||||
stake_amount=0.001,
|
||||
fee_open=fee.return_value,
|
||||
@@ -776,9 +700,9 @@ def test_process_trade_no_whitelist_pair(default_conf, ticker, limit_buy_order,
|
||||
is_open=True,
|
||||
amount=20,
|
||||
open_rate=0.01,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
))
|
||||
Trade.session.add(Trade(
|
||||
Trade.query.session.add(Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
fee_open=fee.return_value,
|
||||
@@ -786,7 +710,7 @@ def test_process_trade_no_whitelist_pair(default_conf, ticker, limit_buy_order,
|
||||
is_open=True,
|
||||
amount=12,
|
||||
open_rate=0.001,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
))
|
||||
|
||||
assert pair not in freqtrade.active_pair_whitelist
|
||||
@@ -1027,7 +951,7 @@ def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None
|
||||
return_value=limit_buy_order['amount'])
|
||||
|
||||
stoploss = MagicMock(return_value={'id': 13434334})
|
||||
mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss)
|
||||
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
||||
@@ -1059,6 +983,9 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Binance',
|
||||
stoploss=stoploss
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
@@ -1083,7 +1010,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
||||
trade.stoploss_order_id = 100
|
||||
|
||||
hanging_stoploss_order = MagicMock(return_value={'status': 'open'})
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', hanging_stoploss_order)
|
||||
mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', hanging_stoploss_order)
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
assert trade.stoploss_order_id == 100
|
||||
@@ -1096,7 +1023,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
||||
trade.stoploss_order_id = 100
|
||||
|
||||
canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'})
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', canceled_stoploss_order)
|
||||
mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', canceled_stoploss_order)
|
||||
stoploss.reset_mock()
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
@@ -1122,14 +1049,14 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
||||
'average': 2,
|
||||
'amount': limit_buy_order['amount'],
|
||||
})
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hit)
|
||||
mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hit)
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is True
|
||||
assert log_has_re(r'STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.', caplog)
|
||||
assert trade.stoploss_order_id is None
|
||||
assert trade.is_open is False
|
||||
|
||||
mocker.patch(
|
||||
'freqtrade.exchange.Exchange.stoploss',
|
||||
'freqtrade.exchange.Binance.stoploss',
|
||||
side_effect=ExchangeError()
|
||||
)
|
||||
trade.is_open = True
|
||||
@@ -1141,9 +1068,9 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
||||
# It should try to add stoploss order
|
||||
trade.stoploss_order_id = 100
|
||||
stoploss.reset_mock()
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order',
|
||||
mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order',
|
||||
side_effect=InvalidOrderException())
|
||||
mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss)
|
||||
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
|
||||
freqtrade.handle_stoploss_on_exchange(trade)
|
||||
assert stoploss.call_count == 1
|
||||
|
||||
@@ -1153,7 +1080,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
||||
trade.is_open = False
|
||||
stoploss.reset_mock()
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order')
|
||||
mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss)
|
||||
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
assert stoploss.call_count == 0
|
||||
|
||||
@@ -1173,6 +1100,9 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf, fee, caplog,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Binance',
|
||||
fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': 100}),
|
||||
stoploss=MagicMock(side_effect=ExchangeError()),
|
||||
)
|
||||
@@ -1207,6 +1137,9 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee,
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
sell=sell_mock,
|
||||
get_fee=fee,
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Binance',
|
||||
fetch_order=MagicMock(return_value={'status': 'canceled'}),
|
||||
stoploss=MagicMock(side_effect=InvalidOrderException()),
|
||||
)
|
||||
@@ -1252,6 +1185,9 @@ def test_create_stoploss_order_insufficient_funds(mocker, default_conf, caplog,
|
||||
sell=sell_mock,
|
||||
get_fee=fee,
|
||||
fetch_order=MagicMock(return_value={'status': 'canceled'}),
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Binance',
|
||||
stoploss=MagicMock(side_effect=InsufficientFundsError()),
|
||||
)
|
||||
patch_get_signal(freqtrade)
|
||||
@@ -1289,6 +1225,9 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Binance',
|
||||
stoploss=stoploss,
|
||||
stoploss_adjust=MagicMock(return_value=True),
|
||||
)
|
||||
@@ -1329,7 +1268,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee,
|
||||
}
|
||||
})
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hanging)
|
||||
mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hanging)
|
||||
|
||||
# stoploss initially at 5%
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
@@ -1344,8 +1283,8 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee,
|
||||
|
||||
cancel_order_mock = MagicMock()
|
||||
stoploss_order_mock = MagicMock(return_value={'id': 13434334})
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', cancel_order_mock)
|
||||
mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss_order_mock)
|
||||
mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock)
|
||||
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock)
|
||||
|
||||
# stoploss should not be updated as the interval is 60 seconds
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
@@ -1392,6 +1331,9 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Binance',
|
||||
stoploss=stoploss,
|
||||
stoploss_adjust=MagicMock(return_value=True),
|
||||
)
|
||||
@@ -1427,9 +1369,10 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c
|
||||
'stopPrice': '0.1'
|
||||
}
|
||||
}
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
|
||||
mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order',
|
||||
side_effect=InvalidOrderException())
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hanging)
|
||||
mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order',
|
||||
return_value=stoploss_order_hanging)
|
||||
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
|
||||
assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/BTC.*", caplog)
|
||||
|
||||
@@ -1438,8 +1381,8 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c
|
||||
|
||||
# Fail creating stoploss order
|
||||
caplog.clear()
|
||||
cancel_mock = mocker.patch("freqtrade.exchange.Exchange.cancel_stoploss_order", MagicMock())
|
||||
mocker.patch("freqtrade.exchange.Exchange.stoploss", side_effect=ExchangeError())
|
||||
cancel_mock = mocker.patch("freqtrade.exchange.Binance.cancel_stoploss_order", MagicMock())
|
||||
mocker.patch("freqtrade.exchange.Binance.stoploss", side_effect=ExchangeError())
|
||||
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
|
||||
assert cancel_mock.call_count == 1
|
||||
assert log_has_re(r"Could not create trailing stoploss order for pair ETH/BTC\..*", caplog)
|
||||
@@ -1461,6 +1404,9 @@ def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Binance',
|
||||
stoploss=stoploss,
|
||||
stoploss_adjust=MagicMock(return_value=True),
|
||||
)
|
||||
@@ -1501,7 +1447,7 @@ def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee,
|
||||
}
|
||||
})
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hanging)
|
||||
mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hanging)
|
||||
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
@@ -1515,8 +1461,8 @@ def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee,
|
||||
|
||||
cancel_order_mock = MagicMock()
|
||||
stoploss_order_mock = MagicMock(return_value={'id': 13434334})
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', cancel_order_mock)
|
||||
mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss_order_mock)
|
||||
mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock)
|
||||
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock)
|
||||
|
||||
# stoploss should not be updated as the interval is 60 seconds
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
@@ -1747,6 +1693,7 @@ def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> No
|
||||
open_rate=0.01,
|
||||
open_date=arrow.utcnow().datetime,
|
||||
amount=11,
|
||||
exchange="binance",
|
||||
)
|
||||
assert not freqtrade.update_trade_state(trade, None)
|
||||
assert log_has_re(r'Orderid for trade .* is empty.', caplog)
|
||||
@@ -1779,7 +1726,6 @@ def test_update_trade_state_withorderdict(default_conf, trades_for_order, limit_
|
||||
# fetch_order should not be called!!
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError))
|
||||
patch_exchange(mocker)
|
||||
Trade.session = MagicMock()
|
||||
amount = sum(x['amount'] for x in trades_for_order)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
trade = Trade(
|
||||
@@ -1805,7 +1751,6 @@ def test_update_trade_state_withorderdict_rounding_fee(default_conf, trades_for_
|
||||
# fetch_order should not be called!!
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError))
|
||||
patch_exchange(mocker)
|
||||
Trade.session = MagicMock()
|
||||
amount = sum(x['amount'] for x in trades_for_order)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
trade = Trade(
|
||||
@@ -1868,7 +1813,6 @@ def test_update_trade_state_sell(default_conf, trades_for_order, limit_sell_orde
|
||||
mocker.patch('freqtrade.wallets.Wallets.update', wallet_mock)
|
||||
|
||||
patch_exchange(mocker)
|
||||
Trade.session = MagicMock()
|
||||
amount = limit_sell_order["amount"]
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
wallet_mock.reset_mock()
|
||||
@@ -2016,7 +1960,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open,
|
||||
# if ROI is reached we must sell
|
||||
patch_get_signal(freqtrade, value=(False, True))
|
||||
assert freqtrade.handle_trade(trade)
|
||||
assert log_has("ETH/BTC - Required profit reached. sell_flag=True, sell_type=SellType.ROI",
|
||||
assert log_has("ETH/BTC - Required profit reached. sell_type=SellType.ROI",
|
||||
caplog)
|
||||
|
||||
|
||||
@@ -2045,7 +1989,7 @@ def test_handle_trade_use_sell_signal(
|
||||
|
||||
patch_get_signal(freqtrade, value=(False, True))
|
||||
assert freqtrade.handle_trade(trade)
|
||||
assert log_has("ETH/BTC - Sell signal received. sell_flag=True, sell_type=SellType.SELL_SIGNAL",
|
||||
assert log_has("ETH/BTC - Sell signal received. sell_type=SellType.SELL_SIGNAL",
|
||||
caplog)
|
||||
|
||||
|
||||
@@ -2110,7 +2054,7 @@ def test_check_handle_timedout_buy_usercustom(default_conf, ticker, limit_buy_or
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
Trade.session.add(open_trade)
|
||||
Trade.query.session.add(open_trade)
|
||||
|
||||
# Ensure default is to return empty (so not mocked yet)
|
||||
freqtrade.check_handle_timedout()
|
||||
@@ -2161,7 +2105,7 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, op
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
Trade.session.add(open_trade)
|
||||
Trade.query.session.add(open_trade)
|
||||
|
||||
freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
|
||||
# check it does cancel buy orders over the time limit
|
||||
@@ -2191,7 +2135,7 @@ def test_check_handle_cancelled_buy(default_conf, ticker, limit_buy_order_old, o
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
Trade.session.add(open_trade)
|
||||
Trade.query.session.add(open_trade)
|
||||
|
||||
# check it does cancel buy orders over the time limit
|
||||
freqtrade.check_handle_timedout()
|
||||
@@ -2218,7 +2162,7 @@ def test_check_handle_timedout_buy_exception(default_conf, ticker, limit_buy_ord
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
Trade.session.add(open_trade)
|
||||
Trade.query.session.add(open_trade)
|
||||
|
||||
# check it does cancel buy orders over the time limit
|
||||
freqtrade.check_handle_timedout()
|
||||
@@ -2248,7 +2192,7 @@ def test_check_handle_timedout_sell_usercustom(default_conf, ticker, limit_sell_
|
||||
open_trade.close_profit_abs = 0.001
|
||||
open_trade.is_open = False
|
||||
|
||||
Trade.session.add(open_trade)
|
||||
Trade.query.session.add(open_trade)
|
||||
# Ensure default is false
|
||||
freqtrade.check_handle_timedout()
|
||||
assert cancel_order_mock.call_count == 0
|
||||
@@ -2296,7 +2240,7 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old,
|
||||
open_trade.close_profit_abs = 0.001
|
||||
open_trade.is_open = False
|
||||
|
||||
Trade.session.add(open_trade)
|
||||
Trade.query.session.add(open_trade)
|
||||
|
||||
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
|
||||
# check it does cancel sell orders over the time limit
|
||||
@@ -2327,7 +2271,7 @@ def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old,
|
||||
open_trade.close_date = arrow.utcnow().shift(minutes=-601).datetime
|
||||
open_trade.is_open = False
|
||||
|
||||
Trade.session.add(open_trade)
|
||||
Trade.query.session.add(open_trade)
|
||||
|
||||
# check it does cancel sell orders over the time limit
|
||||
freqtrade.check_handle_timedout()
|
||||
@@ -2353,13 +2297,13 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
Trade.session.add(open_trade)
|
||||
Trade.query.session.add(open_trade)
|
||||
|
||||
# check it does cancel buy orders over the time limit
|
||||
# note this is for a partially-complete buy order
|
||||
freqtrade.check_handle_timedout()
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 2
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
assert len(trades) == 1
|
||||
assert trades[0].amount == 23.0
|
||||
@@ -2386,7 +2330,7 @@ def test_check_handle_timedout_partial_fee(default_conf, ticker, open_trade, cap
|
||||
|
||||
open_trade.fee_open = fee()
|
||||
open_trade.fee_close = fee()
|
||||
Trade.session.add(open_trade)
|
||||
Trade.query.session.add(open_trade)
|
||||
# cancelling a half-filled order should update the amount to the bought amount
|
||||
# and apply fees if necessary.
|
||||
freqtrade.check_handle_timedout()
|
||||
@@ -2394,7 +2338,7 @@ def test_check_handle_timedout_partial_fee(default_conf, ticker, open_trade, cap
|
||||
assert log_has_re(r"Applying fee on amount for Trade.*", caplog)
|
||||
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 2
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
assert len(trades) == 1
|
||||
# Verify that trade has been updated
|
||||
@@ -2426,7 +2370,7 @@ def test_check_handle_timedout_partial_except(default_conf, ticker, open_trade,
|
||||
|
||||
open_trade.fee_open = fee()
|
||||
open_trade.fee_close = fee()
|
||||
Trade.session.add(open_trade)
|
||||
Trade.query.session.add(open_trade)
|
||||
# cancelling a half-filled order should update the amount to the bought amount
|
||||
# and apply fees if necessary.
|
||||
freqtrade.check_handle_timedout()
|
||||
@@ -2434,7 +2378,7 @@ def test_check_handle_timedout_partial_except(default_conf, ticker, open_trade,
|
||||
assert log_has_re(r"Could not update trade amount: .*", caplog)
|
||||
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 2
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
assert len(trades) == 1
|
||||
# Verify that trade has been updated
|
||||
@@ -2463,7 +2407,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
Trade.session.add(open_trade)
|
||||
Trade.query.session.add(open_trade)
|
||||
|
||||
freqtrade.check_handle_timedout()
|
||||
assert log_has_re(r"Cannot query order for Trade\(id=1, pair=ETH/BTC, amount=90.99181073, "
|
||||
@@ -2486,15 +2430,23 @@ def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> Non
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade._notify_buy_cancel = MagicMock()
|
||||
|
||||
Trade.session = MagicMock()
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/ETH'
|
||||
trade.pair = 'LTC/USDT'
|
||||
trade.open_rate = 200
|
||||
limit_buy_order['filled'] = 0.0
|
||||
limit_buy_order['status'] = 'open'
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
assert freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
cancel_order_mock.reset_mock()
|
||||
caplog.clear()
|
||||
limit_buy_order['filled'] = 0.01
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert log_has_re("Order .* for .* not cancelled, as the filled amount.* unsellable.*", caplog)
|
||||
|
||||
caplog.clear()
|
||||
cancel_order_mock.reset_mock()
|
||||
limit_buy_order['filled'] = 2
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
@@ -2520,7 +2472,6 @@ def test_handle_cancel_buy_exchanges(mocker, caplog, default_conf,
|
||||
nofiy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_buy_cancel')
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
Trade.session = MagicMock()
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/ETH'
|
||||
@@ -2549,9 +2500,9 @@ def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order,
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade._notify_buy_cancel = MagicMock()
|
||||
|
||||
Trade.session = MagicMock()
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/ETH'
|
||||
trade.pair = 'LTC/USDT'
|
||||
trade.open_rate = 200
|
||||
limit_buy_order['filled'] = 0.0
|
||||
limit_buy_order['status'] = 'open'
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
@@ -2652,22 +2603,24 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
|
||||
fetch_ticker=ticker_sell_up
|
||||
)
|
||||
# Prevented sell ...
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI)
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||
assert rpc_mock.call_count == 0
|
||||
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
||||
|
||||
# Repatch with true
|
||||
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
|
||||
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI)
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
||||
|
||||
assert rpc_mock.call_count == 1
|
||||
last_msg = rpc_mock.call_args_list[-1][0][0]
|
||||
assert {
|
||||
'trade_id': 1,
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'type': RPCMessageType.SELL,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'gain': 'profit',
|
||||
'limit': 1.172e-05,
|
||||
@@ -2682,6 +2635,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
|
||||
'sell_reason': SellType.ROI.value,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
'close_rate': ANY,
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -2710,14 +2664,14 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
|
||||
)
|
||||
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
|
||||
sell_reason=SellType.STOP_LOSS)
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||
|
||||
assert rpc_mock.call_count == 2
|
||||
last_msg = rpc_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.044e-05,
|
||||
@@ -2732,6 +2686,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
|
||||
'sell_reason': SellType.STOP_LOSS.value,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
'close_rate': ANY,
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -2766,15 +2721,15 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
|
||||
|
||||
trade.stop_loss = 0.00001099 * 0.99
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
|
||||
sell_reason=SellType.STOP_LOSS)
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||
|
||||
assert rpc_mock.call_count == 2
|
||||
last_msg = rpc_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.08801e-05,
|
||||
@@ -2789,7 +2744,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
|
||||
'sell_reason': SellType.STOP_LOSS.value,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
|
||||
'close_rate': ANY,
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -2812,14 +2767,13 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, c
|
||||
freqtrade.enter_positions()
|
||||
|
||||
trade = Trade.query.first()
|
||||
Trade.session = MagicMock()
|
||||
PairLock.session = MagicMock()
|
||||
|
||||
freqtrade.config['dry_run'] = False
|
||||
trade.stoploss_order_id = "abcd"
|
||||
|
||||
freqtrade.execute_sell(trade=trade, limit=1234,
|
||||
sell_reason=SellType.STOP_LOSS)
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||
assert sellmock.call_count == 1
|
||||
assert log_has('Could not cancel stoploss order abcd', caplog)
|
||||
|
||||
@@ -2869,12 +2823,12 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke
|
||||
)
|
||||
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=SellType.SELL_SIGNAL)
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
assert cancel_order.call_count == 1
|
||||
assert rpc_mock.call_count == 2
|
||||
assert rpc_mock.call_count == 3
|
||||
|
||||
|
||||
def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, fee,
|
||||
@@ -2942,7 +2896,10 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, f
|
||||
assert trade.stoploss_order_id is None
|
||||
assert trade.is_open is False
|
||||
assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value
|
||||
assert rpc_mock.call_count == 2
|
||||
assert rpc_mock.call_count == 3
|
||||
assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.BUY
|
||||
assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.BUY_FILL
|
||||
assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
|
||||
|
||||
|
||||
def test_execute_sell_market_order(default_conf, ticker, fee,
|
||||
@@ -2971,17 +2928,18 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
|
||||
)
|
||||
freqtrade.config['order_types']['sell'] = 'market'
|
||||
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI)
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||
|
||||
assert not trade.is_open
|
||||
assert trade.close_profit == 0.0620716
|
||||
|
||||
assert rpc_mock.call_count == 2
|
||||
assert rpc_mock.call_count == 3
|
||||
last_msg = rpc_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.172e-05,
|
||||
@@ -2996,6 +2954,7 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
|
||||
'sell_reason': SellType.ROI.value,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
'close_rate': ANY,
|
||||
|
||||
} == last_msg
|
||||
|
||||
@@ -3024,8 +2983,9 @@ def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee,
|
||||
fetch_ticker=ticker_sell_up
|
||||
)
|
||||
|
||||
sell_reason = SellCheckTuple(sell_type=SellType.ROI)
|
||||
assert not freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=SellType.ROI)
|
||||
sell_reason=sell_reason)
|
||||
assert mock_insuf.call_count == 1
|
||||
|
||||
|
||||
@@ -3118,7 +3078,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, limit_buy_o
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple(
|
||||
sell_flag=False, sell_type=SellType.NONE))
|
||||
sell_type=SellType.NONE))
|
||||
freqtrade.enter_positions()
|
||||
|
||||
trade = Trade.query.first()
|
||||
@@ -3267,7 +3227,7 @@ def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplo
|
||||
)
|
||||
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
|
||||
sell_reason=SellType.STOP_LOSS)
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||
trade.close(ticker_sell_down()['bid'])
|
||||
assert freqtrade.strategy.is_pair_locked(trade.pair)
|
||||
|
||||
@@ -3964,7 +3924,7 @@ def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order_open,
|
||||
assert trade.stake_amount == 0.001
|
||||
assert trade.is_open
|
||||
assert trade.open_date is not None
|
||||
assert trade.exchange == 'bittrex'
|
||||
assert trade.exchange == 'binance'
|
||||
|
||||
assert len(Trade.query.all()) == 1
|
||||
|
||||
@@ -4420,9 +4380,9 @@ def test_reupdate_buy_order_fees(mocker, default_conf, fee, caplog):
|
||||
is_open=True,
|
||||
amount=20,
|
||||
open_rate=0.01,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
)
|
||||
Trade.session.add(trade)
|
||||
Trade.query.session.add(trade)
|
||||
|
||||
freqtrade.reupdate_buy_order_fees(trade)
|
||||
assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
|
||||
|
@@ -51,8 +51,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 +63,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 +89,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 +156,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 +177,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 +198,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
|
||||
|
@@ -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):
|
||||
|
@@ -7,7 +7,7 @@ from unittest.mock import MagicMock
|
||||
|
||||
import arrow
|
||||
import pytest
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy import create_engine, inspect
|
||||
|
||||
from freqtrade import constants
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
@@ -18,8 +18,8 @@ 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, tmpdir):
|
||||
@@ -64,7 +64,7 @@ def test_init_dryrun_db(default_conf, tmpdir):
|
||||
|
||||
|
||||
@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.
|
||||
|
||||
@@ -102,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
|
||||
@@ -142,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'
|
||||
@@ -177,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'
|
||||
@@ -205,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
|
||||
@@ -233,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'
|
||||
@@ -250,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
|
||||
@@ -274,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'):
|
||||
@@ -290,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
|
||||
@@ -311,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
|
||||
@@ -336,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
|
||||
@@ -370,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
|
||||
@@ -388,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):
|
||||
@@ -400,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',
|
||||
@@ -412,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(
|
||||
@@ -425,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
|
||||
@@ -463,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,
|
||||
@@ -472,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,
|
||||
@@ -500,7 +503,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
|
||||
@@ -624,6 +627,63 @@ 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
|
||||
engine.execute("alter table orders rename to orders_bak")
|
||||
inspector = inspect(engine)
|
||||
|
||||
for index in inspector.get_indexes('orders_bak'):
|
||||
engine.execute(f"drop index {index['name']}")
|
||||
# Recreate table
|
||||
engine.execute("""
|
||||
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)
|
||||
)
|
||||
""")
|
||||
|
||||
engine.execute("""
|
||||
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):
|
||||
"""
|
||||
@@ -694,7 +754,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,
|
||||
)
|
||||
@@ -746,7 +806,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,
|
||||
)
|
||||
|
||||
@@ -771,11 +831,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):
|
||||
@@ -790,7 +855,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()
|
||||
@@ -799,11 +864,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,
|
||||
@@ -843,7 +906,7 @@ def test_to_json(default_conf, fee):
|
||||
'max_rate': None,
|
||||
'strategy': None,
|
||||
'timeframe': None,
|
||||
'exchange': 'bittrex',
|
||||
'exchange': 'binance',
|
||||
}
|
||||
|
||||
# Simulate dry_run entries
|
||||
@@ -858,17 +921,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,
|
||||
@@ -910,7 +971,7 @@ def test_to_json(default_conf, fee):
|
||||
'sell_order_status': None,
|
||||
'strategy': None,
|
||||
'timeframe': None,
|
||||
'exchange': 'bittrex',
|
||||
'exchange': 'binance',
|
||||
}
|
||||
|
||||
|
||||
@@ -923,7 +984,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,
|
||||
)
|
||||
@@ -933,7 +994,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)
|
||||
@@ -982,7 +1043,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,
|
||||
)
|
||||
@@ -1021,7 +1082,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,
|
||||
)
|
||||
@@ -1084,6 +1145,13 @@ def test_get_trades_proxy(fee, use_db):
|
||||
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):
|
||||
|
||||
@@ -1217,11 +1285,24 @@ def test_Trade_object_idem():
|
||||
trade = vars(Trade)
|
||||
localtrade = vars(LocalTrade)
|
||||
|
||||
excludes = (
|
||||
'delete',
|
||||
'session',
|
||||
'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 ('delete', 'session', 'query', 'open_date')):
|
||||
if (not item.startswith('_') and item not in excludes):
|
||||
assert item in localtrade
|
||||
|
||||
# Fails if only a column is added without corresponding parent field
|
||||
|
@@ -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):
|
||||
|
@@ -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
|
||||
|
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