This commit is contained in:
zhangkun9038@dingtalk.com 2025-07-06 17:31:30 +00:00
parent 61b448e8be
commit 3f25ff41ce

View File

@ -53,21 +53,21 @@ class FreqaiPrimer(IStrategy):
"ema200": {"color": "blue"},
"bb_upperband": {"color": "gray"},
"bb_lowerband": {"color": "gray"},
"bb_middleband": {"color": "gray"}
},
"subplots": {
"Signals": {
"enter_long": {"color": "green"},
"exit_long": {"color": "red"}
"enter_long": {"color": "green", "type": "scatter"},
"exit_long": {"color": "red", "type": "scatter"}
},
"Price-Value Divergence": {
"&-price_value_divergence": {"color": "purple"}
"Indicators": {
"&-price_value_divergence": {"color": "purple"},
"volume_z_score": {"color": "orange"},
"rsi": {"color": "cyan"},
"stochrsi_k": {"color": "magenta"},
},
"Volume Z-Score": {
"volume_z_score": {"color": "orange"}
},
"RSI": {
"rsi": {"color": "cyan"}
"Bearish Signals": {
"bearish_signal": {"type": "bar", "color": "red"},
"stochrsi_overbought": {"type": "bar", "color": "orange"},
}
}
}
@ -156,9 +156,9 @@ class FreqaiPrimer(IStrategy):
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["volume_z_score"] = dataframe["volume_z_score"].replace([np.inf, -np.inf], 0).ffill().fillna(0)
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)
return dataframe
def is_stochrsi_overbought(self, dataframe: DataFrame, period=10, threshold=85) -> bool:
@ -172,50 +172,62 @@ class FreqaiPrimer(IStrategy):
avg_stochrsi = dataframe['stochrsi_k'].rolling(window=period).mean()
return avg_stochrsi > threshold
def is_bearish_market(self, dataframe: DataFrame, metadata: dict, timeframe: str = "4h") -> DataFrame:
def is_bearish_market(self, dataframe: DataFrame, metadata: dict, timeframe: str = "4h") -> pd.Series:
"""
判断是否为熊市信号使用已有的 4h 指标
返回一个与 dataframe 等长的布尔 Series
"""
pair = metadata.get('pair', 'Unknown')
logger.debug(f"[{pair}] 开始计算 is_bearish_market")
# 防御性检查
required_columns = ['close_4h', 'high_4h', 'low_4h', 'open_4h', 'stochrsi_k_4h', 'stochrsi_d_4h', 'bb_middle_4h']
missing = [col for col in required_columns if col not in dataframe.columns]
if missing:
logger.error(f"[{pair}] 缺少必要列:{missing},返回全 False")
return pd.Series([False] * len(dataframe), index=dataframe.index)
# 条件1价格低于布林带中轨
cond1 = dataframe['close'] < dataframe['bb_middle']
cond1 = dataframe['close_4h'] < dataframe['bb_middle_4h']
# 条件2STOCHRSI 超买后反转K > 80 并开始向下交叉 D
cond2 = (dataframe['stochrsi_k'] > 80) & (dataframe['stochrsi_k'] < dataframe['stochrsi_d'])
# 条件2STOCHRSI 超买后反转
stochrsi_k = dataframe['stochrsi_k_4h']
stochrsi_d = dataframe['stochrsi_d_4h']
cond2 = (stochrsi_k > 80) & (stochrsi_k < stochrsi_d)
# 条件3出现看跌蜡烛图形态如乌云盖顶、看跌吞没等
def detect_candlestick_bearish(df, i):
open = df['open'].iloc[i]
high = df['high'].iloc[i]
low = df['low'].iloc[i]
close = df['close'].iloc[i]
prev_open = df['open'].iloc[i - 1] if i > 0 else open
prev_close = df['close'].iloc[i - 1] if i > 0 else close
# 条件3看跌蜡烛图形态
open = dataframe['open_4h']
close = dataframe['close_4h']
high = dataframe['high_4h']
low = dataframe['low_4h']
prev_open = open.shift(1)
prev_close = close.shift(1)
# 看跌吞没形态
if open < prev_close and close > prev_open and close < prev_close:
return True
# 乌云盖顶
if close < prev_open and open > prev_close and close < ((prev_open + prev_close) / 2):
return True
# 射击之星
body = abs(open - close)
upper_wick = high - max(open, close)
lower_wick = min(open, close) - low
if upper_wick > 2 * body and lower_wick < body:
return True
# 看跌吞没形态
cond_engulfing = (
(open < prev_close) &
(close > prev_open) &
(close < prev_close)
)
return False
# 乌云盖顶
cond_dark_cloud_cover = (
(close < prev_open) &
(open > prev_close) &
(close < (prev_open + prev_close) / 2)
)
# 在 dataframe 上逐行应用蜡烛图识别
bearish_candle = pd.Series(index=dataframe.index, dtype=bool)
for i in range(len(dataframe)):
bearish_candle.iloc[i] = detect_candlestick_bearish(dataframe, i)
# 射击之星
body = abs(open - close)
upper_wick = high - np.maximum(open, close)
lower_wick = np.minimum(open, close) - low
cond_shooting_star = (upper_wick > 2 * body) & (lower_wick < body)
# 构造最终的熊市信号
bearish_candle = cond_engulfing | cond_dark_cloud_cover | cond_shooting_star
# 最终熊市信号
bearish_signal = cond1 & cond2 & bearish_candle
logger.debug(f"[{pair}] 熊市信号生成完成,共 {bearish_signal.sum()} 根 K 线符合")
return bearish_signal
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
@ -261,9 +273,10 @@ class FreqaiPrimer(IStrategy):
dataframe_4h['bb_middle'] = middleband
dataframe_4h['bb_lower'] = lowerband
# 将 4h 指标映射回主时间框架3m
for col in ['stochrsi_k', 'stochrsi_d', 'bb_upper', 'bb_middle', 'bb_lower']:
dataframe[col + '_4h'] = dataframe_4h[col].reindex(dataframe.index, method='ffill').fillna(method='bfill')
# 映射 OHLC 和其他指标到主时间框架,添加 _4h 后缀
for col in ['open', 'high', 'low', 'close', 'stochrsi_k', 'stochrsi_d', 'bb_upper', 'bb_middle', 'bb_lower']:
new_col = f"{col}_4h"
dataframe[new_col] = dataframe_4h[col].reindex(dataframe.index, method='ffill').bfill()
# 数据清理
for col in ["ema200", "bb_upperband", "bb_middleband", "bb_lowerband", "rsi", "volume_z_score", "&-price_value_divergence", "price_value_divergence"]:
@ -379,7 +392,11 @@ class FreqaiPrimer(IStrategy):
stochrsi_threshold = self.linear_map(trend_score, 0, 100, stochrsi_max, stochrsi_min)
# 获取 4h 时间框架的数据
dataframe_4h = self.dp.get_pair_dataframe(pair=pair, timeframe="4h")
dataframe_4h = dataframe[['close', 'stochrsi_k_4h', 'stochrsi_d_4h', 'bb_middle_4h', 'open']].copy()
bearish_signal = self.is_bearish_market(dataframe_4h, metadata, timeframe="4h")
bearish_signal_aligned = bearish_signal.reindex(dataframe.index, method='ffill').fillna(False)
cond6 = ~bearish_signal_aligned
# 确保指标存在(或提前在 populate_indicators 中计算好)
if not dataframe_4h.empty and 'stochrsi_k' in dataframe_4h.columns:
stochrsi_overbought = self.is_stochrsi_overbought(dataframe_4h, period=10, threshold=85)
@ -393,14 +410,22 @@ class FreqaiPrimer(IStrategy):
dataframe_4h['stochrsi_k'] = stochrsi['fastk']
dataframe_4h['stochrsi_d'] = stochrsi['fastd']
real = ta.TYPPRICE(dataframe_4h)
upperband, middleband, lowerband = ta.BBANDS(real, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
dataframe_4h['bb_upper'] = upperband
dataframe_4h['bb_middle'] = middleband
dataframe_4h['bb_lower'] = lowerband
# 在 populate_entry_trend 中
high = dataframe['high_4h'].values
low = dataframe['low_4h'].values
close = dataframe['close_4h'].values
# 使用已经准备好的 dataframe_4h
bearish_signal = self.is_bearish_market(dataframe_4h, metadata, timeframe="4h")
# 手动实现 TYPPRICE
real = (high + low + close) / 3
upperband, middleband, lowerband = ta.BBANDS(real, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
# 将结果放回 dataframe
dataframe["bb_upperband-period"] = upperband
dataframe["bb_middleband-period"] = middleband
dataframe["bb_lowerband-period"] = lowerband
# 调用熊市判断函数
bearish_signal = self.is_bearish_market(dataframe, metadata, timeframe="4h")
# 映射回主时间框架
dataframe['bearish_signal'] = bearish_signal.reindex(dataframe.index, method='ffill').fillna(False)
@ -427,34 +452,29 @@ class FreqaiPrimer(IStrategy):
cond4 = (dataframe["close"] <= dataframe["bb_lowerband"])
cond5 = (dataframe["stochrsi_k"] < stochrsi_threshold)
cond6 = ~self.is_bearish_market(dataframe_4h, metadata, timeframe="4h")
# cond7 定义
cond7 = ~stochrsi_overbought_aligned
buy_condition = cond1 & cond2 & cond3 & cond4 & cond5 & cond6 & cond7
#buy_condition = cond1 & cond2 & cond3 & cond4 & cond5
conditions.append(buy_condition)
logger.debug(f"[{pair}] 买入条件检查 - "
f"&-price_value_divergence={divergence_value:.6f} < {self.buy_threshold:.6f}: {cond1.iloc[-1]}, "
f"volume_z_score={volume_z_score_value:.2f} > {volume_z_score_threshold:.2f}: {cond2.iloc[-1]}, "
f"rsi={rsi_value:.2f} < {rsi_threshold:.2f}: {cond3.iloc[-1]}, "
f"close={bb_close_value:.6f} <= bb_lowerband={bb_lower_value:.6f}: {cond4.iloc[-1]}, "
f"stochrsi_k={stochrsi_value:.2f} < {stochrsi_threshold:.2f}: {cond5.iloc[-1]}, "
f"非熊市={cond6.iloc[-1]}, "
f"STOCHRSI未持续超买={cond7.iloc[-1]}")
bearish_signal = cond6.iloc[-1]
# logger.debug(f"[{pair}] 买入条件检查 - "
# f"&-price_value_divergence={divergence_value:.6f} < {self.buy_threshold:.6f}: {cond1.iloc[-1]}, "
# f"volume_z_score={volume_z_score_value:.2f} > {volume_z_score_threshold:.2f}: {cond2.iloc[-1]}, "
# f"rsi={rsi_value:.2f} < {rsi_threshold:.2f}: {cond3.iloc[-1]}, "
# f"close={bb_close_value:.6f} <= bb_lowerband={bb_lower_value:.6f}: {cond4.iloc[-1]}, "
# f"stochrsi_k={stochrsi_value:.2f} < {stochrsi_threshold:.2f}: {cond5.iloc[-1]}, "
# f"非熊市={cond6.iloc[-1]}, "
# f"STOCHRSI未持续超买={cond7.iloc[-1]}")
# bearish_signal = cond6.iloc[-1]
buy_condition = cond1 & cond2 & cond3 & cond4 & cond5 & cond6
conditions.append(buy_condition)
logger.debug(f"[{pair}] 买入条件检查 - "
f"&-price_value_divergence={divergence_value:.6f} < {self.buy_threshold:.6f}: {cond1.iloc[-1]}, "
f"volume_z_score={volume_z_score_value:.2f} > {volume_z_score_threshold:.2f}: {cond2.iloc[-1]}, "
f"rsi={rsi_value:.2f} < {rsi_threshold:.2f}: {cond3.iloc[-1]}, "
f"close={bb_close_value:.6f} <= bb_lowerband={bb_lower_value:.6f}: {cond4.iloc[-1]}, "
f"stochrsi_k={stochrsi_value:.2f} < {stochrsi_threshold:.2f}: {cond5.iloc[-1]}, "
f"非熊市={bearish_signal}")
# logger.debug(f"[{pair}] 买入条件检查 - "
# f"&-price_value_divergence={divergence_value:.6f} < {self.buy_threshold:.6f}: {cond1.iloc[-1]}, "
# f"volume_z_score={volume_z_score_value:.2f} > {volume_z_score_threshold:.2f}: {cond2.iloc[-1]}, "
# f"rsi={rsi_value:.2f} < {rsi_threshold:.2f}: {cond3.iloc[-1]}, "
# f"close={bb_close_value:.6f} <= bb_lowerband={bb_lower_value:.6f}: {cond4.iloc[-1]}, "
# f"stochrsi_k={stochrsi_value:.2f} < {stochrsi_threshold:.2f}: {cond5.iloc[-1]}, "
# f"非熊市={bearish_signal}")
else:
logger.warning(f"[{pair}] ⚠️ &-price_value_divergence 列缺失,跳过买入信号生成")