refactor: optimize performance and remove redundant logic in protections
This commit is contained in:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user