mirror of
https://github.com/freqtrade/freqtrade.git
synced 2026-04-28 21:10:12 +00:00
Merge branch 'develop' into feature/fetch-public-trades
This commit is contained in:
@@ -54,6 +54,7 @@ from tests.conftest import (
|
||||
patch_exchange,
|
||||
patched_configuration_load_config_file,
|
||||
)
|
||||
from tests.conftest_hyperopt import hyperopt_test_result
|
||||
from tests.conftest_trades import MOCK_TRADE_COUNT
|
||||
|
||||
|
||||
@@ -1137,7 +1138,8 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
|
||||
pytest.fail(f"Expected well formed JSON, but failed to parse: {captured.out}")
|
||||
|
||||
|
||||
def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results, tmp_path):
|
||||
def test_hyperopt_list(mocker, capsys, caplog, tmp_path):
|
||||
saved_hyperopt_results = hyperopt_test_result()
|
||||
csv_file = tmp_path / "test.csv"
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt_tools.HyperoptTools._test_hyperopt_results_exist",
|
||||
@@ -1507,7 +1509,8 @@ def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results, tmp_path)
|
||||
csv_file.unlink()
|
||||
|
||||
|
||||
def test_hyperopt_show(mocker, capsys, saved_hyperopt_results):
|
||||
def test_hyperopt_show(mocker, capsys):
|
||||
saved_hyperopt_results = hyperopt_test_result()
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.hyperopt_tools.HyperoptTools._test_hyperopt_results_exist",
|
||||
return_value=True,
|
||||
|
||||
1002
tests/conftest.py
1002
tests/conftest.py
File diff suppressed because it is too large
Load Diff
1000
tests/conftest_hyperopt.py
Normal file
1000
tests/conftest_hyperopt.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -343,17 +343,15 @@ def test_create_cum_profit1(testdatadir):
|
||||
def test_calculate_max_drawdown(testdatadir):
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
_, hdate, lowdate, hval, lval, drawdown = calculate_max_drawdown(
|
||||
bt_data, value_col="profit_abs"
|
||||
)
|
||||
assert isinstance(drawdown, float)
|
||||
assert pytest.approx(drawdown) == 0.29753914
|
||||
assert isinstance(hdate, Timestamp)
|
||||
assert isinstance(lowdate, Timestamp)
|
||||
assert isinstance(hval, float)
|
||||
assert isinstance(lval, float)
|
||||
assert hdate == Timestamp("2018-01-16 19:30:00", tz="UTC")
|
||||
assert lowdate == Timestamp("2018-01-16 22:25:00", tz="UTC")
|
||||
drawdown = calculate_max_drawdown(bt_data, value_col="profit_abs")
|
||||
assert isinstance(drawdown.relative_account_drawdown, float)
|
||||
assert pytest.approx(drawdown.relative_account_drawdown) == 0.29753914
|
||||
assert isinstance(drawdown.high_date, Timestamp)
|
||||
assert isinstance(drawdown.low_date, Timestamp)
|
||||
assert isinstance(drawdown.high_value, float)
|
||||
assert isinstance(drawdown.low_value, float)
|
||||
assert drawdown.high_date == Timestamp("2018-01-16 19:30:00", tz="UTC")
|
||||
assert drawdown.low_date == Timestamp("2018-01-16 22:25:00", tz="UTC")
|
||||
|
||||
underwater = calculate_underwater(bt_data)
|
||||
assert isinstance(underwater, DataFrame)
|
||||
@@ -509,19 +507,20 @@ def test_calculate_max_drawdown2():
|
||||
# sort by profit and reset index
|
||||
df = df.sort_values("profit").reset_index(drop=True)
|
||||
df1 = df.copy()
|
||||
drawdown, hdate, ldate, hval, lval, drawdown_rel = calculate_max_drawdown(
|
||||
df, date_col="open_date", value_col="profit"
|
||||
drawdown = calculate_max_drawdown(
|
||||
df, date_col="open_date", starting_balance=0.2, value_col="profit"
|
||||
)
|
||||
# Ensure df has not been altered.
|
||||
assert df.equals(df1)
|
||||
|
||||
assert isinstance(drawdown, float)
|
||||
assert isinstance(drawdown_rel, float)
|
||||
assert isinstance(drawdown.drawdown_abs, float)
|
||||
assert isinstance(drawdown.relative_account_drawdown, float)
|
||||
# High must be before low
|
||||
assert hdate < ldate
|
||||
assert drawdown.high_date < drawdown.low_date
|
||||
# High value must be higher than low value
|
||||
assert hval > lval
|
||||
assert drawdown == 0.091755
|
||||
assert drawdown.high_value > drawdown.low_value
|
||||
assert drawdown.drawdown_abs == 0.091755
|
||||
assert pytest.approx(drawdown.relative_account_drawdown) == 0.32129575
|
||||
|
||||
df = DataFrame(zip(values[:5], dates[:5]), columns=["profit", "open_date"])
|
||||
with pytest.raises(ValueError, match="No losing trade, therefore no drawdown."):
|
||||
@@ -530,10 +529,8 @@ def test_calculate_max_drawdown2():
|
||||
df1 = DataFrame(zip(values[:5], dates[:5]), columns=["profit", "open_date"])
|
||||
df1.loc[:, "profit"] = df1["profit"] * -1
|
||||
# No winning trade ...
|
||||
drawdown, hdate, ldate, hval, lval, drawdown_rel = calculate_max_drawdown(
|
||||
df1, date_col="open_date", value_col="profit"
|
||||
)
|
||||
assert drawdown == 0.043965
|
||||
drawdown = calculate_max_drawdown(df1, date_col="open_date", value_col="profit")
|
||||
assert drawdown.drawdown_abs == 0.043965
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -555,20 +552,20 @@ def test_calculate_max_drawdown_abs(profits, relative, highd, lowdays, result, r
|
||||
# sort by profit and reset index
|
||||
df = df.sort_values("profit_abs").reset_index(drop=True)
|
||||
df1 = df.copy()
|
||||
drawdown, hdate, ldate, hval, lval, drawdown_rel = calculate_max_drawdown(
|
||||
drawdown = calculate_max_drawdown(
|
||||
df, date_col="open_date", starting_balance=1000, relative=relative
|
||||
)
|
||||
# Ensure df has not been altered.
|
||||
assert df.equals(df1)
|
||||
|
||||
assert isinstance(drawdown, float)
|
||||
assert isinstance(drawdown_rel, float)
|
||||
assert hdate == init_date + timedelta(days=highd)
|
||||
assert ldate == init_date + timedelta(days=lowdays)
|
||||
assert isinstance(drawdown.drawdown_abs, float)
|
||||
assert isinstance(drawdown.relative_account_drawdown, float)
|
||||
assert drawdown.high_date == init_date + timedelta(days=highd)
|
||||
assert drawdown.low_date == init_date + timedelta(days=lowdays)
|
||||
|
||||
# High must be before low
|
||||
assert hdate < ldate
|
||||
assert drawdown.high_date < drawdown.low_date
|
||||
# High value must be higher than low value
|
||||
assert hval > lval
|
||||
assert drawdown == result
|
||||
assert pytest.approx(drawdown_rel) == result_rel
|
||||
assert drawdown.high_value > drawdown.low_value
|
||||
assert drawdown.drawdown_abs == result
|
||||
assert pytest.approx(drawdown.relative_account_drawdown) == result_rel
|
||||
|
||||
@@ -69,13 +69,19 @@ def test_download_data_main_trades(mocker):
|
||||
|
||||
assert dl_mock.call_args[1]["timerange"].starttype == "date"
|
||||
assert dl_mock.call_count == 1
|
||||
assert convert_mock.call_count == 1
|
||||
assert convert_mock.call_count == 0
|
||||
dl_mock.reset_mock()
|
||||
|
||||
config.update(
|
||||
{
|
||||
"download_trades": True,
|
||||
"trading_mode": "futures",
|
||||
"convert_trades": True,
|
||||
}
|
||||
)
|
||||
download_data_main(config)
|
||||
|
||||
assert dl_mock.call_args[1]["timerange"].starttype == "date"
|
||||
assert dl_mock.call_count == 1
|
||||
assert convert_mock.call_count == 1
|
||||
|
||||
|
||||
def test_download_data_main_data_invalid(mocker):
|
||||
|
||||
@@ -182,7 +182,7 @@ def test_remove_exchange_credentials(default_conf) -> None:
|
||||
|
||||
|
||||
def test_init_ccxt_kwargs(default_conf, mocker, caplog):
|
||||
mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={}))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
aei_mock = mocker.patch(f"{EXMS}.additional_exchange_init")
|
||||
|
||||
@@ -519,7 +519,7 @@ def test__load_async_markets(default_conf, mocker, caplog):
|
||||
mocker.patch(f"{EXMS}._init_ccxt")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}._load_markets")
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
exchange = Exchange(default_conf)
|
||||
@@ -528,28 +528,26 @@ def test__load_async_markets(default_conf, mocker, caplog):
|
||||
assert exchange._api_async.load_markets.call_count == 1
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
exchange._api_async.load_markets = Mock(side_effect=ccxt.BaseError("deadbeef"))
|
||||
exchange._load_async_markets()
|
||||
|
||||
assert log_has("Could not load async markets. Reason: deadbeef", caplog)
|
||||
exchange._api_async.load_markets = get_mock_coro(side_effect=ccxt.BaseError("deadbeef"))
|
||||
with pytest.raises(ccxt.BaseError, match="deadbeef"):
|
||||
exchange._load_async_markets()
|
||||
|
||||
|
||||
def test__load_markets(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
api_mock = MagicMock()
|
||||
api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError("SomeError"))
|
||||
api_mock.load_markets = get_mock_coro(side_effect=ccxt.BaseError("SomeError"))
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}._load_async_markets")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
Exchange(default_conf)
|
||||
assert log_has("Unable to initialize markets.", caplog)
|
||||
assert log_has("Could not load markets.", caplog)
|
||||
|
||||
expected_return = {"ETH/BTC": "available"}
|
||||
api_mock = MagicMock()
|
||||
api_mock.load_markets = MagicMock(return_value=expected_return)
|
||||
api_mock.load_markets = get_mock_coro(return_value=expected_return)
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
default_conf["exchange"]["pair_whitelist"] = ["ETH/BTC"]
|
||||
ex = Exchange(default_conf)
|
||||
@@ -564,12 +562,12 @@ def test_reload_markets(default_conf, mocker, caplog, time_machine):
|
||||
start_dt = dt_now()
|
||||
time_machine.move_to(start_dt, tick=False)
|
||||
api_mock = MagicMock()
|
||||
api_mock.load_markets = MagicMock(return_value=initial_markets)
|
||||
api_mock.load_markets = get_mock_coro(return_value=initial_markets)
|
||||
default_conf["exchange"]["markets_refresh_interval"] = 10
|
||||
exchange = get_patched_exchange(
|
||||
mocker, default_conf, api_mock, id="binance", mock_markets=False
|
||||
)
|
||||
exchange._load_async_markets = MagicMock()
|
||||
lam_spy = mocker.spy(exchange, "_load_async_markets")
|
||||
assert exchange._last_markets_refresh == dt_ts()
|
||||
|
||||
assert exchange.markets == initial_markets
|
||||
@@ -578,42 +576,45 @@ def test_reload_markets(default_conf, mocker, caplog, time_machine):
|
||||
# less than 10 minutes have passed, no reload
|
||||
exchange.reload_markets()
|
||||
assert exchange.markets == initial_markets
|
||||
assert exchange._load_async_markets.call_count == 0
|
||||
assert lam_spy.call_count == 0
|
||||
|
||||
api_mock.load_markets = MagicMock(return_value=updated_markets)
|
||||
api_mock.load_markets = get_mock_coro(return_value=updated_markets)
|
||||
# more than 10 minutes have passed, reload is executed
|
||||
time_machine.move_to(start_dt + timedelta(minutes=11), tick=False)
|
||||
exchange.reload_markets()
|
||||
assert exchange.markets == updated_markets
|
||||
assert exchange._load_async_markets.call_count == 1
|
||||
assert lam_spy.call_count == 1
|
||||
assert log_has("Performing scheduled market reload..", caplog)
|
||||
|
||||
# Not called again
|
||||
exchange._load_async_markets.reset_mock()
|
||||
lam_spy.reset_mock()
|
||||
|
||||
exchange.reload_markets()
|
||||
assert exchange._load_async_markets.call_count == 0
|
||||
assert lam_spy.call_count == 0
|
||||
|
||||
|
||||
def test_reload_markets_exception(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
api_mock = MagicMock()
|
||||
api_mock.load_markets = MagicMock(side_effect=ccxt.NetworkError("LoadError"))
|
||||
api_mock.load_markets = get_mock_coro(side_effect=ccxt.NetworkError("LoadError"))
|
||||
default_conf["exchange"]["markets_refresh_interval"] = 10
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance")
|
||||
exchange = get_patched_exchange(
|
||||
mocker, default_conf, api_mock, id="binance", mock_markets=False
|
||||
)
|
||||
|
||||
exchange._last_markets_refresh = 2
|
||||
# less than 10 minutes have passed, no reload
|
||||
exchange.reload_markets()
|
||||
assert exchange._last_markets_refresh == 0
|
||||
assert log_has_re(r"Could not reload markets.*", caplog)
|
||||
assert exchange._last_markets_refresh == 2
|
||||
assert log_has_re(r"Could not load markets\..*", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stake_currency", ["ETH", "BTC", "USDT"])
|
||||
def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog):
|
||||
default_conf["stake_currency"] = stake_currency
|
||||
api_mock = MagicMock()
|
||||
type(api_mock).load_markets = MagicMock(
|
||||
type(api_mock).load_markets = get_mock_coro(
|
||||
return_value={
|
||||
"ETH/BTC": {"quote": "BTC"},
|
||||
"LTC/BTC": {"quote": "BTC"},
|
||||
@@ -624,7 +625,6 @@ def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog):
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}._load_async_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
Exchange(default_conf)
|
||||
|
||||
@@ -632,7 +632,7 @@ def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog):
|
||||
def test_validate_stakecurrency_error(default_conf, mocker, caplog):
|
||||
default_conf["stake_currency"] = "XRP"
|
||||
api_mock = MagicMock()
|
||||
type(api_mock).load_markets = MagicMock(
|
||||
type(api_mock).load_markets = get_mock_coro(
|
||||
return_value={
|
||||
"ETH/BTC": {"quote": "BTC"},
|
||||
"LTC/BTC": {"quote": "BTC"},
|
||||
@@ -643,14 +643,13 @@ def test_validate_stakecurrency_error(default_conf, mocker, caplog):
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}._load_async_markets")
|
||||
with pytest.raises(
|
||||
ConfigurationError,
|
||||
match=r"XRP is not available as stake on .*Available currencies are: BTC, ETH, USDT",
|
||||
):
|
||||
Exchange(default_conf)
|
||||
|
||||
type(api_mock).load_markets = MagicMock(side_effect=ccxt.NetworkError("No connection."))
|
||||
type(api_mock).load_markets = get_mock_coro(side_effect=ccxt.NetworkError("No connection."))
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
|
||||
with pytest.raises(
|
||||
@@ -695,24 +694,26 @@ def test_get_pair_base_currency(default_conf, mocker, pair, expected):
|
||||
assert ex.get_pair_base_currency(pair) == expected
|
||||
|
||||
|
||||
def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly
|
||||
def test_validate_pairs(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
type(api_mock).load_markets = MagicMock(
|
||||
return_value={
|
||||
"ETH/BTC": {"quote": "BTC"},
|
||||
"LTC/BTC": {"quote": "BTC"},
|
||||
"XRP/BTC": {"quote": "BTC"},
|
||||
"NEO/BTC": {"quote": "BTC"},
|
||||
}
|
||||
)
|
||||
id_mock = PropertyMock(return_value="test_exchange")
|
||||
type(api_mock).id = id_mock
|
||||
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}._load_async_markets")
|
||||
mocker.patch(
|
||||
f"{EXMS}._load_async_markets",
|
||||
return_value={
|
||||
"ETH/BTC": {"quote": "BTC"},
|
||||
"LTC/BTC": {"quote": "BTC"},
|
||||
"XRP/BTC": {"quote": "BTC"},
|
||||
"NEO/BTC": {"quote": "BTC"},
|
||||
},
|
||||
)
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
# test exchange.validate_pairs directly
|
||||
# No assert - but this should not fail (!)
|
||||
Exchange(default_conf)
|
||||
|
||||
|
||||
@@ -752,7 +753,7 @@ def test_validate_pairs_exception(default_conf, mocker, caplog):
|
||||
|
||||
def test_validate_pairs_restricted(default_conf, mocker, caplog):
|
||||
api_mock = MagicMock()
|
||||
type(api_mock).load_markets = MagicMock(
|
||||
type(api_mock).load_markets = get_mock_coro(
|
||||
return_value={
|
||||
"ETH/BTC": {"quote": "BTC"},
|
||||
"LTC/BTC": {"quote": "BTC"},
|
||||
@@ -762,7 +763,6 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog):
|
||||
)
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}._load_async_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
|
||||
@@ -775,9 +775,9 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog):
|
||||
)
|
||||
|
||||
|
||||
def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog):
|
||||
def test_validate_pairs_stakecompatibility(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
type(api_mock).load_markets = MagicMock(
|
||||
type(api_mock).load_markets = get_mock_coro(
|
||||
return_value={
|
||||
"ETH/BTC": {"quote": "BTC"},
|
||||
"LTC/BTC": {"quote": "BTC"},
|
||||
@@ -788,17 +788,16 @@ def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog):
|
||||
)
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}._load_async_markets")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
|
||||
Exchange(default_conf)
|
||||
|
||||
|
||||
def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, caplog):
|
||||
def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
default_conf["stake_currency"] = ""
|
||||
type(api_mock).load_markets = MagicMock(
|
||||
type(api_mock).load_markets = get_mock_coro(
|
||||
return_value={
|
||||
"ETH/BTC": {"quote": "BTC"},
|
||||
"LTC/BTC": {"quote": "BTC"},
|
||||
@@ -809,7 +808,6 @@ def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, ca
|
||||
)
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}._load_async_markets")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
|
||||
@@ -817,10 +815,10 @@ def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, ca
|
||||
assert type(api_mock).load_markets.call_count == 1
|
||||
|
||||
|
||||
def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog):
|
||||
def test_validate_pairs_stakecompatibility_fail(default_conf, mocker):
|
||||
default_conf["exchange"]["pair_whitelist"].append("HELLO-WORLD")
|
||||
api_mock = MagicMock()
|
||||
type(api_mock).load_markets = MagicMock(
|
||||
type(api_mock).load_markets = get_mock_coro(
|
||||
return_value={
|
||||
"ETH/BTC": {"quote": "BTC"},
|
||||
"LTC/BTC": {"quote": "BTC"},
|
||||
@@ -831,7 +829,6 @@ def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog):
|
||||
)
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}._load_async_markets")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
|
||||
with pytest.raises(OperationalException, match=r"Stake-currency 'BTC' not compatible with.*"):
|
||||
@@ -848,7 +845,7 @@ def test_validate_timeframes(default_conf, mocker, timeframe):
|
||||
type(api_mock).timeframes = timeframes
|
||||
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={}))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
@@ -866,7 +863,7 @@ def test_validate_timeframes_failed(default_conf, mocker):
|
||||
type(api_mock).timeframes = timeframes
|
||||
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={}))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
@@ -896,7 +893,7 @@ def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker):
|
||||
del api_mock.timeframes
|
||||
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={}))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
with pytest.raises(
|
||||
@@ -918,7 +915,7 @@ def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker):
|
||||
del api_mock.timeframes
|
||||
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={"timeframes": None}))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs", MagicMock())
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
with pytest.raises(
|
||||
@@ -940,7 +937,7 @@ def test_validate_timeframes_not_in_config(default_conf, mocker):
|
||||
type(api_mock).timeframes = timeframes
|
||||
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={}))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
@@ -956,7 +953,7 @@ def test_validate_pricing(default_conf, mocker):
|
||||
}
|
||||
type(api_mock).has = PropertyMock(return_value=has)
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={}))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_trading_mode_and_margin_mode")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
@@ -992,7 +989,7 @@ def test_validate_ordertypes(default_conf, mocker):
|
||||
|
||||
type(api_mock).has = PropertyMock(return_value={"createMarketOrder": True})
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={}))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
@@ -1051,7 +1048,7 @@ def test_validate_ordertypes_stop_advanced(default_conf, mocker, exchange_name,
|
||||
default_conf["margin_mode"] = MarginMode.ISOLATED
|
||||
type(api_mock).has = PropertyMock(return_value={"createMarketOrder": True})
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={}))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
@@ -1076,7 +1073,7 @@ def test_validate_ordertypes_stop_advanced(default_conf, mocker, exchange_name,
|
||||
def test_validate_order_types_not_in_config(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={}))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
@@ -1948,7 +1945,9 @@ def test_fetch_trading_fees(default_conf, mocker):
|
||||
assert api_mock.fetch_trading_fees.call_count == 1
|
||||
|
||||
api_mock.fetch_trading_fees.reset_mock()
|
||||
|
||||
# Reload-markets calls fetch_trading_fees, too - so the explicit calls in the below
|
||||
# exception test would be called twice.
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
ccxt_exceptionhandlers(
|
||||
mocker, default_conf, api_mock, exchange_name, "fetch_trading_fees", "fetch_trading_fees"
|
||||
)
|
||||
|
||||
@@ -45,7 +45,25 @@ EXCHANGES = {
|
||||
"workingTime": 1674493798550,
|
||||
"fills": [],
|
||||
"selfTradePreventionMode": "NONE",
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "SOLUSDT",
|
||||
"orderId": 3551312894,
|
||||
"orderListId": -1,
|
||||
"clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba",
|
||||
"transactTime": 1674493798550,
|
||||
"price": "15.50000000",
|
||||
"origQty": "1.10000000",
|
||||
"executedQty": "1.10000000",
|
||||
"cummulativeQuoteQty": "17.05",
|
||||
"status": "FILLED",
|
||||
"timeInForce": "GTC",
|
||||
"type": "LIMIT",
|
||||
"side": "BUY",
|
||||
"workingTime": 1674493798550,
|
||||
"fills": [],
|
||||
"selfTradePreventionMode": "NONE",
|
||||
},
|
||||
],
|
||||
},
|
||||
"binanceus": {
|
||||
@@ -200,6 +218,24 @@ EXCHANGES = {
|
||||
"rebated_fee_currency": "USDT",
|
||||
},
|
||||
],
|
||||
"sample_my_trades": [
|
||||
{
|
||||
"id": "123412341234",
|
||||
"create_time": "167997798",
|
||||
"create_time_ms": "167997798825.566200",
|
||||
"currency_pair": "ETH_USDT",
|
||||
"side": "sell",
|
||||
"role": "taker",
|
||||
"amount": "0.0115",
|
||||
"price": "1712.63",
|
||||
"order_id": "1234123412",
|
||||
"fee": "0.0",
|
||||
"fee_currency": "USDT",
|
||||
"point_fee": "0.03939049",
|
||||
"gt_fee": "0.0",
|
||||
"amend_text": "-",
|
||||
}
|
||||
],
|
||||
},
|
||||
"okx": {
|
||||
"pair": "BTC/USDT",
|
||||
@@ -270,6 +306,36 @@ EXCHANGES = {
|
||||
"hasQuoteVolume": True,
|
||||
"timeframe": "1h",
|
||||
"futures": False,
|
||||
"sample_order": [
|
||||
{
|
||||
"symbol": "SOL-USDT",
|
||||
"orderId": "1762393630149869568",
|
||||
"transactTime": "1674493798550",
|
||||
"price": "15.5",
|
||||
"stopPrice": "0",
|
||||
"origQty": "1.1",
|
||||
"executedQty": "1.1",
|
||||
"cummulativeQuoteQty": "17.05",
|
||||
"status": "FILLED",
|
||||
"type": "LIMIT",
|
||||
"side": "BUY",
|
||||
"clientOrderID": "",
|
||||
},
|
||||
{
|
||||
"symbol": "SOL-USDT",
|
||||
"orderId": "1762393630149869568",
|
||||
"transactTime": "1674493798550",
|
||||
"price": "15.5",
|
||||
"stopPrice": "0",
|
||||
"origQty": "1.1",
|
||||
"executedQty": "1.1",
|
||||
"cummulativeQuoteQty": "17.05",
|
||||
"status": "FILLED",
|
||||
"type": "MARKET",
|
||||
"side": "BUY",
|
||||
"clientOrderID": "",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,8 @@ class TestCCXTExchange:
|
||||
assert isinstance(po["timestamp"], int)
|
||||
assert isinstance(po["price"], float)
|
||||
assert po["price"] == 15.5
|
||||
if po["average"] is not None:
|
||||
if po["status"] == "closed":
|
||||
# Filled orders should have average assigned.
|
||||
assert isinstance(po["average"], float)
|
||||
assert po["average"] == 15.5
|
||||
assert po["symbol"] == pair
|
||||
@@ -86,6 +87,33 @@ class TestCCXTExchange:
|
||||
else:
|
||||
pytest.skip(f"No sample order available for exchange {exchange_name}")
|
||||
|
||||
def test_ccxt_my_trades_parse(self, exchange: EXCHANGE_FIXTURE_TYPE):
|
||||
exch, exchange_name = exchange
|
||||
if trades := EXCHANGES[exchange_name].get("sample_my_trades"):
|
||||
pair = "SOL/USDT"
|
||||
for trade in trades:
|
||||
market = exch._api.markets[pair]
|
||||
po = exch._api.parse_trade(trade)
|
||||
(trade, market)
|
||||
assert isinstance(po["id"], str)
|
||||
assert isinstance(po["side"], str)
|
||||
assert isinstance(po["amount"], float)
|
||||
assert isinstance(po["price"], float)
|
||||
assert isinstance(po["datetime"], str)
|
||||
assert isinstance(po["timestamp"], int)
|
||||
|
||||
if fees := po.get("fees"):
|
||||
assert isinstance(fees, list)
|
||||
for fee in fees:
|
||||
assert isinstance(fee, dict)
|
||||
assert isinstance(fee["cost"], str)
|
||||
# TODO: this should be a float!
|
||||
# assert isinstance(fee["cost"], float)
|
||||
assert isinstance(fee["currency"], str)
|
||||
|
||||
else:
|
||||
pytest.skip(f"No sample Trades available for exchange {exchange_name}")
|
||||
|
||||
def test_ccxt_fetch_tickers(self, exchange: EXCHANGE_FIXTURE_TYPE):
|
||||
exch, exchangename = exchange
|
||||
pair = EXCHANGES[exchangename]["pair"]
|
||||
|
||||
@@ -50,6 +50,7 @@ def freqai_conf(default_conf, tmp_path):
|
||||
freqaiconf.update(
|
||||
{
|
||||
"datadir": Path(default_conf["datadir"]),
|
||||
"runmode": "backtest",
|
||||
"strategy": "freqai_test_strat",
|
||||
"user_data_dir": tmp_path,
|
||||
"strategy-path": "freqtrade/tests/strategy/strats",
|
||||
|
||||
@@ -699,18 +699,20 @@ def test_process_trade_creation(
|
||||
|
||||
|
||||
def test_process_exchange_failures(default_conf_usdt, ticker_usdt, mocker) -> None:
|
||||
# TODO: Move this test to test_worker
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
fetch_ticker=ticker_usdt,
|
||||
reload_markets=MagicMock(side_effect=TemporaryError),
|
||||
reload_markets=MagicMock(),
|
||||
create_order=MagicMock(side_effect=TemporaryError),
|
||||
)
|
||||
sleep_mock = mocker.patch("time.sleep")
|
||||
|
||||
worker = Worker(args=None, config=default_conf_usdt)
|
||||
patch_get_signal(worker.freqtrade)
|
||||
mocker.patch(f"{EXMS}.reload_markets", MagicMock(side_effect=TemporaryError))
|
||||
|
||||
worker._process_running()
|
||||
assert sleep_mock.called is True
|
||||
@@ -1146,6 +1148,36 @@ def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_order
|
||||
assert not freqtrade.execute_entry(pair, stake_amount)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_execute_entry_fully_canceled_on_create(
|
||||
mocker, default_conf_usdt, fee, limit_order_open, is_short
|
||||
) -> None:
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
|
||||
mock_hce = mocker.spy(freqtrade, "handle_cancel_enter")
|
||||
order = limit_order_open[entry_side(is_short)]
|
||||
pair = "ETH/USDT"
|
||||
order["symbol"] = pair
|
||||
order["status"] = "canceled"
|
||||
order["filled"] = 0.0
|
||||
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
fetch_ticker=MagicMock(return_value={"bid": 1.9, "ask": 2.2, "last": 1.9}),
|
||||
create_order=MagicMock(return_value=order),
|
||||
get_rate=MagicMock(return_value=0.11),
|
||||
get_min_pair_stake_amount=MagicMock(return_value=1),
|
||||
get_fee=fee,
|
||||
)
|
||||
stake_amount = 2
|
||||
|
||||
assert freqtrade.execute_entry(pair, stake_amount)
|
||||
assert mock_hce.call_count == 1
|
||||
# an order that immediately cancels completely should delete the order.
|
||||
trades = Trade.get_trades().all()
|
||||
assert len(trades) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_execute_entry_min_leverage(mocker, default_conf_usdt, fee, limit_order, is_short) -> None:
|
||||
default_conf_usdt["trading_mode"] = "futures"
|
||||
@@ -4978,6 +5010,47 @@ def test_handle_onexchange_order_exit(mocker, default_conf_usdt, limit_order, is
|
||||
assert trade.amount == 5.0
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_handle_onexchange_order_fully_canceled_enter(
|
||||
mocker, default_conf_usdt, limit_order, is_short, caplog
|
||||
):
|
||||
default_conf_usdt["dry_run"] = False
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
|
||||
entry_order = limit_order[entry_side(is_short)]
|
||||
entry_order["status"] = "canceled"
|
||||
entry_order["filled"] = 0.0
|
||||
mock_fo = mocker.patch(
|
||||
f"{EXMS}.fetch_orders",
|
||||
return_value=[
|
||||
entry_order,
|
||||
],
|
||||
)
|
||||
mocker.patch(f"{EXMS}.get_rate", return_value=entry_order["price"])
|
||||
|
||||
trade = Trade(
|
||||
pair="ETH/USDT",
|
||||
fee_open=0.001,
|
||||
fee_close=0.001,
|
||||
open_rate=entry_order["price"],
|
||||
open_date=dt_now(),
|
||||
stake_amount=entry_order["cost"],
|
||||
amount=entry_order["amount"],
|
||||
exchange="binance",
|
||||
is_short=is_short,
|
||||
leverage=1,
|
||||
)
|
||||
|
||||
trade.orders.append(Order.parse_from_ccxt_object(entry_order, "ADA/USDT", entry_side(is_short)))
|
||||
Trade.session.add(trade)
|
||||
assert freqtrade.handle_onexchange_order(trade) is True
|
||||
assert log_has_re(r"Trade only had fully canceled entry orders\. .*", caplog)
|
||||
assert mock_fo.call_count == 1
|
||||
trades = Trade.get_trades().all()
|
||||
assert len(trades) == 0
|
||||
|
||||
|
||||
def test_get_valid_price(mocker, default_conf_usdt) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
|
||||
@@ -47,7 +47,7 @@ def generate_result_metrics():
|
||||
"profit_total_abs": 0.001,
|
||||
"profit_total": 0.01,
|
||||
"holding_avg": timedelta(minutes=20),
|
||||
"max_drawdown": 0.001,
|
||||
"max_drawdown_account": 0.001,
|
||||
"max_drawdown_abs": 0.001,
|
||||
"loss": 0.001,
|
||||
"is_initial_point": 0.001,
|
||||
@@ -1063,7 +1063,7 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmp_path, fee) -> None
|
||||
def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmp_path, fee) -> None:
|
||||
mocker.patch(f"{EXMS}.validate_config", MagicMock())
|
||||
mocker.patch(f"{EXMS}.get_fee", fee)
|
||||
mocker.patch(f"{EXMS}._load_markets")
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=get_markets()))
|
||||
(tmp_path / "hyperopt_results").mkdir(parents=True)
|
||||
# Dummy-reduce points to ensure scikit-learn is forced to generate new values
|
||||
|
||||
@@ -1961,9 +1961,25 @@ def test_get_canceled_exit_order_count(fee, is_short):
|
||||
trade = Trade.get_trades([Trade.pair == "ETC/BTC"]).first()
|
||||
# No canceled order.
|
||||
assert trade.get_canceled_exit_order_count() == 0
|
||||
# Property returns the same result
|
||||
assert trade.canceled_exit_order_count == 0
|
||||
|
||||
trade.orders[-1].status = "canceled"
|
||||
assert trade.get_canceled_exit_order_count() == 1
|
||||
assert trade.canceled_exit_order_count == 1
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
@pytest.mark.parametrize("is_short", [True, False])
|
||||
def test_fully_canceled_entry_order_count(fee, is_short):
|
||||
create_mock_trades(fee, is_short=is_short)
|
||||
trade = Trade.get_trades([Trade.pair == "ETC/BTC"]).first()
|
||||
# No canceled order.
|
||||
assert trade.fully_canceled_entry_order_count == 0
|
||||
|
||||
trade.orders[0].status = "canceled"
|
||||
trade.orders[0].filled = 0
|
||||
assert trade.fully_canceled_entry_order_count == 1
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
|
||||
@@ -773,6 +773,7 @@ def test_VolumePairList_whitelist_gen(
|
||||
whitelist_result,
|
||||
caplog,
|
||||
) -> None:
|
||||
whitelist_conf["runmode"] = "backtest"
|
||||
whitelist_conf["pairlists"] = pairlists
|
||||
whitelist_conf["stake_currency"] = base_currency
|
||||
|
||||
@@ -1270,6 +1271,7 @@ def test_ShuffleFilter_init(mocker, whitelist_conf, caplog) -> None:
|
||||
{"method": "StaticPairList"},
|
||||
{"method": "ShuffleFilter", "seed": 43},
|
||||
]
|
||||
whitelist_conf["runmode"] = "backtest"
|
||||
|
||||
exchange = get_patched_exchange(mocker, whitelist_conf)
|
||||
plm = PairListManager(exchange, whitelist_conf)
|
||||
@@ -2306,7 +2308,7 @@ def test_MarketCapPairList_filter(
|
||||
)
|
||||
|
||||
mocker.patch(
|
||||
"freqtrade.plugins.pairlist.MarketCapPairList.CoinGeckoAPI.get_coins_markets",
|
||||
"freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_markets",
|
||||
return_value=test_value,
|
||||
)
|
||||
|
||||
@@ -2344,7 +2346,7 @@ def test_MarketCapPairList_timing(mocker, default_conf_usdt, markets, time_machi
|
||||
)
|
||||
|
||||
mocker.patch(
|
||||
"freqtrade.plugins.pairlist.MarketCapPairList.CoinGeckoAPI.get_coins_markets",
|
||||
"freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_markets",
|
||||
return_value=test_value,
|
||||
)
|
||||
|
||||
|
||||
@@ -8,11 +8,20 @@ import pytest
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
||||
from freqtrade.util.coin_gecko import FtCoinGeckoApi
|
||||
from tests.conftest import log_has, log_has_re
|
||||
|
||||
|
||||
def test_fiat_convert_is_supported(mocker):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
def test_fiat_convert_is_singleton():
|
||||
fiat_convert = CryptoToFiatConverter({"a": 22})
|
||||
fiat_convert2 = CryptoToFiatConverter({})
|
||||
|
||||
assert fiat_convert is fiat_convert2
|
||||
assert id(fiat_convert) == id(fiat_convert2)
|
||||
|
||||
|
||||
def test_fiat_convert_is_supported():
|
||||
fiat_convert = CryptoToFiatConverter({})
|
||||
assert fiat_convert._is_supported_fiat(fiat="USD") is True
|
||||
assert fiat_convert._is_supported_fiat(fiat="usd") is True
|
||||
assert fiat_convert._is_supported_fiat(fiat="abc") is False
|
||||
@@ -20,7 +29,7 @@ def test_fiat_convert_is_supported(mocker):
|
||||
|
||||
|
||||
def test_fiat_convert_find_price(mocker):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
fiat_convert = CryptoToFiatConverter({})
|
||||
|
||||
fiat_convert._coinlistings = {}
|
||||
fiat_convert._backoff = 0
|
||||
@@ -48,7 +57,7 @@ def test_fiat_convert_find_price(mocker):
|
||||
|
||||
def test_fiat_convert_unsupported_crypto(mocker, caplog):
|
||||
mocker.patch("freqtrade.rpc.fiat_convert.CryptoToFiatConverter._coinlistings", return_value=[])
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
fiat_convert = CryptoToFiatConverter({})
|
||||
assert fiat_convert._find_price(crypto_symbol="CRYPTO_123", fiat_symbol="EUR") == 0.0
|
||||
assert log_has("unsupported crypto-symbol CRYPTO_123 - returning 0.0", caplog)
|
||||
|
||||
@@ -58,7 +67,7 @@ def test_fiat_convert_get_price(mocker):
|
||||
"freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price", return_value=28000.0
|
||||
)
|
||||
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
fiat_convert = CryptoToFiatConverter({})
|
||||
|
||||
with pytest.raises(ValueError, match=r"The fiat us dollar is not supported."):
|
||||
fiat_convert.get_price(crypto_symbol="btc", fiat_symbol="US Dollar")
|
||||
@@ -77,20 +86,20 @@ def test_fiat_convert_get_price(mocker):
|
||||
assert find_price.call_count == 1
|
||||
|
||||
|
||||
def test_fiat_convert_same_currencies(mocker):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
def test_fiat_convert_same_currencies():
|
||||
fiat_convert = CryptoToFiatConverter({})
|
||||
|
||||
assert fiat_convert.get_price(crypto_symbol="USD", fiat_symbol="USD") == 1.0
|
||||
|
||||
|
||||
def test_fiat_convert_two_FIAT(mocker):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
def test_fiat_convert_two_FIAT():
|
||||
fiat_convert = CryptoToFiatConverter({})
|
||||
|
||||
assert fiat_convert.get_price(crypto_symbol="USD", fiat_symbol="EUR") == 0.0
|
||||
|
||||
|
||||
def test_loadcryptomap(mocker):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
def test_loadcryptomap():
|
||||
fiat_convert = CryptoToFiatConverter({})
|
||||
assert len(fiat_convert._coinlistings) == 2
|
||||
|
||||
assert fiat_convert._get_gecko_id("btc") == "bitcoin"
|
||||
@@ -100,28 +109,28 @@ def test_fiat_init_network_exception(mocker):
|
||||
# Because CryptoToFiatConverter is a Singleton we reset the listings
|
||||
listmock = MagicMock(side_effect=RequestException)
|
||||
mocker.patch.multiple(
|
||||
"freqtrade.rpc.fiat_convert.CoinGeckoAPI",
|
||||
"freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
|
||||
get_coins_list=listmock,
|
||||
)
|
||||
# with pytest.raises(RequestEsxception):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
fiat_convert = CryptoToFiatConverter({})
|
||||
fiat_convert._coinlistings = {}
|
||||
fiat_convert._load_cryptomap()
|
||||
|
||||
assert len(fiat_convert._coinlistings) == 0
|
||||
|
||||
|
||||
def test_fiat_convert_without_network(mocker):
|
||||
def test_fiat_convert_without_network():
|
||||
# Because CryptoToFiatConverter is a Singleton we reset the value of _coingecko
|
||||
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
fiat_convert = CryptoToFiatConverter({})
|
||||
|
||||
cmc_temp = CryptoToFiatConverter._coingecko
|
||||
CryptoToFiatConverter._coingecko = None
|
||||
cmc_temp = fiat_convert._coingecko
|
||||
fiat_convert._coingecko = None
|
||||
|
||||
assert fiat_convert._coingecko is None
|
||||
assert fiat_convert._find_price(crypto_symbol="btc", fiat_symbol="usd") == 0.0
|
||||
CryptoToFiatConverter._coingecko = cmc_temp
|
||||
fiat_convert._coingecko = cmc_temp
|
||||
|
||||
|
||||
def test_fiat_too_many_requests_response(mocker, caplog):
|
||||
@@ -129,11 +138,11 @@ def test_fiat_too_many_requests_response(mocker, caplog):
|
||||
req_exception = "429 Too Many Requests"
|
||||
listmock = MagicMock(return_value="{}", side_effect=RequestException(req_exception))
|
||||
mocker.patch.multiple(
|
||||
"freqtrade.rpc.fiat_convert.CoinGeckoAPI",
|
||||
"freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
|
||||
get_coins_list=listmock,
|
||||
)
|
||||
# with pytest.raises(RequestEsxception):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
fiat_convert = CryptoToFiatConverter({})
|
||||
fiat_convert._coinlistings = {}
|
||||
fiat_convert._load_cryptomap()
|
||||
|
||||
@@ -144,8 +153,8 @@ def test_fiat_too_many_requests_response(mocker, caplog):
|
||||
)
|
||||
|
||||
|
||||
def test_fiat_multiple_coins(mocker, caplog):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
def test_fiat_multiple_coins(caplog):
|
||||
fiat_convert = CryptoToFiatConverter({})
|
||||
fiat_convert._coinlistings = [
|
||||
{"id": "helium", "symbol": "hnt", "name": "Helium"},
|
||||
{"id": "hymnode", "symbol": "hnt", "name": "Hymnode"},
|
||||
@@ -165,11 +174,11 @@ def test_fiat_invalid_response(mocker, caplog):
|
||||
# Because CryptoToFiatConverter is a Singleton we reset the listings
|
||||
listmock = MagicMock(return_value=None)
|
||||
mocker.patch.multiple(
|
||||
"freqtrade.rpc.fiat_convert.CoinGeckoAPI",
|
||||
"freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
|
||||
get_coins_list=listmock,
|
||||
)
|
||||
# with pytest.raises(RequestEsxception):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
fiat_convert = CryptoToFiatConverter({})
|
||||
fiat_convert._coinlistings = []
|
||||
fiat_convert._load_cryptomap()
|
||||
|
||||
@@ -182,7 +191,7 @@ def test_fiat_invalid_response(mocker, caplog):
|
||||
def test_convert_amount(mocker):
|
||||
mocker.patch("freqtrade.rpc.fiat_convert.CryptoToFiatConverter.get_price", return_value=12345.0)
|
||||
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
fiat_convert = CryptoToFiatConverter({})
|
||||
result = fiat_convert.convert_amount(crypto_amount=1.23, crypto_symbol="BTC", fiat_symbol="USD")
|
||||
assert result == 15184.35
|
||||
|
||||
@@ -193,3 +202,18 @@ def test_convert_amount(mocker):
|
||||
crypto_amount="1.23", crypto_symbol="BTC", fiat_symbol="BTC"
|
||||
)
|
||||
assert result == 1.23
|
||||
|
||||
|
||||
def test_FtCoinGeckoApi():
|
||||
ftc = FtCoinGeckoApi()
|
||||
assert ftc._api_key == ""
|
||||
assert ftc.api_base_url == "https://api.coingecko.com/api/v3/"
|
||||
|
||||
# defaults to demo
|
||||
ftc = FtCoinGeckoApi(api_key="123456")
|
||||
assert ftc._api_key == "123456"
|
||||
assert ftc.api_base_url == "https://api.coingecko.com/api/v3/"
|
||||
|
||||
ftc = FtCoinGeckoApi(api_key="123456", is_demo=False)
|
||||
assert ftc._api_key == "123456"
|
||||
assert ftc.api_base_url == "https://pro-api.coingecko.com/api/v3/"
|
||||
|
||||
@@ -225,7 +225,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
|
||||
def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||
mocker.patch.multiple(
|
||||
"freqtrade.rpc.fiat_convert.CoinGeckoAPI",
|
||||
"freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
|
||||
get_price=MagicMock(return_value={"bitcoin": {"usd": 15000.0}}),
|
||||
)
|
||||
mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=15000.0)
|
||||
@@ -266,7 +266,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||
assert "-0.00" == f"{fiat_profit_sum:.2f}"
|
||||
|
||||
# Test with fiat convert
|
||||
rpc._fiat_converter = CryptoToFiatConverter()
|
||||
rpc._fiat_converter = CryptoToFiatConverter({})
|
||||
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf["stake_currency"], "USD")
|
||||
assert "Since" in headers
|
||||
assert "Pair" in headers
|
||||
@@ -312,7 +312,7 @@ def test__rpc_timeunit_profit(
|
||||
fiat_display_currency = default_conf_usdt["fiat_display_currency"]
|
||||
|
||||
rpc = RPC(freqtradebot)
|
||||
rpc._fiat_converter = CryptoToFiatConverter()
|
||||
rpc._fiat_converter = CryptoToFiatConverter({})
|
||||
|
||||
# Try valid data
|
||||
days = rpc._rpc_timeunit_profit(7, stake_currency, fiat_display_currency)
|
||||
@@ -344,7 +344,7 @@ def test_rpc_trade_history(mocker, default_conf, markets, fee, is_short):
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
create_mock_trades(fee, is_short)
|
||||
rpc = RPC(freqtradebot)
|
||||
rpc._fiat_converter = CryptoToFiatConverter()
|
||||
rpc._fiat_converter = CryptoToFiatConverter({})
|
||||
trades = rpc._rpc_trade_history(2)
|
||||
assert len(trades["trades"]) == 2
|
||||
assert trades["trades_count"] == 2
|
||||
@@ -434,7 +434,7 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None:
|
||||
fiat_display_currency = default_conf_usdt["fiat_display_currency"]
|
||||
|
||||
rpc = RPC(freqtradebot)
|
||||
rpc._fiat_converter = CryptoToFiatConverter()
|
||||
rpc._fiat_converter = CryptoToFiatConverter({})
|
||||
|
||||
res = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
|
||||
assert res["trade_count"] == 0
|
||||
@@ -495,7 +495,7 @@ def test_rpc_balance_handle_error(default_conf, mocker):
|
||||
# ETH will be skipped due to mocked Error below
|
||||
|
||||
mocker.patch.multiple(
|
||||
"freqtrade.rpc.fiat_convert.CoinGeckoAPI",
|
||||
"freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
|
||||
get_price=MagicMock(return_value={"bitcoin": {"usd": 15000.0}}),
|
||||
)
|
||||
mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=15000.0)
|
||||
@@ -509,7 +509,7 @@ def test_rpc_balance_handle_error(default_conf, mocker):
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot)
|
||||
rpc = RPC(freqtradebot)
|
||||
rpc._fiat_converter = CryptoToFiatConverter()
|
||||
rpc._fiat_converter = CryptoToFiatConverter({})
|
||||
with pytest.raises(RPCException, match="Error getting current tickers."):
|
||||
rpc._rpc_balance(default_conf["stake_currency"], default_conf["fiat_display_currency"])
|
||||
|
||||
@@ -558,7 +558,7 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers):
|
||||
]
|
||||
|
||||
mocker.patch.multiple(
|
||||
"freqtrade.rpc.fiat_convert.CoinGeckoAPI",
|
||||
"freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
|
||||
get_price=MagicMock(return_value={"bitcoin": {"usd": 1.2}}),
|
||||
)
|
||||
mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=1.2)
|
||||
@@ -578,7 +578,7 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers):
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
patch_get_signal(freqtradebot)
|
||||
rpc = RPC(freqtradebot)
|
||||
rpc._fiat_converter = CryptoToFiatConverter()
|
||||
rpc._fiat_converter = CryptoToFiatConverter({})
|
||||
|
||||
result = rpc._rpc_balance(
|
||||
default_conf_usdt["stake_currency"], default_conf_usdt["fiat_display_currency"]
|
||||
|
||||
@@ -2564,10 +2564,14 @@ def test_send_msg_buy_notification_no_fiat(
|
||||
("Short", "short_signal_01", 2.0),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("fiat", ["", None])
|
||||
def test_send_msg_exit_notification_no_fiat(
|
||||
default_conf, mocker, direction, enter_signal, leverage, time_machine
|
||||
default_conf, mocker, direction, enter_signal, leverage, time_machine, fiat
|
||||
) -> None:
|
||||
del default_conf["fiat_display_currency"]
|
||||
if fiat is None:
|
||||
del default_conf["fiat_display_currency"]
|
||||
else:
|
||||
default_conf["fiat_display_currency"] = fiat
|
||||
time_machine.move_to("2022-05-02 00:00:00 +00:00", tick=False)
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
|
||||
177
tests/setup.Tests.ps1
Normal file
177
tests/setup.Tests.ps1
Normal file
@@ -0,0 +1,177 @@
|
||||
|
||||
Describe "Setup and Tests" {
|
||||
BeforeAll {
|
||||
# Setup variables
|
||||
$SetupScriptPath = Join-Path $PSScriptRoot "..\setup.ps1"
|
||||
$Global:LogFilePath = Join-Path $env:TEMP "script_log.txt"
|
||||
|
||||
# Check if the setup script exists
|
||||
if (-Not (Test-Path -Path $SetupScriptPath)) {
|
||||
Write-Host "Error: setup.ps1 script not found at path: $SetupScriptPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Mock main to prevent it from running
|
||||
Mock Main {}
|
||||
|
||||
. $SetupScriptPath
|
||||
}
|
||||
|
||||
Context "Write-Log Tests" -Tag "Unit" {
|
||||
It "should write INFO level log" {
|
||||
if (Test-Path $Global:LogFilePath){
|
||||
Remove-Item $Global:LogFilePath -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Write-Log -Message "Test Info Message" -Level "INFO"
|
||||
$Global:LogFilePath | Should -Exist
|
||||
|
||||
$LogContent = Get-Content $Global:LogFilePath
|
||||
$LogContent | Should -Contain "INFO: Test Info Message"
|
||||
}
|
||||
|
||||
It "should write ERROR level log" {
|
||||
if (Test-Path $Global:LogFilePath){
|
||||
Remove-Item $Global:LogFilePath -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Write-Log -Message "Test Error Message" -Level "ERROR"
|
||||
$Global:LogFilePath | Should -Exist
|
||||
|
||||
$LogContent = Get-Content $Global:LogFilePath
|
||||
$LogContent | Should -Contain "ERROR: Test Error Message"
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Get-UserSelection Tests" {
|
||||
Context "Valid input" {
|
||||
It "Should return the correct index for a valid single selection" {
|
||||
$Options = @("Option1", "Option2", "Option3")
|
||||
Mock Read-Host { return "B" }
|
||||
$Result = Get-UserSelection -prompt "Select an option" -options $Options
|
||||
$Result | Should -Be 1
|
||||
}
|
||||
|
||||
It "Should return the correct index for a valid single selection" {
|
||||
$Options = @("Option1", "Option2", "Option3")
|
||||
Mock Read-Host { return "b" }
|
||||
$Result = Get-UserSelection -prompt "Select an option" -options $Options
|
||||
$Result | Should -Be 1
|
||||
}
|
||||
|
||||
It "Should return the default choice when no input is provided" {
|
||||
$Options = @("Option1", "Option2", "Option3")
|
||||
Mock Read-Host { return "" }
|
||||
$Result = Get-UserSelection -prompt "Select an option" -options $Options -defaultChoice "C"
|
||||
$Result | Should -Be 2
|
||||
}
|
||||
}
|
||||
|
||||
Context "Invalid input" {
|
||||
It "Should return -1 for an invalid letter selection" {
|
||||
$Options = @("Option1", "Option2", "Option3")
|
||||
Mock Read-Host { return "X" }
|
||||
$Result = Get-UserSelection -prompt "Select an option" -options $Options
|
||||
$Result | Should -Be -1
|
||||
}
|
||||
|
||||
It "Should return -1 for a selection outside the valid range" {
|
||||
$Options = @("Option1", "Option2", "Option3")
|
||||
Mock Read-Host { return "D" }
|
||||
$Result = Get-UserSelection -prompt "Select an option" -options $Options
|
||||
$Result | Should -Be -1
|
||||
}
|
||||
|
||||
It "Should return -1 for a non-letter input" {
|
||||
$Options = @("Option1", "Option2", "Option3")
|
||||
Mock Read-Host { return "1" }
|
||||
$Result = Get-UserSelection -prompt "Select an option" -options $Options
|
||||
$Result | Should -Be -1
|
||||
}
|
||||
|
||||
It "Should return -1 for mixed valid and invalid input" {
|
||||
Mock Read-Host { return "A,X,B,Y,C,Z" }
|
||||
$Options = @("Option1", "Option2", "Option3")
|
||||
$Indices = Get-UserSelection -prompt "Select options" -options $Options -defaultChoice "A"
|
||||
$Indices | Should -Be -1
|
||||
}
|
||||
}
|
||||
|
||||
Context "Multiple selections" {
|
||||
It "Should handle valid input correctly" {
|
||||
Mock Read-Host { return "A, B, C" }
|
||||
$Options = @("Option1", "Option2", "Option3")
|
||||
$Indices = Get-UserSelection -prompt "Select options" -options $Options -defaultChoice "A"
|
||||
$Indices | Should -Be @(0, 1, 2)
|
||||
}
|
||||
|
||||
It "Should handle valid input without whitespace correctly" {
|
||||
Mock Read-Host { return "A,B,C" }
|
||||
$Options = @("Option1", "Option2", "Option3")
|
||||
$Indices = Get-UserSelection -prompt "Select options" -options $Options -defaultChoice "A"
|
||||
$Indices | Should -Be @(0, 1, 2)
|
||||
}
|
||||
|
||||
It "Should return indices for selected options" {
|
||||
Mock Read-Host { return "a,b" }
|
||||
$Options = @("Option1", "Option2", "Option3")
|
||||
$Indices = Get-UserSelection -prompt "Select options" -options $Options
|
||||
$Indices | Should -Be @(0, 1)
|
||||
}
|
||||
|
||||
It "Should return default choice if no input" {
|
||||
Mock Read-Host { return "" }
|
||||
$Options = @("Option1", "Option2", "Option3")
|
||||
$Indices = Get-UserSelection -prompt "Select options" -options $Options -defaultChoice "C"
|
||||
$Indices | Should -Be @(2)
|
||||
}
|
||||
|
||||
It "Should handle invalid input gracefully" {
|
||||
Mock Read-Host { return "x,y,z" }
|
||||
$Options = @("Option1", "Option2", "Option3")
|
||||
$Indices = Get-UserSelection -prompt "Select options" -options $Options -defaultChoice "A"
|
||||
$Indices | Should -Be -1
|
||||
}
|
||||
|
||||
It "Should handle input without whitespace" {
|
||||
Mock Read-Host { return "a,b,c" }
|
||||
$Options = @("Option1", "Option2", "Option3")
|
||||
$Indices = Get-UserSelection -prompt "Select options" -options $Options
|
||||
$Indices | Should -Be @(0, 1, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Exit-Script Tests" -Tag "Unit" {
|
||||
BeforeEach {
|
||||
Mock Write-Log {}
|
||||
Mock Start-Process {}
|
||||
Mock Read-Host { return "Y" }
|
||||
}
|
||||
|
||||
It "should exit with the given exit code without waiting for key press" {
|
||||
$ExitCode = Exit-Script -ExitCode 0 -isSubShell $true -waitForKeypress $false
|
||||
$ExitCode | Should -Be 0
|
||||
}
|
||||
|
||||
It "should prompt to open log file on error" {
|
||||
Exit-Script -ExitCode 1 -isSubShell $true -waitForKeypress $false
|
||||
Assert-MockCalled Read-Host -Exactly 1
|
||||
Assert-MockCalled Start-Process -Exactly 1
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Find-PythonExecutable' {
|
||||
It 'Returns the first valid Python executable' {
|
||||
Mock Test-PythonExecutable { $true } -ParameterFilter { $PythonExecutable -eq 'python' }
|
||||
$Result = Find-PythonExecutable
|
||||
$Result | Should -Be 'python'
|
||||
}
|
||||
|
||||
It 'Returns null if no valid Python executable is found' {
|
||||
Mock Test-PythonExecutable { $false }
|
||||
$Result = Find-PythonExecutable
|
||||
$Result | Should -Be $null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ class HyperoptableStrategy(StrategyTestV3):
|
||||
bot_loop_started = False
|
||||
bot_started = False
|
||||
|
||||
def bot_loop_start(self):
|
||||
def bot_loop_start(self, **kwargs):
|
||||
self.bot_loop_started = True
|
||||
|
||||
def bot_start(self, **kwargs) -> None:
|
||||
|
||||
@@ -48,7 +48,7 @@ class HyperoptableStrategyV2(StrategyTestV2):
|
||||
|
||||
bot_loop_started = False
|
||||
|
||||
def bot_loop_start(self):
|
||||
def bot_loop_start(self, **kwargs):
|
||||
self.bot_loop_started = True
|
||||
|
||||
def bot_start(self, **kwargs) -> None:
|
||||
|
||||
@@ -659,6 +659,16 @@ def test_validate_default_conf(default_conf) -> None:
|
||||
validate_config_schema(default_conf)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fiat", ["EUR", "USD", "", None])
|
||||
def test_validate_fiat_currency_options(default_conf, fiat) -> None:
|
||||
# Validate via our validator - we allow setting defaults!
|
||||
if fiat is not None:
|
||||
default_conf["fiat_display_currency"] = fiat
|
||||
else:
|
||||
del default_conf["fiat_display_currency"]
|
||||
validate_config_schema(default_conf)
|
||||
|
||||
|
||||
def test_validate_max_open_trades(default_conf):
|
||||
default_conf["max_open_trades"] = float("inf")
|
||||
default_conf["stake_amount"] = "unlimited"
|
||||
|
||||
Reference in New Issue
Block a user