Merge branch 'develop' into pr/Axel-CH/10062
This commit is contained in:
@@ -38,7 +38,7 @@ def mock_trade_1(fee, is_short: bool):
|
||||
trade = Trade(
|
||||
pair="ETH/BTC",
|
||||
stake_amount=0.001,
|
||||
amount=123.0,
|
||||
amount=50.0,
|
||||
amount_requested=123.0,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
@@ -201,7 +201,7 @@ def mock_trade_4(fee, is_short: bool):
|
||||
trade = Trade(
|
||||
pair="ETC/BTC",
|
||||
stake_amount=0.001,
|
||||
amount=123.0,
|
||||
amount=0.0,
|
||||
amount_requested=124.0,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
|
||||
@@ -224,7 +224,7 @@ def mock_trade_usdt_4(fee, is_short: bool):
|
||||
trade = Trade(
|
||||
pair="NEO/USDT",
|
||||
stake_amount=20.0,
|
||||
amount=10.0,
|
||||
amount=0.0,
|
||||
amount_requested=10.01,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
|
||||
@@ -3569,7 +3569,7 @@ def test_cancel_order_with_result(
|
||||
mocker.patch(f"{EXMS}.exchange_has", return_value=True)
|
||||
api_mock = MagicMock()
|
||||
api_mock.cancel_order = MagicMock(return_value=corder)
|
||||
api_mock.fetch_order = MagicMock(return_value={})
|
||||
api_mock.fetch_order = MagicMock(return_value={"id": "1234"})
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
|
||||
res = exchange.cancel_order_with_result("1234", "ETH/BTC", 1234)
|
||||
assert isinstance(res, dict)
|
||||
|
||||
@@ -6,6 +6,7 @@ import pytest
|
||||
|
||||
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||
from freqtrade.exceptions import RetryableOrderError, TemporaryError
|
||||
from freqtrade.exchange.common import API_RETRY_COUNT
|
||||
from freqtrade.exchange.exchange import timeframe_to_minutes
|
||||
from tests.conftest import EXMS, get_patched_exchange, log_has
|
||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
@@ -551,6 +552,7 @@ def test__set_leverage_okx(mocker, default_conf):
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_fetch_stoploss_order_okx(default_conf, mocker):
|
||||
default_conf["dry_run"] = False
|
||||
mocker.patch("freqtrade.exchange.common.time.sleep")
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_order = MagicMock()
|
||||
|
||||
@@ -569,10 +571,10 @@ def test_fetch_stoploss_order_okx(default_conf, mocker):
|
||||
|
||||
with pytest.raises(RetryableOrderError):
|
||||
exchange.fetch_stoploss_order("1234", "ETH/BTC")
|
||||
assert api_mock.fetch_order.call_count == 1
|
||||
assert api_mock.fetch_open_orders.call_count == 1
|
||||
assert api_mock.fetch_closed_orders.call_count == 1
|
||||
assert api_mock.fetch_canceled_orders.call_count == 1
|
||||
assert api_mock.fetch_order.call_count == API_RETRY_COUNT + 1
|
||||
assert api_mock.fetch_open_orders.call_count == API_RETRY_COUNT + 1
|
||||
assert api_mock.fetch_closed_orders.call_count == API_RETRY_COUNT + 1
|
||||
assert api_mock.fetch_canceled_orders.call_count == API_RETRY_COUNT + 1
|
||||
|
||||
api_mock.fetch_order.reset_mock()
|
||||
api_mock.fetch_open_orders.reset_mock()
|
||||
@@ -610,6 +612,39 @@ def test_fetch_stoploss_order_okx(default_conf, mocker):
|
||||
assert dro_mock.call_count == 1
|
||||
|
||||
|
||||
def test_fetch_stoploss_order_okx_exceptions(default_conf_usdt, mocker):
|
||||
default_conf_usdt["dry_run"] = False
|
||||
api_mock = MagicMock()
|
||||
ccxt_exceptionhandlers(
|
||||
mocker,
|
||||
default_conf_usdt,
|
||||
api_mock,
|
||||
"okx",
|
||||
"fetch_stoploss_order",
|
||||
"fetch_order",
|
||||
retries=API_RETRY_COUNT + 1,
|
||||
order_id="12345",
|
||||
pair="ETH/USDT",
|
||||
)
|
||||
|
||||
# Test 2nd part of the function
|
||||
api_mock.fetch_order = MagicMock(side_effect=ccxt.OrderNotFound())
|
||||
api_mock.fetch_closed_orders = MagicMock(return_value=[])
|
||||
api_mock.fetch_canceled_orders = MagicMock(return_value=[])
|
||||
|
||||
ccxt_exceptionhandlers(
|
||||
mocker,
|
||||
default_conf_usdt,
|
||||
api_mock,
|
||||
"okx",
|
||||
"fetch_stoploss_order",
|
||||
"fetch_open_orders",
|
||||
retries=API_RETRY_COUNT + 1,
|
||||
order_id="12345",
|
||||
pair="ETH/USDT",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"sl1,sl2,sl3,side", [(1501, 1499, 1501, "sell"), (1499, 1501, 1499, "buy")]
|
||||
)
|
||||
|
||||
@@ -553,7 +553,7 @@ def test_enter_positions_global_pairlock(
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_handle_protections(mocker, default_conf_usdt, fee, is_short):
|
||||
default_conf_usdt["protections"] = [
|
||||
default_conf_usdt["_strategy_protections"] = [
|
||||
{"method": "CooldownPeriod", "stop_duration": 60},
|
||||
{
|
||||
"method": "StoplossGuard",
|
||||
|
||||
@@ -1299,7 +1299,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
|
||||
# While this test IS a copy of test_backtest_pricecontours, it's needed to ensure
|
||||
# results do not carry-over to the next run, which is not given by using parametrize.
|
||||
patch_exchange(mocker)
|
||||
default_conf["protections"] = [
|
||||
default_conf["_strategy_protections"] = [
|
||||
{
|
||||
"method": "CooldownPeriod",
|
||||
"stop_duration": 3,
|
||||
@@ -1358,7 +1358,7 @@ def test_backtest_pricecontours(
|
||||
default_conf, mocker, testdatadir, protections, contour, expected
|
||||
) -> None:
|
||||
if protections:
|
||||
default_conf["protections"] = protections
|
||||
default_conf["_strategy_protections"] = protections
|
||||
default_conf["enable_protections"] = True
|
||||
|
||||
patch_exchange(mocker)
|
||||
|
||||
@@ -2212,7 +2212,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None:
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pairlists,trade_mode,result",
|
||||
"pairlists,trade_mode,result,coin_market_calls",
|
||||
[
|
||||
(
|
||||
[
|
||||
@@ -2222,6 +2222,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None:
|
||||
],
|
||||
"spot",
|
||||
["BTC/USDT", "ETH/USDT"],
|
||||
1,
|
||||
),
|
||||
(
|
||||
[
|
||||
@@ -2231,6 +2232,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None:
|
||||
],
|
||||
"spot",
|
||||
["BTC/USDT", "ETH/USDT", "XRP/USDT", "ADA/USDT"],
|
||||
1,
|
||||
),
|
||||
(
|
||||
[
|
||||
@@ -2240,6 +2242,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None:
|
||||
],
|
||||
"spot",
|
||||
["BTC/USDT", "ETH/USDT", "XRP/USDT"],
|
||||
1,
|
||||
),
|
||||
(
|
||||
[
|
||||
@@ -2249,6 +2252,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None:
|
||||
],
|
||||
"spot",
|
||||
["BTC/USDT", "ETH/USDT", "XRP/USDT"],
|
||||
1,
|
||||
),
|
||||
(
|
||||
[
|
||||
@@ -2257,6 +2261,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None:
|
||||
],
|
||||
"spot",
|
||||
["BTC/USDT", "ETH/USDT", "XRP/USDT"],
|
||||
1,
|
||||
),
|
||||
(
|
||||
[
|
||||
@@ -2265,6 +2270,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None:
|
||||
],
|
||||
"spot",
|
||||
["BTC/USDT", "ETH/USDT"],
|
||||
1,
|
||||
),
|
||||
(
|
||||
[
|
||||
@@ -2273,6 +2279,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None:
|
||||
],
|
||||
"futures",
|
||||
["ETH/USDT:USDT"],
|
||||
1,
|
||||
),
|
||||
(
|
||||
[
|
||||
@@ -2281,11 +2288,34 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None:
|
||||
],
|
||||
"futures",
|
||||
["ETH/USDT:USDT", "ADA/USDT:USDT"],
|
||||
1,
|
||||
),
|
||||
(
|
||||
[
|
||||
# MarketCapPairList as generator - futures, 1 category
|
||||
{"method": "MarketCapPairList", "number_assets": 2, "categories": ["layer-1"]}
|
||||
],
|
||||
"futures",
|
||||
["ETH/USDT:USDT", "ADA/USDT:USDT"],
|
||||
["layer-1"],
|
||||
),
|
||||
(
|
||||
[
|
||||
# MarketCapPairList as generator - futures, 1 category
|
||||
{
|
||||
"method": "MarketCapPairList",
|
||||
"number_assets": 2,
|
||||
"categories": ["layer-1", "protocol"],
|
||||
}
|
||||
],
|
||||
"futures",
|
||||
["ETH/USDT:USDT", "ADA/USDT:USDT"],
|
||||
["layer-1", "protocol"],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_MarketCapPairList_filter(
|
||||
mocker, default_conf_usdt, trade_mode, markets, pairlists, result
|
||||
mocker, default_conf_usdt, trade_mode, markets, pairlists, result, coin_market_calls
|
||||
):
|
||||
test_value = [
|
||||
{"symbol": "btc"},
|
||||
@@ -2309,8 +2339,16 @@ def test_MarketCapPairList_filter(
|
||||
markets=PropertyMock(return_value=markets),
|
||||
exchange_has=MagicMock(return_value=True),
|
||||
)
|
||||
|
||||
mocker.patch(
|
||||
"freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_categories_list",
|
||||
return_value=[
|
||||
{"category_id": "layer-1"},
|
||||
{"category_id": "protocol"},
|
||||
{"category_id": "defi"},
|
||||
],
|
||||
)
|
||||
|
||||
gcm_mock = mocker.patch(
|
||||
"freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_markets",
|
||||
return_value=test_value,
|
||||
)
|
||||
@@ -2319,6 +2357,15 @@ def test_MarketCapPairList_filter(
|
||||
|
||||
pm = PairListManager(exchange, default_conf_usdt)
|
||||
pm.refresh_pairlist()
|
||||
if isinstance(coin_market_calls, int):
|
||||
assert gcm_mock.call_count == coin_market_calls
|
||||
else:
|
||||
assert gcm_mock.call_count == len(coin_market_calls)
|
||||
for call in coin_market_calls:
|
||||
assert any(
|
||||
"category" in c.kwargs and c.kwargs["category"] == call
|
||||
for c in gcm_mock.call_args_list
|
||||
)
|
||||
|
||||
assert pm.whitelist == result
|
||||
|
||||
@@ -2376,6 +2423,33 @@ def test_MarketCapPairList_timing(mocker, default_conf_usdt, markets, time_machi
|
||||
assert markets_mock.call_count == 3
|
||||
|
||||
|
||||
def test_MarketCapPairList_filter_special_no_pair_from_coingecko(
|
||||
mocker,
|
||||
default_conf_usdt,
|
||||
markets,
|
||||
):
|
||||
default_conf_usdt["pairlists"] = [{"method": "MarketCapPairList", "number_assets": 2}]
|
||||
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
markets=PropertyMock(return_value=markets),
|
||||
exchange_has=MagicMock(return_value=True),
|
||||
)
|
||||
|
||||
# Simulate no pair returned from coingecko
|
||||
gcm_mock = mocker.patch(
|
||||
"freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_markets",
|
||||
return_value=[],
|
||||
)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf_usdt)
|
||||
|
||||
pm = PairListManager(exchange, default_conf_usdt)
|
||||
pm.refresh_pairlist()
|
||||
assert gcm_mock.call_count == 1
|
||||
assert pm.whitelist == []
|
||||
|
||||
|
||||
def test_MarketCapPairList_exceptions(mocker, default_conf_usdt):
|
||||
exchange = get_patched_exchange(mocker, default_conf_usdt)
|
||||
default_conf_usdt["pairlists"] = [{"method": "MarketCapPairList"}]
|
||||
@@ -2391,6 +2465,27 @@ def test_MarketCapPairList_exceptions(mocker, default_conf_usdt):
|
||||
):
|
||||
PairListManager(exchange, default_conf_usdt)
|
||||
|
||||
# Test invalid coinmarkets list
|
||||
mocker.patch(
|
||||
"freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_categories_list",
|
||||
return_value=[
|
||||
{"category_id": "layer-1"},
|
||||
{"category_id": "protocol"},
|
||||
{"category_id": "defi"},
|
||||
],
|
||||
)
|
||||
default_conf_usdt["pairlists"] = [
|
||||
{
|
||||
"method": "MarketCapPairList",
|
||||
"number_assets": 20,
|
||||
"categories": ["layer-1", "defi", "layer250"],
|
||||
}
|
||||
]
|
||||
with pytest.raises(
|
||||
OperationalException, match="Category layer250 not in coingecko category list."
|
||||
):
|
||||
PairListManager(exchange, default_conf_usdt)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pairlists,expected_error,expected_warning",
|
||||
|
||||
@@ -3,14 +3,17 @@ from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade import constants
|
||||
from freqtrade.enums import ExitType
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.persistence import PairLocks, Trade
|
||||
from freqtrade.persistence.trade_model import Order
|
||||
from freqtrade.plugins.protectionmanager import ProtectionManager
|
||||
from tests.conftest import get_patched_freqtradebot, log_has_re
|
||||
|
||||
|
||||
AVAILABLE_PROTECTIONS = ["CooldownPeriod", "LowProfitPairs", "MaxDrawdown", "StoplossGuard"]
|
||||
|
||||
|
||||
def generate_mock_trade(
|
||||
pair: str,
|
||||
fee: float,
|
||||
@@ -88,19 +91,76 @@ def generate_mock_trade(
|
||||
|
||||
|
||||
def test_protectionmanager(mocker, default_conf):
|
||||
default_conf["protections"] = [
|
||||
{"method": protection} for protection in constants.AVAILABLE_PROTECTIONS
|
||||
default_conf["_strategy_protections"] = [
|
||||
{"method": protection} for protection in AVAILABLE_PROTECTIONS
|
||||
]
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
for handler in freqtrade.protections._protection_handlers:
|
||||
assert handler.name in constants.AVAILABLE_PROTECTIONS
|
||||
assert handler.name in AVAILABLE_PROTECTIONS
|
||||
if not handler.has_global_stop:
|
||||
assert handler.global_stop(datetime.now(timezone.utc), "*") is None
|
||||
if not handler.has_local_stop:
|
||||
assert handler.stop_per_pair("XRP/BTC", datetime.now(timezone.utc), "*") is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"protconf,expected",
|
||||
[
|
||||
([], None),
|
||||
([{"method": "StoplossGuard", "lookback_period": 2000, "stop_duration_candles": 10}], None),
|
||||
([{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 10}], None),
|
||||
(
|
||||
[
|
||||
{
|
||||
"method": "StoplossGuard",
|
||||
"lookback_period_candles": 20,
|
||||
"lookback_period": 2000,
|
||||
"stop_duration": 10,
|
||||
}
|
||||
],
|
||||
r"Protections must specify either `lookback_period`.*",
|
||||
),
|
||||
(
|
||||
[
|
||||
{
|
||||
"method": "StoplossGuard",
|
||||
"lookback_period": 20,
|
||||
"stop_duration": 10,
|
||||
"stop_duration_candles": 10,
|
||||
}
|
||||
],
|
||||
r"Protections must specify either `stop_duration`.*",
|
||||
),
|
||||
(
|
||||
[
|
||||
{
|
||||
"method": "StoplossGuard",
|
||||
"lookback_period": 20,
|
||||
"stop_duration": 10,
|
||||
"unlock_at": "20:02",
|
||||
}
|
||||
],
|
||||
r"Protections must specify either `unlock_at`, `stop_duration` or.*",
|
||||
),
|
||||
(
|
||||
[{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "20:02"}],
|
||||
None,
|
||||
),
|
||||
(
|
||||
[{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "55:102"}],
|
||||
"Invalid date format for unlock_at: 55:102.",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_validate_protections(protconf, expected):
|
||||
if expected:
|
||||
with pytest.raises(OperationalException, match=expected):
|
||||
ProtectionManager.validate_protections(protconf)
|
||||
else:
|
||||
ProtectionManager.validate_protections(protconf)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"timeframe,expected_lookback,expected_stop,protconf",
|
||||
[
|
||||
@@ -196,7 +256,7 @@ def test_protections_init(default_conf, timeframe, expected_lookback, expected_s
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_stoploss_guard(mocker, default_conf, fee, caplog, is_short):
|
||||
# Active for both sides (long and short)
|
||||
default_conf["protections"] = [
|
||||
default_conf["_strategy_protections"] = [
|
||||
{"method": "StoplossGuard", "lookback_period": 60, "stop_duration": 40, "trade_limit": 3}
|
||||
]
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
@@ -268,7 +328,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog, is_short):
|
||||
@pytest.mark.parametrize("only_per_side", [False, True])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair, only_per_side):
|
||||
default_conf["protections"] = [
|
||||
default_conf["_strategy_protections"] = [
|
||||
{
|
||||
"method": "StoplossGuard",
|
||||
"lookback_period": 60,
|
||||
@@ -379,7 +439,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_CooldownPeriod(mocker, default_conf, fee, caplog):
|
||||
default_conf["protections"] = [
|
||||
default_conf["_strategy_protections"] = [
|
||||
{
|
||||
"method": "CooldownPeriod",
|
||||
"stop_duration": 60,
|
||||
@@ -425,7 +485,7 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog):
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_CooldownPeriod_unlock_at(mocker, default_conf, fee, caplog, time_machine):
|
||||
default_conf["protections"] = [
|
||||
default_conf["_strategy_protections"] = [
|
||||
{
|
||||
"method": "CooldownPeriod",
|
||||
"unlock_at": "05:00",
|
||||
@@ -509,7 +569,7 @@ def test_CooldownPeriod_unlock_at(mocker, default_conf, fee, caplog, time_machin
|
||||
@pytest.mark.parametrize("only_per_side", [False, True])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_LowProfitPairs(mocker, default_conf, fee, caplog, only_per_side):
|
||||
default_conf["protections"] = [
|
||||
default_conf["_strategy_protections"] = [
|
||||
{
|
||||
"method": "LowProfitPairs",
|
||||
"lookback_period": 400,
|
||||
@@ -599,7 +659,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog, only_per_side):
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_MaxDrawdown(mocker, default_conf, fee, caplog):
|
||||
default_conf["protections"] = [
|
||||
default_conf["_strategy_protections"] = [
|
||||
{
|
||||
"method": "MaxDrawdown",
|
||||
"lookback_period": 1000,
|
||||
@@ -812,7 +872,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
|
||||
def test_protection_manager_desc(
|
||||
mocker, default_conf, protectionconf, desc_expected, exception_expected
|
||||
):
|
||||
default_conf["protections"] = [protectionconf]
|
||||
default_conf["_strategy_protections"] = [protectionconf]
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
short_desc = str(freqtrade.protections.short_desc())
|
||||
|
||||
@@ -1269,7 +1269,7 @@ def test_api_mix_tag(botclient, fee):
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"is_short,current_rate,open_trade_value",
|
||||
[(True, 1.098e-05, 15.0911775), (False, 1.099e-05, 15.1668225)],
|
||||
[(True, 1.098e-05, 6.134625), (False, 1.099e-05, 6.165375)],
|
||||
)
|
||||
def test_api_status(
|
||||
botclient, mocker, ticker, fee, markets, is_short, current_rate, open_trade_value
|
||||
@@ -1294,7 +1294,7 @@ def test_api_status(
|
||||
assert_response(rc)
|
||||
assert len(rc.json()) == 4
|
||||
assert rc.json()[0] == {
|
||||
"amount": 123.0,
|
||||
"amount": 50.0,
|
||||
"amount_requested": 123.0,
|
||||
"close_date": None,
|
||||
"close_timestamp": None,
|
||||
|
||||
@@ -173,7 +173,7 @@ def test_startupmessages_telegram_enabled(mocker, default_conf) -> None:
|
||||
telegram_mock.reset_mock()
|
||||
default_conf["dry_run"] = True
|
||||
default_conf["whitelist"] = {"method": "VolumePairList", "config": {"number_assets": 20}}
|
||||
default_conf["protections"] = [
|
||||
default_conf["_strategy_protections"] = [
|
||||
{"method": "StoplossGuard", "lookback_period": 60, "trade_limit": 2, "stop_duration": 60}
|
||||
]
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
@@ -75,15 +75,13 @@ class StrategyTestV3(IStrategy):
|
||||
protection_cooldown_lookback = IntParameter([0, 50], default=30)
|
||||
|
||||
# TODO: Can this work with protection tests? (replace HyperoptableStrategy implicitly ... )
|
||||
# @property
|
||||
# def protections(self):
|
||||
# prot = []
|
||||
# if self.protection_enabled.value:
|
||||
# prot.append({
|
||||
# "method": "CooldownPeriod",
|
||||
# "stop_duration_candles": self.protection_cooldown_lookback.value
|
||||
# })
|
||||
# return prot
|
||||
@property
|
||||
def protections(self):
|
||||
prot = []
|
||||
if self.protection_enabled.value:
|
||||
# Workaround to simplify tests. This will not work in real scenarios.
|
||||
prot = self.config.get("_strategy_protections", {})
|
||||
return prot
|
||||
|
||||
bot_started = False
|
||||
|
||||
|
||||
@@ -812,65 +812,6 @@ def test_validate_whitelist(default_conf):
|
||||
validate_config_consistency(conf)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"protconf,expected",
|
||||
[
|
||||
([], None),
|
||||
([{"method": "StoplossGuard", "lookback_period": 2000, "stop_duration_candles": 10}], None),
|
||||
([{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 10}], None),
|
||||
(
|
||||
[
|
||||
{
|
||||
"method": "StoplossGuard",
|
||||
"lookback_period_candles": 20,
|
||||
"lookback_period": 2000,
|
||||
"stop_duration": 10,
|
||||
}
|
||||
],
|
||||
r"Protections must specify either `lookback_period`.*",
|
||||
),
|
||||
(
|
||||
[
|
||||
{
|
||||
"method": "StoplossGuard",
|
||||
"lookback_period": 20,
|
||||
"stop_duration": 10,
|
||||
"stop_duration_candles": 10,
|
||||
}
|
||||
],
|
||||
r"Protections must specify either `stop_duration`.*",
|
||||
),
|
||||
(
|
||||
[
|
||||
{
|
||||
"method": "StoplossGuard",
|
||||
"lookback_period": 20,
|
||||
"stop_duration": 10,
|
||||
"unlock_at": "20:02",
|
||||
}
|
||||
],
|
||||
r"Protections must specify either `unlock_at`, `stop_duration` or.*",
|
||||
),
|
||||
(
|
||||
[{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "20:02"}],
|
||||
None,
|
||||
),
|
||||
(
|
||||
[{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "55:102"}],
|
||||
"Invalid date format for unlock_at: 55:102.",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_validate_protections(default_conf, protconf, expected):
|
||||
conf = deepcopy(default_conf)
|
||||
conf["protections"] = protconf
|
||||
if expected:
|
||||
with pytest.raises(OperationalException, match=expected):
|
||||
validate_config_consistency(conf)
|
||||
else:
|
||||
validate_config_consistency(conf)
|
||||
|
||||
|
||||
def test_validate_ask_orderbook(default_conf, caplog) -> None:
|
||||
conf = deepcopy(default_conf)
|
||||
conf["exit_pricing"]["use_order_book"] = True
|
||||
@@ -1533,8 +1474,8 @@ def test_process_deprecated_protections(default_conf, caplog):
|
||||
assert not log_has(message, caplog)
|
||||
|
||||
config["protections"] = []
|
||||
process_temporary_deprecated_settings(config)
|
||||
assert log_has(message, caplog)
|
||||
with pytest.raises(ConfigurationError, match=message):
|
||||
process_temporary_deprecated_settings(config)
|
||||
|
||||
|
||||
def test_flat_vars_to_nested_dict(caplog):
|
||||
|
||||
@@ -362,7 +362,8 @@ def test_sync_wallet_dry(mocker, default_conf_usdt, fee):
|
||||
assert len(freqtrade.wallets._wallets) == 5
|
||||
assert len(freqtrade.wallets._positions) == 0
|
||||
bal = freqtrade.wallets.get_all_balances()
|
||||
assert bal["NEO"].total == 10
|
||||
# NEO trade is not filled yet.
|
||||
assert bal["NEO"].total == 0
|
||||
assert bal["XRP"].total == 10
|
||||
assert bal["LTC"].total == 2
|
||||
usdt_bal = bal["USDT"]
|
||||
@@ -410,11 +411,11 @@ def test_sync_wallet_futures_dry(mocker, default_conf, fee):
|
||||
def test_check_exit_amount(mocker, default_conf, fee):
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
update_mock = mocker.patch("freqtrade.wallets.Wallets.update")
|
||||
total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=123)
|
||||
total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=50.0)
|
||||
|
||||
create_mock_trades(fee, is_short=None)
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade.amount == 123
|
||||
assert trade.amount == 50.0
|
||||
|
||||
assert freqtrade.wallets.check_exit_amount(trade) is True
|
||||
assert update_mock.call_count == 0
|
||||
@@ -423,7 +424,7 @@ def test_check_exit_amount(mocker, default_conf, fee):
|
||||
update_mock.reset_mock()
|
||||
# Reduce returned amount to below the trade amount - which should
|
||||
# trigger a wallet update and return False, triggering "order refinding"
|
||||
total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=100)
|
||||
total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=40)
|
||||
assert freqtrade.wallets.check_exit_amount(trade) is False
|
||||
assert update_mock.call_count == 1
|
||||
assert total_mock.call_count == 2
|
||||
@@ -433,12 +434,12 @@ def test_check_exit_amount_futures(mocker, default_conf, fee):
|
||||
default_conf["trading_mode"] = "futures"
|
||||
default_conf["margin_mode"] = "isolated"
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=123)
|
||||
total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=50)
|
||||
|
||||
create_mock_trades(fee, is_short=None)
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.trading_mode = "futures"
|
||||
assert trade.amount == 123
|
||||
assert trade.amount == 50
|
||||
|
||||
assert freqtrade.wallets.check_exit_amount(trade) is True
|
||||
assert total_mock.call_count == 0
|
||||
|
||||
Reference in New Issue
Block a user