2026-07-23 全市场ETF选股模式落地 + ai-dev-blog技能全局化
背景与目标
用户发现选股页面的 ETF 池只有 21 只,认为有问题。查实后确认:21 只是 api_server.py 里硬编码的内置池,并非数据缺失——DuckDB 里实际有 1900 只基金代码的日线数据(5%.SH / 1%.SZ 前缀,含 ETF/LOF)。目标:让 ETF 选股覆盖全市场,与已有的「全市场个股」模式对等。
后半场是工具链优化:把项目级的 ai-dev-blog 技能转成用户级全局技能,博客成品统一输出到知识库项目 D:/code/thinking,不再散落各项目。
过程回顾
尝试 1:确认问题性质(结果:不是 bug,是设计缺口)
先查 strategy/stock_selector.py 和 api/api_server.py,发现 21 只是硬编码 ETF_POOL(7 宽基 + 14 行业)。再用 MCP duckdb 查库:stock_daily_qfq 里基金代码共 1900 只(初查 51/56/58/15 前缀得 1425,加上 50/16 前缀共 1900)。结论:数据完整,缺的是「全市场ETF」选股模式——个股有 select_all_stocks_from_duckdb,ETF 没有对应物。
尝试 2:方案选择(结果:用户拍板全市场模式)
给两个选项:①新增全市场ETF模式(从 1425+ 只基金按流动性过滤后全量评分);②把硬编码池扩到 50~100 只主流ETF。用户选①。
尝试 3:三层落地(结果:一次通过)
- 策略层:
select_all_etfs_from_duckdb(),镜像个股全市场方法,SQL 里加流动性过滤(近 30 日均成交额 ≥ 1000 万、交易天数 ≥ 10),1900 → 1056 只入池,16 线程并行算因子。 - API 层:
GET /api/stock/select_all_etf,复用预留的all_market_cache["etfs"](原来就有这个空 key,说明设计时留过口子),1 小时缓存 + 后台刷新 + 旧缓存兜底三段式,与个股端点同构;顺带抽了_format_selection_items统一结果格式化。 - 前端层:选股页池切换加第三个按钮「全市场ETF」,标题/统计卡/URL 状态同步适配;ETF类型筛选只对内置池显示(分类映射只覆盖 21 只)。
尝试 4:技能全局化(结果:完成,含一个优先级细节)
把 ai-dev-blog 从 zdq 项目 .agents/skills/ 迁到 C:/Users/three/.agents/skills/,输出目录从 log/blog/(项目相对路径)改为 D:/code/thinking/notes/AI工作/blog/(绝对路径)。关键动作:删除项目级副本——项目级技能优先级高于用户级,不删会盖住全局版。
关键踩坑
- 坑 1:表结构想当然 →
stock_daily_qfq的代码列叫ts_code不是code,首次查询直接 Binder Error。教训:查库前先list_columns,别凭记忆写字段名。 - 坑 2:成交额单位 → 库里
amount单位是元(159915 日均 70~150 亿),流动性阈值 1000 万对应1e7,不是 tushare 风格的千元。用真实样本数据校准后才敢写进 SQL。 - 坑 3:页面缓存 → Playwright 验证时新按钮死活找不到,强制 reload 后才出现。改前端后验证必须先清缓存/硬刷新,否则会误判"改动没生效"。
- 坑 4:首次同步计算超时被误判失败 → 首次调新端点 curl 170s 超时文件没落盘,实际后台已完成计算并写入缓存,第二次 4ms 命中。长耗时接口验证要看服务器侧状态,不能只看客户端超时。
最终方案
全市场ETF选股链路(与个股模式完全同构):
-- 流动性过滤子查询(1900 → 1056 只)
SELECT ts_code FROM stock_daily_qfq
WHERE trade_date >= (SELECT MAX(trade_date) - INTERVAL '30 days' FROM stock_daily_qfq)
AND (ts_code LIKE '5%.SH' OR ts_code LIKE '1%.SZ')
GROUP BY ts_code
HAVING COUNT(*) >= 10 AND AVG(amount) >= 10000000
三模式实测(Playwright):ETF池=21、全市场ETF=1900、全市场个股=7914,表格渲染、URL 状态同步均正常。API 服务已用新代码重启(PID 34968)。
做得好的地方
- 先诊断再动手:没有直接扩池子,而是先确认"21 只是不是数据问题",避免误修。
- 同构复用:ETF 全市场方法/端点/缓存全部镜像个股现有实现, review 成本和出错面都最小;
all_market_cache["etfs"]预留 key 说明原作者也想过这条路。 - 顺带修了一个隐性 bug:前端个股模式池数量一直显示
--(读了不存在的stock_count字段),统一改读total_count后两模式都正常。 - 技能迁移做了完整闭环:新建全局版 → 删项目副本 → 更新 AGENTS.md 技能表 → 建好目标目录,四步缺一不可。
复盘与改进
- DuckDB 表结构(列名、单位)应该沉淀一份速查文档,每次现查浪费时间——后续可补进
db/docs/。 - 验证长耗时接口时,第一发请求就该带足超时并落盘响应,避免"超时→误判→重发"的循环。
- 全市场ETF首次无缓存需 1~2 分钟同步计算,用户体验上空窗期偏长;可考虑像个股一样接入盘前 SOP 预热缓存。
