feat: 信号溯源系统——signal_ledger记录每次推送(策略/版本/温区/原因), 多策略同推检测共振标记(🔥加关注); stale_push_wlin推送行带[策略][温区]标记+共振标记
This commit is contained in:
@@ -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"))
|
||||
@@ -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})"
|
||||
|
||||
Reference in New Issue
Block a user