去掉所有 AI 变量和逻辑,然后只加一个新的,在 confirm_trade_entry 里确认入场是否可信

This commit is contained in:
zhangkun9038@dingtalk.com 2025-12-17 17:42:54 +08:00
parent 0d05406167
commit e0de60144d
2 changed files with 38 additions and 144 deletions

View File

@ -112,13 +112,7 @@
"main_plot": {},
"subplots": {
"&-predictions": {
"&-exit_return": {"color": "red"}
},
"&-risk": {
"&-max_drawdown": {"color": "orange"}
},
"&-timing": {
"&-hold_duration": {"color": "blue"}
"&-entry_confidence": {"color": "green"}
}
}
}

View File

@ -372,38 +372,19 @@ class FreqaiPrimer(IStrategy):
def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
"""
设置 FreqAI 训练标签 (3个变量简化版)
- &-exit_return: 出场回归标签 (未来收益率)
- &-max_drawdown: 最大回撤预测 (入场风险过滤)
- &-hold_duration: 最佳持仓时长 (0-1出场ROI优化)
设置 FreqAI 训练标签 (1个变量简化版)
- &-entry_confidence: 入场置信度评分 (0-1)仅用于 confirm_trade_entry 最终确认
"""
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
# ========== 1. 出场回归标签 ==========
# 直接使用未来收益率
# ========== 入场置信度标签 ==========
# 基于未来收益率估算入场好坏,使用 S 型函数映射到 0-1
future_return = dataframe["close"].shift(-label_period) / dataframe["close"] - 1
dataframe["&-exit_return"] = future_return
# ========== 2. 最大回撤标签 (入场风险过滤) ==========
# 使用未来最低价计算最大回撤,使用 min_periods=1 避免过多 NaN
future_low = dataframe["low"].shift(-1).rolling(window=label_period, min_periods=1).min()
dataframe["&-max_drawdown"] = (future_low / dataframe["close"]) - 1
# ========== 3. 最佳持仓时长标签 (出场ROI优化) ==========
# 基于未来收益率估算持仓价值,归一化到 0-1
# 收益率高 → hold_duration 高 (应该持仓久)
# 收益率低/负 → hold_duration 低 (应该尽快出场)
future_return_clipped = future_return.clip(-0.1, 0.1) # 限制在 -10% ~ +10%
dataframe["&-hold_duration"] = (future_return_clipped + 0.1) / 0.2 # 归一化到 0-1
dataframe["&-entry_confidence"] = 1 / (1 + np.exp(-future_return * 50))
# ========== 统一处理 NaN避免训练数据被全部过滤 ==========
# 所有标签列末尾会因为 shift(-label_period) 产生 NaN用前向填充避免丢失样本
label_columns = [
"&-exit_return", "&-max_drawdown", "&-hold_duration"
]
for col in label_columns:
if col in dataframe.columns:
dataframe[col] = dataframe[col].fillna(method='ffill').fillna(0)
if "&-entry_confidence" in dataframe.columns:
dataframe["&-entry_confidence"] = dataframe["&-entry_confidence"].fillna(method='ffill').fillna(0)
return dataframe
@ -669,54 +650,11 @@ class FreqaiPrimer(IStrategy):
# 合并所有条件
traditional_condition = breakout_condition | volume_spike | macd_downward | rsi_overbought
# ==================== FreqAI 出场信号融合 (市场自适应) ====================
# 检查 FreqAI 预测结果是否可用
if "&-exit_return" in dataframe.columns and "do_predict" in dataframe.columns:
# 为每一行根据市场状态计算自适应退出阈值,避免在 populate_ 中使用 iloc[-1]
if 'market_state' in dataframe.columns:
exit_th_series = dataframe['market_state'].apply(
lambda s: self._get_adaptive_thresholds(s)['exit_threshold']
)
else:
default_th = self._get_adaptive_thresholds('neutral')['exit_threshold']
exit_th_series = np.full(len(dataframe), default_th)
# 回归器预测: 未来收益率
# 熊市时 exit_th 为负值(如-0.02),更容易触发出场
# 牛市时 exit_th 为正值(如+0.02),需要更大下跌才出场
ml_exit_signal = (dataframe["&-exit_return"] < exit_th_series)
# 置信度过滤: do_predict >= 1 表示预测可信
ml_confidence = (dataframe["do_predict"] >= 1)
# 融合策略: 传统条件 OR (ML出场信号 AND ML置信度)
# 出场使用 OR 逻辑,更激进地保护利润
final_condition = traditional_condition | (ml_exit_signal & ml_confidence)
# 记录融合信息(只统计数量,不再依赖最后一行)
logger.info(
f"[{metadata['pair']}] FreqAI出场: "
f"传统={int(traditional_condition.sum())}, "
f"ML出场={int(ml_exit_signal.sum())}, "
f"最终={int(final_condition.sum())}"
)
else:
# FreqAI 未启用,使用传统条件
final_condition = traditional_condition
# 设置出场信号
# ==================== 出场信号:仅使用传统条件 ====================
final_condition = traditional_condition
dataframe.loc[final_condition, 'exit_long'] = 1
# 增强调试信息
#logger.info(f"[{metadata['pair']}] 出场条件检查:")
#logger.info(f" - 价格突破布林带上轨: {breakout_condition.sum()} 次")
#logger.info(f" - 成交量显著放大: {volume_spike.sum()} 次")
#logger.info(f" - MACD 下降趋势: {macd_downward.sum()} 次")
#logger.info(f" - RSI 超买: {rsi_overbought.sum()} 次")
#logger.info(f" - 最终条件: {final_condition.sum()} 次")
#logger.info(f" - 使用参数: exit_bb_upper_deviation={self.exit_bb_upper_deviation.value}, exit_volume_multiplier={self.exit_volume_multiplier.value}, rsi_overbought={self.rsi_overbought.value}")
# 日志记录
if dataframe['exit_long'].sum() > 0:
logger.info(f"[{metadata['pair']}] 触发出场信号数量: {dataframe['exit_long'].sum()}")
@ -767,55 +705,13 @@ class FreqaiPrimer(IStrategy):
)
traditional_condition = condition_count >= self.min_condition_count.value
# ==================== FreqAI 入场信号融合 (市场自适应) ====================
# 检查 FreqAI 预测结果是否可用
if "&-max_drawdown" in dataframe.columns and "do_predict" in dataframe.columns:
# 行级动态阈值,避免在 populate_ 中使用 iloc[-1]
if 'market_state' in dataframe.columns:
drawdown_th_series = dataframe['market_state'].apply(
lambda s: self._get_adaptive_thresholds(s)['max_drawdown']
)
else:
default_th = self._get_adaptive_thresholds('neutral')
drawdown_th_series = np.full(len(dataframe), default_th['max_drawdown'])
# 置信度过滤: do_predict >= 1 表示预测可信
ml_confidence = (dataframe["do_predict"] >= 1)
# 最大回撤过滤: 使用动态阈值
ml_drawdown_safe = (dataframe["&-max_drawdown"] >= drawdown_th_series)
# === 极简融合策略FreqAI 仅做风险否决 ===
bad_risk_mask = (dataframe["&-max_drawdown"] < drawdown_th_series) & ml_confidence
final_condition = traditional_condition & ~bad_risk_mask
# 记录融合信息(仅统计数量)
logger.info(
f"[{metadata['pair']}] FreqAI入场: "
f"传统={int(traditional_condition.sum())}, "
f"回撤安全={int(ml_drawdown_safe.sum())}, "
f"风险否决={int(bad_risk_mask.sum())}, "
f"最终={int(final_condition.sum())}"
)
else:
# FreqAI 未启用,使用传统条件
final_condition = traditional_condition
# ==================== 入场信号:仅使用传统条件 ====================
final_condition = traditional_condition
# 设置入场信号
dataframe.loc[final_condition, 'enter_long'] = 1
# 增强调试信息
#logger.info(f"[{metadata['pair']}] 入场条件检查:")
#logger.info(f" - 价格接近布林带下轨: {close_to_bb_lower_1h.sum()} 次")
#logger.info(f" - RSI 超卖: {rsi_condition_1h.sum()} 次")
#logger.info(f" - StochRSI 超卖: {stochrsi_condition_1h.sum()} 次")
#logger.info(f" - MACD 上升趋势: {macd_condition_1h.sum()} 次")
#logger.info(f" - 成交量或布林带宽度: {(volume_spike | bb_width_condition).sum()} 次")
#logger.info(f" - 趋势确认: {trend_confirmation.sum()} 次")
#logger.info(f" - 最终条件: {final_condition.sum()} 次")
# 在populate_entry_trend方法末尾添加
# 计算条件间的相关性
# 计算条件间的相关性(仅用于分析)
conditions = DataFrame({
'close_to_bb': close_to_bb_lower_1h,
'rsi': rsi_condition_1h,
@ -826,6 +722,7 @@ class FreqaiPrimer(IStrategy):
})
correlation = conditions.corr().mean().mean()
#logger.info(f"[{metadata['pair']}] 条件平均相关性: {correlation:.2f}")
# 日志记录
if dataframe['enter_long'].sum() > 0:
logger.info(f"[{metadata['pair']}] 发现入场信号数量: {dataframe['enter_long'].sum()}")
@ -902,20 +799,35 @@ class FreqaiPrimer(IStrategy):
) -> bool:
"""
交易买入前的确认函数用于最终决定是否执行交易
此处实现剧烈拉升检查逻辑
- 步骤1: 剧烈拉升过滤纯规则
- 步骤2: FreqAI 入场置信度过滤仅作为最终确认不影响回测一致性
"""
# 默认允许交易
allow_trade = True
# 仅对多头交易进行检查
if side == 'long':
# 检查是否处于剧烈拉升的不稳固区域
# 步骤1: 检查是否处于剧烈拉升的不稳固区域
is_unstable_region = self.detect_h1_rapid_rise(pair)
if is_unstable_region:
#logger.info(f"[{pair}] 由于检测到剧烈拉升,取消入场交易")
allow_trade = False
# 如果没有阻止因素,允许交易
# 步骤2: 使用 FreqAI 入场置信度进行二次确认
try:
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if "&-entry_confidence" in dataframe.columns and "do_predict" in dataframe.columns and len(dataframe) > 0:
last_row = dataframe.iloc[-1]
score = last_row["&-entry_confidence"]
do_predict = last_row["do_predict"]
# 简单规则:预测有效且置信度足够高才允许入场
min_confidence = 0.52
if do_predict < 1 or np.isnan(score) or score < min_confidence:
#logger.info(f"[{pair}] 由于 FreqAI 入场置信度不足(score={score:.3f}, do_predict={do_predict}),取消入场交易")
allow_trade = False
except Exception as e:
logger.error(f"[{pair}] confirm_trade_entry 中 FreqAI 置信度检查异常: {str(e)}")
return allow_trade
def custom_stoploss(self, pair: str, trade: 'Trade', current_time, current_rate: float,
@ -964,20 +876,8 @@ class FreqaiPrimer(IStrategy):
dynamic_roi_threshold = 0.0
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
# 优先使用市场状态特征;若不存在则回退为中性市场避免依赖不存在的sma列
# 优先使用市场状态特征;若不存在则回退为中性市场
current_state = dataframe['market_state'].iloc[-1] if 'market_state' in dataframe.columns else 'neutral'
# ==================== FreqAI hold_duration 动态调整 ====================
# 使用 ML 预测的最佳持仓时长来调整 ROI 阈值
# hold_duration 在 0-1 之间0 = 应立即出场1 = 应持有到最后
if '&-hold_duration' in dataframe.columns and 'do_predict' in dataframe.columns:
ml_hold_duration = dataframe['&-hold_duration'].iloc[-1]
ml_predict_valid = dataframe['do_predict'].iloc[-1] >= 1
if ml_predict_valid and not np.isnan(ml_hold_duration):
# 如果预测应尽快出场(hold_duration < 0.3),降低 ROI 阈值(更容易触发出场)
# 如果预测应持有(hold_duration > 0.7),提高 ROI 阈值(更难触发出场)
hold_adjustment = 1.0 + (ml_hold_duration - 0.5) * 0.6 # 范围: 0.7 ~ 1.3
dynamic_roi_threshold = dynamic_roi_threshold * hold_adjustment
entry_tag = trade.enter_tag if hasattr(trade, 'enter_tag') else None
profit_ratio = current_profit / dynamic_roi_threshold if dynamic_roi_threshold > 0 else 0