Test MaxDrawdown desc
This commit is contained in:
parent
089c463cfb
commit
f06b58dc91
@ -40,7 +40,7 @@ class MaxDrawdown(IProtection):
|
|||||||
return (f'{drawdown} > {self._max_allowed_drawdown} in {self._lookback_period} min, '
|
return (f'{drawdown} > {self._max_allowed_drawdown} in {self._lookback_period} min, '
|
||||||
f'locking for {self._stop_duration} min.')
|
f'locking for {self._stop_duration} min.')
|
||||||
|
|
||||||
def _max_drawdown(self, date_now: datetime, pair: str) -> ProtectionReturn:
|
def _max_drawdown(self, date_now: datetime) -> ProtectionReturn:
|
||||||
"""
|
"""
|
||||||
Evaluate recent trades for drawdown ...
|
Evaluate recent trades for drawdown ...
|
||||||
"""
|
"""
|
||||||
@ -49,23 +49,21 @@ class MaxDrawdown(IProtection):
|
|||||||
Trade.is_open.is_(False),
|
Trade.is_open.is_(False),
|
||||||
Trade.close_date > look_back_until,
|
Trade.close_date > look_back_until,
|
||||||
]
|
]
|
||||||
if pair:
|
|
||||||
filters.append(Trade.pair == pair)
|
|
||||||
trades = Trade.get_trades(filters).all()
|
trades = Trade.get_trades(filters).all()
|
||||||
|
|
||||||
trades_df = pd.DataFrame(trades)
|
trades_df = pd.DataFrame([trade.to_json() for trade in trades])
|
||||||
|
|
||||||
if len(trades) < self._trade_limit:
|
if len(trades) < self._trade_limit:
|
||||||
# Not enough trades in the relevant period
|
# Not enough trades in the relevant period
|
||||||
return False, None, None
|
return False, None, None
|
||||||
|
|
||||||
# Drawdown is always positive
|
# Drawdown is always positive
|
||||||
drawdown, _, _ = calculate_max_drawdown(trades_df)
|
drawdown, _, _ = calculate_max_drawdown(trades_df, value_col='close_profit')
|
||||||
|
|
||||||
if drawdown > self._max_allowed_drawdown:
|
if drawdown > self._max_allowed_drawdown:
|
||||||
self.log_once(
|
self.log_once(
|
||||||
f"Trading for {pair} stopped due to {drawdown:.2f} < {self._max_allowed_drawdown} "
|
f"Trading stopped due to Max Drawdown {drawdown:.2f} < {self._max_allowed_drawdown}"
|
||||||
f"within {self._lookback_period} minutes.", logger.info)
|
f" within {self._lookback_period} minutes.", logger.info)
|
||||||
until = self.calculate_lock_end(trades, self._stop_duration)
|
until = self.calculate_lock_end(trades, self._stop_duration)
|
||||||
|
|
||||||
return True, until, self._reason(drawdown)
|
return True, until, self._reason(drawdown)
|
||||||
|
@ -46,7 +46,7 @@ def test_protectionmanager(mocker, default_conf):
|
|||||||
if not handler.has_global_stop:
|
if not handler.has_global_stop:
|
||||||
assert handler.global_stop(datetime.utcnow()) == (False, None, None)
|
assert handler.global_stop(datetime.utcnow()) == (False, None, None)
|
||||||
if not handler.has_local_stop:
|
if not handler.has_local_stop:
|
||||||
assert handler.local_stop('XRP/BTC', datetime.utcnow()) == (False, None, None)
|
assert handler.stop_per_pair('XRP/BTC', datetime.utcnow()) == (False, None, None)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
@ -249,6 +249,71 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
|
|||||||
assert not PairLocks.is_global_lock()
|
assert not PairLocks.is_global_lock()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
|
def test_MaxDrawdown(mocker, default_conf, fee, caplog):
|
||||||
|
default_conf['protections'] = [{
|
||||||
|
"method": "MaxDrawdown",
|
||||||
|
"lookback_period": 1000,
|
||||||
|
"stopduration": 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')
|
||||||
|
caplog.clear()
|
||||||
|
|
||||||
|
Trade.session.add(generate_mock_trade(
|
||||||
|
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||||
|
min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
|
||||||
|
))
|
||||||
|
Trade.session.add(generate_mock_trade(
|
||||||
|
'XRP/BTC', fee.return_value, False, sell_reason=SellType.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 PairLocks.is_global_lock()
|
||||||
|
|
||||||
|
Trade.session.add(generate_mock_trade(
|
||||||
|
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
|
||||||
|
min_ago_open=1200, min_ago_close=1100, profit_rate=0.5,
|
||||||
|
))
|
||||||
|
|
||||||
|
# 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 PairLocks.is_global_lock()
|
||||||
|
assert not log_has_re(message, caplog)
|
||||||
|
|
||||||
|
# Winning trade ... (should not lock, does not change drawdown!)
|
||||||
|
Trade.session.add(generate_mock_trade(
|
||||||
|
'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value,
|
||||||
|
min_ago_open=320, min_ago_close=410, profit_rate=1.5,
|
||||||
|
))
|
||||||
|
assert not freqtrade.protections.global_stop()
|
||||||
|
assert not PairLocks.is_global_lock()
|
||||||
|
|
||||||
|
caplog.clear()
|
||||||
|
|
||||||
|
# Add additional negative trade, causing a loss of > 15%
|
||||||
|
Trade.session.add(generate_mock_trade(
|
||||||
|
'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value,
|
||||||
|
min_ago_open=20, min_ago_close=10, profit_rate=0.8,
|
||||||
|
))
|
||||||
|
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
|
||||||
|
# local lock not supported
|
||||||
|
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", [
|
@pytest.mark.parametrize("protectionconf,desc_expected,exception_expected", [
|
||||||
({"method": "StoplossGuard", "lookback_period": 60, "trade_limit": 2},
|
({"method": "StoplossGuard", "lookback_period": 60, "trade_limit": 2},
|
||||||
"[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, "
|
"[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, "
|
||||||
@ -264,6 +329,11 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
|
|||||||
"profit < 0.0 within 60 minutes.'}]",
|
"profit < 0.0 within 60 minutes.'}]",
|
||||||
None
|
None
|
||||||
),
|
),
|
||||||
|
({"method": "MaxDrawdown", "stopduration": 60},
|
||||||
|
"[{'MaxDrawdown': 'MaxDrawdown - Max drawdown protection, stop trading if drawdown is > 0.0 "
|
||||||
|
"within 60 minutes.'}]",
|
||||||
|
None
|
||||||
|
),
|
||||||
])
|
])
|
||||||
def test_protection_manager_desc(mocker, default_conf, protectionconf,
|
def test_protection_manager_desc(mocker, default_conf, protectionconf,
|
||||||
desc_expected, exception_expected):
|
desc_expected, exception_expected):
|
||||||
|
Loading…
Reference in New Issue
Block a user