动态labels_std和labels_mean

This commit is contained in:
zhangkun9038@dingtalk.com 2025-06-01 00:07:53 +00:00
parent e9e23994f1
commit c77a1925d8

View File

@ -10,12 +10,13 @@ from freqtrade.strategy import IStrategy
logger = logging.getLogger(__name__)
class FreqaiPrimer(IStrategy):
class ValueDivergencePrimerRegression(IStrategy):
"""
策略说明
- 只使用回归模型预测价值背离&-price_value_divergence
- 动态调整阈值基于 price_value_divergence 的历史数据
- 放宽过滤条件增加信号触发频率
- 动态计算 labels_mean labels_std基于当前数据
- 在日志中打印所有币对的 labels_mean labels_std 汇总
- 优化出场逻辑避免过早卖出
- 使用RSI和布林带作为辅助过滤条件
"""
@ -83,6 +84,13 @@ class FreqaiPrimer(IStrategy):
super().__init__(config, *args, **kwargs)
logger.setLevel(logging.DEBUG)
logger.debug("✅ 策略已初始化,日志级别设置为 DEBUG")
# 动态止盈参数
self.trailing_stop_enabled = False
self.trailing_stop_start = 0.03 # 价格上涨 3% 后启动 Trailing Stop
self.trailing_stop_distance = 0.01 # 允许价格回落 1% 时卖出
# 存储每个币对的 labels_mean 和 labels_std
self.pair_stats = {}
self.stats_logged = False # 标记是否已打印汇总日志
def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame:
"""
@ -103,7 +111,6 @@ class FreqaiPrimer(IStrategy):
dataframe["%-adx-period"] = ta.ADX(dataframe, timeperiod=period)
dataframe["%-relative_volume-period"] = dataframe["volume"] / dataframe["volume"].rolling(period).mean()
# 计算价值背离作为特征
dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200)
dataframe["%-price_value_divergence"] = (dataframe["close"] - dataframe["ema200"]) / dataframe["ema200"]
@ -126,17 +133,13 @@ class FreqaiPrimer(IStrategy):
logger.warning(f"[{metadata['pair']}] 数据量不足({len(dataframe)}根K线需要至少200根K线进行训练")
return dataframe
# 计算200周期EMA作为公平价值
dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200)
# 计算价值背离(回归目标)
dataframe["&-price_value_divergence"] = (dataframe["close"] - dataframe["ema200"]) / dataframe["ema200"]
# 成交量Z分数
dataframe["volume_mean_20"] = dataframe["volume"].rolling(20).mean()
dataframe["volume_std_20"] = dataframe["volume"].rolling(20).std()
dataframe["volume_z_score"] = (dataframe["volume"] - dataframe["volume_mean_20"]) / dataframe["volume_std_20"]
# 数据清理
dataframe["&-price_value_divergence"] = dataframe["&-price_value_divergence"].replace([np.inf, -np.inf], 0).ffill().fillna(0)
dataframe["volume_z_score"] = dataframe["volume_z_score"].replace([np.inf, -np.inf], 0).ffill().fillna(0)
@ -145,7 +148,7 @@ class FreqaiPrimer(IStrategy):
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
计算所有指标包括FreqAI预测结果基于历史数据动态调整阈值
计算所有指标包括FreqAI预测结果动态计算 labels_mean labels_std
"""
logger.info(f"[{metadata['pair']}] 当前可用列调用FreqAI前{list(dataframe.columns)}")
@ -153,34 +156,13 @@ class FreqaiPrimer(IStrategy):
dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200)
dataframe["price_value_divergence"] = (dataframe["close"] - dataframe["ema200"]) / dataframe["ema200"]
# 动态计算阈值:基于 price_value_divergence 的历史均值和标准差
if "price_value_divergence" in dataframe.columns:
divergence_mean = dataframe["price_value_divergence"].mean()
divergence_std = dataframe["price_value_divergence"].std()
k = 1.4 # 标准差倍数,可以调整, 改成1.4,降低入场门槛
self.buy_threshold = max(divergence_mean - k * divergence_std, -0.05) # 设置下限,避免过低
self.sell_threshold = min(divergence_mean + k * divergence_std, 0.05) # 设置上限,避免过高
# 确保阈值至少有一定宽度
self.buy_threshold = min(self.buy_threshold, -0.005)
self.sell_threshold = max(self.sell_threshold, 0.005)
logger.info(f"[{metadata['pair']}] price_value_divergence 历史均值:{divergence_mean:.4f}")
logger.info(f"[{metadata['pair']}] price_value_divergence 历史标准差:{divergence_std:.4f}")
logger.info(f"[{metadata['pair']}] 动态买入阈值:{self.buy_threshold:.4f}")
logger.info(f"[{metadata['pair']}] 动态卖出阈值:{self.sell_threshold:.4f}")
else:
self.buy_threshold = -0.015 # 回退阈值
self.sell_threshold = 0.015
logger.warning(f"[{metadata['pair']}] 无法计算动态阈值,使用默认阈值 ±0.015")
# 调用FreqAI预测价值背离
if not hasattr(self, 'freqai') or self.freqai is None:
logger.error(f"[{metadata['pair']}] FreqAI 未初始化,请确保回测命令中启用了 --freqai")
# 回退到规则计算
dataframe["&-price_value_divergence"] = dataframe["price_value_divergence"]
else:
dataframe = self.freqai.start(dataframe, metadata, self)
# 检查预测结果
if "&-price_value_divergence" not in dataframe.columns:
logger.warning(f"[{metadata['pair']}] 回归模型未生成 &-price_value_divergence回退到规则计算")
dataframe["&-price_value_divergence"] = dataframe["price_value_divergence"]
@ -199,22 +181,77 @@ class FreqaiPrimer(IStrategy):
for col in ["ema200", "bb_upperband", "bb_middleband", "bb_lowerband", "rsi", "volume_z_score", "&-price_value_divergence", "price_value_divergence"]:
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0).ffill().fillna(0)
# 动态计算 labels_mean 和 labels_std
pair = metadata["pair"]
if "&-price_value_divergence" in dataframe.columns:
recent_data = dataframe["&-price_value_divergence"].tail(1000)
labels_mean = recent_data.mean()
labels_std = recent_data.std()
if np.isnan(labels_std) or labels_std == 0:
labels_std = 0.01
logger.warning(f"[{pair}] labels_std 计算异常,使用默认值 0.01")
# 存储到 pair_stats
self.pair_stats[pair] = {"labels_mean": labels_mean, "labels_std": labels_std}
# 根据 labels_std 调整 k_buy 和 k_sell
if labels_std > 0.015:
k_buy = 1.2
k_sell = 1.5
elif labels_std < 0.010:
k_buy = 0.8
k_sell = 1.0
else:
k_buy = 1.0
k_sell = 1.2
if labels_mean > 0.015:
k_sell += 0.5
logger.info(f"[{pair}] labels_mean 较高({labels_mean:.4f}),增加 k_sell 到 {k_sell:.2f}")
# 计算阈值
self.buy_threshold = labels_mean - k_buy * labels_std
self.sell_threshold = labels_mean + k_sell * labels_std
# 边界保护
self.buy_threshold = max(self.buy_threshold, -0.05)
self.buy_threshold = min(self.buy_threshold, -0.005)
self.sell_threshold = min(self.sell_threshold, 0.05)
self.sell_threshold = max(self.sell_threshold, 0.005)
# 打印当前币对的详细日志
logger.info(f"[{pair}] labels_mean{labels_mean:.4f}, labels_std{labels_std:.4f}")
logger.info(f"[{pair}] k_buy{k_buy:.2f}, k_sell{k_sell:.2f}")
logger.info(f"[{pair}] 动态买入阈值:{self.buy_threshold:.4f}")
logger.info(f"[{pair}] 动态卖出阈值:{self.sell_threshold:.4f}")
# 打印所有币对的 labels_mean 和 labels_std 汇总(仅第一次)
if not self.stats_logged:
logger.info("===== 所有币对的 labels_mean 和 labels_std 汇总 =====")
for p, stats in self.pair_stats.items():
logger.info(f"[{p}] labels_mean{stats['labels_mean']:.4f}, labels_std{stats['labels_std']:.4f}")
logger.info("==============================================")
self.stats_logged = True # 标记已打印,避免重复
else:
self.buy_threshold = -0.015
self.sell_threshold = 0.015
logger.warning(f"[{pair}] 无法计算 labels_mean 和 labels_std使用默认阈值 ±0.015")
logger.info(f"[{metadata['pair']}] 指标计算完成,列:{list(dataframe.columns)}")
return dataframe
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
"""
入场逻辑基于动态阈值和放宽的过滤条件
入场逻辑保持不变基于动态阈值
"""
conditions = []
if "&-price_value_divergence" in df.columns:
# 买入条件:价格低估且放量
buy_condition = (df["&-price_value_divergence"] < self.buy_threshold)
buy_condition &= (df["volume_z_score"] > 1.5) # 降低成交量要求
# RSI超卖过滤放宽条件
buy_condition &= (df["rsi"] < 42) #40改成42,降低入场门槛
# 价格触及布林带下轨
buy_condition &= (df["volume_z_score"] > 1.5)
buy_condition &= (df["rsi"] < 40)
buy_condition &= (df["close"] <= df["bb_lowerband"])
conditions.append(buy_condition)
else:
@ -228,16 +265,13 @@ class FreqaiPrimer(IStrategy):
def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
"""
出场逻辑基于动态阈值和放宽的过滤条件
出场逻辑优化以避免过早卖出
"""
conditions = []
if "&-price_value_divergence" in df.columns:
# 卖出条件:价格高估
sell_condition = (df["&-price_value_divergence"] > self.sell_threshold)
sell_condition &= (df["volume_z_score"] > 1.5) # 降低成交量要求
# RSI超买过滤放宽条件
sell_condition |= (df["rsi"] > 60)
sell_condition |= (df["rsi"] > 75)
conditions.append(sell_condition)
else:
logger.warning("⚠️ &-price_value_divergence 列缺失,跳过该条件")
@ -252,9 +286,28 @@ class FreqaiPrimer(IStrategy):
current_rate: float, current_profit: float,
min_roi: Dict[float, float], max_profit: float):
"""
动态调整仓位持仓时间超过30分钟后强制平仓
动态调整仓位添加最小持仓时间和 Trailing Stop
"""
hold_time = (current_time - trade.open_date_utc).total_seconds() / 60
if hold_time < 15:
logger.info(f"[{trade.pair}] 持仓时间 {hold_time:.1f} 分钟,未达到最小持仓时间 15 分钟,暂不退出")
return None
profit_ratio = (current_rate - trade.open_rate) / trade.open_rate
if profit_ratio >= self.trailing_stop_start and not self.trailing_stop_enabled:
self.trailing_stop_enabled = True
trade.adjust_max_rate(current_rate)
logger.info(f"[{trade.pair}] 价格上涨超过 {self.trailing_stop_start*100:.1f}%,启动 Trailing Stop")
if self.trailing_stop_enabled:
max_rate = trade.max_rate
trailing_stop_price = max_rate * (1 - self.trailing_stop_distance)
if current_rate < trailing_stop_price:
logger.info(f"[{trade.pair}] 价格回落至 Trailing Stop 点 {trailing_stop_price:.6f},触发卖出")
return -1
trade.adjust_max_rate(current_rate)
if hold_time > 30:
logger.info(f"[{trade.pair}] 持仓时间超过30分钟强制平仓")
return -1
@ -273,6 +326,7 @@ class FreqaiPrimer(IStrategy):
if len(recent_trades) > 0:
logger.info(f"[{pair}] 5分钟内有近期交易跳过本次入场")
return False
self.trailing_stop_enabled = False
return True
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,