复盘汇总加相对主榜、相对同市值桶两列超额,最大单日样本占比与样本纪律三档标注,候选单逐日表
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
1d6ee160c8
commit
3681c21a32
|
|
@ -150,14 +150,24 @@ def _md(df: pd.DataFrame) -> str:
|
|||
return "\n".join(lines)
|
||||
|
||||
|
||||
def summarize(ret: pd.Series, codes: list[str], base_all: float, base_main: float) -> dict | None:
|
||||
def summarize(ret: pd.Series, codes: list[str], base_all: float, base_main: float,
|
||||
caps: dict | None = None, cap_base: dict | None = None) -> dict | None:
|
||||
"""一份名单在一个计划日、一个期限上的读数。
|
||||
excess_all 相对全池等权;excess_main 相对主榜等权(同为有券商覆盖的篮子,剥掉大票对小票的贝塔);
|
||||
excess_cap 相对同市值桶均值(每票减当日同桶全池均值再平均,方案 1.8c 的市值中性口径)。"""
|
||||
r = ret.reindex([c for c in codes if c in ret.index]).dropna()
|
||||
if r.empty:
|
||||
return None
|
||||
return {"n": int(len(r)), "ret": round(float(r.mean()), 2),
|
||||
out = {"n": int(len(r)), "ret": round(float(r.mean()), 2),
|
||||
"excess_all": round(float(r.mean() - base_all), 2),
|
||||
"excess_main": round(float(r.mean() - base_main), 2) if base_main is not None else None,
|
||||
"excess_cap": None,
|
||||
"beat": round(float((r > base_all).mean() * 100), 1)}
|
||||
if caps and cap_base:
|
||||
adj = [float(v) - cap_base[caps[c]] for c, v in r.items() if caps.get(c) in cap_base]
|
||||
if adj:
|
||||
out["excess_cap"] = round(sum(adj) / len(adj), 2)
|
||||
return out
|
||||
|
||||
|
||||
# ============================================================================
|
||||
|
|
@ -208,6 +218,12 @@ def run(since: str, until: str | None, horizons: tuple, start: str, out_dir: str
|
|||
base_all = float(ret.dropna().mean())
|
||||
main_ret = ret.reindex([c for c in main_codes if c in ret.index]).dropna()
|
||||
base_main = float(main_ret.mean()) if not main_ret.empty else None
|
||||
cap_base: dict = {} # 当日三个市值桶各自的全池均值,供 excess_cap 用
|
||||
if caps:
|
||||
for cap in ("小盘", "中盘", "大盘"):
|
||||
v = ret.reindex([c for c, b in caps.items() if b == cap and c in ret.index]).dropna()
|
||||
if not v.empty:
|
||||
cap_base[cap] = float(v.mean())
|
||||
regime_post = "涨周" if base_all > 0 else "跌周"
|
||||
regime_pre = (("弱势日" if reg.get("weak_day") else "非弱势日")
|
||||
if reg and reg.get("weak_day") is not None else "无标签")
|
||||
|
|
@ -217,20 +233,20 @@ def run(since: str, until: str | None, horizons: tuple, start: str, out_dir: str
|
|||
"全池等权": list(ret.dropna().index),
|
||||
}
|
||||
for name, codes in lists.items():
|
||||
s = summarize(ret, codes, base_all, base_main)
|
||||
s = summarize(ret, codes, base_all, base_main, caps, cap_base)
|
||||
if s:
|
||||
rows.append({"date": day, "h": h, "list": name, "group": "全部",
|
||||
"regime_post": regime_post, "regime_pre": regime_pre, **s})
|
||||
# 分组:档位、判决、吸筹三态、市值
|
||||
for tier in ("强传导", "弱传导", "无传导"):
|
||||
codes = [c for c, t in tier_of.items() if t == tier]
|
||||
s = summarize(ret, codes, base_all, base_main)
|
||||
s = summarize(ret, codes, base_all, base_main, caps, cap_base)
|
||||
if s:
|
||||
rows.append({"date": day, "h": h, "list": "主榜", "group": f"档位={tier}",
|
||||
"regime_post": regime_post, "regime_pre": regime_pre, **s})
|
||||
for v in ("候选", "关注", "仅展示"):
|
||||
codes = [c for c, vv in verdict_of.items() if vv == v]
|
||||
s = summarize(ret, codes, base_all, base_main)
|
||||
s = summarize(ret, codes, base_all, base_main, caps, cap_base)
|
||||
if s:
|
||||
rows.append({"date": day, "h": h, "list": "档位表", "group": f"判决={v}",
|
||||
"regime_post": regime_post, "regime_pre": regime_pre, **s})
|
||||
|
|
@ -238,14 +254,14 @@ def run(since: str, until: str | None, horizons: tuple, start: str, out_dir: str
|
|||
("潜在吸筹", lambda s: str(s).startswith("潜在")),
|
||||
("其他", lambda s: not (str(s).startswith("明确") or str(s).startswith("潜在")))):
|
||||
codes = [c for c, s in acc.items() if s and pred(s)]
|
||||
s = summarize(ret, codes, base_all, base_main)
|
||||
s = summarize(ret, codes, base_all, base_main, caps, cap_base)
|
||||
if s:
|
||||
rows.append({"date": day, "h": h, "list": "全池", "group": f"吸筹={st_label}",
|
||||
"regime_post": regime_post, "regime_pre": regime_pre, **s})
|
||||
if caps:
|
||||
for cap in ("小盘", "中盘", "大盘"):
|
||||
codes = [c for c in cands if caps.get(c) == cap]
|
||||
s = summarize(ret, codes, base_all, base_main)
|
||||
s = summarize(ret, codes, base_all, base_main, caps, cap_base)
|
||||
if s:
|
||||
rows.append({"date": day, "h": h, "list": "候选单", "group": f"市值={cap}",
|
||||
"regime_post": regime_post, "regime_pre": regime_pre, **s})
|
||||
|
|
@ -266,10 +282,17 @@ def run(since: str, until: str | None, horizons: tuple, start: str, out_dir: str
|
|||
# 带 _w 的三列按样本加权(大名单的日子权重大),与方案 1.8b/1.8c 手工读数同口径,只作对账。
|
||||
n = g["n"].astype(float)
|
||||
w = n / n.sum() if n.sum() else n
|
||||
return pd.Series({"days": g["date"].nunique(), "n": int(n.sum()),
|
||||
days, total = g["date"].nunique(), int(n.sum())
|
||||
# 样本纪律(方案 2.4)机器标注:可读=计划日≥20 且样本≥100;方向=样本≥100;其余不作数
|
||||
grade = "可读" if (days >= 20 and total >= 100) else ("方向" if total >= 100 else "不作数")
|
||||
return pd.Series({"days": days, "n": total,
|
||||
"ret": round(g["ret"].mean(), 2),
|
||||
"excess_all": round(g["excess_all"].mean(), 2),
|
||||
"excess_main": round(g["excess_main"].mean(), 2) if g["excess_main"].notna().any() else None,
|
||||
"excess_cap": round(g["excess_cap"].mean(), 2) if g["excess_cap"].notna().any() else None,
|
||||
"beat": round(g["beat"].mean(), 1),
|
||||
"share": round(float(n.max() / n.sum() * 100), 0) if n.sum() else None,
|
||||
"grade": grade,
|
||||
"ret_w": round(float((g["ret"] * w).sum()), 2),
|
||||
"excess_w": round(float((g["excess_all"] * w).sum()), 2),
|
||||
"beat_w": round(float((g["beat"] * w).sum()), 1)})
|
||||
|
|
@ -278,14 +301,23 @@ def run(since: str, until: str | None, horizons: tuple, start: str, out_dir: str
|
|||
regime_tbl = df[df["group"] == "全部"].groupby(["regime_post", "list", "h"]).apply(agg).reset_index()
|
||||
pre_tbl = df[(df["group"] == "全部") & (df["regime_pre"] != "无标签")] \
|
||||
.groupby(["regime_pre", "list", "h"]).apply(agg).reset_index()
|
||||
h0 = int(df["h"].min())
|
||||
daily_c = df[(df["list"] == "候选单") & (df["group"] == "全部") & (df["h"] == h0)] \
|
||||
[["date", "n", "ret", "excess_all", "excess_cap", "beat", "regime_post"]].sort_values("date")
|
||||
|
||||
md = [f"# 候选单复盘 · {days_plan[0]} 至 {stamp}(起点价 {start})", "",
|
||||
f"计划日 {len(days_plan)} 期;样本纪律:分组样本少于一百只或覆盖计划日少于二十个只看方向;"
|
||||
f"口径切换的总样本少于六十个计划日只写方向。", "",
|
||||
"## 一、六份名单 × 期限(超额=相对全池等权,命中=跑赢全池比例;"
|
||||
"不带后缀的列按计划日等权,带 _w 的列按样本加权,后者只用于与方案第一节手工读数对账)", "",
|
||||
"## 一、六份名单 × 期限", "",
|
||||
"列的读法:excess_all 相对全池等权,excess_main 相对主榜等权(剥掉大票对小票的贝塔),"
|
||||
"excess_cap 相对同市值桶(成交额除以换手率三分位,方案 1.8c 口径);beat 是跑赢全池比例,"
|
||||
"全池自己的 beat 只有四成多(分布右偏),读它时与全池等权那一行相减;share 是最大单日样本占比"
|
||||
"(超过三成说明读数由一天主导,只看逐日表);grade 是样本纪律三档。"
|
||||
"不带后缀的列按计划日等权(主口径),带 _w 的列按样本加权,只用于与方案第一节手工读数对账。", "",
|
||||
_md(lists_tbl), "",
|
||||
"## 二、分组读数", "", _md(groups_tbl), "",
|
||||
f"## 二之二、候选单逐日(期限 {h0} 日;第一节按日等权的读数就是这张表的平均,看集中度)", "",
|
||||
_md(daily_c), "",
|
||||
"## 三、按事后环境分组(未来 h 日全池涨跌,只作解释,不作交易前置)", "",
|
||||
_md(regime_tbl), ""]
|
||||
if not pre_tbl.empty:
|
||||
|
|
|
|||
Loading…
Reference in New Issue