Merge remote-tracking branch 'upstream/develop' into feature/fetch-public-trades
This commit is contained in:
@@ -30,7 +30,7 @@ def test_validate_is_int():
|
||||
assert not validate_is_int('-ee')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('exchange', ['bittrex', 'binance', 'kraken'])
|
||||
@pytest.mark.parametrize('exchange', ['bybit', 'binance', 'kraken'])
|
||||
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))
|
||||
|
||||
@@ -32,7 +32,7 @@ from tests.conftest_trades import MOCK_TRADE_COUNT
|
||||
|
||||
def test_setup_utils_configuration():
|
||||
args = [
|
||||
'list-exchanges', '--config', 'config_examples/config_bittrex.example.json',
|
||||
'list-exchanges', '--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
]
|
||||
|
||||
config = setup_utils_configuration(get_args(args), RunMode.OTHER)
|
||||
@@ -49,7 +49,7 @@ def test_start_trading_fail(mocker, caplog):
|
||||
exitmock = mocker.patch("freqtrade.worker.Worker.exit", MagicMock())
|
||||
args = [
|
||||
'trade',
|
||||
'-c', 'config_examples/config_bittrex.example.json'
|
||||
'-c', 'tests/testdata/testconfigs/main_test_config.json'
|
||||
]
|
||||
start_trading(get_args(args))
|
||||
assert exitmock.call_count == 1
|
||||
@@ -68,7 +68,7 @@ def test_start_webserver(mocker, caplog):
|
||||
|
||||
args = [
|
||||
'webserver',
|
||||
'-c', 'config_examples/config_bittrex.example.json'
|
||||
'-c', 'tests/testdata/testconfigs/main_test_config.json'
|
||||
]
|
||||
start_webserver(get_args(args))
|
||||
assert api_server_mock.call_count == 1
|
||||
@@ -84,7 +84,7 @@ def test_list_exchanges(capsys):
|
||||
captured = capsys.readouterr()
|
||||
assert re.match(r"Exchanges available for Freqtrade.*", captured.out)
|
||||
assert re.search(r".*binance.*", captured.out)
|
||||
assert re.search(r".*bittrex.*", captured.out)
|
||||
assert re.search(r".*bybit.*", captured.out)
|
||||
|
||||
# Test with --one-column
|
||||
args = [
|
||||
@@ -95,7 +95,7 @@ def test_list_exchanges(capsys):
|
||||
start_list_exchanges(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert re.search(r"^binance$", captured.out, re.MULTILINE)
|
||||
assert re.search(r"^bittrex$", captured.out, re.MULTILINE)
|
||||
assert re.search(r"^bybit$", captured.out, re.MULTILINE)
|
||||
|
||||
# Test with --all
|
||||
args = [
|
||||
@@ -107,7 +107,7 @@ def test_list_exchanges(capsys):
|
||||
captured = capsys.readouterr()
|
||||
assert re.match(r"All exchanges supported by the ccxt library.*", captured.out)
|
||||
assert re.search(r".*binance.*", captured.out)
|
||||
assert re.search(r".*bittrex.*", captured.out)
|
||||
assert re.search(r".*bingx.*", captured.out)
|
||||
assert re.search(r".*bitmex.*", captured.out)
|
||||
|
||||
# Test with --one-column --all
|
||||
@@ -120,7 +120,7 @@ def test_list_exchanges(capsys):
|
||||
start_list_exchanges(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert re.search(r"^binance$", captured.out, re.MULTILINE)
|
||||
assert re.search(r"^bittrex$", captured.out, re.MULTILINE)
|
||||
assert re.search(r"^bingx$", captured.out, re.MULTILINE)
|
||||
assert re.search(r"^bitmex$", captured.out, re.MULTILINE)
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ def test_list_timeframes(mocker, capsys):
|
||||
'1h': 'hour',
|
||||
'1d': 'day',
|
||||
}
|
||||
patch_exchange(mocker, api_mock=api_mock, id='bittrex')
|
||||
patch_exchange(mocker, api_mock=api_mock, id='bybit')
|
||||
args = [
|
||||
"list-timeframes",
|
||||
]
|
||||
@@ -143,25 +143,25 @@ def test_list_timeframes(mocker, capsys):
|
||||
match=r"This command requires a configured exchange.*"):
|
||||
start_list_timeframes(pargs)
|
||||
|
||||
# Test with --config config_examples/config_bittrex.example.json
|
||||
# Test with --config tests/testdata/testconfigs/main_test_config.json
|
||||
args = [
|
||||
"list-timeframes",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
]
|
||||
start_list_timeframes(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert re.match("Timeframes available for the exchange `Bittrex`: "
|
||||
assert re.match("Timeframes available for the exchange `Bybit`: "
|
||||
"1m, 5m, 30m, 1h, 1d",
|
||||
captured.out)
|
||||
|
||||
# Test with --exchange bittrex
|
||||
# Test with --exchange bybit
|
||||
args = [
|
||||
"list-timeframes",
|
||||
"--exchange", "bittrex",
|
||||
"--exchange", "bybit",
|
||||
]
|
||||
start_list_timeframes(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert re.match("Timeframes available for the exchange `Bittrex`: "
|
||||
assert re.match("Timeframes available for the exchange `Bybit`: "
|
||||
"1m, 5m, 30m, 1h, 1d",
|
||||
captured.out)
|
||||
|
||||
@@ -190,7 +190,7 @@ def test_list_timeframes(mocker, capsys):
|
||||
# Test with --one-column
|
||||
args = [
|
||||
"list-timeframes",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--one-column",
|
||||
]
|
||||
start_list_timeframes(get_args(args))
|
||||
@@ -217,7 +217,7 @@ def test_list_timeframes(mocker, capsys):
|
||||
def test_list_markets(mocker, markets_static, capsys):
|
||||
|
||||
api_mock = MagicMock()
|
||||
patch_exchange(mocker, api_mock=api_mock, id='bittrex', mock_markets=markets_static)
|
||||
patch_exchange(mocker, api_mock=api_mock, id='binance', mock_markets=markets_static)
|
||||
|
||||
# Test with no --config
|
||||
args = [
|
||||
@@ -229,15 +229,15 @@ def test_list_markets(mocker, markets_static, capsys):
|
||||
match=r"This command requires a configured exchange.*"):
|
||||
start_list_markets(pargs, False)
|
||||
|
||||
# Test with --config config_examples/config_bittrex.example.json
|
||||
# Test with --config tests/testdata/testconfigs/main_test_config.json
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--print-list",
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
captured = capsys.readouterr()
|
||||
assert ("Exchange Bittrex has 12 active markets: "
|
||||
assert ("Exchange Binance has 12 active markets: "
|
||||
"ADA/USDT:USDT, BLK/BTC, ETH/BTC, ETH/USDT, ETH/USDT:USDT, LTC/BTC, "
|
||||
"LTC/ETH, LTC/USD, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n"
|
||||
in captured.out)
|
||||
@@ -255,16 +255,16 @@ def test_list_markets(mocker, markets_static, capsys):
|
||||
assert re.match("\nExchange Binance has 12 active markets:\n",
|
||||
captured.out)
|
||||
|
||||
patch_exchange(mocker, api_mock=api_mock, id="bittrex", mock_markets=markets_static)
|
||||
patch_exchange(mocker, api_mock=api_mock, id="binance", mock_markets=markets_static)
|
||||
# Test with --all: all markets
|
||||
args = [
|
||||
"list-markets", "--all",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--print-list",
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
captured = capsys.readouterr()
|
||||
assert ("Exchange Bittrex has 14 markets: "
|
||||
assert ("Exchange Binance has 14 markets: "
|
||||
"ADA/USDT:USDT, BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, ETH/USDT:USDT, "
|
||||
"LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n"
|
||||
in captured.out)
|
||||
@@ -272,24 +272,24 @@ def test_list_markets(mocker, markets_static, capsys):
|
||||
# Test list-pairs subcommand: active pairs
|
||||
args = [
|
||||
"list-pairs",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--print-list",
|
||||
]
|
||||
start_list_markets(get_args(args), True)
|
||||
captured = capsys.readouterr()
|
||||
assert ("Exchange Bittrex has 9 active pairs: "
|
||||
assert ("Exchange Binance has 9 active pairs: "
|
||||
"BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, NEO/BTC, TKN/BTC, XRP/BTC.\n"
|
||||
in captured.out)
|
||||
|
||||
# Test list-pairs subcommand with --all: all pairs
|
||||
args = [
|
||||
"list-pairs", "--all",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--print-list",
|
||||
]
|
||||
start_list_markets(get_args(args), True)
|
||||
captured = capsys.readouterr()
|
||||
assert ("Exchange Bittrex has 11 pairs: "
|
||||
assert ("Exchange Binance has 11 pairs: "
|
||||
"BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, "
|
||||
"TKN/BTC, XRP/BTC.\n"
|
||||
in captured.out)
|
||||
@@ -297,133 +297,133 @@ def test_list_markets(mocker, markets_static, capsys):
|
||||
# active markets, base=ETH, LTC
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--base", "ETH", "LTC",
|
||||
"--print-list",
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
captured = capsys.readouterr()
|
||||
assert ("Exchange Bittrex has 7 active markets with ETH, LTC as base currencies: "
|
||||
assert ("Exchange Binance has 7 active markets with ETH, LTC as base currencies: "
|
||||
"ETH/BTC, ETH/USDT, ETH/USDT:USDT, LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n"
|
||||
in captured.out)
|
||||
|
||||
# active markets, base=LTC
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--base", "LTC",
|
||||
"--print-list",
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
captured = capsys.readouterr()
|
||||
assert ("Exchange Bittrex has 4 active markets with LTC as base currency: "
|
||||
assert ("Exchange Binance has 4 active markets with LTC as base currency: "
|
||||
"LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n"
|
||||
in captured.out)
|
||||
|
||||
# active markets, quote=USDT, USD
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--quote", "USDT", "USD",
|
||||
"--print-list",
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
captured = capsys.readouterr()
|
||||
assert ("Exchange Bittrex has 5 active markets with USDT, USD as quote currencies: "
|
||||
assert ("Exchange Binance has 5 active markets with USDT, USD as quote currencies: "
|
||||
"ADA/USDT:USDT, ETH/USDT, ETH/USDT:USDT, LTC/USD, XLTCUSDT.\n"
|
||||
in captured.out)
|
||||
|
||||
# active markets, quote=USDT
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--quote", "USDT",
|
||||
"--print-list",
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
captured = capsys.readouterr()
|
||||
assert ("Exchange Bittrex has 4 active markets with USDT as quote currency: "
|
||||
assert ("Exchange Binance has 4 active markets with USDT as quote currency: "
|
||||
"ADA/USDT:USDT, ETH/USDT, ETH/USDT:USDT, XLTCUSDT.\n"
|
||||
in captured.out)
|
||||
|
||||
# active markets, base=LTC, quote=USDT
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--base", "LTC", "--quote", "USDT",
|
||||
"--print-list",
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
captured = capsys.readouterr()
|
||||
assert ("Exchange Bittrex has 1 active market with LTC as base currency and "
|
||||
assert ("Exchange Binance has 1 active market with LTC as base currency and "
|
||||
"with USDT as quote currency: XLTCUSDT.\n"
|
||||
in captured.out)
|
||||
|
||||
# active pairs, base=LTC, quote=USDT
|
||||
args = [
|
||||
"list-pairs",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--base", "LTC", "--quote", "USD",
|
||||
"--print-list",
|
||||
]
|
||||
start_list_markets(get_args(args), True)
|
||||
captured = capsys.readouterr()
|
||||
assert ("Exchange Bittrex has 1 active pair with LTC as base currency and "
|
||||
assert ("Exchange Binance has 1 active pair with LTC as base currency and "
|
||||
"with USD as quote currency: LTC/USD.\n"
|
||||
in captured.out)
|
||||
|
||||
# active markets, base=LTC, quote=USDT, NONEXISTENT
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--base", "LTC", "--quote", "USDT", "NONEXISTENT",
|
||||
"--print-list",
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
captured = capsys.readouterr()
|
||||
assert ("Exchange Bittrex has 1 active market with LTC as base currency and "
|
||||
assert ("Exchange Binance has 1 active market with LTC as base currency and "
|
||||
"with USDT, NONEXISTENT as quote currencies: XLTCUSDT.\n"
|
||||
in captured.out)
|
||||
|
||||
# active markets, base=LTC, quote=NONEXISTENT
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--base", "LTC", "--quote", "NONEXISTENT",
|
||||
"--print-list",
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
captured = capsys.readouterr()
|
||||
assert ("Exchange Bittrex has 0 active markets with LTC as base currency and "
|
||||
assert ("Exchange Binance has 0 active markets with LTC as base currency and "
|
||||
"with NONEXISTENT as quote currency.\n"
|
||||
in captured.out)
|
||||
|
||||
# Test tabular output
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
captured = capsys.readouterr()
|
||||
assert ("Exchange Bittrex has 12 active markets:\n"
|
||||
assert ("Exchange Binance has 12 active markets:\n"
|
||||
in captured.out)
|
||||
|
||||
# Test tabular output, no markets found
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--base", "LTC", "--quote", "NONEXISTENT",
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
captured = capsys.readouterr()
|
||||
assert ("Exchange Bittrex has 0 active markets with LTC as base currency and "
|
||||
assert ("Exchange Binance has 0 active markets with LTC as base currency and "
|
||||
"with NONEXISTENT as quote currency.\n"
|
||||
in captured.out)
|
||||
|
||||
# Test --print-json
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--print-json"
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
@@ -435,7 +435,7 @@ def test_list_markets(mocker, markets_static, capsys):
|
||||
# Test --print-csv
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--print-csv"
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
@@ -447,7 +447,7 @@ def test_list_markets(mocker, markets_static, capsys):
|
||||
# Test --one-column
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--one-column"
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
@@ -459,7 +459,7 @@ def test_list_markets(mocker, markets_static, capsys):
|
||||
# Test --one-column
|
||||
args = [
|
||||
"list-markets",
|
||||
'--config', 'config_examples/config_bittrex.example.json',
|
||||
'--config', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
"--one-column"
|
||||
]
|
||||
with pytest.raises(OperationalException, match=r"Cannot get markets.*"):
|
||||
@@ -772,7 +772,7 @@ def test_download_data_all_pairs(mocker, markets):
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_download_data(pargs)
|
||||
expected = set(['ETH/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'])
|
||||
expected = set(['BTC/USDT', 'ETH/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'])
|
||||
assert set(dl_mock.call_args_list[0][1]['pairs']) == expected
|
||||
assert dl_mock.call_count == 1
|
||||
|
||||
@@ -788,7 +788,7 @@ def test_download_data_all_pairs(mocker, markets):
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_download_data(pargs)
|
||||
expected = set(['ETH/USDT', 'LTC/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'])
|
||||
expected = set(['BTC/USDT', 'ETH/USDT', 'LTC/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'])
|
||||
assert set(dl_mock.call_args_list[0][1]['pairs']) == expected
|
||||
|
||||
|
||||
@@ -971,7 +971,7 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
args = [
|
||||
'test-pairlist',
|
||||
'-c', 'config_examples/config_bittrex.example.json'
|
||||
'-c', 'tests/testdata/testconfigs/main_test_config.json'
|
||||
]
|
||||
|
||||
start_test_pairlist(get_args(args))
|
||||
@@ -985,7 +985,7 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
|
||||
|
||||
args = [
|
||||
'test-pairlist',
|
||||
'-c', 'config_examples/config_bittrex.example.json',
|
||||
'-c', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
'--one-column',
|
||||
]
|
||||
start_test_pairlist(get_args(args))
|
||||
@@ -994,7 +994,7 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
|
||||
|
||||
args = [
|
||||
'test-pairlist',
|
||||
'-c', 'config_examples/config_bittrex.example.json',
|
||||
'-c', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
'--print-json',
|
||||
]
|
||||
start_test_pairlist(get_args(args))
|
||||
@@ -1445,12 +1445,13 @@ def test_start_list_data(testdatadir, capsys):
|
||||
start_list_data(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert "Found 2 pair / timeframe combinations." in captured.out
|
||||
assert ("\n| Pair | Timeframe | Type | From | To |\n"
|
||||
in captured.out)
|
||||
assert (
|
||||
"\n| Pair | Timeframe | Type "
|
||||
"| From | To | Candles |\n") in captured.out
|
||||
assert "UNITTEST/BTC" not in captured.out
|
||||
assert (
|
||||
"\n| XRP/ETH | 1m | spot | 2019-10-11 00:00:00 | 2019-10-13 11:19:00 |\n"
|
||||
in captured.out)
|
||||
"\n| XRP/ETH | 1m | spot | "
|
||||
"2019-10-11 00:00:00 | 2019-10-13 11:19:00 | 2469 |\n") in captured.out
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
@@ -1508,7 +1509,7 @@ def test_backtesting_show(mocker, testdatadir, capsys):
|
||||
pargs['config'] = None
|
||||
start_backtesting_show(pargs)
|
||||
assert sbr.call_count == 1
|
||||
out, err = capsys.readouterr()
|
||||
out, _err = capsys.readouterr()
|
||||
assert "Pairs for Strategy" in out
|
||||
|
||||
|
||||
|
||||
+132
-48
@@ -3,7 +3,7 @@ import json
|
||||
import logging
|
||||
import re
|
||||
from copy import deepcopy
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from unittest.mock import MagicMock, Mock, PropertyMock
|
||||
@@ -11,19 +11,18 @@ from unittest.mock import MagicMock, Mock, PropertyMock
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import pytest
|
||||
from xdist.scheduler.loadscope import LoadScopeScheduling
|
||||
|
||||
from freqtrade import constants
|
||||
from freqtrade.commands import Arguments
|
||||
from freqtrade.data.converter import ohlcv_to_dataframe, trades_list_to_df
|
||||
from freqtrade.edge import PairInfo
|
||||
from freqtrade.enums import CandleType, MarginMode, RunMode, SignalDirection, TradingMode
|
||||
from freqtrade.exchange import Exchange
|
||||
from freqtrade.exchange.exchange import timeframe_to_minutes
|
||||
from freqtrade.exchange import Exchange, timeframe_to_minutes, timeframe_to_seconds
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
from freqtrade.persistence import LocalTrade, Order, Trade, init_db
|
||||
from freqtrade.resolvers import ExchangeResolver
|
||||
from freqtrade.util import dt_ts
|
||||
from freqtrade.util.datetime_helpers import dt_now
|
||||
from freqtrade.util import dt_now, dt_ts
|
||||
from freqtrade.worker import Worker
|
||||
from tests.conftest_trades import (leverage_trade, mock_trade_1, mock_trade_2, mock_trade_3,
|
||||
mock_trade_4, mock_trade_5, mock_trade_6, short_trade)
|
||||
@@ -56,6 +55,27 @@ def pytest_configure(config):
|
||||
setattr(config.option, 'markexpr', 'not longrun')
|
||||
|
||||
|
||||
class FixtureScheduler(LoadScopeScheduling):
|
||||
# Based on the suggestion in
|
||||
# https://github.com/pytest-dev/pytest-xdist/issues/18
|
||||
|
||||
def _split_scope(self, nodeid):
|
||||
if 'exchange_online' in nodeid:
|
||||
try:
|
||||
# Extract exchange ID from nodeid
|
||||
exchange_id = nodeid.split('[')[1].split('-')[0].rstrip(']')
|
||||
return exchange_id
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pass
|
||||
|
||||
return nodeid
|
||||
|
||||
|
||||
def pytest_xdist_make_scheduler(config, log):
|
||||
return FixtureScheduler(config, log)
|
||||
|
||||
|
||||
def log_has(line, logs):
|
||||
"""Check if line is found on some caplog's message."""
|
||||
return any(line == message for message in logs.messages)
|
||||
@@ -85,17 +105,62 @@ def get_args(args):
|
||||
return Arguments(args).get_parsed_arg()
|
||||
|
||||
|
||||
def generate_trades_history(n_rows, start_date: Optional[datetime] = None, days=5):
|
||||
np.random.seed(42)
|
||||
if not start_date:
|
||||
start_date = datetime(2020, 1, 1, tzinfo=timezone.utc)
|
||||
|
||||
# Generate random data
|
||||
end_date = start_date + timedelta(days=days)
|
||||
_start_timestamp = start_date.timestamp()
|
||||
_end_timestamp = pd.to_datetime(end_date).timestamp()
|
||||
|
||||
random_timestamps_in_seconds = np.random.uniform(_start_timestamp, _end_timestamp, n_rows)
|
||||
timestamp = pd.to_datetime(random_timestamps_in_seconds, unit='s')
|
||||
|
||||
id = [
|
||||
f'a{np.random.randint(1e6, 1e7 - 1)}cd{np.random.randint(100, 999)}'
|
||||
for _ in range(n_rows)
|
||||
]
|
||||
|
||||
side = np.random.choice(['buy', 'sell'], n_rows)
|
||||
|
||||
# Initial price and subsequent changes
|
||||
initial_price = 0.019626
|
||||
price_changes = np.random.normal(0, initial_price * 0.05, n_rows)
|
||||
price = np.cumsum(np.concatenate(([initial_price], price_changes)))[:n_rows]
|
||||
|
||||
amount = np.random.uniform(0.011, 20, n_rows)
|
||||
cost = price * amount
|
||||
|
||||
# Create DataFrame
|
||||
df = pd.DataFrame({'timestamp': timestamp, 'id': id, 'type': None, 'side': side,
|
||||
'price': price, 'amount': amount, 'cost': cost})
|
||||
df['date'] = pd.to_datetime(df['timestamp'], unit='ms', utc=True)
|
||||
df = df.sort_values('timestamp').reset_index(drop=True)
|
||||
assert list(df.columns) == constants.DEFAULT_TRADES_COLUMNS + ['date']
|
||||
return df
|
||||
|
||||
|
||||
def generate_test_data(timeframe: str, size: int, start: str = '2020-07-05'):
|
||||
np.random.seed(42)
|
||||
|
||||
base = np.random.normal(20, 2, size=size)
|
||||
if timeframe == '1M':
|
||||
if timeframe == '1y':
|
||||
date = pd.date_range(start, periods=size, freq='1YS', tz='UTC')
|
||||
elif timeframe == '1M':
|
||||
date = pd.date_range(start, periods=size, freq='1MS', tz='UTC')
|
||||
elif timeframe == '1w':
|
||||
elif timeframe == '3M':
|
||||
date = pd.date_range(start, periods=size, freq='3MS', tz='UTC')
|
||||
elif timeframe == '1w' or timeframe == '7d':
|
||||
date = pd.date_range(start, periods=size, freq='1W-MON', tz='UTC')
|
||||
else:
|
||||
tf_mins = timeframe_to_minutes(timeframe)
|
||||
date = pd.date_range(start, periods=size, freq=f'{tf_mins}min', tz='UTC')
|
||||
if tf_mins >= 1:
|
||||
date = pd.date_range(start, periods=size, freq=f'{tf_mins}min', tz='UTC')
|
||||
else:
|
||||
tf_secs = timeframe_to_seconds(timeframe)
|
||||
date = pd.date_range(start, periods=size, freq=f'{tf_secs}s', tz='UTC')
|
||||
df = pd.DataFrame({
|
||||
'date': date,
|
||||
'open': base,
|
||||
@@ -531,6 +596,7 @@ def get_default_conf(testdatadir):
|
||||
"internals": {},
|
||||
"export": "none",
|
||||
"dataformat_ohlcv": "feather",
|
||||
"runmode": "dry_run",
|
||||
"candle_type_def": CandleType.SPOT,
|
||||
}
|
||||
return configuration
|
||||
@@ -939,6 +1005,58 @@ def get_markets():
|
||||
'maintenance_rate': '0.005',
|
||||
},
|
||||
},
|
||||
'BTC/USDT': {
|
||||
'id': 'USDT-BTC',
|
||||
'symbol': 'BTC/USDT',
|
||||
'base': 'BTC',
|
||||
'quote': 'USDT',
|
||||
'settle': None,
|
||||
'baseId': 'BTC',
|
||||
'quoteId': 'USDT',
|
||||
'settleId': None,
|
||||
'type': 'spot',
|
||||
'spot': True,
|
||||
'margin': True,
|
||||
'swap': False,
|
||||
'future': False,
|
||||
'option': False,
|
||||
'active': True,
|
||||
'contract': None,
|
||||
'linear': None,
|
||||
'inverse': None,
|
||||
'taker': 0.0006,
|
||||
'maker': 0.0002,
|
||||
'contractSize': None,
|
||||
'expiry': None,
|
||||
'expiryDateTime': None,
|
||||
'strike': None,
|
||||
'optionType': None,
|
||||
'precision': {
|
||||
'amount': 4,
|
||||
'price': 4,
|
||||
},
|
||||
'limits': {
|
||||
'leverage': {
|
||||
'min': 1,
|
||||
'max': 100,
|
||||
},
|
||||
'amount': {
|
||||
'min': 0.000221,
|
||||
'max': None,
|
||||
},
|
||||
'price': {
|
||||
'min': 1e-02,
|
||||
'max': None,
|
||||
},
|
||||
'cost': {
|
||||
'min': None,
|
||||
'max': None,
|
||||
},
|
||||
},
|
||||
'info': {
|
||||
'maintenance_rate': '0.005',
|
||||
},
|
||||
},
|
||||
'LTC/USDT': {
|
||||
'id': 'USDT-LTC',
|
||||
'symbol': 'LTC/USDT',
|
||||
@@ -2363,14 +2481,7 @@ def trades_history_df(trades_history):
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def fetch_trades_result():
|
||||
return [{'info': {'a': 126181329,
|
||||
'p': '0.01962700',
|
||||
'q': '0.04000000',
|
||||
'f': 138604155,
|
||||
'l': 138604155,
|
||||
'T': 1565798399463,
|
||||
'm': False,
|
||||
'M': True},
|
||||
return [{'info': ['0.01962700', '0.04000000', '1565798399.4631551', 'b', 'm', '', '126181329'],
|
||||
'timestamp': 1565798399463,
|
||||
'datetime': '2019-08-14T15:59:59.463Z',
|
||||
'symbol': 'ETH/BTC',
|
||||
@@ -2383,14 +2494,7 @@ def fetch_trades_result():
|
||||
'amount': 0.04,
|
||||
'cost': 0.00078508,
|
||||
'fee': None},
|
||||
{'info': {'a': 126181330,
|
||||
'p': '0.01962700',
|
||||
'q': '0.24400000',
|
||||
'f': 138604156,
|
||||
'l': 138604156,
|
||||
'T': 1565798399629,
|
||||
'm': False,
|
||||
'M': True},
|
||||
{'info': ['0.01962700', '0.24400000', '1565798399.6291551', 'b', 'm', '', '126181330'],
|
||||
'timestamp': 1565798399629,
|
||||
'datetime': '2019-08-14T15:59:59.629Z',
|
||||
'symbol': 'ETH/BTC',
|
||||
@@ -2403,14 +2507,7 @@ def fetch_trades_result():
|
||||
'amount': 0.244,
|
||||
'cost': 0.004788987999999999,
|
||||
'fee': None},
|
||||
{'info': {'a': 126181331,
|
||||
'p': '0.01962600',
|
||||
'q': '0.01100000',
|
||||
'f': 138604157,
|
||||
'l': 138604157,
|
||||
'T': 1565798399752,
|
||||
'm': True,
|
||||
'M': True},
|
||||
{'info': ['0.01962600', '0.01100000', '1565798399.7521551', 's', 'm', '', '126181331'],
|
||||
'timestamp': 1565798399752,
|
||||
'datetime': '2019-08-14T15:59:59.752Z',
|
||||
'symbol': 'ETH/BTC',
|
||||
@@ -2423,14 +2520,7 @@ def fetch_trades_result():
|
||||
'amount': 0.011,
|
||||
'cost': 0.00021588599999999999,
|
||||
'fee': None},
|
||||
{'info': {'a': 126181332,
|
||||
'p': '0.01962600',
|
||||
'q': '0.01100000',
|
||||
'f': 138604158,
|
||||
'l': 138604158,
|
||||
'T': 1565798399862,
|
||||
'm': True,
|
||||
'M': True},
|
||||
{'info': ['0.01962600', '0.01100000', '1565798399.8621551', 's', 'm', '', '126181332'],
|
||||
'timestamp': 1565798399862,
|
||||
'datetime': '2019-08-14T15:59:59.862Z',
|
||||
'symbol': 'ETH/BTC',
|
||||
@@ -2443,14 +2533,8 @@ def fetch_trades_result():
|
||||
'amount': 0.011,
|
||||
'cost': 0.00021588599999999999,
|
||||
'fee': None},
|
||||
{'info': {'a': 126181333,
|
||||
'p': '0.01952600',
|
||||
'q': '0.01200000',
|
||||
'f': 138604158,
|
||||
'l': 138604158,
|
||||
'T': 1565798399872,
|
||||
'm': True,
|
||||
'M': True},
|
||||
{'info': ['0.01952600', '0.01200000', '1565798399.8721551', 's', 'm', '', '126181333',
|
||||
1565798399872512133],
|
||||
'timestamp': 1565798399872,
|
||||
'datetime': '2019-08-14T15:59:59.872Z',
|
||||
'symbol': 'ETH/BTC',
|
||||
|
||||
@@ -17,7 +17,8 @@ from freqtrade.data.history import (get_timerange, load_data, load_pair_history,
|
||||
validate_backtest_data)
|
||||
from freqtrade.data.history.idatahandler import IDataHandler
|
||||
from freqtrade.enums import CandleType
|
||||
from tests.conftest import generate_test_data, log_has, log_has_re
|
||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
|
||||
from tests.conftest import generate_test_data, generate_trades_history, log_has, log_has_re
|
||||
from tests.data.test_history import _clean_test_file
|
||||
|
||||
|
||||
@@ -51,6 +52,49 @@ def test_trades_to_ohlcv(trades_history_df, caplog):
|
||||
assert 'close' in df.columns
|
||||
assert df.iloc[0, :]['high'] == 0.019627
|
||||
assert df.iloc[0, :]['low'] == 0.019626
|
||||
assert df.iloc[0, :]['date'] == pd.Timestamp('2019-08-14 15:59:00+0000')
|
||||
|
||||
df_1h = trades_to_ohlcv(trades_history_df, '1h')
|
||||
assert len(df_1h) == 1
|
||||
assert df_1h.iloc[0, :]['high'] == 0.019627
|
||||
assert df_1h.iloc[0, :]['low'] == 0.019626
|
||||
assert df_1h.iloc[0, :]['date'] == pd.Timestamp('2019-08-14 15:00:00+0000')
|
||||
|
||||
df_1s = trades_to_ohlcv(trades_history_df, '1s')
|
||||
assert len(df_1s) == 2
|
||||
assert df_1s.iloc[0, :]['high'] == 0.019627
|
||||
assert df_1s.iloc[0, :]['low'] == 0.019627
|
||||
assert df_1s.iloc[0, :]['date'] == pd.Timestamp('2019-08-14 15:59:49+0000')
|
||||
assert df_1s.iloc[-1, :]['date'] == pd.Timestamp('2019-08-14 15:59:59+0000')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('timeframe,rows,days,candles,start,end,weekday', [
|
||||
('1s', 20_000, 5, 19522, '2020-01-01 00:00:05', '2020-01-05 23:59:27', None),
|
||||
('1m', 20_000, 5, 6745, '2020-01-01 00:00:00', '2020-01-05 23:59:00', None),
|
||||
('5m', 20_000, 5, 1440, '2020-01-01 00:00:00', '2020-01-05 23:55:00', None),
|
||||
('15m', 20_000, 5, 480, '2020-01-01 00:00:00', '2020-01-05 23:45:00', None),
|
||||
('1h', 20_000, 5, 120, '2020-01-01 00:00:00', '2020-01-05 23:00:00', None),
|
||||
('2h', 20_000, 5, 60, '2020-01-01 00:00:00', '2020-01-05 22:00:00', None),
|
||||
('4h', 20_000, 5, 30, '2020-01-01 00:00:00', '2020-01-05 20:00:00', None),
|
||||
('8h', 20_000, 5, 15, '2020-01-01 00:00:00', '2020-01-05 16:00:00', None),
|
||||
('12h', 20_000, 5, 10, '2020-01-01 00:00:00', '2020-01-05 12:00:00', None),
|
||||
('1d', 20_000, 5, 5, '2020-01-01 00:00:00', '2020-01-05 00:00:00', 'Sunday'),
|
||||
('7d', 20_000, 37, 6, '2020-01-06 00:00:00', '2020-02-10 00:00:00', 'Monday'),
|
||||
('1w', 20_000, 37, 6, '2020-01-06 00:00:00', '2020-02-10 00:00:00', 'Monday'),
|
||||
('1M', 20_000, 74, 3, '2020-01-01 00:00:00', '2020-03-01 00:00:00', None),
|
||||
('3M', 20_000, 100, 2, '2020-01-01 00:00:00', '2020-04-01 00:00:00', None),
|
||||
('1y', 20_000, 1000, 3, '2020-01-01 00:00:00', '2022-01-01 00:00:00', None),
|
||||
])
|
||||
def test_trades_to_ohlcv_multi(timeframe, rows, days, candles, start, end, weekday):
|
||||
trades_history = generate_trades_history(n_rows=rows, days=days)
|
||||
df = trades_to_ohlcv(trades_history, timeframe)
|
||||
assert not df.empty
|
||||
assert len(df) == candles
|
||||
assert df.iloc[0, :]['date'] == pd.Timestamp(f'{start}+0000')
|
||||
assert df.iloc[-1, :]['date'] == pd.Timestamp(f'{end}+0000')
|
||||
if weekday:
|
||||
# Weekday is only relevant for daily and weekly candles.
|
||||
assert df.iloc[-1, :]['date'].day_name() == weekday
|
||||
|
||||
|
||||
def test_ohlcv_fill_up_missing_data(testdatadir, caplog):
|
||||
@@ -132,6 +176,45 @@ def test_ohlcv_fill_up_missing_data2(caplog):
|
||||
f"{len(data)} - after: {len(data2)}.*", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('timeframe', [
|
||||
'1s', '1m', '5m', '15m', '1h', '2h', '4h', '8h', '12h', '1d', '7d', '1w', '1M', '3M', '1y'
|
||||
])
|
||||
def test_ohlcv_to_dataframe_multi(timeframe):
|
||||
data = generate_test_data(timeframe, 180)
|
||||
assert len(data) == 180
|
||||
df = ohlcv_to_dataframe(data, timeframe, 'UNITTEST/USDT')
|
||||
assert len(df) == len(data) - 1
|
||||
df1 = ohlcv_to_dataframe(data, timeframe, 'UNITTEST/USDT', drop_incomplete=False)
|
||||
assert len(df1) == len(data)
|
||||
assert data.equals(df1)
|
||||
|
||||
data1 = data.copy()
|
||||
if timeframe in ('1M', '3M', '1y'):
|
||||
data1.loc[:, 'date'] = data1.loc[:, 'date'] + pd.to_timedelta('1w')
|
||||
else:
|
||||
# Shift by half a timeframe
|
||||
data1.loc[:, 'date'] = data1.loc[:, 'date'] + (pd.to_timedelta(timeframe) / 2)
|
||||
df2 = ohlcv_to_dataframe(data1, timeframe, 'UNITTEST/USDT')
|
||||
|
||||
assert len(df2) == len(data) - 1
|
||||
tfs = timeframe_to_seconds(timeframe)
|
||||
tfm = timeframe_to_minutes(timeframe)
|
||||
if 1 <= tfm < 10000:
|
||||
# minute based resampling does not work on timeframes >= 1 week
|
||||
ohlcv_dict = {
|
||||
'open': 'first',
|
||||
'high': 'max',
|
||||
'low': 'min',
|
||||
'close': 'last',
|
||||
'volume': 'sum'
|
||||
}
|
||||
dfs = data1.resample(f"{tfs}s", on='date').agg(ohlcv_dict).reset_index(drop=False)
|
||||
dfm = data1.resample(f"{tfm}min", on='date').agg(ohlcv_dict).reset_index(drop=False)
|
||||
|
||||
assert dfs.equals(dfm)
|
||||
assert dfs.equals(df1)
|
||||
|
||||
|
||||
def test_ohlcv_to_dataframe_1M():
|
||||
|
||||
# Monthly ticks from 2019-09-01 to 2023-07-01
|
||||
|
||||
@@ -148,19 +148,25 @@ def test_jsondatahandler_ohlcv_load(testdatadir, caplog):
|
||||
def test_datahandler_ohlcv_data_min_max(testdatadir):
|
||||
dh = JsonDataHandler(testdatadir)
|
||||
min_max = dh.ohlcv_data_min_max('UNITTEST/BTC', '5m', 'spot')
|
||||
assert len(min_max) == 2
|
||||
assert len(min_max) == 3
|
||||
|
||||
# Empty pair
|
||||
min_max = dh.ohlcv_data_min_max('UNITTEST/BTC', '8m', 'spot')
|
||||
assert len(min_max) == 2
|
||||
assert len(min_max) == 3
|
||||
assert min_max[0] == datetime.fromtimestamp(0, tz=timezone.utc)
|
||||
assert min_max[0] == min_max[1]
|
||||
# Empty pair2
|
||||
min_max = dh.ohlcv_data_min_max('NOPAIR/XXX', '4m', 'spot')
|
||||
assert len(min_max) == 2
|
||||
min_max = dh.ohlcv_data_min_max('NOPAIR/XXX', '41m', 'spot')
|
||||
assert len(min_max) == 3
|
||||
assert min_max[0] == datetime.fromtimestamp(0, tz=timezone.utc)
|
||||
assert min_max[0] == min_max[1]
|
||||
|
||||
# Existing pair ...
|
||||
min_max = dh.ohlcv_data_min_max('UNITTEST/BTC', '1m', 'spot')
|
||||
assert len(min_max) == 3
|
||||
assert min_max[0] == datetime(2017, 11, 4, 23, 2, tzinfo=timezone.utc)
|
||||
assert min_max[1] == datetime(2017, 11, 14, 22, 59, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def test_datahandler__check_empty_df(testdatadir, caplog):
|
||||
dh = JsonDataHandler(testdatadir)
|
||||
@@ -513,11 +519,11 @@ def test_gethandlerclass():
|
||||
|
||||
def test_get_datahandler(testdatadir):
|
||||
dh = get_datahandler(testdatadir, 'json')
|
||||
assert type(dh) == JsonDataHandler
|
||||
assert isinstance(dh, JsonDataHandler)
|
||||
dh = get_datahandler(testdatadir, 'jsongz')
|
||||
assert type(dh) == JsonGzDataHandler
|
||||
assert isinstance(dh, JsonGzDataHandler)
|
||||
dh1 = get_datahandler(testdatadir, 'jsongz', dh)
|
||||
assert id(dh1) == id(dh)
|
||||
|
||||
dh = get_datahandler(testdatadir, 'hdf5')
|
||||
assert type(dh) == HDF5DataHandler
|
||||
assert isinstance(dh, HDF5DataHandler)
|
||||
|
||||
@@ -194,7 +194,7 @@ def test_get_producer_df(default_conf):
|
||||
assert la == empty_la
|
||||
|
||||
# non existent timeframe, empty dataframe
|
||||
datframe, la = dataprovider.get_producer_df(pair, timeframe='1h')
|
||||
_dataframe, la = dataprovider.get_producer_df(pair, timeframe='1h')
|
||||
assert dataframe.empty
|
||||
assert la == empty_la
|
||||
|
||||
@@ -508,16 +508,13 @@ def test_dp_get_required_startup(default_conf_usdt):
|
||||
dp = DataProvider(default_conf_usdt, None)
|
||||
|
||||
# No FreqAI config
|
||||
assert dp.get_required_startup('5m', False) == 0
|
||||
assert dp.get_required_startup('1h', False) == 0
|
||||
assert dp.get_required_startup('1d', False) == 0
|
||||
assert dp.get_required_startup('1d', True) == 0
|
||||
assert dp.get_required_startup('5m') == 0
|
||||
assert dp.get_required_startup('1h') == 0
|
||||
assert dp.get_required_startup('1d') == 0
|
||||
|
||||
dp._config['startup_candle_count'] = 20
|
||||
assert dp.get_required_startup('5m', False) == 20
|
||||
assert dp.get_required_startup('5m', True) == 20
|
||||
assert dp.get_required_startup('1h', False) == 20
|
||||
assert dp.get_required_startup('5m') == 20
|
||||
assert dp.get_required_startup('1h') == 20
|
||||
assert dp.get_required_startup('1h') == 20
|
||||
|
||||
# With freqAI config
|
||||
@@ -532,37 +529,19 @@ def test_dp_get_required_startup(default_conf_usdt):
|
||||
]
|
||||
}
|
||||
}
|
||||
assert dp.get_required_startup('5m', False) == 20
|
||||
assert dp.get_required_startup('5m', True) == 5780
|
||||
|
||||
assert dp.get_required_startup('1h', False) == 20
|
||||
assert dp.get_required_startup('1h', True) == 500
|
||||
|
||||
assert dp.get_required_startup('1d', False) == 20
|
||||
assert dp.get_required_startup('1d', True) == 40
|
||||
assert dp.get_required_startup('5m') == 5780
|
||||
assert dp.get_required_startup('1h') == 500
|
||||
assert dp.get_required_startup('1d') == 40
|
||||
|
||||
# FreqAI kindof ignores startup_candle_count if it's below indicator_periods_candles
|
||||
dp._config['startup_candle_count'] = 0
|
||||
assert dp.get_required_startup('5m', False) == 20
|
||||
assert dp.get_required_startup('5m', True) == 5780
|
||||
|
||||
assert dp.get_required_startup('1h', False) == 20
|
||||
assert dp.get_required_startup('1h', True) == 500
|
||||
|
||||
assert dp.get_required_startup('1d', False) == 20
|
||||
assert dp.get_required_startup('1d', True) == 40
|
||||
assert dp.get_required_startup('5m') == 5780
|
||||
assert dp.get_required_startup('1h') == 500
|
||||
assert dp.get_required_startup('1d') == 40
|
||||
|
||||
dp._config['freqai']['feature_parameters']['indicator_periods_candles'][1] = 50
|
||||
assert dp.get_required_startup('5m', False) == 50
|
||||
assert dp.get_required_startup('5m', True) == 5810
|
||||
|
||||
assert dp.get_required_startup('1h', False) == 50
|
||||
assert dp.get_required_startup('1h', True) == 530
|
||||
|
||||
assert dp.get_required_startup('1d', False) == 50
|
||||
assert dp.get_required_startup('1d', True) == 70
|
||||
assert dp.get_required_startup('5m') == 5810
|
||||
assert dp.get_required_startup('1h') == 530
|
||||
assert dp.get_required_startup('1d') == 70
|
||||
|
||||
# scenario from issue https://github.com/freqtrade/freqtrade/issues/9432
|
||||
@@ -577,12 +556,6 @@ def test_dp_get_required_startup(default_conf_usdt):
|
||||
}
|
||||
}
|
||||
dp._config['startup_candle_count'] = 40
|
||||
assert dp.get_required_startup('5m', False) == 40
|
||||
assert dp.get_required_startup('5m', True) == 51880
|
||||
|
||||
assert dp.get_required_startup('1h', False) == 40
|
||||
assert dp.get_required_startup('1h', True) == 4360
|
||||
|
||||
assert dp.get_required_startup('1d', False) == 40
|
||||
assert dp.get_required_startup('1d', True) == 220
|
||||
assert dp.get_required_startup('5m') == 51880
|
||||
assert dp.get_required_startup('1h') == 4360
|
||||
assert dp.get_required_startup('1d') == 220
|
||||
|
||||
@@ -38,7 +38,7 @@ def test_download_data_main_all_pairs(mocker, markets):
|
||||
"timeframes": ["5m", "1h"]
|
||||
})
|
||||
download_data_main(config)
|
||||
expected = set(['ETH/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'])
|
||||
expected = set(['BTC/USDT', 'ETH/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'])
|
||||
assert set(dl_mock.call_args_list[0][1]['pairs']) == expected
|
||||
assert dl_mock.call_count == 1
|
||||
|
||||
@@ -50,7 +50,7 @@ def test_download_data_main_all_pairs(mocker, markets):
|
||||
"include_inactive": True
|
||||
})
|
||||
download_data_main(config)
|
||||
expected = set(['ETH/USDT', 'LTC/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'])
|
||||
expected = set(['BTC/USDT', 'ETH/USDT', 'LTC/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'])
|
||||
assert set(dl_mock.call_args_list[0][1]['pairs']) == expected
|
||||
|
||||
|
||||
|
||||
@@ -508,8 +508,9 @@ def test_refresh_backtest_ohlcv_data(
|
||||
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||
mocker.patch.object(Path, "unlink", MagicMock())
|
||||
default_conf['trading_mode'] = trademode
|
||||
|
||||
ex = get_patched_exchange(mocker, default_conf)
|
||||
ex = get_patched_exchange(mocker, default_conf, id='bybit')
|
||||
timerange = TimeRange.parse_timerange("20190101-20190102")
|
||||
refresh_backtest_ohlcv_data(exchange=ex, pairs=["ETH/BTC", "XRP/BTC"],
|
||||
timeframes=["1m", "5m"], datadir=testdatadir,
|
||||
@@ -521,6 +522,9 @@ def test_refresh_backtest_ohlcv_data(
|
||||
assert dl_mock.call_args[1]['timerange'].starttype == 'date'
|
||||
|
||||
assert log_has_re(r"Downloading pair ETH/BTC, .* interval 1m\.", caplog)
|
||||
if trademode == 'futures':
|
||||
assert log_has_re(r"Downloading pair ETH/BTC, funding_rate, interval 8h\.", caplog)
|
||||
assert log_has_re(r"Downloading pair ETH/BTC, mark, interval 4h\.", caplog)
|
||||
|
||||
|
||||
def test_download_data_no_markets(mocker, default_conf, caplog, testdatadir):
|
||||
|
||||
+113
-43
@@ -13,7 +13,7 @@ from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||
from freqtrade.exceptions import (DDosProtection, DependencyException, ExchangeError,
|
||||
InsufficientFundsError, InvalidOrderException,
|
||||
OperationalException, PricingError, TemporaryError)
|
||||
from freqtrade.exchange import (Binance, Bittrex, Exchange, Kraken, market_is_active,
|
||||
from freqtrade.exchange import (Binance, Bybit, Exchange, Kraken, market_is_active,
|
||||
timeframe_to_prev_date)
|
||||
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_COUNT,
|
||||
calculate_backoff, remove_exchange_credentials)
|
||||
@@ -55,7 +55,7 @@ get_entry_rate_data = [
|
||||
('bid', 6, 5, None, 0, 5), # last not available - uses bid
|
||||
]
|
||||
|
||||
get_sell_rate_data = [
|
||||
get_exit_rate_data = [
|
||||
('bid', 12.0, 11.0, 11.5, 0.0, 11.0), # full bid side
|
||||
('bid', 12.0, 11.0, 11.5, 1.0, 11.5), # full last side
|
||||
('bid', 12.0, 11.0, 11.5, 0.5, 11.25), # between bid and lat
|
||||
@@ -228,10 +228,10 @@ def test_exchange_resolver(default_conf, mocker, caplog):
|
||||
assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog)
|
||||
caplog.clear()
|
||||
|
||||
default_conf['exchange']['name'] = 'Bittrex'
|
||||
default_conf['exchange']['name'] = 'Bybit'
|
||||
exchange = ExchangeResolver.load_exchange(default_conf)
|
||||
assert isinstance(exchange, Exchange)
|
||||
assert isinstance(exchange, Bittrex)
|
||||
assert isinstance(exchange, Bybit)
|
||||
assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.",
|
||||
caplog)
|
||||
caplog.clear()
|
||||
@@ -263,8 +263,8 @@ def test_exchange_resolver(default_conf, mocker, caplog):
|
||||
|
||||
def test_validate_order_time_in_force(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
# explicitly test bittrex, exchanges implementing other policies need separate tests
|
||||
ex = get_patched_exchange(mocker, default_conf, id="bittrex")
|
||||
# explicitly test bybit, exchanges implementing other policies need separate tests
|
||||
ex = get_patched_exchange(mocker, default_conf, id="bybit")
|
||||
tif = {
|
||||
"buy": "gtc",
|
||||
"sell": "gtc",
|
||||
@@ -273,11 +273,14 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog):
|
||||
ex.validate_order_time_in_force(tif)
|
||||
tif2 = {
|
||||
"buy": "fok",
|
||||
"sell": "ioc",
|
||||
"sell": "ioc22",
|
||||
}
|
||||
with pytest.raises(OperationalException, match=r"Time in force.*not supported for .*"):
|
||||
ex.validate_order_time_in_force(tif2)
|
||||
|
||||
tif2 = {
|
||||
"buy": "fok",
|
||||
"sell": "ioc",
|
||||
}
|
||||
# Patch to see if this will pass if the values are in the ft dict
|
||||
ex._ft_has.update({"order_time_in_force": ["GTC", "FOK", "IOC"]})
|
||||
ex.validate_order_time_in_force(tif2)
|
||||
@@ -915,7 +918,6 @@ def test_validate_ordertypes(default_conf, mocker):
|
||||
mocker.patch(f'{EXMS}.validate_timeframes')
|
||||
mocker.patch(f'{EXMS}.validate_stakecurrency')
|
||||
mocker.patch(f'{EXMS}.validate_pricing')
|
||||
mocker.patch(f'{EXMS}.name', 'Bittrex')
|
||||
|
||||
default_conf['order_types'] = {
|
||||
'entry': 'limit',
|
||||
@@ -2510,8 +2512,10 @@ def test_fetch_l2_order_book_exception(default_conf, mocker, exchange_name):
|
||||
|
||||
@pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", get_entry_rate_data)
|
||||
def test_get_entry_rate(mocker, default_conf, caplog, side, ask, bid,
|
||||
last, last_ab, expected) -> None:
|
||||
last, last_ab, expected, time_machine) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
start_dt = datetime(2023, 12, 1, 0, 10, 0, tzinfo=timezone.utc)
|
||||
time_machine.move_to(start_dt, tick=False)
|
||||
if last_ab is None:
|
||||
del default_conf['entry_pricing']['price_last_balance']
|
||||
else:
|
||||
@@ -2519,39 +2523,65 @@ def test_get_entry_rate(mocker, default_conf, caplog, side, ask, bid,
|
||||
default_conf['entry_pricing']['price_side'] = side
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': ask, 'last': last, 'bid': bid})
|
||||
log_msg = "Using cached entry rate for ETH/BTC."
|
||||
|
||||
assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=True) == expected
|
||||
assert not log_has("Using cached entry rate for ETH/BTC.", caplog)
|
||||
assert not log_has(log_msg, caplog)
|
||||
|
||||
time_machine.move_to(start_dt + timedelta(minutes=4), tick=False)
|
||||
# Running a 2nd time without Refresh!
|
||||
caplog.clear()
|
||||
assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=False) == expected
|
||||
assert log_has("Using cached entry rate for ETH/BTC.", caplog)
|
||||
assert log_has(log_msg, caplog)
|
||||
|
||||
time_machine.move_to(start_dt + timedelta(minutes=6), tick=False)
|
||||
# Running a 2nd time - forces refresh due to ttl timeout
|
||||
caplog.clear()
|
||||
assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=False) == expected
|
||||
assert not log_has(log_msg, caplog)
|
||||
|
||||
# Running a 2nd time with Refresh on!
|
||||
caplog.clear()
|
||||
assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=True) == expected
|
||||
assert not log_has("Using cached entry rate for ETH/BTC.", caplog)
|
||||
assert not log_has(log_msg, caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', get_sell_rate_data)
|
||||
@pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', get_exit_rate_data)
|
||||
def test_get_exit_rate(default_conf, mocker, caplog, side, bid, ask,
|
||||
last, last_ab, expected) -> None:
|
||||
last, last_ab, expected, time_machine) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
start_dt = datetime(2023, 12, 1, 0, 10, 0, tzinfo=timezone.utc)
|
||||
time_machine.move_to(start_dt, tick=False)
|
||||
|
||||
default_conf['exit_pricing']['price_side'] = side
|
||||
if last_ab is not None:
|
||||
default_conf['exit_pricing']['price_last_balance'] = last_ab
|
||||
mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': ask, 'bid': bid, 'last': last})
|
||||
pair = "ETH/BTC"
|
||||
log_msg = "Using cached exit rate for ETH/BTC."
|
||||
|
||||
# Test regular mode
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
rate = exchange.get_rate(pair, side="exit", is_short=False, refresh=True)
|
||||
assert not log_has("Using cached exit rate for ETH/BTC.", caplog)
|
||||
assert not log_has(log_msg, caplog)
|
||||
assert isinstance(rate, float)
|
||||
assert rate == expected
|
||||
# Use caching
|
||||
rate = exchange.get_rate(pair, side="exit", is_short=False, refresh=False)
|
||||
assert rate == expected
|
||||
assert log_has("Using cached exit rate for ETH/BTC.", caplog)
|
||||
caplog.clear()
|
||||
assert exchange.get_rate(pair, side="exit", is_short=False, refresh=False) == expected
|
||||
assert log_has(log_msg, caplog)
|
||||
|
||||
time_machine.move_to(start_dt + timedelta(minutes=4), tick=False)
|
||||
# Caching still active - TTL didn't expire
|
||||
caplog.clear()
|
||||
assert exchange.get_rate(pair, side="exit", is_short=False, refresh=False) == expected
|
||||
assert log_has(log_msg, caplog)
|
||||
|
||||
time_machine.move_to(start_dt + timedelta(minutes=6), tick=False)
|
||||
# Caching expired - refresh forced
|
||||
caplog.clear()
|
||||
assert exchange.get_rate(pair, side="exit", is_short=False, refresh=False) == expected
|
||||
assert not log_has(log_msg, caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("entry,is_short,side,ask,bid,last,last_ab,expected", [
|
||||
@@ -2647,9 +2677,9 @@ def test_get_exit_rate_exception(default_conf, mocker, is_short):
|
||||
@pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", get_entry_rate_data)
|
||||
@pytest.mark.parametrize("side2", ['bid', 'ask'])
|
||||
@pytest.mark.parametrize("use_order_book", [True, False])
|
||||
def test_get_rates_testing_buy(mocker, default_conf, caplog, side, ask, bid,
|
||||
last, last_ab, expected,
|
||||
side2, use_order_book, order_book_l2) -> None:
|
||||
def test_get_rates_testing_entry(mocker, default_conf, caplog, side, ask, bid,
|
||||
last, last_ab, expected,
|
||||
side2, use_order_book, order_book_l2) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
if last_ab is None:
|
||||
del default_conf['entry_pricing']['price_last_balance']
|
||||
@@ -2683,10 +2713,10 @@ def test_get_rates_testing_buy(mocker, default_conf, caplog, side, ask, bid,
|
||||
assert api_mock.fetch_ticker.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', get_sell_rate_data)
|
||||
@pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', get_exit_rate_data)
|
||||
@pytest.mark.parametrize("side2", ['bid', 'ask'])
|
||||
@pytest.mark.parametrize("use_order_book", [True, False])
|
||||
def test_get_rates_testing_sell(default_conf, mocker, caplog, side, bid, ask,
|
||||
def test_get_rates_testing_exit(default_conf, mocker, caplog, side, bid, ask,
|
||||
last, last_ab, expected,
|
||||
side2, use_order_book, order_book_l2) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
@@ -2766,7 +2796,6 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na
|
||||
assert res_ohlcv[9][4] == 0.07668
|
||||
assert res_ohlcv[9][5] == 16.65244264
|
||||
|
||||
# Bittrex use-case (real data from Bittrex)
|
||||
# This OHLCV data is ordered ASC (oldest first, newest last)
|
||||
ohlcv = [
|
||||
[1527827700000, 0.07659999, 0.0766, 0.07627, 0.07657998, 1.85216924],
|
||||
@@ -2815,10 +2844,17 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
|
||||
exchange._api_async.fetch_trades = get_mock_coro(fetch_trades_result)
|
||||
|
||||
pair = 'ETH/BTC'
|
||||
res = await exchange._async_fetch_trades(pair, since=None, params=None)
|
||||
res, pagid = await exchange._async_fetch_trades(pair, since=None, params=None)
|
||||
assert isinstance(res, list)
|
||||
assert isinstance(res[0], list)
|
||||
assert isinstance(res[1], list)
|
||||
if exchange._trades_pagination == 'id':
|
||||
if exchange_name == 'kraken':
|
||||
assert pagid == 1565798399872512133
|
||||
else:
|
||||
assert pagid == '126181333'
|
||||
else:
|
||||
assert pagid == 1565798399872
|
||||
|
||||
assert exchange._api_async.fetch_trades.call_count == 1
|
||||
assert exchange._api_async.fetch_trades.call_args[0][0] == pair
|
||||
@@ -2827,11 +2863,20 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
|
||||
assert log_has_re(f"Fetching trades for pair {pair}, since .*", caplog)
|
||||
caplog.clear()
|
||||
exchange._api_async.fetch_trades.reset_mock()
|
||||
res = await exchange._async_fetch_trades(pair, since=None, params={'from': '123'})
|
||||
res, pagid = await exchange._async_fetch_trades(pair, since=None, params={'from': '123'})
|
||||
assert exchange._api_async.fetch_trades.call_count == 1
|
||||
assert exchange._api_async.fetch_trades.call_args[0][0] == pair
|
||||
assert exchange._api_async.fetch_trades.call_args[1]['limit'] == 1000
|
||||
assert exchange._api_async.fetch_trades.call_args[1]['params'] == {'from': '123'}
|
||||
|
||||
if exchange._trades_pagination == 'id':
|
||||
if exchange_name == 'kraken':
|
||||
assert pagid == 1565798399872512133
|
||||
else:
|
||||
assert pagid == '126181333'
|
||||
else:
|
||||
assert pagid == 1565798399872
|
||||
|
||||
assert log_has_re(f"Fetching trades for pair {pair}, params: .*", caplog)
|
||||
exchange.close()
|
||||
|
||||
@@ -2886,8 +2931,9 @@ async def test__async_fetch_trades_contract_size(default_conf, mocker, caplog, e
|
||||
)
|
||||
|
||||
pair = 'ETH/USDT:USDT'
|
||||
res = await exchange._async_fetch_trades(pair, since=None, params=None)
|
||||
res, pagid = await exchange._async_fetch_trades(pair, since=None, params=None)
|
||||
assert res[0][5] == 300
|
||||
assert pagid is not None
|
||||
exchange.close()
|
||||
|
||||
|
||||
@@ -2897,13 +2943,17 @@ async def test__async_get_trade_history_id(default_conf, mocker, exchange_name,
|
||||
fetch_trades_result):
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
if exchange._trades_pagination != 'id':
|
||||
exchange.close()
|
||||
pytest.skip("Exchange does not support pagination by trade id")
|
||||
pagination_arg = exchange._trades_pagination_arg
|
||||
|
||||
async def mock_get_trade_hist(pair, *args, **kwargs):
|
||||
if 'since' in kwargs:
|
||||
# Return first 3
|
||||
return fetch_trades_result[:-2]
|
||||
elif kwargs.get('params', {}).get(pagination_arg) == fetch_trades_result[-3]['id']:
|
||||
elif kwargs.get('params', {}).get(pagination_arg) in (
|
||||
fetch_trades_result[-3]['id'], 1565798399752):
|
||||
# Return 2
|
||||
return fetch_trades_result[-3:-1]
|
||||
else:
|
||||
@@ -2919,7 +2969,8 @@ async def test__async_get_trade_history_id(default_conf, mocker, exchange_name,
|
||||
assert isinstance(ret, tuple)
|
||||
assert ret[0] == pair
|
||||
assert isinstance(ret[1], list)
|
||||
assert len(ret[1]) == len(fetch_trades_result)
|
||||
if exchange_name != 'kraken':
|
||||
assert len(ret[1]) == len(fetch_trades_result)
|
||||
assert exchange._api_async.fetch_trades.call_count == 3
|
||||
fetch_trades_cal = exchange._api_async.fetch_trades.call_args_list
|
||||
# first call (using since, not fromId)
|
||||
@@ -2932,6 +2983,21 @@ async def test__async_get_trade_history_id(default_conf, mocker, exchange_name,
|
||||
assert exchange._ft_has['trades_pagination_arg'] in fetch_trades_cal[1][1]['params']
|
||||
|
||||
|
||||
@pytest.mark.parametrize('trade_id, expected', [
|
||||
('1234', True),
|
||||
('170544369512007228', True),
|
||||
('1705443695120072285', True),
|
||||
('170544369512007228555', True),
|
||||
])
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test__valid_trade_pagination_id(mocker, default_conf_usdt, exchange_name, trade_id, expected):
|
||||
if exchange_name == 'kraken':
|
||||
pytest.skip("Kraken has a different pagination id format, and an explicit test.")
|
||||
exchange = get_patched_exchange(mocker, default_conf_usdt, id=exchange_name)
|
||||
|
||||
assert exchange._valid_trade_pagination_id('XRP/USDT', trade_id) == expected
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
async def test__async_get_trade_history_time(default_conf, mocker, caplog, exchange_name,
|
||||
@@ -2947,6 +3013,9 @@ async def test__async_get_trade_history_time(default_conf, mocker, caplog, excha
|
||||
|
||||
caplog.set_level(logging.DEBUG)
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
if exchange._trades_pagination != 'time':
|
||||
exchange.close()
|
||||
pytest.skip("Exchange does not support pagination by timestamp")
|
||||
# Monkey-patch async function
|
||||
exchange._api_async.fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
|
||||
pair = 'ETH/BTC'
|
||||
@@ -2979,9 +3048,9 @@ async def test__async_get_trade_history_time_empty(default_conf, mocker, caplog,
|
||||
|
||||
async def mock_get_trade_hist(pair, *args, **kwargs):
|
||||
if kwargs['since'] == trades_history[0][0]:
|
||||
return trades_history[:-1]
|
||||
return trades_history[:-1], trades_history[:-1][-1][0]
|
||||
else:
|
||||
return []
|
||||
return [], None
|
||||
|
||||
caplog.set_level(logging.DEBUG)
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
@@ -3193,7 +3262,7 @@ def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
|
||||
mocker.patch(f'{mock_prefix}.fetch_stoploss_order', side_effect=exc)
|
||||
co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
|
||||
assert co['amount'] == 555
|
||||
assert co == {'fee': {}, 'status': 'canceled', 'amount': 555, 'info': {}}
|
||||
assert co == {'id': '_', 'fee': {}, 'status': 'canceled', 'amount': 555, 'info': {}}
|
||||
|
||||
with pytest.raises(InvalidOrderException):
|
||||
exc = InvalidOrderException("Did not find order")
|
||||
@@ -3410,7 +3479,7 @@ def test_get_fee(default_conf, mocker, exchange_name):
|
||||
|
||||
|
||||
def test_stoploss_order_unsupported_exchange(default_conf, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='bittrex')
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='bitpanda')
|
||||
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
|
||||
exchange.create_stoploss(
|
||||
pair='ETH/BTC',
|
||||
@@ -3606,10 +3675,10 @@ def test_ohlcv_candle_limit(default_conf, mocker, exchange_name):
|
||||
timeframes = ('1m', '5m', '1h')
|
||||
expected = exchange._ft_has['ohlcv_candle_limit']
|
||||
for timeframe in timeframes:
|
||||
if 'ohlcv_candle_limit_per_timeframe' in exchange._ft_has:
|
||||
expected = exchange._ft_has['ohlcv_candle_limit_per_timeframe'][timeframe]
|
||||
# This should only run for bittrex
|
||||
assert exchange_name == 'bittrex'
|
||||
# if 'ohlcv_candle_limit_per_timeframe' in exchange._ft_has:
|
||||
# expected = exchange._ft_has['ohlcv_candle_limit_per_timeframe'][timeframe]
|
||||
# This should only run for bittrex
|
||||
# assert exchange_name == 'bittrex'
|
||||
assert exchange.ohlcv_candle_limit(timeframe, CandleType.SPOT) == expected
|
||||
|
||||
|
||||
@@ -4522,10 +4591,10 @@ def test_amount_to_contract_precision(
|
||||
|
||||
|
||||
@pytest.mark.parametrize('exchange_name,open_rate,is_short,trading_mode,margin_mode', [
|
||||
# Bittrex
|
||||
('bittrex', 2.0, False, 'spot', None),
|
||||
('bittrex', 2.0, False, 'spot', 'cross'),
|
||||
('bittrex', 2.0, True, 'spot', 'isolated'),
|
||||
# Bybit
|
||||
('bybit', 2.0, False, 'spot', None),
|
||||
('bybit', 2.0, False, 'spot', 'cross'),
|
||||
('bybit', 2.0, True, 'spot', 'isolated'),
|
||||
# Binance
|
||||
('binance', 2.0, False, 'spot', None),
|
||||
('binance', 2.0, False, 'spot', 'cross'),
|
||||
@@ -4947,7 +5016,7 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers):
|
||||
exchange.get_max_leverage("BTC/USDT:USDT", 1000000000.01)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", ['bittrex', 'binance', 'kraken', 'gate', 'okx', 'bybit'])
|
||||
@pytest.mark.parametrize("exchange_name", ['binance', 'kraken', 'gate', 'okx', 'bybit'])
|
||||
def test__get_params(mocker, default_conf, exchange_name):
|
||||
api_mock = MagicMock()
|
||||
mocker.patch(f'{EXMS}.exchange_has', return_value=True)
|
||||
@@ -5283,3 +5352,4 @@ def test_price_to_precision_with_default_conf(default_conf, mocker):
|
||||
patched_ex = get_patched_exchange(mocker, conf)
|
||||
prec_price = patched_ex.price_to_precision("XRP/USDT", 1.0000000101)
|
||||
assert prec_price == 1.00000001
|
||||
assert prec_price == 1.00000001
|
||||
|
||||
@@ -10,7 +10,7 @@ from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.exchange import (amount_to_contract_precision, amount_to_precision,
|
||||
date_minus_candles, price_to_precision, timeframe_to_minutes,
|
||||
timeframe_to_msecs, timeframe_to_next_date, timeframe_to_prev_date,
|
||||
timeframe_to_seconds)
|
||||
timeframe_to_resample_freq, timeframe_to_seconds)
|
||||
from freqtrade.exchange.check_exchange import check_exchange
|
||||
from tests.conftest import log_has_re
|
||||
|
||||
@@ -124,6 +124,21 @@ def test_timeframe_to_msecs():
|
||||
assert timeframe_to_msecs("1d") == 86400000
|
||||
|
||||
|
||||
@pytest.mark.parametrize("timeframe,expected", [
|
||||
("1s", '1s'),
|
||||
("15s", '15s'),
|
||||
("5m", '300s'),
|
||||
("10m", '600s'),
|
||||
("1h", '3600s'),
|
||||
("1d", '86400s'),
|
||||
("1w", '1W-MON'),
|
||||
("1M", '1MS'),
|
||||
("1y", '1YS'),
|
||||
])
|
||||
def test_timeframe_to_resample_freq(timeframe, expected):
|
||||
assert timeframe_to_resample_freq(timeframe) == expected
|
||||
|
||||
|
||||
def test_timeframe_to_prev_date():
|
||||
# 2019-08-12 13:22:08
|
||||
date = datetime.fromtimestamp(1565616128, tz=timezone.utc)
|
||||
|
||||
@@ -14,7 +14,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
(0.99, 220 * 0.99, "sell"),
|
||||
(0.98, 220 * 0.98, "sell"),
|
||||
])
|
||||
def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side):
|
||||
def test_create_stoploss_order_htx(default_conf, mocker, limitratio, expected, side):
|
||||
api_mock = MagicMock()
|
||||
order_id = f'test_prod_buy_{randint(0, 10 ** 6)}'
|
||||
order_type = 'stop-limit'
|
||||
@@ -29,7 +29,7 @@ def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected,
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'htx')
|
||||
|
||||
with pytest.raises(InvalidOrderException):
|
||||
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
||||
@@ -58,7 +58,7 @@ def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected,
|
||||
# test exception handling
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'htx')
|
||||
exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
||||
order_types={}, side=side, leverage=1.0)
|
||||
|
||||
@@ -69,20 +69,20 @@ def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected,
|
||||
exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
||||
order_types={}, side=side, leverage=1.0)
|
||||
|
||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "huobi",
|
||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "htx",
|
||||
"create_stoploss", "create_order", retries=1,
|
||||
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
|
||||
side=side, leverage=1.0)
|
||||
|
||||
|
||||
def test_create_stoploss_order_dry_run_huobi(default_conf, mocker):
|
||||
def test_create_stoploss_order_dry_run_htx(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
order_type = 'stop-limit'
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'htx')
|
||||
|
||||
with pytest.raises(InvalidOrderException):
|
||||
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
||||
@@ -103,8 +103,8 @@ def test_create_stoploss_order_dry_run_huobi(default_conf, mocker):
|
||||
assert order['amount'] == 1
|
||||
|
||||
|
||||
def test_stoploss_adjust_huobi(mocker, default_conf):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='huobi')
|
||||
def test_stoploss_adjust_htx(mocker, default_conf):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='htx')
|
||||
order = {
|
||||
'type': 'stop',
|
||||
'price': 1500,
|
||||
@@ -13,11 +13,14 @@ STOPLOSS_ORDERTYPE = 'stop-loss'
|
||||
STOPLOSS_LIMIT_ORDERTYPE = 'stop-loss-limit'
|
||||
|
||||
|
||||
def test_buy_kraken_trading_agreement(default_conf, mocker):
|
||||
@pytest.mark.parametrize("order_type,time_in_force,expected_params", [
|
||||
('limit', 'ioc', {'timeInForce': 'IOC', 'trading_agreement': 'agree'}),
|
||||
('limit', 'PO', {'postOnly': True, 'trading_agreement': 'agree'}),
|
||||
('market', None, {'trading_agreement': 'agree'})
|
||||
])
|
||||
def test_kraken_trading_agreement(default_conf, mocker, order_type, time_in_force, expected_params):
|
||||
api_mock = MagicMock()
|
||||
order_id = f'test_prod_buy_{randint(0, 10 ** 6)}'
|
||||
order_type = 'limit'
|
||||
time_in_force = 'ioc'
|
||||
order_id = f'test_prod_{order_type}_{randint(0, 10 ** 6)}'
|
||||
api_mock.options = {}
|
||||
api_mock.create_order = MagicMock(return_value={
|
||||
'id': order_id,
|
||||
@@ -49,41 +52,9 @@ def test_buy_kraken_trading_agreement(default_conf, mocker):
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'buy'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'IOC',
|
||||
'trading_agreement': 'agree'}
|
||||
assert api_mock.create_order.call_args[0][4] == (200 if order_type == 'limit' else None)
|
||||
|
||||
|
||||
def test_sell_kraken_trading_agreement(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
order_id = f'test_prod_sell_{randint(0, 10 ** 6)}'
|
||||
order_type = 'market'
|
||||
api_mock.options = {}
|
||||
api_mock.create_order = MagicMock(return_value={
|
||||
'id': order_id,
|
||||
'symbol': 'ETH/BTC',
|
||||
'info': {
|
||||
'foo': 'bar'
|
||||
}
|
||||
})
|
||||
default_conf['dry_run'] = False
|
||||
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
|
||||
|
||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type,
|
||||
side="sell", amount=1, rate=200, leverage=1.0)
|
||||
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
assert order['id'] == order_id
|
||||
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'sell'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
assert api_mock.create_order.call_args[0][5] == {'trading_agreement': 'agree'}
|
||||
assert api_mock.create_order.call_args[0][5] == expected_params
|
||||
|
||||
|
||||
def test_get_balances_prod(default_conf, mocker):
|
||||
@@ -212,19 +183,17 @@ def test_create_stoploss_order_kraken(default_conf, mocker, ordertype, side, adj
|
||||
assert 'info' in order
|
||||
assert order['id'] == order_id
|
||||
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
|
||||
if ordertype == 'limit':
|
||||
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_LIMIT_ORDERTYPE
|
||||
assert api_mock.create_order.call_args_list[0][1]['params'] == {
|
||||
'trading_agreement': 'agree',
|
||||
'price2': adjustedprice
|
||||
}
|
||||
else:
|
||||
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
|
||||
assert api_mock.create_order.call_args_list[0][1]['params'] == {
|
||||
'trading_agreement': 'agree'}
|
||||
assert api_mock.create_order.call_args_list[0][1]['type'] == ordertype
|
||||
assert api_mock.create_order.call_args_list[0][1]['params'] == {
|
||||
'trading_agreement': 'agree',
|
||||
'stopLossPrice': 220
|
||||
}
|
||||
assert api_mock.create_order.call_args_list[0][1]['side'] == side
|
||||
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
|
||||
assert api_mock.create_order.call_args_list[0][1]['price'] == 220
|
||||
if ordertype == 'limit':
|
||||
assert api_mock.create_order.call_args_list[0][1]['price'] == adjustedprice
|
||||
else:
|
||||
assert api_mock.create_order.call_args_list[0][1]['price'] is None
|
||||
|
||||
# test exception handling
|
||||
with pytest.raises(DependencyException):
|
||||
@@ -282,7 +251,7 @@ def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side):
|
||||
assert 'info' in order
|
||||
assert 'type' in order
|
||||
|
||||
assert order['type'] == STOPLOSS_ORDERTYPE
|
||||
assert order['type'] == 'market'
|
||||
assert order['price'] == 220
|
||||
assert order['amount'] == 1
|
||||
|
||||
@@ -294,11 +263,22 @@ def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side):
|
||||
def test_stoploss_adjust_kraken(mocker, default_conf, sl1, sl2, sl3, side):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='kraken')
|
||||
order = {
|
||||
'type': STOPLOSS_ORDERTYPE,
|
||||
'price': 1500,
|
||||
'type': 'market',
|
||||
'stopLossPrice': 1500,
|
||||
}
|
||||
assert exchange.stoploss_adjust(sl1, order, side=side)
|
||||
assert not exchange.stoploss_adjust(sl2, order, side=side)
|
||||
# Test with invalid order case ...
|
||||
order['type'] = 'stop_loss_limit'
|
||||
assert not exchange.stoploss_adjust(sl3, order, side=side)
|
||||
# diff. order type ...
|
||||
order['type'] = 'limit'
|
||||
assert exchange.stoploss_adjust(sl3, order, side=side)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('trade_id, expected', [
|
||||
('1234', False),
|
||||
('170544369512007228', False),
|
||||
('1705443695120072285', True),
|
||||
('170544369512007228555', True),
|
||||
])
|
||||
def test__valid_trade_pagination_id_kraken(mocker, default_conf_usdt, trade_id, expected):
|
||||
exchange = get_patched_exchange(mocker, default_conf_usdt, id='kraken')
|
||||
assert exchange._valid_trade_pagination_id('XRP/USDT', trade_id) == expected
|
||||
|
||||
@@ -247,7 +247,7 @@ EXCHANGES = {
|
||||
'timeframe': '1h',
|
||||
'orderbook_max_entries': 50,
|
||||
},
|
||||
'huobi': {
|
||||
'htx': {
|
||||
'pair': 'ETH/BTC',
|
||||
'stake_currency': 'BTC',
|
||||
'hasQuoteVolume': True,
|
||||
|
||||
@@ -218,9 +218,6 @@ class TestCCXTExchange:
|
||||
|
||||
def test_ccxt__async_get_candle_history(self, exchange: EXCHANGE_FIXTURE_TYPE):
|
||||
exc, exchangename = exchange
|
||||
if exchangename in ('bittrex'):
|
||||
# For some weired reason, this test returns random lengths for bittrex.
|
||||
pytest.skip("Exchange doesn't provide stable ohlcv history")
|
||||
|
||||
if not exc._ft_has['ohlcv_has_history']:
|
||||
pytest.skip("Exchange does not support candle history")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import platform
|
||||
import sys
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
@@ -15,6 +16,10 @@ from freqtrade.resolvers.freqaimodel_resolver import FreqaiModelResolver
|
||||
from tests.conftest import get_patched_exchange
|
||||
|
||||
|
||||
def is_py12() -> bool:
|
||||
return sys.version_info >= (3, 12)
|
||||
|
||||
|
||||
def is_mac() -> bool:
|
||||
machine = platform.system()
|
||||
return "Darwin" in machine
|
||||
@@ -31,7 +36,7 @@ def patch_torch_initlogs(mocker) -> None:
|
||||
module_name = 'torch'
|
||||
mocked_module = types.ModuleType(module_name)
|
||||
sys.modules[module_name] = mocked_module
|
||||
else:
|
||||
elif not is_py12():
|
||||
mocker.patch("torch._logging._init_logs")
|
||||
|
||||
|
||||
@@ -54,7 +59,7 @@ def freqai_conf(default_conf, tmp_path):
|
||||
"backtest_period_days": 10,
|
||||
"live_retrain_hours": 0,
|
||||
"expiration_hours": 1,
|
||||
"identifier": "uniqe-id100",
|
||||
"identifier": "unique-id100",
|
||||
"live_trained_timestamp": 0,
|
||||
"data_kitchen_thread_count": 2,
|
||||
"activate_tensorboard": False,
|
||||
|
||||
@@ -6,11 +6,17 @@ from unittest.mock import PropertyMock
|
||||
import pytest
|
||||
|
||||
from freqtrade.commands.optimize_commands import setup_optimize_configuration
|
||||
from freqtrade.configuration.timerange import TimeRange
|
||||
from freqtrade.data import history
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.enums import RunMode
|
||||
from freqtrade.enums.candletype import CandleType
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has_re, patch_exchange,
|
||||
patched_configuration_load_config_file)
|
||||
from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, get_patched_exchange, log_has_re,
|
||||
patch_exchange, patched_configuration_load_config_file)
|
||||
from tests.freqai.conftest import get_patched_freqai_strategy
|
||||
|
||||
|
||||
def test_freqai_backtest_start_backtest_list(freqai_conf, mocker, testdatadir, caplog):
|
||||
@@ -40,7 +46,16 @@ def test_freqai_backtest_start_backtest_list(freqai_conf, mocker, testdatadir, c
|
||||
Backtesting.cleanup()
|
||||
|
||||
|
||||
def test_freqai_backtest_load_data(freqai_conf, mocker, caplog):
|
||||
@pytest.mark.parametrize(
|
||||
"timeframe, expected_startup_candle_count",
|
||||
[
|
||||
("5m", 876),
|
||||
("15m", 492),
|
||||
("1d", 302),
|
||||
],
|
||||
)
|
||||
def test_freqai_backtest_load_data(freqai_conf, mocker, caplog,
|
||||
timeframe, expected_startup_candle_count):
|
||||
patch_exchange(mocker)
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
@@ -48,10 +63,14 @@ def test_freqai_backtest_load_data(freqai_conf, mocker, caplog):
|
||||
PropertyMock(return_value=['HULUMULU/USDT', 'XRP/USDT']))
|
||||
mocker.patch('freqtrade.optimize.backtesting.history.load_data')
|
||||
mocker.patch('freqtrade.optimize.backtesting.history.get_timerange', return_value=(now, now))
|
||||
freqai_conf['timeframe'] = timeframe
|
||||
freqai_conf.get('freqai', {}).get('feature_parameters', {}).update({'include_timeframes': []})
|
||||
backtesting = Backtesting(deepcopy(freqai_conf))
|
||||
backtesting.load_bt_data()
|
||||
|
||||
assert log_has_re('Increasing startup_candle_count for freqai to.*', caplog)
|
||||
assert log_has_re(f'Increasing startup_candle_count for freqai on {timeframe} '
|
||||
f'to {expected_startup_candle_count}', caplog)
|
||||
assert history.load_data.call_args[1]['startup_candles'] == expected_startup_candle_count
|
||||
|
||||
Backtesting.cleanup()
|
||||
|
||||
@@ -85,3 +104,35 @@ def test_freqai_backtest_live_models_model_not_found(freqai_conf, mocker, testda
|
||||
Backtesting(bt_config)
|
||||
|
||||
Backtesting.cleanup()
|
||||
|
||||
|
||||
def test_freqai_backtest_consistent_timerange(mocker, freqai_conf):
|
||||
freqai_conf['runmode'] = 'backtest'
|
||||
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
||||
PropertyMock(return_value=['XRP/USDT:USDT']))
|
||||
|
||||
gbs = mocker.patch('freqtrade.optimize.backtesting.generate_backtest_stats')
|
||||
|
||||
freqai_conf['candle_type_def'] = CandleType.FUTURES
|
||||
freqai_conf.get('exchange', {}).update({'pair_whitelist': ['XRP/USDT:USDT']})
|
||||
freqai_conf.get('freqai', {}).get('feature_parameters', {}).update(
|
||||
{'include_timeframes': ['5m', '1h'], 'include_corr_pairlist': []})
|
||||
freqai_conf['timerange'] = '20211120-20211121'
|
||||
|
||||
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
|
||||
exchange = get_patched_exchange(mocker, freqai_conf)
|
||||
|
||||
strategy.dp = DataProvider(freqai_conf, exchange)
|
||||
strategy.freqai_info = freqai_conf.get("freqai", {})
|
||||
freqai = strategy.freqai
|
||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||
|
||||
timerange = TimeRange.parse_timerange("20211115-20211122")
|
||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||
|
||||
backtesting = Backtesting(deepcopy(freqai_conf))
|
||||
backtesting.start()
|
||||
|
||||
gbs.call_args[1]['min_date'] == datetime(2021, 11, 20, 0, 0, tzinfo=timezone.utc)
|
||||
gbs.call_args[1]['max_date'] == datetime(2021, 11, 21, 0, 0, tzinfo=timezone.utc)
|
||||
Backtesting.cleanup()
|
||||
|
||||
@@ -15,6 +15,7 @@ from tests.freqai.conftest import get_patched_freqai_strategy
|
||||
|
||||
|
||||
def test_update_historic_data(mocker, freqai_conf):
|
||||
freqai_conf['runmode'] = 'backtest'
|
||||
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
|
||||
exchange = get_patched_exchange(mocker, freqai_conf)
|
||||
strategy.dp = DataProvider(freqai_conf, exchange)
|
||||
|
||||
@@ -3,6 +3,7 @@ from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from freqtrade.configuration import TimeRange
|
||||
@@ -75,7 +76,7 @@ def test_filter_features(mocker, freqai_conf):
|
||||
freqai, unfiltered_dataframe = make_unfiltered_dataframe(mocker, freqai_conf)
|
||||
freqai.dk.find_features(unfiltered_dataframe)
|
||||
|
||||
filtered_df, labels = freqai.dk.filter_features(
|
||||
filtered_df, _labels = freqai.dk.filter_features(
|
||||
unfiltered_dataframe,
|
||||
freqai.dk.training_features_list,
|
||||
freqai.dk.label_list,
|
||||
@@ -135,3 +136,64 @@ def test_get_full_model_path(mocker, freqai_conf, model):
|
||||
|
||||
model_path = freqai.dk.get_full_models_path(freqai_conf)
|
||||
assert model_path.is_dir() is True
|
||||
|
||||
|
||||
def test_get_pair_data_for_features_with_prealoaded_data(mocker, freqai_conf):
|
||||
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
|
||||
exchange = get_patched_exchange(mocker, freqai_conf)
|
||||
strategy.dp = DataProvider(freqai_conf, exchange)
|
||||
strategy.freqai_info = freqai_conf.get("freqai", {})
|
||||
freqai = strategy.freqai
|
||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||
|
||||
_, base_df = freqai.dd.get_base_and_corr_dataframes(timerange, "LTC/BTC", freqai.dk)
|
||||
df = freqai.dk.get_pair_data_for_features("LTC/BTC", "5m", strategy, base_dataframes=base_df)
|
||||
|
||||
assert df is base_df["5m"]
|
||||
assert not df.empty
|
||||
|
||||
|
||||
def test_get_pair_data_for_features_without_preloaded_data(mocker, freqai_conf):
|
||||
freqai_conf.update({"timerange": "20180115-20180130"})
|
||||
freqai_conf['runmode'] = 'backtest'
|
||||
|
||||
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
|
||||
exchange = get_patched_exchange(mocker, freqai_conf)
|
||||
strategy.dp = DataProvider(freqai_conf, exchange)
|
||||
strategy.freqai_info = freqai_conf.get("freqai", {})
|
||||
freqai = strategy.freqai
|
||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||
|
||||
base_df = {'5m': pd.DataFrame()}
|
||||
df = freqai.dk.get_pair_data_for_features("LTC/BTC", "5m", strategy, base_dataframes=base_df)
|
||||
|
||||
assert df is not base_df["5m"]
|
||||
assert not df.empty
|
||||
assert df.iloc[0]['date'].strftime("%Y-%m-%d %H:%M:%S") == "2018-01-11 23:00:00"
|
||||
assert df.iloc[-1]['date'].strftime("%Y-%m-%d %H:%M:%S") == "2018-01-30 00:00:00"
|
||||
|
||||
|
||||
def test_populate_features(mocker, freqai_conf):
|
||||
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
|
||||
exchange = get_patched_exchange(mocker, freqai_conf)
|
||||
strategy.dp = DataProvider(freqai_conf, exchange)
|
||||
strategy.freqai_info = freqai_conf.get("freqai", {})
|
||||
freqai = strategy.freqai
|
||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||
timerange = TimeRange.parse_timerange("20180115-20180130")
|
||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||
|
||||
corr_df, base_df = freqai.dd.get_base_and_corr_dataframes(timerange, "LTC/BTC", freqai.dk)
|
||||
mocker.patch.object(strategy, 'feature_engineering_expand_all', return_value=base_df["5m"])
|
||||
df = freqai.dk.populate_features(base_df["5m"], "LTC/BTC", strategy,
|
||||
base_dataframes=base_df, corr_dataframes=corr_df)
|
||||
|
||||
strategy.feature_engineering_expand_all.assert_called_once()
|
||||
pd.testing.assert_frame_equal(base_df["5m"],
|
||||
strategy.feature_engineering_expand_all.call_args[0][0])
|
||||
|
||||
assert df.iloc[0]['date'].strftime("%Y-%m-%d %H:%M:%S") == "2018-01-15 00:00:00"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import logging
|
||||
import platform
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
@@ -16,24 +15,24 @@ from freqtrade.optimize.backtesting import Backtesting
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.plugins.pairlistmanager import PairListManager
|
||||
from tests.conftest import EXMS, create_mock_trades, get_patched_exchange, log_has_re
|
||||
from tests.freqai.conftest import (get_patched_freqai_strategy, is_mac, make_rl_config,
|
||||
from tests.freqai.conftest import (get_patched_freqai_strategy, is_mac, is_py12, make_rl_config,
|
||||
mock_pytorch_mlp_model_training_parameters)
|
||||
|
||||
|
||||
def is_py11() -> bool:
|
||||
return sys.version_info >= (3, 11)
|
||||
|
||||
|
||||
def is_arm() -> bool:
|
||||
machine = platform.machine()
|
||||
return "arm" in machine or "aarch64" in machine
|
||||
|
||||
|
||||
def can_run_model(model: str) -> None:
|
||||
is_pytorch_model = 'Reinforcement' in model or 'PyTorch' in model
|
||||
|
||||
if is_py12() and ("Catboost" in model or is_pytorch_model):
|
||||
pytest.skip("Model not supported on python 3.12 yet.")
|
||||
|
||||
if is_arm() and "Catboost" in model:
|
||||
pytest.skip("CatBoost is not supported on ARM.")
|
||||
|
||||
is_pytorch_model = 'Reinforcement' in model or 'PyTorch' in model
|
||||
if is_pytorch_model and is_mac() and not is_arm():
|
||||
pytest.skip("Reinforcement learning / PyTorch module not available on intel based Mac OS.")
|
||||
|
||||
@@ -176,6 +175,7 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s
|
||||
'CatboostClassifier',
|
||||
'XGBoostClassifier',
|
||||
'XGBoostRFClassifier',
|
||||
'SKLearnRandomForestClassifier',
|
||||
'PyTorchMLPClassifier',
|
||||
])
|
||||
def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
|
||||
@@ -298,8 +298,11 @@ def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog)
|
||||
|
||||
def test_start_backtesting_subdaily_backtest_period(mocker, freqai_conf):
|
||||
freqai_conf.update({"timerange": "20180120-20180124"})
|
||||
freqai_conf.get("freqai", {}).update({"backtest_period_days": 0.5})
|
||||
freqai_conf.get("freqai", {}).update({"save_backtest_models": True})
|
||||
freqai_conf['runmode'] = 'backtest'
|
||||
freqai_conf.get("freqai", {}).update({
|
||||
"backtest_period_days": 0.5,
|
||||
"save_backtest_models": True,
|
||||
})
|
||||
freqai_conf.get("freqai", {}).get("feature_parameters", {}).update(
|
||||
{"indicator_periods_candles": [2]})
|
||||
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
|
||||
@@ -326,6 +329,7 @@ def test_start_backtesting_subdaily_backtest_period(mocker, freqai_conf):
|
||||
|
||||
def test_start_backtesting_from_existing_folder(mocker, freqai_conf, caplog):
|
||||
freqai_conf.update({"timerange": "20180120-20180130"})
|
||||
freqai_conf['runmode'] = 'backtest'
|
||||
freqai_conf.get("freqai", {}).update({"save_backtest_models": True})
|
||||
freqai_conf.get("freqai", {}).get("feature_parameters", {}).update(
|
||||
{"indicator_periods_candles": [2]})
|
||||
@@ -389,6 +393,7 @@ def test_start_backtesting_from_existing_folder(mocker, freqai_conf, caplog):
|
||||
|
||||
|
||||
def test_backtesting_fit_live_predictions(mocker, freqai_conf, caplog):
|
||||
freqai_conf['runmode'] = 'backtest'
|
||||
freqai_conf.get("freqai", {}).update({"fit_live_predictions_candles": 10})
|
||||
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
|
||||
exchange = get_patched_exchange(mocker, freqai_conf)
|
||||
@@ -522,8 +527,8 @@ def test_get_state_info(mocker, freqai_conf, dp_exists, caplog, tickers):
|
||||
|
||||
if is_mac():
|
||||
pytest.skip("Reinforcement learning module not available on intel based Mac OS")
|
||||
if is_py11():
|
||||
pytest.skip("Reinforcement learning currently not available on python 3.11.")
|
||||
if is_py12():
|
||||
pytest.skip("Reinforcement learning currently not available on python 3.12.")
|
||||
|
||||
freqai_conf.update({"freqaimodel": "ReinforcementLearner"})
|
||||
freqai_conf.update({"timerange": "20180110-20180130"})
|
||||
|
||||
@@ -734,7 +734,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||
'min_rate': [0.10370188, 0.10300000000000001],
|
||||
'max_rate': [0.10501, 0.1038888],
|
||||
'is_open': [False, False],
|
||||
'enter_tag': [None, None],
|
||||
'enter_tag': ['', ''],
|
||||
"leverage": [1.0, 1.0],
|
||||
"is_short": [False, False],
|
||||
'open_timestamp': [1517251200000, 1517283000000],
|
||||
@@ -742,14 +742,18 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||
'orders': [
|
||||
[
|
||||
{'amount': 0.00957442, 'safe_price': 0.104445, 'ft_order_side': 'buy',
|
||||
'order_filled_timestamp': 1517251200000, 'ft_is_entry': True},
|
||||
'order_filled_timestamp': 1517251200000, 'ft_is_entry': True,
|
||||
'ft_order_tag': ''},
|
||||
{'amount': 0.00957442, 'safe_price': 0.10496853383458644, 'ft_order_side': 'sell',
|
||||
'order_filled_timestamp': 1517265300000, 'ft_is_entry': False}
|
||||
'order_filled_timestamp': 1517265300000, 'ft_is_entry': False,
|
||||
'ft_order_tag': 'roi'}
|
||||
], [
|
||||
{'amount': 0.0097064, 'safe_price': 0.10302485, 'ft_order_side': 'buy',
|
||||
'order_filled_timestamp': 1517283000000, 'ft_is_entry': True},
|
||||
'order_filled_timestamp': 1517283000000, 'ft_is_entry': True,
|
||||
'ft_order_tag': ''},
|
||||
{'amount': 0.0097064, 'safe_price': 0.10354126528822055, 'ft_order_side': 'sell',
|
||||
'order_filled_timestamp': 1517285400000, 'ft_is_entry': False}
|
||||
'order_filled_timestamp': 1517285400000, 'ft_is_entry': False,
|
||||
'ft_order_tag': 'roi'}
|
||||
]
|
||||
]
|
||||
})
|
||||
@@ -1132,6 +1136,7 @@ def test_processed(default_conf, mocker, testdatadir) -> None:
|
||||
def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadir) -> None:
|
||||
default_conf['use_exit_signal'] = False
|
||||
default_conf['max_open_trades'] = 10
|
||||
default_conf['runmode'] = 'backtest'
|
||||
mocker.patch(f'{EXMS}.get_fee', fee)
|
||||
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
||||
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=100000)
|
||||
@@ -1298,6 +1303,7 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
|
||||
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
|
||||
mocker.patch(f'{EXMS}.get_fee', fee)
|
||||
default_conf['max_open_trades'] = 10
|
||||
default_conf['runmode'] = 'backtest'
|
||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf,
|
||||
pair='UNITTEST/BTC', datadir=testdatadir)
|
||||
default_conf['timeframe'] = '1m'
|
||||
@@ -1342,6 +1348,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
|
||||
dataframe['exit_short'] = 0
|
||||
return dataframe
|
||||
|
||||
default_conf['runmode'] = 'backtest'
|
||||
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
||||
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
|
||||
mocker.patch(f'{EXMS}.get_fee', fee)
|
||||
|
||||
@@ -72,7 +72,7 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
|
||||
'min_rate': [0.10370188, 0.10300000000000001],
|
||||
'max_rate': [0.10481985, 0.1038888],
|
||||
'is_open': [False, False],
|
||||
'enter_tag': [None, None],
|
||||
'enter_tag': ['', ''],
|
||||
'leverage': [1.0, 1.0],
|
||||
'is_short': [False, False],
|
||||
'open_timestamp': [1517251200000, 1517283000000],
|
||||
@@ -148,7 +148,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
|
||||
assert pytest.approx(trade.amount) == 47.61904762 * leverage
|
||||
assert len(trade.orders) == 1
|
||||
# Increase position by 100
|
||||
backtesting.strategy.adjust_trade_position = MagicMock(return_value=100)
|
||||
backtesting.strategy.adjust_trade_position = MagicMock(return_value=(100, 'PartIncrease'))
|
||||
|
||||
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time)
|
||||
|
||||
@@ -156,6 +156,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
|
||||
assert pytest.approx(trade.stake_amount) == 200.0
|
||||
assert pytest.approx(trade.amount) == 95.23809524 * leverage
|
||||
assert len(trade.orders) == 2
|
||||
assert trade.orders[-1].ft_order_tag == 'PartIncrease'
|
||||
assert pytest.approx(trade.liquidation_price) == (0.1038916 if leverage == 1 else 1.2127791)
|
||||
|
||||
# Reduce by more than amount - no change to trade.
|
||||
@@ -171,13 +172,14 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
|
||||
assert pytest.approx(trade.liquidation_price) == (0.1038916 if leverage == 1 else 1.2127791)
|
||||
|
||||
# Reduce position by 50
|
||||
backtesting.strategy.adjust_trade_position = MagicMock(return_value=-100)
|
||||
backtesting.strategy.adjust_trade_position = MagicMock(return_value=(-100, 'partDecrease'))
|
||||
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time)
|
||||
|
||||
assert trade
|
||||
assert pytest.approx(trade.stake_amount) == 100.0
|
||||
assert pytest.approx(trade.amount) == 47.61904762 * leverage
|
||||
assert len(trade.orders) == 3
|
||||
assert trade.orders[-1].ft_order_tag == 'partDecrease'
|
||||
assert trade.nr_of_successful_entries == 2
|
||||
assert trade.nr_of_successful_exits == 1
|
||||
assert pytest.approx(trade.liquidation_price) == (0.1038916 if leverage == 1 else 1.2127791)
|
||||
|
||||
@@ -254,7 +254,7 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None:
|
||||
'is_best': True
|
||||
}
|
||||
)
|
||||
out, err = capsys.readouterr()
|
||||
out, _err = capsys.readouterr()
|
||||
assert all(x in out
|
||||
for x in ["Best", "2/2", " 1", "0.10%", "0.00100000 BTC (1.00%)", "00:20:00"])
|
||||
|
||||
@@ -333,7 +333,7 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
|
||||
|
||||
parallel.assert_called_once()
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
out, _err = capsys.readouterr()
|
||||
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
|
||||
# Should be called for historical candle data
|
||||
assert dumper.call_count == 1
|
||||
@@ -577,7 +577,7 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
|
||||
|
||||
parallel.assert_called_once()
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
out, _err = capsys.readouterr()
|
||||
result_str = (
|
||||
'{"params":{"mfi-value":null,"sell-mfi-value":null},"minimal_roi"'
|
||||
':{},"stoploss":null,"trailing_stop":null,"max_open_trades":null}'
|
||||
@@ -624,7 +624,7 @@ def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
|
||||
|
||||
parallel.assert_called_once()
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
out, _err = capsys.readouterr()
|
||||
assert '{"params":{"mfi-value":null,"sell-mfi-value":null},"minimal_roi":{},"stoploss":null}' in out # noqa: E501
|
||||
# Should be called for historical candle data
|
||||
assert dumper.call_count == 1
|
||||
@@ -666,7 +666,7 @@ def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
|
||||
|
||||
parallel.assert_called_once()
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
out, _err = capsys.readouterr()
|
||||
assert '{"minimal_roi":{},"stoploss":null}' in out
|
||||
|
||||
assert dumper.call_count == 1
|
||||
@@ -704,7 +704,7 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
|
||||
|
||||
parallel.assert_called_once()
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
out, _err = capsys.readouterr()
|
||||
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
|
||||
assert dumper.call_count == 1
|
||||
assert dumper2.call_count == 1
|
||||
@@ -777,7 +777,7 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
|
||||
|
||||
parallel.assert_called_once()
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
out, _err = capsys.readouterr()
|
||||
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
|
||||
assert dumper.called
|
||||
assert dumper.call_count == 1
|
||||
@@ -819,7 +819,7 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
|
||||
|
||||
parallel.assert_called_once()
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
out, _err = capsys.readouterr()
|
||||
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
|
||||
assert dumper.called
|
||||
assert dumper.call_count == 1
|
||||
@@ -1051,7 +1051,7 @@ def test_max_open_trades_dump(mocker, hyperopt_conf, tmp_path, fee, capsys) -> N
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
out, _err = capsys.readouterr()
|
||||
|
||||
assert 'max_open_trades = -1' in out
|
||||
assert 'max_open_trades = inf' not in out
|
||||
@@ -1070,7 +1070,7 @@ def test_max_open_trades_dump(mocker, hyperopt_conf, tmp_path, fee, capsys) -> N
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
out, _err = capsys.readouterr()
|
||||
|
||||
assert '"max_open_trades":-1' in out
|
||||
|
||||
|
||||
@@ -143,8 +143,8 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf
|
||||
|
||||
instance = LookaheadAnalysis(lookahead_conf, strategy_obj)
|
||||
instance.current_analysis = analysis
|
||||
table, headers, data = (LookaheadAnalysisSubFunctions.
|
||||
text_table_lookahead_analysis_instances(lookahead_conf, [instance]))
|
||||
_table, _headers, data = (LookaheadAnalysisSubFunctions.
|
||||
text_table_lookahead_analysis_instances(lookahead_conf, [instance]))
|
||||
|
||||
# check row contents for a try that has too few signals
|
||||
assert data[0][0] == 'strategy_test_v3_with_lookahead_bias.py'
|
||||
@@ -158,14 +158,14 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf
|
||||
analysis.false_exit_signals = 10
|
||||
instance = LookaheadAnalysis(lookahead_conf, strategy_obj)
|
||||
instance.current_analysis = analysis
|
||||
table, headers, data = (LookaheadAnalysisSubFunctions.
|
||||
text_table_lookahead_analysis_instances(lookahead_conf, [instance]))
|
||||
_table, _headers, data = (LookaheadAnalysisSubFunctions.
|
||||
text_table_lookahead_analysis_instances(lookahead_conf, [instance]))
|
||||
assert data[0][2].__contains__("error")
|
||||
|
||||
# edit it into not showing an error
|
||||
instance.failed_bias_check = False
|
||||
table, headers, data = (LookaheadAnalysisSubFunctions.
|
||||
text_table_lookahead_analysis_instances(lookahead_conf, [instance]))
|
||||
_table, _headers, data = (LookaheadAnalysisSubFunctions.
|
||||
text_table_lookahead_analysis_instances(lookahead_conf, [instance]))
|
||||
assert data[0][0] == 'strategy_test_v3_with_lookahead_bias.py'
|
||||
assert data[0][1] == 'strategy_test_v3_with_lookahead_bias'
|
||||
assert data[0][2] # True
|
||||
@@ -176,8 +176,8 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf
|
||||
|
||||
analysis.false_indicators.append('falseIndicator1')
|
||||
analysis.false_indicators.append('falseIndicator2')
|
||||
table, headers, data = (LookaheadAnalysisSubFunctions.
|
||||
text_table_lookahead_analysis_instances(lookahead_conf, [instance]))
|
||||
_table, _headers, data = (LookaheadAnalysisSubFunctions.
|
||||
text_table_lookahead_analysis_instances(lookahead_conf, [instance]))
|
||||
|
||||
assert data[0][6] == 'falseIndicator1, falseIndicator2'
|
||||
|
||||
@@ -185,7 +185,7 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf
|
||||
assert len(data) == 1
|
||||
|
||||
# check amount of multiple rows
|
||||
table, headers, data = (LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
|
||||
_table, _headers, data = (LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
|
||||
lookahead_conf, [instance, instance, instance]))
|
||||
assert len(data) == 3
|
||||
|
||||
|
||||
@@ -513,7 +513,7 @@ def test_show_sorted_pairlist(testdatadir, default_conf, capsys):
|
||||
|
||||
show_sorted_pairlist(default_conf, bt_data)
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
out, _err = capsys.readouterr()
|
||||
assert 'Pairs for Strategy StrategyTestV3: \n[' in out
|
||||
assert 'TOTAL' not in out
|
||||
assert '"ETH/BTC", // ' in out
|
||||
|
||||
@@ -107,8 +107,8 @@ def test_recursive_helper_text_table_recursive_analysis_instances(recursive_conf
|
||||
|
||||
instance = RecursiveAnalysis(recursive_conf, strategy_obj)
|
||||
instance.dict_recursive = dict_diff
|
||||
table, headers, data = (RecursiveAnalysisSubFunctions.
|
||||
text_table_recursive_analysis_instances([instance]))
|
||||
_table, _headers, data = (RecursiveAnalysisSubFunctions.
|
||||
text_table_recursive_analysis_instances([instance]))
|
||||
|
||||
# check row contents for a try that has too few signals
|
||||
assert data[0][0] == 'rsi'
|
||||
@@ -119,8 +119,8 @@ def test_recursive_helper_text_table_recursive_analysis_instances(recursive_conf
|
||||
dict_diff = dict()
|
||||
instance = RecursiveAnalysis(recursive_conf, strategy_obj)
|
||||
instance.dict_recursive = dict_diff
|
||||
table, headers, data = (RecursiveAnalysisSubFunctions.
|
||||
text_table_recursive_analysis_instances([instance]))
|
||||
_table, _headers, data = (RecursiveAnalysisSubFunctions.
|
||||
text_table_recursive_analysis_instances([instance]))
|
||||
assert len(data) == 0
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import pytest
|
||||
|
||||
from freqtrade.persistence import FtNoDBContext, PairLocks, Trade
|
||||
|
||||
|
||||
@pytest.mark.parametrize('timeframe', ['', '5m', '1d'])
|
||||
def test_FtNoDBContext(timeframe):
|
||||
PairLocks.timeframe = ''
|
||||
assert Trade.use_db is True
|
||||
assert PairLocks.use_db is True
|
||||
assert PairLocks.timeframe == ''
|
||||
|
||||
with FtNoDBContext(timeframe):
|
||||
assert Trade.use_db is False
|
||||
assert PairLocks.use_db is False
|
||||
assert PairLocks.timeframe == timeframe
|
||||
|
||||
with FtNoDBContext():
|
||||
assert Trade.use_db is False
|
||||
assert PairLocks.use_db is False
|
||||
assert PairLocks.timeframe == ''
|
||||
|
||||
assert Trade.use_db is True
|
||||
assert PairLocks.use_db is True
|
||||
@@ -18,6 +18,7 @@ from freqtrade.persistence import LocalTrade, Trade
|
||||
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist, expand_pairlist
|
||||
from freqtrade.plugins.pairlistmanager import PairListManager
|
||||
from freqtrade.resolvers import PairListResolver
|
||||
from freqtrade.util.datetime_helpers import dt_now
|
||||
from tests.conftest import (EXMS, create_mock_trades_usdt, get_patched_exchange,
|
||||
get_patched_freqtradebot, log_has, log_has_re, num_log_has)
|
||||
|
||||
@@ -1513,3 +1514,144 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None:
|
||||
pm.refresh_pairlist()
|
||||
assert pm.whitelist == []
|
||||
assert log_has_re(r'Whitelist with 0 pairs: \[]', caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('pairlists,trade_mode,result', [
|
||||
([
|
||||
# Get 2 pairs
|
||||
{"method": "StaticPairList", "allow_inactive": True},
|
||||
{"method": "MarketCapPairList", "number_assets": 2}
|
||||
], 'spot', ['BTC/USDT', 'ETH/USDT']),
|
||||
([
|
||||
# Get 6 pairs
|
||||
{"method": "StaticPairList", "allow_inactive": True},
|
||||
{"method": "MarketCapPairList", "number_assets": 6}
|
||||
], 'spot', ['BTC/USDT', 'ETH/USDT', 'XRP/USDT', 'ADA/USDT']),
|
||||
([
|
||||
# Get 3 pairs within top 6 ranks
|
||||
{"method": "StaticPairList", "allow_inactive": True},
|
||||
{"method": "MarketCapPairList", "max_rank": 6, "number_assets": 3}
|
||||
], 'spot', ['BTC/USDT', 'ETH/USDT', 'XRP/USDT']),
|
||||
|
||||
([
|
||||
# Get 4 pairs within top 8 ranks
|
||||
{"method": "StaticPairList", "allow_inactive": True},
|
||||
{"method": "MarketCapPairList", "max_rank": 8, "number_assets": 4}
|
||||
], 'spot', ['BTC/USDT', 'ETH/USDT', 'XRP/USDT']),
|
||||
([
|
||||
# MarketCapPairList as generator
|
||||
{"method": "MarketCapPairList", "number_assets": 5}
|
||||
], 'spot', ['BTC/USDT', 'ETH/USDT', 'XRP/USDT']),
|
||||
([
|
||||
# MarketCapPairList as generator - low max_rank
|
||||
{"method": "MarketCapPairList", "max_rank": 2, "number_assets": 5}
|
||||
], 'spot', ['BTC/USDT', 'ETH/USDT']),
|
||||
([
|
||||
# MarketCapPairList as generator - futures - low max_rank
|
||||
{"method": "MarketCapPairList", "max_rank": 2, "number_assets": 5}
|
||||
], 'futures', ['ETH/USDT:USDT']),
|
||||
([
|
||||
# MarketCapPairList as generator - futures - low number_assets
|
||||
{"method": "MarketCapPairList", "number_assets": 2}
|
||||
], 'futures', ['ETH/USDT:USDT', 'ADA/USDT:USDT']),
|
||||
])
|
||||
def test_MarketCapPairList_filter(
|
||||
mocker, default_conf_usdt, trade_mode, markets, pairlists, result
|
||||
):
|
||||
test_value = [
|
||||
{"symbol": "btc"},
|
||||
{"symbol": "eth"},
|
||||
{"symbol": "usdt"},
|
||||
{"symbol": "bnb"},
|
||||
{"symbol": "sol"},
|
||||
{"symbol": "xrp"},
|
||||
{"symbol": "usdc"},
|
||||
{"symbol": "steth"},
|
||||
{"symbol": "ada"},
|
||||
{"symbol": "avax"},
|
||||
]
|
||||
|
||||
default_conf_usdt['trading_mode'] = trade_mode
|
||||
if trade_mode == 'spot':
|
||||
default_conf_usdt['exchange']['pair_whitelist'].extend(['BTC/USDT', 'ETC/USDT', 'ADA/USDT'])
|
||||
default_conf_usdt['pairlists'] = pairlists
|
||||
mocker.patch.multiple(EXMS,
|
||||
markets=PropertyMock(return_value=markets),
|
||||
exchange_has=MagicMock(return_value=True),
|
||||
)
|
||||
|
||||
mocker.patch("freqtrade.plugins.pairlist.MarketCapPairList.CoinGeckoAPI.get_coins_markets",
|
||||
return_value=test_value)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf_usdt)
|
||||
|
||||
pm = PairListManager(exchange, default_conf_usdt)
|
||||
pm.refresh_pairlist()
|
||||
|
||||
assert pm.whitelist == result
|
||||
|
||||
|
||||
def test_MarketCapPairList_timing(mocker, default_conf_usdt, markets, time_machine):
|
||||
test_value = [
|
||||
{"symbol": "btc"},
|
||||
{"symbol": "eth"},
|
||||
{"symbol": "usdt"},
|
||||
{"symbol": "bnb"},
|
||||
{"symbol": "sol"},
|
||||
{"symbol": "xrp"},
|
||||
{"symbol": "usdc"},
|
||||
{"symbol": "steth"},
|
||||
{"symbol": "ada"},
|
||||
{"symbol": "avax"},
|
||||
]
|
||||
|
||||
default_conf_usdt['trading_mode'] = 'spot'
|
||||
default_conf_usdt['exchange']['pair_whitelist'].extend(['BTC/USDT', 'ETC/USDT', 'ADA/USDT'])
|
||||
default_conf_usdt['pairlists'] = [{"method": "MarketCapPairList", "number_assets": 2}]
|
||||
|
||||
markets_mock = MagicMock(return_value=markets)
|
||||
mocker.patch.multiple(EXMS,
|
||||
get_markets=markets_mock,
|
||||
exchange_has=MagicMock(return_value=True),
|
||||
)
|
||||
|
||||
mocker.patch("freqtrade.plugins.pairlist.MarketCapPairList.CoinGeckoAPI.get_coins_markets",
|
||||
return_value=test_value)
|
||||
|
||||
start_dt = dt_now()
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf_usdt)
|
||||
time_machine.move_to(start_dt)
|
||||
|
||||
pm = PairListManager(exchange, default_conf_usdt)
|
||||
markets_mock.reset_mock()
|
||||
pm.refresh_pairlist()
|
||||
assert markets_mock.call_count == 3
|
||||
markets_mock.reset_mock()
|
||||
|
||||
time_machine.move_to(start_dt + timedelta(hours=20))
|
||||
pm.refresh_pairlist()
|
||||
# Cached pairlist ...
|
||||
assert markets_mock.call_count == 1
|
||||
|
||||
markets_mock.reset_mock()
|
||||
time_machine.move_to(start_dt + timedelta(days=2))
|
||||
pm.refresh_pairlist()
|
||||
# No longer cached pairlist ...
|
||||
assert markets_mock.call_count == 3
|
||||
|
||||
|
||||
def test_MarketCapPairList_exceptions(mocker, default_conf_usdt, markets, time_machine):
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf_usdt)
|
||||
default_conf_usdt['pairlists'] = [{"method": "MarketCapPairList"}]
|
||||
with pytest.raises(OperationalException, match=r"`number_assets` not specified.*"):
|
||||
# No number_assets
|
||||
PairListManager(exchange, default_conf_usdt)
|
||||
|
||||
default_conf_usdt['pairlists'] = [{
|
||||
"method": "MarketCapPairList", 'number_assets': 20, 'max_rank': 260
|
||||
}]
|
||||
with pytest.raises(OperationalException,
|
||||
match="This filter only support marketcap rank up to 250."):
|
||||
PairListManager(exchange, default_conf_usdt)
|
||||
|
||||
@@ -82,7 +82,7 @@ def test_fetch_pairlist_mock_response_html(mocker, rpl_config):
|
||||
remote_pairlist = RemotePairList(exchange, pairlistmanager, rpl_config,
|
||||
rpl_config['pairlists'][0], 0)
|
||||
|
||||
with pytest.raises(OperationalException, match='RemotePairList is not of type JSON, abort.'):
|
||||
with pytest.raises(OperationalException, match='RemotePairList is not of type JSON.'):
|
||||
remote_pairlist.fetch_pairlist()
|
||||
|
||||
|
||||
@@ -107,9 +107,11 @@ def test_fetch_pairlist_timeout_keep_last_pairlist(mocker, rpl_config, caplog):
|
||||
rpl_config['pairlists'][0], 0)
|
||||
|
||||
remote_pairlist._last_pairlist = ["BTC/USDT", "ETH/USDT", "LTC/USDT"]
|
||||
remote_pairlist._init_done = True
|
||||
pairlist_url = rpl_config['pairlists'][0]['pairlist_url']
|
||||
pairs, _time_elapsed = remote_pairlist.fetch_pairlist()
|
||||
|
||||
pairs, time_elapsed = remote_pairlist.fetch_pairlist()
|
||||
assert log_has(f"Was not able to fetch pairlist from: {remote_pairlist._pairlist_url}", caplog)
|
||||
assert log_has(f'Error: Was not able to fetch pairlist from: ' f'{pairlist_url}', caplog)
|
||||
assert log_has("Keeping last fetched pairlist", caplog)
|
||||
assert pairs == ["BTC/USDT", "ETH/USDT", "LTC/USDT"]
|
||||
|
||||
@@ -281,7 +283,7 @@ def test_remote_pairlist_blacklist(mocker, rpl_config, caplog, markets, tickers)
|
||||
remote_pairlist = RemotePairList(exchange, pairlistmanager, rpl_config,
|
||||
rpl_config["pairlists"][1], 1)
|
||||
|
||||
pairs, time_elapsed = remote_pairlist.fetch_pairlist()
|
||||
pairs, _time_elapsed = remote_pairlist.fetch_pairlist()
|
||||
|
||||
assert pairs == ["XRP/USDT"]
|
||||
|
||||
@@ -334,7 +336,7 @@ def test_remote_pairlist_whitelist(mocker, rpl_config, processing_mode, markets,
|
||||
remote_pairlist = RemotePairList(exchange, pairlistmanager, rpl_config,
|
||||
rpl_config["pairlists"][1], 1)
|
||||
|
||||
pairs, time_elapsed = remote_pairlist.fetch_pairlist()
|
||||
pairs, _time_elapsed = remote_pairlist.fetch_pairlist()
|
||||
|
||||
assert pairs == ["XRP/USDT"]
|
||||
|
||||
|
||||
+15
-5
@@ -9,7 +9,7 @@ from sqlalchemy import select
|
||||
from freqtrade.edge import PairInfo
|
||||
from freqtrade.enums import SignalDirection, State, TradingMode
|
||||
from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.persistence import Order, Trade
|
||||
from freqtrade.persistence.pairlock_middleware import PairLocks
|
||||
from freqtrade.rpc import RPC, RPCException
|
||||
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
||||
@@ -99,7 +99,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05,
|
||||
'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY,
|
||||
'remaining': ANY, 'status': ANY, 'ft_is_entry': True, 'ft_fee_base': None,
|
||||
'funding_fee': ANY,
|
||||
'funding_fee': ANY, 'ft_order_tag': None,
|
||||
}],
|
||||
}
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
@@ -355,8 +355,18 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short):
|
||||
rpc._rpc_delete('200')
|
||||
|
||||
trades = Trade.session.scalars(select(Trade)).all()
|
||||
trades[1].stoploss_order_id = '1234'
|
||||
trades[2].stoploss_order_id = '1234'
|
||||
trades[2].stoploss_order_id = '102'
|
||||
trades[2].orders.append(
|
||||
Order(
|
||||
ft_order_side='stoploss',
|
||||
ft_pair=trades[2].pair,
|
||||
ft_is_open=True,
|
||||
ft_amount=trades[2].amount,
|
||||
ft_price=trades[2].stop_loss,
|
||||
order_id='102',
|
||||
status='open',
|
||||
)
|
||||
)
|
||||
assert len(trades) > 2
|
||||
|
||||
res = rpc._rpc_delete('1')
|
||||
@@ -369,7 +379,7 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short):
|
||||
cancel_mock.reset_mock()
|
||||
stoploss_mock.reset_mock()
|
||||
|
||||
res = rpc._rpc_delete('2')
|
||||
res = rpc._rpc_delete('5')
|
||||
assert isinstance(res, dict)
|
||||
assert stoploss_mock.call_count == 1
|
||||
assert res['cancel_order_count'] == 1
|
||||
|
||||
@@ -112,7 +112,7 @@ def assert_response(response, expected_code=200, needs_cors=True):
|
||||
|
||||
|
||||
def test_api_not_found(botclient):
|
||||
ftbot, client = botclient
|
||||
_ftbot, client = botclient
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/invalid_url")
|
||||
assert_response(rc, 404)
|
||||
@@ -120,7 +120,7 @@ def test_api_not_found(botclient):
|
||||
|
||||
|
||||
def test_api_ui_fallback(botclient, mocker):
|
||||
ftbot, client = botclient
|
||||
_ftbot, client = botclient
|
||||
|
||||
rc = client_get(client, "/favicon.ico")
|
||||
assert rc.status_code == 200
|
||||
@@ -150,7 +150,7 @@ def test_api_ui_fallback(botclient, mocker):
|
||||
|
||||
|
||||
def test_api_ui_version(botclient, mocker):
|
||||
ftbot, client = botclient
|
||||
_ftbot, client = botclient
|
||||
|
||||
mocker.patch('freqtrade.commands.deploy_commands.read_ui_version', return_value='0.1.2')
|
||||
rc = client_get(client, "/ui_version")
|
||||
@@ -230,7 +230,7 @@ def test_api_unauthorized(botclient):
|
||||
|
||||
|
||||
def test_api_token_login(botclient):
|
||||
ftbot, client = botclient
|
||||
_ftbot, client = botclient
|
||||
rc = client.post(f"{BASE_URI}/token/login",
|
||||
data=None,
|
||||
headers={'Authorization': _basic_auth_str('WRONG_USER', 'WRONG_PASS'),
|
||||
@@ -249,7 +249,7 @@ def test_api_token_login(botclient):
|
||||
|
||||
|
||||
def test_api_token_refresh(botclient):
|
||||
ftbot, client = botclient
|
||||
_ftbot, client = botclient
|
||||
rc = client_post(client, f"{BASE_URI}/token/login")
|
||||
assert_response(rc)
|
||||
rc = client.post(f"{BASE_URI}/token/refresh",
|
||||
@@ -541,7 +541,7 @@ def test_api_count(botclient, mocker, ticker, fee, markets, is_short):
|
||||
|
||||
|
||||
def test_api_locks(botclient):
|
||||
ftbot, client = botclient
|
||||
_ftbot, client = botclient
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/locks")
|
||||
assert_response(rc)
|
||||
@@ -728,7 +728,6 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short):
|
||||
|
||||
ftbot.strategy.order_types['stoploss_on_exchange'] = True
|
||||
trades = Trade.session.scalars(select(Trade)).all()
|
||||
trades[1].stoploss_order_id = '1234'
|
||||
Trade.commit()
|
||||
assert len(trades) > 2
|
||||
|
||||
@@ -745,9 +744,9 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short):
|
||||
assert cancel_mock.call_count == 0
|
||||
|
||||
assert len(trades) - 1 == len(Trade.session.scalars(select(Trade)).all())
|
||||
rc = client_delete(client, f"{BASE_URI}/trades/2")
|
||||
rc = client_delete(client, f"{BASE_URI}/trades/5")
|
||||
assert_response(rc)
|
||||
assert rc.json()['result_msg'] == 'Deleted trade 2. Closed 1 open orders.'
|
||||
assert rc.json()['result_msg'] == 'Deleted trade 5. Closed 1 open orders.'
|
||||
assert len(trades) - 2 == len(Trade.session.scalars(select(Trade)).all())
|
||||
assert stoploss_mock.call_count == 1
|
||||
|
||||
@@ -822,7 +821,7 @@ def test_api_trade_reload_trade(botclient, mocker, fee, markets, ticker, is_shor
|
||||
|
||||
|
||||
def test_api_logs(botclient):
|
||||
ftbot, client = botclient
|
||||
_ftbot, client = botclient
|
||||
rc = client_get(client, f"{BASE_URI}/logs")
|
||||
assert_response(rc)
|
||||
assert len(rc.json()) == 2
|
||||
@@ -1229,7 +1228,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
|
||||
|
||||
|
||||
def test_api_version(botclient):
|
||||
ftbot, client = botclient
|
||||
_ftbot, client = botclient
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/version")
|
||||
assert_response(rc)
|
||||
@@ -1237,7 +1236,7 @@ def test_api_version(botclient):
|
||||
|
||||
|
||||
def test_api_blacklist(botclient, mocker):
|
||||
ftbot, client = botclient
|
||||
_ftbot, client = botclient
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/blacklist")
|
||||
assert_response(rc)
|
||||
@@ -1304,7 +1303,7 @@ def test_api_blacklist(botclient, mocker):
|
||||
|
||||
|
||||
def test_api_whitelist(botclient):
|
||||
ftbot, client = botclient
|
||||
_ftbot, client = botclient
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/whitelist")
|
||||
assert_response(rc)
|
||||
@@ -1559,7 +1558,7 @@ def test_api_pair_candles(botclient, ohlcv_history):
|
||||
|
||||
|
||||
def test_api_pair_history(botclient, mocker):
|
||||
ftbot, client = botclient
|
||||
_ftbot, client = botclient
|
||||
timeframe = '5m'
|
||||
lfm = mocker.patch('freqtrade.strategy.interface.IStrategy.load_freqAI_model')
|
||||
# No pair
|
||||
@@ -1604,9 +1603,9 @@ def test_api_pair_history(botclient, mocker):
|
||||
assert 'data' in result
|
||||
data = result['data']
|
||||
assert len(data) == 289
|
||||
# analyed DF has 28 columns
|
||||
assert len(result['columns']) == 28
|
||||
assert len(data[0]) == 28
|
||||
# analyed DF has 30 columns
|
||||
assert len(result['columns']) == 30
|
||||
assert len(data[0]) == 30
|
||||
date_col_idx = [idx for idx, c in enumerate(result['columns']) if c == 'date'][0]
|
||||
rsi_col_idx = [idx for idx, c in enumerate(result['columns']) if c == 'rsi'][0]
|
||||
|
||||
@@ -1699,7 +1698,7 @@ def test_api_strategies(botclient, tmp_path):
|
||||
|
||||
|
||||
def test_api_strategy(botclient):
|
||||
ftbot, client = botclient
|
||||
_ftbot, client = botclient
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/strategy/{CURRENT_TEST_STRATEGY}")
|
||||
|
||||
@@ -1718,7 +1717,7 @@ def test_api_strategy(botclient):
|
||||
|
||||
|
||||
def test_api_exchanges(botclient):
|
||||
ftbot, client = botclient
|
||||
_ftbot, client = botclient
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/exchanges")
|
||||
assert_response(rc)
|
||||
@@ -1770,6 +1769,7 @@ def test_api_freqaimodels(botclient, tmp_path, mocker):
|
||||
{'name': 'LightGBMRegressorMultiTarget'},
|
||||
{'name': 'ReinforcementLearner'},
|
||||
{'name': 'ReinforcementLearner_multiproc'},
|
||||
{'name': 'SKlearnRandomForestClassifier'},
|
||||
{'name': 'XGBoostClassifier'},
|
||||
{'name': 'XGBoostRFClassifier'},
|
||||
{'name': 'XGBoostRFRegressor'},
|
||||
@@ -1788,6 +1788,7 @@ def test_api_freqaimodels(botclient, tmp_path, mocker):
|
||||
'LightGBMRegressorMultiTarget',
|
||||
'ReinforcementLearner',
|
||||
'ReinforcementLearner_multiproc',
|
||||
'SKlearnRandomForestClassifier',
|
||||
'XGBoostClassifier',
|
||||
'XGBoostRFClassifier',
|
||||
'XGBoostRFRegressor',
|
||||
@@ -1953,7 +1954,7 @@ def test_list_available_pairs(botclient):
|
||||
|
||||
|
||||
def test_sysinfo(botclient):
|
||||
ftbot, client = botclient
|
||||
_ftbot, client = botclient
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/sysinfo")
|
||||
assert_response(rc)
|
||||
@@ -2233,7 +2234,7 @@ def test_api_patch_backtest_history_entry(botclient, tmp_path: Path):
|
||||
|
||||
|
||||
def test_health(botclient):
|
||||
ftbot, client = botclient
|
||||
_ftbot, client = botclient
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/health")
|
||||
|
||||
@@ -2244,7 +2245,7 @@ def test_health(botclient):
|
||||
|
||||
|
||||
def test_api_ws_subscribe(botclient, mocker):
|
||||
ftbot, client = botclient
|
||||
_ftbot, client = botclient
|
||||
ws_url = f"/api/v1/message/ws?token={_TEST_WS_TOKEN}"
|
||||
|
||||
sub_mock = mocker.patch('freqtrade.rpc.api_server.ws.WebSocketChannel.set_subscriptions')
|
||||
@@ -2267,7 +2268,7 @@ def test_api_ws_subscribe(botclient, mocker):
|
||||
def test_api_ws_requests(botclient, caplog):
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
ftbot, client = botclient
|
||||
_ftbot, client = botclient
|
||||
ws_url = f"/api/v1/message/ws?token={_TEST_WS_TOKEN}"
|
||||
|
||||
# Test whitelist request
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Unit test file for rpc/external_message_consumer.py
|
||||
"""
|
||||
import asyncio
|
||||
import functools
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import MagicMock
|
||||
@@ -302,19 +301,16 @@ async def test_emc_receive_messages_valid(default_conf, caplog, mocker):
|
||||
dp = DataProvider(default_conf, None, None, None)
|
||||
emc = ExternalMessageConsumer(default_conf, dp)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
def change_running(emc): emc._running = not emc._running
|
||||
|
||||
class TestChannel:
|
||||
async def recv(self, *args, **kwargs):
|
||||
emc._running = False
|
||||
return {"type": "whitelist", "data": ["BTC/USDT"]}
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
return asyncio.Future()
|
||||
|
||||
try:
|
||||
change_running(emc)
|
||||
loop.call_soon(functools.partial(change_running, emc=emc))
|
||||
emc._running = True
|
||||
await emc._receive_messages(TestChannel(), test_producer, lock)
|
||||
|
||||
assert log_has_re(r"Received message of type `whitelist`.+", caplog)
|
||||
@@ -349,19 +345,16 @@ async def test_emc_receive_messages_invalid(default_conf, caplog, mocker):
|
||||
dp = DataProvider(default_conf, None, None, None)
|
||||
emc = ExternalMessageConsumer(default_conf, dp)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
def change_running(emc): emc._running = not emc._running
|
||||
|
||||
class TestChannel:
|
||||
async def recv(self, *args, **kwargs):
|
||||
emc._running = False
|
||||
return {"type": ["BTC/USDT"]}
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
return asyncio.Future()
|
||||
|
||||
try:
|
||||
change_running(emc)
|
||||
loop.call_soon(functools.partial(change_running, emc=emc))
|
||||
emc._running = True
|
||||
await emc._receive_messages(TestChannel(), test_producer, lock)
|
||||
|
||||
assert log_has_re(r"Invalid message from.+", caplog)
|
||||
@@ -396,8 +389,8 @@ async def test_emc_receive_messages_timeout(default_conf, caplog, mocker):
|
||||
dp = DataProvider(default_conf, None, None, None)
|
||||
emc = ExternalMessageConsumer(default_conf, dp)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
def change_running(emc): emc._running = not emc._running
|
||||
def change_running():
|
||||
emc._running = not emc._running
|
||||
|
||||
class TestChannel:
|
||||
async def recv(self, *args, **kwargs):
|
||||
@@ -407,8 +400,7 @@ async def test_emc_receive_messages_timeout(default_conf, caplog, mocker):
|
||||
return asyncio.Future()
|
||||
|
||||
try:
|
||||
change_running(emc)
|
||||
loop.call_soon(functools.partial(change_running, emc=emc))
|
||||
change_running()
|
||||
|
||||
with pytest.raises(asyncio.TimeoutError):
|
||||
await emc._receive_messages(TestChannel(), test_producer, lock)
|
||||
@@ -447,19 +439,16 @@ async def test_emc_receive_messages_handle_error(default_conf, caplog, mocker):
|
||||
|
||||
emc.handle_producer_message = MagicMock(side_effect=Exception)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
def change_running(emc): emc._running = not emc._running
|
||||
|
||||
class TestChannel:
|
||||
async def recv(self, *args, **kwargs):
|
||||
emc._running = False
|
||||
return {"type": "whitelist", "data": ["BTC/USDT"]}
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
return asyncio.Future()
|
||||
|
||||
try:
|
||||
change_running(emc)
|
||||
loop.call_soon(functools.partial(change_running, emc=emc))
|
||||
emc._running = True
|
||||
await emc._receive_messages(TestChannel(), test_producer, lock)
|
||||
|
||||
assert log_has_re(r"Error handling producer message.+", caplog)
|
||||
|
||||
+120
-82
@@ -599,7 +599,7 @@ async def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt)
|
||||
telegram, _freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt)
|
||||
|
||||
# Move date to within day
|
||||
time_machine.move_to('2022-06-11 08:00:00+00:00')
|
||||
@@ -1154,11 +1154,11 @@ async def test_telegram_forceexit_handle(default_conf, update, ticker, fee,
|
||||
'profit_amount': 6.314e-05,
|
||||
'profit_ratio': 0.0629778,
|
||||
'stake_currency': 'BTC',
|
||||
'quote_currency': 'BTC',
|
||||
'base_currency': 'ETH',
|
||||
'fiat_currency': 'USD',
|
||||
'buy_tag': ANY,
|
||||
'enter_tag': ANY,
|
||||
'sell_reason': ExitType.FORCE_EXIT.value,
|
||||
'exit_reason': ExitType.FORCE_EXIT.value,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
@@ -1166,6 +1166,8 @@ async def test_telegram_forceexit_handle(default_conf, update, ticker, fee,
|
||||
'stake_amount': 0.0009999999999054,
|
||||
'sub_trade': False,
|
||||
'cumulative_profit': 0.0,
|
||||
'is_final_exit': False,
|
||||
'final_profit_ratio': None,
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -1227,11 +1229,11 @@ async def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee
|
||||
'profit_amount': -5.497e-05,
|
||||
'profit_ratio': -0.05482878,
|
||||
'stake_currency': 'BTC',
|
||||
'quote_currency': 'BTC',
|
||||
'base_currency': 'ETH',
|
||||
'fiat_currency': 'USD',
|
||||
'buy_tag': ANY,
|
||||
'enter_tag': ANY,
|
||||
'sell_reason': ExitType.FORCE_EXIT.value,
|
||||
'exit_reason': ExitType.FORCE_EXIT.value,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
@@ -1239,6 +1241,8 @@ async def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee
|
||||
'stake_amount': 0.0009999999999054,
|
||||
'sub_trade': False,
|
||||
'cumulative_profit': 0.0,
|
||||
'is_final_exit': False,
|
||||
'final_profit_ratio': None,
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -1290,11 +1294,11 @@ async def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -
|
||||
'profit_amount': -4.09e-06,
|
||||
'profit_ratio': -0.00408133,
|
||||
'stake_currency': 'BTC',
|
||||
'quote_currency': 'BTC',
|
||||
'base_currency': 'ETH',
|
||||
'fiat_currency': 'USD',
|
||||
'buy_tag': ANY,
|
||||
'enter_tag': ANY,
|
||||
'sell_reason': ExitType.FORCE_EXIT.value,
|
||||
'exit_reason': ExitType.FORCE_EXIT.value,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
@@ -1302,6 +1306,8 @@ async def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -
|
||||
'stake_amount': 0.0009999999999054,
|
||||
'sub_trade': False,
|
||||
'cumulative_profit': 0.0,
|
||||
'is_final_exit': False,
|
||||
'final_profit_ratio': None,
|
||||
} == msg
|
||||
|
||||
|
||||
@@ -1474,7 +1480,7 @@ async def test_telegram_performance_handle(default_conf_usdt, update, ticker, fe
|
||||
fetch_ticker=ticker,
|
||||
get_fee=fee,
|
||||
)
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt)
|
||||
telegram, _freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt)
|
||||
|
||||
# Create some test data
|
||||
create_mock_trades_usdt(fee)
|
||||
@@ -1649,7 +1655,7 @@ async def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -
|
||||
|
||||
async def test_whitelist_static(default_conf, update, mocker) -> None:
|
||||
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
telegram, _freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
await telegram._whitelist(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
@@ -1999,7 +2005,10 @@ def test_send_msg_enter_notification(default_conf, mocker, caplog, message_type,
|
||||
'stake_amount': 0.01465333,
|
||||
'stake_amount_fiat': 0.0,
|
||||
'stake_currency': 'BTC',
|
||||
'quote_currency': 'BTC',
|
||||
'base_currency': 'ETH',
|
||||
'fiat_currency': 'USD',
|
||||
'sub_trade': False,
|
||||
'current_rate': 1.099e-05,
|
||||
'amount': 1333.3333333333335,
|
||||
'analyzed_candle': {'open': 1.1, 'high': 2.2, 'low': 1.0, 'close': 1.5},
|
||||
@@ -2008,17 +2017,19 @@ def test_send_msg_enter_notification(default_conf, mocker, caplog, message_type,
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
telegram.send_msg(msg)
|
||||
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
|
||||
leverage_text = f' ({leverage:.1g}x)' if leverage and leverage != 1.0 else ''
|
||||
|
||||
assert msg_mock.call_args[0][0] == (
|
||||
f'\N{LARGE BLUE CIRCLE} *Binance (dry):* {enter} ETH/BTC (#1)\n'
|
||||
f'\N{LARGE BLUE CIRCLE} *Binance (dry):* New Trade (#1)\n'
|
||||
f'*Pair:* `ETH/BTC`\n'
|
||||
'*Candle OHLC*: `1.1, 2.2, 1.0, 1.5`\n'
|
||||
f'*Enter Tag:* `{enter_signal}`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
f'{leverage_text}'
|
||||
'*Open Rate:* `0.00001099`\n'
|
||||
'*Current Rate:* `0.00001099`\n'
|
||||
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
|
||||
f'*Direction:* `{enter}'
|
||||
f'{leverage_text}`\n'
|
||||
'*Open Rate:* `0.00001099 BTC`\n'
|
||||
'*Current Rate:* `0.00001099 BTC`\n'
|
||||
'*Total:* `0.01465333 BTC / 180.895 USD`'
|
||||
)
|
||||
|
||||
freqtradebot.config['telegram']['notification_settings'] = {'buy': 'off'}
|
||||
@@ -2106,20 +2117,25 @@ def test_send_msg_entry_fill_notification(default_conf, mocker, message_type, en
|
||||
'leverage': leverage,
|
||||
'stake_amount': 0.01465333,
|
||||
'direction': entered,
|
||||
'sub_trade': False,
|
||||
'stake_currency': 'BTC',
|
||||
'quote_currency': 'BTC',
|
||||
'base_currency': 'ETH',
|
||||
'fiat_currency': 'USD',
|
||||
'open_rate': 1.099e-05,
|
||||
'amount': 1333.3333333333335,
|
||||
'open_date': dt_now() - timedelta(hours=1)
|
||||
})
|
||||
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage != 1.0 else ''
|
||||
leverage_text = f' ({leverage:.1g}x)' if leverage != 1.0 else ''
|
||||
assert msg_mock.call_args[0][0] == (
|
||||
f'\N{CHECK MARK} *Binance (dry):* {entered}ed ETH/BTC (#1)\n'
|
||||
f'\N{CHECK MARK} *Binance (dry):* New Trade filled (#1)\n'
|
||||
f'*Pair:* `ETH/BTC`\n'
|
||||
f'*Enter Tag:* `{enter_signal}`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
f"{leverage_text}"
|
||||
'*Open Rate:* `0.00001099`\n'
|
||||
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
|
||||
f'*Direction:* `{entered}'
|
||||
f"{leverage_text}`\n"
|
||||
'*Open Rate:* `0.00001099 BTC`\n'
|
||||
'*Total:* `0.01465333 BTC / 180.895 USD`'
|
||||
)
|
||||
|
||||
msg_mock.reset_mock()
|
||||
@@ -2134,6 +2150,8 @@ def test_send_msg_entry_fill_notification(default_conf, mocker, message_type, en
|
||||
'sub_trade': True,
|
||||
'direction': entered,
|
||||
'stake_currency': 'BTC',
|
||||
'quote_currency': 'BTC',
|
||||
'base_currency': 'ETH',
|
||||
'fiat_currency': 'USD',
|
||||
'open_rate': 1.099e-05,
|
||||
'amount': 1333.3333333333335,
|
||||
@@ -2141,16 +2159,18 @@ def test_send_msg_entry_fill_notification(default_conf, mocker, message_type, en
|
||||
})
|
||||
|
||||
assert msg_mock.call_args[0][0] == (
|
||||
f'\N{CHECK MARK} *Binance (dry):* {entered}ed ETH/BTC (#1)\n'
|
||||
f'\N{CHECK MARK} *Binance (dry):* Position increase filled (#1)\n'
|
||||
f'*Pair:* `ETH/BTC`\n'
|
||||
f'*Enter Tag:* `{enter_signal}`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
f"{leverage_text}"
|
||||
'*Open Rate:* `0.00001099`\n'
|
||||
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
|
||||
f'*Direction:* `{entered}'
|
||||
f"{leverage_text}`\n"
|
||||
'*Open Rate:* `0.00001099 BTC`\n'
|
||||
'*New Total:* `0.01465333 BTC / 180.895 USD`'
|
||||
)
|
||||
|
||||
|
||||
def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
def test_send_msg_exit_notification(default_conf, mocker) -> None:
|
||||
|
||||
with time_machine.travel("2022-09-01 05:00:00 +00:00", tick=False):
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
@@ -2165,14 +2185,16 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
'leverage': 1.0,
|
||||
'direction': 'Long',
|
||||
'gain': 'loss',
|
||||
'order_rate': 3.201e-05,
|
||||
'order_rate': 3.201e-04,
|
||||
'amount': 1333.3333333333335,
|
||||
'order_type': 'market',
|
||||
'open_rate': 7.5e-05,
|
||||
'current_rate': 3.201e-05,
|
||||
'open_rate': 7.5e-04,
|
||||
'current_rate': 3.201e-04,
|
||||
'profit_amount': -0.05746268,
|
||||
'profit_ratio': -0.57405275,
|
||||
'stake_currency': 'ETH',
|
||||
'quote_currency': 'ETH',
|
||||
'base_currency': 'KEY',
|
||||
'fiat_currency': 'USD',
|
||||
'enter_tag': 'buy_signal1',
|
||||
'exit_reason': ExitType.STOP_LOSS.value,
|
||||
@@ -2181,14 +2203,14 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == (
|
||||
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
|
||||
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
|
||||
'*Unrealized Profit:* `-57.41% (loss: -0.05746 ETH / -24.812 USD)`\n'
|
||||
'*Enter Tag:* `buy_signal1`\n'
|
||||
'*Exit Reason:* `stop_loss`\n'
|
||||
'*Direction:* `Long`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Current Rate:* `0.00003201`\n'
|
||||
'*Exit Rate:* `0.00003201`\n'
|
||||
'*Open Rate:* `0.00075 ETH`\n'
|
||||
'*Current Rate:* `0.00032 ETH`\n'
|
||||
'*Exit Rate:* `0.00032 ETH`\n'
|
||||
'*Duration:* `1:00:00 (60.0 min)`'
|
||||
)
|
||||
|
||||
@@ -2200,15 +2222,17 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
'pair': 'KEY/ETH',
|
||||
'direction': 'Long',
|
||||
'gain': 'loss',
|
||||
'order_rate': 3.201e-05,
|
||||
'order_rate': 3.201e-04,
|
||||
'amount': 1333.3333333333335,
|
||||
'order_type': 'market',
|
||||
'open_rate': 7.5e-05,
|
||||
'current_rate': 3.201e-05,
|
||||
'open_rate': 7.5e-04,
|
||||
'current_rate': 3.201e-04,
|
||||
'cumulative_profit': -0.15746268,
|
||||
'profit_amount': -0.05746268,
|
||||
'profit_ratio': -0.57405275,
|
||||
'stake_currency': 'ETH',
|
||||
'quote_currency': 'ETH',
|
||||
'base_currency': 'KEY',
|
||||
'fiat_currency': 'USD',
|
||||
'enter_tag': 'buy_signal1',
|
||||
'exit_reason': ExitType.STOP_LOSS.value,
|
||||
@@ -2219,16 +2243,16 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == (
|
||||
'\N{WARNING SIGN} *Binance (dry):* Partially exiting KEY/ETH (#1)\n'
|
||||
'*Unrealized Sub Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
|
||||
'*Cumulative Profit:* (`-0.15746268 ETH / -24.812 USD`)\n'
|
||||
'*Unrealized Sub Profit:* `-57.41% (loss: -0.05746 ETH / -24.812 USD)`\n'
|
||||
'*Cumulative Profit:* `-0.15746 ETH / -24.812 USD`\n'
|
||||
'*Enter Tag:* `buy_signal1`\n'
|
||||
'*Exit Reason:* `stop_loss`\n'
|
||||
'*Direction:* `Long`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Current Rate:* `0.00003201`\n'
|
||||
'*Exit Rate:* `0.00003201`\n'
|
||||
'*Remaining:* `(0.01 ETH, -24.812 USD)`'
|
||||
'*Open Rate:* `0.00075 ETH`\n'
|
||||
'*Current Rate:* `0.00032 ETH`\n'
|
||||
'*Exit Rate:* `0.00032 ETH`\n'
|
||||
'*Remaining:* `0.01 ETH / -24.812 USD`'
|
||||
)
|
||||
|
||||
msg_mock.reset_mock()
|
||||
@@ -2239,14 +2263,17 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
'pair': 'KEY/ETH',
|
||||
'direction': 'Long',
|
||||
'gain': 'loss',
|
||||
'order_rate': 3.201e-05,
|
||||
'order_rate': 3.201e-04,
|
||||
'amount': 1333.3333333333335,
|
||||
'order_type': 'market',
|
||||
'open_rate': 7.5e-05,
|
||||
'current_rate': 3.201e-05,
|
||||
'open_rate': 7.5e-04,
|
||||
'current_rate': 3.201e-04,
|
||||
'profit_amount': -0.05746268,
|
||||
'profit_ratio': -0.57405275,
|
||||
'stake_currency': 'ETH',
|
||||
'quote_currency': 'ETH',
|
||||
'base_currency': 'KEY',
|
||||
'fiat_currency': None,
|
||||
'enter_tag': 'buy_signal1',
|
||||
'exit_reason': ExitType.STOP_LOSS.value,
|
||||
'open_date': dt_now() - timedelta(days=1, hours=2, minutes=30),
|
||||
@@ -2254,21 +2281,21 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == (
|
||||
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
|
||||
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH)`\n'
|
||||
'*Unrealized Profit:* `-57.41% (loss: -0.05746 ETH)`\n'
|
||||
'*Enter Tag:* `buy_signal1`\n'
|
||||
'*Exit Reason:* `stop_loss`\n'
|
||||
'*Direction:* `Long`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Current Rate:* `0.00003201`\n'
|
||||
'*Exit Rate:* `0.00003201`\n'
|
||||
'*Open Rate:* `0.00075 ETH`\n'
|
||||
'*Current Rate:* `0.00032 ETH`\n'
|
||||
'*Exit Rate:* `0.00032 ETH`\n'
|
||||
'*Duration:* `1 day, 2:30:00 (1590.0 min)`'
|
||||
)
|
||||
# Reset singleton function to avoid random breaks
|
||||
telegram._rpc._fiat_converter.convert_amount = old_convamount
|
||||
|
||||
|
||||
async def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
|
||||
async def test_send_msg_exit_cancel_notification(default_conf, mocker) -> None:
|
||||
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
@@ -2306,7 +2333,7 @@ async def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
|
||||
('Long', 'long_signal_01', 1.0),
|
||||
('Long', 'long_signal_01', 5.0),
|
||||
('Short', 'short_signal_01', 2.0)])
|
||||
def test_send_msg_sell_fill_notification(default_conf, mocker, direction,
|
||||
def test_send_msg_exit_fill_notification(default_conf, mocker, direction,
|
||||
enter_signal, leverage) -> None:
|
||||
|
||||
default_conf['telegram']['notification_settings']['exit_fill'] = 'on'
|
||||
@@ -2321,31 +2348,34 @@ def test_send_msg_sell_fill_notification(default_conf, mocker, direction,
|
||||
'leverage': leverage,
|
||||
'direction': direction,
|
||||
'gain': 'loss',
|
||||
'limit': 3.201e-05,
|
||||
'limit': 3.201e-04,
|
||||
'amount': 1333.3333333333335,
|
||||
'order_type': 'market',
|
||||
'open_rate': 7.5e-05,
|
||||
'close_rate': 3.201e-05,
|
||||
'open_rate': 7.5e-04,
|
||||
'close_rate': 3.201e-04,
|
||||
'profit_amount': -0.05746268,
|
||||
'profit_ratio': -0.57405275,
|
||||
'stake_currency': 'ETH',
|
||||
'quote_currency': 'ETH',
|
||||
'base_currency': 'KEY',
|
||||
'fiat_currency': None,
|
||||
'enter_tag': enter_signal,
|
||||
'exit_reason': ExitType.STOP_LOSS.value,
|
||||
'open_date': dt_now() - timedelta(days=1, hours=2, minutes=30),
|
||||
'close_date': dt_now(),
|
||||
})
|
||||
|
||||
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
|
||||
leverage_text = f' ({leverage:.1g}x)`\n' if leverage and leverage != 1.0 else '`\n'
|
||||
assert msg_mock.call_args[0][0] == (
|
||||
'\N{WARNING SIGN} *Binance (dry):* Exited KEY/ETH (#1)\n'
|
||||
'*Profit:* `-57.41% (loss: -0.05746268 ETH)`\n'
|
||||
'*Profit:* `-57.41% (loss: -0.05746 ETH)`\n'
|
||||
f'*Enter Tag:* `{enter_signal}`\n'
|
||||
'*Exit Reason:* `stop_loss`\n'
|
||||
f"*Direction:* `{direction}`\n"
|
||||
f"*Direction:* `{direction}"
|
||||
f"{leverage_text}"
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Exit Rate:* `0.00003201`\n'
|
||||
'*Open Rate:* `0.00075 ETH`\n'
|
||||
'*Exit Rate:* `0.00032 ETH`\n'
|
||||
'*Duration:* `1 day, 2:30:00 (1590.0 min)`'
|
||||
)
|
||||
|
||||
@@ -2416,24 +2446,29 @@ def test_send_msg_buy_notification_no_fiat(
|
||||
'open_rate': 1.099e-05,
|
||||
'order_type': 'limit',
|
||||
'direction': enter,
|
||||
'sub_trade': False,
|
||||
'stake_amount': 0.01465333,
|
||||
'stake_amount_fiat': 0.0,
|
||||
'stake_currency': 'BTC',
|
||||
'quote_currency': 'BTC',
|
||||
'base_currency': 'ETH',
|
||||
'fiat_currency': None,
|
||||
'current_rate': 1.099e-05,
|
||||
'amount': 1333.3333333333335,
|
||||
'open_date': dt_now() - timedelta(hours=1)
|
||||
})
|
||||
|
||||
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
|
||||
leverage_text = f' ({leverage:.1g}x)' if leverage and leverage != 1.0 else ''
|
||||
assert msg_mock.call_args[0][0] == (
|
||||
f'\N{LARGE BLUE CIRCLE} *Binance:* {enter} ETH/BTC (#1)\n'
|
||||
f'\N{LARGE BLUE CIRCLE} *Binance:* New Trade (#1)\n'
|
||||
'*Pair:* `ETH/BTC`\n'
|
||||
f'*Enter Tag:* `{enter_signal}`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
f'{leverage_text}'
|
||||
'*Open Rate:* `0.00001099`\n'
|
||||
'*Current Rate:* `0.00001099`\n'
|
||||
'*Total:* `(0.01465333 BTC)`'
|
||||
f'*Direction:* `{enter}'
|
||||
f'{leverage_text}`\n'
|
||||
'*Open Rate:* `0.00001099 BTC`\n'
|
||||
'*Current Rate:* `0.00001099 BTC`\n'
|
||||
'*Total:* `0.01465333 BTC`'
|
||||
)
|
||||
|
||||
|
||||
@@ -2443,7 +2478,7 @@ def test_send_msg_buy_notification_no_fiat(
|
||||
('Long', 'long_signal_01', 5.0),
|
||||
('Short', 'short_signal_01', 2.0),
|
||||
])
|
||||
def test_send_msg_sell_notification_no_fiat(
|
||||
def test_send_msg_exit_notification_no_fiat(
|
||||
default_conf, mocker, direction, enter_signal, leverage, time_machine) -> None:
|
||||
del default_conf['fiat_display_currency']
|
||||
time_machine.move_to('2022-05-02 00:00:00 +00:00', tick=False)
|
||||
@@ -2457,14 +2492,17 @@ def test_send_msg_sell_notification_no_fiat(
|
||||
'gain': 'loss',
|
||||
'leverage': leverage,
|
||||
'direction': direction,
|
||||
'order_rate': 3.201e-05,
|
||||
'sub_trade': False,
|
||||
'order_rate': 3.201e-04,
|
||||
'amount': 1333.3333333333335,
|
||||
'order_type': 'limit',
|
||||
'open_rate': 7.5e-05,
|
||||
'current_rate': 3.201e-05,
|
||||
'open_rate': 7.5e-04,
|
||||
'current_rate': 3.201e-04,
|
||||
'profit_amount': -0.05746268,
|
||||
'profit_ratio': -0.57405275,
|
||||
'stake_currency': 'ETH',
|
||||
'quote_currency': 'ETH',
|
||||
'base_currency': 'KEY',
|
||||
'fiat_currency': 'USD',
|
||||
'enter_tag': enter_signal,
|
||||
'exit_reason': ExitType.STOP_LOSS.value,
|
||||
@@ -2472,37 +2510,37 @@ def test_send_msg_sell_notification_no_fiat(
|
||||
'close_date': dt_now(),
|
||||
})
|
||||
|
||||
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
|
||||
leverage_text = f' ({leverage:.1g}x)' if leverage and leverage != 1.0 else ''
|
||||
assert msg_mock.call_args[0][0] == (
|
||||
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
|
||||
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH)`\n'
|
||||
'*Unrealized Profit:* `-57.41% (loss: -0.05746 ETH)`\n'
|
||||
f'*Enter Tag:* `{enter_signal}`\n'
|
||||
'*Exit Reason:* `stop_loss`\n'
|
||||
f'*Direction:* `{direction}`\n'
|
||||
f'{leverage_text}'
|
||||
f'*Direction:* `{direction}'
|
||||
f'{leverage_text}`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Current Rate:* `0.00003201`\n'
|
||||
'*Exit Rate:* `0.00003201`\n'
|
||||
'*Open Rate:* `0.00075 ETH`\n'
|
||||
'*Current Rate:* `0.00032 ETH`\n'
|
||||
'*Exit Rate:* `0.00032 ETH`\n'
|
||||
'*Duration:* `2:35:03 (155.1 min)`'
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('msg,expected', [
|
||||
({'profit_percent': 20.1, 'exit_reason': 'roi'}, "\N{ROCKET}"),
|
||||
({'profit_percent': 5.1, 'exit_reason': 'roi'}, "\N{ROCKET}"),
|
||||
({'profit_percent': 2.56, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
|
||||
({'profit_percent': 1.0, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
|
||||
({'profit_percent': 0.0, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
|
||||
({'profit_percent': -5.0, 'exit_reason': 'stop_loss'}, "\N{WARNING SIGN}"),
|
||||
({'profit_percent': -2.0, 'exit_reason': 'sell_signal'}, "\N{CROSS MARK}"),
|
||||
({'profit_ratio': 0.201, 'exit_reason': 'roi'}, "\N{ROCKET}"),
|
||||
({'profit_ratio': 0.051, 'exit_reason': 'roi'}, "\N{ROCKET}"),
|
||||
({'profit_ratio': 0.0256, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
|
||||
({'profit_ratio': 0.01, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
|
||||
({'profit_ratio': 0.0, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
|
||||
({'profit_ratio': -0.05, 'exit_reason': 'stop_loss'}, "\N{WARNING SIGN}"),
|
||||
({'profit_ratio': -0.02, 'exit_reason': 'sell_signal'}, "\N{CROSS MARK}"),
|
||||
])
|
||||
def test__sell_emoji(default_conf, mocker, msg, expected):
|
||||
def test__exit_emoji(default_conf, mocker, msg, expected):
|
||||
del default_conf['fiat_display_currency']
|
||||
|
||||
telegram, _, _ = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
assert telegram._get_sell_emoji(msg) == expected
|
||||
assert telegram._get_exit_emoji(msg) == expected
|
||||
|
||||
|
||||
async def test_telegram__send_msg(default_conf, mocker, caplog) -> None:
|
||||
@@ -2609,7 +2647,7 @@ async def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
|
||||
|
||||
|
||||
async def test_change_market_direction(default_conf, mocker, update) -> None:
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
telegram, _, _msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
assert telegram._rpc._freqtrade.strategy.market_direction == MarketDirection.NONE
|
||||
context = MagicMock()
|
||||
context.args = ["long"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103, protected-access
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
@@ -331,6 +332,7 @@ def test_send_msg_webhook(default_conf, mocker):
|
||||
|
||||
|
||||
def test_exception_send_msg(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.DEBUG)
|
||||
default_conf["webhook"] = get_webhook_dict()
|
||||
del default_conf["webhook"]["entry"]
|
||||
del default_conf["webhook"]["webhookentry"]
|
||||
|
||||
@@ -152,7 +152,7 @@ class StrategyTestV3(IStrategy):
|
||||
(
|
||||
qtpylib.crossed_below(dataframe['rsi'], self.sell_rsi.value)
|
||||
),
|
||||
'enter_short'] = 1
|
||||
('enter_short', 'enter_tag')] = (1, 'short_Tag')
|
||||
|
||||
return dataframe
|
||||
|
||||
@@ -176,7 +176,7 @@ class StrategyTestV3(IStrategy):
|
||||
(
|
||||
qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)
|
||||
),
|
||||
'exit_short'] = 1
|
||||
('exit_short', 'exit_tag')] = (1, 'short_Tag')
|
||||
|
||||
return dataframe
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ def test_returns_latest_signal(ohlcv_history):
|
||||
_STRATEGY.config['trading_mode'] = 'spot'
|
||||
|
||||
|
||||
def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history):
|
||||
def test_analyze_pair_empty(mocker, caplog, ohlcv_history):
|
||||
mocker.patch.object(_STRATEGY.dp, 'ohlcv', return_value=ohlcv_history)
|
||||
mocker.patch.object(
|
||||
_STRATEGY, '_analyze_ticker_internal',
|
||||
@@ -1019,3 +1019,30 @@ def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog):
|
||||
|
||||
StrategyResolver.load_strategy(default_conf)
|
||||
assert log_has("Invalid parameter file format.", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('function,raises', [
|
||||
('populate_entry_trend', True),
|
||||
('advise_entry', False),
|
||||
('populate_exit_trend', True),
|
||||
('advise_exit', False),
|
||||
])
|
||||
def test_pandas_warning_direct(ohlcv_history, function, raises):
|
||||
|
||||
df = _STRATEGY.populate_indicators(ohlcv_history, {'pair': 'ETH/BTC'})
|
||||
if raises:
|
||||
with pytest.warns(FutureWarning):
|
||||
# Test for Future warning
|
||||
# FutureWarning: Setting an item of incompatible dtype is
|
||||
# deprecated and will raise in a future error of pandas
|
||||
# https://github.com/pandas-dev/pandas/issues/56503
|
||||
getattr(_STRATEGY, function)(df, {'pair': 'ETH/BTC'})
|
||||
else:
|
||||
getattr(_STRATEGY, function)(df, {'pair': 'ETH/BTC'})
|
||||
|
||||
|
||||
def test_pandas_warning_through_analyze_pair(ohlcv_history, mocker, recwarn):
|
||||
|
||||
mocker.patch.object(_STRATEGY.dp, 'ohlcv', return_value=ohlcv_history)
|
||||
_STRATEGY.analyze_pair('ETH/BTC')
|
||||
assert len(recwarn) == 0
|
||||
|
||||
@@ -173,7 +173,7 @@ def test_download_data_options() -> None:
|
||||
def test_plot_dataframe_options() -> None:
|
||||
args = [
|
||||
'plot-dataframe',
|
||||
'-c', 'config_examples/config_bittrex.example.json',
|
||||
'-c', 'tests/testdata/testconfigs/main_test_config.json',
|
||||
'--indicators1', 'sma10', 'sma100',
|
||||
'--indicators2', 'macd', 'fastd', 'fastk',
|
||||
'--plot-limit', '30',
|
||||
|
||||
@@ -15,7 +15,7 @@ from freqtrade.configuration.deprecated_settings import (check_conflicting_setti
|
||||
process_deprecated_setting,
|
||||
process_removed_setting,
|
||||
process_temporary_deprecated_settings)
|
||||
from freqtrade.configuration.environment_vars import flat_vars_to_nested_dict
|
||||
from freqtrade.configuration.environment_vars import _flat_vars_to_nested_dict
|
||||
from freqtrade.configuration.load_config import (load_config_file, load_file, load_from_files,
|
||||
log_config_error_range)
|
||||
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL, ENV_VAR_PREFIX
|
||||
@@ -1419,7 +1419,7 @@ def test_flat_vars_to_nested_dict(caplog):
|
||||
'chat_id': '2151'
|
||||
}
|
||||
}
|
||||
res = flat_vars_to_nested_dict(test_args, ENV_VAR_PREFIX)
|
||||
res = _flat_vars_to_nested_dict(test_args, ENV_VAR_PREFIX)
|
||||
assert res == expected
|
||||
|
||||
assert log_has("Loading variable 'FREQTRADE__EXCHANGE__SOME_SETTING'", caplog)
|
||||
|
||||
+87
-57
@@ -438,6 +438,7 @@ def test_enter_positions_no_pairs_left(default_conf_usdt, ticker_usdt, limit_buy
|
||||
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
|
||||
get_fee=fee,
|
||||
)
|
||||
mocker.patch('freqtrade.configuration.config_validation._validate_whitelist')
|
||||
default_conf_usdt['exchange']['pair_whitelist'] = whitelist
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
patch_get_signal(freqtrade)
|
||||
@@ -626,15 +627,16 @@ def test_process_exchange_failures(default_conf_usdt, ticker_usdt, mocker) -> No
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
fetch_ticker=ticker_usdt,
|
||||
create_order=MagicMock(side_effect=TemporaryError)
|
||||
reload_markets=MagicMock(side_effect=TemporaryError),
|
||||
create_order=MagicMock(side_effect=TemporaryError),
|
||||
)
|
||||
sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None)
|
||||
sleep_mock = mocker.patch('time.sleep')
|
||||
|
||||
worker = Worker(args=None, config=default_conf_usdt)
|
||||
patch_get_signal(worker.freqtrade)
|
||||
|
||||
worker._process_running()
|
||||
assert sleep_mock.has_calls()
|
||||
assert sleep_mock.called is True
|
||||
|
||||
|
||||
def test_process_operational_exception(default_conf_usdt, ticker_usdt, mocker) -> None:
|
||||
@@ -857,7 +859,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
||||
open_order['id'] = '22'
|
||||
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
|
||||
assert freqtrade.execute_entry(pair, stake_amount)
|
||||
assert enter_rate_mock.call_count == 1
|
||||
assert enter_rate_mock.call_count == 2
|
||||
assert enter_mm.call_count == 1
|
||||
call_args = enter_mm.call_args_list[0][1]
|
||||
assert call_args['pair'] == pair
|
||||
@@ -879,7 +881,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
||||
fix_price = 0.06
|
||||
assert freqtrade.execute_entry(pair, stake_amount, fix_price, is_short=is_short)
|
||||
# Make sure get_rate wasn't called again
|
||||
assert enter_rate_mock.call_count == 0
|
||||
assert enter_rate_mock.call_count == 1
|
||||
|
||||
assert enter_mm.call_count == 2
|
||||
call_args = enter_mm.call_args_list[1][1]
|
||||
@@ -1160,9 +1162,9 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
|
||||
freqtrade.enter_positions()
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_short = is_short
|
||||
trade.is_open = True
|
||||
trade.stoploss_order_id = None
|
||||
assert trade.is_short == is_short
|
||||
assert trade.is_open
|
||||
assert trade.stoploss_order_id is None
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
assert stoploss.call_count == 1
|
||||
@@ -1170,34 +1172,21 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
|
||||
# Second case: when stoploss is set but it is not yet hit
|
||||
# should do nothing and return false
|
||||
stop_order_dict.update({'id': "102"})
|
||||
trade.is_open = True
|
||||
trade.stoploss_order_id = "102"
|
||||
trade.orders.append(
|
||||
Order(
|
||||
ft_order_side='stoploss',
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=True,
|
||||
ft_amount=trade.amount,
|
||||
ft_price=trade.stop_loss,
|
||||
order_id='102',
|
||||
status='open',
|
||||
)
|
||||
)
|
||||
|
||||
hanging_stoploss_order = MagicMock(return_value={'status': 'open'})
|
||||
hanging_stoploss_order = MagicMock(return_value={'id': '13434334', 'status': 'open'})
|
||||
mocker.patch(f'{EXMS}.fetch_stoploss_order', hanging_stoploss_order)
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
assert trade.stoploss_order_id == "102"
|
||||
hanging_stoploss_order.assert_called_once_with('13434334', trade.pair)
|
||||
assert trade.stoploss_order_id == "13434334"
|
||||
|
||||
# Third case: when stoploss was set but it was canceled for some reason
|
||||
# should set a stoploss immediately and return False
|
||||
caplog.clear()
|
||||
trade.is_open = True
|
||||
trade.stoploss_order_id = "102"
|
||||
|
||||
canceled_stoploss_order = MagicMock(return_value={'id': '103_1', 'status': 'canceled'})
|
||||
canceled_stoploss_order = MagicMock(return_value={'id': '13434334', 'status': 'canceled'})
|
||||
mocker.patch(f'{EXMS}.fetch_stoploss_order', canceled_stoploss_order)
|
||||
stoploss.reset_mock()
|
||||
amount_before = trade.amount
|
||||
@@ -1213,25 +1202,14 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
# should unset stoploss_order_id and return true
|
||||
# as a trade actually happened
|
||||
caplog.clear()
|
||||
freqtrade.enter_positions()
|
||||
stop_order_dict.update({'id': "104"})
|
||||
stop_order_dict.update({'id': "103_1"})
|
||||
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_short = is_short
|
||||
trade.is_open = True
|
||||
trade.stoploss_order_id = "104"
|
||||
trade.orders.append(Order(
|
||||
ft_order_side='stoploss',
|
||||
order_id='104',
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=True,
|
||||
ft_amount=trade.amount,
|
||||
ft_price=0.0,
|
||||
))
|
||||
assert trade
|
||||
|
||||
stoploss_order_hit = MagicMock(return_value={
|
||||
'id': "104",
|
||||
'id': "103_1",
|
||||
'status': 'closed',
|
||||
'type': 'stop_loss_limit',
|
||||
'price': 3,
|
||||
@@ -1273,7 +1251,40 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
assert stoploss.call_count == 0
|
||||
|
||||
# Seventh case: emergency exit triggered
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_handle_stoploss_on_exchange_emergency(mocker, default_conf_usdt, fee, is_short,
|
||||
limit_order) -> None:
|
||||
stop_order_dict = {'id': "13434334"}
|
||||
stoploss = MagicMock(return_value=stop_order_dict)
|
||||
enter_order = limit_order[entry_side(is_short)]
|
||||
exit_order = limit_order[exit_side(is_short)]
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
fetch_ticker=MagicMock(return_value={
|
||||
'bid': 1.9,
|
||||
'ask': 2.2,
|
||||
'last': 1.9
|
||||
}),
|
||||
create_order=MagicMock(side_effect=[
|
||||
enter_order,
|
||||
exit_order,
|
||||
]),
|
||||
get_fee=fee,
|
||||
create_stoploss=stoploss
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
||||
|
||||
freqtrade.enter_positions()
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade.is_short == is_short
|
||||
assert trade.is_open
|
||||
assert trade.stoploss_order_id is None
|
||||
|
||||
# emergency exit triggered
|
||||
# Trailing stop should not act anymore
|
||||
stoploss_order_cancelled = MagicMock(side_effect=[{
|
||||
'id': "107",
|
||||
@@ -1287,7 +1298,6 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
'info': {'stopPrice': 22},
|
||||
}])
|
||||
trade.stoploss_order_id = "107"
|
||||
trade.is_open = True
|
||||
trade.stoploss_last_update = dt_now() - timedelta(hours=1)
|
||||
trade.stop_loss = 24
|
||||
trade.exit_reason = None
|
||||
@@ -1548,7 +1558,7 @@ def test_create_stoploss_order_invalid_order(
|
||||
|
||||
# Rpc is sending first buy, then sell
|
||||
assert rpc_mock.call_count == 2
|
||||
assert rpc_mock.call_args_list[0][0][0]['sell_reason'] == ExitType.EMERGENCY_EXIT.value
|
||||
assert rpc_mock.call_args_list[0][0][0]['exit_reason'] == ExitType.EMERGENCY_EXIT.value
|
||||
assert rpc_mock.call_args_list[0][0][0]['order_type'] == 'market'
|
||||
assert rpc_mock.call_args_list[0][0][0]['type'] == 'exit'
|
||||
assert rpc_mock.call_args_list[1][0][0]['type'] == 'exit_fill'
|
||||
@@ -1606,12 +1616,15 @@ def test_create_stoploss_order_insufficient_funds(
|
||||
])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_handle_stoploss_on_exchange_trailing(
|
||||
mocker, default_conf_usdt, fee, is_short, bid, ask, limit_order, stop_price, hang_price
|
||||
mocker, default_conf_usdt, fee, is_short, bid, ask, limit_order, stop_price, hang_price,
|
||||
time_machine,
|
||||
) -> None:
|
||||
# When trailing stoploss is set
|
||||
enter_order = limit_order[entry_side(is_short)]
|
||||
exit_order = limit_order[exit_side(is_short)]
|
||||
stoploss = MagicMock(return_value={'id': 13434334, 'status': 'open'})
|
||||
stoploss = MagicMock(return_value={'id': '13434334', 'status': 'open'})
|
||||
start_dt = dt_now()
|
||||
time_machine.move_to(start_dt, tick=False)
|
||||
patch_RPCManager(mocker)
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
@@ -1685,6 +1698,8 @@ def test_handle_stoploss_on_exchange_trailing(
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
|
||||
assert trade.stoploss_order_id == '13434334'
|
||||
|
||||
# price jumped 2x
|
||||
mocker.patch(
|
||||
f'{EXMS}.fetch_ticker',
|
||||
@@ -1706,16 +1721,15 @@ def test_handle_stoploss_on_exchange_trailing(
|
||||
cancel_order_mock.assert_not_called()
|
||||
stoploss_order_mock.assert_not_called()
|
||||
|
||||
# Move time by 10s ... so stoploss order should be replaced.
|
||||
time_machine.move_to(start_dt + timedelta(minutes=10), tick=False)
|
||||
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
assert trade.stop_loss == stop_price[1]
|
||||
trade.stoploss_order_id = '100'
|
||||
|
||||
# setting stoploss_on_exchange_interval to 0 seconds
|
||||
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
|
||||
cancel_order_mock.assert_called_once_with('100', 'ETH/USDT')
|
||||
cancel_order_mock.assert_called_once_with('13434334', 'ETH/USDT')
|
||||
stoploss_order_mock.assert_called_once_with(
|
||||
amount=30,
|
||||
pair='ETH/USDT',
|
||||
@@ -2228,6 +2242,7 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_order, is_short, ca
|
||||
order = limit_order[entry_side(is_short)]
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_enter')
|
||||
mocker.patch(f'{EXMS}.fetch_order', return_value=order)
|
||||
mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[])
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=0.0)
|
||||
@@ -2297,6 +2312,7 @@ def test_update_trade_state_withorderdict(
|
||||
order_id = "oid_123456"
|
||||
order['id'] = order_id
|
||||
mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order)
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_enter')
|
||||
# fetch_order should not be called!!
|
||||
mocker.patch(f'{EXMS}.fetch_order', MagicMock(side_effect=ValueError))
|
||||
patch_exchange(mocker)
|
||||
@@ -2340,6 +2356,7 @@ def test_update_trade_state_exception(mocker, default_conf_usdt, is_short, limit
|
||||
order = limit_order[entry_side(is_short)]
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
mocker.patch(f'{EXMS}.fetch_order', return_value=order)
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_enter')
|
||||
|
||||
# TODO: should not be magicmock
|
||||
trade = MagicMock()
|
||||
@@ -3486,7 +3503,7 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'kraken', 'bittrex'],
|
||||
@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'kraken', 'bybit'],
|
||||
indirect=['limit_buy_order_canceled_empty'])
|
||||
def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_short, fee,
|
||||
limit_buy_order_canceled_empty) -> None:
|
||||
@@ -3767,9 +3784,9 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
|
||||
'profit_amount': 0.29554455 if is_short else 5.685,
|
||||
'profit_ratio': 0.00493809 if is_short else 0.09451372,
|
||||
'stake_currency': 'USDT',
|
||||
'quote_currency': 'USDT',
|
||||
'fiat_currency': 'USD',
|
||||
'base_currency': 'ETH',
|
||||
'sell_reason': ExitType.ROI.value,
|
||||
'exit_reason': ExitType.ROI.value,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
@@ -3777,6 +3794,8 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
|
||||
'sub_trade': False,
|
||||
'cumulative_profit': 0.0,
|
||||
'stake_amount': pytest.approx(60),
|
||||
'is_final_exit': False,
|
||||
'final_profit_ratio': None,
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -3832,9 +3851,9 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
|
||||
'profit_amount': -5.65990099 if is_short else -0.00075,
|
||||
'profit_ratio': -0.0945681 if is_short else -1.247e-05,
|
||||
'stake_currency': 'USDT',
|
||||
'quote_currency': 'USDT',
|
||||
'base_currency': 'ETH',
|
||||
'fiat_currency': 'USD',
|
||||
'sell_reason': ExitType.STOP_LOSS.value,
|
||||
'exit_reason': ExitType.STOP_LOSS.value,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
@@ -3842,6 +3861,8 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
|
||||
'sub_trade': False,
|
||||
'cumulative_profit': 0.0,
|
||||
'stake_amount': pytest.approx(60),
|
||||
'is_final_exit': False,
|
||||
'final_profit_ratio': None,
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -3918,9 +3939,9 @@ def test_execute_trade_exit_custom_exit_price(
|
||||
'profit_amount': pytest.approx(profit_amount),
|
||||
'profit_ratio': profit_ratio,
|
||||
'stake_currency': 'USDT',
|
||||
'quote_currency': 'USDT',
|
||||
'base_currency': 'ETH',
|
||||
'fiat_currency': 'USD',
|
||||
'sell_reason': 'foo',
|
||||
'exit_reason': 'foo',
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
@@ -3928,6 +3949,8 @@ def test_execute_trade_exit_custom_exit_price(
|
||||
'sub_trade': False,
|
||||
'cumulative_profit': 0.0,
|
||||
'stake_amount': pytest.approx(60),
|
||||
'is_final_exit': False,
|
||||
'final_profit_ratio': None,
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -3991,9 +4014,9 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
|
||||
'profit_amount': -0.3 if is_short else -0.8985,
|
||||
'profit_ratio': -0.00501253 if is_short else -0.01493766,
|
||||
'stake_currency': 'USDT',
|
||||
'quote_currency': 'USDT',
|
||||
'fiat_currency': 'USD',
|
||||
'base_currency': 'ETH',
|
||||
'sell_reason': ExitType.STOP_LOSS.value,
|
||||
'exit_reason': ExitType.STOP_LOSS.value,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
@@ -4001,6 +4024,8 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
|
||||
'sub_trade': False,
|
||||
'cumulative_profit': 0.0,
|
||||
'stake_amount': pytest.approx(60),
|
||||
'is_final_exit': False,
|
||||
'final_profit_ratio': None,
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -4257,9 +4282,9 @@ def test_execute_trade_exit_market_order(
|
||||
'profit_amount': pytest.approx(profit_amount),
|
||||
'profit_ratio': profit_ratio,
|
||||
'stake_currency': 'USDT',
|
||||
'quote_currency': 'USDT',
|
||||
'base_currency': 'ETH',
|
||||
'fiat_currency': 'USD',
|
||||
'sell_reason': ExitType.ROI.value,
|
||||
'exit_reason': ExitType.ROI.value,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
@@ -4267,7 +4292,8 @@ def test_execute_trade_exit_market_order(
|
||||
'sub_trade': False,
|
||||
'cumulative_profit': 0.0,
|
||||
'stake_amount': pytest.approx(60),
|
||||
|
||||
'is_final_exit': False,
|
||||
'final_profit_ratio': None,
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -6699,11 +6725,15 @@ def test_check_and_call_adjust_trade_position(mocker, default_conf_usdt, fee, ca
|
||||
)
|
||||
create_mock_trades(fee)
|
||||
caplog.set_level(logging.DEBUG)
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=10)
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(10, 'aaaa'))
|
||||
freqtrade.process_open_trade_positions()
|
||||
assert log_has_re(r"Max adjustment entries for .* has been reached\.", caplog)
|
||||
assert freqtrade.strategy.adjust_trade_position.call_count == 1
|
||||
|
||||
caplog.clear()
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-10)
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(-10, 'partial_exit_c'))
|
||||
freqtrade.process_open_trade_positions()
|
||||
assert log_has_re(r"LIMIT_SELL has been fulfilled.*", caplog)
|
||||
assert freqtrade.strategy.adjust_trade_position.call_count == 1
|
||||
trade = Trade.get_trades(trade_filter=[Trade.id == 5]).first()
|
||||
assert trade.orders[-1].ft_order_tag == 'partial_exit_c'
|
||||
|
||||
+25
-11
@@ -11,8 +11,7 @@ from freqtrade.rpc.rpc import RPC
|
||||
from tests.conftest import EXMS, get_patched_freqtradebot, log_has_re, patch_get_signal
|
||||
|
||||
|
||||
def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
limit_buy_order, mocker) -> None:
|
||||
def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, mocker) -> None:
|
||||
"""
|
||||
Tests workflow of selling stoploss_on_exchange.
|
||||
Sells
|
||||
@@ -537,7 +536,7 @@ def test_dca_order_adjust_entry_replace_fails(
|
||||
# Create DCA order for 2nd trade (so we have 2 open orders on 2 trades)
|
||||
# this 2nd order won't fill.
|
||||
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=20)
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(20, 'PeNF'))
|
||||
|
||||
freqtrade.process()
|
||||
|
||||
@@ -628,12 +627,13 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
|
||||
assert log_has_re(
|
||||
r"Remaining amount of \d\.\d+.* would be smaller than the minimum of 10.", caplog)
|
||||
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-20)
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(-20, 'PES'))
|
||||
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 2
|
||||
assert trade.orders[-1].ft_order_side == 'sell'
|
||||
assert trade.orders[-1].ft_order_tag == 'PES'
|
||||
assert pytest.approx(trade.stake_amount) == 40.198
|
||||
assert pytest.approx(trade.amount) == 20.099 * leverage
|
||||
assert trade.open_rate == 2.0
|
||||
@@ -650,28 +650,42 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
|
||||
caplog.clear()
|
||||
|
||||
# Sell more than what we got (we got ~20 coins left)
|
||||
# First adjusts the amount to 20 - then rejects.
|
||||
# Doesn't exit, as the amount is too high.
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-50)
|
||||
freqtrade.process()
|
||||
assert log_has_re("Adjusting amount to trade.amount as it is higher.*", caplog)
|
||||
assert log_has_re("Remaining amount of 0.0 would be smaller than the minimum of 10.", caplog)
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 2
|
||||
|
||||
# Amount too low...
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-(trade.stake_amount * 0.99))
|
||||
freqtrade.process()
|
||||
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 2
|
||||
|
||||
# Amount exactly comes out as exactly 0
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(
|
||||
return_value=-(trade.amount / trade.leverage * 2.02))
|
||||
freqtrade.process()
|
||||
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 3
|
||||
|
||||
assert trade.orders[-1].ft_order_side == 'sell'
|
||||
assert pytest.approx(trade.stake_amount) == 40.198
|
||||
assert trade.is_open
|
||||
assert trade.is_open is False
|
||||
|
||||
# use amount that would trunc to 0.0 once selling
|
||||
mocker.patch(f"{EXMS}.amount_to_contract_precision", lambda s, p, v: round(v, 1))
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-0.01)
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 2
|
||||
assert len(trade.orders) == 3
|
||||
assert trade.orders[-1].ft_order_side == 'sell'
|
||||
assert pytest.approx(trade.stake_amount) == 40.198
|
||||
assert trade.is_open
|
||||
assert trade.is_open is False
|
||||
assert log_has_re('Amount to exit is 0.0 due to exchange limits - not exiting.', caplog)
|
||||
expected_profit = starting_amount - 40.1980 + trade.realized_profit
|
||||
expected_profit = starting_amount - 60 + trade.realized_profit
|
||||
assert pytest.approx(freqtrade.wallets.get_free('USDT')) == expected_profit
|
||||
if spot:
|
||||
assert pytest.approx(freqtrade.wallets.get_total('USDT')) == expected_profit
|
||||
|
||||
@@ -63,9 +63,9 @@ def test_set_loggers_syslog():
|
||||
setup_logging_pre()
|
||||
setup_logging(config)
|
||||
assert len(logger.handlers) == 3
|
||||
assert [x for x in logger.handlers if type(x) == logging.handlers.SysLogHandler]
|
||||
assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
|
||||
assert [x for x in logger.handlers if type(x) == FTBufferingHandler]
|
||||
assert [x for x in logger.handlers if isinstance(x, logging.handlers.SysLogHandler)]
|
||||
assert [x for x in logger.handlers if isinstance(x, FTStdErrStreamHandler)]
|
||||
assert [x for x in logger.handlers if isinstance(x, FTBufferingHandler)]
|
||||
# setting up logging again should NOT cause the loggers to be added a second time.
|
||||
setup_logging(config)
|
||||
assert len(logger.handlers) == 3
|
||||
@@ -86,9 +86,9 @@ def test_set_loggers_Filehandler(tmp_path):
|
||||
setup_logging_pre()
|
||||
setup_logging(config)
|
||||
assert len(logger.handlers) == 3
|
||||
assert [x for x in logger.handlers if type(x) == logging.handlers.RotatingFileHandler]
|
||||
assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
|
||||
assert [x for x in logger.handlers if type(x) == FTBufferingHandler]
|
||||
assert [x for x in logger.handlers if isinstance(x, logging.handlers.RotatingFileHandler)]
|
||||
assert [x for x in logger.handlers if isinstance(x, FTStdErrStreamHandler)]
|
||||
assert [x for x in logger.handlers if isinstance(x, FTBufferingHandler)]
|
||||
# setting up logging again should NOT cause the loggers to be added a second time.
|
||||
setup_logging(config)
|
||||
assert len(logger.handlers) == 3
|
||||
@@ -112,7 +112,7 @@ def test_set_loggers_journald(mocker):
|
||||
setup_logging(config)
|
||||
assert len(logger.handlers) == 3
|
||||
assert [x for x in logger.handlers if type(x).__name__ == "JournaldLogHandler"]
|
||||
assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
|
||||
assert [x for x in logger.handlers if isinstance(x, FTStdErrStreamHandler)]
|
||||
# reset handlers to not break pytest
|
||||
logger.handlers = orig_handlers
|
||||
|
||||
|
||||
+10
-10
@@ -67,12 +67,12 @@ def test_main_fatal_exception(mocker, default_conf, caplog) -> None:
|
||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock())
|
||||
|
||||
args = ['trade', '-c', 'config_examples/config_bittrex.example.json']
|
||||
args = ['trade', '-c', 'tests/testdata/testconfigs/main_test_config.json']
|
||||
|
||||
# Test Main + the KeyboardInterrupt exception
|
||||
with pytest.raises(SystemExit):
|
||||
main(args)
|
||||
assert log_has('Using config: config_examples/config_bittrex.example.json ...', caplog)
|
||||
assert log_has('Using config: tests/testdata/testconfigs/main_test_config.json ...', caplog)
|
||||
assert log_has('Fatal exception!', caplog)
|
||||
|
||||
|
||||
@@ -85,12 +85,12 @@ def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None:
|
||||
mocker.patch('freqtrade.wallets.Wallets.update', MagicMock())
|
||||
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock())
|
||||
|
||||
args = ['trade', '-c', 'config_examples/config_bittrex.example.json']
|
||||
args = ['trade', '-c', 'tests/testdata/testconfigs/main_test_config.json']
|
||||
|
||||
# Test Main + the KeyboardInterrupt exception
|
||||
with pytest.raises(SystemExit):
|
||||
main(args)
|
||||
assert log_has('Using config: config_examples/config_bittrex.example.json ...', caplog)
|
||||
assert log_has('Using config: tests/testdata/testconfigs/main_test_config.json ...', caplog)
|
||||
assert log_has('SIGINT received, aborting ...', caplog)
|
||||
|
||||
|
||||
@@ -106,12 +106,12 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None:
|
||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock())
|
||||
|
||||
args = ['trade', '-c', 'config_examples/config_bittrex.example.json']
|
||||
args = ['trade', '-c', 'tests/testdata/testconfigs/main_test_config.json']
|
||||
|
||||
# Test Main + the KeyboardInterrupt exception
|
||||
with pytest.raises(SystemExit):
|
||||
main(args)
|
||||
assert log_has('Using config: config_examples/config_bittrex.example.json ...', caplog)
|
||||
assert log_has('Using config: tests/testdata/testconfigs/main_test_config.json ...', caplog)
|
||||
assert log_has('Oh snap!', caplog)
|
||||
|
||||
|
||||
@@ -160,13 +160,13 @@ def test_main_reload_config(mocker, default_conf, caplog) -> None:
|
||||
args = Arguments([
|
||||
'trade',
|
||||
'-c',
|
||||
'config_examples/config_bittrex.example.json'
|
||||
'tests/testdata/testconfigs/main_test_config.json'
|
||||
]).get_parsed_arg()
|
||||
worker = Worker(args=args, config=default_conf)
|
||||
with pytest.raises(SystemExit):
|
||||
main(['trade', '-c', 'config_examples/config_bittrex.example.json'])
|
||||
main(['trade', '-c', 'tests/testdata/testconfigs/main_test_config.json'])
|
||||
|
||||
assert log_has('Using config: config_examples/config_bittrex.example.json ...', caplog)
|
||||
assert log_has('Using config: tests/testdata/testconfigs/main_test_config.json ...', caplog)
|
||||
assert worker_mock.call_count == 4
|
||||
assert reconfigure_mock.call_count == 1
|
||||
assert isinstance(worker.freqtrade, FreqtradeBot)
|
||||
@@ -187,7 +187,7 @@ def test_reconfigure(mocker, default_conf) -> None:
|
||||
args = Arguments([
|
||||
'trade',
|
||||
'-c',
|
||||
'config_examples/config_bittrex.example.json'
|
||||
'tests/testdata/testconfigs/main_test_config.json'
|
||||
]).get_parsed_arg()
|
||||
worker = Worker(args=args, config=default_conf)
|
||||
freqtrade = worker.freqtrade
|
||||
|
||||
+3
-27
@@ -7,36 +7,12 @@ from unittest.mock import MagicMock
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from freqtrade.misc import (dataframe_to_json, decimals_per_coin, deep_merge_dicts, file_dump_json,
|
||||
file_load_json, is_file_in_dir, json_to_dataframe, pair_to_filename,
|
||||
parse_db_uri_for_logging, plural, round_coin_value, safe_value_fallback,
|
||||
from freqtrade.misc import (dataframe_to_json, deep_merge_dicts, file_dump_json, file_load_json,
|
||||
is_file_in_dir, json_to_dataframe, pair_to_filename,
|
||||
parse_db_uri_for_logging, plural, safe_value_fallback,
|
||||
safe_value_fallback2)
|
||||
|
||||
|
||||
def test_decimals_per_coin():
|
||||
assert decimals_per_coin('USDT') == 3
|
||||
assert decimals_per_coin('EUR') == 3
|
||||
assert decimals_per_coin('BTC') == 8
|
||||
assert decimals_per_coin('ETH') == 5
|
||||
|
||||
|
||||
def test_round_coin_value():
|
||||
assert round_coin_value(222.222222, 'USDT') == '222.222 USDT'
|
||||
assert round_coin_value(222.2, 'USDT', keep_trailing_zeros=True) == '222.200 USDT'
|
||||
assert round_coin_value(222.2, 'USDT') == '222.2 USDT'
|
||||
assert round_coin_value(222.12745, 'EUR') == '222.127 EUR'
|
||||
assert round_coin_value(0.1274512123, 'BTC') == '0.12745121 BTC'
|
||||
assert round_coin_value(0.1274512123, 'ETH') == '0.12745 ETH'
|
||||
|
||||
assert round_coin_value(222.222222, 'USDT', False) == '222.222'
|
||||
assert round_coin_value(222.2, 'USDT', False) == '222.2'
|
||||
assert round_coin_value(222.00, 'USDT', False) == '222'
|
||||
assert round_coin_value(222.12745, 'EUR', False) == '222.127'
|
||||
assert round_coin_value(0.1274512123, 'BTC', False) == '0.12745121'
|
||||
assert round_coin_value(0.1274512123, 'ETH', False) == '0.12745'
|
||||
assert round_coin_value(222.2, 'USDT', False, True) == '222.200'
|
||||
|
||||
|
||||
def test_file_dump_json(mocker) -> None:
|
||||
file_open = mocker.patch('freqtrade.misc.Path.open', MagicMock())
|
||||
json_dump = mocker.patch('rapidjson.dump', MagicMock())
|
||||
|
||||
@@ -377,7 +377,7 @@ def test_start_plot_dataframe(mocker):
|
||||
aup = mocker.patch("freqtrade.plot.plotting.load_and_plot_trades", MagicMock())
|
||||
args = [
|
||||
"plot-dataframe",
|
||||
"--config", "config_examples/config_bittrex.example.json",
|
||||
"--config", "tests/testdata/testconfigs/main_test_config.json",
|
||||
"--pairs", "ETH/BTC"
|
||||
]
|
||||
start_plot_dataframe(get_args(args))
|
||||
@@ -420,7 +420,7 @@ def test_start_plot_profit(mocker):
|
||||
aup = mocker.patch("freqtrade.plot.plotting.plot_profit", MagicMock())
|
||||
args = [
|
||||
"plot-profit",
|
||||
"--config", "config_examples/config_bittrex.example.json",
|
||||
"--config", "tests/testdata/testconfigs/main_test_config.json",
|
||||
"--pairs", "ETH/BTC"
|
||||
]
|
||||
start_plot_profit(get_args(args))
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"max_open_trades": 3,
|
||||
"stake_currency": "BTC",
|
||||
"stake_amount": 0.05,
|
||||
"tradable_balance_ratio": 0.99,
|
||||
"fiat_display_currency": "USD",
|
||||
"timeframe": "5m",
|
||||
"dry_run": true,
|
||||
"cancel_open_orders_on_exit": false,
|
||||
"unfilledtimeout": {
|
||||
"entry": 10,
|
||||
"exit": 10,
|
||||
"exit_timeout_count": 0,
|
||||
"unit": "minutes"
|
||||
},
|
||||
"entry_pricing": {
|
||||
"price_side": "same",
|
||||
"use_order_book": true,
|
||||
"order_book_top": 1,
|
||||
"price_last_balance": 0.0,
|
||||
"check_depth_of_market": {
|
||||
"enabled": false,
|
||||
"bids_to_ask_delta": 1
|
||||
}
|
||||
},
|
||||
"exit_pricing":{
|
||||
"price_side": "same",
|
||||
"use_order_book": true,
|
||||
"order_book_top": 1
|
||||
},
|
||||
"exchange": {
|
||||
"name": "binance",
|
||||
"key": "your_exchange_key",
|
||||
"secret": "your_exchange_secret",
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {},
|
||||
"pair_whitelist": [
|
||||
"ETH/BTC",
|
||||
"LTC/BTC",
|
||||
"ETC/BTC",
|
||||
"RVN/BTC",
|
||||
"CRO/BTC",
|
||||
"XLM/BTC",
|
||||
"XRP/BTC",
|
||||
"TRX/BTC",
|
||||
"ADA/BTC",
|
||||
"DOT/BTC"
|
||||
],
|
||||
"pair_blacklist": [
|
||||
"DOGE/BTC"
|
||||
]
|
||||
},
|
||||
"pairlists": [
|
||||
{"method": "StaticPairList"}
|
||||
],
|
||||
"telegram": {
|
||||
"enabled": false,
|
||||
"token": "your_telegram_token",
|
||||
"chat_id": "your_telegram_chat_id"
|
||||
},
|
||||
"api_server": {
|
||||
"enabled": false,
|
||||
"listen_ip_address": "127.0.0.1",
|
||||
"listen_port": 8080,
|
||||
"verbosity": "error",
|
||||
"jwt_secret_key": "somethingrandom",
|
||||
"CORS_origins": [],
|
||||
"username": "freqtrader",
|
||||
"password": "SuperSecurePassword"
|
||||
},
|
||||
"bot_name": "freqtrade",
|
||||
"initial_state": "running",
|
||||
"force_entry_enable": false,
|
||||
"internals": {
|
||||
"process_throttle_secs": 5
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
|
||||
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.util.binance_mig import migrate_binance_futures_data, migrate_binance_futures_names
|
||||
from freqtrade.util.migrations import (migrate_binance_futures_data, migrate_binance_futures_names,
|
||||
migrate_data)
|
||||
from tests.conftest import create_mock_trades_usdt, log_has
|
||||
|
||||
|
||||
@@ -55,3 +54,13 @@ def test_binance_mig_db_conversion(default_conf_usdt, fee, caplog):
|
||||
default_conf_usdt['trading_mode'] = 'futures'
|
||||
migrate_binance_futures_names(default_conf_usdt)
|
||||
assert log_has('Migrating binance futures pairs in database.', caplog)
|
||||
|
||||
|
||||
def test_migration_wrapper(default_conf_usdt, mocker):
|
||||
default_conf_usdt['trading_mode'] = 'futures'
|
||||
binmock = mocker.patch('freqtrade.util.migrations.migrate_binance_futures_data')
|
||||
funding_mock = mocker.patch('freqtrade.util.migrations.migrate_funding_fee_timeframe')
|
||||
migrate_data(default_conf_usdt)
|
||||
|
||||
assert binmock.call_count == 1
|
||||
assert funding_mock.call_count == 1
|
||||
@@ -0,0 +1,37 @@
|
||||
from freqtrade.util import decimals_per_coin, fmt_coin, round_value
|
||||
|
||||
|
||||
def test_decimals_per_coin():
|
||||
assert decimals_per_coin('USDT') == 3
|
||||
assert decimals_per_coin('EUR') == 3
|
||||
assert decimals_per_coin('BTC') == 8
|
||||
assert decimals_per_coin('ETH') == 5
|
||||
|
||||
|
||||
def test_fmt_coin():
|
||||
assert fmt_coin(222.222222, 'USDT') == '222.222 USDT'
|
||||
assert fmt_coin(222.2, 'USDT', keep_trailing_zeros=True) == '222.200 USDT'
|
||||
assert fmt_coin(222.2, 'USDT') == '222.2 USDT'
|
||||
assert fmt_coin(222.12745, 'EUR') == '222.127 EUR'
|
||||
assert fmt_coin(0.1274512123, 'BTC') == '0.12745121 BTC'
|
||||
assert fmt_coin(0.1274512123, 'ETH') == '0.12745 ETH'
|
||||
|
||||
assert fmt_coin(222.222222, 'USDT', False) == '222.222'
|
||||
assert fmt_coin(222.2, 'USDT', False) == '222.2'
|
||||
assert fmt_coin(222.00, 'USDT', False) == '222'
|
||||
assert fmt_coin(222.12745, 'EUR', False) == '222.127'
|
||||
assert fmt_coin(0.1274512123, 'BTC', False) == '0.12745121'
|
||||
assert fmt_coin(0.1274512123, 'ETH', False) == '0.12745'
|
||||
assert fmt_coin(222.2, 'USDT', False, True) == '222.200'
|
||||
|
||||
|
||||
def test_round_value():
|
||||
|
||||
assert round_value(222.222222, 3) == '222.222'
|
||||
assert round_value(222.2, 3) == '222.2'
|
||||
assert round_value(222.00, 3) == '222'
|
||||
assert round_value(222.12745, 3) == '222.127'
|
||||
assert round_value(0.1274512123, 8) == '0.12745121'
|
||||
assert round_value(0.1274512123, 5) == '0.12745'
|
||||
assert round_value(222.2, 3, True) == '222.200'
|
||||
assert round_value(222.2, 0, True) == '222'
|
||||
@@ -0,0 +1,29 @@
|
||||
from shutil import copytree
|
||||
|
||||
from freqtrade.util.migrations import migrate_funding_fee_timeframe
|
||||
|
||||
|
||||
def test_migrate_funding_rate_timeframe(default_conf_usdt, tmp_path, testdatadir):
|
||||
|
||||
copytree(testdatadir / 'futures', tmp_path / 'futures')
|
||||
file_4h = tmp_path / 'futures' / 'XRP_USDT_USDT-4h-funding_rate.feather'
|
||||
file_8h = tmp_path / 'futures' / 'XRP_USDT_USDT-8h-funding_rate.feather'
|
||||
file_1h = tmp_path / 'futures' / 'XRP_USDT_USDT-1h-futures.feather'
|
||||
file_8h.rename(file_4h)
|
||||
assert file_1h.exists()
|
||||
assert file_4h.exists()
|
||||
assert not file_8h.exists()
|
||||
|
||||
default_conf_usdt['datadir'] = tmp_path
|
||||
|
||||
# Inactive on spot trading ...
|
||||
migrate_funding_fee_timeframe(default_conf_usdt, None)
|
||||
|
||||
default_conf_usdt['trading_mode'] = 'futures'
|
||||
|
||||
migrate_funding_fee_timeframe(default_conf_usdt, None)
|
||||
|
||||
assert not file_4h.exists()
|
||||
assert file_8h.exists()
|
||||
# futures files is untouched.
|
||||
assert file_1h.exists()
|
||||
Reference in New Issue
Block a user