mirror of
https://github.com/freqtrade/freqtrade.git
synced 2026-01-20 05:50:36 +00:00
ruff format: tests/plugins
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -7,10 +7,10 @@ from freqtrade.persistence.models import PairLock
|
||||
from freqtrade.util import dt_now
|
||||
|
||||
|
||||
@pytest.mark.parametrize('use_db', (False, True))
|
||||
@pytest.mark.parametrize("use_db", (False, True))
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_PairLocks(use_db):
|
||||
PairLocks.timeframe = '5m'
|
||||
PairLocks.timeframe = "5m"
|
||||
PairLocks.use_db = use_db
|
||||
# No lock should be present
|
||||
if use_db:
|
||||
@@ -18,28 +18,28 @@ def test_PairLocks(use_db):
|
||||
|
||||
assert PairLocks.use_db == use_db
|
||||
|
||||
pair = 'ETH/BTC'
|
||||
pair = "ETH/BTC"
|
||||
assert not PairLocks.is_pair_locked(pair)
|
||||
PairLocks.lock_pair(pair, dt_now() + timedelta(minutes=4))
|
||||
# ETH/BTC locked for 4 minutes (on both sides)
|
||||
assert PairLocks.is_pair_locked(pair)
|
||||
assert PairLocks.is_pair_locked(pair, side='long')
|
||||
assert PairLocks.is_pair_locked(pair, side='short')
|
||||
assert PairLocks.is_pair_locked(pair, side="long")
|
||||
assert PairLocks.is_pair_locked(pair, side="short")
|
||||
|
||||
pair = 'BNB/BTC'
|
||||
PairLocks.lock_pair(pair, dt_now() + timedelta(minutes=4), side='long')
|
||||
pair = "BNB/BTC"
|
||||
PairLocks.lock_pair(pair, dt_now() + timedelta(minutes=4), side="long")
|
||||
assert not PairLocks.is_pair_locked(pair)
|
||||
assert PairLocks.is_pair_locked(pair, side='long')
|
||||
assert not PairLocks.is_pair_locked(pair, side='short')
|
||||
assert PairLocks.is_pair_locked(pair, side="long")
|
||||
assert not PairLocks.is_pair_locked(pair, side="short")
|
||||
|
||||
pair = 'BNB/USDT'
|
||||
PairLocks.lock_pair(pair, dt_now() + timedelta(minutes=4), side='short')
|
||||
pair = "BNB/USDT"
|
||||
PairLocks.lock_pair(pair, dt_now() + timedelta(minutes=4), side="short")
|
||||
assert not PairLocks.is_pair_locked(pair)
|
||||
assert not PairLocks.is_pair_locked(pair, side='long')
|
||||
assert PairLocks.is_pair_locked(pair, side='short')
|
||||
assert not PairLocks.is_pair_locked(pair, side="long")
|
||||
assert PairLocks.is_pair_locked(pair, side="short")
|
||||
|
||||
# XRP/BTC should not be locked now
|
||||
pair = 'XRP/BTC'
|
||||
pair = "XRP/BTC"
|
||||
assert not PairLocks.is_pair_locked(pair)
|
||||
# Unlocking a pair that's not locked should not raise an error
|
||||
PairLocks.unlock_pair(pair)
|
||||
@@ -52,12 +52,12 @@ def test_PairLocks(use_db):
|
||||
assert len(locks) == 2
|
||||
|
||||
# Unlock original pair
|
||||
pair = 'ETH/BTC'
|
||||
pair = "ETH/BTC"
|
||||
PairLocks.unlock_pair(pair)
|
||||
assert not PairLocks.is_pair_locked(pair)
|
||||
assert not PairLocks.is_global_lock()
|
||||
|
||||
pair = 'BTC/USDT'
|
||||
pair = "BTC/USDT"
|
||||
# Lock until 14:30
|
||||
lock_time = datetime(2020, 5, 1, 14, 30, 0, tzinfo=timezone.utc)
|
||||
PairLocks.lock_pair(pair, lock_time)
|
||||
@@ -73,18 +73,18 @@ def test_PairLocks(use_db):
|
||||
|
||||
locks = PairLocks.get_pair_locks(pair, lock_time + timedelta(minutes=-2))
|
||||
assert len(locks) == 1
|
||||
assert 'PairLock' in str(locks[0])
|
||||
assert "PairLock" in str(locks[0])
|
||||
|
||||
# Unlock all
|
||||
PairLocks.unlock_pair(pair, lock_time + timedelta(minutes=-2))
|
||||
assert not PairLocks.is_global_lock(lock_time + timedelta(minutes=-50))
|
||||
|
||||
# Global lock
|
||||
PairLocks.lock_pair('*', lock_time)
|
||||
PairLocks.lock_pair("*", lock_time)
|
||||
assert PairLocks.is_global_lock(lock_time + timedelta(minutes=-50))
|
||||
# Global lock also locks every pair separately
|
||||
assert PairLocks.is_pair_locked(pair, lock_time + timedelta(minutes=-50))
|
||||
assert PairLocks.is_pair_locked('XRP/USDT', lock_time + timedelta(minutes=-50))
|
||||
assert PairLocks.is_pair_locked("XRP/USDT", lock_time + timedelta(minutes=-50))
|
||||
|
||||
if use_db:
|
||||
locks = PairLocks.get_all_locks()
|
||||
@@ -100,10 +100,10 @@ def test_PairLocks(use_db):
|
||||
PairLocks.use_db = True
|
||||
|
||||
|
||||
@pytest.mark.parametrize('use_db', (False, True))
|
||||
@pytest.mark.parametrize("use_db", (False, True))
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_PairLocks_getlongestlock(use_db):
|
||||
PairLocks.timeframe = '5m'
|
||||
PairLocks.timeframe = "5m"
|
||||
# No lock should be present
|
||||
PairLocks.use_db = use_db
|
||||
if use_db:
|
||||
@@ -111,7 +111,7 @@ def test_PairLocks_getlongestlock(use_db):
|
||||
|
||||
assert PairLocks.use_db == use_db
|
||||
|
||||
pair = 'ETH/BTC'
|
||||
pair = "ETH/BTC"
|
||||
assert not PairLocks.is_pair_locked(pair)
|
||||
PairLocks.lock_pair(pair, dt_now() + timedelta(minutes=4))
|
||||
# ETH/BTC locked for 4 minutes
|
||||
@@ -132,10 +132,10 @@ def test_PairLocks_getlongestlock(use_db):
|
||||
PairLocks.use_db = True
|
||||
|
||||
|
||||
@pytest.mark.parametrize('use_db', (False, True))
|
||||
@pytest.mark.parametrize("use_db", (False, True))
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_PairLocks_reason(use_db):
|
||||
PairLocks.timeframe = '5m'
|
||||
PairLocks.timeframe = "5m"
|
||||
PairLocks.use_db = use_db
|
||||
# No lock should be present
|
||||
if use_db:
|
||||
@@ -143,15 +143,15 @@ def test_PairLocks_reason(use_db):
|
||||
|
||||
assert PairLocks.use_db == use_db
|
||||
|
||||
PairLocks.lock_pair('XRP/USDT', dt_now() + timedelta(minutes=4), 'TestLock1')
|
||||
PairLocks.lock_pair('ETH/USDT', dt_now() + timedelta(minutes=4), 'TestLock2')
|
||||
PairLocks.lock_pair("XRP/USDT", dt_now() + timedelta(minutes=4), "TestLock1")
|
||||
PairLocks.lock_pair("ETH/USDT", dt_now() + timedelta(minutes=4), "TestLock2")
|
||||
|
||||
assert PairLocks.is_pair_locked('XRP/USDT')
|
||||
assert PairLocks.is_pair_locked('ETH/USDT')
|
||||
assert PairLocks.is_pair_locked("XRP/USDT")
|
||||
assert PairLocks.is_pair_locked("ETH/USDT")
|
||||
|
||||
PairLocks.unlock_reason('TestLock1')
|
||||
assert not PairLocks.is_pair_locked('XRP/USDT')
|
||||
assert PairLocks.is_pair_locked('ETH/USDT')
|
||||
PairLocks.unlock_reason("TestLock1")
|
||||
assert not PairLocks.is_pair_locked("XRP/USDT")
|
||||
assert PairLocks.is_pair_locked("ETH/USDT")
|
||||
|
||||
PairLocks.reset_locks()
|
||||
PairLocks.use_db = True
|
||||
|
||||
@@ -11,12 +11,16 @@ from freqtrade.plugins.protectionmanager import ProtectionManager
|
||||
from tests.conftest import get_patched_freqtradebot, log_has_re
|
||||
|
||||
|
||||
def generate_mock_trade(pair: str, fee: float, is_open: bool,
|
||||
exit_reason: str = ExitType.EXIT_SIGNAL,
|
||||
min_ago_open: int = None, min_ago_close: int = None,
|
||||
profit_rate: float = 0.9,
|
||||
is_short: bool = False,
|
||||
):
|
||||
def generate_mock_trade(
|
||||
pair: str,
|
||||
fee: float,
|
||||
is_open: bool,
|
||||
exit_reason: str = ExitType.EXIT_SIGNAL,
|
||||
min_ago_open: int = None,
|
||||
min_ago_close: int = None,
|
||||
profit_rate: float = 0.9,
|
||||
is_short: bool = False,
|
||||
):
|
||||
open_rate = random.random()
|
||||
|
||||
trade = Trade(
|
||||
@@ -29,32 +33,15 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool,
|
||||
open_rate=open_rate,
|
||||
is_open=is_open,
|
||||
amount=0.01 / open_rate,
|
||||
exchange='binance',
|
||||
exchange="binance",
|
||||
is_short=is_short,
|
||||
leverage=1,
|
||||
)
|
||||
|
||||
trade.orders.append(Order(
|
||||
ft_order_side=trade.entry_side,
|
||||
order_id=f'{pair}-{trade.entry_side}-{trade.open_date}',
|
||||
ft_is_open=False,
|
||||
ft_pair=pair,
|
||||
ft_amount=trade.amount,
|
||||
ft_price=trade.open_rate,
|
||||
amount=trade.amount,
|
||||
filled=trade.amount,
|
||||
remaining=0,
|
||||
price=open_rate,
|
||||
average=open_rate,
|
||||
status="closed",
|
||||
order_type="market",
|
||||
side=trade.entry_side,
|
||||
))
|
||||
if not is_open:
|
||||
close_price = open_rate * (2 - profit_rate if is_short else profit_rate)
|
||||
trade.orders.append(Order(
|
||||
ft_order_side=trade.exit_side,
|
||||
order_id=f'{pair}-{trade.exit_side}-{trade.close_date}',
|
||||
trade.orders.append(
|
||||
Order(
|
||||
ft_order_side=trade.entry_side,
|
||||
order_id=f"{pair}-{trade.entry_side}-{trade.open_date}",
|
||||
ft_is_open=False,
|
||||
ft_pair=pair,
|
||||
ft_amount=trade.amount,
|
||||
@@ -62,12 +49,33 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool,
|
||||
amount=trade.amount,
|
||||
filled=trade.amount,
|
||||
remaining=0,
|
||||
price=close_price,
|
||||
average=close_price,
|
||||
price=open_rate,
|
||||
average=open_rate,
|
||||
status="closed",
|
||||
order_type="market",
|
||||
side=trade.exit_side,
|
||||
))
|
||||
side=trade.entry_side,
|
||||
)
|
||||
)
|
||||
if not is_open:
|
||||
close_price = open_rate * (2 - profit_rate if is_short else profit_rate)
|
||||
trade.orders.append(
|
||||
Order(
|
||||
ft_order_side=trade.exit_side,
|
||||
order_id=f"{pair}-{trade.exit_side}-{trade.close_date}",
|
||||
ft_is_open=False,
|
||||
ft_pair=pair,
|
||||
ft_amount=trade.amount,
|
||||
ft_price=trade.open_rate,
|
||||
amount=trade.amount,
|
||||
filled=trade.amount,
|
||||
remaining=0,
|
||||
price=close_price,
|
||||
average=close_price,
|
||||
status="closed",
|
||||
order_type="market",
|
||||
side=trade.exit_side,
|
||||
)
|
||||
)
|
||||
|
||||
trade.recalc_open_trade_value()
|
||||
if not is_open:
|
||||
@@ -80,54 +88,79 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool,
|
||||
|
||||
|
||||
def test_protectionmanager(mocker, default_conf):
|
||||
default_conf['protections'] = [{'method': protection}
|
||||
for protection in constants.AVAILABLE_PROTECTIONS]
|
||||
default_conf["protections"] = [
|
||||
{"method": protection} for protection in constants.AVAILABLE_PROTECTIONS
|
||||
]
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
for handler in freqtrade.protections._protection_handlers:
|
||||
assert handler.name in constants.AVAILABLE_PROTECTIONS
|
||||
if not handler.has_global_stop:
|
||||
assert handler.global_stop(datetime.now(timezone.utc), '*') is None
|
||||
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
|
||||
assert handler.stop_per_pair("XRP/BTC", datetime.now(timezone.utc), "*") is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize('timeframe,expected,protconf', [
|
||||
('1m', [20, 10],
|
||||
[{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 10}]),
|
||||
('5m', [100, 15],
|
||||
[{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 15}]),
|
||||
('1h', [1200, 40],
|
||||
[{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 40}]),
|
||||
('1d', [1440, 5],
|
||||
[{"method": "StoplossGuard", "lookback_period_candles": 1, "stop_duration": 5}]),
|
||||
('1m', [20, 5],
|
||||
[{"method": "StoplossGuard", "lookback_period": 20, "stop_duration_candles": 5}]),
|
||||
('5m', [15, 25],
|
||||
[{"method": "StoplossGuard", "lookback_period": 15, "stop_duration_candles": 5}]),
|
||||
('1h', [50, 600],
|
||||
[{"method": "StoplossGuard", "lookback_period": 50, "stop_duration_candles": 10}]),
|
||||
('1h', [60, 540],
|
||||
[{"method": "StoplossGuard", "lookback_period_candles": 1, "stop_duration_candles": 9}]),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"timeframe,expected,protconf",
|
||||
[
|
||||
(
|
||||
"1m",
|
||||
[20, 10],
|
||||
[{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 10}],
|
||||
),
|
||||
(
|
||||
"5m",
|
||||
[100, 15],
|
||||
[{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 15}],
|
||||
),
|
||||
(
|
||||
"1h",
|
||||
[1200, 40],
|
||||
[{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 40}],
|
||||
),
|
||||
(
|
||||
"1d",
|
||||
[1440, 5],
|
||||
[{"method": "StoplossGuard", "lookback_period_candles": 1, "stop_duration": 5}],
|
||||
),
|
||||
(
|
||||
"1m",
|
||||
[20, 5],
|
||||
[{"method": "StoplossGuard", "lookback_period": 20, "stop_duration_candles": 5}],
|
||||
),
|
||||
(
|
||||
"5m",
|
||||
[15, 25],
|
||||
[{"method": "StoplossGuard", "lookback_period": 15, "stop_duration_candles": 5}],
|
||||
),
|
||||
(
|
||||
"1h",
|
||||
[50, 600],
|
||||
[{"method": "StoplossGuard", "lookback_period": 50, "stop_duration_candles": 10}],
|
||||
),
|
||||
(
|
||||
"1h",
|
||||
[60, 540],
|
||||
[{"method": "StoplossGuard", "lookback_period_candles": 1, "stop_duration_candles": 9}],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_protections_init(default_conf, timeframe, expected, protconf):
|
||||
default_conf['timeframe'] = timeframe
|
||||
default_conf["timeframe"] = timeframe
|
||||
man = ProtectionManager(default_conf, protconf)
|
||||
assert len(man._protection_handlers) == len(protconf)
|
||||
assert man._protection_handlers[0]._lookback_period == expected[0]
|
||||
assert man._protection_handlers[0]._stop_duration == expected[1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('is_short', [False, True])
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
@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'] = [{
|
||||
"method": "StoplossGuard",
|
||||
"lookback_period": 60,
|
||||
"stop_duration": 40,
|
||||
"trade_limit": 3
|
||||
}]
|
||||
default_conf["protections"] = [
|
||||
{"method": "StoplossGuard", "lookback_period": 60, "stop_duration": 40, "trade_limit": 3}
|
||||
]
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
message = r"Trading stopped due to .*"
|
||||
assert not freqtrade.protections.global_stop()
|
||||
@@ -135,8 +168,13 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog, is_short):
|
||||
caplog.clear()
|
||||
|
||||
generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=200, min_ago_close=30, is_short=is_short,
|
||||
"XRP/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=200,
|
||||
min_ago_close=30,
|
||||
is_short=is_short,
|
||||
)
|
||||
|
||||
assert not freqtrade.protections.global_stop()
|
||||
@@ -144,13 +182,23 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog, is_short):
|
||||
caplog.clear()
|
||||
# This trade does not count, as it's closed too long ago
|
||||
generate_mock_trade(
|
||||
'BCH/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=250, min_ago_close=100, is_short=is_short,
|
||||
"BCH/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=250,
|
||||
min_ago_close=100,
|
||||
is_short=is_short,
|
||||
)
|
||||
|
||||
generate_mock_trade(
|
||||
'ETH/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=240, min_ago_close=30, is_short=is_short,
|
||||
"ETH/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=240,
|
||||
min_ago_close=30,
|
||||
is_short=is_short,
|
||||
)
|
||||
# 3 Trades closed - but the 2nd has been closed too long ago.
|
||||
assert not freqtrade.protections.global_stop()
|
||||
@@ -158,8 +206,13 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog, is_short):
|
||||
caplog.clear()
|
||||
|
||||
generate_mock_trade(
|
||||
'LTC/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=180, min_ago_close=30, is_short=is_short,
|
||||
"LTC/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=180,
|
||||
min_ago_close=30,
|
||||
is_short=is_short,
|
||||
)
|
||||
|
||||
assert freqtrade.protections.global_stop()
|
||||
@@ -168,36 +221,44 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog, is_short):
|
||||
|
||||
# Test 5m after lock-period - this should try and relock the pair, but end-time
|
||||
# should be the previous end-time
|
||||
end_time = PairLocks.get_pair_longest_lock('*').lock_end_time + timedelta(minutes=5)
|
||||
end_time = PairLocks.get_pair_longest_lock("*").lock_end_time + timedelta(minutes=5)
|
||||
freqtrade.protections.global_stop(end_time)
|
||||
assert not PairLocks.is_global_lock(end_time)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('only_per_pair', [False, True])
|
||||
@pytest.mark.parametrize('only_per_side', [False, True])
|
||||
@pytest.mark.parametrize("only_per_pair", [False, True])
|
||||
@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'] = [{
|
||||
"method": "StoplossGuard",
|
||||
"lookback_period": 60,
|
||||
"trade_limit": 2,
|
||||
"stop_duration": 60,
|
||||
"only_per_pair": only_per_pair,
|
||||
"only_per_side": only_per_side,
|
||||
}]
|
||||
check_side = 'long' if only_per_side else '*'
|
||||
default_conf["protections"] = [
|
||||
{
|
||||
"method": "StoplossGuard",
|
||||
"lookback_period": 60,
|
||||
"trade_limit": 2,
|
||||
"stop_duration": 60,
|
||||
"only_per_pair": only_per_pair,
|
||||
"only_per_side": only_per_side,
|
||||
}
|
||||
]
|
||||
check_side = "long" if only_per_side else "*"
|
||||
is_short = False
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
message = r"Trading stopped due to .*"
|
||||
pair = 'XRP/BTC'
|
||||
pair = "XRP/BTC"
|
||||
assert not freqtrade.protections.stop_per_pair(pair)
|
||||
assert not freqtrade.protections.global_stop()
|
||||
assert not log_has_re(message, caplog)
|
||||
caplog.clear()
|
||||
|
||||
generate_mock_trade(
|
||||
pair, fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=200, min_ago_close=30, profit_rate=0.9, is_short=is_short
|
||||
pair,
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=200,
|
||||
min_ago_close=30,
|
||||
profit_rate=0.9,
|
||||
is_short=is_short,
|
||||
)
|
||||
|
||||
assert not freqtrade.protections.stop_per_pair(pair)
|
||||
@@ -206,13 +267,25 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
|
||||
caplog.clear()
|
||||
# This trade does not count, as it's closed too long ago
|
||||
generate_mock_trade(
|
||||
pair, fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=250, min_ago_close=100, profit_rate=0.9, is_short=is_short
|
||||
pair,
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=250,
|
||||
min_ago_close=100,
|
||||
profit_rate=0.9,
|
||||
is_short=is_short,
|
||||
)
|
||||
# Trade does not count for per pair stop as it's the wrong pair.
|
||||
generate_mock_trade(
|
||||
'ETH/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=240, min_ago_close=30, profit_rate=0.9, is_short=is_short
|
||||
"ETH/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=240,
|
||||
min_ago_close=30,
|
||||
profit_rate=0.9,
|
||||
is_short=is_short,
|
||||
)
|
||||
# 3 Trades closed - but the 2nd has been closed too long ago.
|
||||
assert not freqtrade.protections.stop_per_pair(pair)
|
||||
@@ -226,23 +299,35 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
|
||||
|
||||
# Trade does not count potentially, as it's in the wrong direction
|
||||
generate_mock_trade(
|
||||
pair, fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=150, min_ago_close=25, profit_rate=0.9, is_short=not is_short
|
||||
pair,
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=150,
|
||||
min_ago_close=25,
|
||||
profit_rate=0.9,
|
||||
is_short=not is_short,
|
||||
)
|
||||
freqtrade.protections.stop_per_pair(pair)
|
||||
assert freqtrade.protections.global_stop() != only_per_pair
|
||||
assert PairLocks.is_pair_locked(pair, side=check_side) != (only_per_side and only_per_pair)
|
||||
assert PairLocks.is_global_lock(side=check_side) != only_per_pair
|
||||
if only_per_side:
|
||||
assert not PairLocks.is_pair_locked(pair, side='*')
|
||||
assert not PairLocks.is_global_lock(side='*')
|
||||
assert not PairLocks.is_pair_locked(pair, side="*")
|
||||
assert not PairLocks.is_global_lock(side="*")
|
||||
|
||||
caplog.clear()
|
||||
|
||||
# 2nd Trade that counts with correct pair
|
||||
generate_mock_trade(
|
||||
pair, fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=180, min_ago_close=31, profit_rate=0.9, is_short=is_short
|
||||
pair,
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=180,
|
||||
min_ago_close=31,
|
||||
profit_rate=0.9,
|
||||
is_short=is_short,
|
||||
)
|
||||
|
||||
freqtrade.protections.stop_per_pair(pair)
|
||||
@@ -250,174 +335,239 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
|
||||
assert PairLocks.is_pair_locked(pair, side=check_side)
|
||||
assert PairLocks.is_global_lock(side=check_side) != only_per_pair
|
||||
if only_per_side:
|
||||
assert not PairLocks.is_pair_locked(pair, side='*')
|
||||
assert not PairLocks.is_global_lock(side='*')
|
||||
assert not PairLocks.is_pair_locked(pair, side="*")
|
||||
assert not PairLocks.is_global_lock(side="*")
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_CooldownPeriod(mocker, default_conf, fee, caplog):
|
||||
default_conf['protections'] = [{
|
||||
"method": "CooldownPeriod",
|
||||
"stop_duration": 60,
|
||||
}]
|
||||
default_conf["protections"] = [
|
||||
{
|
||||
"method": "CooldownPeriod",
|
||||
"stop_duration": 60,
|
||||
}
|
||||
]
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
message = r"Trading stopped due to .*"
|
||||
assert not freqtrade.protections.global_stop()
|
||||
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
||||
assert not freqtrade.protections.stop_per_pair("XRP/BTC")
|
||||
|
||||
assert not log_has_re(message, caplog)
|
||||
caplog.clear()
|
||||
|
||||
generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=200, min_ago_close=30,
|
||||
"XRP/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=200,
|
||||
min_ago_close=30,
|
||||
)
|
||||
|
||||
assert not freqtrade.protections.global_stop()
|
||||
assert freqtrade.protections.stop_per_pair('XRP/BTC')
|
||||
assert PairLocks.is_pair_locked('XRP/BTC')
|
||||
assert freqtrade.protections.stop_per_pair("XRP/BTC")
|
||||
assert PairLocks.is_pair_locked("XRP/BTC")
|
||||
assert not PairLocks.is_global_lock()
|
||||
|
||||
generate_mock_trade(
|
||||
'ETH/BTC', fee.return_value, False, exit_reason=ExitType.ROI.value,
|
||||
min_ago_open=205, min_ago_close=35,
|
||||
"ETH/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.ROI.value,
|
||||
min_ago_open=205,
|
||||
min_ago_close=35,
|
||||
)
|
||||
|
||||
assert not freqtrade.protections.global_stop()
|
||||
assert not PairLocks.is_pair_locked('ETH/BTC')
|
||||
assert freqtrade.protections.stop_per_pair('ETH/BTC')
|
||||
assert PairLocks.is_pair_locked('ETH/BTC')
|
||||
assert not PairLocks.is_pair_locked("ETH/BTC")
|
||||
assert freqtrade.protections.stop_per_pair("ETH/BTC")
|
||||
assert PairLocks.is_pair_locked("ETH/BTC")
|
||||
assert not PairLocks.is_global_lock()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('only_per_side', [False, True])
|
||||
@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'] = [{
|
||||
"method": "LowProfitPairs",
|
||||
"lookback_period": 400,
|
||||
"stop_duration": 60,
|
||||
"trade_limit": 2,
|
||||
"required_profit": 0.0,
|
||||
"only_per_side": only_per_side,
|
||||
}]
|
||||
default_conf["protections"] = [
|
||||
{
|
||||
"method": "LowProfitPairs",
|
||||
"lookback_period": 400,
|
||||
"stop_duration": 60,
|
||||
"trade_limit": 2,
|
||||
"required_profit": 0.0,
|
||||
"only_per_side": only_per_side,
|
||||
}
|
||||
]
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
message = r"Trading stopped due to .*"
|
||||
assert not freqtrade.protections.global_stop()
|
||||
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
||||
assert not freqtrade.protections.stop_per_pair("XRP/BTC")
|
||||
|
||||
assert not log_has_re(message, caplog)
|
||||
caplog.clear()
|
||||
|
||||
generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=800, min_ago_close=450, profit_rate=0.9,
|
||||
"XRP/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=800,
|
||||
min_ago_close=450,
|
||||
profit_rate=0.9,
|
||||
)
|
||||
|
||||
Trade.commit()
|
||||
# Not locked with 1 trade
|
||||
assert not freqtrade.protections.global_stop()
|
||||
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
||||
assert not PairLocks.is_pair_locked('XRP/BTC')
|
||||
assert not freqtrade.protections.stop_per_pair("XRP/BTC")
|
||||
assert not PairLocks.is_pair_locked("XRP/BTC")
|
||||
assert not PairLocks.is_global_lock()
|
||||
|
||||
generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=200, min_ago_close=120, profit_rate=0.9,
|
||||
"XRP/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=200,
|
||||
min_ago_close=120,
|
||||
profit_rate=0.9,
|
||||
)
|
||||
|
||||
Trade.commit()
|
||||
# Not locked with 1 trade (first trade is outside of lookback_period)
|
||||
assert not freqtrade.protections.global_stop()
|
||||
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
||||
assert not PairLocks.is_pair_locked('XRP/BTC')
|
||||
assert not freqtrade.protections.stop_per_pair("XRP/BTC")
|
||||
assert not PairLocks.is_pair_locked("XRP/BTC")
|
||||
assert not PairLocks.is_global_lock()
|
||||
|
||||
# Add positive trade
|
||||
generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.ROI.value,
|
||||
min_ago_open=20, min_ago_close=10, profit_rate=1.15, is_short=True
|
||||
"XRP/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.ROI.value,
|
||||
min_ago_open=20,
|
||||
min_ago_close=10,
|
||||
profit_rate=1.15,
|
||||
is_short=True,
|
||||
)
|
||||
Trade.commit()
|
||||
assert freqtrade.protections.stop_per_pair('XRP/BTC') != only_per_side
|
||||
assert not PairLocks.is_pair_locked('XRP/BTC', side='*')
|
||||
assert PairLocks.is_pair_locked('XRP/BTC', side='long') == only_per_side
|
||||
assert freqtrade.protections.stop_per_pair("XRP/BTC") != only_per_side
|
||||
assert not PairLocks.is_pair_locked("XRP/BTC", side="*")
|
||||
assert PairLocks.is_pair_locked("XRP/BTC", side="long") == only_per_side
|
||||
|
||||
generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=110, min_ago_close=21, profit_rate=0.8,
|
||||
"XRP/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=110,
|
||||
min_ago_close=21,
|
||||
profit_rate=0.8,
|
||||
)
|
||||
Trade.commit()
|
||||
|
||||
# Locks due to 2nd trade
|
||||
assert freqtrade.protections.global_stop() != only_per_side
|
||||
assert freqtrade.protections.stop_per_pair('XRP/BTC') != only_per_side
|
||||
assert PairLocks.is_pair_locked('XRP/BTC', side='long')
|
||||
assert PairLocks.is_pair_locked('XRP/BTC', side='*') != only_per_side
|
||||
assert freqtrade.protections.stop_per_pair("XRP/BTC") != only_per_side
|
||||
assert PairLocks.is_pair_locked("XRP/BTC", side="long")
|
||||
assert PairLocks.is_pair_locked("XRP/BTC", side="*") != only_per_side
|
||||
assert not PairLocks.is_global_lock()
|
||||
Trade.commit()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_MaxDrawdown(mocker, default_conf, fee, caplog):
|
||||
default_conf['protections'] = [{
|
||||
"method": "MaxDrawdown",
|
||||
"lookback_period": 1000,
|
||||
"stop_duration": 60,
|
||||
"trade_limit": 3,
|
||||
"max_allowed_drawdown": 0.15
|
||||
}]
|
||||
default_conf["protections"] = [
|
||||
{
|
||||
"method": "MaxDrawdown",
|
||||
"lookback_period": 1000,
|
||||
"stop_duration": 60,
|
||||
"trade_limit": 3,
|
||||
"max_allowed_drawdown": 0.15,
|
||||
}
|
||||
]
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
message = r"Trading stopped due to Max.*"
|
||||
|
||||
assert not freqtrade.protections.global_stop()
|
||||
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
||||
assert not freqtrade.protections.stop_per_pair("XRP/BTC")
|
||||
caplog.clear()
|
||||
|
||||
generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
|
||||
"XRP/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=1000,
|
||||
min_ago_close=900,
|
||||
profit_rate=1.1,
|
||||
)
|
||||
generate_mock_trade(
|
||||
'ETH/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
|
||||
"ETH/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=1000,
|
||||
min_ago_close=900,
|
||||
profit_rate=1.1,
|
||||
)
|
||||
generate_mock_trade(
|
||||
'NEO/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
|
||||
"NEO/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=1000,
|
||||
min_ago_close=900,
|
||||
profit_rate=1.1,
|
||||
)
|
||||
Trade.commit()
|
||||
# No losing trade yet ... so max_drawdown will raise exception
|
||||
assert not freqtrade.protections.global_stop()
|
||||
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
||||
assert not freqtrade.protections.stop_per_pair("XRP/BTC")
|
||||
|
||||
generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=500, min_ago_close=400, profit_rate=0.9,
|
||||
"XRP/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=500,
|
||||
min_ago_close=400,
|
||||
profit_rate=0.9,
|
||||
)
|
||||
# Not locked with one trade
|
||||
assert not freqtrade.protections.global_stop()
|
||||
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
||||
assert not PairLocks.is_pair_locked('XRP/BTC')
|
||||
assert not freqtrade.protections.stop_per_pair("XRP/BTC")
|
||||
assert not PairLocks.is_pair_locked("XRP/BTC")
|
||||
assert not PairLocks.is_global_lock()
|
||||
|
||||
generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=1200, min_ago_close=1100, profit_rate=0.5,
|
||||
"XRP/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.STOP_LOSS.value,
|
||||
min_ago_open=1200,
|
||||
min_ago_close=1100,
|
||||
profit_rate=0.5,
|
||||
)
|
||||
Trade.commit()
|
||||
|
||||
# Not locked with 1 trade (2nd trade is outside of lookback_period)
|
||||
assert not freqtrade.protections.global_stop()
|
||||
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
||||
assert not PairLocks.is_pair_locked('XRP/BTC')
|
||||
assert not freqtrade.protections.stop_per_pair("XRP/BTC")
|
||||
assert not PairLocks.is_pair_locked("XRP/BTC")
|
||||
assert not PairLocks.is_global_lock()
|
||||
assert not log_has_re(message, caplog)
|
||||
|
||||
# Winning trade ... (should not lock, does not change drawdown!)
|
||||
generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.ROI.value,
|
||||
min_ago_open=320, min_ago_close=410, profit_rate=1.5,
|
||||
"XRP/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.ROI.value,
|
||||
min_ago_open=320,
|
||||
min_ago_close=410,
|
||||
profit_rate=1.5,
|
||||
)
|
||||
Trade.commit()
|
||||
assert not freqtrade.protections.global_stop()
|
||||
@@ -427,63 +577,89 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
|
||||
|
||||
# Add additional negative trade, causing a loss of > 15%
|
||||
generate_mock_trade(
|
||||
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.ROI.value,
|
||||
min_ago_open=20, min_ago_close=10, profit_rate=0.8,
|
||||
"XRP/BTC",
|
||||
fee.return_value,
|
||||
False,
|
||||
exit_reason=ExitType.ROI.value,
|
||||
min_ago_open=20,
|
||||
min_ago_close=10,
|
||||
profit_rate=0.8,
|
||||
)
|
||||
Trade.commit()
|
||||
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
||||
assert not freqtrade.protections.stop_per_pair("XRP/BTC")
|
||||
# local lock not supported
|
||||
assert not PairLocks.is_pair_locked('XRP/BTC')
|
||||
assert not PairLocks.is_pair_locked("XRP/BTC")
|
||||
assert freqtrade.protections.global_stop()
|
||||
assert PairLocks.is_global_lock()
|
||||
assert log_has_re(message, caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("protectionconf,desc_expected,exception_expected", [
|
||||
({"method": "StoplossGuard", "lookback_period": 60, "trade_limit": 2, "stop_duration": 60},
|
||||
"[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, "
|
||||
"2 stoplosses with profit < 0.00% within 60 minutes.'}]",
|
||||
None
|
||||
),
|
||||
({"method": "CooldownPeriod", "stop_duration": 60},
|
||||
"[{'CooldownPeriod': 'CooldownPeriod - Cooldown period of 60 minutes.'}]",
|
||||
None
|
||||
),
|
||||
({"method": "LowProfitPairs", "lookback_period": 60, "stop_duration": 60},
|
||||
"[{'LowProfitPairs': 'LowProfitPairs - Low Profit Protection, locks pairs with "
|
||||
"profit < 0.0 within 60 minutes.'}]",
|
||||
None
|
||||
),
|
||||
({"method": "MaxDrawdown", "lookback_period": 60, "stop_duration": 60},
|
||||
"[{'MaxDrawdown': 'MaxDrawdown - Max drawdown protection, stop trading "
|
||||
"if drawdown is > 0.0 within 60 minutes.'}]",
|
||||
None
|
||||
),
|
||||
({"method": "StoplossGuard", "lookback_period_candles": 12, "trade_limit": 2,
|
||||
"required_profit": -0.05, "stop_duration": 60},
|
||||
"[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, "
|
||||
"2 stoplosses with profit < -5.00% within 12 candles.'}]",
|
||||
None
|
||||
),
|
||||
({"method": "CooldownPeriod", "stop_duration_candles": 5},
|
||||
"[{'CooldownPeriod': 'CooldownPeriod - Cooldown period of 5 candles.'}]",
|
||||
None
|
||||
),
|
||||
({"method": "LowProfitPairs", "lookback_period_candles": 11, "stop_duration": 60},
|
||||
"[{'LowProfitPairs': 'LowProfitPairs - Low Profit Protection, locks pairs with "
|
||||
"profit < 0.0 within 11 candles.'}]",
|
||||
None
|
||||
),
|
||||
({"method": "MaxDrawdown", "lookback_period_candles": 20, "stop_duration": 60},
|
||||
"[{'MaxDrawdown': 'MaxDrawdown - Max drawdown protection, stop trading "
|
||||
"if drawdown is > 0.0 within 20 candles.'}]",
|
||||
None
|
||||
),
|
||||
])
|
||||
def test_protection_manager_desc(mocker, default_conf, protectionconf,
|
||||
desc_expected, exception_expected):
|
||||
|
||||
default_conf['protections'] = [protectionconf]
|
||||
@pytest.mark.parametrize(
|
||||
"protectionconf,desc_expected,exception_expected",
|
||||
[
|
||||
(
|
||||
{
|
||||
"method": "StoplossGuard",
|
||||
"lookback_period": 60,
|
||||
"trade_limit": 2,
|
||||
"stop_duration": 60,
|
||||
},
|
||||
"[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, "
|
||||
"2 stoplosses with profit < 0.00% within 60 minutes.'}]",
|
||||
None,
|
||||
),
|
||||
(
|
||||
{"method": "CooldownPeriod", "stop_duration": 60},
|
||||
"[{'CooldownPeriod': 'CooldownPeriod - Cooldown period of 60 minutes.'}]",
|
||||
None,
|
||||
),
|
||||
(
|
||||
{"method": "LowProfitPairs", "lookback_period": 60, "stop_duration": 60},
|
||||
"[{'LowProfitPairs': 'LowProfitPairs - Low Profit Protection, locks pairs with "
|
||||
"profit < 0.0 within 60 minutes.'}]",
|
||||
None,
|
||||
),
|
||||
(
|
||||
{"method": "MaxDrawdown", "lookback_period": 60, "stop_duration": 60},
|
||||
"[{'MaxDrawdown': 'MaxDrawdown - Max drawdown protection, stop trading "
|
||||
"if drawdown is > 0.0 within 60 minutes.'}]",
|
||||
None,
|
||||
),
|
||||
(
|
||||
{
|
||||
"method": "StoplossGuard",
|
||||
"lookback_period_candles": 12,
|
||||
"trade_limit": 2,
|
||||
"required_profit": -0.05,
|
||||
"stop_duration": 60,
|
||||
},
|
||||
"[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, "
|
||||
"2 stoplosses with profit < -5.00% within 12 candles.'}]",
|
||||
None,
|
||||
),
|
||||
(
|
||||
{"method": "CooldownPeriod", "stop_duration_candles": 5},
|
||||
"[{'CooldownPeriod': 'CooldownPeriod - Cooldown period of 5 candles.'}]",
|
||||
None,
|
||||
),
|
||||
(
|
||||
{"method": "LowProfitPairs", "lookback_period_candles": 11, "stop_duration": 60},
|
||||
"[{'LowProfitPairs': 'LowProfitPairs - Low Profit Protection, locks pairs with "
|
||||
"profit < 0.0 within 11 candles.'}]",
|
||||
None,
|
||||
),
|
||||
(
|
||||
{"method": "MaxDrawdown", "lookback_period_candles": 20, "stop_duration": 60},
|
||||
"[{'MaxDrawdown': 'MaxDrawdown - Max drawdown protection, stop trading "
|
||||
"if drawdown is > 0.0 within 20 candles.'}]",
|
||||
None,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_protection_manager_desc(
|
||||
mocker, default_conf, protectionconf, desc_expected, exception_expected
|
||||
):
|
||||
default_conf["protections"] = [protectionconf]
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
short_desc = str(freqtrade.protections.short_desc())
|
||||
|
||||
@@ -12,59 +12,57 @@ from tests.conftest import EXMS, get_patched_exchange, get_patched_freqtradebot,
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def rpl_config(default_conf):
|
||||
default_conf['stake_currency'] = 'USDT'
|
||||
default_conf["stake_currency"] = "USDT"
|
||||
|
||||
default_conf['exchange']['pair_whitelist'] = [
|
||||
'ETH/USDT',
|
||||
'XRP/USDT',
|
||||
]
|
||||
default_conf['exchange']['pair_blacklist'] = [
|
||||
'BLK/USDT'
|
||||
default_conf["exchange"]["pair_whitelist"] = [
|
||||
"ETH/USDT",
|
||||
"XRP/USDT",
|
||||
]
|
||||
default_conf["exchange"]["pair_blacklist"] = ["BLK/USDT"]
|
||||
|
||||
return default_conf
|
||||
|
||||
|
||||
def test_gen_pairlist_with_local_file(mocker, rpl_config):
|
||||
|
||||
mock_file = MagicMock()
|
||||
mock_file.read.return_value = '{"pairs": ["TKN/USDT","ETH/USDT","NANO/USDT"]}'
|
||||
mocker.patch('freqtrade.plugins.pairlist.RemotePairList.open', return_value=mock_file)
|
||||
mocker.patch("freqtrade.plugins.pairlist.RemotePairList.open", return_value=mock_file)
|
||||
|
||||
mock_file_path = mocker.patch('freqtrade.plugins.pairlist.RemotePairList.Path')
|
||||
mock_file_path = mocker.patch("freqtrade.plugins.pairlist.RemotePairList.Path")
|
||||
mock_file_path.exists.return_value = True
|
||||
|
||||
jsonparse = json.loads(mock_file.read.return_value)
|
||||
mocker.patch('freqtrade.plugins.pairlist.RemotePairList.rapidjson.load', return_value=jsonparse)
|
||||
mocker.patch("freqtrade.plugins.pairlist.RemotePairList.rapidjson.load", return_value=jsonparse)
|
||||
|
||||
rpl_config['pairlists'] = [
|
||||
rpl_config["pairlists"] = [
|
||||
{
|
||||
"method": "RemotePairList",
|
||||
'number_assets': 2,
|
||||
'refresh_period': 1800,
|
||||
'keep_pairlist_on_failure': True,
|
||||
'pairlist_url': 'file:///pairlist.json',
|
||||
'bearer_token': '',
|
||||
'read_timeout': 60
|
||||
"number_assets": 2,
|
||||
"refresh_period": 1800,
|
||||
"keep_pairlist_on_failure": True,
|
||||
"pairlist_url": "file:///pairlist.json",
|
||||
"bearer_token": "",
|
||||
"read_timeout": 60,
|
||||
}
|
||||
]
|
||||
|
||||
exchange = get_patched_exchange(mocker, rpl_config)
|
||||
pairlistmanager = PairListManager(exchange, rpl_config)
|
||||
|
||||
remote_pairlist = RemotePairList(exchange, pairlistmanager, rpl_config,
|
||||
rpl_config['pairlists'][0], 0)
|
||||
remote_pairlist = RemotePairList(
|
||||
exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0
|
||||
)
|
||||
|
||||
result = remote_pairlist.gen_pairlist([])
|
||||
|
||||
assert result == ['TKN/USDT', 'ETH/USDT']
|
||||
assert result == ["TKN/USDT", "ETH/USDT"]
|
||||
|
||||
|
||||
def test_fetch_pairlist_mock_response_html(mocker, rpl_config):
|
||||
mock_response = MagicMock()
|
||||
mock_response.headers = {'content-type': 'text/html'}
|
||||
mock_response.headers = {"content-type": "text/html"}
|
||||
|
||||
rpl_config['pairlists'] = [
|
||||
rpl_config["pairlists"] = [
|
||||
{
|
||||
"method": "RemotePairList",
|
||||
"pairlist_url": "http://example.com/pairlist",
|
||||
@@ -77,17 +75,19 @@ def test_fetch_pairlist_mock_response_html(mocker, rpl_config):
|
||||
exchange = get_patched_exchange(mocker, rpl_config)
|
||||
pairlistmanager = PairListManager(exchange, rpl_config)
|
||||
|
||||
mocker.patch("freqtrade.plugins.pairlist.RemotePairList.requests.get",
|
||||
return_value=mock_response)
|
||||
remote_pairlist = RemotePairList(exchange, pairlistmanager, rpl_config,
|
||||
rpl_config['pairlists'][0], 0)
|
||||
mocker.patch(
|
||||
"freqtrade.plugins.pairlist.RemotePairList.requests.get", return_value=mock_response
|
||||
)
|
||||
remote_pairlist = RemotePairList(
|
||||
exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0
|
||||
)
|
||||
|
||||
with pytest.raises(OperationalException, match='RemotePairList is not of type JSON.'):
|
||||
with pytest.raises(OperationalException, match="RemotePairList is not of type JSON."):
|
||||
remote_pairlist.fetch_pairlist()
|
||||
|
||||
|
||||
def test_fetch_pairlist_timeout_keep_last_pairlist(mocker, rpl_config, caplog):
|
||||
rpl_config['pairlists'] = [
|
||||
rpl_config["pairlists"] = [
|
||||
{
|
||||
"method": "RemotePairList",
|
||||
"pairlist_url": "http://example.com/pairlist",
|
||||
@@ -100,25 +100,27 @@ def test_fetch_pairlist_timeout_keep_last_pairlist(mocker, rpl_config, caplog):
|
||||
exchange = get_patched_exchange(mocker, rpl_config)
|
||||
pairlistmanager = PairListManager(exchange, rpl_config)
|
||||
|
||||
mocker.patch("freqtrade.plugins.pairlist.RemotePairList.requests.get",
|
||||
side_effect=requests.exceptions.RequestException)
|
||||
mocker.patch(
|
||||
"freqtrade.plugins.pairlist.RemotePairList.requests.get",
|
||||
side_effect=requests.exceptions.RequestException,
|
||||
)
|
||||
|
||||
remote_pairlist = RemotePairList(exchange, pairlistmanager, rpl_config,
|
||||
rpl_config['pairlists'][0], 0)
|
||||
remote_pairlist = RemotePairList(
|
||||
exchange, pairlistmanager, rpl_config, 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']
|
||||
pairlist_url = rpl_config["pairlists"][0]["pairlist_url"]
|
||||
pairs, _time_elapsed = remote_pairlist.fetch_pairlist()
|
||||
|
||||
assert log_has(f'Error: Was not able to fetch pairlist from: ' f'{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"]
|
||||
|
||||
|
||||
def test_remote_pairlist_init_no_pairlist_url(mocker, rpl_config):
|
||||
|
||||
rpl_config['pairlists'] = [
|
||||
rpl_config["pairlists"] = [
|
||||
{
|
||||
"method": "RemotePairList",
|
||||
"number_assets": 10,
|
||||
@@ -127,14 +129,16 @@ def test_remote_pairlist_init_no_pairlist_url(mocker, rpl_config):
|
||||
]
|
||||
|
||||
get_patched_exchange(mocker, rpl_config)
|
||||
with pytest.raises(OperationalException, match=r'`pairlist_url` not specified.'
|
||||
r' Please check your configuration for "pairlist.config.pairlist_url"'):
|
||||
with pytest.raises(
|
||||
OperationalException,
|
||||
match=r"`pairlist_url` not specified."
|
||||
r' Please check your configuration for "pairlist.config.pairlist_url"',
|
||||
):
|
||||
get_patched_freqtradebot(mocker, rpl_config)
|
||||
|
||||
|
||||
def test_remote_pairlist_init_no_number_assets(mocker, rpl_config):
|
||||
|
||||
rpl_config['pairlists'] = [
|
||||
rpl_config["pairlists"] = [
|
||||
{
|
||||
"method": "RemotePairList",
|
||||
"pairlist_url": "http://example.com/pairlist",
|
||||
@@ -144,14 +148,16 @@ def test_remote_pairlist_init_no_number_assets(mocker, rpl_config):
|
||||
|
||||
get_patched_exchange(mocker, rpl_config)
|
||||
|
||||
with pytest.raises(OperationalException, match=r'`number_assets` not specified. '
|
||||
'Please check your configuration for "pairlist.config.number_assets"'):
|
||||
with pytest.raises(
|
||||
OperationalException,
|
||||
match=r"`number_assets` not specified. "
|
||||
'Please check your configuration for "pairlist.config.number_assets"',
|
||||
):
|
||||
get_patched_freqtradebot(mocker, rpl_config)
|
||||
|
||||
|
||||
def test_fetch_pairlist_mock_response_valid(mocker, rpl_config):
|
||||
|
||||
rpl_config['pairlists'] = [
|
||||
rpl_config["pairlists"] = [
|
||||
{
|
||||
"method": "RemotePairList",
|
||||
"pairlist_url": "http://example.com/pairlist",
|
||||
@@ -166,21 +172,21 @@ def test_fetch_pairlist_mock_response_valid(mocker, rpl_config):
|
||||
|
||||
mock_response.json.return_value = {
|
||||
"pairs": ["ETH/USDT", "XRP/USDT", "LTC/USDT", "EOS/USDT"],
|
||||
"refresh_period": 60
|
||||
"refresh_period": 60,
|
||||
}
|
||||
|
||||
mock_response.headers = {
|
||||
"content-type": "application/json"
|
||||
}
|
||||
mock_response.headers = {"content-type": "application/json"}
|
||||
|
||||
mock_response.elapsed.total_seconds.return_value = 0.4
|
||||
mocker.patch("freqtrade.plugins.pairlist.RemotePairList.requests.get",
|
||||
return_value=mock_response)
|
||||
mocker.patch(
|
||||
"freqtrade.plugins.pairlist.RemotePairList.requests.get", return_value=mock_response
|
||||
)
|
||||
|
||||
exchange = get_patched_exchange(mocker, rpl_config)
|
||||
pairlistmanager = PairListManager(exchange, rpl_config)
|
||||
remote_pairlist = RemotePairList(exchange, pairlistmanager, rpl_config,
|
||||
rpl_config['pairlists'][0], 0)
|
||||
remote_pairlist = RemotePairList(
|
||||
exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0
|
||||
)
|
||||
pairs, time_elapsed = remote_pairlist.fetch_pairlist()
|
||||
|
||||
assert pairs == ["ETH/USDT", "XRP/USDT", "LTC/USDT", "EOS/USDT"]
|
||||
@@ -189,7 +195,7 @@ def test_fetch_pairlist_mock_response_valid(mocker, rpl_config):
|
||||
|
||||
|
||||
def test_remote_pairlist_init_wrong_mode(mocker, rpl_config):
|
||||
rpl_config['pairlists'] = [
|
||||
rpl_config["pairlists"] = [
|
||||
{
|
||||
"method": "RemotePairList",
|
||||
"mode": "blacklis",
|
||||
@@ -201,11 +207,11 @@ def test_remote_pairlist_init_wrong_mode(mocker, rpl_config):
|
||||
|
||||
with pytest.raises(
|
||||
OperationalException,
|
||||
match=r'`mode` not configured correctly. Supported Modes are "whitelist","blacklist"'
|
||||
match=r'`mode` not configured correctly. Supported Modes are "whitelist","blacklist"',
|
||||
):
|
||||
get_patched_freqtradebot(mocker, rpl_config)
|
||||
|
||||
rpl_config['pairlists'] = [
|
||||
rpl_config["pairlists"] = [
|
||||
{
|
||||
"method": "RemotePairList",
|
||||
"mode": "blacklist",
|
||||
@@ -216,14 +222,13 @@ def test_remote_pairlist_init_wrong_mode(mocker, rpl_config):
|
||||
]
|
||||
|
||||
with pytest.raises(
|
||||
OperationalException,
|
||||
match=r'A `blacklist` mode RemotePairList can not be.*first.*'
|
||||
OperationalException, match=r"A `blacklist` mode RemotePairList can not be.*first.*"
|
||||
):
|
||||
get_patched_freqtradebot(mocker, rpl_config)
|
||||
|
||||
|
||||
def test_remote_pairlist_init_wrong_proc_mode(mocker, rpl_config):
|
||||
rpl_config['pairlists'] = [
|
||||
rpl_config["pairlists"] = [
|
||||
{
|
||||
"method": "RemotePairList",
|
||||
"processing_mode": "filler",
|
||||
@@ -237,25 +242,19 @@ def test_remote_pairlist_init_wrong_proc_mode(mocker, rpl_config):
|
||||
get_patched_exchange(mocker, rpl_config)
|
||||
with pytest.raises(
|
||||
OperationalException,
|
||||
match=r'`processing_mode` not configured correctly. Supported Modes are "filter","append"'
|
||||
match=r'`processing_mode` not configured correctly. Supported Modes are "filter","append"',
|
||||
):
|
||||
get_patched_freqtradebot(mocker, rpl_config)
|
||||
|
||||
|
||||
def test_remote_pairlist_blacklist(mocker, rpl_config, caplog, markets, tickers):
|
||||
|
||||
mock_response = MagicMock()
|
||||
|
||||
mock_response.json.return_value = {
|
||||
"pairs": ["XRP/USDT"],
|
||||
"refresh_period": 60
|
||||
}
|
||||
mock_response.json.return_value = {"pairs": ["XRP/USDT"], "refresh_period": 60}
|
||||
|
||||
mock_response.headers = {
|
||||
"content-type": "application/json"
|
||||
}
|
||||
mock_response.headers = {"content-type": "application/json"}
|
||||
|
||||
rpl_config['pairlists'] = [
|
||||
rpl_config["pairlists"] = [
|
||||
{
|
||||
"method": "StaticPairList",
|
||||
},
|
||||
@@ -263,31 +262,34 @@ def test_remote_pairlist_blacklist(mocker, rpl_config, caplog, markets, tickers)
|
||||
"method": "RemotePairList",
|
||||
"mode": "blacklist",
|
||||
"pairlist_url": "http://example.com/pairlist",
|
||||
"number_assets": 3
|
||||
}
|
||||
"number_assets": 3,
|
||||
},
|
||||
]
|
||||
|
||||
mocker.patch.multiple(EXMS,
|
||||
markets=PropertyMock(return_value=markets),
|
||||
exchange_has=MagicMock(return_value=True),
|
||||
get_tickers=tickers
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
markets=PropertyMock(return_value=markets),
|
||||
exchange_has=MagicMock(return_value=True),
|
||||
get_tickers=tickers,
|
||||
)
|
||||
|
||||
mocker.patch("freqtrade.plugins.pairlist.RemotePairList.requests.get",
|
||||
return_value=mock_response)
|
||||
mocker.patch(
|
||||
"freqtrade.plugins.pairlist.RemotePairList.requests.get", return_value=mock_response
|
||||
)
|
||||
|
||||
exchange = get_patched_exchange(mocker, rpl_config)
|
||||
|
||||
pairlistmanager = PairListManager(exchange, rpl_config)
|
||||
|
||||
remote_pairlist = RemotePairList(exchange, pairlistmanager, rpl_config,
|
||||
rpl_config["pairlists"][1], 1)
|
||||
remote_pairlist = RemotePairList(
|
||||
exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][1], 1
|
||||
)
|
||||
|
||||
pairs, _time_elapsed = remote_pairlist.fetch_pairlist()
|
||||
|
||||
assert pairs == ["XRP/USDT"]
|
||||
|
||||
whitelist = remote_pairlist.filter_pairlist(rpl_config['exchange']['pair_whitelist'], {})
|
||||
whitelist = remote_pairlist.filter_pairlist(rpl_config["exchange"]["pair_whitelist"], {})
|
||||
assert whitelist == ["ETH/USDT"]
|
||||
|
||||
assert log_has(f"Blacklist - Filtered out pairs: {pairs}", caplog)
|
||||
@@ -295,19 +297,13 @@ def test_remote_pairlist_blacklist(mocker, rpl_config, caplog, markets, tickers)
|
||||
|
||||
@pytest.mark.parametrize("processing_mode", ["filter", "append"])
|
||||
def test_remote_pairlist_whitelist(mocker, rpl_config, processing_mode, markets, tickers):
|
||||
|
||||
mock_response = MagicMock()
|
||||
|
||||
mock_response.json.return_value = {
|
||||
"pairs": ["XRP/USDT"],
|
||||
"refresh_period": 60
|
||||
}
|
||||
mock_response.json.return_value = {"pairs": ["XRP/USDT"], "refresh_period": 60}
|
||||
|
||||
mock_response.headers = {
|
||||
"content-type": "application/json"
|
||||
}
|
||||
mock_response.headers = {"content-type": "application/json"}
|
||||
|
||||
rpl_config['pairlists'] = [
|
||||
rpl_config["pairlists"] = [
|
||||
{
|
||||
"method": "StaticPairList",
|
||||
},
|
||||
@@ -316,29 +312,32 @@ def test_remote_pairlist_whitelist(mocker, rpl_config, processing_mode, markets,
|
||||
"mode": "whitelist",
|
||||
"processing_mode": processing_mode,
|
||||
"pairlist_url": "http://example.com/pairlist",
|
||||
"number_assets": 3
|
||||
}
|
||||
"number_assets": 3,
|
||||
},
|
||||
]
|
||||
|
||||
mocker.patch.multiple(EXMS,
|
||||
markets=PropertyMock(return_value=markets),
|
||||
exchange_has=MagicMock(return_value=True),
|
||||
get_tickers=tickers
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
markets=PropertyMock(return_value=markets),
|
||||
exchange_has=MagicMock(return_value=True),
|
||||
get_tickers=tickers,
|
||||
)
|
||||
|
||||
mocker.patch("freqtrade.plugins.pairlist.RemotePairList.requests.get",
|
||||
return_value=mock_response)
|
||||
mocker.patch(
|
||||
"freqtrade.plugins.pairlist.RemotePairList.requests.get", return_value=mock_response
|
||||
)
|
||||
|
||||
exchange = get_patched_exchange(mocker, rpl_config)
|
||||
|
||||
pairlistmanager = PairListManager(exchange, rpl_config)
|
||||
|
||||
remote_pairlist = RemotePairList(exchange, pairlistmanager, rpl_config,
|
||||
rpl_config["pairlists"][1], 1)
|
||||
remote_pairlist = RemotePairList(
|
||||
exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][1], 1
|
||||
)
|
||||
|
||||
pairs, _time_elapsed = remote_pairlist.fetch_pairlist()
|
||||
|
||||
assert pairs == ["XRP/USDT"]
|
||||
|
||||
whitelist = remote_pairlist.filter_pairlist(rpl_config['exchange']['pair_whitelist'], {})
|
||||
assert whitelist == (["XRP/USDT"] if processing_mode == "filter" else ['ETH/USDT', 'XRP/USDT'])
|
||||
whitelist = remote_pairlist.filter_pairlist(rpl_config["exchange"]["pair_whitelist"], {})
|
||||
assert whitelist == (["XRP/USDT"] if processing_mode == "filter" else ["ETH/USDT", "XRP/USDT"])
|
||||
|
||||
Reference in New Issue
Block a user