diff --git a/freqtrade/templates/freqaiprimer.py b/freqtrade/templates/freqaiprimer.py index 753b26ff..c9b89470 100644 --- a/freqtrade/templates/freqaiprimer.py +++ b/freqtrade/templates/freqaiprimer.py @@ -1523,185 +1523,7 @@ class FreqaiPrimer(IStrategy): self.strategy_log(f"[{pair}] 入场被拒绝,原因列表: {', '.join(rejected_conditions)}") return False - def confirm_trade_exit( - self, - pair: str, - trade: 'Trade', - order_type: str, - amount: float, - rate: float, - time_in_force: str, - exit_reason: str, - current_time: datetime, - **kwargs, - ) -> bool: - """ - 交易卖出前的确认函数,用于最终决定是否执行出场 - 此处使用 ML 审核官(exit_signal 置信度)过滤出场 - """ - self.strategy_log(f"[{pair}] confirm_trade_exit 被调用 - 价格: {rate:.8f}, 出场原因: {exit_reason}, 时间: {current_time}") - # 风险控制类退出原因:不经过 ML 审核官,直接允许出场 - if exit_reason in ['stop_loss', 'trailing_stop_loss', 'emergency_exit', 'force_exit']: - self.strategy_log(f"[{pair}] 风险控制退出,不走 ML 审核官: exit_reason={exit_reason}") - return True - # 默认允许出场 - allow_exit = True - - try: - df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) - if len(df) > 0: - last_row = df.iloc[-1] - exit_prob = None - - # 优先使用 FreqAI 的 exit_signal 预测列 - if '&s-exit_signal' in df.columns: - exit_prob = float(last_row['&s-exit_signal']) - elif '&-exit_signal_prob' in df.columns: - exit_prob = float(last_row['&-exit_signal_prob']) - elif '&-s-exit_signal_prob' in df.columns: - exit_prob = float(last_row['&-s-exit_signal_prob']) - elif '&-exit_signal' in df.columns: - val = last_row['&-exit_signal'] - if isinstance(val, (int, float)): - exit_prob = float(val) - else: - # 文本标签时,简单映射为 0/1 - exit_prob = 1.0 if str(val).lower() in ['exit', 'sell', '1'] else 0.0 - - if exit_prob is not None: - # 确保概率在 [0, 1] 范围内(分类器输出可能有浮点误差) - exit_prob = max(0.0, min(1.0, exit_prob)) - # 从 kwargs 获取当前利润,freqtrade 会传入 current_profit - current_profit = float(kwargs.get('current_profit', 0.0)) - - # 获取出场一字基础阈值 - base_threshold = self.ml_exit_signal_threshold.value - - # 计算持仓时长(分钟) - try: - trade_age_minutes = max(0.0, (current_time - trade.open_date_utc).total_seconds() / 60.0) - except Exception: - trade_age_minutes = 0.0 - - # 基于持仓时长的阈值衰减:持仓越久,阈值越低,越容易出场 - age_factor = min(trade_age_minutes / (24 * 60.0), 1.0) # 0~1,对应 0~24 小时+ - dynamic_threshold = base_threshold * (1.0 - 0.3 * age_factor) - - # 小利润单(<=2%)再额外放宽 20% - if current_profit <= 0.02: - dynamic_threshold *= 0.8 - - # 新增:读取 AI 预测的未来波动率信号(极端化方案) - future_vol_signal = None - if '&s-future_volatility' in df.columns: - future_vol_signal = float(last_row['&s-future_volatility']) - elif '&-future_volatility' in df.columns: - future_vol_signal = float(last_row['&-future_volatility']) - - # 极端化逻辑:根据 AI 预测的未来波动率直接接管部分出场决策 - if future_vol_signal is not None and exit_reason == 'exit_signal': - # 情况A:AI 预测强趋势(高波动),且当前不亏损 → 忽略本次 exit_signal,继续持有 - if future_vol_signal > 0.65 and current_profit >= 0: - self.strategy_log( - f"[波动率 AI] [{pair}] AI 预测强趋势(高波动 {future_vol_signal:.2f}),忽略本次 exit_signal,继续持有 | " - f"持仓: {trade_age_minutes:.1f}min, 利润: {current_profit:.4f}" - ) - allow_exit = False - return allow_exit - # 情况B:AI 预测震荡市(低波动) → 强制接受 exit_signal,立即出场 - elif future_vol_signal < 0.35: - self.strategy_log( - f"[波动率 AI] [{pair}] AI 预测震荡市(低波动 {future_vol_signal:.2f}),强制接受 exit_signal 出场 | " - f"持仓: {trade_age_minutes:.1f}min, 利润: {current_profit:.4f}" - ) - return True - # 介于 0.35-0.65 之间:中性区间,不做强制处理,继续走原有 ML 审核官逻辑 - - # 设定下限,避免阈值过低 - dynamic_threshold = max(0.05, dynamic_threshold) - - if exit_prob < dynamic_threshold: - self.strategy_log( - f"[{pair}] ML 审核官拒绝出场: exit_signal 概率 {exit_prob:.2f} < 动态阈值 {dynamic_threshold:.2f}" - f" | 原应出场原因: {exit_reason} | 持仓: {trade_age_minutes:.1f}min, 利润: {current_profit:.4f}" - f" | 波动率AI: {future_vol_signal if future_vol_signal is not None else 'N/A'}" - ) - allow_exit = False - else: - if future_vol_signal is not None: - future_vol_str = f"{future_vol_signal:.2f}" - else: - future_vol_str = "N/A" - - # 格式化数字,限制有效位数不超过5位 - def format_number(value, max_digits=5): - """格式化数字,限制有效位数""" - if value == 0: - return "0" - # 转换为字符串并去掉末尾的0 - s = f"{value:.10f}".rstrip('0').rstrip('.') - # 如果小数点前的数字超过max_digits,使用科学计数法 - if len(s.split('.')[0]) > max_digits: - return f"{value:.2e}" - # 如果总长度超过max_digits,截断 - if len(s) > max_digits: - # 保留小数点前的数字 - if '.' in s: - integer_part, decimal_part = s.split('.') - if len(integer_part) > max_digits: - return f"{value:.2e}" - else: - # 截断小数部分 - max_decimal = max_digits - len(integer_part) - 1 - if max_decimal > 0: - s = integer_part + '.' + decimal_part[:max_decimal] - else: - s = integer_part - else: - # 整数部分超过限制 - if len(s) > max_digits: - return f"{value:.2e}" - return s - - # 格式化价格,限制有效位数不超过5位 - def format_number(value, max_digits=5): - """格式化数字,限制有效位数""" - if value == 0: - return "0" - # 转换为字符串并去掉末尾的0 - s = f"{value:.10f}".rstrip('0').rstrip('.') - # 如果小数点前的数字超过max_digits,使用科学计数法 - if len(s.split('.')[0]) > max_digits: - return f"{value:.2e}" - # 如果总长度超过max_digits,截断 - if len(s) > max_digits: - # 保留小数点前的数字 - if '.' in s: - integer_part, decimal_part = s.split('.') - if len(integer_part) > max_digits: - return f"{value:.2e}" - else: - # 截断小数部分 - max_decimal = max_digits - len(integer_part) - 1 - if max_decimal > 0: - s = integer_part + '.' + decimal_part[:max_decimal] - else: - s = integer_part - else: - # 整数部分超过限制 - if len(s) > max_digits: - return f"{value:.2e}" - return s - - self.strategy_log( - f"[{pair}] ML 审核官允许出场: exit_signal 概率 {exit_prob:.2f} >= 动态阈值 {dynamic_threshold:.2f}" - f" | 出场原因: {exit_reason} | 持仓: {format_number(trade_age_minutes)}min, 利润: {format_number(current_profit)}" - f" | 波动率AI: {future_vol_str}" - ) - except Exception as e: - logger.warning(f"[{pair}] ML 审核官出场检查失败,允许出场: {e}") - return allow_exit def custom_stoploss(self, pair: str, trade: 'Trade', current_time, current_rate: float, current_profit: float, **kwargs) -> float: @@ -1731,10 +1553,19 @@ class FreqaiPrimer(IStrategy): def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float, time_in_force: str, exit_reason: str, - current_time: datetime, **kwargs) -> bool: + current_time: datetime, current_profit: float, **kwargs) -> bool: """ 智能出场确认:结合ML预测信号来决定是否出场 """ + # 计算持仓时长(分钟) + trade_age_minutes = (current_time - trade.open_date_utc).total_seconds() / 60 + + # 设置最小持仓时间保护,避免交易刚开仓就立刻被平仓 + min_hold_minutes = 3 # 至少持有3分钟 + if trade_age_minutes < min_hold_minutes: + self.strategy_log(f"[{pair}] 交易开仓时间仅{trade_age_minutes:.1f}分钟,小于最小持仓时间{min_hold_minutes}分钟,阻止出场") + return False + try: # 获取当前的ML预测数据 df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) @@ -1763,7 +1594,7 @@ class FreqaiPrimer(IStrategy): # 基于ML预测的智能决策 if current_entry_prob is not None and predicted_volatility is not None: # 如果当前入场信号概率很高,说明市场条件仍然良好,可以继续持有 - if current_entry_prob > 0.6 and trade.current_profit() < 0.05: + if current_entry_prob > 0.6 and current_profit < 0.05: # 高入场概率 + 低利润,建议继续持有(除非已有相当利润) self.strategy_log(f"[{pair}] 当前入场信号概率高({current_entry_prob:.2f}),建议继续持有") return False # 阻止出场