refactor: optimize performance and remove redundant logic in protections

pull/12835/head
ABS 2 days ago
parent dbaece9462
commit 5c737d977d

@ -2423,7 +2423,8 @@ class FreqtradeBot(LoggingMixin):
self.strategy.lock_pair(pair, datetime.now(UTC), reason="Auto lock", side=side) self.strategy.lock_pair(pair, datetime.now(UTC), reason="Auto lock", side=side)
starting_balance = self.wallets.get_starting_balance() starting_balance = self.wallets.get_starting_balance()
prot_trig = self.protections.stop_per_pair( prot_trig = self.protections.stop_per_pair(
pair, side=side, starting_balance=starting_balance) pair, side=side, starting_balance=starting_balance
)
if prot_trig: if prot_trig:
msg: RPCProtectionMsg = { msg: RPCProtectionMsg = {
"type": RPCMessageType.PROTECTION_TRIGGER, "type": RPCMessageType.PROTECTION_TRIGGER,

@ -66,8 +66,11 @@ class ProtectionManager:
return result return result
def stop_per_pair( def stop_per_pair(
self, pair, now: datetime | None = None, side: LongShort = "long", self,
starting_balance: float = 0.0 pair,
now: datetime | None = None,
side: LongShort = "long",
starting_balance: float = 0.0,
) -> PairLock | None: ) -> PairLock | None:
if not now: if not now:
now = datetime.now(UTC) now = datetime.now(UTC)

@ -53,7 +53,7 @@ class CooldownPeriod(IProtection):
return None return None
def global_stop( def global_stop(
self, date_now: datetime, side: LongShort, starting_balance: float = 0.0 self, date_now: datetime, side: LongShort, starting_balance: float
) -> ProtectionReturn | None: ) -> ProtectionReturn | None:
""" """
Stops trading (position entering) for all pairs Stops trading (position entering) for all pairs
@ -65,7 +65,7 @@ class CooldownPeriod(IProtection):
return None return None
def stop_per_pair( def stop_per_pair(
self, pair: str, date_now: datetime, side: LongShort, starting_balance: float = 0.0 self, pair: str, date_now: datetime, side: LongShort, starting_balance: float
) -> ProtectionReturn | None: ) -> ProtectionReturn | None:
""" """
Stops trading (position entering) for this pair Stops trading (position entering) for this pair

@ -103,7 +103,7 @@ class IProtection(LoggingMixin, ABC):
@abstractmethod @abstractmethod
def global_stop( def global_stop(
self, date_now: datetime, side: LongShort, starting_balance: float = 0.0 self, date_now: datetime, side: LongShort, starting_balance: float
) -> ProtectionReturn | None: ) -> ProtectionReturn | None:
""" """
Stops trading (position entering) for all pairs Stops trading (position entering) for all pairs
@ -112,7 +112,7 @@ class IProtection(LoggingMixin, ABC):
@abstractmethod @abstractmethod
def stop_per_pair( def stop_per_pair(
self, pair: str, date_now: datetime, side: LongShort, starting_balance: float = 0.0 self, pair: str, date_now: datetime, side: LongShort, starting_balance: float
) -> ProtectionReturn | None: ) -> ProtectionReturn | None:
""" """
Stops trading (position entering) for this pair Stops trading (position entering) for this pair

@ -82,7 +82,7 @@ class LowProfitPairs(IProtection):
return None return None
def global_stop( def global_stop(
self, date_now: datetime, side: LongShort, starting_balance: float = 0.0 self, date_now: datetime, side: LongShort, starting_balance: float
) -> ProtectionReturn | None: ) -> ProtectionReturn | None:
""" """
Stops trading (position entering) for all pairs Stops trading (position entering) for all pairs
@ -93,7 +93,7 @@ class LowProfitPairs(IProtection):
return None return None
def stop_per_pair( def stop_per_pair(
self, pair: str, date_now: datetime, side: LongShort, starting_balance: float = 0.0 self, pair: str, date_now: datetime, side: LongShort, starting_balance: float
) -> ProtectionReturn | None: ) -> ProtectionReturn | None:
""" """
Stops trading (position entering) for this pair Stops trading (position entering) for this pair

@ -1,5 +1,5 @@
import logging import logging
from datetime import UTC, datetime, timedelta from datetime import datetime, timedelta
from typing import Any from typing import Any
import pandas as pd import pandas as pd
@ -48,41 +48,50 @@ class MaxDrawdown(IProtection):
""" """
look_back_until = date_now - timedelta(minutes=self._lookback_period) look_back_until = date_now - timedelta(minutes=self._lookback_period)
# Get all closed trades to calculate balance at the start of the window trades_in_window = Trade.get_trades_proxy(is_open=False, close_date=look_back_until)
all_closed_trades = Trade.get_trades_proxy(is_open=False)
trades_in_window = []
profit_before_window = 0.0
for trade in all_closed_trades:
if trade.close_date:
# Ensure close_date is aware for comparison
close_date = (trade.close_date.replace(tzinfo=UTC)
if trade.close_date.tzinfo is None else trade.close_date)
if close_date > look_back_until:
trades_in_window.append(trade)
else:
profit_before_window += (trade.close_profit_abs or 0.0)
if len(trades_in_window) < self._trade_limit: if len(trades_in_window) < self._trade_limit:
# Not enough trades in the relevant period
return None return None
# Calculate actual balance at the start of the lookback window # Get all trades to calculate cumulative profit before the window
actual_starting_balance = starting_balance + profit_before_window all_closed_trades = Trade.get_trades_proxy(is_open=False)
profit_before_window = sum(
trade.close_profit_abs or 0.0
for trade in all_closed_trades
if trade.close_date_utc <= look_back_until
)
trades_df = pd.DataFrame([trade.to_json() for trade in trades_in_window]) # Get calculation mode
method = self._protection_config.get("method", "ratios")
# Drawdown is always positive
try: try:
# Use absolute profit calculation with the actual balance at window start. if method == "equity":
drawdown_obj = calculate_max_drawdown( # Standard equity-based drawdown
trades_df, trades_df = pd.DataFrame(
value_col="profit_abs", [
starting_balance=actual_starting_balance, {"close_date": t.close_date_utc, "profit_abs": t.close_profit_abs}
relative=True for t in trades_in_window
) ]
# Use relative drawdown to compare against max_allowed_drawdown percentage )
drawdown = drawdown_obj.relative_account_drawdown actual_starting_balance = starting_balance + profit_before_window
drawdown_obj = calculate_max_drawdown(
trades_df,
value_col="profit_abs",
starting_balance=actual_starting_balance,
relative=True,
)
drawdown = drawdown_obj.relative_account_drawdown
else:
# Legacy ratios-based calculation (default)
trades_df = pd.DataFrame(
[
{"close_date": t.close_date_utc, "close_profit": t.close_profit}
for t in trades_in_window
]
)
drawdown_obj = calculate_max_drawdown(trades_df, value_col="close_profit")
# In ratios mode, drawdown_abs is the cumulative ratio drop
drawdown = drawdown_obj.drawdown_abs
except ValueError: except ValueError:
return None return None
@ -104,7 +113,7 @@ class MaxDrawdown(IProtection):
return None return None
def global_stop( def global_stop(
self, date_now: datetime, side: LongShort, starting_balance: float = 0.0 self, date_now: datetime, side: LongShort, starting_balance: float
) -> ProtectionReturn | None: ) -> ProtectionReturn | None:
""" """
Stops trading (position entering) for all pairs Stops trading (position entering) for all pairs
@ -115,7 +124,7 @@ class MaxDrawdown(IProtection):
return self._max_drawdown(date_now, starting_balance) return self._max_drawdown(date_now, starting_balance)
def stop_per_pair( def stop_per_pair(
self, pair: str, date_now: datetime, side: LongShort, starting_balance: float = 0.0 self, pair: str, date_now: datetime, side: LongShort, starting_balance: float
) -> ProtectionReturn | None: ) -> ProtectionReturn | None:
""" """
Stops trading (position entering) for this pair Stops trading (position entering) for this pair

@ -87,7 +87,7 @@ class StoplossGuard(IProtection):
) )
def global_stop( def global_stop(
self, date_now: datetime, side: LongShort, starting_balance: float = 0.0 self, date_now: datetime, side: LongShort, starting_balance: float
) -> ProtectionReturn | None: ) -> ProtectionReturn | None:
""" """
Stops trading (position entering) for all pairs Stops trading (position entering) for all pairs
@ -100,7 +100,7 @@ class StoplossGuard(IProtection):
return self._stoploss_guard(date_now, None, side) return self._stoploss_guard(date_now, None, side)
def stop_per_pair( def stop_per_pair(
self, pair: str, date_now: datetime, side: LongShort, starting_balance: float = 0.0 self, pair: str, date_now: datetime, side: LongShort, starting_balance: float
) -> ProtectionReturn | None: ) -> ProtectionReturn | None:
""" """
Stops trading (position entering) for this pair Stops trading (position entering) for this pair

Loading…
Cancel
Save