主观多头周度业绩榜 · 产品分类与四象限定位分析报告
技能实体位置(重要)
本技能是一个自包含技能包,位于:
C:\Users\HT\Desktop\subjective-long-weekly-report\
> 这是唯一正式入口(2026-09-17 起)。C:\Users\HT\Desktop\券商SKILLs\subjective-long-weekly-report\
> 下已留一份同内容的归档快照,仅供查阅;自动化任务与手工调用一律走本目录。
包里自带 lib/(路径层 + Wind 客户端 + 回归内核 + 分类锚点)、scripts/(9 步流水线 + 自检)、
assets/(内嵌 ECharts 5.5.0)、data/(历史期次数据(当前 35 期)+ 指数缓存,是资产,不要删)。
lib/cluster_anchors.json 是分类体系的定义文件(四个类型的名字、档位、配色、簇质心)。
它决定「低波领跑型 / 高波进取型 / 低波蛰伏型 / 高波落后型」这四类的身份,
改动它等于换一套分类口径,须按踩坑清单第 17 条当版本变更来做,不可随手编辑。
执行时不要重新实现任何东西 —— 直接调用包里的脚本。数据目录默认就在包内,
脚本通过 __file__ 定位自身,整个包可以任意挪位置、拷到新电脑照样跑。
一、每周执行流程(增量,务必按顺序)
步骤 1 · 拉取期清单(agent 用 MCP 完成)
从 IMA 知识库列出《主观多头管理人业绩榜》的全部期:
mcp__ima-mcp__get_knowledge_list(
knowledge_base_id="7337674910215195",
folder_id="folder_7354341090423503",
limit=50,
sort_type="TITLE_DESC_SORT_TYPE")
若提示「result exceeds maximum allowed tokens, Output has been saved to <路径>」,
先把结果落盘路径记下来,交给步骤 1b 解析(不要去读整个大文件再手工抄)。
补充检索(清单不全时):
mcp__ima-mcp__search_knowledge(
knowledge_base_id="7337674910215195",
folder_id="folder_7354341090423503",
query="点睛业绩放送_主观多头管理人业绩榜")
步骤 1b · 解析成标准期清单
python scripts/00_scan_ima_dump.py "<dump文件或目录>" --filter 主观多头
输出 data/ima_catalog.json:{ "0907-0911": "excel_xxx...", ... }。
(期标签就是标题里 (MMDD-MMDD) 那段;若目录里也有其它榜单,--filter 用来筛策略名。)
步骤 2 · 算差集,决定抓什么
python scripts/00_plan_incremental.py
它会把清单与本地 data/weekly_raw/ 比对,输出 data/pending.json:
| 类别 | 含义 | 动作 |
|---|---|---|
fetch |
本地没有的期 | 抓 |
updated |
同一期 media_id 变了(官方修订) | 重抓 |
orphan |
本地有、清单里没有 | 保留不动 |
若输出「无新增 / 无修订」——立即停止,不要抓任何数据、不要重跑报告。
这就是增量的意义:绝大多数周次到这一步就结束了。
步骤 3 · 抓取新增期(只抓 pending 里列出的)
对 pending.json 的每一项,逐期调用:
mcp__ima-mcp__fetch_media_content(media_id="<pending 里给的 id>")
把返回 JSON 里 content 字段的原始字符串一字不改(保留全部 | 和换行)
写入 data/weekly_raw/<期标签>.txt(UTF-8)。
- 每次只传一个
media_id。 - 若某次结果被落盘,去
Read该文件取出content(注意还原\n转义)再写入。 - 不要碰
pending之外的任何期。
步骤 4 · 一键跑完 + 自检
python scripts/run_all.py
依次执行:校验原始数据 → 解析 → 增量取指数 → 增量取基准 → 面板归因 → 分位/锚定分类
→ 超额 → 分类象限 → 生成报告 → Node 自检。
run_all.py 会自己找一个带 numpy/pandas/statsmodels 的 Python(先看环境变量 SLWR_PY,
再看常见安装路径),所以用哪个解释器启动它都行;它内部再用同一个解释器跑 9 个子脚本。
日志开头若出现 [env] 使用解释器:... 就是自动切换成功了。
本机可用解释器:C:\Users\HT\AppData\Local\Programs\Python\Python39\python.exe(依赖齐全)。
01_verify_raw.py会把「缺免责声明 / 表格行数 < 25 / 空文件」的原始文件移入
data/quarantine/并中断流程 —— 这是刻意的,脏数据不能进报告。此时回到步骤 3 重抓。- 缺 Node 环境时自检自动跳过,不影响报告产出。
步骤 5 · 交付
报告固定输出到桌面(同名覆盖,不带日期):
C:\Users\HT\Desktop\主观多头周度业绩榜_产品分类与四象限定位分析报告.html
用 present_files 打开预览,并在回复里给出当期核心结论(见下方「结论怎么写」)。
需要留档带日期的快照时,另跑一次并加环境变量:
SLWR_OUT_FILE=主观多头周度业绩榜_四象限报告_<期标签>.html python scripts/09_gen_report.py
二、结论怎么写(沿用已确认的偏好)
结论先行、要具体、不要泛泛而谈。 从 data/report2.json 取数,至少覆盖:
- 当期样本量、今年收益中位与极值、收益差是否由科技暴露驱动(看
corr_ytd_tech)。 - 四象限分布(双优 / 进取 / 蛰伏 / 承压各几只),以及空象限要明说——
「蛰伏区为空」这种情况必须解释成因(如"回撤浅于沪深300 的产品全部同时跑赢"),
并说明这是真实特征而非画法问题。 - 分类结果:4 个类型的只数、收益中位、回撤中位、科技暴露,以及类型与象限是两套正交视角
(类型 = 群体体征,象限 = 当期位置),不要把两者混为一谈。 - 准入建议分层:第一顺位(双优区)→ 第二顺位(进取区中信息比率靠前)→ 审慎(承压区)。
- 数据问题如实说:缺期、榜单 YTD 重述跳变、风格因子共线性。
口径红线:
- 颜色 红涨绿跌(正收益 #C0392B、负收益 #1E8449);
风险类指标(波动、回撤、连亏)用中性蓝系,不套红绿。
- 累计收益一律用榜单披露值,不用自建复利(跨缺期会系统性偏低)。
- 基准累计用日频区间口径,不用按周复利合成(缺期段会漏掉涨跌)。
- 报告里的每个数字都要能溯源到 data/ 下的中间产物。
三、口径速查
| 项目 | 口径 |
|---|---|
| 分类 | 锚定型:四个类型的身份(名字/档位/配色/质心)由 lib/cluster_anchors.json 固定,每期只做「把产品分配到固定簇」。特征 = 收益·回撤·夏普·周胜率·科技暴露(z-score)。无随机性、与样本数/顺序无关 |
| 锚点来源 | 0707-0911 期那次获得用户认可的四类划分(低波领跑型 / 高波进取型 / 低波蛰伏型 / 高波落后型) |
| 手肘曲线 | kmeans_stable(k-means++ 60 起点)仅作参考,回答「若自由聚类会分成什么样」,不参与出报告的归类 |
| 训练样本 | 观测满 28 周的产品参与质心估计;短样本按最近质心归类并以 △ 标记(每只都有类型) |
| 象限原点 | 沪深300 今年收益 / 最大回撤(不是 0、不是全池中位) |
| 中位参考线 | 紫色虚线 #8E44AD(全池中位) |
| 基准十字 | 深蓝实线 #1F456E(沪深300),原点处另有菱形基准点 |
| 超范围样本 | 半幅 = ceil(p95/2)*2(x)与 ceil(p95/3)*3(y);超出者压缩到边界 + 三角标记 |
| 风格归因 | 6 个申万大类风格指数带约束回归(权重∈[0,1]、Σ=1),主口径 = 滚动 12 周平均权重 |
| 输出文件名 | 主观多头周度业绩榜_产品分类与四象限定位分析报告.html(固定,同名覆盖) |
四、踩坑清单(改脚本前必读)
- ECharts 图例取色必须在三处同时设:系列级
color+itemStyle.color、
data 项itemStyle.color、legend.data显式传{name, icon, itemStyle:{color}}。
只在 data 项设颜色,图例会退回默认调色板(蓝/绿/黄/红),出现「图例蓝、点画红」。 markLine/markArea必须与 series 在同一次setOption里下发,
分两次会依赖 merge 语义,独立渲染时校验会报「数据点 0」。- 象限底色用
markArea(四个[{xAxis,yAxis},{xAxis,yAxis}]区块),不要用散点画多边形。 - Python 模板字符串里的裸
%(如「95% 分位」)要写成%%,否则str % (...)抛 ValueError。 - 占位符要替换多轮(
__LIMITS__里还嵌着__RANGE__)。 - 自检桩的
getContext()要返回带measureText的对象,否则 ECharts SSR 抛
n.getContext is not a function。 - 自检桩里
.map(d => d.itemStyle.color)对基准点(无 data 级 itemStyle)会 TypeError,
需加d.itemStyle && ...保护。 - 同一文件不要并行编辑(会互相覆盖,工具却报 success)。
- 窗口与年份一律由数据推断(
lib/slwr_window.py):报告年取「净值日期」最大年份,
上一年 = 报告年 − 1,取数起点 = 报告年 1/1 往前 20 天。不要写死年份。 - 榜单里「上一年收益率」这一列的原始列名会随年份变(如 2025 年叫「2025年收益率」),
02_parse_weekly.py用正则动态识别并统一输出为「上一年收益率」,下游只认这个列名。 - 口径混用是最容易被抓的错:类型只数存在「聚类训练样本」与「全池」两个口径
(本期 75 只 / 82 只),两者可以并存,但必须显式标注——09_gen_report.py的方法节
用「聚类训练样本 75 只的簇规模为…;短样本按最近质心归类后,全池 82 只的类型分布为…」
一句话同时交代清楚。卡片、总表、交叉表、正文一律用全池口径。 - 空象限不能回避:不要偷偷改成别的画法。保留卡片、写明成因,并说明这是本期真实特征。
- 测试环境:本机 Git Bash 缺 coreutils(
ls/mkdir不可用),跑命令请用 PowerShell 工具
或直接在 Bash 里先export PATH="/c/Users/HT/.workbuddy/binaries/PortableGit/versions/1.2.0/usr/bin:$PATH"。 - Windows 下给 python 传路径要用
C:/...形式,不能用 Git Bash 的/c/...。 %格式化的参数顺序必须与占位符一一对应。09_gen_report.py的ADVICE段曾因参数元组
排错(把len(_mw)、GAP_TXT放到第二段位置),导致字符串被喂给%.3f,报
TypeError: must be real number, not str。改模板时先数占位符、再排参数,
按「第一段 → 第二段 → 第三段」顺序写参数元组。- 报告正文里禁止出现任何随数据滚动的写死内容:类型名(低波领跑型/高波蛰伏型…)、
期数("34 期")、缺失期("0601-0605 缺失")都必须由数据生成。
期数用__NWEEKS__、空档用__GAP_TXT__(这两个占位符已在fill()里注册);
类型名一律按rank_y索引(TBY/TNAME),不写类型名。
校验方法:生成后grep一遍旧数字/旧类型名,应当全部为 0。 -
分类必须用「锚定型」,不能用自由 K-means(2026-09-17 踩到,代价最大的一条)。
事故经过:原先用「单起点 + 固定种子」的 K-means,初始质心是
X[rng.choice(len(X), k, replace=False)]——抽到的下标同时取决于样本个数和样本顺序。
只新增 0601-0605 一期,2 只产品跨过 28 周门槛(73 → 75 只),种子 7 抽到的初值就从
[2,20,21,70]变成[13,22,50,64],Lloyd 迭代收敛到另一个局部最优,整批标签重排:
73 只共有产品里 31 只换了类型。而收益/回撤/夏普逐只一字未变——用户看到的现象是
"分类被改了",实际数据没动。换成 k-means++ 60 起点取最优(迁移降到 1 只)仍不够:
它是另一个全局最优解,与用户已认可的那版划分不同,等于换了一套分类体系。正确做法:锚定型分类(
lib/stable_kmeans.py的kmeans_anchored+lib/cluster_anchors.json):
· 四个类型的身份(名字、rank_y/rank_d 档位、配色、原始特征空间的簇质心)在锚点文件里固定;
· 每期只做「把当期样本分配到这四个固定簇」(锚点投影到当期 z 空间 → Lloyd 迭代);
· 完全没有随机性,与样本数量、样本排序、随机种子都无关;
· 类型名不再按当期收益重排 —— 否则同一批产品会被改名叫「进取」或「领跑」;
·06_analyze2.py与08_prep2.py共用同一份锚点和同一个实现,两处结果必然一致。
效果:34 期输入逐只完整复现用户认可的那版;加一期后迁移 1 只(真实数据变化)。锚点不能随手改:它定义了分类体系本身。若要更换(例如市场风格长期切换、成员显著漂移),
必须当作一次明确的版本变更:在cluster_anchors.json的source里写清期次与原因、
在报告方法节说明、并告知用户,不要让它每期静默漂移。
08_prep2.py每次会打印「类型稳定性:与上期同类型 X 只 / 可比 Y 只 → 迁移 Z 只」,
并把当期成员快照写入data/cluster_stability.json,供下周对比。
若某周迁移只数突然偏大(比如 >10 只),先查数据是否真的变了,再检查是否有人把分类改回了自由 K-means。
五、故障排查
| 现象 | 处理 |
|---|---|
00_plan_incremental.py 报「缺少期清单」 |
先做步骤 1、1b 生成 data/ima_catalog.json |
01_verify_raw.py 报「不合格」并中断 |
回到步骤 3 重抓被隔离的期(看 data/quarantine/) |
03/04 报 Wind 鉴权失败 |
检查 ~/.wind-alice/config.env 里的 WIND_API_KEY,或设环境变量 |
05_analyze.py 报 KeyError: '上一年收益率' |
说明 weekly_long.csv 是旧版脚本产出的,重跑 02_parse_weekly.py |
| 报告打开是空白 | 检查 assets/echarts.min.js 是否在位;报告必须是单文件内嵌,无 CDN |
| 想强制重取行情 | python scripts/03_fetch_indices.py --full(谨慎,会消耗 Wind 额度) |