通达信涨停组合 · 12 因子证据量化
用途
把「本地通达信涨停组合板块 → 基础股票池 → 个股硬证/半年报补充 → 12 因子打分 → 可核查的可视化报告」这条流水线固化为一键可复用的流程。面向 A 股短线动量 / 龙头筛选,输出带证据等级与排名的 HTML / CSV。
何时使用
- 用户要求刷新「涨停组合 / 强势龙头」股票池,并按因子打分、排序、出榜单。
- 用户给出本地通达信安装目录,希望用本地板块 + 日线 + 公开证据生成量化报告。
- 需要每日或定时重算同一套 12 因子模型,并追踪当日新进股。
前置条件
- Windows 环境,本地安装通达信,且已下载日线与板块数据(含
T0002/、vipdoc/)。 - Python 3.10+,仅用标准库,无需 pip 安装。
- 通达信「涨停组合」板块已建立,对应文件
T0002/blocknew/ZTZH.blk:纯文本、CRLF、每行 7 位内部码(首位0=深市,1=沪市)。
目录结构
tdx-12factor-quant/
├── SKILL.md
├── scripts/
│ ├── config.py 路径解析(环境变量 / 自动探测)
│ ├── check_env.py 环境自检
│ ├── refresh_pool.py 由 ZTZH.blk 重建基础池 + 新进历史
│ ├── refresh_style_supplement.py 东方财富概念成分补充源(可选 / 联网)
│ ├── fetch_float_mcap.py 联网拉取流通市值(f21)缓存(供 <20亿 删除筛除)
│ ├── prune_pool.py 先删:四道规则剔池 + 写回 ZTZH.blk + 删除报告(核验/打分之前)
│ ├── verify_new_entries.py 个股硬证核验排程/写回(新进当天 + 3 天轮转)
│ ├── quant_score13_v7.py 12 因子打分引擎 + 报告生成
│ └── write_top50_block.py 把 TOP50 写回通达信自定义板块(blk + blocknew.cfg)
├── assets/
│ └── example_new_evidence.json 新进股证据样例(schema)
└── references/
├── data_formats.md 文件格式与解析规则
├── scoring_rules.md 12 因子、合并、加分/扣分、种子数据
├── evidence_research.md 新进股取证规范与打分口径
└── troubleshooting.md 常见问题排查
配置路径
推荐用环境变量,无需改代码:
set TDX_HOME=C:\new_tdx
set TDX_WORK_DIR=C:\my_quant
TDX_HOME:通达信安装目录(其下应有T0002\与vipdoc\)。未设置时自动探测C:\new_tdx、D:\new_tdx、C:\通达信等。TDX_WORK_DIR:工作目录,存放基础池 CSV、各证据 JSON 与报告;默认当前目录。
也可直接修改 scripts/config.py 顶部 _CANDIDATES 的第一项。所有脚本共用该配置。
工作流
第 1 步:环境自检
python scripts\check_env.py
输出全部 OK 再继续。若 ZTZH.blk / szs.tnf / shs.tnf / infoharbor_block.dat / vipdoc 有 MISS,先修正 TDX_HOME,并确保通达信已下载数据。
第 2 步:刷新基础池
python scripts\refresh_pool.py
- 读
ZTZH.blk,按市场隔离从szs.tnf(深)/shs.tnf(沪)匹配名称。 - 重写
强势龙头组合12因子打分.csv(8 列:排名,代码,名称,市场,板块,行业,是否涨停,涨跌幅%)。 涨跌幅%/是否涨停按当日日线重算,不沿用旧池值:用.day最后两根算涨跌幅,涨停判定用round(前收×(1+限幅),2)与「最高=收盘」封板条件(ST 5% / 科创·创业 20% / 主板 10%),且仅当个股日线末日 == 该市场指数末日(沪sh000001/ 深sz399001)才填值,停牌/滞后一律留空。行业列仍沿用旧池(由industry_map.json维护)。旧版沿用旧值导致这两列长期全空 → 页面「收盘涨停」统计卡恒为 0、涨停行不高亮、三份 CSV 空列;若旧池残留昨天的值更会把隔夜涨停当今天展示。- 写出
today_new_codes.json(当日新增)与new_codes_history.json(近 7 日新进,供前 30 高亮)。
校验:若「BLK 原始成员数」为 0,或刷新后只数远小于 50,立即停止排查,不要用空池/残池继续;若「名称未命中」非空,记录这些代码。
第 3 步:先删(四道规则,必须在核验和打分之前)
python scripts\prune_pool.py :: 执行删除
python scripts\prune_pool.py --dry-run :: 只看会删谁,不改文件
铁序(2026-11-04 用户口径,勿颠倒):
选股(C130) → 刷新基础池(含新进标记) → 拉流通市值 → 【先删】 → 证据核验 → 统一打分排名 → 取前50/前80/总表 → 发布
为什么删除必须独立成步且排在核验之前:
- 被删股不参与统一排名,更不该占用「新进当天必查 / 3 天轮转重查」的核验配额(否则每天白查一批马上要删的票);
- 删除必须留可审计产物
pool_prune_report.json,而不是藏在打分循环的continue里; - 核验、打分、排名、三表全部基于同一份已删净的池,天然同源,不存在二次过滤。
产出与副作用:
- 原地重写
强势龙头组合12因子打分.csv(只留存活股,排名列重编 1..N,表头与 BOM 保持原样); - 写回
ZTZH.blk(移除命中股,写前备份.bak_<时间戳>;无命中则不动); pool_prune_report.json({date, before, after, removed:[{code,name,reason,detail}]})——下游--phase finalize用它做硬 gate;- 同步从
new_codes_history.json[今日]剔除被删代码,避免「今日新进 N 只」与实际待核验数矛盾; - 四道规则与打分引擎完全同口径,删完再跑打分,引擎内筛除应全部为
0(保留为兜底/幂等)。
停牌判定的三道安全网(2026-11-04 加固,改动前必须读懂):
- 分市场基准日:沪用
sh000001、深用sz399001各自的末日,不再用max(沪,深)。
旧实现一旦某市场只更新到前一天(常见:深市晚几分钟到账),该市场全部股票会被判「停牌」而整批误删。 - 无日线文件 ≠ 停牌:
_day_last_date区分「文件不存在 →None」与「空文件 →0」,
两者都保留并告警(多为新股未下载/文件异常);只有「文件存在但末日早于该市场基准日」才算停牌。 - 比例保护:某市场判停牌只数占该市场池内只数 >30%(且样本 ≥10)→ 视为日线未更新完整,
整步中止(退出码 3),不删任何股票、不改池、不写板块。daily_run.py会识别这句提示并说明
「等通达信补全日线后重跑 --phase prepare」,而不是当成代码故障。
第 4 步:保鲜风格板块补充源(可选,联网)
python scripts\refresh_style_supplement.py
拉取东方财富 6 个概念板块成分,更新 style_blocks_supplement.json。失败会保留旧文件、不阻断流程;若本机网络不可达,可跳过(引擎仅用本地 infoharbor_block.dat)。
第 5 步:证据核验(个股硬证,排在先删之后、打分之前)
口径(2026-11-04 起,用户硬规则):池内所有股票都要有个股硬证;每只 每 3 天必须重查一遍;新进股当天必查;核验完成后才统一打分排名并发布。
用 scripts/verify_new_entries.py 排程,Agent 按排程联网核验:
python scripts\verify_new_entries.py --plan :: 输出当日核验清单 JSON(配额自适应)
python scripts\verify_new_entries.py --write 结果.json :: 写回证据 + 记录核验日期
python scripts\verify_new_entries.py --mark-skip 600001,... :: 查无证据标记(同样记日期)
plan.new= 当日新进股(必须全部核验);plan.refresh= 池内满--refresh-days(默认 3)天未核验的存量股,按最久未查优先轮转。- 配额口径:存量配额 =
ceil(池/3)(198 只池 → 66 只),新进股不占这个配额,所以limit = 66 + len(new)(如 11 只新进 → 77)。旧版让新进吃存量配额,导致「3 天过一遍全池」永远欠账。 stats关键字段:todo今日应查总数;overdue已满 3 天应重查的存量总数;overdue_not_picked因配额不足本次没排上(>0 说明轮转在欠账);never_verified从未核验过;days_behind最久逾期天数。days_behind与never_verified应该逐步下降,否则扩配额或查是否有大批股长期查无证据。plan.pruned.applied=true表示池已经过第 3 步删除,pool_size即有效池。- 核验日期记录在
new_evidence_meta.json({code: {last_verified, result}}),是 3 天轮转的唯一依据;首次接手老数据可用--bootstrap把已有记录分散到最近 3 天。 - 证据只接受个股级五类:
个股硬证/个股公告/个股调研/个股研报/个股财报;查不到公司级证据就写{"no_evidence": true}(保持赛道基准托底,不硬造)。五类等级在打分引擎里由同一份INDIV_GRADE_SET单点派生(共振加分、等级优先级都认「个股研报」),勿在别处另写一份白名单。 - 单次重查的分数在
--write写入层限幅 ±3 分,避免一次检索噪声打乱榜单(打分引擎里另一处限幅只在TDX_KEEP_SEED_EVIDENCE=1的作者模式触发)。限幅基准锁定为「当天首次写回前」的旧分并存进meta.day_base/base_date,同日分批--write不会累计漂移。 - 同日重复写回是幂等的:自动化为防超时常把累计结果分批
--write、或失败后重试同一份结果文件。no_evidence_streak只在跨不同核验日才 +1(no_evidence_date已是今天则原样返回),不会把同一天的一次查无算成「连续 2 次」而误降级硬证;分数限幅也用当日固定基准。 - 「查无证据」不会让旧硬证永久生效:
--write/--mark-skip记no_evidence_streak,第 1 次只记日期并保留旧分(容忍检索噪声),连续第 2 次(不同核验日)把旧 f12/f13 降级为「证据失效」(分数减半且 ≤4、不再标 ★);任一次成功核验即清零连击。旧版「只记核验日期、不动分数」会让 3 天轮转形同虚设。 - 某维度提交了但 grade/分数不合规时不再沿用旧个股硬证,该维度同样降级,避免「证据降级」被静默吞掉。
--write/--mark-skip都要求基础池可读:池为空直接拒绝(fail-closed),且拒收不在当前池的代码。daily_run.py强制把TDX_WORK_DIR设为脚本所在目录HERE(不再允许外部环境变量覆盖):本脚本的池、删除报告、证据 JSON、_publish、验收全基于 HERE,子脚本却读config.WORK_DIR = TDX_WORK_DIR or cwd,二者不一致会造成「prepare 写 A、finalize 读 B」的割裂。需要隔离测试时请直接给子脚本设TDX_WORK_DIR,不要给daily_run.py设。- 本步骤由 Agent 用 WebSearch / WebFetch 完成;引擎本身不联网取证。
第 6 步:打分并生成报告
python scripts\quant_score13_v7.py
输出(均在工作目录):
强势龙头组合12因子证据打分_v7.html/.csv(全量,含全部个股)强势龙头组合12因子证据等级矩阵.csv强势龙头组合12因子量化股票池_TOP50.html/.csv(全池统一打分排序前 50)强势龙头组合12因子量化股票池_TOP80.html/.csv(全池统一打分排序前 80)
三张榜的关系:同源同口径,只是截取长度不同
| 榜单 | 收录范围 | 名次含义 |
|---|---|---|
总表 证据打分_v7 |
全量 N 只 | 全池原始名次 1..N |
| TOP80 | 全池统一打分排序的前 80 行 | 全池名次 1..80,= 总表前 80 行 |
| TOP50 | 全池统一打分排序的前 50 行 | 全池名次 1..50,= 总表前 50 行 = TOP80 前 50 行 |
铁律(2026-11-04 用户纠正,勿再回退):全池先统一打分、统一排序,再取前 N,不得在取前 50 时按「个股硬证」二次过滤。
- 曾经的错误做法:[x for x in results if x["verified"]][:50] + 内部重编 1..50 → 全池第 1 名(仅赛道证据,如瑞芯微 98 分)进不了 TOP50,且同票在三表名次对不上,学员看到的「第 1 名」不是真第 1。
- 正确做法:top50_list = [dict(r) for r in results[:50]],沿用全池原始 rank。
- 赛道证据股(无 ★)照样进榜、按同一把尺子比较;页面说明区会写明「本表含个股硬证 X 只、赛道级证据 Y 只」。
- 统计卡(榜首总分/门槛/平均分/赛道票数)必须按实际入表的这批股票计算,不能用全量 totals,否则与表体自相矛盾。
第 7 步:把 TOP50 写回通达信板块(在行情软件里直接跟)
报告发到云端解决「看」,写回本地板块才解决「跟」:在通达信里直接翻 K 线、挂条件单、设预警。
python scripts\write_top50_block.py :: 取 TOP50 前 50 名写入板块
python scripts\write_top50_block.py --dry-run :: 只预览,不动磁盘
python scripts\write_top50_block.py --limit 30 --name "13因子TOP30"
- 数据源 =
强势龙头组合12因子量化股票池_TOP50.csv,按排名升序取前 N,不重排、不过滤 → 本地板块 = 网页 TOP50 = 全池总分前 50。 - 写两个文件:
T0002\blocknew\<CODE>.blk(每行 7 位内部码 + CRLF)与blocknew.cfg(定长 120 字节/条:[0:50]板块名 GBK、[50:120]blk 文件名 ASCII)。默认CODE=MRZT50、名称13因子TOP50。 - upsert 自愈:cfg 里已有同名板块(例如先在通达信界面手工建过,TDX 会给它随机分配 6 位文件名)→ 沿用它的文件名,不会出现两个重名板块;cfg 记录被通达信覆盖掉 → 下次运行自动补登记。
daily_run.py --phase finalize在打分之后、上传之前自动调用本步(失败只 WARN,不阻断发布),所以日常无需手动跑。- ⚠️ 通达信把
blocknew.cfg缓存在内存里:写完必须完全退出通达信再打开才能看到新板块;若退出时把 cfg 覆盖回去(板块消失),重跑一次本脚本即可。
第 8 步:汇报
汇报:BLK 成员数、基础池只数、最终打分只数(=基础池−ST)、本次新增股及进入 TOP30 者、证据/财报覆盖、补充源成功数、输出位置、本地板块写入只数(如启用);若名称未命中或剔除异常一并提示。
12 因子速览
| # | 因子 | 满分 | 说明 |
|---|---|---|---|
| 1 | 国产替代动量 | 10 | 国产化率 / 替代空间 |
| 2 | 稀缺动量 | 10 | 资源 / 供给稀缺 |
| 3 | 垄断动量 | 10 | 寡头 / 龙头地位 |
| 4 | 涨价动量 | 10 | 产品提价 |
| 5 | 产能动量 | 10 | 扩产 / 产能翻倍 |
| 6 | 订单动量 | 10 | 大单 / 排产 |
| 7 | 上游动量 | 10 | 产业链上游 |
| 8 | 调研催化动量 | 10 | 调研 F12 与催化 F13 取 max |
| 9 | 利润动量 | 10 | 归母净利同比 |
| 10 | 收入动量 | 10 | 营收同比 |
| 11 | 风格动量 | 20 | 命中风格板块 ×5,封顶 20 |
| 12 | 价格波动 | 10 | 近 5 日跌幅 5%–13% +10 |
总分 130。另有:合并组「≥2 维均 ≥6 额外 +1 共振」、ST/*ST/退市剔除、60 日涨幅超过 50% 扣 10 分(防追高)、近 7 日新进前 30 高亮。完整口径见 references/scoring_rules.md。
重要约定
- 引擎内置的
EVIDENCE_F12/EVIDENCE_F13/BOOST/FIN是原作者存量个股研究。默认已清空(学员 / 分发模式),仅保留赛道基准(QUANT/CATALYST)与赛道证据(TRACK_F12/F13),个股证据由new_evidence.json提供。若要复现原作者报告,设set TDX_KEEP_SEED_EVIDENCE=1。详见references/scoring_rules.md。 new_evidence.json专供每日新进股覆盖,不要修改引擎硬编码来加新证据。- 名称解析必须按市场隔离(6/9 开头查
shs.tnf,其余查szs.tnf),切勿把两表update合并,否则沪市指数记录会覆盖深市股名。 - 本地通达信无财务库,
fin(营收/净利同比)必须外部核验。 - 价格 / 日线读取本地
vipdoc,需保证通达信当日已下载完成。 - 行业列空的股票:遇到就直接补映射(用户硬规则,勿询问、勿遗留默认值)。跑分出现
WARN 未知赛道(ind不在QUANT): <code>说明该 code 通达信「行业」列为空且industry_map.json也无记录 → 赛道因子套默认基准分、打分失真。标准动作:① 用 WebSearch 或名称语义确定该 code 真实申万/东方财富行业;② 往工作目录industry_map.json加code:"一级-二级-三级";③ 往scripts/quant_score13_v7.py的IND_OVERRIDE(第726行)加code:"QUANT赛道码"兜底;④ 同步脚本到@user_9fb383fd/yinziztzh/scripts/(保持 MD5 一致);⑤ 重跑至WARN 未知赛道归零。详见references/troubleshooting.md第5节。
四道删除筛除与板块写回(2026-10-27 新增;2026-11-04 增至四道并前置为「先删」步骤)
执行位置(2026-11-04 起):四道筛除由独立脚本 prune_pool.py 在第 3 步执行,位置在「刷新池」之后、「证据核验」之前;打分引擎内保留同口径代码作为兜底(幂等),正常应全部为 0。
- 前置到核验之前的原因:被删股不该占用新进/轮转的核验配额;删除需留可审计报告
pool_prune_report.json;核验、打分、排名、三表必须基于同一份已删净的池。 - 删除是按当日事实判定(涨幅、市值、停牌状态都会变),不做永久黑名单:明天若不再触发规则,重新选回板块即正常参评。
四道规则(prune_pool.py 与 quant_score13_v7.py 完全同口径):
- ST / *ST / 退市删除:名称以
ST/*ST开头或名称含"退"、或板块列为"ST股",直接剔除。 - 停牌删除:个股日线
vipdoc最后一条记录的日期 早于市场最新交易日(sh000001/sz399001日线末条取最大)→ 判定为当日停牌,直接剔除。停牌股无当日成交,其价格类因子(价格波动/均价动量/跃升)与涨跌幅全是陈旧值,却仍会被静态证据因子打出高分,不剔除会长期占据榜单(典型:002623 亚玛顿 11-03/11-04 停牌、日线停在 11-02,仍被算出 82 分排第 7)。复牌当日自动回到正常评分。指数日线缺失(MKT_LATEST=0)时本道自动跳过,避免误删全池。 - 60 日涨幅 > 70% 过热删除:复用本地
vipdoc日线算出的RISE60(不复权原始收盘价口径,与行情软件显示一致;与 >50% 仅扣 10 分是两套逻辑,互不影响),>70%直接剔除。 - 流通市值 < 20 亿删除:本地通达信无流通股本数据,由
fetch_float_mcap.py联网从东方财富拉取全池股票的「流通市值(f21,单位:元)」并缓存到工作目录float_mcap.json;< 2_000_000_000剔除。
- 数据安全 / 容错:float_mcap.json缺失或网络拉取失败 → 该筛除自动跳过并告警、不阻断流程(不会误删);daily_run.py已在删除步骤前自动跑fetch_float_mcap.py,失败保留旧缓存。
- 用户也可离线手动编辑float_mcap.json(格式{ "600519": 210000000000, ... },单位元)。
板块写回(会改手动维护的涨停组合):prune_pool.py 把命中任一删除规则的股票从 ZTZH.blk 移除,实现板块自清洁;写前自动备份 ZTZH.blk.bak_<时间戳>,无命中则不动文件。即使市值数据缺失,ST、停牌与涨幅>70% 的删除仍会写回(三者不依赖市值数据)。行业列空不通过写回删除(仅影响评分,不碰板块文件)。恢复板块用最新 .bak_ 覆盖 ZTZH.blk 即可。
注意:tdx_pick_limitup.py 对 ZTZH.blk 是追加写(只加不减),因此被删股在后续交易日若仍满足「13 日内 ≥2 涨停」会被重新选回板块——这是预期行为(按当日事实重评);当天它会在第 3 步再次被删、并从 new_codes_history[今日] 剔除,不会重复占用核验配额。
流程健壮性加固清单(2026-11-04 全链路复查后新增)
按下表逐条确认,这些都是踩过或差点踩到的坑,勿回退:
| 环节 | 加固内容 | 不加会怎样 |
|---|---|---|
就绪校验 check_day_ready.py |
① 判定为「指数必须达标 + 每市场真实个股达标率 ≥80%」;② 样本按 6 位代码段筛(沪 60/68、深 00/30,排除 sh000001~sh000005 / sz399xxx 指数文件)、每市场 20 只;③ 某市场真实样本 <5 只直接硬失败;④ 周末返回退出码 4(不可重试) |
最隐蔽的一处:旧版 base[0] in "036" 把 sh000001.day~sh000005.day(上证指数系列)当成「沪市普通股」,沪市 5/5 达标其实全是指数、从没验过任何一只真实沪股——指数更新而个股没更新时会直接放行陈旧数据;样本只有 5 只时 2 只停牌就跌破阈值整天空转;周末/样本静默消失时空转 30 分钟 |
就绪轮询 wait_day_ready |
前 5 次间隔 60s(密探到账瞬间),之后 300s;共 10 次(最迟 17:05 开始选股) | 旧版固定 300s/12 次,16:36 到账却要等到 16:40 才发现,且窗口拖到 17:35 压掉核验时间 |
刷池 refresh_pool.py |
① 按 FUND_PREFIX/INDEX_PREFIX/SKIP_NAME + 北交/三板(4/8 开头) 剔除非个股;② added/_added_today/历史清单全部与过滤后的有效池取交集(不是 blk 原始成员);③ 今日新进为空时显式删除当天键;④ 名称为空占比 >50% 直接中止刷池;⑤ 市场列按6 位代码判定(不用 blk 内部码首位) |
519xxx/8xxxxx 靠「并集」无限传承;旧条目残留导致「今天没有新进」却仍排核验;tnf 快照整体失效时产出全空名称的脏池;内部码错位会造成「market=沪 + 名称取深市」的错配 |
先删 prune_pool.py |
见上「停牌判定三道安全网」;另记录 market_base 与 no_day_file 到报告 |
单边市场未更新 → 整批误删;日线缺失 → 股票凭空消失 |
市值 fetch_float_mcap.py |
新值优先 + 旧缓存补缺(不再整体覆盖);返回空时告警缓存年龄 | 某天只拉到部分就把其余股票从缓存抹掉,这些股票「查无市值」被静默漏筛;旧缓存过期则市值失真 |
打分 quant_score13_v7.py |
① 排序加二级键 (-total, code);② 页面删除文案读先删报告;③ filtered=True 分支打 WARN;④ 停牌兜底与 prune 完全同口径:分市场基准 + 无日线文件不判停牌;⑤ 页面核验机制说明与当前流程同步 |
引擎兜底用 max(沪,深) 单基准、且缺文件当 0 分,会把 prune 刚保留的股票二次剔除(池 198 变 193)并触发「兜底不为 0」误警;页面写着已删除的自动化 ID 和错误时间,学员看到过期信息 |
收敛 converge_index_top50.js |
① 今天禁止自愈删除(无 TOP50 时保留条目、交验收报错);② 结尾打印对可能已 delete 的日期判空;③ 「今天」用本地时区格式化 | 上传残缺时自愈会把当天唯一报告连同对象物理删除,云端空白且不可恢复;index[date].files 对已删键抛 TypeError 让收敛 exit 1(此时对象已删);toISOString() 是 UTC,北京时间 00:00-08:00 跑会把「今天」当历史删掉 |
上传 publish_cos.js |
本次有任一文件上传失败就跳过 --prune-older |
部分失败仍 prune,会把云端唯一可用的「昨天」整目录删掉,而今天又缺 TOP50 → 学员端直接空白 |
核验排程 verify_new_entries.py |
① 新进只认今天的键(不再退化取最近日期);② 轮转判定用 days < refresh_days(不是 <=);③ 存量配额 = ceil(池/3),新进不占存量配额;④ stats 增 overdue / overdue_not_picked / never_verified / days_behind;⑤ --write 拒收不在当前池的代码 |
退化取最近日期会把上周新进全部重查、白耗配额并打乱节奏;<= 让「3 天轮转」实际变 4 天;新进占配额导致存量永久欠账(198 池 + 11 新进只轮 55 只);within_window 旧算法把「已到期没排上」算成在窗口内,欠账看不出来;写入已删股让证据库膨胀 |
发布 daily_run.py |
① 池行数必须 == 删除报告 after;② converge 失败重试 1 次;③ 发布后硬验收:索引只有今天 + 仅 1 个 TOP50 + 实际拉一次 TOP50(HTTP 200 / 体积 / 含今日日期);④ 部分文件失败不直接 abort,先收敛再按 TOP50 是否可用判定;⑤ --skip-refresh 禁与 --phase 同用;⑥ 不带 --phase 必须显式 --force-skip-verify |
池被改动仍照发;--skip-refresh --phase prepare 会把旧池重写成「今天」的文件、连带 pruned 报告,直接绕过时效 gate 发布隔夜数据;索引指向死链无人发现;部分失败时学员端留 2-3 个入口 |
先删比例保护 prune_pool.py |
停牌占比阈值 30% → 10% 或绝对 ≥8 只;板块备份改为「只删 >30 天且保留最近 10 份」 | 旧 30% 太松:指数已更新、某市场 10%~25% 个股日线没跟上时,会把十几只当「真实停牌」删掉还照常发报告;每次写回生成 1 个 .bak_,实测已堆到 50 个 |
产物一致性 quant_score13_v7.py |
① 证据等级矩阵 CSV 按总分名次排序并复用总表 rank;② 模板「用于未单独核验的 140 只」等历史硬编码改动态 @@TRK@@;③ bar() 按量纲缩放(风格 20 分制、其余 10 分制)并封顶 100%;④ new_sc 统一 clamp;⑤ 页面「总分140」补注 +15/-10 |
矩阵表 198 只里 197 只名次与总表对不上、整张表还是按代码序;页面写着 10-12 的「140 只/前100名覆盖99只」与当天 68 只矛盾;风格 15/20 分渲染成 width:150%/200% 溢出单元格;证据库若被手改越界会污染总分 |
核验排程 gate verify_new_entries.py |
--plan 先校验「池是今天生成 + 当天 pool_prune_report.json + 池行数==报告 after」,不满足退出码 2(--allow-stale 仅供离线调试) |
prepare 中途失败时仍拿隔夜旧池排出几十只待核验,agent 联网查完才在 finalize 被拒,白耗一整轮配额与时间 |
发布验收 daily_run.py |
① partial_publish=True 时索引残留其它日期降级为警告(不是失败);② finalize 读 mcap_coverage/mcap_age_days,低于 90% 或超 3 天打醒目 WARN |
上一轮加了「上传部分失败跳过 prune 保护昨天」,但验收仍硬要求「仅今天」,于是「降级但学员端可用」被误报成发布失败;市值漏筛长期无声无息 |
调度 automation ec14345f |
rrule 从 FREQ=DAILY 改为 FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;wait_day_ready 识别退出码 4 立即放弃 |
周末照常触发、白等 30 分钟后放弃,每周浪费 2 次完整自动化窗口 |
第五轮:等级单点化 quant_score13_v7.py |
GRADE_PRIORITY / _INDIV_GRADES 提到模块顶部由 INDIV_GRADE_SET 单点派生并加启动自检,补上「个股研报」;新增「证据失效」等级 |
旧版在打分循环里另硬编码一份个股级集合,漏了「个股研报」:该股页面标 ★ 却拿不到共振 +1、等级优先级为 0(比「—」还低);实测 198 只池里 5 只受影响、3 只各少 1 分 |
第五轮:证据失效 verify_new_entries.py |
① 「查无证据」连续 2 次降级旧硬证(第 1 次保留旧分容忍噪声,成功核验清零连击);② 被拒维度不再沿用旧个股硬证;③ --write/--mark-skip 池为空 fail-closed、拒收池外代码;④ 备份名带毫秒 |
旧版查无证据只记日期、不动分数,3 天前查到的硬证会永久生效(页面永远标 ★、分数永远不变),「每 3 天重查」等于白查;池读不到时旧版 if _pool_now and ... 会静默跳过全部校验;同秒两次写回覆盖同一备份 |
第五轮:发布时效 daily_run.py |
① 发布前硬校验 3 份报告 mtime=今天 + 齐全 + ≥10KB;② _publish 用 rmtree 整体重建;③ finalize 打印「今日已核验 N 只 / 池内从未核验 M 只」,0 只打 WARN |
旧版只判 os.path.exists:打分脚本部分失败却返回 0 时,本地残留的昨天 TOP80/全量页会被当今天发布,且这两份不在发布验收范围内;_publish 子目录里的旧 HTML 会被递归扫到上传;三段式里「核验有没有跑」完全无痕迹 |
| 第六轮:写回幂等 verify_new_entries.py | ① no_evidence_streak 同日不累加(no_evidence_date == TODAY 直接返回);② 限幅基准存 meta.day_base/base_date,同日锁定 | 实测同一天 3 次 --write 同一份查无结果:旧版 streak 1→2→3,第 2 批就把 9 分个股调研砍成 4 分「证据失效」、第 3 批再砍到 2 分;同一天 3 次写高分:5→8→10 绕过 ±3 单次限幅。自动化明确允许分批写回,故这是必现路径 |
| 第六轮:当日行情 refresh_pool.py | 涨跌幅% / 是否涨停 按最新交易日日线重算(分市场指数末日对齐 + 封板价判定),不再沿用旧池 | 实测 198 只这两列全空,页面「收盘涨停」统计卡恒显示 0、涨停行无高亮、三份 CSV 空列;修复后 198/198 有值、32 只涨停、范围 −10~20%,代码与行业列逐行不变 |
| 第六轮:路径单点 daily_run.py | env["TDX_WORK_DIR"] 强制 HERE,忽略外部覆盖 | 旧版 env.get() 允许外部覆盖,子脚本写到别处而主脚本从 HERE 读,会造成 prepare/finalize 数据割裂 |
| 第六轮:学员端 fetch_report.js | ① 缺 SDK 时降级匿名公有读(不再 exit(1));② 对外链接统一 pubUrl() 匿名永久地址,去掉「签名 1 小时有效」误导文案 | 老师本机有密钥却没配 NODE_PATH 时学员端直接失败;有密钥时 rec.url 是 1 小时签名链接,若输出给学员会过期 403 |
| 第七轮:写回字段保全 verify_new_entries.py | --write 必须以 dict(old) 为基底、只覆盖本次核验的字段;跳过条件改 if not payload and not weak_keys | 旧版重建 {"name","updated","f12","f13"},每次轮转重查都会静默丢掉 fin(半年报营收/利润)与 boost(上游加分):实测 000420 吉林化纤 62→53 分、排名 63→110,且 TOP80/总表/矩阵跟着整体错位。另外「只提交一个不合规维度」时 payload 为空会 continue,旧个股硬证不降级,把第五轮的降级修复整条绕过 |
| 第七轮:产物可复现 quant_score13_v7.py | 赛道明细补充行 for ind in sorted(set(...)) | set 迭代顺序受进程 hash 随机化影响,同一份数据连跑两次生成的赛道表顺序不同,全量页 HTML 字节漂移(内容相同但无法逐字节比对/审计);修复后连续两次运行字节一致 |
| 第七轮:分发闭环 | 补齐老师端 SKILL.md 的 slug/version/displayName(否则 publish --dry-run 直接报「缺少 slug」);tdx-report-publish 同步项目根 converge_index_top50.js 并升版本;三个 zip 与源目录逐文件一致 | 老师端 zip 此前根本无法发布;发布技能包里的 converge 还是 10-27 旧版(会自愈删除当天报告);publish_cos.js 的 --prune-older 正则修复也没进 zip |
| 第八轮:本地板块 write_top50_block.py(2026-11-05 用户指令) | 新增:把 TOP50 CSV 前 50 名写成通达信自定义板块(MRZT50.blk + 在 blocknew.cfg 追加/校正 120 字节记录),finalize 打分后自动调用;同名板块存在则沿用 TDX 分配的文件名;blk 内容不变不重写、备份保留最近 10 份;写完读回自校验 | 旧流程只发云端报告,本地通达信里没有当日榜单,无法直接翻 K 线/挂预警;手工维护板块每天要复制 50 个代码,必然漏更新。格式为实测:cfg 定长 120 字节/条([0:50] 名称 GBK + [50:] blk 名 ASCII),blk 每行 7 位内部码 + CRLF、共 9 字节 |
| 第九轮:备份清理 write_top50_block.py(2026-11-05 顺手修) | cleanup_baks() 同时清理 <code>.blk.bak_* 与 blocknew.cfg.bak_*(合并保留最近 10 份) | 旧版只清 blk 备份,cfg 备份(仅在板块首次创建/改名时产生)长期不清理会零星累积;虽不影响功能,但属备份清理遗漏,已补齐 |
| 第十轮:赛道字典季调 quant_score13_v7.py(2026-11-05 季度深度复核后用户拍板) | 用户授权调整两套产业字典:① 微调 A(上调)电网链 CABLE 催化分 4→5、ELECTRICAL 5→6(十五五电网超 5 万亿、特高压设备招标 +97.7% 强于原保守假设);② 微调 B(下调)CHEM_RESOURCE QUANT idx4 涨价维度 6→5(钛白粉同比仅 +2.89% 基本持平、有机硅 DMC 冲高回落,涨价维度略高估)。B 的「利润」维度未改:利润动量由每只股半年报真实财务 FIN 驱动(sc[13]),不归赛道字典管,无对应旋钮 | 字典原是 2026 年中产业研究静态快照,季度复核发现电网链低估、化工资源涨价高估;经 WebSearch 近 3 个月数据核对后调整,其余 ~35 个赛道保持 |
| 第十一轮:高位放量预警标记 quant_score13_v7.py(2026-11-05 用户指令「接入每日 TOP50 输出」) | 新增「涨停后高开低走放量阴线」预警标记:全池 results 排序后由 load_warning_flags() 读本地 vipdoc 日线逐只判定,CSV(主表/TOP80/TOP50)末尾追加 高位放量预警(🚩) 与 警示日期 两列,学员端 HTML(证据页 + TOP50/TOP80 页)表尾同列显示 🚩(title 注明信号日)。判定口径:第 N 日封板涨停(按板块限幅、high==close)→ 第 N+1 日高开(open>昨收) 且 收阴(close<open) 且 放量(量>涨停日量),仅标记最近 13 个交易日内出现的信号日;复用 tdx_pick_limitup.py 的封板判定 | 旧流程榜单只给因子分与名次,用户无法在每日 TOP50 / 学员端一眼识别这类高位出货警示形态(涨停次日放量长上影/阴线);本标记是纯增量列,不改任何打分/排名/三表同源,与独立公式、通达信条件选股口径完全一致 |
排障
见 references/troubleshooting.md:BLK 为空、名称未命中、东财 API 失败、未知赛道 WARN、ST 剔除异常、60 日扣分误判、HTML 编码等。