← 文档库
📄 文档learn/截面选基-理解手册/11-截面选品与择时信号的双时钟错配与持仓年龄.md
🌱 创建于2026-10-23 22:19
✏️ 修改于2026-10-24 12:37
👁️ 浏览量—
🔗 短链接/~learn/s/944b3ca8
🔐 可见性

11 - 截面选品与择时信号的双时钟错配 · 持仓年龄

> 建立日期:2026-10-23 > 状态:现象已量化确认,整改方案全部「未实施」,待确认后再动代码。 > 本文是「截面选品(周频 Top10)↔ 择时策略池(每日 position 信号)」两个时钟打架这一整篇讨论的归档,同时把「策略持仓年龄」的度量口径一并定义清楚。

---

0. 一句话结论

截面选品是「每周定稿、周一执行」的低频决策,择时信号是「每天变」的高频决策,二者的更新频率天然不一致;系统当前把它们按日强行合成,结果就是「品种在 Top10 边缘反复进出 + 权重每天大幅跳动」,产生了大量与因子观点无关的换手。 这不是 bug,是多时钟(multi-horizon)错配,属于策略设计问题,必须显式建模(迟滞 / 平滑 / 换手预算 / 确认制),不能靠"再调调因子权重"解决。

---

1. 用户观察到的现象(原始问题)

对照截面网页「策略持仓」表格,每一行有 前仓位 / 现仓位 / 预测仓位 三列,由择时策略池的买卖信号驱动。用户观察到:

由此引申出的问题是:一个"被选进来 3 天、择时信号又刚翻空"的持仓,到底该怎么算?它的"持仓年龄"是多少?该按哪一方的观点执行?

---

2. 系统里真实存在的「三到四个时钟」

先把事实摆清楚(依据代码与 DB 实况,不是推测):

# 时钟 频率 谁在驱动 代码/数据位置
:-:-----------------------------------
① 截面打分 每交易日都会算一遍 generate_push_message() → save_weekly_snapshot() engine.py
② 组合权重重算 每交易日重算(DB 中 cross_section_portfolio 共 95 天记录) save_portfolio_timing() cross_section.py
③ 择时 position 信号 每日更新(不同锚点节奏差异极大,见 §5.3) 择时策略池 cross_section_portfolio.timing_today / timing_predicted
④ 人工实际执行 周一 9:30–10:30 一次(设计铁律第 1 条) 人 铁律:「周五出结果,周一执行」

关键矛盾:设计意图是「周频决策 + 周频执行」,但系统内部实际是「日频重算」。也就是说,周二到周五每天网页上的"现仓位"都在变,但用户并不会去调仓 —— 网页展示的是一个"如果今天执行会是什么样"的虚拟组合,它在 5 个交易日里可能已经换了两三轮。

这就解释了为什么用户会觉得"品种在乱跳":看的其实是日频虚拟组合,执行的却是周频真实组合,两个东西根本不是同一个组合。

---

3. 量化里的正式叫法(术语表)

中文 英文 含义 在本系统的体现
--------------------------------
多时钟错配 multi-horizon / multi-frequency mismatch 信号生成周期与执行周期不一致 截面周频 vs 择时每日
信号衰减 signal decay / alpha decay 信号预测力随持有时间下降 排名第 1 的品种 2 周后可能掉到 30 名
信号半衰期 signal half-life 信号强度衰减到一半所需时间 本系统核心品类的排名半衰期约 1–2 周
陈旧信号 stale signal 用已经过期的观点做当下决策 周一执行的是上周五的因子分数
双边打脸 whipsaw 买入后立即反向、卖出后立即反转,双向付出成本 Top10 边缘品种反复进出
换手率 turnover 单位时间买卖金额占比 见 §5.2,均值约 19%/日
滞回 / 迟滞 hysteresis 进/出用不同阈值,避免在边界抖动 目前没有(进出同一阈值)
持仓年龄 holding age / position age 一个仓位已持有多久 目前未统计,本文 §7 定义
换手预算 turnover budget 给定时间窗内允许的最大换手 仅周中再平衡器有 30%/日上限
实现缺口 implementation shortfall 决策价与成交价的差 周一开盘执行 vs 周五收盘价
调仓周期不匹配 rebalance horizon mismatch 同上 multi-horizon 的另一说法 —

> 与「追涨杀跌」的区别:追涨杀跌是因子方向问题;多时钟错配是频率结构问题。因子再好,频率错了照样被摩擦成本吃掉。

---

4. 三个时钟各自是怎么运作的(读代码得到的事实)

4.1 截面打分:每天全量算

cross_section_weekly_snapshot 表实测:每个交易日 109 行(全池 109 只全部入库,is_top 全为 1,即"全量排名模式")。也就是说「Top10」只是前端取 rank<=10 的展示,底层每天都有完整 109 只的排名。

4.2 组合权重:每天重算,每天变

cross_section_portfolio 表实测:95 个交易日,每个交易日都有一版持仓与权重。近期样本:

日期 持仓只数 权重合计
------:---::---:
2026-10-23 11 99.89%
2026-10-22 11 98.64%
2026-10-21 12 98.60%
2026-10-20 11 98.37%
2026-10-19 7 38.55%
2026-10-16 7 37.27%
2026-10-15 5 23.30%
2026-10-14 8 52.45%
2026-10-13 10 41.33%
2026-10-12 11 55.78%

注意 10-12 ~ 10-19 这一周:权重合计从 55.78% 掉到 23.30% 又回到 38.55%,只数从 11 → 10 → 8 → 5 → 7 → 7。如果用户真按这个每日调仓,一周内要把仓位砍到 1/4 再买回来 —— 这显然是择时信号在剧烈摆动,而不是因子观点在变。

4.3 择时信号:不同锚点节奏天差地别

按锚点统计 timing_today 同一天的均值,取 变化 ≥ 20 个百分点 记为一次"信号翻转":

锚点 有数据天数 大幅翻转次数 翻转频率 典型翻转
------:---::---::---:------
QQQ(Invesco QQQ Trust,纳斯达克100 ETF) 94 16 约每 6 天 1 次 09-15 50→75、09-18 68.75→43.75、09-22 25→56.25、09-26 81.25→56.25
HSI(恒生指数) 74 6 约每 12 天 1 次 08-11 21.84→79.59、08-12 79.59→52.38(次日又反过来)、09-14 61.22→26.53
399102.SZ(创业板综) 71 4 约每 18 天 1 次 07-02 0.53→62.07、07-13 39.08→18.39、08-18 32.18→64.37、09-11 42.53→5.75
510880(红利ETF) 47 1 极稳 08-09 8.82→43.74
511260(十年国债ETF) 5 0 — —

这张表是本文最重要的一张:同样是"择时信号",QQQ 锚点平均 6 天就大幅变一次,红利锚点 47 天只变 1 次。当一个新品种被选入 Top10、它挂的锚点是 QQQ 时,"品种年龄"和"信号年龄"根本不是一回事 —— 品种可能才进来 2 天,而它的信号已经在 2 天里翻了 1 次。

---

5. 实测证据:错配到底有多大

5.1 组合成员日间进出(近 15 个交易日)

日期 新进 退出
------:---::---:
2026-10-23 5 5
2026-10-22 2 3
2026-10-21 5 4
2026-10-20 6 2
2026-10-19 2 2
2026-10-16 4 2
2026-10-15 1 4(持仓仅 5 只)
2026-10-14 2 4
2026-10-13 4 5
2026-10-12 1 1
2026-10-09 4 4
2026-10-08 1 1
2026-10-02 0 0
2026-10-01 2 2

平均每天换掉 3 只左右;极端日(10-20、10-23)一天换 5 只。一个 11 只的组合,日周转接近 30%。

> 口径注脚(2026-10-24 核对):上表的「成员」= cross_section_portfolio 里 weight > 0 的行。所以「退出」不等于被排名踢出 Top10,大量是①择时 position 归零(行被剔除)②QDII 溢价排除后被候补顶替③对冲位/国债位轮换。真正由「截面打分换名次」造成的换手只占一小部分,见下表。

排名 Top10 变动 vs 组合成员变动(同期对比)

日期 排名 Top10 换手 组合成员换手 单边换手 持仓数
------:---::---::---::---:
2026-10-19 0 进 0 出 2 进 2 出 19.5% 7
2026-10-20 2 进 2 出 6 进 2 出 50.8% 11
2026-10-21 1 进 1 出 5 进 4 出 19.6% 12
2026-10-22 1 进 1 出 2 进 3 出 12.4% 11
2026-10-23 0 进 0 出 5 进 5 出 31.5% 11

最近 4 个交易日排名 Top10 只动了 0~2 只,组合却每天换 5~6 只 —— 这就是「选品时钟」和「执行时钟」错配的直接量化:换手主因不在打分排名,而在择时/溢价/对冲位。

> 补充证据(10-14 → 10-15):排名 Top10 只差 1 进 1 出(518880.SH 黄金ETF 掉到 13 名、159612.SZ 标普500ETF 从 11 名补上),组合却少掉 4 只(513400.SH 道琼斯ETF、513800.SH 东证指数ETF、511260.SH 十年国债ETF、511090.SH 30年国债ETF),而两天 exposure_cap 都是 0.46 —— 不是仓位上限压的,是择时/对冲位。

5.2 单边换手率(ΣΔw / 2)

日期 单边换手 日期 单边换手
------:---:------:---:
2026-09-30 5.2% 2026-10-16 22.3%
2026-10-01 16.4% 2026-10-19 19.5%
2026-10-02 0.5% 2026-10-20 50.8%
2026-10-08 7.6% 2026-10-21 19.6%
2026-10-09 22.3% 2026-10-22 12.4%
2026-10-12 8.9% 2026-10-23 31.5%
2026-10-13 25.5%
2026-10-14 19.2%
2026-10-15 23.6%

近 15 日平均单边换手约 19%/日。 按 A 股 ETF 双边摩擦(佣金 + 冲击成本,保守取单边 0.05%~0.10%)估算,年化摩擦成本约 19% × 2 × 250 × 0.05% ≈ 4.8%(乐观口径),悲观口径接近 10%。这是一个可以直接吞掉全部超额收益的量级。

5.3 2026-10-23 组合快照(示例,说明重量级错配)

品种 权重 前仓位 → 现仓位 → 预测 锚点
------:---::---:------
511090.SH 30年国债ETF 39.62% 89.21 平 —
512800.SH 银行ETF 12.00% 48.68 → 50.44 → 36.33 510880
512890.SH 红利低波ETF 12.00% 48.68 → 50.44 → 36.33 510880
511260.SH 十年国债ETF 11.08% 99.79 平 —
159502.SZ 标普生物科技ETF 7.16% 71.57 平 —
159612.SZ 标普500ETF 5.00% 80 → 100 → 40 SPY
159577.SZ 美国50ETF 3.92% 85.95 → 77.45 → 72.22 QQQ
513800.SH 东证指数ETF 3.68% 39 平 EWJ
513050.SH 中概互联ETF 2.58% 20.41 平 HSI
512200.SH 房地产ETF 1.50% 22.99 → 20.69 399102.SZ
510060.SH 央企ETF 1.35% 48.68 → 50.44 → 36.33 510880

一句话读法:159612.SZ 标普500ETF 的择时信号从 80 直接跳到 100,预测又要跌到 40 —— 这个"预测仓位"一栏本身就在剧烈摆动,如果每天照着它调,标普500 一天之内要走 100→40。

---

6. 错配的四层代价

层 代价 说明
:--:------------
① 直接成本 佣金 + 买卖价差 + 冲击成本 换手 19%/日 → 年化摩擦 5%~10%
② 噪声成本 把"信号抖动"当成"新信息" QQQ 锚点 6 天翻一次,其中大部分是噪声
③ 行为成本 用户在周中看到组合大变,忍不住手动干预 破坏「周一调仓」铁律,实盘与回测脱节
④ 归因污染 事后无法判断收益来自"选品"还是"择时" 两个时钟都在动,归因变成一笔糊涂账

其中 ③ 最隐蔽:只要网页每天展示一个变化剧烈的组合,用户的纪律迟早会被击穿。 这是"UI 反过来塑造行为"的典型例子。

---

7. 持仓年龄:三个不同的年龄,别混用

「持仓年龄」不是单一指标。必须拆成三个,否则永远对不齐。

名称 建议字段 定义 当前系统状态
---------------------------------
品种年龄 days_in_portfolio 该品种连续出现在 cross_section_portfolio 中的交易日数 ❌ 未统计(可从历史表回算)
信号年龄 days_since_flip 该品种所挂锚点的 timing_today 距离上次大幅翻转(Δ≥20pt)的天数 ❌ 未统计
排名年龄 rank_stability 该品种连续在 Top10 内的周数(≈streak_weeks) ⚠️ 部分有(快照表已有 streak_weeks,但只覆盖排名,不含权重)

为什么必须分开:

「前仓位 / 现仓位 / 预测仓位」三列的正确解读:它不是"一个仓位的演变",而是"同一天三个不同口径的计算结果"。真正该展示的是「上周一实际执行 → 今天虚拟 → 下周一建议」,而不是"昨天/今天/明天"。

7.1 建议新增的观测字段(未实施,候选)

days_in_portfolio   INT   -- 品种在组合中的连续交易日数
days_since_flip     INT   -- 锚点择时信号距上次大幅翻转的天数
weight_change_5d    REAL  -- 近 5 日权重累计变化(衡量平滑度)
turnover_20d        REAL  -- 该品种近 20 日累计单边换手

> 落地时注意:cross_section_portfolio 新增列必须同步 CREATE / MIGRATE / INSERT 列列表 / VALUES 占位符 / 参数 tuple 五处(该文件 L402 已写明这条教训)。

---

8. 系统里已有的抗抖动机制(先盘点,别重复造)

8.1 组合层持仓惯性阻尼(v4,2026-10-23 才真正生效)

位置:engine.py 的 _apply_holding_inertia() / _commit_inertia_portfolio(),状态文件 .holding_inertia.json(adata/db/ 下)。

当前参数(v4,commit 1d4b039)

常量 值 含义
------:--:------
_INERTIA_TARGET_N 10 真实组合目标持仓数(与排名表行数解耦)
_MAX_NEW_PER_WINDOW 3 滚动 5 个交易日内最多新进 3 只(不是自然周,也不是"一百多只")
_NEW_WINDOW_DAYS 5 滚动窗口长度(交易日)
_INERTIA_BONUS 0.030 在位成员评分加成(最长叠到 0.045)
_ENTRY_MARGIN 0.015 超配额新进者的压分幅度
_HARD_EXIT_SCORE 0.20 评分低于此不再保护
_INERTIA_OFF_REGIMES BEAR / SEVERE_BEAR 熊市整层停用

⚠️ 关键事实:v4 之前(10-23 19:03 之前)这层从未执行过。 旧版守卫是 if len(ranked) < top_n * 2: return ranked,而推送路径传 top_n=99、全池 109 行 → 109 < 198 恒成立 → 每个交易日都直接早退。三重佐证:

所以 §5.1 / §5.2 那些换手数据全部是在「裸跑」口径下产生的,不含任何惯性保护。新逻辑 10-23 收盘后才提交,最早 10-26(周一)首次运行只记录不阻尼,10-27 起才产生实际加分保护,滚动 5 日配额要到 10-29~10-30 才形成完整窗口。

它能解决什么、不能解决什么

结论:它解决的是"进不进 TopN",不解决本文说的"权重每天跳"和"信号频率错配"。

8.2 周中再平衡器的换手上限(已存在)

位置:midweek_rebalancer.py。

这是现成的「换手预算」参考实现,可直接借鉴其阈值结构,不必另起一套。

---

9. 候选整改方案(全部「未实施」,待确认)

按优先级排列,P0 只观测不改逻辑,确认问题量级后再逐层动。

优先级 方案 做什么 风险 依赖
:---:--------------------------
P0 观测层 新增 §7.1 四个字段 + 页面展示「品种年龄 / 信号年龄」两列(只读,不改任何权重) 无 无
P1 进出迟滞(hysteresis) 进入 Top10 用 rank<=10,但退出用 rank>13(或 rank>15);留 3~5 名缓冲带 低;会略微降低"及时性" P0 数据先看分布
P2 权重 EMA 平滑 w_t = α·w_target + (1-α)·w_{t-1},α 取 0.2~0.3;对应"半衰期 2~3 期" 中;会滞后于真实信号 P1 验证后
P3 换手预算 每日 ΣΔw/2 ≤ 10%(对比当前均值 19%、峰值 50.8%),超出部分顺延次日 中;极端行情可能跟不上 复用 midweek_rebalancer 阈值结构
P4 择时确认制 锚点信号翻转必须连续 2 日确认才生效(QQQ 6 天翻一次,噪声占大头) 中高;可能错过快速反转 需回测确认
P5 池子/仓位分层解耦 截面负责"买什么",择时负责"买多少",两者不在同一张表上相乘 高;属于架构级改动 需要单独设计文档

建议推进顺序:P0 → 数据观察 4~8 周 → P1 + P3(低风险组合)→ 回测验证 → 再议 P2/P4/P5。

> ⚠️ 禁止在 P0 观测数据出来之前直接上 P2/P4 —— 参数(α、确认天数)没有数据支撑就是拍脑袋。

---

10. 回测验证口径(做任何整改前必须先定义)

对比 A/B/C/D 四组,同一段区间、同一套 ETF 池、同一执行假设(周一开盘价):

组 配置
:--:------
A 现状(每日重算,无任何平滑)
B A + 进出迟滞(P1)
C B + 权重 EMA α=0.25(P2)
D C + 每日换手上限 10%(P3)

必须看的指标:

指标 为什么看
---------------
年化单边换手 直接对应成本
成本后年化收益 摩擦吃掉多少超额
夏普 / Calmar 平滑是否改善了风险调整收益
最大回撤 平滑会不会在市场急跌时"迟钝"
组合成员半衰期 一个品种平均待多久(周)
20 日双边打脸率 买入后 20 日内反向卖出的比例,本文问题的直接度量
品种年龄分布 是否还大量出现"1~3 天就换掉"

> 只看"成本后收益"会误判:平滑可能在降低换手的同时也降低了动量捕捉能力,必须同时看回撤和打脸率。

---

11. 与既有文档的关系

文档 关系
------------
04-选基择时融合方案 04 讲的是「仓位总量怎么映射」(单宏观 / 大类映射 / 波动率目标 / BL);本文讲的是「映射之后权重怎么不抖」。04 是"定水位",本文是"防水位每天忽高忽低"。
06-仓位满仓化与择时联动方案 06 解决了"目标 95% 仓位 + 动态递补",本文发现递补过程本身就在制造高频换手;06 的五层过滤提供了"哪些品种可以跳过",可作为本文 P1 迟滞规则的输入。
09-市场状态平滑过渡 09 平滑的是"天气判断"(regime 标签),本文平滑的是"选品与信号"。同一类问题的两个层次,方法论可互相借鉴(滞回 / 连续化 / 标签滞后 ≈ 本文的迟滞与 EMA)。
03-约束自己 铁律第 1 条「周一调仓」是本文的执行基准。若网页继续展示日频虚拟组合,这条铁律迟早被击穿 —— 这也是本文要提出「展示口径」改造的原因。

---

12. 未决问题(需要拍板)

- 方案 a:展示"上周五定稿 → 本周执行"的实盘口径,日频变化不展示(推荐); - 方案 b:继续展示日频虚拟组合,但加显眼提示"这不是你的真实持仓"; - 方案 c:两种都展示,加 tab 切换。

▶ 📜 变更历史(2 次提交)

7fe2a3e2026-10-24 12:37docs(selection): 补充惯性层 v4 从未生效的实证 + 排名Top10与组合成员换手对比表
4ea80e62026-10-23 22:19feat: 文档收藏(⭐置顶)+ 新增《截面选品与择时信号双时钟错配与持仓年龄》文档
🍃quant 知识库
by 伊甸园和枫

🔊 播放设置

勾选音源后调节配比,总和自动归一化
已选 0 个 · 合计:0%
合成中...
00:00/00:00
1.0x

分享图片

生成中...