plan v2: 每主题限额(默认5, 防单板块刷屏) + 预期空间列现算兜底
(当日 upside 表在 18:40 建表时行情未到、常为空——读表改为空则现算)
This commit is contained in:
parent
83f2b1409f
commit
dc201905f6
44
plan.py
44
plan.py
|
|
@ -94,7 +94,27 @@ def _num(v) -> str:
|
||||||
return "—" if v is None or pd.isna(v) else f"{v:.2f}"
|
return "—" if v is None or pd.isna(v) else f"{v:.2f}"
|
||||||
|
|
||||||
|
|
||||||
def generate(date: str | None = None, top: int = 20, obs_top: int = 10) -> str:
|
def _pick(ranked: pd.Series, ev: dict, top: int, theme_cap: int):
|
||||||
|
"""按分数从高到低取 top 条;每个传导主题最多 theme_cap 条(0=不设限)。
|
||||||
|
|
||||||
|
为什么设限:传导目标是环节级,同环节全体成员共享同一条证据,不设限时
|
||||||
|
榜单会被三四个环节刷屏——20 个名额实际只是 4 注。限额后变成
|
||||||
|
"多条线索 × 每条线索取最冷最便宜的几只",被挤掉的仍在完整档位表里。"""
|
||||||
|
out, cnt = [], {}
|
||||||
|
for k, s in ranked.items():
|
||||||
|
e = ev.get(k)
|
||||||
|
theme = e[0] if e else "(无传导)"
|
||||||
|
if theme_cap and cnt.get(theme, 0) >= theme_cap:
|
||||||
|
continue
|
||||||
|
cnt[theme] = cnt.get(theme, 0) + 1
|
||||||
|
out.append((k, s))
|
||||||
|
if len(out) >= top:
|
||||||
|
break
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def generate(date: str | None = None, top: int = 20, obs_top: int = 10,
|
||||||
|
theme_cap: int = 5) -> str:
|
||||||
ds = date or _latest_date("t_factor_akg_score")
|
ds = date or _latest_date("t_factor_akg_score")
|
||||||
if not ds:
|
if not ds:
|
||||||
raise SystemExit("t_factor_akg_score 还没有数据——先 build akg_score。")
|
raise SystemExit("t_factor_akg_score 还没有数据——先 build akg_score。")
|
||||||
|
|
@ -103,6 +123,15 @@ def generate(date: str | None = None, top: int = 20, obs_top: int = 10) -> str:
|
||||||
if score.empty or gate.empty:
|
if score.empty or gate.empty:
|
||||||
raise SystemExit(f"{ds} 缺 akg_score / akg_gate——先 build 该日再出计划。")
|
raise SystemExit(f"{ds} 缺 akg_score / akg_gate——先 build 该日再出计划。")
|
||||||
upside = _factor("t_factor_akg_upside", ds)
|
upside = _factor("t_factor_akg_upside", ds)
|
||||||
|
if upside.empty:
|
||||||
|
# 晚间 18:40 建当日 upside 表时行情源(~19:50 发布)还没到,当日表常为空。
|
||||||
|
# 这里现算兜底:此刻库里已有晚到的当日价,as-of 口径不变(consensus<=当日)。
|
||||||
|
import factors
|
||||||
|
df_up = factors.build_upside(ds, ds)
|
||||||
|
if df_up is not None and not df_up.empty:
|
||||||
|
x = df_up.copy()
|
||||||
|
x["k"] = x["stock_code"].map(common.to_prefix)
|
||||||
|
upside = x.groupby("k")["factor_value"].max().astype(float)
|
||||||
hd = _latest_date("t_factor_akg_heat", ds)
|
hd = _latest_date("t_factor_akg_heat", ds)
|
||||||
heat = _factor("t_factor_akg_heat", hd) if hd else pd.Series(dtype=float)
|
heat = _factor("t_factor_akg_heat", hd) if hd else pd.Series(dtype=float)
|
||||||
names = _names()
|
names = _names()
|
||||||
|
|
@ -119,23 +148,26 @@ def generate(date: str | None = None, top: int = 20, obs_top: int = 10) -> str:
|
||||||
f"\"谁已经动了\"看的是上个交易日收盘;拍点方案定版前均如此)。")
|
f"\"谁已经动了\"看的是上个交易日收盘;拍点方案定版前均如此)。")
|
||||||
L.append("")
|
L.append("")
|
||||||
|
|
||||||
L.append(f"## 主榜 Top {min(top, len(main))}(有券商预期、目标价不低于现价)")
|
cap_txt = f",每主题限额 {theme_cap}" if theme_cap else ""
|
||||||
|
main_rows = _pick(main, ev, top, theme_cap)
|
||||||
|
L.append(f"## 主榜 Top {len(main_rows)}(有券商预期、目标价不低于现价{cap_txt})")
|
||||||
L.append("")
|
L.append("")
|
||||||
L.append("| # | 代码 | 名称 | 总分 | 档位 | 传导证据 | 热度 | 预期空间 |")
|
L.append("| # | 代码 | 名称 | 总分 | 档位 | 传导证据 | 热度 | 预期空间 |")
|
||||||
L.append("|---|------|------|------|------|----------|------|----------|")
|
L.append("|---|------|------|------|------|----------|------|----------|")
|
||||||
for i, (k, s) in enumerate(main.head(top).items(), 1):
|
for i, (k, s) in enumerate(main_rows, 1):
|
||||||
e = ev.get(k)
|
e = ev.get(k)
|
||||||
etxt = f"{e[0]}({e[1]} 源,已动 {e[2]:.0%})" if e else "—"
|
etxt = f"{e[0]}({e[1]} 源,已动 {e[2]:.0%})" if e else "—"
|
||||||
L.append(f"| {i} | {k} | {names.get(k, '—')} | {s:.1f} | {_tier_label(s)} "
|
L.append(f"| {i} | {k} | {names.get(k, '—')} | {s:.1f} | {_tier_label(s)} "
|
||||||
f"| {etxt} | {_num(heat.get(k))} | {_pct(upside.get(k))} |")
|
f"| {etxt} | {_num(heat.get(k))} | {_pct(upside.get(k))} |")
|
||||||
L.append("")
|
L.append("")
|
||||||
|
|
||||||
L.append(f"## 观察档 Top {min(obs_top, len(obs))}"
|
obs_rows = _pick(obs, ev, obs_top, theme_cap)
|
||||||
f"(无券商预期、但在传导链上——没有估值锚,置信度低)")
|
L.append(f"## 观察档 Top {len(obs_rows)}"
|
||||||
|
f"(无券商预期、但在传导链上——没有估值锚,置信度低{cap_txt})")
|
||||||
L.append("")
|
L.append("")
|
||||||
L.append("| # | 代码 | 名称 | 分 | 传导证据 | 热度 |")
|
L.append("| # | 代码 | 名称 | 分 | 传导证据 | 热度 |")
|
||||||
L.append("|---|------|------|----|----------|------|")
|
L.append("|---|------|------|----|----------|------|")
|
||||||
for i, (k, s) in enumerate(obs.head(obs_top).items(), 1):
|
for i, (k, s) in enumerate(obs_rows, 1):
|
||||||
e = ev.get(k)
|
e = ev.get(k)
|
||||||
etxt = f"{e[0]}({e[1]} 源,已动 {e[2]:.0%})" if e else "—"
|
etxt = f"{e[0]}({e[1]} 源,已动 {e[2]:.0%})" if e else "—"
|
||||||
L.append(f"| {i} | {k} | {names.get(k, '—')} | {s:.1f} "
|
L.append(f"| {i} | {k} | {names.get(k, '—')} | {s:.1f} "
|
||||||
|
|
|
||||||
4
run.py
4
run.py
|
|
@ -145,6 +145,8 @@ def main():
|
||||||
pl.add_argument("--date", help="默认取 score 表最新日")
|
pl.add_argument("--date", help="默认取 score 表最新日")
|
||||||
pl.add_argument("--top", type=int, default=20, help="主榜条数")
|
pl.add_argument("--top", type=int, default=20, help="主榜条数")
|
||||||
pl.add_argument("--obs-top", type=int, default=10, help="观察档条数")
|
pl.add_argument("--obs-top", type=int, default=10, help="观察档条数")
|
||||||
|
pl.add_argument("--theme-cap", type=int, default=5,
|
||||||
|
help="每个传导主题最多几条(防单板块刷屏;0=不设限)")
|
||||||
p = sub.add_parser("probe")
|
p = sub.add_parser("probe")
|
||||||
p.add_argument("--section", choices=["all", "pools", "price", "upside", "corr"],
|
p.add_argument("--section", choices=["all", "pools", "price", "upside", "corr"],
|
||||||
default="all",
|
default="all",
|
||||||
|
|
@ -178,7 +180,7 @@ def main():
|
||||||
cmd_register()
|
cmd_register()
|
||||||
elif a.cmd == "plan":
|
elif a.cmd == "plan":
|
||||||
import plan
|
import plan
|
||||||
plan.generate(a.date, a.top, a.obs_top)
|
plan.generate(a.date, a.top, a.obs_top, a.theme_cap)
|
||||||
elif a.cmd == "tracks":
|
elif a.cmd == "tracks":
|
||||||
import tracks
|
import tracks
|
||||||
tracks.coverage_report()
|
tracks.coverage_report()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue