丙路加覆盖基数门槛:机构太少时收缩比例不作数
2026-09-04 全市场实测发现的:六只判为逻辑存疑的票里有五只,触发是覆盖机构从三家 减到两家,也就是一家机构那两周没发报告就构成 33% 的收缩。机构数这么少时百分比不 说明任何事,而逻辑存疑会出减持提议,误判的代价是让人对着噪声做裁决。 加一条基数门槛,默认前窗口至少五家。它的性质是:五家时走掉一家只有 20%、够不着 30% 那条线,必须走掉两家才算——这正是"覆盖在收缩"该有的含义。基数不够时预测下修 照样记转弱,只是不带硬触发,于是整体落到无法判断进人工复核,而不是出减持提议。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
86727827ae
commit
ac7832620d
|
|
@ -243,7 +243,7 @@ def from_judgement(cur, prev=None, *, stale_days: int = 60) -> dict:
|
||||||
|
|
||||||
def from_broker(now, prev, *, as_of: str | None = None, min_firms: int = 2,
|
def from_broker(now, prev, *, as_of: str | None = None, min_firms: int = 2,
|
||||||
eps_drop: float = 0.05, eps_rise: float = 0.05,
|
eps_drop: float = 0.05, eps_rise: float = 0.05,
|
||||||
cover_drop: float = 0.30) -> dict:
|
cover_drop: float = 0.30, cover_min_base: int = 5) -> dict:
|
||||||
"""把同一财年同一预测期的每股收益预测中位数与覆盖机构数,归一成一路信号。
|
"""把同一财年同一预测期的每股收益预测中位数与覆盖机构数,归一成一路信号。
|
||||||
|
|
||||||
now 与 prev 是两个等长窗口的读数,各是 {"eps": 中位数, "firms": 机构数}。
|
now 与 prev 是两个等长窗口的读数,各是 {"eps": 中位数, "firms": 机构数}。
|
||||||
|
|
@ -252,6 +252,13 @@ def from_broker(now, prev, *, as_of: str | None = None, min_firms: int = 2,
|
||||||
|
|
||||||
每股收益从正转负或从负转正时,变化率没有意义,单独标记为转亏或扭亏而不算百分比。
|
每股收益从正转负或从负转正时,变化率没有意义,单独标记为转亏或扭亏而不算百分比。
|
||||||
转亏方向明确是坏消息,但它不满足逻辑存疑那个双条件,所以只是转弱不带 hard。
|
转亏方向明确是坏消息,但它不满足逻辑存疑那个双条件,所以只是转弱不带 hard。
|
||||||
|
|
||||||
|
覆盖收缩要同时满足比例与基数两个条件才算数。基数门槛 cover_min_base 是 2026-09-04
|
||||||
|
全市场实测加进来的:不加它,六只判为逻辑存疑的票里有五只的触发是覆盖从三家减到两家,
|
||||||
|
也就是一家机构那两周没发报告就构成 33% 的收缩。机构数这么少时百分比不说明任何事。
|
||||||
|
默认 5 家的性质是——前窗口有五家时,走掉一家只有 20%、够不着 30% 这条线,必须走掉
|
||||||
|
两家才算,这正是"覆盖在收缩"该有的含义。基数不够时预测下修照样记转弱,只是不带
|
||||||
|
hard,于是整体落到无法判断进人工复核,而不是出减持提议。
|
||||||
"""
|
"""
|
||||||
n0, n1 = int((prev or {}).get("firms") or 0), int((now or {}).get("firms") or 0)
|
n0, n1 = int((prev or {}).get("firms") or 0), int((now or {}).get("firms") or 0)
|
||||||
if n0 < int(min_firms) or n1 < int(min_firms):
|
if n0 < int(min_firms) or n1 < int(min_firms):
|
||||||
|
|
@ -271,10 +278,14 @@ def from_broker(now, prev, *, as_of: str | None = None, min_firms: int = 2,
|
||||||
else "每股收益预测由亏转盈,变化率不适用"))
|
else "每股收益预测由亏转盈,变化率不适用"))
|
||||||
chg = (e1 - e0) / abs(e0) if e0 else 0.0
|
chg = (e1 - e0) / abs(e0) if e0 else 0.0
|
||||||
if chg <= -float(eps_drop):
|
if chg <= -float(eps_drop):
|
||||||
hard = shrink > float(cover_drop)
|
shrunk = shrink > float(cover_drop)
|
||||||
|
hard = shrunk and n0 >= int(cover_min_base)
|
||||||
why = f"每股收益预测中位数下修 {abs(chg):.0%}"
|
why = f"每股收益预测中位数下修 {abs(chg):.0%}"
|
||||||
if hard:
|
if hard:
|
||||||
why += f",同时覆盖机构从 {n0} 家收缩到 {n1} 家(减 {shrink:.0%})"
|
why += f",同时覆盖机构从 {n0} 家收缩到 {n1} 家(减 {shrink:.0%})"
|
||||||
|
elif shrunk:
|
||||||
|
why += (f",覆盖机构从 {n0} 家减到 {n1} 家,但前窗口不足 {cover_min_base} 家,"
|
||||||
|
f"这个比例不作数")
|
||||||
return signal(PATH_BROKER, SIG_DOWN, as_of=as_of, coverage=n1, hard=hard,
|
return signal(PATH_BROKER, SIG_DOWN, as_of=as_of, coverage=n1, hard=hard,
|
||||||
why=why, refs=refs)
|
why=why, refs=refs)
|
||||||
if chg >= float(eps_rise):
|
if chg >= float(eps_rise):
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,15 @@ def test_broker():
|
||||||
r = ls.from_broker({"eps": 1.8, "firms": 3}, {"eps": 2.4, "firms": 9}, as_of=DS)
|
r = ls.from_broker({"eps": 1.8, "firms": 3}, {"eps": 2.4, "firms": 9}, as_of=DS)
|
||||||
t("下修且覆盖收缩,两个条件同现 -> 转弱且达到存疑门槛",
|
t("下修且覆盖收缩,两个条件同现 -> 转弱且达到存疑门槛",
|
||||||
r["signal"] == ls.SIG_DOWN and r["hard"] and "收缩" in r["why"])
|
r["signal"] == ls.SIG_DOWN and r["hard"] and "收缩" in r["why"])
|
||||||
|
r = ls.from_broker({"eps": 1.8, "firms": 2}, {"eps": 2.4, "firms": 3}, as_of=DS)
|
||||||
|
t("下修且比例上收缩,但前窗口只有三家 -> 转弱不带硬触发(一家没发报告就是 33%)",
|
||||||
|
r["signal"] == ls.SIG_DOWN and not r["hard"] and "不作数" in r["why"])
|
||||||
|
r = ls.from_broker({"eps": 1.8, "firms": 4}, {"eps": 2.4, "firms": 5}, as_of=DS)
|
||||||
|
t("前窗口够五家、走掉一家只有 20% -> 够不着收缩线",
|
||||||
|
r["signal"] == ls.SIG_DOWN and not r["hard"])
|
||||||
|
r = ls.from_broker({"eps": 1.8, "firms": 3}, {"eps": 2.4, "firms": 5},
|
||||||
|
as_of=DS, cover_min_base=3)
|
||||||
|
t("基数门槛旋钮生效", r["signal"] == ls.SIG_DOWN and r["hard"])
|
||||||
r = ls.from_broker({"eps": 2.4, "firms": 3}, {"eps": 2.4, "firms": 9}, as_of=DS)
|
r = ls.from_broker({"eps": 2.4, "firms": 3}, {"eps": 2.4, "firms": 9}, as_of=DS)
|
||||||
t("只覆盖收缩而预测没动 -> 平稳(覆盖只作否决位,不单独定负)",
|
t("只覆盖收缩而预测没动 -> 平稳(覆盖只作否决位,不单独定负)",
|
||||||
r["signal"] == ls.SIG_FLAT)
|
r["signal"] == ls.SIG_FLAT)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue