自定义两个protections

This commit is contained in:
zhangkun9038@dingtalk.com 2025-08-27 14:05:18 +08:00
parent 9ef20b609b
commit 43a0070351
8 changed files with 280 additions and 8 deletions

View File

@ -18,26 +18,26 @@
"timeout": 20000
},
"pair_whitelist": [
"AAVE/USDT",
"ADA/USDT",
"API3/USDT",
"APT/USDT",
"ARB/USDT",
"AVAX/USDT",
"BETH/USDT",
"BCH/USDT",
"BERA/USDT",
"BNB/USDT",
"BTC/USDT",
"CRV/USDT",
"CRO/USDT",
"DOGE/USDT",
"ETH/USDT",
"FIL/USDT",
"LDO/USDT",
"IP/USDT",
"LINK/USDT",
"LTC/USDT",
"OP/USDT",
"OMI/USDT",
"PENGU/USDT",
"PEPE/USDT",
"PI/USDT",
"RSS3/USDT",
"PROMPT/USDT",
"SD/USDT",
"SOL/USDT",
"SUI/USDT",
"TON/USDT",

View File

@ -23,6 +23,7 @@ services:
- "./config_examples:/freqtrade/config_examples"
- "./freqtrade/templates:/freqtrade/templates"
- "./freqtrade/exchange/:/freqtrade/exchange"
- "./freqtrade/plugins/protections:/freqtrade/freqtrade/plugins/protections"
- "./ccxt/async_support/okx.py:/home/ftuser/.local/lib/python3.12/site-packages/ccxt/async_support/okx.py"
# FreqAI核心文件挂载 - 确保使用我们修改的版本
- "./freqtrade/freqai/data_kitchen.py:/freqtrade/freqai/data_kitchen.py"

View File

@ -1,2 +1,8 @@
# flake8: noqa: F401
from freqtrade.plugins.protections.iprotection import IProtection, ProtectionReturn
from freqtrade.plugins.protections.cooldown_period import CooldownPeriod
from freqtrade.plugins.protections.low_profit_pairs import LowProfitPairs
from freqtrade.plugins.protections.max_drawdown_protection import MaxDrawdown
from freqtrade.plugins.protections.stoploss_guard import StoplossGuard
from freqtrade.plugins.protections.total_loss_protection import TotalLossProtection
from freqtrade.plugins.protections.consecutive_loss_protection import ConsecutiveLossProtection

View File

@ -0,0 +1,122 @@
import logging
from datetime import datetime, timedelta
from typing import Any
import logging
from freqtrade.constants import Config, LongShort
from freqtrade.persistence import Trade
from freqtrade.plugins.protections import IProtection, ProtectionReturn
logger = logging.getLogger(__name__)
class ConsecutiveLossProtection(IProtection):
has_global_stop: bool = False
has_local_stop: bool = True
def __init__(self, config: Config, protection_config: dict[str, Any]) -> None:
super().__init__(config, protection_config)
# 兼容旧版配置参数名
# 从模板配置中读取参数(分钟)
self._max_consecutive_losses = protection_config.get("max_consecutive_losses", 3)
self._stop_duration = protection_config.get("stop_duration", 30)
self._lookback_period = protection_config.get("lookback_period", 120)
# 设置默认值对应20根K线内3次亏损锁5根K线新参数名基于candles
# 优先使用基于candles的配置参数
self._loss_limit = protection_config.get("loss_limit", self._max_consecutive_losses)
# 确保lookback_period是基于candles计算的
from freqtrade.exchange import timeframe_to_minutes
tf_in_min = timeframe_to_minutes(config["timeframe"])
# 计算基于candles的参数值
if "lookback_period_candles" in protection_config:
self._lookback_period_candles = protection_config["lookback_period_candles"]
self._lookback_period = tf_in_min * self._lookback_period_candles
else:
# 从分钟转换为candles数
self._lookback_period_candles = int(self._lookback_period / tf_in_min)
if "stop_duration_candles" in protection_config:
self._stop_duration_candles = protection_config["stop_duration_candles"]
else:
# 从分钟转换为candles数
self._stop_duration_candles = int(self._stop_duration / tf_in_min)
def short_desc(self) -> str:
"""
Short method description - used for startup-messages
"""
return (
f"{self.name} - Loss Count Protection, locks pairs with "
f"{self._loss_limit} losses within {self._lookback_period_candles} candles, "
f"locking for {self._stop_duration_candles} candles."
)
def _reason(self, loss_count: int) -> str:
"""
LockReason to use
"""
return (
f"{loss_count} losses within {self._lookback_period_candles} candles, "
f"locking for {self._stop_duration_candles} candles."
)
def _loss_count_check(
self, date_now: datetime, pair: str, side: LongShort
) -> ProtectionReturn | None:
"""
Evaluate recent trades for pair to check for number of losses
"""
look_back_until = date_now - timedelta(minutes=self._lookback_period)
# Get closed trades for the pair within the lookback period
trades = Trade.get_trades_proxy(
pair=pair, is_open=False, close_date=look_back_until
)
if not trades:
return None
# Count the number of losing trades in the lookback period
loss_count = sum(1 for trade in trades if trade.close_profit and trade.close_profit < 0)
if loss_count >= self._loss_limit:
self.log_once(
f"Trading for {pair} stopped due to {loss_count} losses within "
f"{self._lookback_period_candles} candles.",
logger.info,
)
until = self.calculate_lock_end(trades)
return ProtectionReturn(
lock=True,
until=until,
reason=self._reason(loss_count),
lock_side=side,
)
return None
def global_stop(self, date_now: datetime, side: LongShort) -> ProtectionReturn | None:
"""
Stops trading (position entering) for all pairs
This must evaluate to true for the whole period of the "cooldown period".
:return: Tuple of [bool, until, reason].
If true, all pairs will be locked with <reason> until <until>
"""
return None
def stop_per_pair(
self, pair: str, date_now: datetime, side: LongShort
) -> ProtectionReturn | None:
"""
Stops trading (position entering) for this pair
This must evaluate to true for the whole period of the "cooldown period".
:return: Tuple of [bool, until, reason].
If true, this pair will be locked with <reason> until <until>
"""
return self._loss_count_check(date_now, pair=pair, side=side)

View File

@ -0,0 +1,121 @@
import logging
from datetime import datetime, timedelta
from typing import Any
from freqtrade.constants import Config, LongShort
from freqtrade.persistence import Trade
from freqtrade.plugins.protections.iprotection import IProtection, ProtectionReturn
logger = logging.getLogger(__name__)
class TotalLossProtection(IProtection):
has_global_stop: bool = True
has_local_stop: bool = False
def __init__(self, config: Config, protection_config: dict[str, Any]) -> None:
super().__init__(config, protection_config)
# 配置参数10根半小时K线的亏损阈值10美元
self._threshold_10_candles = protection_config.get("threshold_10_candles", 10.0)
# 配置参数20根半小时K线的亏损阈值15美元
self._threshold_20_candles = protection_config.get("threshold_20_candles", 15.0)
def short_desc(self) -> str:
"""
Short method description - used for startup-messages
"""
return (
f"{self.name} - Total Loss Protection, stop trading if loss > {self._threshold_10_candles} within last 10 30min candles or > {self._threshold_20_candles} within last 20 30min candles."
)
def _reason(self, lookback_period: int, total_loss: float, threshold: float) -> str:
"""
Lock reason text
"""
return (
f"Total Loss {total_loss:.2f} > {threshold} in {lookback_period} 30min candles, "
f"locking {self.unlock_reason_time_element}."
)
def _total_loss_check(
self, date_now: datetime, side: LongShort
) -> ProtectionReturn | None:
"""
检查最近10根和20根半小时K线内的总亏损
1根半小时K线 = 30分钟
10根半小时K线 = 5小时
20根半小时K线 = 10小时
"""
# 计算10根半小时K线的回溯时间5小时
look_back_until_10_candles = date_now - timedelta(hours=5)
# 计算20根半小时K线的回溯时间10小时
look_back_until_20_candles = date_now - timedelta(hours=10)
# 获取10根K线时间窗口内的所有已关闭交易
trades_10_candles = Trade.get_trades_proxy(
is_open=False,
close_date=look_back_until_10_candles
)
# 获取20根K线时间窗口内的所有已关闭交易
trades_20_candles = Trade.get_trades_proxy(
is_open=False,
close_date=look_back_until_20_candles
)
# 计算10根K线时间窗口内的总亏损
total_loss_10_candles = sum(
min(0, trade.close_profit) # 只计算亏损部分
for trade in trades_10_candles
if trade.close_profit is not None
) * -1 # 转换为正数表示亏损金额
# 计算20根K线时间窗口内的总亏损
total_loss_20_candles = sum(
min(0, trade.close_profit) # 只计算亏损部分
for trade in trades_20_candles
if trade.close_profit is not None
) * -1 # 转换为正数表示亏损金额
# 检查是否触发任一条件
if total_loss_10_candles > self._threshold_10_candles and trades_10_candles:
self.log_once(
f"Trading stopped due to Total Loss Protection: {total_loss_10_candles:.2f} > {self._threshold_10_candles} within last 10 30min candles.",
logger.info,
)
until = self.calculate_lock_end(trades_10_candles)
return ProtectionReturn(
lock=True,
until=until,
reason=self._reason(10, total_loss_10_candles, self._threshold_10_candles),
)
elif total_loss_20_candles > self._threshold_20_candles and trades_20_candles:
self.log_once(
f"Trading stopped due to Total Loss Protection: {total_loss_20_candles:.2f} > {self._threshold_20_candles} within last 20 30min candles.",
logger.info,
)
until = self.calculate_lock_end(trades_20_candles)
return ProtectionReturn(
lock=True,
until=until,
reason=self._reason(20, total_loss_20_candles, self._threshold_20_candles),
)
return None
def global_stop(self, date_now: datetime, side: LongShort) -> ProtectionReturn | None:
"""
停止所有交易对的交易开仓
"""
return self._total_loss_check(date_now, side)
def stop_per_pair(
self, pair: str, date_now: datetime, side: LongShort
) -> ProtectionReturn | None:
"""
本保护机制不支持单个交易对的停止
"""
return None

View File

@ -2087,6 +2087,24 @@ class FreqaiPrimer(IStrategy):
"trade_limit": 4, # 4笔交易限制
"stop_duration_candles": 24, # 72分钟暂停24根3分钟K线
"max_allowed_drawdown": 0.20 # 20%最大回撤容忍度
},
{
"method": "TotalLossProtection",
# 10根半小时K线内亏损阈值10美元
"threshold_10_candles": 10.0,
# 20根半小时K线内亏损阈值15美元
"threshold_20_candles": 15.0,
# 锁定时长2小时
"stop_duration": 120
},
{
"method": "ConsecutiveLossProtection",
# 最大连续亏损次数
"max_consecutive_losses": 3,
# 锁定时长3小时
"stop_duration": 180,
# 回看期6小时
"lookback_period": 360
}
]

View File

@ -190,6 +190,7 @@ echo "docker-compose run --rm freqtrade backtesting $PAIRS_FLAG \
--freqaimodel LightGBMRegressorMultiTarget \
--config /freqtrade/config_examples/$CONFIG_FILE \
--config /freqtrade/templates/freqaiprimer.json \
--enable-protections \
--strategy-path /freqtrade/templates \
--strategy $STRATEGY_NAME \
--timerange $START_DATE-$END_DATE \
@ -202,6 +203,7 @@ docker-compose run --rm freqtrade backtesting $PAIRS_FLAG \
--freqaimodel LightGBMRegressorMultiTarget \
--config /freqtrade/config_examples/$CONFIG_FILE \
--config /freqtrade/templates/freqaiprimer.json \
--enable-protections \
--strategy-path /freqtrade/templates \
--strategy $STRATEGY_NAME \
--timerange $START_DATE-$END_DATE \

View File

@ -226,6 +226,7 @@ echo "docker-compose run --rm freqtrade hyperopt $PAIRS_FLAG \
--strategy $STRATEGY_NAME \
--config /freqtrade/config_examples/$CONFIG_FILE \
--config /freqtrade/templates/${PARAMS_NAME}.json \
--enable-protections \
--strategy-path /freqtrade/templates \
--timerange ${START_DATE}-${END_DATE} \
-e 100 \
@ -239,6 +240,7 @@ docker-compose run --rm freqtrade hyperopt $PAIRS_FLAG \
--strategy $STRATEGY_NAME \
--config /freqtrade/config_examples/$CONFIG_FILE \
--config /freqtrade/templates/${PARAMS_NAME}.json \
--enable-protections \
--strategy-path /freqtrade/templates \
--timerange ${START_DATE}-${END_DATE} \
-e 100 \