美股多因子选股 Skill(道指 30)
用腾讯自选股连接器(westock-mcp)做数据源,本地 Python 计算因子/复合/回测,输出 Top10 名单 + HTML 报告。不依赖 akshare / yfinance(它们常被限流),数据源走 MCP 更稳定。
前置条件
- 已连接 westock-mcp 连接器(对话里能调用
data_kline/data_quote)。 - 运行环境有 Python(含 pandas / numpy / scipy)。skill 脚本用
${SKILL_DIR}解析路径,无任何硬编码绝对路径,可复制到其它机器的 WorkBuddy 直接使用。
因子与方向(已修正原脚本 bug)
EP(+1) BP(+1) LnMV(-1) Mom12m(+1) Rev1m(-1) Vol60(-1)。
合成时显式乘 direction:等权=方向调整 z 的均值;IC 加权=用方向调整后的 z 与前瞻收益算 IC,权重=max(滚动IC均值,0) 归一化。
> 注:原独立脚本 build_composite_us.py 等权未乘 direction,会把 Rev1m/Vol60 用反;本 skill 已修正。
运行步骤(严格按顺序)
> 默认动作 = 直接用现有数据算/回测/选股 (跳过取数). 拉取/刷新数据是选做——仅当用户明确说"补全数据/刷新/更新/拉最新/补数"时才执行步骤 1→2; 否则跳过, 直接进步骤 3 跑 compute.py (用 data/stock.db 已有数据, 不浪费 token/限频).
0. 连接检查:先轻量调用 data_quote(code="usAAPL"),若报错"连接器未连接"则停止并告知用户先在 WorkBuddy 连接 westock-mcp 连接器,不要静默继续。
1. 新鲜度检查(选做,仅当用户要求补数据时)
python ${SKILL_DIR}/scripts/freshness.py
生成 data/_fetch_plan.json:{ticker: start_date}。仅当用户要求"补全/刷新数据"时才跑, 默认跳过:
- 池里没有历史 → NEW,start = 全量起始(默认 2015-01-01)
- 已有历史 → STALE,start = max_date + 1(增量补数,通常只补最近几天,很快)
> freshness 读取 data/stock.db 的 price 表最大日期;增量数据量小,补数开销极低。用户未要求补数时整步跳过。
2. 取数(选做,仅当用户要求补数据时,批量 MCP → 立即落盘,不进对话 context)
读取 config/universe.csv,拼成逗号代码串 codes="usAAPL,usMSFT,..."(每只加 us 前缀)。
westock 的 data_kline / data_quote 均支持 codes 批量参数(一次传多只),比逐只调用省事且等价的正常用法。
- 行情(K线,批量):data_kline(codes=codes, period="day", fq="qfq", start="2015-01-01", limit=2000)。
- 30 只一次返回可能过大/超时,按每批 ≤10 只拆(如 3 批 ×10),逐批串行调用。
- 大响应 westock 会自动落盘到临时文件;把该文件路径直接传给 land.py:
python ${SKILL_DIR}/scripts/land.py --kind price --ticker BATCH --input <auto_saved_path>
- 若返回内联 JSON,自行写到 /tmp/kline_batchN.json 再传同一命令。
- land.py 已兼容批量结构(data.data 为 [{symbol, data:{nodes}}] 列表),一次写入全部标的,并 upsert 进 data/stock.db 的 price 表(PK ticker+date)。parquet 仍写但仅作备份。
- 估值(批量):data_quote(codes=codes),返回 dict 结构 {"usAAPL":{...}, ...}。落盘后:
python ${SKILL_DIR}/scripts/land.py --kind quote --ticker BATCH --input <path>
(light 端点,30 只可一次传完)
- 基准指数(道指,单只):data_kline(code="usDJI", period="day", fq="qfq", start="2015-01-01", limit=2000),落盘:
python ${SKILL_DIR}/scripts/land.py --kind dji --ticker DJI --input <path>
compute.py 会自动加载 data/stock.db 的 benchmark 表(由 land.py 写入)作回测基准(净值曲线叠加道指线 + 打印相对道指超额)。该表缺失则跳过基准对比,选股仍正常。
- 限流处理:若返回 服务限频(SKILL_003),等待 1~2 秒后重试该批;不要并行批量调用(并发端点会触发限频),逐批串行即可。
- 失败容错:单批失败记日志跳过,不影响其它批次;全部完成后进入步骤 3。
3. 计算 + 复合 + 回测 + 选股 + 报告
python ${SKILL_DIR}/scripts/compute.py
compute.py 读取 data/stock.db 的 price/quote/benchmark 表(不再读 parquet)。
产出:
- data/factors/us_top10_equal_weight.csv / us_top10_ic_weight.csv(最新 Top10)
- data/factors/us_nav_equal_weight.csv / us_nav_ic_weight.csv(净值)
- report/us_report.html(最新别名, 每次覆盖)
- 报告输出(按行情收盘日命名, 不同日期互不覆盖):
- report/us_factor_top10_{date}.html — 净值曲线 + 名单 + 因子暴露 + IC 权重
- report/us_factor_top10_{date}.csv — 选股明细 (UTF-8-sig, 列: 排名/代码/名称/综合分_等权/综合分_IC加权/各因子z/基准日), Excel 中文不乱码
- {date} = us_price 最新截面日期(即取数收盘日).
4. 展示:用 present_files 一次性把结果文件推送到对话(自动在右侧内嵌面板打开 HTML 实时预览,并把 CSV 作为产物卡片列在结果区,用户不用点进 HTML 也能看到选股表)。务必同时传入以下全部文件(HTML 放第一个,作为主预览):
present_files([
"<SKILL_DIR>/report/us_report.html",
"<SKILL_DIR>/data/factors/us_top10_equal_weight.csv",
"<SKILL_DIR>/data/factors/us_top10_ic_weight.csv",
"<SKILL_DIR>/data/factors/us_nav_ic_weight.csv",
"<SKILL_DIR>/data/factors/us_nav_dji.csv",
])
注:路径用 SKILL_DIR 解析,不要写死绝对路径。<SKILL_DIR> 在执行时替换为 C:\Users\chunh\.workbuddy\skills\us-stock-selector。
增量与扩展
- 首次运行(本地无
data/stock.db或空库):先python scripts/migrate_to_sqlite.py把历史 parquet(us_price/us_quote/us_dji) 灌入 sqlite,再走取数流程。 - 再次运行(默认动作,直接算/回测,不补数):直接步骤 3
compute.py(用已有data/stock.db)。仅当用户明确说"补全/刷新/更新/拉最新数据"时才走步骤 1→2 去取数。 - 强制全量刷新:删除
data/stock.db后重跑migrate_to_sqlite.py+ 全量取数;或freshness.py --full-start 2015-01-01。 - 换股票池:编辑
config/universe.csv(加一行即新增一只,下次运行识别为 NEW 全量补数)。未来可改为港股/其它美股指数池,只需替换该文件 + 调整code前缀(港股用hk、A股用sh/sz)。
已知限制
- EP/BP/LnMV 由快照估值(PE/PB/市值)沿价格序列构造,是横截面近似,非精确 TTM 财报序列。
- 无美股财报接口,故成长(RevG/NetG/EpG)、质量(ROE/ROA/GPM) 因子暂缺。
- 回测用每日 Top10 等权、复利前瞻 1 日收益,换手较高,仅作策略有效性示意。回测净值已叠加道指基准对比(前提:步骤 2 已落盘
data/us_dji.parquet)。