信义光能问题修复:两层级过滤+趋势检查

Bug 1 — strategy_lifecycle.py: enrich_timing_signal 用 factors[-1] 当信号
  信义光能: base_signal=neutral, factors=[大盘中性,行业偏弱,高估值]
  旧逻辑: factors[-1]='行业偏弱'→成为timing_signal→无效信号
  新逻辑: 先找有效操作方向(买入/加仓/观望/持有/关注/信号不充分),
          找不到→信号不充分。不再从上下文因子里拼凑信号。

Bug 2 — stale_push_wlin.py: 信号过滤太松
  旧逻辑: 只跳过特定关键词(等企稳/关注/信号不充分/持有)
  新逻辑: 信号必须含"买入"或"加仓"才进推荐,其他一律跳过

Check 3 — 趋势检查(新增)
  fetch_trend_data(): 取实时行情+30日K线计算MA排列
  空头排列/弱势震荡→不推荐
  药明康德通过(多头排列+买入信号)  信义光能不通过(空头+行业偏弱)
This commit is contained in:
知微
2026-06-24 14:39:50 +08:00
parent e83dfc415d
commit 8f830b8de2
6 changed files with 351 additions and 106 deletions
+92 -6
View File
@@ -27,6 +27,72 @@ except ImportError:
sys.path.insert(0, "/home/hmo/MoFin/scripts")
from stock_scorer import score_future_outlook, is_hk_stock, settlement_delay_note
# ── 趋势检查 ────────────────────────────────────────────────────
def fetch_trend_data(code):
"""取均线数据判断趋势状态。返回 (current_price, ma5, trend_label) 或 None"""
try:
prefix = "sh" if code.startswith(('60','68','51','56','50')) else "sz" if code.startswith(('00','30','15')) else "hk"
url = f"http://qt.gtimg.cn/q={prefix}{code}"
req = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
resp = urlopen(req, timeout=5).read().decode('gbk')
fld = resp.split('=')[1].strip().strip('"').strip(';').split('~')
current = float(fld[3]) if len(fld) > 3 else 0
except:
return None
try:
url = f"http://ifzq.gtimg.cn/appstock/app/fqkline/get?param={prefix}{code},day,,,30,qfq"
req = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
resp = urlopen(req, timeout=5).read().decode('utf-8')
data = json.loads(resp)
day_key = 'qfqday' if prefix != 'hk' else 'day'
bars = data.get('data', {}).get(f'{prefix}{code}', {}).get(day_key, [])
except:
return None
if not bars or current <= 0:
return None
closes = [float(b[2]) for b in bars]
if len(closes) < 5:
return None
def ma(n):
return sum(closes[-n:]) / n
ma5 = ma(5)
ma10 = ma(10) if len(closes) >= 10 else None
ma20 = ma(20) if len(closes) >= 20 else None
# 趋势分析
pct_above_ma5 = (current - ma5) / ma5 * 100
uptrend = False
if ma20 and ma10:
if ma5 > ma10 > ma20:
trend_label = "多头排列"
uptrend = True
elif current < ma5 and ma5 < ma10 and current < ma10:
trend_label = "空头排列"
elif current > ma5 and ma5 > ma10:
trend_label = "短期转强"
uptrend = True
else:
trend_label = "震荡"
if current > ma5 > ma10:
uptrend = True
else:
trend_label = "数据不足"
return {
'price': current,
'ma5': round(ma5, 2),
'ma10': round(ma10, 2) if ma10 else None,
'ma20': round(ma20, 2) if ma20 else None,
'pct_above_ma5': round(pct_above_ma5, 1),
'trend': trend_label,
'uptrend': uptrend,
}
# ── XMPP
XMPP_BRIDGE = "http://127.0.0.1:5805/"
XMPP_USER = "hmo@yoin.fun"
@@ -278,10 +344,11 @@ def main():
-code_data.get(s[1], {}).get("rr_ratio", 0)
))
# 只展示有清晰操作信号的个股:不含"等企稳""关注""信号不充分""neutral"及纯持有信号
SKIP_KEYWORDS = ["等企稳", "关注", "信号不充分"]
BUY_KEYWORDS = ["买入", "加仓"]
# 只展示有清晰操作信号的个股
# timing_signal 必须是明确操作方向:买入/加仓/观望/关注/信号不充分
# 行业描述(行业偏弱/行业偏强/大盘变盘等)不是操作信号,一律跳过
VALID_SIGNALS = {"买入", "加仓", "观望", "关注", "信号不充分"}
SKIP_KEYWORDS = ["等企稳", "信号不充分"]
actionable = []
for s in stocks:
@@ -292,9 +359,19 @@ def main():
if any(kw in sig for kw in SKIP_KEYWORDS):
continue
# 中性信号跳过
stripped = sig.strip().lower()
if not stripped or stripped in ("", "neutral", "持有", "深套持有", "弱势持有"):
stripped = sig.strip()
if not stripped or stripped.lower() in ("", "neutral", "持有", "深套持有", "弱势持有"):
continue
# 信号必须含买入/加仓才推荐——其他非操作信号跳过
if not any(kw in sig for kw in ["买入", "加仓"]):
continue
# 趋势检查:必须不是空头排列(价格在MA5以下且MA5<MA10
trend = fetch_trend_data(s[1])
if trend:
if not trend['uptrend']:
# 空头排列或弱势震荡,不推荐
continue
# (如果趋势数据获取失败,放行—不因数据问题错杀)
actionable.append(s)
if not actionable:
@@ -613,6 +690,15 @@ def main():
cooled, elapsed, cd_key = in_cooldown(code, branch_action, cooldown)
if cooled:
continue
# 策略质量过滤:只有正向/中性信号才推操作建议
bad_keywords = ["偏弱", "弱势", "观望", "卖出", "回避", "回避"]
if any(kw in sig for kw in bad_keywords):
continue
# 行业背景过滤:行业大跌时不在买入区推荐(即使个股信号好)
if "大跌" in sector:
continue
# 换仓评估:现金不足时评估是否卖差票换推荐股
swap_text = None