fix: 信号-分析对齐(reconcile以已存分析为唯一事实源) + prompt持仓上下文(已持有/未持有分开给建议,禁止两头写)

This commit is contained in:
hmo
2026-07-22 10:29:23 +08:00
parent f42dc9381b
commit c91d4c33f0
3 changed files with 72 additions and 30 deletions
+28 -2
View File
@@ -95,6 +95,14 @@ def collect_data(code):
data["changelog_json"] = r[12] or ""
data["reassessed_at"] = r[13] or ""
data["position_advice"] = r[14] or ""
# 持仓状态(2026-07-22 老爸要求:LLM 必须知道是否持有/成本/股数)
hr = conn.execute("SELECT shares, cost, price FROM holdings WHERE code=? AND is_active=1 AND shares>0", (code,)).fetchone()
if hr and hr[0]:
data["held"] = True
data["held_shares"] = hr[0]
data["held_cost"] = hr[1] or 0
else:
data["held"] = False
conn.close()
# 从腾讯API拉最新价和基本面
@@ -205,6 +213,20 @@ def build_prompt(data):
# 完整分析原文(不截断)
_full_analysis = data.get('full_analysis', '') or ''
_fa_display = _full_analysis if _full_analysis else '(首次分析,无历史)'
# ── 持仓上下文(2026-07-22 老爸要求:LLM 必须知道持有状态,建议不得两头都写)──
if data.get('held'):
_sh = data.get('held_shares', 0)
_cost = data.get('held_cost', 0)
_px = data.get('price', 0) or 0
_pnl = ((_px - _cost) / _cost * 100) if _cost else 0
_position_context = (f"⚠️ 我当前【已持有】{data['code']}{_sh}股,成本{_cost:.2f}元,"
f"现价{_px}元(盈亏{_pnl:+.1f}%)。你的建议必须基于「已持有」状态给出"
f"(加减仓/止损止盈/持有观察),禁止给「未持有者」的建仓建议。")
else:
_position_context = (f"⚠️ 我当前【未持有】{data['code']}。你的建议必须基于「未持有」状态给出"
f"(是否建仓/什么价位建仓/仓位多大),禁止假设我有浮盈、"
f"禁止出现「已持仓者」视角的建议。")
_orig_strategy_section = f"""当前策略参数: {_params_str}
@@ -235,6 +257,7 @@ PE={data.get('pe','?')}(最新财报) 市值={data.get('mcap','?')}亿
当前信号:{data.get('timing_signal','?')} 分类:{data.get('stock_category','?')}
我的总资产={total}元,可用现金={cash}元。
{_position_context}
请严格按以下格式输出(注意节标题不可省略):
@@ -373,8 +396,11 @@ def save_result(code, full_text, parsed):
conn.execute(sql, params)
conn.commit()
# ── 推荐操作 tag 同步(与 XMPP 动作级信号同源)──
sync_recommend_tag(conn, code, parsed.get("signal", ""))
# ── 信号以分析为唯一事实源(防信号/分析脱节)──
from mofin_db import reconcile_signal_from_analysis
final_sig = reconcile_signal_from_analysis(conn, code)
# ── 推荐操作 tag 同步(跟随对齐后的信号)──
sync_recommend_tag(conn, code, final_sig)
# 买入信号推送已统一收拢到 sync_recommend_tag 的转场推送(防双重告警)。
# 本路径只负责写库+tag,推送由 mofin_db.push_recommend_alert 在 tag 转场时触发。
+8 -28
View File
@@ -550,34 +550,14 @@ def main():
else:
print(f" ⚠️ 12维分析未完成,跳过保存")
print(f" [DB] holding_strategies 已更新: {code}")
# 从LLM输出提取信号
if _full_analysis_text and '【综合结论】' in _full_analysis_text:
try:
_sig_line = [l for l in _full_analysis_text.split('\n') if '综合结论' in l]
if _sig_line:
_sig = '买入' if '买入' in _sig_line[0] else '关注' if '关注' in _sig_line[0] else '观望' if '观望' in _sig_line[0] else '卖出' if '卖出' in _sig_line[0] else ''
if _sig:
_ts_conn = __import__('sqlite3').connect('/home/hmo/MoFin/data/mofin.db')
_ts_conn.execute(
"UPDATE holding_strategies SET timing_signal=? WHERE code=? AND status='active'", (_sig, code))
_ts_conn.commit()
# 推荐操作 tag 同步(与 XMPP 动作级信号同源)
from mofin_db import sync_recommend_tag
sync_recommend_tag(_ts_conn, code, _sig)
_ts_conn.close()
print(f" ✅ LLM信号={_sig} 已写入")
# 买入信号→推XMPP
if _sig == "买入":
try:
_nr2 = __import__('sqlite3').connect('/home/hmo/MoFin/data/mofin.db').execute(
"SELECT name, price, entry_low, entry_high, stop_loss, take_profit, position_advice FROM holding_strategies WHERE code=? AND status='active'", (code,)).fetchone()
if _nr2:
_xm = f"📈 {_nr2[0] or code}({code}) 价{_nr2[1]}→12维买入信号!区间{_nr2[2]}~{_nr2[3]}{_nr2[4]}{_nr2[5]} 仓位{_nr2[6] or '-'}"
from alert_helper import notify as _notify2, ACTION as _ACT2
_notify2("买入信号", _xm, _ACT2)
print(f" 📨 XMPP推送买入信号")
except: pass
except: pass
# 信号以已存分析为唯一事实源(防信号/分析脱节)
# 推荐推送统一走 reconcile→tag→摘要队列(batch 结束统一发,不再单只推送)
from mofin_db import reconcile_signal_from_analysis
_rc_conn = __import__('sqlite3').connect('/home/hmo/MoFin/data/mofin.db')
_sig = reconcile_signal_from_analysis(_rc_conn, code)
_rc_conn.close()
if _sig:
print(f" ✅ LLM信号={_sig} 已对齐")
# 冷却期已更新(reassessed_at写入)
except Exception as _dbe:
print(f" [DB FAIL] holding_strategies 写入失败: {_dbe}", file=sys.stderr)