diff --git a/freqtrade/templates/freqaiprimer.py b/freqtrade/templates/freqaiprimer.py index 23da82b2..7602396f 100644 --- a/freqtrade/templates/freqaiprimer.py +++ b/freqtrade/templates/freqaiprimer.py @@ -979,6 +979,9 @@ class FreqaiPrimer(IStrategy): dataframe['bb_lower_3m'] = bb_ma_3m - (bb_std_value * bb_std_3m) dataframe['bb_upper_3m'] = bb_ma_3m + (bb_std_value * bb_std_3m) + # 计算布林带宽度百分比(3m) + dataframe['bb_width_3m'] = (dataframe['bb_upper_3m'] - dataframe['bb_lower_3m']) / bb_ma_3m * 100 + # 使用 rolling 计算 RSI(减少看前偏差) delta_3m = dataframe['close'].diff() gain_3m = delta_3m.where(delta_3m > 0, 0).rolling(window=rsi_length_value).mean() @@ -997,6 +1000,12 @@ class FreqaiPrimer(IStrategy): dataframe['kdj_d_3m'] = kdj_3m['STOCHd_9_3_3'] dataframe['kdj_j_3m'] = 3 * dataframe['kdj_k_3m'] - 2 * dataframe['kdj_d_3m'] + # 新增 CCI 指标(3m) + dataframe['cci_3m'] = ta.cci(dataframe['high'], dataframe['low'], dataframe['close'], length=20) + + # 新增 Williams %R 指标(3m) + dataframe['willr_3m'] = ta.willr(dataframe['high'], dataframe['low'], dataframe['close'], length=14) + # 新增 MACD 指标 macd_3m = ta.macd(dataframe['close'], fast=12, slow=26, signal=9) dataframe['macd_3m'] = macd_3m['MACD_12_26_9'] @@ -1060,6 +1069,9 @@ class FreqaiPrimer(IStrategy): df_1h['bb_lower_1h'] = bb_ma_1h - (bb_std_value * bb_std_1h) df_1h['bb_upper_1h'] = bb_ma_1h + (bb_std_value * bb_std_1h) + # 计算布林带宽度百分比(1h) + df_1h['bb_width_1h'] = (df_1h['bb_upper_1h'] - df_1h['bb_lower_1h']) / bb_ma_1h * 100 + # 添加 EMA5 和 EMA20 用于趋势过滤(方案2:宽松条件) df_1h['ema_5_1h'] = df_1h['close'].ewm(span=5, adjust=False).mean() df_1h['ema_20_1h'] = df_1h['close'].ewm(span=20, adjust=False).mean() @@ -1097,6 +1109,12 @@ class FreqaiPrimer(IStrategy): df_1h['kdj_d_1h'] = kdj_1h['STOCHd_9_3_3'] df_1h['kdj_j_1h'] = 3 * df_1h['kdj_k_1h'] - 2 * df_1h['kdj_d_1h'] + # 新增 CCI 指标(1h) + df_1h['cci_1h'] = ta.cci(df_1h['high'], df_1h['low'], df_1h['close'], length=20) + + # 新增 Williams %R 指标(1h) + df_1h['willr_1h'] = ta.willr(df_1h['high'], df_1h['low'], df_1h['close'], length=14) + # 新增 MACD 指标 macd_1h = ta.macd(df_1h['close'], fast=12, slow=26, signal=9) df_1h['macd_1h'] = macd_1h['MACD_12_26_9'] @@ -1204,8 +1222,10 @@ class FreqaiPrimer(IStrategy): # 增加 1h 趋势交易特征列 df_1h = df_1h[[ 'date', 'rsi_1h', 'trend_1h', 'ema_50_1h', 'ema_200_1h', - 'bb_lower_1h', 'bb_upper_1h', 'stochrsi_k_1h', 'stochrsi_d_1h', + 'bb_lower_1h', 'bb_upper_1h', 'bb_width_1h', # 布林带 + 宽度 + 'stochrsi_k_1h', 'stochrsi_d_1h', 'kdj_k_1h', 'kdj_d_1h', 'kdj_j_1h', # KDJ 指标 + 'cci_1h', 'willr_1h', # CCI + Williams %R 'macd_1h', 'macd_signal_1h', 'ema_5_1h', 'ema_20_1h', 'ema5_cross_above_ema20', # === 1h 趋势特征 === 'adx_1h', 'plus_di_1h', 'minus_di_1h', # ADX 趋势强度 @@ -1336,6 +1356,22 @@ class FreqaiPrimer(IStrategy): kdj_3m_death_cross = (dataframe['kdj_k_3m'] > 80) & (dataframe['kdj_k_3m'] < dataframe['kdj_d_3m']) & (dataframe['kdj_k_3m'].shift(1) >= dataframe['kdj_d_3m'].shift(1)) kdj_3m_exit = kdj_3m_death_cross + # 条件6: CCI 超买出场(1h CCI > 100) + cci_overbought = pd.Series(False, index=dataframe.index) + if 'cci_1h' in dataframe.columns: + cci_overbought = dataframe['cci_1h'] > 100 + + # 条件7: Williams %R 超买出场(3m %R > -20) + willr_overbought = pd.Series(False, index=dataframe.index) + if 'willr_3m' in dataframe.columns: + willr_overbought = dataframe['willr_3m'] > -20 + + # 条件8: 布林带宽度突然收窄(趋势衰竭) + bb_width_shrink = pd.Series(False, index=dataframe.index) + if 'bb_width_1h' in dataframe.columns: + # 宽度从高位(>6%)快速收窄到低位(<3%) + bb_width_shrink = (dataframe['bb_width_1h'] < 3) & (dataframe['bb_width_1h'].shift(5) > 6) + # === 新增:1h 趋势反转检测 === # 计算趋势反转得分(0-100,得分越高越可能反转) if 'ema_bear_align_1h' in dataframe.columns and 'adx_1h' in dataframe.columns: @@ -1400,13 +1436,16 @@ class FreqaiPrimer(IStrategy): self.strategy_log(f"[{metadata['pair']}] 警告:缺少趋势特征,趋势出场禁用") # === 最终出场条件 === - # 方案1:传统条件 OR 趋势反转 OR KDJ 高位死叉 + # 综合多个指标的出场信号 final_condition = ( breakout_condition | volume_spike | macd_downward | rsi_overbought | kdj_3m_exit | + cci_overbought | + willr_overbought | + bb_width_shrink | trend_protected_exit ) @@ -1508,6 +1547,41 @@ class FreqaiPrimer(IStrategy): tech_score += kdj_1h_score tech_components += 1 + # 1.7 CCI 得分(1h 级别,避免半山腰抄底) + if 'cci_1h' in dataframe.columns: + # CCI < -100 超卖得满分,CCI > 100 超买得 0 分 + # 线性映射:-100 -> 100分,0 -> 50分,100 -> 0分 + cci_score = ((100 - dataframe['cci_1h'].clip(-100, 100)) / 2).clip(0, 100) + tech_score += cci_score + tech_components += 1 + + # 1.8 Williams %R 得分(3m 级别,精准抄底) + if 'willr_3m' in dataframe.columns: + # Williams %R 范围 -100 到 0 + # %R < -80 深度超卖得满分,%R > -20 超买得 0 分 + # 映射:-100 -> 100分,-80 -> 100分,-50 -> 50分,-20 -> 0分,0 -> 0分 + willr_score = (((-20) - dataframe['willr_3m'].clip(-100, 0)) / 60 * 100).clip(0, 100) + tech_score += willr_score + tech_components += 1 + + # 1.9 布林带宽度得分(识别波动率变化) + if 'bb_width_1h' in dataframe.columns: + # 宽度过小(<2%)表示震荡末期,即将突破,得高分 + # 宽度适中(2-5%)正常,中等分 + # 宽度过大(>8%)高波动,低分 + bb_width_score = pd.Series(50.0, index=dataframe.index) # 默认 50 分 + bb_width_score = bb_width_score.where( + dataframe['bb_width_1h'] >= 2, + ((2 - dataframe['bb_width_1h'].clip(0, 2)) / 2 * 50 + 50) # <2% 时得 50-100 分 + ) + bb_width_score = bb_width_score.where( + dataframe['bb_width_1h'] <= 8, + ((dataframe['bb_width_1h'].clip(8, 15) - 8) / 7 * -50 + 50) # >8% 时得 0-50 分 + ) + bb_width_score = bb_width_score.clip(0, 100) + tech_score += bb_width_score + tech_components += 1 + # 计算技术指标平均得分 if tech_components > 0: tech_score = tech_score / tech_components