宽基指数择时罗盘
> Pay Skill 范式:鉴权型(agent_local)
> 鉴权型:履约在本机执行(需本地取数/解析大文件),远端网关只做支付鉴权,支付通过后返回 fulfillment: "agent_local",Agent 再跑 scripts/ 下的脚本产出结果。
Overview
把宽基指数择时落成一套可复现的多因子合成打分 + 仓位信号框架,并给出统计可交叉校验的诊断:
- 多指数择时:对 7 个宽基指数(上证50 / 沪深300 / 中证500 / 中证1000 / 中证2000 / 创业板指 / 中证全指),把原始指标加工成特征(移动平均、指数移动平均、比值、乖离率、一阶/二阶差分、近五年历史分位),经仓位函数映射为
pos ∈ [0,1],再按滚动窗口夏普加权合成为复合仓位,输出每日看多 / 看空 / 中性信号。 - 回测诊断:择时组合净值、年化收益 / 年化超额 / 年化波动 / 贝塔 / 阿尔法 / 夏普 / 胜率 / 盈亏比 / 最大回撤,以及择时准确率(仓位变化方向 vs 当日指数涨跌方向)。
- T-M 择时能力检验:
r_p - r_f = a + b(R_m - r_f) + g(R_m - r_f)^2 + e,g显著即具备「市场上行加仓、下行减仓」的择时能力。 - 组合配置:选沪深300 / 中证500 / 中证1000 / 中证2000 / 创业板指,夏普加权合成(权重和为 1)。
输入:指数因子面板(真实宽基指数点位 + 成交额 + 全市场占比 + 十年期国债收益率 + 指数 PE/PB/DR/破净率/换手率),或仅指数点位由本包自动构造价格/金额特征。输出:择时诊断表 + T-M 回归表 + 组合配置表。
边界:真实宽基指数择时需要指数层面与宏观/基本面字段,共享缓存仅含个股日线、不含上述字段,故 --mode timing 默认如实标注【未获取】;仅输出方法论与文献对照(见 catalog)。提供指数面板后即可真实计算。
When to Use
- 用户说「想验证宽基指数能不能做择时、年化超额有多少」时
- 用户说「如何把均线/成交额/PE/破净率这些指标合成成一个仓位信号」时
- 用户说「手里有指数点位 + 成交额 + 估值因子面板,帮我算择时信号和回测」时
- 用户说「想检验某个指数择时策略到底有没有择时能力(T-M 回归)」时
- 用户说「多个宽基指数怎么配权重、用什么合成方式最稳」时
Workflow
统一入口 scripts/run_local.py,按 --mode 分派:
--mode timing(需 --input 指数因子面板 或 --prices 指数点位)
1. 载入指数面板(列 date,index_code,因子列...);若无面板但有 --prices,自动从指数点位构造价格/金额特征(PMA/PEMA/比值/乖离/差分/5年分位;成交额类特征需 --amount 成交额文件)。
2. 每个因子按「近五年(最多 1260、最少 250 交易日)历史」算均值/最小/最大,依均值回归或概率分布假设经仓位函数映射为 pos ∈ [0,1];方向(正/负 IC)由该因子与次日指数超额收益的截面相关符号自动判定。
3. 各因子 pos 序列按滚动窗口夏普加权(权重截断 ≥0、归一化)合成为复合仓位;pos ≥ 0.6 看多、≤ 0.4 看空、其余中性。
4. 回测择时组合(模型:当日收益 = pos × 指数收益),输出年化/超额/波动/贝塔/阿尔法/夏普/胜率/盈亏比/最大回撤与择时准确率;做 T-M 回归得 g 与 p(g)。
5. 多指数时再做夏普加权组合配置。
--mode catalog(不需数据)
1. 直接输出研究框架、各宽基指数择时回测统计、组合配置模型、T-M 回归对照(来自某被验证过的方法论文献实测,不具名、不编造),见 references/methodology.md 与 references/output_templates.md。
--mode all:以上按顺序执行。
输入数据规格
--prices(指数点位,可选,缺 --input 时据此外构造特征):列 date,index_code,close。
--amount(成交额,可选,配合 --prices 构造金额类特征):列 date,index_code,amount。
--input(指数因子面板,推荐,可含任意已算好的因子列):列 date,index_code,<因子1>,<因子2>,...
- date:交易日 YYYY-MM-DD
- index_code:指数标识(与点位文件一致)
- 因子列:原始指标或其加工特征均可(如 PMA10toPEMA60、VEMA5toVEMA250_d2、close_5pos、PE、PJR、RP、GEMdiff 等);方向由本包按 IC 符号自动判定。
缺 --input 且缺 --prices 时:timing 如实标注未获取,仅给 catalog 对照。
Notes / Constraints
- 真实宽基指数择时依赖指数点位 + 成交额 + 全市场占比 + 十年期国债收益率 + 指数 PE/PB/DR/破净率/换手率(成分股层面);共享缓存仅个股日线,不含上述字段,默认【未获取】。
- 仓位函数采用均值回归假设;复合仓位按滚动 35 天夏普加权(合成移动窗口经截面测试选定),无风险利率年化 2%。
- 本包择时组合模型为「当日收益 = pos × 指数收益」(未投资部分计 0 收益),属简化口径;真实回测含滑点(约 0.2%)与ETF 手续费,结论以用户自身数据为准。
- 择时准确率仅一种定义(仓位变化方向 vs 当日指数涨跌方向一致占比),非唯一口径。
- 取不到数据 → 输出「未获取」,绝不编造;合成数据仅用于管线自测,不当作结论。
- 不构成投资建议;历史有效不代表未来有效,择时模型在熊市年份风险调整后收益可能较弱、有效性随市场结构变化而衰减。
Pay Skill 模式(鉴权型,按次付费,已接入 X402)
本 Skill 为付费技能(Pay Skill),按次收费,已通过腾讯 SkillHub +
微信支付 Agent Pay X402 协议接入。不存在免费运行模式——
未完成支付授权前,不得执行本地履约脚本、不得输出任何付费内容;
鉴权型只由服务端返回的 paid: true 解锁本机履约。
> 使用者须已安装并开通 weixinpay_pay 插件(微信 AI 支付 / AI 专属卡)。
> Step 0 检查不通过时不发起付费调用,直接引导开通后重试。
鉴权型:履约在本机执行(需调用本地取数脚本 / 解析本地或远程大文件),
远端网关只负责订单鉴权与支付状态确认,支付通过后返回 fulfillment: "agent_local",
不返回业务结果;Agent 收到 paid: true 后才在本地跑 scripts/ 下的脚本产出结果。
- 统一网关主接口:
POST https://pay.zerone-iteration.com/api/invoke
(请求体{"skill": "index-timing-multifactor", "skill_version": "1.0.1", "query": "...", "amount": 990}) - 兼容入口:
POST https://pay.zerone-iteration.com/api/resource(等价于默认 Skill) - 支付回调:
POST https://pay.zerone-iteration.com/api/pay/notify - 退款回调:
POST https://pay.zerone-iteration.com/api/refund/notify - 实现:透明网关(nginx + Java 17 / Spring Boot + WechatPay-Java,微信支付公钥模式);
skill/skill_version仅做格式校验后透传进 X402 预下单,不查 SkillHub(不查注册表、不反查是否真实存在/已发布/版本匹配);
无本地白名单,新增 Skill 免 Java 注册、免改 JAR、免重启,直接POST /api/invoke传正确 slug+版本即可 - 状态:透明网关联调通过(DEV_MODE 冒烟 402 + payment_code + X-Out-Trade-No → paid=true 已验证);生产需置
INDEX_TIMING_MULTIFACTOR_DEV_MODE=false并填齐双密钥。
Agent 侧调用规范(含付费前置插件检查)
Step 0 付费前置插件检查(必须,缺失则不得发起付费调用)
- 检测当前环境中
weixinpay_pay插件(微信支付 Agent Pay / AI 专属卡)是否已安装并已开通; - 若不存在或未开通:不调用
/api/invoke,改为提示用户先安装/开通微信 AI 支付,完成后再继续; - 若可用:继续 Step 1。
Step 1 调用统一网关:POST /api/invoke,请求体:
{
"skill": "index-timing-multifactor",
"skill_version": "1.0.1",
"query": "用户原始需求(用于订单留痕,不作为履约输入)",
"amount": 990
}
skill:必填,固定为本 Skill 的 slugindex-timing-multifactor(与 SkillHub 已发布的一致;透明网关下传错 slug 时错误延后到 SkillHub/微信预下单环节才返回);skill_version:必填,当前版本号1.0.1(缺失返回400 MISSING_SKILL_VERSION);amount:必填,单位分,服务端校验为大于 0 的整数(小数/布尔/零/负数一律400 INVALID_AMOUNT,不静默截断);
区间不强制,价格由 SkillHub 产品定价约束。
Step 2 支付触发(X402 预下单 → 402):未支付时服务端依次执行——
- 生成订单号
out_trade_no(402 响应头X-Out-Trade-No与响应体out_trade_no均会返回,Agent 须记下); - 调用微信支付 Native 下单,获取
code_url; - 用 SkillHub 开发者私钥(SHA256withRSA)对 X402 AI 预下单请求签名;
- 调用
POST https://payapp.weixin.qq.com/palmpayminiapp/clawagentpay/preorder换取payment_code; - 返回 HTTP 402,响应头
WeixinPay-Required与 bodyWeixinPay块中
同时携带 X402 预下单返回的payment_code(必须是 X402 的payment_code,
不能直接把微信code_url当作payment_code返回),并回显本单amount与out_trade_no。
⚠️ body 的WeixinPay嵌套结构最易遗漏:缺失时 weixinpay 插件从 body 解析不到
payment_code,会报「当前支付订单已过期」,该报错常被误判为「返回了 code_url」。
Step 3 用户支付授权:Agent 将 WeixinPay-Required 头(或 body WeixinPay 块)中的
payment_code 作为 paymentCode 交给 weixinpay_pay 插件,向用户申请支付授权。
Step 4 二次调用鉴权:用户完成支付后,Agent 再次调用 POST /api/invoke
(请求体同 Step 1,带请求头 X-Out-Trade-No: <402 返回的订单号>);
服务端按订单号主动向微信查单验证支付成功(不依赖回调到达)后返回:
{"ok": true, "paid": true, "fulfillment": "agent_local",
"skill": "index-timing-multifactor", "out_trade_no": "...", "already_fulfilled": false}
Step 5 本地履约(本 Skill 的关键差异):Agent 收到 paid: true 且
fulfillment: "agent_local" 后,在本机执行 scripts/ 下的脚本产出结果
(见 scripts/README.md),再把结果交付给用户。
支付未确认前,不执行本地取数脚本、不输出任何付费内容
(未支付重试返回 402 {"code":"NOT_PAID"})。
> 幂等说明:already_fulfilled: true 表示同一订单此前已鉴权通过;
> 此时直接复用上次已产出的结果,不要重复跑取数脚本(避免重复消耗外部接口配额)。
异常处理说明(Agent 与服务端两侧)
| 异常场景 | 处理方式 |
|---|---|
weixinpay_pay 插件不可用 |
不发起付费调用,引导用户安装/开通微信 AI 支付后重试 |
| 用户拒绝支付 / 支付取消 | 如实告知用户支付未完成,不推进后续流程、不输出付费内容;可重新发起(服务端生成新订单) |
| 支付失败(余额不足/风控拦截) | 如实转达失败原因,引导稍后重试;失败订单作废,不据此履约 |
payment_code 过期(最长 15 分钟) |
提示订单已过期,Agent 重新调用获取新订单与新 payment_code,不得复用旧码 |
| 报错「当前支付订单已过期」 | 先查 402 响应体是否有 WeixinPay 嵌套结构,其次才是 code_url 方向 |
| 支付成功但二次调用仍 402(回调延迟) | 等待 3~5 秒后重试,最多 3 次(指数退避),携带 X-Out-Trade-No、订单号不变 |
| 网络异常 / 5xx | 指数退避重试(1s/2s/4s,上限 3 次);持续失败告知服务暂不可用 |
| 本地取数脚本失败(数据源不可用/文件缺失) | 如实告知用户并说明原因,不得因为已付费而编造数据;可引导稍后重试同一订单(already_fulfilled 为 false 时不会重复扣费) |
| 金额校验失败(400 INVALID_AMOUNT) | 修正 amount 为大于 0 的整数分后重试 |
| skill 传错 / 未发布(错误延后到 X402/微信预下单环节) | 核对 skill/skill_version 与 SkillHub 已发布条目一致后重试,不得随便换 slug |
| skill_version 缺失(400 MISSING_SKILL_VERSION) | 补上当前版本号 1.0.1 后重试 |
| 未支付即重试(402 NOT_PAID) | 等待用户完成支付授权后按 Step 4 重试,不得跳过支付环节 |
| 退款 | 服务端经 /api/refund/notify 接收回调;已退款订单的 payment_code 失效,再次调用将重新走支付流程 |
> 核心原则(SkillHub 官方教程 FAQ):支付失败/未确认时必须如实告知用户,绝不据此推进后续流程。
核心特征对照表(供审核查验)
| # | 核心特征 | 实现位置 |
|---|---|---|
| 1 | X402 AI 预下单换取 payment_code(开发者私钥 SHA256withRSA 签名,调官方 preorder 接口;WeixinPay-Required 返回 payment_code 而非微信 code_url) |
pay/server.py skillpay_x402_preorder();链路 Step 2.3~2.5 |
| 2 | 支付触发(未支付返回 402 + WeixinPay-Required 头/体双通道) |
pay/server.py _handle_invoke();链路 Step 2.5 |
| 3 | 订单号传递(out_trade_no 全链路关联:下单→预下单→回调置位→X-Out-Trade-No 重试→二次调用) |
pay/server.py generate_trade_no() / mark_paid / 402 响应 X-Out-Trade-No 头 |
| 4 | 重试机制(402 回调延迟重试带 X-Out-Trade-No、5xx 指数退避、订单过期重新下单) |
上表「异常处理说明」 |
| 5 | 付费前置插件检查(weixinpay_pay 可用性检测,缺失不发起付费调用) |
链路 Step 0 |
| 6 | 异常处理说明(支付失败/取消/过期/退款/本地脚本失败等全场景) | 上表「异常处理说明」 |
| 7 | 透明网关(skill/skill_version 仅做格式校验后透传,不查 SkillHub、不查注册表) |
pay/server.py resolve_skill()(无 SKILL_REGISTRY,免 Java 注册/重启) |
包内 pay/server.py 为 Python 参考实现(含 APIv3 签名、X402 预下单、回调验签解密、
主动查单、幂等鉴权、透明网关无需注册表),部署说明见 PAY_SKILL_UPGRADE.md 与 pay_skill.yaml。
注意:本 Skill 涉及两套独立密钥/证书——
- 微信支付 API 证书(
wechat_pay):用于微信 Native 下单与主动查单; - SkillHub 开发者密钥(
skillhub):用于 X402 预下单签名。
私钥均不得写入 Skill 包或仓库,须通过环境变量注入服务端。