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