自定义两个protections
This commit is contained in:
parent
9ef20b609b
commit
43a0070351
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
122
freqtrade/plugins/protections/consecutive_loss_protection.py
Normal file
122
freqtrade/plugins/protections/consecutive_loss_protection.py
Normal 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)
|
||||
121
freqtrade/plugins/protections/total_loss_protection.py
Normal file
121
freqtrade/plugins/protections/total_loss_protection.py
Normal 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
|
||||
@ -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
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@ -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 \
|
||||
|
||||
@ -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 \
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user