From e27a0747108633864546c475d137db4e89a92dc4 Mon Sep 17 00:00:00 2001 From: hmo Date: Thu, 13 Aug 2026 18:19:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=A1=E5=8F=B7=E6=BA=AF=E6=BA=90?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E2=80=94=E2=80=94signal=5Fledger=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E6=AF=8F=E6=AC=A1=E6=8E=A8=E9=80=81(=E7=AD=96?= =?UTF-8?q?=E7=95=A5/=E7=89=88=E6=9C=AC/=E6=B8=A9=E5=8C=BA/=E5=8E=9F?= =?UTF-8?q?=E5=9B=A0),=20=E5=A4=9A=E7=AD=96=E7=95=A5=E5=90=8C=E6=8E=A8?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=E5=85=B1=E6=8C=AF=E6=A0=87=E8=AE=B0(?= =?UTF-8?q?=F0=9F=94=A5=E5=8A=A0=E5=85=B3=E6=B3=A8);=20stale=5Fpush=5Fwlin?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E8=A1=8C=E5=B8=A6[=E7=AD=96=E7=95=A5][?= =?UTF-8?q?=E6=B8=A9=E5=8C=BA]=E6=A0=87=E8=AE=B0+=E5=85=B1=E6=8C=AF?= =?UTF-8?q?=E6=A0=87=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/profile-scripts/signal_ledger.py | 143 ++++++++++++++++++++++ deploy/profile-scripts/stale_push_wlin.py | 56 ++++++++- 2 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 deploy/profile-scripts/signal_ledger.py diff --git a/deploy/profile-scripts/signal_ledger.py b/deploy/profile-scripts/signal_ledger.py new file mode 100644 index 00000000..c62b8063 --- /dev/null +++ b/deploy/profile-scripts/signal_ledger.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""signal_ledger.py — 信号溯源系统(2026-08-13 老莫要求) + +核心:记录每次股票被推荐的原因(策略/版本/时间/温区),多策略同推标记共振加关注。 + +表 signal_ledger: + id, code, name, strategy, version, regime, temp_band, pushed_at, reason, + resonance_count, resonance_strategies, source_module, updated_at + +共振检测:同一股票 24h 内被多个激活策略推 → resonance_count>1,标记"多策略共振"(加关注) + +用法: + from signal_ledger import record_signal, get_resonance + record_signal(code="300750", name="宁德时代", strategy="v_oversold", version="v_oversold", + regime="trend_down", temp_band="panic", reason="进买入区+重评买入", source_module="stale_push_wlin") + res = get_resonance("300750") # 返回该股票近24h共振信息 +""" +import json +import sqlite3 +from pathlib import Path +from datetime import datetime, timedelta + +DB = "/home/hmo/MoFin/data/mofin.db" +RESONANCE_WINDOW_H = 24 # 共振窗口(小时) + + +def _conn(): + conn = sqlite3.connect(DB, timeout=30) + conn.execute("PRAGMA busy_timeout=30000") + return conn + + +def init_table(): + conn = _conn() + conn.execute(""" + CREATE TABLE IF NOT EXISTS signal_ledger ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + code TEXT NOT NULL, + name TEXT, + strategy TEXT, + version TEXT, + regime TEXT, + temp_band TEXT, + pushed_at TIMESTAMP, + reason TEXT, + resonance_count INTEGER DEFAULT 1, + resonance_strategies TEXT, + source_module TEXT, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """) + conn.execute("CREATE INDEX IF NOT EXISTS idx_signal_ledger_code ON signal_ledger(code)") + conn.execute("CREATE INDEX IF NOT EXISTS idx_signal_ledger_pushed ON signal_ledger(pushed_at)") + conn.commit() + conn.close() + + +def record_signal(code, name="", strategy="", version="", regime="", temp_band="", + reason="", source_module=""): + """记录一次信号推送,并检测多策略共振""" + init_table() + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + conn = _conn() + try: + # 共振检测:近 24h 内同一股票被其他策略推过 + cutoff = (datetime.now() - timedelta(hours=RESONANCE_WINDOW_H)).strftime("%Y-%m-%d %H:%M:%S") + rows = conn.execute( + """SELECT DISTINCT strategy FROM signal_ledger + WHERE code=? AND pushed_at >= ? AND strategy != ? AND strategy != ''""", + (code, cutoff, strategy) + ).fetchall() + other_strats = [r[0] for r in rows if r[0]] + resonance_count = len(other_strats) + (1 if strategy else 0) + resonance_strategies = ",".join(sorted(set([strategy] + other_strats))) if strategy else "" + + conn.execute( + """INSERT INTO signal_ledger + (code, name, strategy, version, regime, temp_band, pushed_at, reason, + resonance_count, resonance_strategies, source_module) + VALUES (?,?,?,?,?,?,?,?,?,?,?)""", + (code, name, strategy, version, regime, temp_band, now, reason, + resonance_count, resonance_strategies, source_module) + ) + conn.commit() + return {"resonance_count": resonance_count, "resonance_strategies": resonance_strategies} + finally: + conn.close() + + +def get_resonance(code, hours=RESONANCE_WINDOW_H): + """查某股票近 N 小时的共振信息""" + init_table() + cutoff = (datetime.now() - timedelta(hours=hours)).strftime("%Y-%m-%d %H:%M:%S") + conn = _conn() + try: + rows = conn.execute( + """SELECT strategy, version, regime, pushed_at, reason FROM signal_ledger + WHERE code=? AND pushed_at >= ? ORDER BY pushed_at DESC""", + (code, cutoff) + ).fetchall() + if not rows: + return None + strats = sorted({r[0] for r in rows if r[0]}) + return { + "code": code, + "count": len(strats), + "strategies": strats, + "latest": rows[0][3], + "signals": [{"strategy": r[0], "version": r[1], "regime": r[2], "at": r[3], "reason": r[4]} for r in rows], + } + finally: + conn.close() + + +def get_recent_signals(limit=50): + """查最近推送的信号(供评估)""" + init_table() + conn = _conn() + try: + rows = conn.execute( + """SELECT code, name, strategy, regime, temp_band, pushed_at, reason, + resonance_count, resonance_strategies, source_module + FROM signal_ledger ORDER BY pushed_at DESC LIMIT ?""", + (limit,) + ).fetchall() + return [ + {"code": r[0], "name": r[1], "strategy": r[2], "regime": r[3], "temp": r[4], + "at": r[5], "reason": r[6], "resonance": r[7], "res_strats": r[8], "source": r[9]} + for r in rows + ] + finally: + conn.close() + + +if __name__ == "__main__": + init_table() + # 自测 + r1 = record_signal("300750", "宁德时代", "v_oversold", "v_oversold", "trend_down", "panic", "进买入区", "test") + print("单策略:", r1) + r2 = record_signal("300750", "宁德时代", "v_mr_sel", "v_mr_sel", "trend_down", "panic", "超跌信号", "test") + print("多策略共振:", r2) + print("共振查询:", get_resonance("300750")) diff --git a/deploy/profile-scripts/stale_push_wlin.py b/deploy/profile-scripts/stale_push_wlin.py index 86ac58f7..2d0afbaf 100644 --- a/deploy/profile-scripts/stale_push_wlin.py +++ b/deploy/profile-scripts/stale_push_wlin.py @@ -366,6 +366,28 @@ def main(): except Exception as _e: print(f"[DB_LOAD FAIL] {_e}", file=sys.stderr) + # 2026-08-13 温区自适应:当前温区激活策略集合(regime_weights.active) + _active_strats = None + try: + from regime_gate import _load_weights + _w = _load_weights() + if _w and _w.get("weights"): + _active_strats = {k for k, v in _w["weights"].items() if v.get("matched")} + except Exception: + pass # 无温区数据 → 不过滤(兼容旧逻辑) + + def _is_active(code): + """该自选股的策略是否在当前温区激活""" + if _active_strats is None: + return True + e = code_data.get(code, {}) + for key in ("tag", "version", "strategy_type", "decision_type"): + v = e.get(key) + if v and str(v) in _active_strats: + return True + # 不在激活集(或无标识)→ 视为非激活(保守不推) + return False + cash = load_cash() stocks = [] stale_list = [] @@ -912,10 +934,40 @@ def main(): ) action_tag = "🛒" if (lots > 0 or swap_text) else "⚠️" - + + # 2026-08-13 信号溯源:记录策略/版本/温区 + 共振标记(多策略同推加关注) + strat_id = d.get("tag") or d.get("version") or d.get("strategy_type") or d.get("decision_type") or "" + regime_now = "" + temp_now = "" + try: + from regime_gate import get_current_regime + _rg = get_current_regime() + regime_now = _rg.get("regime", "") + from temp_band import get_market_temp + _tp = get_market_temp() + temp_now = _tp.get("band", "") + except Exception: + pass + # 溯源记录 + 共振检测 + resonance_tag = "" + try: + from signal_ledger import record_signal + _res = record_signal( + code=code, name=name, strategy=strat_id, version=strat_id, + regime=regime_now, temp_band=temp_now, + reason=f"进买入区{buy_low}~{buy_high} + 重评{sig}", + source_module="stale_push_wlin", + ) + if _res and _res.get("resonance_count", 1) > 1: + resonance_tag = f" 🔥多策略共振({_res['resonance_count']}策略: {_res['resonance_strategies']})" + except Exception: + pass + strat_tag = f" [{strat_id}]" if strat_id else "" + regime_tag = f"[{regime_now}]" if regime_now else "" + lines.append( f" {action_tag} {name}({code}) {pfx}{price:.2f} 买区{buy_low}~{buy_high} | " - f"1手{lot:,.0f}元 RR={rr:.1f} 损{sl} 盈{tp}\n" + f"1手{lot:,.0f}元 RR={rr:.1f} 损{sl} 盈{tp}{strat_tag}{regime_tag}{resonance_tag}\n" f" {analysis}\n" f" 技术{ss['强撑']}→{ss['弱撑']}→{ss['弱压']}→{ss['强压']} | 信号{sig}\n" f" 仓位:理论{theo_pct}%×总资产 | 建议{actual_pct}%({details})"