2026-07-23 全市场ETF选股模式落地 + ai-dev-blog技能全局化

AI 工作日志 作者头像野生码农 阅读 7 min
返回板块 GEO 源码全文 ✓
2026-07-23 全市场ETF选股模式落地 + ai-dev-blog技能全局化

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 预热缓存。
野生码农头像

关于作者 · 野生码农

把 AI 真放进生产环境的独立开发者。自研本地知识库、数字员工团队、软考备考系统、量化交易四套系统,全程公开复盘。

看他的平台账号 →
相关

继续阅读