处理自动刷新时间

This commit is contained in:
zlt 2026-08-13 15:37:21 +08:00
parent f53e0e3846
commit 2315ac3bd3
2 changed files with 24 additions and 6 deletions

View File

@ -36,6 +36,16 @@ def _limit() -> int:
return max(1, param_store.get_int("PMS_UPSTREAM_LIMIT", 40)) return max(1, param_store.get_int("PMS_UPSTREAM_LIMIT", 40))
def _alert_scan() -> int:
# 告警流扫更大窗口, 防高频源(如 price_notice)把风控告警挤出"最新N"。
return max(_limit(), param_store.get_int("PMS_UPSTREAM_ALERT_SCAN", 300))
def _alert_per_cat() -> int:
# 每个类型最多留最新 N 条, 高频源只占它自己那一档, 不挤别的类。
return max(1, param_store.get_int("PMS_UPSTREAM_ALERT_PER_CAT", 20))
def _c208(db: int): def _c208(db: int):
"""208(与 SIGNAL_REDIS 同实例)的只读客户端, 按库缓存。""" """208(与 SIGNAL_REDIS 同实例)的只读客户端, 按库缓存。"""
key = ("208", db) key = ("208", db)
@ -117,6 +127,9 @@ _ALERT_META = {
"money_flow_in_intensity": ("看涨", "money_flow", "资金流强度"), "money_flow_in_intensity": ("看涨", "money_flow", "资金流强度"),
"capital_accumulation": ("看涨", "capital", "资金分布"), "capital_accumulation": ("看涨", "capital", "资金分布"),
"volume_breakout": ("看涨", "volume", "放量异动"), "volume_breakout": ("看涨", "volume", "放量异动"),
# 股价异动通知: 是"价格大幅变动"事件, 不是情绪信号 → 方向用 涨/跌(区别于看涨/看跌), 单独一类。
"price_notice_up": ("", "price_notice", "股价异动"),
"price_notice_down": ("", "price_notice", "股价异动"),
"multi_source": (None, "multi", "多源升级"), "multi_source": (None, "multi", "多源升级"),
"intraday_buy_emitted": ("", "buy_emitted", "买入镜像"), "intraday_buy_emitted": ("", "buy_emitted", "买入镜像"),
} }
@ -124,8 +137,10 @@ _ALERT_META = {
def _read_alerts(c, ymd): def _read_alerts(c, ymd):
key = "intraday_alerts:%s" % ymd key = "intraday_alerts:%s" % ymd
per_cat = _alert_per_cat()
counts = {} # cat -> 已收数量; 每类只留最新 per_cat 条(仍按 newest-first)
out = [] out = []
for _id, f in c.xrevrange(key, count=_limit()): for _id, f in c.xrevrange(key, count=_alert_scan()):
src = str(f.get("source") or "") src = str(f.get("source") or "")
meta = f.get("metadata") meta = f.get("metadata")
if not isinstance(meta, dict): if not isinstance(meta, dict):
@ -134,12 +149,15 @@ def _read_alerts(c, ymd):
except Exception: except Exception:
meta = {} meta = {}
dir_fixed, cat, cat_label = _ALERT_META.get(src, (None, "other", "其他")) dir_fixed, cat, cat_label = _ALERT_META.get(src, (None, "other", "其他"))
if counts.get(cat, 0) >= per_cat: # 该类已满 → 跳过, 高频源不挤掉别的类
continue
if dir_fixed is None: if dir_fixed is None:
# multi_source 或未登记源: 方向以 metadata.direction 为准, 拿不到就 — # multi_source 或未登记源: 方向以 metadata.direction 为准, 拿不到就 —
mdir = str((meta or {}).get("direction") or "").lower() mdir = str((meta or {}).get("direction") or "").lower()
d = "看跌" if mdir in ("down", "short", "bear") else ("看涨" if mdir in ("up", "long", "bull") else "") d = "看跌" if mdir in ("down", "short", "bear") else ("看涨" if mdir in ("up", "long", "bull") else "")
else: else:
d = dir_fixed d = dir_fixed
counts[cat] = counts.get(cat, 0) + 1
out.append({"ts_code": f.get("ts_code"), "source": src, "direction": d, out.append({"ts_code": f.get("ts_code"), "source": src, "direction": d,
"cat": cat, "cat_label": cat_label, "cat": cat, "cat_label": cat_label,
"level": f.get("level"), "value": _num(f.get("value")), "time": _hm(f.get("trigger_time"))}) "level": f.get("level"), "value": _num(f.get("value")), "time": _hm(f.get("trigger_time"))})

View File

@ -193,7 +193,7 @@ pre.json{background:var(--surface-2);border:1px solid var(--hair);border-radius:
<span class="ab-badge">{{ alertStripCount }} 条<span v-if="alertTotalCount>alertStripCount" class="muted" style="font-weight:400"> / 全部 {{ alertTotalCount }}</span></span> <span class="ab-badge">{{ alertStripCount }} 条<span v-if="alertTotalCount>alertStripCount" class="muted" style="font-weight:400"> / 全部 {{ alertTotalCount }}</span></span>
<span class="ab-latest" v-if="alertLatest.ts_code">最新 · <span class="ab-latest" v-if="alertLatest.ts_code">最新 ·
<b>{{ nm(alertLatest.ts_code) }}</b> <span class="muted mono">{{ alertLatest.ts_code }}</span> <b>{{ nm(alertLatest.ts_code) }}</b> <span class="muted mono">{{ alertLatest.ts_code }}</span>
<span v-if="alertLatest.direction && alertLatest.direction!=='—'" :class="alertLatest.direction==='看涨'?'clr-up':'clr-down'">{{ alertLatest.direction }}</span> <span v-if="alertLatest.direction && alertLatest.direction!=='—'" :class="['看涨','涨'].includes(alertLatest.direction)?'clr-up':'clr-down'">{{ alertLatest.direction }}</span>
<span class="muted">{{ alertLatest.cat_label }}</span> <span class="muted">{{ alertLatest.cat_label }}</span>
<span class="disp" :class="alertLatest.level==='DANGER'?'deny':(alertLatest.level==='WARNING'?'queue':'wait')" style="padding:1px 7px">{{ alertLatest.level }}</span> <span class="disp" :class="alertLatest.level==='DANGER'?'deny':(alertLatest.level==='WARNING'?'queue':'wait')" style="padding:1px 7px">{{ alertLatest.level }}</span>
<span class="muted">{{ alertLatest.time }}</span> <span class="muted">{{ alertLatest.time }}</span>
@ -217,7 +217,7 @@ pre.json{background:var(--surface-2);border:1px solid var(--hair);border-radius:
</div> </div>
<el-table :data="g.items.slice(0,20)" size="small" max-height="240" empty-text="暂无"> <el-table :data="g.items.slice(0,20)" size="small" max-height="240" empty-text="暂无">
<el-table-column label="股票" min-width="140"><template #default="s"><b>{{ nm(s.row.ts_code) }}</b> <span class="muted mono">{{ s.row.ts_code }}</span></template></el-table-column> <el-table-column label="股票" min-width="140"><template #default="s"><b>{{ nm(s.row.ts_code) }}</b> <span class="muted mono">{{ s.row.ts_code }}</span></template></el-table-column>
<el-table-column label="方向" width="58"><template #default="s"><span v-if="s.row.direction && s.row.direction!=='—'" :class="s.row.direction==='看涨'?'clr-up':'clr-down'">{{ s.row.direction }}</span><span v-else class="muted"></span></template></el-table-column> <el-table-column label="方向" width="58"><template #default="s"><span v-if="s.row.direction && s.row.direction!=='—'" :class="['看涨','涨'].includes(s.row.direction)?'clr-up':'clr-down'">{{ s.row.direction }}</span><span v-else class="muted"></span></template></el-table-column>
<el-table-column label="级别" width="86"><template #default="s"><span class="disp" :class="s.row.level==='DANGER'?'deny':(s.row.level==='WARNING'?'queue':'wait')">{{ s.row.level }}</span></template></el-table-column> <el-table-column label="级别" width="86"><template #default="s"><span class="disp" :class="s.row.level==='DANGER'?'deny':(s.row.level==='WARNING'?'queue':'wait')">{{ s.row.level }}</span></template></el-table-column>
<el-table-column label="来源" min-width="150"><template #default="s"><span class="muted" style="font-size:11px">{{ s.row.source }}</span></template></el-table-column> <el-table-column label="来源" min-width="150"><template #default="s"><span class="muted" style="font-size:11px">{{ s.row.source }}</span></template></el-table-column>
<el-table-column label="时间" width="56" align="right"><template #default="s"><span class="mono muted">{{ s.row.time }}</span></template></el-table-column> <el-table-column label="时间" width="56" align="right"><template #default="s"><span class="mono muted">{{ s.row.time }}</span></template></el-table-column>
@ -1395,7 +1395,7 @@ createApp({
const upstream = ref({ sources: {} }); const upstream = ref({ sources: {} });
const showUpstream = ref(true); const showUpstream = ref(true);
const showAlerts = ref(false); const showAlerts = ref(false);
const alertShowMap = reactive({ money_flow:true, capital:true, qrs:true, volume:true, multi:true, buy_emitted:false, other:true }); const alertShowMap = reactive({ money_flow:true, capital:true, qrs:true, volume:true, multi:true, price_notice:false, buy_emitted:false, other:true });
const openScan = ref({ by_code: {}, notes: [] }); const openScan = ref({ by_code: {}, notes: [] });
const insTab = ref('live'); const insTab = ref('live');
const stratDlg = reactive({ visible:false, busy:false, reasons:[], ts_code:'', price:0, const stratDlg = reactive({ visible:false, busy:false, reasons:[], ts_code:'', price:0,
@ -1823,8 +1823,8 @@ createApp({
} }
const usrc = k => (upstream.value.sources || {})[k]; const usrc = k => (upstream.value.sources || {})[k];
const alertItems = computed(() => { const a = usrc('alerts'); return (a && Array.isArray(a.items)) ? a.items : []; }); const alertItems = computed(() => { const a = usrc('alerts'); return (a && Array.isArray(a.items)) ? a.items : []; });
const ALERT_CAT_ORDER = ['money_flow','capital','qrs','volume','multi','buy_emitted','other']; const ALERT_CAT_ORDER = ['money_flow','capital','qrs','volume','multi','price_notice','buy_emitted','other'];
const ALERT_CAT_DIRECTIONAL = { money_flow:true, capital:true, qrs:true, volume:true, multi:true, buy_emitted:false, other:false }; const ALERT_CAT_DIRECTIONAL = { money_flow:true, capital:true, qrs:true, volume:true, multi:true, price_notice:true, buy_emitted:false, other:false };
const catShown = k => alertShowMap[k] !== false; // 未登记类默认进折叠条 const catShown = k => alertShowMap[k] !== false; // 未登记类默认进折叠条
function toggleCat(k){ alertShowMap[k] = !catShown(k); } function toggleCat(k){ alertShowMap[k] = !catShown(k); }
const alertGroups = computed(() => { const alertGroups = computed(() => {