Merge branch 'develop' into interface_ordertimeoutcallback
This commit is contained in:
116
tests/commands/test_build_config.py
Normal file
116
tests/commands/test_build_config.py
Normal file
@@ -0,0 +1,116 @@
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
import rapidjson
|
||||
|
||||
from freqtrade.commands.build_config_commands import (ask_user_config,
|
||||
ask_user_overwrite,
|
||||
start_new_config,
|
||||
validate_is_float,
|
||||
validate_is_int)
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from tests.conftest import get_args, log_has_re
|
||||
|
||||
|
||||
def test_validate_is_float():
|
||||
assert validate_is_float('2.0')
|
||||
assert validate_is_float('2.1')
|
||||
assert validate_is_float('0.1')
|
||||
assert validate_is_float('-0.5')
|
||||
assert not validate_is_float('-0.5e')
|
||||
|
||||
|
||||
def test_validate_is_int():
|
||||
assert validate_is_int('2')
|
||||
assert validate_is_int('6')
|
||||
assert validate_is_int('-1')
|
||||
assert validate_is_int('500')
|
||||
assert not validate_is_int('2.0')
|
||||
assert not validate_is_int('2.1')
|
||||
assert not validate_is_int('-2.1')
|
||||
assert not validate_is_int('-ee')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('exchange', ['bittrex', 'binance', 'kraken', 'ftx'])
|
||||
def test_start_new_config(mocker, caplog, exchange):
|
||||
wt_mock = mocker.patch.object(Path, "write_text", MagicMock())
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||
unlink_mock = mocker.patch.object(Path, "unlink", MagicMock())
|
||||
mocker.patch('freqtrade.commands.build_config_commands.ask_user_overwrite', return_value=True)
|
||||
|
||||
sample_selections = {
|
||||
'max_open_trades': 3,
|
||||
'stake_currency': 'USDT',
|
||||
'stake_amount': 100,
|
||||
'fiat_display_currency': 'EUR',
|
||||
'ticker_interval': '15m',
|
||||
'dry_run': True,
|
||||
'exchange_name': exchange,
|
||||
'exchange_key': 'sampleKey',
|
||||
'exchange_secret': 'Samplesecret',
|
||||
'telegram': False,
|
||||
'telegram_token': 'asdf1244',
|
||||
'telegram_chat_id': '1144444',
|
||||
}
|
||||
mocker.patch('freqtrade.commands.build_config_commands.ask_user_config',
|
||||
return_value=sample_selections)
|
||||
args = [
|
||||
"new-config",
|
||||
"--config",
|
||||
"coolconfig.json"
|
||||
]
|
||||
start_new_config(get_args(args))
|
||||
|
||||
assert log_has_re("Writing config to .*", caplog)
|
||||
assert wt_mock.call_count == 1
|
||||
assert unlink_mock.call_count == 1
|
||||
result = rapidjson.loads(wt_mock.call_args_list[0][0][0],
|
||||
parse_mode=rapidjson.PM_COMMENTS | rapidjson.PM_TRAILING_COMMAS)
|
||||
assert result['exchange']['name'] == exchange
|
||||
assert result['ticker_interval'] == '15m'
|
||||
|
||||
|
||||
def test_start_new_config_exists(mocker, caplog):
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.commands.build_config_commands.ask_user_overwrite', return_value=False)
|
||||
args = [
|
||||
"new-config",
|
||||
"--config",
|
||||
"coolconfig.json"
|
||||
]
|
||||
with pytest.raises(OperationalException, match=r"Configuration .* already exists\."):
|
||||
start_new_config(get_args(args))
|
||||
|
||||
|
||||
def test_ask_user_overwrite(mocker):
|
||||
"""
|
||||
Once https://github.com/tmbo/questionary/issues/35 is implemented, improve this test.
|
||||
"""
|
||||
prompt_mock = mocker.patch('freqtrade.commands.build_config_commands.prompt',
|
||||
return_value={'overwrite': False})
|
||||
assert not ask_user_overwrite(Path('test.json'))
|
||||
assert prompt_mock.call_count == 1
|
||||
|
||||
prompt_mock.reset_mock()
|
||||
prompt_mock = mocker.patch('freqtrade.commands.build_config_commands.prompt',
|
||||
return_value={'overwrite': True})
|
||||
assert ask_user_overwrite(Path('test.json'))
|
||||
assert prompt_mock.call_count == 1
|
||||
|
||||
|
||||
def test_ask_user_config(mocker):
|
||||
"""
|
||||
Once https://github.com/tmbo/questionary/issues/35 is implemented, improve this test.
|
||||
"""
|
||||
prompt_mock = mocker.patch('freqtrade.commands.build_config_commands.prompt',
|
||||
return_value={'overwrite': False})
|
||||
answers = ask_user_config()
|
||||
assert isinstance(answers, dict)
|
||||
assert prompt_mock.call_count == 1
|
||||
|
||||
prompt_mock = mocker.patch('freqtrade.commands.build_config_commands.prompt',
|
||||
return_value={})
|
||||
|
||||
with pytest.raises(OperationalException, match=r"User interrupted interactive questions\."):
|
||||
ask_user_config()
|
@@ -4,9 +4,10 @@ from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.commands import (start_create_userdir, start_download_data,
|
||||
start_hyperopt_list, start_hyperopt_show,
|
||||
start_list_exchanges, start_list_markets,
|
||||
from freqtrade.commands import (start_convert_data, start_create_userdir,
|
||||
start_download_data, start_hyperopt_list,
|
||||
start_hyperopt_show, start_list_exchanges,
|
||||
start_list_hyperopts, start_list_markets,
|
||||
start_list_strategies, start_list_timeframes,
|
||||
start_new_hyperopt, start_new_strategy,
|
||||
start_test_pairlist, start_trading)
|
||||
@@ -639,7 +640,7 @@ def test_start_list_strategies(mocker, caplog, capsys):
|
||||
args = [
|
||||
"list-strategies",
|
||||
"--strategy-path",
|
||||
str(Path(__file__).parent.parent / "strategy"),
|
||||
str(Path(__file__).parent.parent / "strategy" / "strats"),
|
||||
"-1"
|
||||
]
|
||||
pargs = get_args(args)
|
||||
@@ -654,7 +655,7 @@ def test_start_list_strategies(mocker, caplog, capsys):
|
||||
args = [
|
||||
"list-strategies",
|
||||
"--strategy-path",
|
||||
str(Path(__file__).parent.parent / "strategy"),
|
||||
str(Path(__file__).parent.parent / "strategy" / "strats"),
|
||||
]
|
||||
pargs = get_args(args)
|
||||
# pargs['config'] = None
|
||||
@@ -665,6 +666,39 @@ def test_start_list_strategies(mocker, caplog, capsys):
|
||||
assert "DefaultStrategy" in captured.out
|
||||
|
||||
|
||||
def test_start_list_hyperopts(mocker, caplog, capsys):
|
||||
|
||||
args = [
|
||||
"list-hyperopts",
|
||||
"--hyperopt-path",
|
||||
str(Path(__file__).parent.parent / "optimize"),
|
||||
"-1"
|
||||
]
|
||||
pargs = get_args(args)
|
||||
# pargs['config'] = None
|
||||
start_list_hyperopts(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert "TestHyperoptLegacy" not in captured.out
|
||||
assert "legacy_hyperopt.py" not in captured.out
|
||||
assert "DefaultHyperOpt" in captured.out
|
||||
assert "test_hyperopt.py" not in captured.out
|
||||
|
||||
# Test regular output
|
||||
args = [
|
||||
"list-hyperopts",
|
||||
"--hyperopt-path",
|
||||
str(Path(__file__).parent.parent / "optimize"),
|
||||
]
|
||||
pargs = get_args(args)
|
||||
# pargs['config'] = None
|
||||
start_list_hyperopts(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert "TestHyperoptLegacy" not in captured.out
|
||||
assert "legacy_hyperopt.py" not in captured.out
|
||||
assert "DefaultHyperOpt" in captured.out
|
||||
assert "test_hyperopt.py" in captured.out
|
||||
|
||||
|
||||
def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
|
||||
patch_exchange(mocker, mock_markets=True)
|
||||
mocker.patch.multiple('freqtrade.exchange.Exchange',
|
||||
@@ -744,6 +778,121 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results):
|
||||
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",
|
||||
"--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",
|
||||
"--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",
|
||||
"--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",
|
||||
"--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",
|
||||
"--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",
|
||||
"--profitable",
|
||||
"--no-details",
|
||||
"--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",
|
||||
"--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"])
|
||||
|
||||
|
||||
def test_hyperopt_show(mocker, capsys, hyperopt_results):
|
||||
@@ -824,3 +973,47 @@ def test_hyperopt_show(mocker, capsys, hyperopt_results):
|
||||
with pytest.raises(OperationalException,
|
||||
match="The index of the epoch to show should be less than 4."):
|
||||
start_hyperopt_show(pargs)
|
||||
|
||||
|
||||
def test_convert_data(mocker, testdatadir):
|
||||
ohlcv_mock = mocker.patch("freqtrade.commands.data_commands.convert_ohlcv_format")
|
||||
trades_mock = mocker.patch("freqtrade.commands.data_commands.convert_trades_format")
|
||||
args = [
|
||||
"convert-data",
|
||||
"--format-from",
|
||||
"json",
|
||||
"--format-to",
|
||||
"jsongz",
|
||||
"--datadir",
|
||||
str(testdatadir),
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_convert_data(pargs, True)
|
||||
assert trades_mock.call_count == 0
|
||||
assert ohlcv_mock.call_count == 1
|
||||
assert ohlcv_mock.call_args[1]['convert_from'] == 'json'
|
||||
assert ohlcv_mock.call_args[1]['convert_to'] == 'jsongz'
|
||||
assert ohlcv_mock.call_args[1]['erase'] is False
|
||||
|
||||
|
||||
def test_convert_data_trades(mocker, testdatadir):
|
||||
ohlcv_mock = mocker.patch("freqtrade.commands.data_commands.convert_ohlcv_format")
|
||||
trades_mock = mocker.patch("freqtrade.commands.data_commands.convert_trades_format")
|
||||
args = [
|
||||
"convert-trade-data",
|
||||
"--format-from",
|
||||
"jsongz",
|
||||
"--format-to",
|
||||
"json",
|
||||
"--datadir",
|
||||
str(testdatadir),
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_convert_data(pargs, False)
|
||||
assert ohlcv_mock.call_count == 0
|
||||
assert trades_mock.call_count == 1
|
||||
assert trades_mock.call_args[1]['convert_from'] == 'jsongz'
|
||||
assert trades_mock.call_args[1]['convert_to'] == 'json'
|
||||
assert trades_mock.call_args[1]['erase'] is False
|
||||
|
@@ -257,6 +257,7 @@ def default_conf(testdatadir):
|
||||
"db_url": "sqlite://",
|
||||
"user_data_dir": Path("user_data"),
|
||||
"verbosity": 3,
|
||||
"strategy_path": str(Path(__file__).parent / "strategy" / "strats"),
|
||||
"strategy": "DefaultStrategy"
|
||||
}
|
||||
return configuration
|
||||
|
@@ -1,9 +1,15 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
import logging
|
||||
|
||||
from freqtrade.data.converter import parse_ticker_dataframe, ohlcv_fill_up_missing_data
|
||||
from freqtrade.data.history import load_pair_history, validate_backtest_data, get_timerange
|
||||
from freqtrade.configuration.timerange import TimeRange
|
||||
from freqtrade.data.converter import (convert_ohlcv_format,
|
||||
convert_trades_format,
|
||||
ohlcv_fill_up_missing_data,
|
||||
parse_ticker_dataframe, trim_dataframe)
|
||||
from freqtrade.data.history import (get_timerange, load_data,
|
||||
load_pair_history, validate_backtest_data)
|
||||
from tests.conftest import log_has
|
||||
from tests.data.test_history import _backup_file, _clean_test_file
|
||||
|
||||
|
||||
def test_dataframe_correct_columns(result):
|
||||
@@ -145,3 +151,113 @@ def test_ohlcv_drop_incomplete(caplog):
|
||||
assert len(data) == 3
|
||||
|
||||
assert log_has("Dropping last candle", caplog)
|
||||
|
||||
|
||||
def test_trim_dataframe(testdatadir) -> None:
|
||||
data = load_data(
|
||||
datadir=testdatadir,
|
||||
timeframe='1m',
|
||||
pairs=['UNITTEST/BTC']
|
||||
)['UNITTEST/BTC']
|
||||
min_date = int(data.iloc[0]['date'].timestamp())
|
||||
max_date = int(data.iloc[-1]['date'].timestamp())
|
||||
data_modify = data.copy()
|
||||
|
||||
# Remove first 30 minutes (1800 s)
|
||||
tr = TimeRange('date', None, min_date + 1800, 0)
|
||||
data_modify = trim_dataframe(data_modify, tr)
|
||||
assert not data_modify.equals(data)
|
||||
assert len(data_modify) < len(data)
|
||||
assert len(data_modify) == len(data) - 30
|
||||
assert all(data_modify.iloc[-1] == data.iloc[-1])
|
||||
assert all(data_modify.iloc[0] == data.iloc[30])
|
||||
|
||||
data_modify = data.copy()
|
||||
# Remove last 30 minutes (1800 s)
|
||||
tr = TimeRange(None, 'date', 0, max_date - 1800)
|
||||
data_modify = trim_dataframe(data_modify, tr)
|
||||
assert not data_modify.equals(data)
|
||||
assert len(data_modify) < len(data)
|
||||
assert len(data_modify) == len(data) - 30
|
||||
assert all(data_modify.iloc[0] == data.iloc[0])
|
||||
assert all(data_modify.iloc[-1] == data.iloc[-31])
|
||||
|
||||
data_modify = data.copy()
|
||||
# Remove first 25 and last 30 minutes (1800 s)
|
||||
tr = TimeRange('date', 'date', min_date + 1500, max_date - 1800)
|
||||
data_modify = trim_dataframe(data_modify, tr)
|
||||
assert not data_modify.equals(data)
|
||||
assert len(data_modify) < len(data)
|
||||
assert len(data_modify) == len(data) - 55
|
||||
# first row matches 25th original row
|
||||
assert all(data_modify.iloc[0] == data.iloc[25])
|
||||
|
||||
|
||||
def test_convert_trades_format(mocker, default_conf, testdatadir):
|
||||
file = testdatadir / "XRP_ETH-trades.json.gz"
|
||||
file_new = testdatadir / "XRP_ETH-trades.json"
|
||||
_backup_file(file, copy_file=True)
|
||||
default_conf['datadir'] = testdatadir
|
||||
|
||||
assert not file_new.exists()
|
||||
|
||||
convert_trades_format(default_conf, convert_from='jsongz',
|
||||
convert_to='json', erase=False)
|
||||
|
||||
assert file_new.exists()
|
||||
assert file.exists()
|
||||
|
||||
# Remove original file
|
||||
file.unlink()
|
||||
# Convert back
|
||||
convert_trades_format(default_conf, convert_from='json',
|
||||
convert_to='jsongz', erase=True)
|
||||
|
||||
assert file.exists()
|
||||
assert not file_new.exists()
|
||||
|
||||
_clean_test_file(file)
|
||||
if file_new.exists():
|
||||
file_new.unlink()
|
||||
|
||||
|
||||
def test_convert_ohlcv_format(mocker, default_conf, testdatadir):
|
||||
file1 = testdatadir / "XRP_ETH-5m.json"
|
||||
file1_new = testdatadir / "XRP_ETH-5m.json.gz"
|
||||
file2 = testdatadir / "XRP_ETH-1m.json"
|
||||
file2_new = testdatadir / "XRP_ETH-1m.json.gz"
|
||||
_backup_file(file1, copy_file=True)
|
||||
_backup_file(file2, copy_file=True)
|
||||
default_conf['datadir'] = testdatadir
|
||||
default_conf['pairs'] = ['XRP_ETH']
|
||||
default_conf['timeframes'] = ['1m', '5m']
|
||||
|
||||
assert not file1_new.exists()
|
||||
assert not file2_new.exists()
|
||||
|
||||
convert_ohlcv_format(default_conf, convert_from='json',
|
||||
convert_to='jsongz', erase=False)
|
||||
|
||||
assert file1_new.exists()
|
||||
assert file2_new.exists()
|
||||
assert file1.exists()
|
||||
assert file2.exists()
|
||||
|
||||
# Remove original files
|
||||
file1.unlink()
|
||||
file2.unlink()
|
||||
# Convert back
|
||||
convert_ohlcv_format(default_conf, convert_from='jsongz',
|
||||
convert_to='json', erase=True)
|
||||
|
||||
assert file1.exists()
|
||||
assert file2.exists()
|
||||
assert not file1_new.exists()
|
||||
assert not file2_new.exists()
|
||||
|
||||
_clean_test_file(file1)
|
||||
_clean_test_file(file2)
|
||||
if file1_new.exists():
|
||||
file1_new.unlink()
|
||||
if file2_new.exists():
|
||||
file2_new.unlink()
|
||||
|
@@ -7,24 +7,24 @@ from shutil import copyfile
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
import arrow
|
||||
import pytest
|
||||
from pandas import DataFrame
|
||||
from pandas.testing import assert_frame_equal
|
||||
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.data.history import (_download_pair_history,
|
||||
_download_trades_history,
|
||||
_load_cached_data_for_updating,
|
||||
convert_trades_to_ohlcv, get_timerange,
|
||||
load_data, load_pair_history,
|
||||
load_tickerdata_file, pair_data_filename,
|
||||
pair_trades_filename,
|
||||
refresh_backtest_ohlcv_data,
|
||||
refresh_backtest_trades_data,
|
||||
refresh_data,
|
||||
trim_dataframe, trim_tickerlist,
|
||||
validate_backtest_data)
|
||||
from freqtrade.data.converter import parse_ticker_dataframe
|
||||
from freqtrade.data.history.history_utils import (
|
||||
_download_pair_history, _download_trades_history,
|
||||
_load_cached_data_for_updating, convert_trades_to_ohlcv, get_timerange,
|
||||
load_data, load_pair_history, refresh_backtest_ohlcv_data,
|
||||
refresh_backtest_trades_data, refresh_data, validate_backtest_data)
|
||||
from freqtrade.data.history.idatahandler import (IDataHandler, get_datahandler,
|
||||
get_datahandlerclass)
|
||||
from freqtrade.data.history.jsondatahandler import (JsonDataHandler,
|
||||
JsonGzDataHandler)
|
||||
from freqtrade.exchange import timeframe_to_minutes
|
||||
from freqtrade.misc import file_dump_json
|
||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||
from freqtrade.resolvers import StrategyResolver
|
||||
from tests.conftest import (get_patched_exchange, log_has, log_has_re,
|
||||
patch_exchange)
|
||||
|
||||
@@ -96,8 +96,9 @@ def test_load_data_1min_ticker(ticker_history, mocker, caplog, testdatadir) -> N
|
||||
|
||||
|
||||
def test_load_data_startup_candles(mocker, caplog, default_conf, testdatadir) -> None:
|
||||
ltfmock = mocker.patch('freqtrade.data.history.load_tickerdata_file',
|
||||
MagicMock(return_value=None))
|
||||
ltfmock = mocker.patch(
|
||||
'freqtrade.data.history.jsondatahandler.JsonDataHandler._ohlcv_load',
|
||||
MagicMock(return_value=DataFrame()))
|
||||
timerange = TimeRange('date', None, 1510639620, 0)
|
||||
load_pair_history(pair='UNITTEST/BTC', timeframe='1m',
|
||||
datadir=testdatadir, timerange=timerange,
|
||||
@@ -143,27 +144,52 @@ def test_testdata_path(testdatadir) -> None:
|
||||
assert str(Path('tests') / 'testdata') in str(testdatadir)
|
||||
|
||||
|
||||
def test_pair_data_filename():
|
||||
fn = pair_data_filename(Path('freqtrade/hello/world'), 'ETH/BTC', '5m')
|
||||
@pytest.mark.parametrize("pair,expected_result", [
|
||||
("ETH/BTC", 'freqtrade/hello/world/ETH_BTC-5m.json'),
|
||||
("Fabric Token/ETH", 'freqtrade/hello/world/Fabric_Token_ETH-5m.json'),
|
||||
("ETHH20", 'freqtrade/hello/world/ETHH20-5m.json'),
|
||||
(".XBTBON2H", 'freqtrade/hello/world/_XBTBON2H-5m.json'),
|
||||
("ETHUSD.d", 'freqtrade/hello/world/ETHUSD_d-5m.json'),
|
||||
("ACC_OLD/BTC", 'freqtrade/hello/world/ACC_OLD_BTC-5m.json'),
|
||||
])
|
||||
def test_json_pair_data_filename(pair, expected_result):
|
||||
fn = JsonDataHandler._pair_data_filename(Path('freqtrade/hello/world'), pair, '5m')
|
||||
assert isinstance(fn, Path)
|
||||
assert fn == Path('freqtrade/hello/world/ETH_BTC-5m.json')
|
||||
|
||||
|
||||
def test_pair_trades_filename():
|
||||
fn = pair_trades_filename(Path('freqtrade/hello/world'), 'ETH/BTC')
|
||||
assert fn == Path(expected_result)
|
||||
fn = JsonGzDataHandler._pair_data_filename(Path('freqtrade/hello/world'), pair, '5m')
|
||||
assert isinstance(fn, Path)
|
||||
assert fn == Path('freqtrade/hello/world/ETH_BTC-trades.json.gz')
|
||||
assert fn == Path(expected_result + '.gz')
|
||||
|
||||
|
||||
def test_load_cached_data_for_updating(mocker) -> None:
|
||||
datadir = Path(__file__).parent.parent.joinpath('testdata')
|
||||
@pytest.mark.parametrize("pair,expected_result", [
|
||||
("ETH/BTC", 'freqtrade/hello/world/ETH_BTC-trades.json'),
|
||||
("Fabric Token/ETH", 'freqtrade/hello/world/Fabric_Token_ETH-trades.json'),
|
||||
("ETHH20", 'freqtrade/hello/world/ETHH20-trades.json'),
|
||||
(".XBTBON2H", 'freqtrade/hello/world/_XBTBON2H-trades.json'),
|
||||
("ETHUSD.d", 'freqtrade/hello/world/ETHUSD_d-trades.json'),
|
||||
("ACC_OLD_BTC", 'freqtrade/hello/world/ACC_OLD_BTC-trades.json'),
|
||||
])
|
||||
def test_json_pair_trades_filename(pair, expected_result):
|
||||
fn = JsonDataHandler._pair_trades_filename(Path('freqtrade/hello/world'), pair)
|
||||
assert isinstance(fn, Path)
|
||||
assert fn == Path(expected_result)
|
||||
|
||||
fn = JsonGzDataHandler._pair_trades_filename(Path('freqtrade/hello/world'), pair)
|
||||
assert isinstance(fn, Path)
|
||||
assert fn == Path(expected_result + '.gz')
|
||||
|
||||
|
||||
def test_load_cached_data_for_updating(mocker, testdatadir) -> None:
|
||||
|
||||
data_handler = get_datahandler(testdatadir, 'json')
|
||||
|
||||
test_data = None
|
||||
test_filename = datadir.joinpath('UNITTEST_BTC-1m.json')
|
||||
test_filename = testdatadir.joinpath('UNITTEST_BTC-1m.json')
|
||||
with open(test_filename, "rt") as file:
|
||||
test_data = json.load(file)
|
||||
|
||||
# change now time to test 'line' cases
|
||||
test_data_df = parse_ticker_dataframe(test_data, '1m', 'UNITTEST/BTC',
|
||||
fill_missing=False, drop_incomplete=False)
|
||||
# now = last cached item + 1 hour
|
||||
now_ts = test_data[-1][0] / 1000 + 60 * 60
|
||||
mocker.patch('arrow.utcnow', return_value=arrow.get(now_ts))
|
||||
@@ -171,72 +197,36 @@ def test_load_cached_data_for_updating(mocker) -> None:
|
||||
# timeframe starts earlier than the cached data
|
||||
# should fully update data
|
||||
timerange = TimeRange('date', None, test_data[0][0] / 1000 - 1, 0)
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
assert data == []
|
||||
data, start_ts = _load_cached_data_for_updating('UNITTEST/BTC', '1m', timerange, data_handler)
|
||||
assert data.empty
|
||||
assert start_ts == test_data[0][0] - 1000
|
||||
|
||||
# same with 'line' timeframe
|
||||
num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 120
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m',
|
||||
TimeRange(None, 'line', 0, -num_lines))
|
||||
assert data == []
|
||||
assert start_ts < test_data[0][0] - 1
|
||||
|
||||
# timeframe starts in the center of the cached data
|
||||
# should return the chached data w/o the last item
|
||||
timerange = TimeRange('date', None, test_data[0][0] / 1000 + 1, 0)
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
assert data == test_data[:-1]
|
||||
assert test_data[-2][0] < start_ts < test_data[-1][0]
|
||||
data, start_ts = _load_cached_data_for_updating('UNITTEST/BTC', '1m', timerange, data_handler)
|
||||
|
||||
# same with 'line' timeframe
|
||||
num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 30
|
||||
timerange = TimeRange(None, 'line', 0, -num_lines)
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
assert data == test_data[:-1]
|
||||
assert test_data[-2][0] < start_ts < test_data[-1][0]
|
||||
assert_frame_equal(data, test_data_df.iloc[:-1])
|
||||
assert test_data[-2][0] <= start_ts < test_data[-1][0]
|
||||
|
||||
# timeframe starts after the chached data
|
||||
# should return the chached data w/o the last item
|
||||
timerange = TimeRange('date', None, test_data[-1][0] / 1000 + 1, 0)
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
assert data == test_data[:-1]
|
||||
assert test_data[-2][0] < start_ts < test_data[-1][0]
|
||||
|
||||
# Try loading last 30 lines.
|
||||
# Not supported by _load_cached_data_for_updating, we always need to get the full data.
|
||||
num_lines = 30
|
||||
timerange = TimeRange(None, 'line', 0, -num_lines)
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
assert data == test_data[:-1]
|
||||
assert test_data[-2][0] < start_ts < test_data[-1][0]
|
||||
|
||||
# no timeframe is set
|
||||
# should return the chached data w/o the last item
|
||||
num_lines = 30
|
||||
timerange = TimeRange(None, 'line', 0, -num_lines)
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
assert data == test_data[:-1]
|
||||
assert test_data[-2][0] < start_ts < test_data[-1][0]
|
||||
timerange = TimeRange('date', None, test_data[-1][0] / 1000 + 100, 0)
|
||||
data, start_ts = _load_cached_data_for_updating('UNITTEST/BTC', '1m', timerange, data_handler)
|
||||
assert_frame_equal(data, test_data_df.iloc[:-1])
|
||||
assert test_data[-2][0] <= start_ts < test_data[-1][0]
|
||||
|
||||
# no datafile exist
|
||||
# should return timestamp start time
|
||||
timerange = TimeRange('date', None, now_ts - 10000, 0)
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'NONEXIST/BTC', '1m', timerange)
|
||||
assert data == []
|
||||
data, start_ts = _load_cached_data_for_updating('NONEXIST/BTC', '1m', timerange, data_handler)
|
||||
assert data.empty
|
||||
assert start_ts == (now_ts - 10000) * 1000
|
||||
|
||||
# same with 'line' timeframe
|
||||
num_lines = 30
|
||||
timerange = TimeRange(None, 'line', 0, -num_lines)
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'NONEXIST/BTC', '1m', timerange)
|
||||
assert data == []
|
||||
assert start_ts == (now_ts - num_lines * 60) * 1000
|
||||
|
||||
# no datafile exist, no timeframe is set
|
||||
# should return an empty array and None
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'NONEXIST/BTC', '1m', None)
|
||||
assert data == []
|
||||
data, start_ts = _load_cached_data_for_updating('NONEXIST/BTC', '1m', None, data_handler)
|
||||
assert data.empty
|
||||
assert start_ts is None
|
||||
|
||||
|
||||
@@ -293,7 +283,9 @@ def test_download_pair_history2(mocker, default_conf, testdatadir) -> None:
|
||||
[1509836520000, 0.00162008, 0.00162008, 0.00162008, 0.00162008, 108.14853839],
|
||||
[1509836580000, 0.00161, 0.00161, 0.00161, 0.00161, 82.390199]
|
||||
]
|
||||
json_dump_mock = mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
|
||||
json_dump_mock = mocker.patch(
|
||||
'freqtrade.data.history.jsondatahandler.JsonDataHandler.ohlcv_store',
|
||||
return_value=None)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=tick)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
_download_pair_history(testdatadir, exchange, pair="UNITTEST/BTC", timeframe='1m')
|
||||
@@ -325,17 +317,6 @@ def test_download_backtesting_data_exception(ticker_history, mocker, caplog,
|
||||
)
|
||||
|
||||
|
||||
def test_load_tickerdata_file(testdatadir) -> None:
|
||||
# 7 does not exist in either format.
|
||||
assert not load_tickerdata_file(testdatadir, 'UNITTEST/BTC', '7m')
|
||||
# 1 exists only as a .json
|
||||
tickerdata = load_tickerdata_file(testdatadir, 'UNITTEST/BTC', '1m')
|
||||
assert _BTC_UNITTEST_LENGTH == len(tickerdata)
|
||||
# 8 .json is empty and will fail if it's loaded. .json.gz is a copy of 1.json
|
||||
tickerdata = load_tickerdata_file(testdatadir, 'UNITTEST/BTC', '8m')
|
||||
assert _BTC_UNITTEST_LENGTH == len(tickerdata)
|
||||
|
||||
|
||||
def test_load_partial_missing(testdatadir, caplog) -> None:
|
||||
# Make sure we start fresh - test missing data at start
|
||||
start = arrow.get('2018-01-01T00:00:00')
|
||||
@@ -361,6 +342,7 @@ def test_load_partial_missing(testdatadir, caplog) -> None:
|
||||
# timedifference in 5 minutes
|
||||
td = ((end - start).total_seconds() // 60 // 5) + 1
|
||||
assert td != len(tickerdata['UNITTEST/BTC'])
|
||||
|
||||
# Shift endtime with +5 - as last candle is dropped (partial candle)
|
||||
end_real = arrow.get(tickerdata['UNITTEST/BTC'].iloc[-1, 0]).shift(minutes=5)
|
||||
assert log_has(f'Missing data at end for pair '
|
||||
@@ -370,7 +352,7 @@ def test_load_partial_missing(testdatadir, caplog) -> None:
|
||||
|
||||
def test_init(default_conf, mocker) -> None:
|
||||
assert {} == load_data(
|
||||
datadir='',
|
||||
datadir=Path(''),
|
||||
pairs=[],
|
||||
timeframe=default_conf['ticker_interval']
|
||||
)
|
||||
@@ -379,110 +361,18 @@ def test_init(default_conf, mocker) -> None:
|
||||
def test_init_with_refresh(default_conf, mocker) -> None:
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
refresh_data(
|
||||
datadir='',
|
||||
datadir=Path(''),
|
||||
pairs=[],
|
||||
timeframe=default_conf['ticker_interval'],
|
||||
exchange=exchange
|
||||
)
|
||||
assert {} == load_data(
|
||||
datadir='',
|
||||
datadir=Path(''),
|
||||
pairs=[],
|
||||
timeframe=default_conf['ticker_interval']
|
||||
)
|
||||
|
||||
|
||||
def test_trim_tickerlist(testdatadir) -> None:
|
||||
file = testdatadir / 'UNITTEST_BTC-1m.json'
|
||||
with open(file) as data_file:
|
||||
ticker_list = json.load(data_file)
|
||||
ticker_list_len = len(ticker_list)
|
||||
|
||||
# Test the pattern ^(\d{8})-(\d{8})$
|
||||
# This pattern extract a window between the dates
|
||||
timerange = TimeRange('date', 'date', ticker_list[5][0] / 1000, ticker_list[10][0] / 1000 - 1)
|
||||
ticker = trim_tickerlist(ticker_list, timerange)
|
||||
ticker_len = len(ticker)
|
||||
|
||||
assert ticker_len == 5
|
||||
assert ticker_list[0] is not ticker[0] # The first element should be different
|
||||
assert ticker_list[5] is ticker[0] # The list starts at the index 5
|
||||
assert ticker_list[9] is ticker[-1] # The list ends at the index 9 (5 elements)
|
||||
|
||||
# Test the pattern ^-(\d{8})$
|
||||
# This pattern extracts elements from the start to the date
|
||||
timerange = TimeRange(None, 'date', 0, ticker_list[10][0] / 1000 - 1)
|
||||
ticker = trim_tickerlist(ticker_list, timerange)
|
||||
ticker_len = len(ticker)
|
||||
|
||||
assert ticker_len == 10
|
||||
assert ticker_list[0] is ticker[0] # The start of the list is included
|
||||
assert ticker_list[9] is ticker[-1] # The element 10 is not included
|
||||
|
||||
# Test the pattern ^(\d{8})-$
|
||||
# This pattern extracts elements from the date to now
|
||||
timerange = TimeRange('date', None, ticker_list[10][0] / 1000 - 1, None)
|
||||
ticker = trim_tickerlist(ticker_list, timerange)
|
||||
ticker_len = len(ticker)
|
||||
|
||||
assert ticker_len == ticker_list_len - 10
|
||||
assert ticker_list[10] is ticker[0] # The first element is element #10
|
||||
assert ticker_list[-1] is ticker[-1] # The last element is the same
|
||||
|
||||
# Test a wrong pattern
|
||||
# This pattern must return the list unchanged
|
||||
timerange = TimeRange(None, None, None, 5)
|
||||
ticker = trim_tickerlist(ticker_list, timerange)
|
||||
ticker_len = len(ticker)
|
||||
|
||||
assert ticker_list_len == ticker_len
|
||||
|
||||
# passing empty list
|
||||
timerange = TimeRange(None, None, None, 5)
|
||||
ticker = trim_tickerlist([], timerange)
|
||||
assert 0 == len(ticker)
|
||||
assert not ticker
|
||||
|
||||
|
||||
def test_trim_dataframe(testdatadir) -> None:
|
||||
data = load_data(
|
||||
datadir=testdatadir,
|
||||
timeframe='1m',
|
||||
pairs=['UNITTEST/BTC']
|
||||
)['UNITTEST/BTC']
|
||||
min_date = int(data.iloc[0]['date'].timestamp())
|
||||
max_date = int(data.iloc[-1]['date'].timestamp())
|
||||
data_modify = data.copy()
|
||||
|
||||
# Remove first 30 minutes (1800 s)
|
||||
tr = TimeRange('date', None, min_date + 1800, 0)
|
||||
data_modify = trim_dataframe(data_modify, tr)
|
||||
assert not data_modify.equals(data)
|
||||
assert len(data_modify) < len(data)
|
||||
assert len(data_modify) == len(data) - 30
|
||||
assert all(data_modify.iloc[-1] == data.iloc[-1])
|
||||
assert all(data_modify.iloc[0] == data.iloc[30])
|
||||
|
||||
data_modify = data.copy()
|
||||
# Remove last 30 minutes (1800 s)
|
||||
tr = TimeRange(None, 'date', 0, max_date - 1800)
|
||||
data_modify = trim_dataframe(data_modify, tr)
|
||||
assert not data_modify.equals(data)
|
||||
assert len(data_modify) < len(data)
|
||||
assert len(data_modify) == len(data) - 30
|
||||
assert all(data_modify.iloc[0] == data.iloc[0])
|
||||
assert all(data_modify.iloc[-1] == data.iloc[-31])
|
||||
|
||||
data_modify = data.copy()
|
||||
# Remove first 25 and last 30 minutes (1800 s)
|
||||
tr = TimeRange('date', 'date', min_date + 1500, max_date - 1800)
|
||||
data_modify = trim_dataframe(data_modify, tr)
|
||||
assert not data_modify.equals(data)
|
||||
assert len(data_modify) < len(data)
|
||||
assert len(data_modify) == len(data) - 55
|
||||
# first row matches 25th original row
|
||||
assert all(data_modify.iloc[0] == data.iloc[25])
|
||||
|
||||
|
||||
def test_file_dump_json_tofile(testdatadir) -> None:
|
||||
file = testdatadir / 'test_{id}.json'.format(id=str(uuid.uuid4()))
|
||||
data = {'bar': 'foo'}
|
||||
@@ -509,7 +399,9 @@ def test_file_dump_json_tofile(testdatadir) -> None:
|
||||
|
||||
def test_get_timerange(default_conf, mocker, testdatadir) -> None:
|
||||
patch_exchange(mocker)
|
||||
strategy = DefaultStrategy(default_conf)
|
||||
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
data = strategy.tickerdata_to_dataframe(
|
||||
load_data(
|
||||
@@ -525,7 +417,9 @@ def test_get_timerange(default_conf, mocker, testdatadir) -> None:
|
||||
|
||||
def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) -> None:
|
||||
patch_exchange(mocker)
|
||||
strategy = DefaultStrategy(default_conf)
|
||||
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
data = strategy.tickerdata_to_dataframe(
|
||||
load_data(
|
||||
@@ -547,7 +441,9 @@ def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir)
|
||||
|
||||
def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> None:
|
||||
patch_exchange(mocker)
|
||||
strategy = DefaultStrategy(default_conf)
|
||||
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
timerange = TimeRange('index', 'index', 200, 250)
|
||||
data = strategy.tickerdata_to_dataframe(
|
||||
@@ -567,7 +463,8 @@ def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> No
|
||||
|
||||
|
||||
def test_refresh_backtest_ohlcv_data(mocker, default_conf, markets, caplog, testdatadir):
|
||||
dl_mock = mocker.patch('freqtrade.data.history._download_pair_history', MagicMock())
|
||||
dl_mock = mocker.patch('freqtrade.data.history.history_utils._download_pair_history',
|
||||
MagicMock())
|
||||
mocker.patch(
|
||||
'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)
|
||||
)
|
||||
@@ -588,7 +485,8 @@ def test_refresh_backtest_ohlcv_data(mocker, default_conf, markets, caplog, test
|
||||
|
||||
|
||||
def test_download_data_no_markets(mocker, default_conf, caplog, testdatadir):
|
||||
dl_mock = mocker.patch('freqtrade.data.history._download_pair_history', MagicMock())
|
||||
dl_mock = mocker.patch('freqtrade.data.history.history_utils._download_pair_history',
|
||||
MagicMock())
|
||||
|
||||
ex = get_patched_exchange(mocker, default_conf)
|
||||
mocker.patch(
|
||||
@@ -608,7 +506,8 @@ def test_download_data_no_markets(mocker, default_conf, caplog, testdatadir):
|
||||
|
||||
|
||||
def test_refresh_backtest_trades_data(mocker, default_conf, markets, caplog, testdatadir):
|
||||
dl_mock = mocker.patch('freqtrade.data.history._download_trades_history', MagicMock())
|
||||
dl_mock = mocker.patch('freqtrade.data.history.history_utils._download_trades_history',
|
||||
MagicMock())
|
||||
mocker.patch(
|
||||
'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)
|
||||
)
|
||||
@@ -638,12 +537,12 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad
|
||||
ght_mock)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
file1 = testdatadir / 'ETH_BTC-trades.json.gz'
|
||||
|
||||
data_handler = get_datahandler(testdatadir, data_format='jsongz')
|
||||
_backup_file(file1)
|
||||
|
||||
assert not file1.is_file()
|
||||
|
||||
assert _download_trades_history(datadir=testdatadir, exchange=exchange,
|
||||
assert _download_trades_history(data_handler=data_handler, exchange=exchange,
|
||||
pair='ETH/BTC')
|
||||
assert log_has("New Amount of trades: 5", caplog)
|
||||
assert file1.is_file()
|
||||
@@ -654,7 +553,7 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_historic_trades',
|
||||
MagicMock(side_effect=ValueError))
|
||||
|
||||
assert not _download_trades_history(datadir=testdatadir, exchange=exchange,
|
||||
assert not _download_trades_history(data_handler=data_handler, exchange=exchange,
|
||||
pair='ETH/BTC')
|
||||
assert log_has_re('Failed to download historic trades for pair: "ETH/BTC".*', caplog)
|
||||
|
||||
@@ -686,3 +585,73 @@ def test_convert_trades_to_ohlcv(mocker, default_conf, testdatadir, caplog):
|
||||
|
||||
_clean_test_file(file1)
|
||||
_clean_test_file(file5)
|
||||
|
||||
|
||||
def test_jsondatahandler_ohlcv_get_pairs(testdatadir):
|
||||
pairs = JsonDataHandler.ohlcv_get_pairs(testdatadir, '5m')
|
||||
# Convert to set to avoid failures due to sorting
|
||||
assert set(pairs) == {'UNITTEST/BTC', 'XLM/BTC', 'ETH/BTC', 'TRX/BTC', 'LTC/BTC',
|
||||
'XMR/BTC', 'ZEC/BTC', 'ADA/BTC', 'ETC/BTC', 'NXT/BTC',
|
||||
'DASH/BTC', 'XRP/ETH'}
|
||||
|
||||
pairs = JsonGzDataHandler.ohlcv_get_pairs(testdatadir, '8m')
|
||||
assert set(pairs) == {'UNITTEST/BTC'}
|
||||
|
||||
|
||||
def test_jsondatahandler_trades_get_pairs(testdatadir):
|
||||
pairs = JsonGzDataHandler.trades_get_pairs(testdatadir)
|
||||
# Convert to set to avoid failures due to sorting
|
||||
assert set(pairs) == {'XRP/ETH'}
|
||||
|
||||
|
||||
def test_jsondatahandler_ohlcv_purge(mocker, testdatadir):
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
|
||||
mocker.patch.object(Path, "unlink", MagicMock())
|
||||
dh = JsonGzDataHandler(testdatadir)
|
||||
assert not dh.ohlcv_purge('UNITTEST/NONEXIST', '5m')
|
||||
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||
assert dh.ohlcv_purge('UNITTEST/NONEXIST', '5m')
|
||||
|
||||
|
||||
def test_jsondatahandler_trades_purge(mocker, testdatadir):
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
|
||||
mocker.patch.object(Path, "unlink", MagicMock())
|
||||
dh = JsonGzDataHandler(testdatadir)
|
||||
assert not dh.trades_purge('UNITTEST/NONEXIST')
|
||||
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||
assert dh.trades_purge('UNITTEST/NONEXIST')
|
||||
|
||||
|
||||
def test_jsondatahandler_ohlcv_append(testdatadir):
|
||||
dh = JsonGzDataHandler(testdatadir)
|
||||
with pytest.raises(NotImplementedError):
|
||||
dh.ohlcv_append('UNITTEST/ETH', '5m', DataFrame())
|
||||
|
||||
|
||||
def test_jsondatahandler_trades_append(testdatadir):
|
||||
dh = JsonGzDataHandler(testdatadir)
|
||||
with pytest.raises(NotImplementedError):
|
||||
dh.trades_append('UNITTEST/ETH', [])
|
||||
|
||||
|
||||
def test_gethandlerclass():
|
||||
cl = get_datahandlerclass('json')
|
||||
assert cl == JsonDataHandler
|
||||
assert issubclass(cl, IDataHandler)
|
||||
cl = get_datahandlerclass('jsongz')
|
||||
assert cl == JsonGzDataHandler
|
||||
assert issubclass(cl, IDataHandler)
|
||||
assert issubclass(cl, JsonDataHandler)
|
||||
with pytest.raises(ValueError, match=r"No datahandler for .*"):
|
||||
get_datahandlerclass('DeadBeef')
|
||||
|
||||
|
||||
def test_get_datahandler(testdatadir):
|
||||
dh = get_datahandler(testdatadir, 'json')
|
||||
assert type(dh) == JsonDataHandler
|
||||
dh = get_datahandler(testdatadir, 'jsongz')
|
||||
assert type(dh) == JsonGzDataHandler
|
||||
dh1 = get_datahandler(testdatadir, 'jsongz', dh)
|
||||
assert id(dh1) == id(dh)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing import Dict, List, NamedTuple
|
||||
from typing import Dict, List, NamedTuple, Optional
|
||||
|
||||
import arrow
|
||||
from pandas import DataFrame
|
||||
@@ -23,14 +23,14 @@ class BTContainer(NamedTuple):
|
||||
"""
|
||||
Minimal BacktestContainer defining Backtest inputs and results.
|
||||
"""
|
||||
data: List[float]
|
||||
data: List[List[float]]
|
||||
stop_loss: float
|
||||
roi: Dict[str, float]
|
||||
trades: List[BTrade]
|
||||
profit_perc: float
|
||||
trailing_stop: bool = False
|
||||
trailing_only_offset_is_reached: bool = False
|
||||
trailing_stop_positive: float = None
|
||||
trailing_stop_positive: Optional[float] = None
|
||||
trailing_stop_positive_offset: float = 0.0
|
||||
use_sell_signal: bool = False
|
||||
|
||||
|
@@ -364,7 +364,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
|
||||
default_conf["trailing_stop"] = data.trailing_stop
|
||||
default_conf["trailing_only_offset_is_reached"] = data.trailing_only_offset_is_reached
|
||||
# Only add this to configuration If it's necessary
|
||||
if data.trailing_stop_positive:
|
||||
if data.trailing_stop_positive is not None:
|
||||
default_conf["trailing_stop_positive"] = data.trailing_stop_positive
|
||||
default_conf["trailing_stop_positive_offset"] = data.trailing_stop_positive_offset
|
||||
default_conf["ask_strategy"] = {"use_sell_signal": data.use_sell_signal}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument
|
||||
|
||||
import math
|
||||
import random
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
@@ -15,13 +14,13 @@ from freqtrade.configuration import TimeRange
|
||||
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_backtesting
|
||||
from freqtrade.data import history
|
||||
from freqtrade.data.btanalysis import evaluate_result_multi
|
||||
from freqtrade.data.converter import parse_ticker_dataframe
|
||||
from freqtrade.data.converter import clean_ohlcv_dataframe
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.data.history import get_timerange
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
from freqtrade.resolvers import StrategyResolver
|
||||
from freqtrade.state import RunMode
|
||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||
from freqtrade.strategy.interface import SellType
|
||||
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
||||
patched_configuration_load_config_file)
|
||||
@@ -50,47 +49,33 @@ def trim_dictlist(dict_list, num):
|
||||
|
||||
def load_data_test(what, testdatadir):
|
||||
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
||||
pair = history.load_tickerdata_file(testdatadir, timeframe='1m',
|
||||
pair='UNITTEST/BTC', timerange=timerange)
|
||||
datalen = len(pair)
|
||||
data = history.load_pair_history(pair='UNITTEST/BTC', datadir=testdatadir,
|
||||
timeframe='1m', timerange=timerange,
|
||||
drop_incomplete=False,
|
||||
fill_up_missing=False)
|
||||
|
||||
base = 0.001
|
||||
if what == 'raise':
|
||||
data = [
|
||||
[
|
||||
pair[x][0], # Keep old dates
|
||||
x * base, # But replace O,H,L,C
|
||||
x * base + 0.0001,
|
||||
x * base - 0.0001,
|
||||
x * base,
|
||||
pair[x][5], # Keep old volume
|
||||
] for x in range(0, datalen)
|
||||
]
|
||||
data.loc[:, 'open'] = data.index * base
|
||||
data.loc[:, 'high'] = data.index * base + 0.0001
|
||||
data.loc[:, 'low'] = data.index * base - 0.0001
|
||||
data.loc[:, 'close'] = data.index * base
|
||||
|
||||
if what == 'lower':
|
||||
data = [
|
||||
[
|
||||
pair[x][0], # Keep old dates
|
||||
1 - x * base, # But replace O,H,L,C
|
||||
1 - x * base + 0.0001,
|
||||
1 - x * base - 0.0001,
|
||||
1 - x * base,
|
||||
pair[x][5] # Keep old volume
|
||||
] for x in range(0, datalen)
|
||||
]
|
||||
data.loc[:, 'open'] = 1 - data.index * base
|
||||
data.loc[:, 'high'] = 1 - data.index * base + 0.0001
|
||||
data.loc[:, 'low'] = 1 - data.index * base - 0.0001
|
||||
data.loc[:, 'close'] = 1 - data.index * base
|
||||
|
||||
if what == 'sine':
|
||||
hz = 0.1 # frequency
|
||||
data = [
|
||||
[
|
||||
pair[x][0], # Keep old dates
|
||||
math.sin(x * hz) / 1000 + base, # But replace O,H,L,C
|
||||
math.sin(x * hz) / 1000 + base + 0.0001,
|
||||
math.sin(x * hz) / 1000 + base - 0.0001,
|
||||
math.sin(x * hz) / 1000 + base,
|
||||
pair[x][5] # Keep old volume
|
||||
] for x in range(0, datalen)
|
||||
]
|
||||
return {'UNITTEST/BTC': parse_ticker_dataframe(data, '1m', pair="UNITTEST/BTC",
|
||||
fill_missing=True)}
|
||||
data.loc[:, 'open'] = np.sin(data.index * hz) / 1000 + base
|
||||
data.loc[:, 'high'] = np.sin(data.index * hz) / 1000 + base + 0.0001
|
||||
data.loc[:, 'low'] = np.sin(data.index * hz) / 1000 + base - 0.0001
|
||||
data.loc[:, 'close'] = np.sin(data.index * hz) / 1000 + base
|
||||
|
||||
return {'UNITTEST/BTC': clean_ohlcv_dataframe(data, timeframe='1m', pair='UNITTEST/BTC',
|
||||
fill_missing=True)}
|
||||
|
||||
|
||||
def simple_backtest(config, contour, num_results, mocker, testdatadir) -> None:
|
||||
@@ -114,21 +99,6 @@ def simple_backtest(config, contour, num_results, mocker, testdatadir) -> None:
|
||||
assert len(results) == num_results
|
||||
|
||||
|
||||
def mocked_load_data(datadir, pairs=[], timeframe='0m',
|
||||
timerange=None, *args, **kwargs):
|
||||
tickerdata = history.load_tickerdata_file(datadir, 'UNITTEST/BTC', '1m', timerange=timerange)
|
||||
pairdata = {'UNITTEST/BTC': parse_ticker_dataframe(tickerdata, '1m', pair="UNITTEST/BTC",
|
||||
fill_missing=True)}
|
||||
return pairdata
|
||||
|
||||
|
||||
# use for mock ccxt.fetch_ohlvc'
|
||||
def _load_pair_as_ticks(pair, tickfreq):
|
||||
ticks = history.load_tickerdata_file(None, timeframe=tickfreq, pair=pair)
|
||||
ticks = ticks[-201:]
|
||||
return ticks
|
||||
|
||||
|
||||
# FIX: fixturize this?
|
||||
def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'):
|
||||
data = history.load_data(datadir=datadir, timeframe='1m', pairs=[pair])
|
||||
@@ -287,8 +257,8 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
]
|
||||
args = get_args(args)
|
||||
start_backtesting(args)
|
||||
pargs = get_args(args)
|
||||
start_backtesting(pargs)
|
||||
assert log_has('Starting freqtrade in Backtesting mode', caplog)
|
||||
assert start_mock.call_count == 1
|
||||
|
||||
@@ -339,18 +309,17 @@ def test_tickerdata_with_fee(default_conf, mocker, testdatadir) -> None:
|
||||
|
||||
def test_tickerdata_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
|
||||
patch_exchange(mocker)
|
||||
# timerange = TimeRange(None, 'line', 0, -100)
|
||||
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
||||
tick = history.load_tickerdata_file(testdatadir, 'UNITTEST/BTC', '1m', timerange=timerange)
|
||||
tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick, '1m', pair="UNITTEST/BTC",
|
||||
fill_missing=True)}
|
||||
|
||||
tickerlist = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange,
|
||||
fill_up_missing=True)
|
||||
backtesting = Backtesting(default_conf)
|
||||
data = backtesting.strategy.tickerdata_to_dataframe(tickerlist)
|
||||
assert len(data['UNITTEST/BTC']) == 102
|
||||
|
||||
# Load strategy to compare the result between Backtesting function and strategy are the same
|
||||
strategy = DefaultStrategy(default_conf)
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
data2 = strategy.tickerdata_to_dataframe(tickerlist)
|
||||
assert data['UNITTEST/BTC'].equals(data2['UNITTEST/BTC'])
|
||||
|
||||
@@ -359,7 +328,6 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
|
||||
def get_timerange(input1):
|
||||
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
||||
|
||||
mocker.patch('freqtrade.data.history.load_data', mocked_load_data)
|
||||
mocker.patch('freqtrade.data.history.get_timerange', get_timerange)
|
||||
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', MagicMock())
|
||||
patch_exchange(mocker)
|
||||
@@ -389,7 +357,8 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) ->
|
||||
def get_timerange(input1):
|
||||
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
||||
|
||||
mocker.patch('freqtrade.data.history.load_pair_history', MagicMock(return_value=pd.DataFrame()))
|
||||
mocker.patch('freqtrade.data.history.history_utils.load_pair_history',
|
||||
MagicMock(return_value=pd.DataFrame()))
|
||||
mocker.patch('freqtrade.data.history.get_timerange', get_timerange)
|
||||
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', MagicMock())
|
||||
patch_exchange(mocker)
|
||||
@@ -693,13 +662,7 @@ def test_backtest_record(default_conf, fee, mocker):
|
||||
def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
||||
default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
|
||||
|
||||
async def load_pairs(pair, timeframe, since):
|
||||
return _load_pair_as_ticks(pair, timeframe)
|
||||
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_ohlcv = load_pairs
|
||||
|
||||
patch_exchange(mocker, api_mock)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock())
|
||||
mocker.patch('freqtrade.optimize.backtesting.generate_text_table', MagicMock())
|
||||
|
||||
@@ -739,12 +702,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
||||
def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
|
||||
|
||||
async def load_pairs(pair, timeframe, since):
|
||||
return _load_pair_as_ticks(pair, timeframe)
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_ohlcv = load_pairs
|
||||
|
||||
patch_exchange(mocker, api_mock)
|
||||
patch_exchange(mocker)
|
||||
backtestmock = MagicMock()
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
|
||||
gen_table_mock = MagicMock()
|
||||
@@ -757,14 +715,14 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
'backtesting',
|
||||
'--config', 'config.json',
|
||||
'--datadir', str(testdatadir),
|
||||
'--strategy-path', str(Path(__file__).parents[2] / 'freqtrade/templates'),
|
||||
'--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'),
|
||||
'--ticker-interval', '1m',
|
||||
'--timerange', '1510694220-1510700340',
|
||||
'--enable-position-stacking',
|
||||
'--disable-max-market-positions',
|
||||
'--strategy-list',
|
||||
'DefaultStrategy',
|
||||
'SampleStrategy',
|
||||
'TestStrategyLegacy',
|
||||
]
|
||||
args = get_args(args)
|
||||
start_backtesting(args)
|
||||
@@ -787,7 +745,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
'up to 2017-11-14T22:58:00+00:00 (0 days)..',
|
||||
'Parameter --enable-position-stacking detected ...',
|
||||
'Running backtesting for Strategy DefaultStrategy',
|
||||
'Running backtesting for Strategy SampleStrategy',
|
||||
'Running backtesting for Strategy TestStrategyLegacy',
|
||||
]
|
||||
|
||||
for line in exists:
|
||||
|
@@ -82,8 +82,8 @@ def test_start(mocker, fee, edge_conf, caplog) -> None:
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
]
|
||||
args = get_args(args)
|
||||
start_edge(args)
|
||||
pargs = get_args(args)
|
||||
start_edge(pargs)
|
||||
assert log_has('Starting freqtrade in Edge mode', caplog)
|
||||
assert start_mock.call_count == 1
|
||||
|
||||
|
@@ -2,6 +2,7 @@
|
||||
import locale
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
import pandas as pd
|
||||
@@ -9,9 +10,9 @@ import pytest
|
||||
from arrow import Arrow
|
||||
from filelock import Timeout
|
||||
|
||||
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_hyperopt
|
||||
from freqtrade.data.converter import parse_ticker_dataframe
|
||||
from freqtrade.data.history import load_tickerdata_file
|
||||
from freqtrade.commands.optimize_commands import (setup_optimize_configuration,
|
||||
start_hyperopt)
|
||||
from freqtrade.data.history import load_data
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.optimize.default_hyperopt import DefaultHyperOpt
|
||||
from freqtrade.optimize.default_hyperopt_loss import DefaultHyperOptLoss
|
||||
@@ -42,13 +43,19 @@ def hyperopt_results():
|
||||
'profit_percent': [-0.1, 0.2, 0.3],
|
||||
'profit_abs': [-0.2, 0.4, 0.6],
|
||||
'trade_duration': [10, 30, 10],
|
||||
'sell_reason': [SellType.STOP_LOSS, SellType.ROI, SellType.ROI]
|
||||
'sell_reason': [SellType.STOP_LOSS, SellType.ROI, SellType.ROI],
|
||||
'close_time':
|
||||
[
|
||||
datetime(2019, 1, 1, 9, 26, 3, 478039),
|
||||
datetime(2019, 2, 1, 9, 26, 3, 478039),
|
||||
datetime(2019, 3, 1, 9, 26, 3, 478039)
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# Functions for recurrent object patching
|
||||
def create_trials(mocker, hyperopt, testdatadir) -> None:
|
||||
def create_trials(mocker, hyperopt, testdatadir) -> List[Dict]:
|
||||
"""
|
||||
When creating trials, mock the hyperopt Trials so that *by default*
|
||||
- we don't create any pickle'd files in the filesystem
|
||||
@@ -222,10 +229,10 @@ def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None
|
||||
'--hyperopt', 'DefaultHyperOpt',
|
||||
'--epochs', '5'
|
||||
]
|
||||
args = get_args(args)
|
||||
pargs = get_args(args)
|
||||
|
||||
with pytest.raises(OperationalException, match=r"Please ensure that the hyperopt dependencies"):
|
||||
start_hyperopt(args)
|
||||
start_hyperopt(pargs)
|
||||
|
||||
|
||||
def test_start(mocker, default_conf, caplog) -> None:
|
||||
@@ -240,8 +247,8 @@ def test_start(mocker, default_conf, caplog) -> None:
|
||||
'--hyperopt', 'DefaultHyperOpt',
|
||||
'--epochs', '5'
|
||||
]
|
||||
args = get_args(args)
|
||||
start_hyperopt(args)
|
||||
pargs = get_args(args)
|
||||
start_hyperopt(pargs)
|
||||
|
||||
assert log_has('Starting freqtrade in Hyperopt mode', caplog)
|
||||
assert start_mock.call_count == 1
|
||||
@@ -263,9 +270,9 @@ def test_start_no_data(mocker, default_conf, caplog) -> None:
|
||||
'--hyperopt', 'DefaultHyperOpt',
|
||||
'--epochs', '5'
|
||||
]
|
||||
args = get_args(args)
|
||||
pargs = get_args(args)
|
||||
with pytest.raises(OperationalException, match='No data found. Terminating.'):
|
||||
start_hyperopt(args)
|
||||
start_hyperopt(pargs)
|
||||
|
||||
|
||||
def test_start_filelock(mocker, default_conf, caplog) -> None:
|
||||
@@ -280,16 +287,19 @@ def test_start_filelock(mocker, default_conf, caplog) -> None:
|
||||
'--hyperopt', 'DefaultHyperOpt',
|
||||
'--epochs', '5'
|
||||
]
|
||||
args = get_args(args)
|
||||
start_hyperopt(args)
|
||||
pargs = get_args(args)
|
||||
start_hyperopt(pargs)
|
||||
assert log_has("Another running instance of freqtrade Hyperopt detected.", caplog)
|
||||
|
||||
|
||||
def test_loss_calculation_prefer_correct_trade_count(default_conf, hyperopt_results) -> None:
|
||||
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
||||
correct = hl.hyperopt_loss_function(hyperopt_results, 600)
|
||||
over = hl.hyperopt_loss_function(hyperopt_results, 600 + 100)
|
||||
under = hl.hyperopt_loss_function(hyperopt_results, 600 - 100)
|
||||
correct = hl.hyperopt_loss_function(hyperopt_results, 600,
|
||||
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||
over = hl.hyperopt_loss_function(hyperopt_results, 600 + 100,
|
||||
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||
under = hl.hyperopt_loss_function(hyperopt_results, 600 - 100,
|
||||
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||
assert over > correct
|
||||
assert under > correct
|
||||
|
||||
@@ -299,8 +309,10 @@ def test_loss_calculation_prefer_shorter_trades(default_conf, hyperopt_results)
|
||||
resultsb.loc[1, 'trade_duration'] = 20
|
||||
|
||||
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
||||
longer = hl.hyperopt_loss_function(hyperopt_results, 100)
|
||||
shorter = hl.hyperopt_loss_function(resultsb, 100)
|
||||
longer = hl.hyperopt_loss_function(hyperopt_results, 100,
|
||||
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||
shorter = hl.hyperopt_loss_function(resultsb, 100,
|
||||
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||
assert shorter < longer
|
||||
|
||||
|
||||
@@ -311,9 +323,12 @@ def test_loss_calculation_has_limited_profit(default_conf, hyperopt_results) ->
|
||||
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
|
||||
|
||||
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
||||
correct = hl.hyperopt_loss_function(hyperopt_results, 600)
|
||||
over = hl.hyperopt_loss_function(results_over, 600)
|
||||
under = hl.hyperopt_loss_function(results_under, 600)
|
||||
correct = hl.hyperopt_loss_function(hyperopt_results, 600,
|
||||
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||
over = hl.hyperopt_loss_function(results_over, 600,
|
||||
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||
under = hl.hyperopt_loss_function(results_under, 600,
|
||||
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||
assert over < correct
|
||||
assert under > correct
|
||||
|
||||
@@ -336,6 +351,24 @@ def test_sharpe_loss_prefers_higher_profits(default_conf, hyperopt_results) -> N
|
||||
assert under > correct
|
||||
|
||||
|
||||
def test_sharpe_loss_daily_prefers_higher_profits(default_conf, hyperopt_results) -> None:
|
||||
results_over = hyperopt_results.copy()
|
||||
results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2
|
||||
results_under = hyperopt_results.copy()
|
||||
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
|
||||
|
||||
default_conf.update({'hyperopt_loss': 'SharpeHyperOptLossDaily'})
|
||||
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
||||
correct = hl.hyperopt_loss_function(hyperopt_results, len(hyperopt_results),
|
||||
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||
over = hl.hyperopt_loss_function(results_over, len(hyperopt_results),
|
||||
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||
under = hl.hyperopt_loss_function(results_under, len(hyperopt_results),
|
||||
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||
assert over < correct
|
||||
assert under > correct
|
||||
|
||||
|
||||
def test_onlyprofit_loss_prefers_higher_profits(default_conf, hyperopt_results) -> None:
|
||||
results_over = hyperopt_results.copy()
|
||||
results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2
|
||||
@@ -543,9 +576,7 @@ def test_has_space(hyperopt, spaces, expected_results):
|
||||
|
||||
|
||||
def test_populate_indicators(hyperopt, testdatadir) -> None:
|
||||
tick = load_tickerdata_file(testdatadir, 'UNITTEST/BTC', '1m')
|
||||
tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick, '1m', pair="UNITTEST/BTC",
|
||||
fill_missing=True)}
|
||||
tickerlist = load_data(testdatadir, '1m', ['UNITTEST/BTC'], fill_up_missing=True)
|
||||
dataframes = hyperopt.backtesting.strategy.tickerdata_to_dataframe(tickerlist)
|
||||
dataframe = hyperopt.custom_hyperopt.populate_indicators(dataframes['UNITTEST/BTC'],
|
||||
{'pair': 'UNITTEST/BTC'})
|
||||
@@ -557,9 +588,7 @@ def test_populate_indicators(hyperopt, testdatadir) -> None:
|
||||
|
||||
|
||||
def test_buy_strategy_generator(hyperopt, testdatadir) -> None:
|
||||
tick = load_tickerdata_file(testdatadir, 'UNITTEST/BTC', '1m')
|
||||
tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick, '1m', pair="UNITTEST/BTC",
|
||||
fill_missing=True)}
|
||||
tickerlist = load_data(testdatadir, '1m', ['UNITTEST/BTC'], fill_up_missing=True)
|
||||
dataframes = hyperopt.backtesting.strategy.tickerdata_to_dataframe(tickerlist)
|
||||
dataframe = hyperopt.custom_hyperopt.populate_indicators(dataframes['UNITTEST/BTC'],
|
||||
{'pair': 'UNITTEST/BTC'})
|
||||
|
@@ -15,20 +15,21 @@ def test_generate_text_table(default_conf, mocker):
|
||||
'profit_percent': [0.1, 0.2],
|
||||
'profit_abs': [0.2, 0.4],
|
||||
'trade_duration': [10, 30],
|
||||
'profit': [2, 0],
|
||||
'loss': [0, 0]
|
||||
'wins': [2, 0],
|
||||
'draws': [0, 0],
|
||||
'losses': [0, 0]
|
||||
}
|
||||
)
|
||||
|
||||
result_str = (
|
||||
'| Pair | Buy Count | Avg Profit % | Cum Profit % | Tot Profit BTC '
|
||||
'| Tot Profit % | Avg Duration | Wins | Losses |\n'
|
||||
'|:--------|------------:|---------------:|---------------:|-----------------:'
|
||||
'|---------------:|:---------------|-------:|---------:|\n'
|
||||
'| ETH/BTC | 2 | 15.00 | 30.00 | 0.60000000 '
|
||||
'| 15.00 | 0:20:00 | 2 | 0 |\n'
|
||||
'| TOTAL | 2 | 15.00 | 30.00 | 0.60000000 '
|
||||
'| 15.00 | 0:20:00 | 2 | 0 |'
|
||||
'| 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 |'
|
||||
)
|
||||
assert generate_text_table(data={'ETH/BTC': {}},
|
||||
stake_currency='BTC', max_open_trades=2,
|
||||
@@ -43,21 +44,22 @@ def test_generate_text_table_sell_reason(default_conf, mocker):
|
||||
'profit_percent': [0.1, 0.2, -0.1],
|
||||
'profit_abs': [0.2, 0.4, -0.2],
|
||||
'trade_duration': [10, 30, 10],
|
||||
'profit': [2, 0, 0],
|
||||
'loss': [0, 0, 1],
|
||||
'wins': [2, 0, 0],
|
||||
'draws': [0, 0, 0],
|
||||
'losses': [0, 0, 1],
|
||||
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
||||
}
|
||||
)
|
||||
|
||||
result_str = (
|
||||
'| Sell Reason | Sell Count | Wins | Losses | Avg Profit % |'
|
||||
' Cum Profit % | Tot Profit BTC | Tot Profit % |\n'
|
||||
'|:--------------|-------------:|-------:|---------:|---------------:|'
|
||||
'---------------:|-----------------:|---------------:|\n'
|
||||
'| roi | 2 | 2 | 0 | 15 |'
|
||||
' 30 | 0.6 | 15 |\n'
|
||||
'| stop_loss | 1 | 0 | 1 | -10 |'
|
||||
' -10 | -0.2 | -5 |'
|
||||
'| 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 |'
|
||||
)
|
||||
assert generate_text_table_sell_reason(
|
||||
data={'ETH/BTC': {}},
|
||||
@@ -67,38 +69,40 @@ def test_generate_text_table_sell_reason(default_conf, mocker):
|
||||
|
||||
def test_generate_text_table_strategy(default_conf, mocker):
|
||||
results = {}
|
||||
results['ETH/BTC'] = pd.DataFrame(
|
||||
results['TestStrategy1'] = pd.DataFrame(
|
||||
{
|
||||
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
||||
'profit_percent': [0.1, 0.2, 0.3],
|
||||
'profit_abs': [0.2, 0.4, 0.5],
|
||||
'trade_duration': [10, 30, 10],
|
||||
'profit': [2, 0, 0],
|
||||
'loss': [0, 0, 1],
|
||||
'wins': [2, 0, 0],
|
||||
'draws': [0, 0, 0],
|
||||
'losses': [0, 0, 1],
|
||||
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
||||
}
|
||||
)
|
||||
results['LTC/BTC'] = pd.DataFrame(
|
||||
results['TestStrategy2'] = pd.DataFrame(
|
||||
{
|
||||
'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'],
|
||||
'profit_percent': [0.4, 0.2, 0.3],
|
||||
'profit_abs': [0.4, 0.4, 0.5],
|
||||
'trade_duration': [15, 30, 15],
|
||||
'profit': [4, 1, 0],
|
||||
'loss': [0, 0, 1],
|
||||
'wins': [4, 1, 0],
|
||||
'draws': [0, 0, 0],
|
||||
'losses': [0, 0, 1],
|
||||
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
||||
}
|
||||
)
|
||||
|
||||
result_str = (
|
||||
'| Strategy | buy count | avg profit % | cum profit % '
|
||||
'| tot profit BTC | tot profit % | avg duration | profit | loss |\n'
|
||||
'|:-----------|------------:|---------------:|---------------:'
|
||||
'|-----------------:|---------------:|:---------------|---------:|-------:|\n'
|
||||
'| ETH/BTC | 3 | 20.00 | 60.00 '
|
||||
'| 1.10000000 | 30.00 | 0:17:00 | 3 | 0 |\n'
|
||||
'| LTC/BTC | 3 | 30.00 | 90.00 '
|
||||
'| 1.30000000 | 45.00 | 0:20:00 | 3 | 0 |'
|
||||
'| Strategy | Buys | Avg Profit % | Cum Profit % | Tot'
|
||||
' Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |\n'
|
||||
'|:--------------|-------:|---------------:|---------------:|------'
|
||||
'-----------:|---------------:|:---------------|-------:|--------:|---------:|\n'
|
||||
'| TestStrategy1 | 3 | 20.00 | 60.00 | '
|
||||
' 1.10000000 | 30.00 | 0:17:00 | 3 | 0 | 0 |\n'
|
||||
'| TestStrategy2 | 3 | 30.00 | 90.00 | '
|
||||
' 1.30000000 | 45.00 | 0:20:00 | 3 | 0 | 0 |'
|
||||
)
|
||||
assert generate_text_table_strategy('BTC', 2, all_results=results) == result_str
|
||||
|
||||
@@ -111,4 +115,4 @@ def test_generate_edge_table(edge_conf, mocker):
|
||||
assert generate_edge_table(results).count(':|') == 7
|
||||
assert generate_edge_table(results).count('| ETH/BTC |') == 1
|
||||
assert generate_edge_table(results).count(
|
||||
'| risk reward ratio | required risk reward | expectancy |') == 1
|
||||
'| Risk Reward Ratio | Required Risk Reward | Expectancy |') == 1
|
||||
|
@@ -122,7 +122,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||
assert "Since" in headers
|
||||
assert "Pair" in headers
|
||||
assert 'instantly' == result[0][2]
|
||||
assert 'ETH/BTC' == result[0][1]
|
||||
assert 'ETH/BTC' in result[0][1]
|
||||
assert '-0.59%' == result[0][3]
|
||||
# Test with fiatconvert
|
||||
|
||||
@@ -131,7 +131,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||
assert "Since" in headers
|
||||
assert "Pair" in headers
|
||||
assert 'instantly' == result[0][2]
|
||||
assert 'ETH/BTC' == result[0][1]
|
||||
assert 'ETH/BTC' in result[0][1]
|
||||
assert '-0.59% (-0.09)' == result[0][3]
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
@@ -140,7 +140,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||
rpc._freqtrade.exchange._cached_ticker = {}
|
||||
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||
assert 'instantly' == result[0][2]
|
||||
assert 'ETH/BTC' == result[0][1]
|
||||
assert 'ETH/BTC' in result[0][1]
|
||||
assert 'nan%' == result[0][3]
|
||||
|
||||
|
||||
|
@@ -284,7 +284,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ')
|
||||
|
||||
assert int(fields[0]) == 1
|
||||
assert fields[1] == 'ETH/BTC'
|
||||
assert 'ETH/BTC' in fields[1]
|
||||
assert msg_mock.call_count == 1
|
||||
|
||||
|
||||
@@ -1200,12 +1200,35 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None:
|
||||
'stake_amount': 0.001,
|
||||
'stake_amount_fiat': 0.0,
|
||||
'stake_currency': 'BTC',
|
||||
'fiat_currency': 'USD'
|
||||
'fiat_currency': 'USD',
|
||||
'current_rate': 1.099e-05,
|
||||
'amount': 1333.3333333333335,
|
||||
'open_date': arrow.utcnow().shift(hours=-1)
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== '*Bittrex:* Buying ETH/BTC\n' \
|
||||
'at rate `0.00001099\n' \
|
||||
'(0.001000 BTC,0.000 USD)`'
|
||||
'*Amount:* `1333.33333333`\n' \
|
||||
'*Open Rate:* `0.00001099`\n' \
|
||||
'*Current Rate:* `0.00001099`\n' \
|
||||
'*Total:* `(0.001000 BTC, 0.000 USD)`'
|
||||
|
||||
|
||||
def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None:
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.rpc.telegram.Telegram',
|
||||
_init=MagicMock(),
|
||||
_send_msg=msg_mock
|
||||
)
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
telegram = Telegram(freqtradebot)
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.BUY_CANCEL_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'pair': 'ETH/BTC',
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== ('*Bittrex:* Cancelling Open Buy Order for ETH/BTC')
|
||||
|
||||
|
||||
def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
@@ -1239,13 +1262,13 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== ('*Binance:* Selling KEY/ETH\n'
|
||||
'*Rate:* `0.00003201`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Current Rate:* `0.00003201`\n'
|
||||
'*Close Rate:* `0.00003201`\n'
|
||||
'*Sell Reason:* `stop_loss`\n'
|
||||
'*Duration:* `1:00:00 (60.0 min)`\n'
|
||||
'*Profit:* `-57.41%`` (loss: -0.05746268 ETH`` / -24.812 USD)`')
|
||||
'*Profit:* `-57.41%` `(loss: -0.05746268 ETH / -24.812 USD)`')
|
||||
|
||||
msg_mock.reset_mock()
|
||||
telegram.send_msg({
|
||||
@@ -1267,10 +1290,10 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== ('*Binance:* Selling KEY/ETH\n'
|
||||
'*Rate:* `0.00003201`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Current Rate:* `0.00003201`\n'
|
||||
'*Close Rate:* `0.00003201`\n'
|
||||
'*Sell Reason:* `stop_loss`\n'
|
||||
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
|
||||
'*Profit:* `-57.41%`')
|
||||
@@ -1278,6 +1301,37 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
telegram._fiat_converter.convert_amount = old_convamount
|
||||
|
||||
|
||||
def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.rpc.telegram.Telegram',
|
||||
_init=MagicMock(),
|
||||
_send_msg=msg_mock
|
||||
)
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
telegram = Telegram(freqtradebot)
|
||||
old_convamount = telegram._fiat_converter.convert_amount
|
||||
telegram._fiat_converter.convert_amount = lambda a, b, c: -24.812
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'KEY/ETH',
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== ('*Binance:* Cancelling Open Sell Order for KEY/ETH')
|
||||
|
||||
msg_mock.reset_mock()
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'KEY/ETH',
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== ('*Binance:* Cancelling Open Sell Order for KEY/ETH')
|
||||
# Reset singleton function to avoid random breaks
|
||||
telegram._fiat_converter.convert_amount = old_convamount
|
||||
|
||||
|
||||
def test_send_msg_status_notification(default_conf, mocker) -> None:
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
@@ -1360,12 +1414,17 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
|
||||
'stake_amount': 0.001,
|
||||
'stake_amount_fiat': 0.0,
|
||||
'stake_currency': 'BTC',
|
||||
'fiat_currency': None
|
||||
'fiat_currency': None,
|
||||
'current_rate': 1.099e-05,
|
||||
'amount': 1333.3333333333335,
|
||||
'open_date': arrow.utcnow().shift(hours=-1)
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== '*Bittrex:* Buying ETH/BTC\n' \
|
||||
'at rate `0.00001099\n' \
|
||||
'(0.001000 BTC)`'
|
||||
'*Amount:* `1333.33333333`\n' \
|
||||
'*Open Rate:* `0.00001099`\n' \
|
||||
'*Current Rate:* `0.00001099`\n' \
|
||||
'*Total:* `(0.001000 BTC)`'
|
||||
|
||||
|
||||
def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
|
||||
@@ -1398,10 +1457,10 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== '*Binance:* Selling KEY/ETH\n' \
|
||||
'*Rate:* `0.00003201`\n' \
|
||||
'*Amount:* `1333.33333333`\n' \
|
||||
'*Open Rate:* `0.00007500`\n' \
|
||||
'*Current Rate:* `0.00003201`\n' \
|
||||
'*Close Rate:* `0.00003201`\n' \
|
||||
'*Sell Reason:* `stop_loss`\n' \
|
||||
'*Duration:* `2:35:03 (155.1 min)`\n' \
|
||||
'*Profit:* `-57.41%`'
|
||||
|
@@ -13,24 +13,34 @@ from tests.conftest import get_patched_freqtradebot, log_has
|
||||
|
||||
def get_webhook_dict() -> dict:
|
||||
return {
|
||||
"enabled": True,
|
||||
"url": "https://maker.ifttt.com/trigger/freqtrade_test/with/key/c764udvJ5jfSlswVRukZZ2/",
|
||||
"webhookbuy": {
|
||||
"value1": "Buying {pair}",
|
||||
"value2": "limit {limit:8f}",
|
||||
"value3": "{stake_amount:8f} {stake_currency}"
|
||||
},
|
||||
"webhooksell": {
|
||||
"value1": "Selling {pair}",
|
||||
"value2": "limit {limit:8f}",
|
||||
"value3": "profit: {profit_amount:8f} {stake_currency}"
|
||||
},
|
||||
"webhookstatus": {
|
||||
"value1": "Status: {status}",
|
||||
"value2": "",
|
||||
"value3": ""
|
||||
}
|
||||
}
|
||||
"enabled": True,
|
||||
"url": "https://maker.ifttt.com/trigger/freqtrade_test/with/key/c764udvJ5jfSlswVRukZZ2/",
|
||||
"webhookbuy": {
|
||||
"value1": "Buying {pair}",
|
||||
"value2": "limit {limit:8f}",
|
||||
"value3": "{stake_amount:8f} {stake_currency}"
|
||||
},
|
||||
"webhookbuycancel": {
|
||||
"value1": "Cancelling Open Buy Order for {pair}",
|
||||
"value2": "limit {limit:8f}",
|
||||
"value3": "{stake_amount:8f} {stake_currency}"
|
||||
},
|
||||
"webhooksell": {
|
||||
"value1": "Selling {pair}",
|
||||
"value2": "limit {limit:8f}",
|
||||
"value3": "profit: {profit_amount:8f} {stake_currency}"
|
||||
},
|
||||
"webhooksellcancel": {
|
||||
"value1": "Cancelling Open Sell Order for {pair}",
|
||||
"value2": "limit {limit:8f}",
|
||||
"value3": "profit: {profit_amount:8f} {stake_currency}"
|
||||
},
|
||||
"webhookstatus": {
|
||||
"value1": "Status: {status}",
|
||||
"value2": "",
|
||||
"value3": ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test__init__(mocker, default_conf):
|
||||
@@ -44,6 +54,9 @@ def test_send_msg(default_conf, mocker):
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
|
||||
# Test buy
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
msg = {
|
||||
'type': RPCMessageType.BUY_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
@@ -54,8 +67,6 @@ def test_send_msg(default_conf, mocker):
|
||||
'stake_currency': 'BTC',
|
||||
'fiat_currency': 'EUR'
|
||||
}
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
webhook.send_msg(msg=msg)
|
||||
assert msg_mock.call_count == 1
|
||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||
@@ -64,6 +75,27 @@ def test_send_msg(default_conf, mocker):
|
||||
default_conf["webhook"]["webhookbuy"]["value2"].format(**msg))
|
||||
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 = {
|
||||
'type': RPCMessageType.BUY_CANCEL_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'pair': 'ETH/BTC',
|
||||
'limit': 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"]["webhookbuycancel"]["value1"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||
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)
|
||||
@@ -90,7 +122,32 @@ def test_send_msg(default_conf, mocker):
|
||||
default_conf["webhook"]["webhooksell"]["value2"].format(**msg))
|
||||
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 = {
|
||||
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'pair': 'ETH/BTC',
|
||||
'gain': "profit",
|
||||
'limit': 0.005,
|
||||
'amount': 0.8,
|
||||
'order_type': 'limit',
|
||||
'open_rate': 0.004,
|
||||
'current_rate': 0.005,
|
||||
'profit_amount': 0.001,
|
||||
'profit_percent': 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"]["webhooksellcancel"]["value1"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||
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.CUSTOM_NOTIFICATION]:
|
||||
|
156
tests/strategy/strats/default_strategy.py
Normal file
156
tests/strategy/strats/default_strategy.py
Normal file
@@ -0,0 +1,156 @@
|
||||
# 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.interface import IStrategy
|
||||
|
||||
|
||||
class DefaultStrategy(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
|
||||
ticker_interval = '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',
|
||||
}
|
||||
|
||||
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: Raw data from the exchange and parsed by parse_ticker_dataframe()
|
||||
: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'] < 35) &
|
||||
(dataframe['fastd'] < 35) &
|
||||
(dataframe['adx'] > 30) &
|
||||
(dataframe['plus_di'] > 0.5)
|
||||
) |
|
||||
(
|
||||
(dataframe['adx'] > 65) &
|
||||
(dataframe['plus_di'] > 0.5)
|
||||
),
|
||||
'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'], 70)) |
|
||||
(qtpylib.crossed_above(dataframe['fastd'], 70))
|
||||
) &
|
||||
(dataframe['adx'] > 10) &
|
||||
(dataframe['minus_di'] > 0)
|
||||
) |
|
||||
(
|
||||
(dataframe['adx'] > 70) &
|
||||
(dataframe['minus_di'] > 0.5)
|
||||
),
|
||||
'sell'] = 1
|
||||
return dataframe
|
9
tests/strategy/strats/failing_strategy.py
Normal file
9
tests/strategy/strats/failing_strategy.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# The strategy which fails to load due to non-existent dependency
|
||||
|
||||
import nonexiting_module # noqa
|
||||
|
||||
from freqtrade.strategy.interface import IStrategy
|
||||
|
||||
|
||||
class TestStrategyLegacy(IStrategy):
|
||||
pass
|
@@ -1,6 +1,6 @@
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||
from .strats.default_strategy import DefaultStrategy
|
||||
|
||||
|
||||
def test_default_strategy_structure():
|
||||
|
@@ -7,12 +7,13 @@ import arrow
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.data.converter import parse_ticker_dataframe
|
||||
from freqtrade.data.history import load_tickerdata_file
|
||||
from freqtrade.data.history import load_data
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||
from freqtrade.resolvers import StrategyResolver
|
||||
from tests.conftest import get_patched_exchange, log_has, log_has_re
|
||||
|
||||
from .strats.default_strategy import DefaultStrategy
|
||||
|
||||
# Avoid to reinit the same object again and again
|
||||
_STRATEGY = DefaultStrategy(config={})
|
||||
|
||||
@@ -104,12 +105,12 @@ def test_get_signal_handles_exceptions(mocker, default_conf):
|
||||
|
||||
|
||||
def test_tickerdata_to_dataframe(default_conf, testdatadir) -> None:
|
||||
strategy = DefaultStrategy(default_conf)
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
||||
tick = load_tickerdata_file(testdatadir, 'UNITTEST/BTC', '1m', timerange=timerange)
|
||||
tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick, '1m', pair="UNITTEST/BTC",
|
||||
fill_missing=True)}
|
||||
tickerlist = load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange,
|
||||
fill_up_missing=True)
|
||||
data = strategy.tickerdata_to_dataframe(tickerlist)
|
||||
assert len(data['UNITTEST/BTC']) == 102 # partial candle was removed
|
||||
|
||||
@@ -120,7 +121,8 @@ def test_min_roi_reached(default_conf, fee) -> None:
|
||||
min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1},
|
||||
{0: 0.1, 20: 0.05, 55: 0.01}]
|
||||
for roi in min_roi_list:
|
||||
strategy = DefaultStrategy(default_conf)
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
strategy.minimal_roi = roi
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
@@ -158,7 +160,8 @@ def test_min_roi_reached2(default_conf, fee) -> None:
|
||||
},
|
||||
]
|
||||
for roi in min_roi_list:
|
||||
strategy = DefaultStrategy(default_conf)
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
strategy.minimal_roi = roi
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
@@ -192,7 +195,8 @@ def test_min_roi_reached3(default_conf, fee) -> None:
|
||||
30: 0.05,
|
||||
55: 0.30,
|
||||
}
|
||||
strategy = DefaultStrategy(default_conf)
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
strategy.minimal_roi = min_roi
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
@@ -292,7 +296,8 @@ def test__analyze_ticker_internal_skip_analyze(ticker_history, mocker, caplog) -
|
||||
|
||||
|
||||
def test_is_pair_locked(default_conf):
|
||||
strategy = DefaultStrategy(default_conf)
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
# dict should be empty
|
||||
assert not strategy._pair_locked_until
|
||||
|
||||
|
@@ -2,7 +2,6 @@
|
||||
import logging
|
||||
import warnings
|
||||
from base64 import urlsafe_b64encode
|
||||
from os import path
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -15,7 +14,7 @@ from tests.conftest import log_has, log_has_re
|
||||
|
||||
|
||||
def test_search_strategy():
|
||||
default_location = Path(__file__).parent.parent.joinpath('strategy').resolve()
|
||||
default_location = Path(__file__).parent / 'strats'
|
||||
|
||||
s, _ = StrategyResolver._search_object(
|
||||
directory=default_location,
|
||||
@@ -30,12 +29,23 @@ def test_search_strategy():
|
||||
assert s is None
|
||||
|
||||
|
||||
def test_search_all_strategies():
|
||||
directory = Path(__file__).parent
|
||||
strategies = StrategyResolver.search_all_objects(directory)
|
||||
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 isinstance(strategies[0], dict)
|
||||
|
||||
|
||||
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 isinstance(strategies[0], dict)
|
||||
# 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 None]) == 1
|
||||
|
||||
|
||||
def test_load_strategy(default_conf, result):
|
||||
@@ -61,13 +71,12 @@ def test_load_strategy_base64(result, caplog, default_conf):
|
||||
def test_load_strategy_invalid_directory(result, caplog, default_conf):
|
||||
default_conf['strategy'] = 'DefaultStrategy'
|
||||
extra_dir = Path.cwd() / 'some/path'
|
||||
strategy = StrategyResolver._load_strategy('DefaultStrategy', config=default_conf,
|
||||
extra_dir=extra_dir)
|
||||
with pytest.raises(OperationalException):
|
||||
StrategyResolver._load_strategy('DefaultStrategy', config=default_conf,
|
||||
extra_dir=extra_dir)
|
||||
|
||||
assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog)
|
||||
|
||||
assert 'rsi' in strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
|
||||
|
||||
|
||||
def test_load_not_found_strategy(default_conf):
|
||||
default_conf['strategy'] = 'NotFoundStrategy'
|
||||
@@ -315,7 +324,7 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf):
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:deprecated")
|
||||
def test_deprecate_populate_indicators(result, default_conf):
|
||||
default_location = path.join(path.dirname(path.realpath(__file__)))
|
||||
default_location = Path(__file__).parent / "strats"
|
||||
default_conf.update({'strategy': 'TestStrategyLegacy',
|
||||
'strategy_path': default_location})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
@@ -349,7 +358,7 @@ def test_deprecate_populate_indicators(result, default_conf):
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:deprecated")
|
||||
def test_call_deprecated_function(result, monkeypatch, default_conf):
|
||||
default_location = path.join(path.dirname(path.realpath(__file__)))
|
||||
default_location = Path(__file__).parent / "strats"
|
||||
default_conf.update({'strategy': 'TestStrategyLegacy',
|
||||
'strategy_path': default_location})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
@@ -18,7 +18,8 @@ def test_parse_args_none() -> None:
|
||||
assert isinstance(arguments.parser, argparse.ArgumentParser)
|
||||
|
||||
|
||||
def test_parse_args_defaults() -> None:
|
||||
def test_parse_args_defaults(mocker) -> None:
|
||||
mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True]))
|
||||
args = Arguments(['trade']).get_parsed_arg()
|
||||
assert args["config"] == ['config.json']
|
||||
assert args["strategy_path"] is None
|
||||
@@ -26,6 +27,26 @@ def test_parse_args_defaults() -> None:
|
||||
assert args["verbosity"] == 0
|
||||
|
||||
|
||||
def test_parse_args_default_userdatadir(mocker) -> None:
|
||||
mocker.patch.object(Path, "is_file", MagicMock(return_value=True))
|
||||
args = Arguments(['trade']).get_parsed_arg()
|
||||
# configuration defaults to user_data if that is available.
|
||||
assert args["config"] == [str(Path('user_data/config.json'))]
|
||||
assert args["strategy_path"] is None
|
||||
assert args["datadir"] is None
|
||||
assert args["verbosity"] == 0
|
||||
|
||||
|
||||
def test_parse_args_userdatadir(mocker) -> None:
|
||||
mocker.patch.object(Path, "is_file", MagicMock(return_value=True))
|
||||
args = Arguments(['trade', '--user-data-dir', 'user_data']).get_parsed_arg()
|
||||
# configuration defaults to user_data if that is available.
|
||||
assert args["config"] == [str(Path('user_data/config.json'))]
|
||||
assert args["strategy_path"] is None
|
||||
assert args["datadir"] is None
|
||||
assert args["verbosity"] == 0
|
||||
|
||||
|
||||
def test_parse_args_config() -> None:
|
||||
args = Arguments(['trade', '-c', '/dev/null']).get_parsed_arg()
|
||||
assert args["config"] == ['/dev/null']
|
||||
@@ -208,7 +229,7 @@ def test_config_notrequired(mocker) -> None:
|
||||
assert pargs["config"] is None
|
||||
|
||||
# When file exists:
|
||||
mocker.patch.object(Path, "is_file", MagicMock(return_value=True))
|
||||
mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True]))
|
||||
args = [
|
||||
'download-data',
|
||||
]
|
||||
|
@@ -212,6 +212,7 @@ def test_load_config_file_exception(mocker) -> None:
|
||||
|
||||
|
||||
def test_load_config(default_conf, mocker) -> None:
|
||||
del default_conf['strategy_path']
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
args = Arguments(['trade']).get_parsed_arg()
|
||||
|
@@ -300,7 +300,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker, edge_conf
|
||||
|
||||
# stoploss shoud be hit
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert log_has('executed sell, reason: SellType.STOP_LOSS', caplog)
|
||||
assert log_has('Executing Sell for NEO/BTC. Reason: SellType.STOP_LOSS', caplog)
|
||||
assert trade.sell_reason == SellType.STOP_LOSS.value
|
||||
|
||||
|
||||
@@ -921,7 +921,7 @@ def test_get_buy_rate(mocker, default_conf, ask, last, last_ab, expected) -> Non
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
MagicMock(return_value={'ask': ask, 'last': last}))
|
||||
|
||||
assert freqtrade.get_buy_rate('ETH/BTC') == expected
|
||||
assert freqtrade.get_buy_rate('ETH/BTC', True) == expected
|
||||
|
||||
|
||||
def test_execute_buy(mocker, default_conf, fee, limit_buy_order) -> None:
|
||||
@@ -1964,7 +1964,7 @@ def test_check_handle_cancelled_buy(default_conf, ticker, limit_buy_order_old, o
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
nb_trades = len(trades)
|
||||
assert nb_trades == 0
|
||||
assert log_has_re("Buy order canceled on Exchange for Trade.*", caplog)
|
||||
assert log_has_re("Buy order cancelled on exchange for Trade.*", caplog)
|
||||
|
||||
|
||||
def test_check_handle_timedout_buy_exception(default_conf, ticker, limit_buy_order_old, open_trade,
|
||||
@@ -2045,7 +2045,7 @@ def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old,
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert rpc_mock.call_count == 1
|
||||
assert open_trade.is_open is True
|
||||
assert log_has_re("Sell order canceled on exchange for Trade.*", caplog)
|
||||
assert log_has_re("Sell order cancelled on exchange for Trade.*", caplog)
|
||||
|
||||
|
||||
def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old_partial,
|
||||
@@ -2067,7 +2067,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
|
||||
# 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
|
||||
@@ -2101,7 +2101,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.* Order", 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 tradehas been updated
|
||||
@@ -2140,7 +2140,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 tradehas been updated
|
||||
@@ -3524,7 +3524,7 @@ def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2) -> None:
|
||||
default_conf['telegram']['enabled'] = False
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
assert freqtrade.get_buy_rate('ETH/BTC') == 0.043935
|
||||
assert freqtrade.get_buy_rate('ETH/BTC', True) == 0.043935
|
||||
assert ticker_mock.call_count == 0
|
||||
|
||||
|
||||
@@ -3549,7 +3549,7 @@ def test_order_book_bid_strategy2(mocker, default_conf, order_book_l2) -> None:
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
# orderbook shall be used even if tickers would be lower.
|
||||
assert freqtrade.get_buy_rate('ETH/BTC') != 0.042
|
||||
assert freqtrade.get_buy_rate('ETH/BTC', True) != 0.042
|
||||
assert ticker_mock.call_count == 0
|
||||
|
||||
|
||||
|
@@ -5,8 +5,9 @@ from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
import pytest
|
||||
|
||||
from pathlib import Path
|
||||
from freqtrade.commands import Arguments
|
||||
from freqtrade.exceptions import OperationalException, FreqtradeException
|
||||
from freqtrade.exceptions import FreqtradeException, OperationalException
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
from freqtrade.main import main
|
||||
from freqtrade.state import State
|
||||
@@ -26,6 +27,7 @@ def test_parse_args_backtesting(mocker) -> None:
|
||||
Test that main() can start backtesting and also ensure we can pass some specific arguments
|
||||
further argument parsing is done in test_arguments.py
|
||||
"""
|
||||
mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True]))
|
||||
backtesting_mock = mocker.patch('freqtrade.commands.start_backtesting')
|
||||
backtesting_mock.__name__ = PropertyMock("start_backtesting")
|
||||
# it's sys.exit(0) at the end of backtesting
|
||||
@@ -42,6 +44,7 @@ def test_parse_args_backtesting(mocker) -> None:
|
||||
|
||||
|
||||
def test_main_start_hyperopt(mocker) -> None:
|
||||
mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True]))
|
||||
hyperopt_mock = mocker.patch('freqtrade.commands.start_hyperopt', MagicMock())
|
||||
hyperopt_mock.__name__ = PropertyMock("start_hyperopt")
|
||||
# it's sys.exit(0) at the end of hyperopt
|
||||
|
@@ -4,10 +4,12 @@ import datetime
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.data.converter import parse_ticker_dataframe
|
||||
from freqtrade.data.history import pair_data_filename
|
||||
from freqtrade.misc import (datesarray_to_datetimearray, file_dump_json,
|
||||
file_load_json, format_ms_time, plural, shorten_date)
|
||||
file_load_json, format_ms_time, pair_to_filename,
|
||||
plural, shorten_date)
|
||||
|
||||
|
||||
def test_shorten_date() -> None:
|
||||
@@ -48,16 +50,36 @@ def test_file_dump_json(mocker) -> None:
|
||||
def test_file_load_json(mocker, testdatadir) -> None:
|
||||
|
||||
# 7m .json does not exist
|
||||
ret = file_load_json(pair_data_filename(testdatadir, 'UNITTEST/BTC', '7m'))
|
||||
ret = file_load_json(testdatadir / 'UNITTEST_BTC-7m.json')
|
||||
assert not ret
|
||||
# 1m json exists (but no .gz exists)
|
||||
ret = file_load_json(pair_data_filename(testdatadir, 'UNITTEST/BTC', '1m'))
|
||||
ret = file_load_json(testdatadir / 'UNITTEST_BTC-1m.json')
|
||||
assert ret
|
||||
# 8 .json is empty and will fail if it's loaded. .json.gz is a copy of 1.json
|
||||
ret = file_load_json(pair_data_filename(testdatadir, 'UNITTEST/BTC', '8m'))
|
||||
ret = file_load_json(testdatadir / 'UNITTEST_BTC-8m.json')
|
||||
assert ret
|
||||
|
||||
|
||||
@pytest.mark.parametrize("pair,expected_result", [
|
||||
("ETH/BTC", 'ETH_BTC'),
|
||||
("Fabric Token/ETH", 'Fabric_Token_ETH'),
|
||||
("ETHH20", 'ETHH20'),
|
||||
(".XBTBON2H", '_XBTBON2H'),
|
||||
("ETHUSD.d", 'ETHUSD_d'),
|
||||
("ADA-0327", 'ADA_0327'),
|
||||
("BTC-USD-200110", 'BTC_USD_200110'),
|
||||
("F-AKRO/USDT", 'F_AKRO_USDT'),
|
||||
("LC+/ETH", 'LC__ETH'),
|
||||
("CMT@18/ETH", 'CMT_18_ETH'),
|
||||
("LBTC:1022/SAI", 'LBTC_1022_SAI'),
|
||||
("$PAC/BTC", '_PAC_BTC'),
|
||||
("ACC_OLD/BTC", 'ACC_OLD_BTC'),
|
||||
])
|
||||
def test_pair_to_filename(pair, expected_result):
|
||||
pair_s = pair_to_filename(pair)
|
||||
assert pair_s == expected_result
|
||||
|
||||
|
||||
def test_format_ms_time() -> None:
|
||||
# Date 2018-04-10 18:02:01
|
||||
date_in_epoch_ms = 1523383321000
|
||||
|
@@ -19,7 +19,7 @@ from freqtrade.plot.plotting import (add_indicators, add_profit,
|
||||
generate_profit_graph, init_plotscript,
|
||||
load_and_plot_trades, plot_profit,
|
||||
plot_trades, store_plot_file)
|
||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||
from freqtrade.resolvers import StrategyResolver
|
||||
from tests.conftest import get_args, log_has, log_has_re
|
||||
|
||||
|
||||
@@ -70,9 +70,11 @@ def test_add_indicators(default_conf, testdatadir, caplog):
|
||||
indicators1 = {"ema10": {}}
|
||||
indicators2 = {"macd": {"color": "red"}}
|
||||
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
# Generate buy/sell signals and indicators
|
||||
strat = DefaultStrategy(default_conf)
|
||||
data = strat.analyze_ticker(data, {'pair': pair})
|
||||
data = strategy.analyze_ticker(data, {'pair': pair})
|
||||
fig = generate_empty_figure()
|
||||
|
||||
# Row 1
|
||||
@@ -181,9 +183,11 @@ def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir)
|
||||
data = history.load_pair_history(pair=pair, timeframe='1m',
|
||||
datadir=testdatadir, timerange=timerange)
|
||||
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
# Generate buy/sell signals and indicators
|
||||
strat = DefaultStrategy(default_conf)
|
||||
data = strat.analyze_ticker(data, {'pair': pair})
|
||||
data = strategy.analyze_ticker(data, {'pair': pair})
|
||||
|
||||
indicators1 = []
|
||||
indicators2 = []
|
||||
|
Reference in New Issue
Block a user