← 文档库
📄 文档learn/择时策略/2026-08-11-牛熊判断与牛市仓位优化探讨.md
🌱 创建于2026-08-20 10:30
✏️ 修改于2026-08-20 10:30
👁️ 浏览量—
🔗 短链接/~learn/s/acfb2820
🔐 可见性

牛熊判断与牛市仓位优化 · 探讨文档(v3)

> 2026-08-11 v3:所有流程/状态/架构图改用 Mermaid 渲染,替代 ASCII 模拟图。 > > 2026-08-11 v2:修正事实性错误、补齐 v1 逻辑漏洞、加入新思路(策略共识度、相对强度、ATR 自适应阈值)、调整仓位合成公式、明确再进入规则、细化落地顺序与验证方案。 > > 2026-08-11 v1:初版提出三层体系框架。 > > 起因:实盘复盘报告(learn/投资复盘/2026-08-11-择时策略实盘复盘.html)指出「牛市踏空是最大短板,建议牛市放宽仓位上限」,用户提出两个尖锐质疑: > > 1. 怎么判断牛熊? 单看 60 日均线不靠谱——震荡市会在均线上下反复穿越,收益反而被磨掉。 > 2. 牛市满仓 vs 牛市末期回撤:回撤最大的时刻往往就是牛市末期。如果牛市满仓,低回撤特质不就被抵消了吗? > > 本文正面拆解这两个问题,提出「趋势确认 × 估值刹车 × 回落保护」三层体系作为改进方向,供探讨。

---

一、先承认:这两个质疑都成立

1.1 质疑 1 成立:单均线判断牛熊 = 经典 whipsaw 陷阱

上一份报告建议「60 日均线之上放宽仓位」,这个建议确实粗糙。它的致命缺陷是:

更深一层的问题(v2 补):当前策略池里其实并非"不知道震荡市"——EnhancedFourMAStrategy 已经内置了 ADX 三态分类(trend/range/extreme),并且把 range 态仓位压到 0.4~0.6。但组合层的 position 是 30+ 个策略 stock_value_ratio 的加权平均,不同策略的降仓时机不同步:A 策略觉得是震荡压到 0.4,B 策略觉得是趋势加到 0.8,加权后反而变成 0.6 不上不下,震荡市里依然在被磨。所以层 1 状态机必须加在组合层,不能指望单个策略内部的 ADX 自动生效。

结论:问题不在「要不要在牛市进攻」,而在「用什么信号判断牛市才不会在震荡市被磨」。

1.2 质疑 2 成立:牛市末期恰恰是最大回撤的来源

纯趋势策略有两个固有缺陷,它们正好在牛市末期叠加爆发:

缺陷 表现
------
确认滞后 趋势确认「牛市」时,往往已经走完一大半主升浪
反转滞后 趋势确认「熊市」时,往往已经吃满回撤

而牛市末期的特征(估值极高、情绪亢奋、波动率飙升)恰恰不是趋势指标能捕捉的——趋势指标会说「还在涨」,但估值和波动率已经亮红灯。「牛市就满仓」等于在风险回报比最差的时候暴露最大的仓位。

结论:不能用一个「牛市开关」决定满仓。需要把「该不该参与」和「该参与多深」拆成两个独立问题。

---

二、设计原则:择时的本质不是预测牛熊,而是管理风险暴露

与其问「现在是不是牛市」(一个预测问题,永远答不准),不如问三个递进的问题:

三个问题用三种互相独立的信息源回答:

层 回答的问题 信息源 现状(v2 修正)
------------
层 1 趋势状态机 能不能参与 价格行为(均线/ADX/多周期/策略共识度) EnhancedFourMAStrategy 内部已有 ADX(默认 threshold=25,实际配置覆盖为 20),但不传导到组合层,需在组合层新写
层 2 估值刹车 参与多深 估值分位 + 情绪指标 valuation_fetcher 已有 pe_ttm/pb/pe_pct/pb_pct(完整可用);moneyflow_mkt_dc 接口存在但未在正式策略中使用,需验证;情绪/宽度(breadth)数据暂无模块,需新建数据管道
层 3 回落保护 何时强制撤退 波动率 + 净值回撤 + ATR 自适应阈值 VolatilityChannelStrategy 有 ATR 手动实现,但未注册到任何品种(4 个实例均未 add_strategy_to_board);ATR 计算代码可复用,逻辑需新写

任何一层判断错,另外两层都能兜住——这是三层的核心价值。下图展示三层如何形成纵深防御:

flowchart TD subgraph INPUT["市场输入"] P["价格/成交量
OHLCV"] V["估值数据
PE/PB 分位"] M["资金/情绪
moneyflow"] end subgraph L1["🛡️ 层 1 · 趋势状态机
(定性:能不能参与)"] L1A["ADX 趋势强度"] L1B["MA60 + 斜率 + 周线"] L1C["策略共识度"] L1D["相对强度 RS"] L1O{"状态?"} L1A --> L1O L1B --> L1O L1C --> L1O L1D --> L1O end subgraph L2["🛡️ 层 2 · 估值刹车
(定量:参与多深)"] L2A["PE/PB 5年分位档"] L2B["品种类型适配
(成长用PE/周期用PB)"] L2O["仓位上限
20%~100%"] L2A --> L2O L2B --> L2A end subgraph L3["🛡️ 层 3 · 回落保护
(兜底:何时撤退)"] L3A["指数ATR止损
(双触发)"] L3B["策略净值硬止损"] L3C["波动率目标调整"] L3O["强制减仓/清仓"] L3A --> L3O L3B --> L3O L3C -.->|收紧阈值| L3A end P --> L1A P --> L1B P --> L3A P --> L3B P --> L3C V --> L2A M -.->|未来增强| L2 L1O -->|"进攻(1.0)
中性(0.25)
防御(0.05)"| BASE["基础仓位"] L2O -->|"上限取min"| CAP["封顶后仓位"] L3O -->|"事件驱动砍仓"| FINAL["🎯 最终仓位"] BASE --> CAP CAP --> FINAL style L1 fill:#1a3a5c,stroke:#3b82f6,color:#fff style L2 fill:#3a2a1a,stroke:#f59e0b,color:#fff style L3 fill:#3a1a1a,stroke:#ef4444,color:#fff style FINAL fill:#1a3a1a,stroke:#22c55e,color:#fff

---

三、层 1:趋势状态机 —— 解决「震荡市被磨」

核心思路:给系统加一个「中立/无效」状态

双状态模型的问题是没有「我不知道」这个选项。改成三态状态机——进入进攻态要闯 5 道门槛,退出只要满足两个条件且有缓冲带:

stateDiagram-v2 direction TB [*] --> 震荡中性态 震荡中性态 --> 进攻态 : 5条件同时满足
① ADX ≥ 20
② 价格>MA60 且 MA60上行≥3日
③ 周收盘 > 周MA20
④ 策略共识度 ≥ 70%
⑤ RS跨品种排名 ≥ 50% 进攻态 --> 防御态 : 双条件同时满足
① 价格跌破 MA60×(1−缓冲带)
② MA60斜率拐头向下
缓冲带=max(3%,2ATR/close) 防御态 --> 进攻态 : 再次满足进攻态
全部5个条件 进攻态 --> 震荡中性态 : ADX<20 或
共识度<50% 或
周线破位 防御态 --> 震荡中性态 : 下跌趋势衰竭
(反弹但未达进攻条件) note right of 震荡中性态 默认状态 仓位: 20~30% 底仓 + 货基 end note note right of 进攻态 牛市确认 仓位: L2估值档封顶 (60%~100%) end note note right of 防御态 熊市确认 仓位: 0~10% end note

五个关键机制:

- 进入进攻态要同时满足 5 个条件(全部成立才进,门槛高); - 退出进攻态要同时满足"价格跌破 + MA60 斜率拐头"两个条件; - 缓冲带宽度自适应:缓冲带 = max(3%, 2 × ATR(20)/close)——高波动时自动加宽,低波动时缩窄,避免固定百分比在不同品种/不同时期失灵; - 任何状态切换都要持续 N=3 日确认,单日穿越不算数。

`` 策略共识度 = stock_value_ratio > 0.8 的策略权重之和 ` 30+ 个策略来源多样(均线/量价/RSI/动量/日历/布林带),它们一致看多的时候大概率是真趋势,分歧大的时候大概率是震荡。这是纯粹利用策略多样性本身做市场状态探测,不依赖外部数据。共识度 < 50% 时即使 ADX>20 也强制留在中性态;共识度 ≥ 70% 才允许进进攻态。

  • 相对强度(RS)过滤(v2 新增):实盘复盘数据表明,真正跑输的品种(创业板、科创50、QQQ)都是过去一年最强的品种——不是说它们不该加仓,而是对相对强度弱的品种(60日涨幅在所有品种里排名后 50%),即使自身均线多头排列,也先维持中性态,等它自己证明"跑赢了"再进。避免在"假突破"里加仓。

> 直接回应质疑 1:震荡市不是「在均线上下来回磨」,而是被 ADX + 共识度双重判为「无效态」锁在低仓位;即使偶尔误判,滞回缓冲带(自适应宽度)+ 持续 N 日确认 + MA 斜率条件也保证不会来回切换。磨耗的来源(高频方向切换)被结构性消除。

---

四、层 2:估值刹车 —— 解决「牛市末期裸奔」

核心思路:满仓只在「趋势确认 + 估值不贵」同时成立时发生

牛市末期 ≠ 趋势信号错,而是「趋势仍在、但风险回报比已极差」。所以用估值分位把进攻态切成若干仓位档:

趋势状态 估值分位 目标仓位上限
---------
进攻态 PE/PB 分位均 < 30%(低估) 100% 满仓
进攻态 30% ~ 60% 80%
进攻态 60% ~ 80% 60%
进攻态 80% ~ 95%(牛市后期) 40%
进攻态 > 95%(极端高估) 20% 底仓
震荡/无效态 任意 20~30% 底仓
防御态 任意 0~10%

下面这张图展示了仓位如何随估值分位自动阶梯下降(牛市从左下往右上走,仓位自动收缩):

flowchart LR subgraph 估值分位轴["估值分位(PE/PB 5年滚动)"] direction LR V1["< 30%
🟢 低估"] --- V2["30-60%
🟢 合理"] --- V3["60-80%
🟡 偏高"] --- V4["80-95%
🟠 高估"] --- V5["> 95%
🔴 极端"] end subgraph 仓位轴["仓位上限"] direction LR P1["100%
满仓"] --- P2["80%"] --- P3["60%"] --- P4["40%"] --- P5["20%
底仓"] end V1 --> P1 V2 --> P2 V3 --> P3 V4 --> P4 V5 --> P5 style V1 fill:#166534,color:#fff style V2 fill:#166534,color:#fff style V3 fill:#854d0e,color:#fff style V4 fill:#9a3412,color:#fff style V5 fill:#991b1b,color:#fff style P1 fill:#166534,color:#fff style P2 fill:#166534,color:#fff style P3 fill:#854d0e,color:#fff style P4 fill:#9a3412,color:#fff style P5 fill:#991b1b,color:#fff

v2 修正:估值分位的口径问题

v1 笼统说"PE_TTM 历史分位",在 A 股实践里有几个坑:

  • 全历史分位被早期极端值绑架:创业板 2015 年 PE 到过 120 倍,用全历史分位永远"低估"。→ 用 5 年滚动分位。
  • 不同品种适用不同估值指标:
flowchart TD START["输入品种代码"] --> TYPE{"品种类型?"} TYPE -->|宽基/成长
创业板/科创50/沪深300/中证500| USE_PE["使用 PE_TTM
5年滚动分位"] TYPE -->|金融/周期/红利
红利ETF/银行/证券/煤炭/上证50| USE_PB["使用 PB
5年滚动分位
(PE在周期顶反指)"] TYPE -->|黄金 GLD| NO_GOLD["❌ 禁用层2
(黄金无盈利概念)"] TYPE -->|海外ETF
QQQ等| NO_QQQ["⏸️ 暂禁用层2
(等数据管道补全)"] USE_PE --> CROSS{"PE与PB
分位是否一致?"} USE_PB --> CROSS CROSS -->|一致| GEAR_OK["按该档位定上限"] CROSS -->|分歧
(PE高/PB低 或反之)| GEAR_CONS["取更紧档位
(保守原则)"] GEAR_OK --> SPECIAL{"是否红利类?"} GEAR_CONS --> SPECIAL SPECIAL -->|是| RELAX["放宽上限到80%
(不让L2压制已验证策略)"] SPECIAL -->|否| FINAL_GEAR["✅ 输出档位上限"] RELAX --> FINAL_GEAR NO_GOLD --> BYPASS["层2不封顶
(仅靠L1+L3)"] NO_QQQ --> BYPASS style NO_GOLD fill:#991b1b,color:#fff style NO_QQQ fill:#854d0e,color:#fff style FINAL_GEAR fill:#166534,color:#fff
  • PE 与 PB 做交叉验证:PE 和 PB 分位都落在该档位时才判定;两者分歧(如 PE 高估、PB 低估)取保守档位(用更紧的那个)。
  • 估值刹车不是绝对值硬顶:红利类品种 PE 常年在 5~7 倍波动,分位对它几乎没区分度,这类品种 L2 上限放宽到 80%(不让 L2 把红利的优秀策略仓位压掉——实盘复盘里红利现有策略已经做到+6.28%收益、夏普 1.06、回撤减半,L2 不该干预)。

情绪指标增强(待数据管道就位后启用)

用成交额/换手率/两融余额的历史分位,> 90% 时强制再降一档(牛市顶部情绪指标通常比估值更极端)。当前状态:moneyflow_mkt_dc 接口存在但未经验证,市场广度(涨跌家数/advance-decline)模块不存在,需要新建数据管道,v2 阶段不纳入实现,先留接口。

> 直接回应质疑 2(前半):满仓不是「牛市就满仓」,而是「牛市 + 便宜 + 有共识」才满仓。牛市末期估值高位时层 2 已经自动把仓位压到 40% 以下,回撤的暴露被提前削减。

---

五、层 3:回落保护 —— 兜底「前两层判断错了怎么办」

估值刹车不是万能的(A 股泡沫可以持续数月、估值 >95% 后还能再涨 20%)。所以需要第三层只靠价格行为、不靠任何预测的兜底。

Trailing Stop 触发与再进入流程

trailing stop 是一个事件驱动的子状态机——触发后独立于主状态机运行,直到恢复条件满足才复位:

stateDiagram-v2 direction TB [*] --> 持仓正常 持仓正常 --> 指数减半 : 指数从60日高点回撤
> k_atr × ATR(20)
且 MA60拐头向下 持仓正常 --> 净值减半 : 策略净值从历史高点
回撤 > 8% 持仓正常 --> 清仓止损 : 策略净值从历史高点
回撤 > 15% 指数减半 --> 清仓止损 : 指数继续回撤
> 2×减半阈值 净值减半 --> 清仓止损 : 策略净值继续回撤
> 15% 指数减半 --> 持仓正常 : 收复近20日高点
或连续3日收盘
高于触发价 净值减半 --> 持仓正常 : 策略净值从触发点
反弹 > 3% 清仓止损 --> 分阶段建仓 : L1重新满足进攻态
全部5个条件
且L2估值≤95%分位 分阶段建仓 --> 持仓正常 : D1: 30%
D2: 60%
D3: 100% note right of 持仓正常 仓位 = L1×L2上限 正常跟踪 end note note left of 指数减半 仓位 × 0.5 k_atr按估值档自适应: 低估=5, 中估=4, 偏高=3 高估=2, 极高=1.5 高波动时k_atr再-1 end note note right of 净值减半 仓位 × 0.5 慢熊阴跌兜底 end note note left of 清仓止损 仓位 → 0~10% 切回防御态 end note

其中 k_atr 按估值档位动态调整(v1 用固定百分比,v2 改用 ATR 单位自适应):

层 2 估值档位 k_atr(指数回撤多少倍 ATR 触发减半) 含义
---------
低估(<30%) 5 宽松,牛市初期允许大回调
中估(30~60%) 4 正常
偏高(60~80%) 3 警惕
高估(80~95%) 2 紧
极高(>95%) 1.5 极紧,稍有风吹草动就减半

为什么用 ATR 单位而不是固定百分比:

  • 创业板日波动 ±3% 很常见,固定 8% = 2~3 天;
  • 红利低波日波动 ±0.5%,固定 8% = 3~4 周;
  • 用 ATR 单位后阈值天然跨品种、跨时段自适应,不需要为每个品种单独调参。

为什么加策略净值硬止损:即使指数没跌多少(可能是慢熊阴跌),如果仓位管理不当导致策略净值回撤已经很大,也要强制砍仓——这是对"层 1、层 2 完全失灵"场景的最后兜底。

波动率目标仓位(Volatility Targeting)—— 自适应降杠杆(保留,作为可选增强)

vol_adj = 目标年化波动率(默认 12%) / max(当前实现波动率, 8%)
实现波动率 = 近 20 日收益率标准差 × √252

v2 明确:vol targeting 作为 L3 的"辅助系数",不直接乘到最终仓位里,而是用来收紧 trailing stop 的阈值——波动率高时自动把 k_atr 再减 1(相当于更早触发保护)。这样避免 vol targeting 本身和 L2 乘法叠加导致仓位过低。

宽度(Breadth)预警(推迟到数据管道就绪后)

  • 跟踪"组合内下跌品种数量占比"或"全市场涨跌家数比",普跌但指数还撑着(权重托底)时预警降仓;
  • 牛市末期典型的"指数新高、多数个股已破位"结构,宽度指标能提前 1~2 周提示。
  • v2 阶段不实现(需要新建数据管道),先记录为后续增强项。

> 直接回应质疑 2(后半):即使层 1、层 2 全部判断错(满仓在顶部),层 3 的双触发 trailing stop + 硬止损保证最大回撤被限制在可控区间——「低回撤」特质不依赖「判断对」,而依赖「跌了就撤」的机械规则,并且有明确的"什么时候回来"规则避免永久踏空。

---

六、三层协同:最终仓位怎么算(v2 改公式)

v1 公式的问题(乘法过于保守)

v1 公式 最终仓位 = L1 × L2 × L3 会出现"多约束叠加导致仓位过低"的问题:

  • 牛市初期波动率稍高时,L1=1.0 × L2=1.0 × L3=0.7 = 70%,错过最干净的主升浪起点;
  • 熊市末期估值低位时,L1=0(防御态)直接锁死,但这个时候恰恰最该加仓——不过 L1=0 是主开关,这个是对的;
  • L2 和 L3 都在向下约束,乘法会"双杀"。

v2 公式:主开关 + 单向 min 约束 + 事件驱动

仓位计算是一个管线(pipeline),而不是简单的乘法:

flowchart LR A["各策略独立
generate_signals()"] --> B["加权求和
calculate_average_holding_ratio()"] B --> C["原始 position%
(现有系统输出)"] C --> D["L1 主开关
× 状态系数
进攻=1.0
中性=0.25
防御=0.05"] D --> E["× 共识度微调
0.9~1.0"] E --> F["× RS微调
0.9~1.0"] F --> G["基础仓位"] G --> H{"取 min 封顶"} I["L2 估值档位上限
20%~100%"] --> H J["L3 vol_adj
(辅助上限)"] --> H H --> K["封顶后仓位"] K --> L{"L3 trailing
事件触发?"} L -->|无触发| M["✅ 最终仓位"] L -->|减半| N["× 0.5"] L -->|清仓| O["→ 0~10%"] N --> M O --> M style C fill:#1e3a5f,stroke:#3b82f6,color:#fff style G fill:#1a3a1a,stroke:#22c55e,color:#fff style M fill:#14532d,stroke:#22c55e,color:#fff,stroke-width:3px

具体逻辑:

  • L1 是主开关:防御态直接 0~10%,震荡态 25%,进攻态 100%;共识度和 RS 对进攻态仓位做 ±10% 的微调(不做大调整);
  • L2 和 L3 只做"向下封顶":取两者中更紧的那个作为仓位上限,不做乘法叠加;
  • Trailing stop 是事件驱动:不是日常系数,而是触发后临时砍半/清仓,等恢复条件满足再复位。

典型场景的仓位路径

下图展示 8 个典型场景在管线中最终落到多少仓位(节点标签上的数字是该场景下该环节的仓位值):

flowchart TB subgraph 牛市初期["🟢 牛市初期
低估+刚确认+共识高"] D1["L1=1.0"] --> G1["基础=1.0"] G1 --> H1["min(1.0,1.0)=1.0"] H1 --> R1["最终=100%"] end subgraph 牛市中期["🟢 牛市中期
估值50%"] D2["L1=1.0"] --> G2["基础=1.0"] G2 --> H2["min(0.8,1.0)=0.8"] H2 --> R2["最终=80%"] end subgraph 牛市末期涨["🟡 牛市末期
高估+还涨"] D3["L1=1.0"] --> G3["基础=1.0"] G3 --> H3["min(0.4,0.8)=0.4"] H3 --> R3["最终=40%"] end subgraph 牛市末期跌["🔴 牛市末期
高位+开始跌"] D4["L1=1.0"] --> G4["基础=1.0"] G4 --> H4["min(0.4,0.7)=0.4"] H4 --> T4["trailing减半"] T4 --> R4["最终=20%"] end subgraph 震荡市["⚪ 震荡市
ADX<20"] D5["L1=0.25"] --> G5["基础=0.25"] G5 --> H5["L1锁死→0.25"] H5 --> R5["最终=25%"] end subgraph 熊市["🔴 熊市确认"] D6["L1=0.05"] --> G6["基础=0.05"] G6 --> H6["L1锁死→0.05"] H6 --> R6["最终=5%"] end subgraph 熊市末期反弹["🟡 熊市末期反弹
估值低但趋势未确认"] D7["L1=0.25"] --> G7["基础=0.25"] G7 --> H7["L1锁死→0.25
(L2想给100%也没用)"] H7 --> R7["最终=25%"] end subgraph 回调恢复["🟢 回调恢复
减过仓+趋势还在"] D8["L1=1.0"] --> G8["基础=1.0"] G8 --> H8["min(0.8,1.0)=0.8"] H8 --> T8["trailing已恢复"] T8 --> R8["最终=80%"] end style R1 fill:#166534,color:#fff style R2 fill:#166534,color:#fff style R3 fill:#854d0e,color:#fff style R4 fill:#991b1b,color:#fff style R5 fill:#4b5563,color:#fff style R6 fill:#991b1b,color:#fff style R7 fill:#854d0e,color:#fff style R8 fill:#166534,color:#fff

互锁关系(任一层的误判都能被兜住):

flowchart LR E1["层1误判:
把震荡当牛市"] --> F1["→ 层2兜底:
震荡市估值通常不极端
但共识度<70%直接挡在门外"] E2["层2失灵:
高估后泡沫继续
迟迟不降仓"] --> F2["→ 层3兜底:
双触发trailing stop
硬止损砍仓"] E3["层3误触发:
正常回调触发减半"] --> F3["→ 再进入规则兜底:
收复20日高点后
按明确条件加回"] E4["层1反应慢:
V型反转来不及切防御"] --> F4["→ 层3兜底:
策略净值8%硬止损
不等L1切换就减仓"] style E1 fill:#991b1b,color:#fff style E2 fill:#991b1b,color:#fff style E3 fill:#991b1b,color:#fff style E4 fill:#991b1b,color:#fff style F1 fill:#166534,color:#fff style F2 fill:#166534,color:#fff style F3 fill:#166534,color:#fff style F4 fill:#166534,color:#fff

---

七、与现有系统怎么接(不推翻,只加一层壳)

三层体系不需要重写策略池,而是在现有「多策略加权择时」之上加一个组合级仓位调节器——是一个纯 overlay,不侵入现有策略代码:

flowchart LR subgraph 现有系统["现有系统(不动)"] S1["timing_config.py
注册30+策略"] --> S2["load_mult_market_strategy()"] S2 --> S3["calculate_portfolio_metrics()
逐策略generate_signals"] S3 --> S4["calculate_average_holding_ratio()"] S4 --> S5["原始 position%"] end subgraph 新增overlay["🆕 组合层仓位调节器(新增)"] O1["L1 状态机
layer1_trend_state.py"] O2["L2 估值档位
layer2_valuation_brake.py"] O3["L3 回落保护
layer3_drawdown_protect.py"] O4["合成最终仓位
position_overlay.py"] O1 --> O4 O2 --> O4 O3 --> O4 end S5 -->|"输入"| O4 O4 --> FINAL["🎯 最终 position%
→ 持仓建议/推送"] subgraph 数据源["外部数据(新增依赖)"] D1["valuation_fetcher
(pe_ttm/pb)"] D2["跨品种60日涨幅
(show_marker主循环一次算完)"] D3["历史高点跟踪
(在overlay内部维护)"] end D1 --> O2 D2 --> O1 D3 --> O3 style 新增overlay fill:#1a3a1a,stroke:#22c55e,color:#fff style 现有系统 fill:#1a1a2e,stroke:#6366f1,color:#fff style FINAL fill:#14532d,stroke:#22c55e,color:#fff,stroke-width:3px

接入点与开关

每层独立开关,便于逐层 A/B:

TIMING_L1_TREND_STATE = False      # 层1趋势状态机
TIMING_L2_VALUATION_BRAKE = False  # 层2估值刹车
TIMING_L3_TRAILING_STOP = False    # 层3回落保护
  • 每层可以独立 A/B;
  • 全 False 时完全等价于现状(零影响);
  • 落地阶段按顺序打开,避免三层同时上出问题无法定位。

文件组织

新建 timing_overlay/ 目录,四个文件各司其职,不往现有大文件塞:

flowchart TD ROOT["adata/aohan/quant/timing_overlay/
(新建目录)"] ROOT --> F1["layer1_trend_state.py
ADX+MA60+周线+共识度+RS"] ROOT --> F2["layer2_valuation_brake.py
PE/PB分位+品种适配"] ROOT --> F3["layer3_drawdown_protect.py
ATR止损+净值止损+再进入"] ROOT --> F4["position_overlay.py
🎯 组合入口
管线调度+状态维护"] style F4 fill:#14532d,stroke:#22c55e,color:#fff,stroke-width:2px

---

八、验证方案(先想清楚怎么证明有效)

Walk-Forward 滚动窗口验证

v1 提"10 年样本内 + 1 年样本外"太粗糙——三层体系有 5~7 个参数,全样本优化必过拟合。必须做 Walk-Forward Analysis:训练集逐年扩展,验证集始终是训练集之后未见过的一年,共 7 个窗口:

gantt title Walk-Forward 滚动窗口验证(2015-2026) dateFormat YYYY-MM-DD axisFormat %Y tickInterval 1year section 窗口1 训练 2015-2018 :t1, 2015-01-01, 1460d 验证 2019 :v1, after t1, 365d section 窗口2 训练 2015-2019 :t2, 2015-01-01, 1825d 验证 2020 :v2, after t2, 365d section 窗口3 训练 2015-2020 :t3, 2015-01-01, 2190d 验证 2021 :v3, after t3, 365d section 窗口4 训练 2015-2021 :t4, 2015-01-01, 2555d 验证 2022 (熊市) :v4, after t4, 365d section 窗口5 训练 2015-2022 :t5, 2015-01-01, 2920d 验证 2023 (震荡) :v5, after t5, 365d section 窗口6 训练 2015-2023 :t6, 2015-01-01, 3285d 验证 2024 (震荡) :v6, after t6, 365d section 窗口7 训练 2015-2024 :t7, 2015-01-01, 3650d 验证 2025-2026 (牛市):v7, after t7, 580d section 市场周期 2019-2021 牛市 :milestone, 2019-01-01, 1095d 2022 熊市 :crit, 2022-01-01, 365d 2023-2024 震荡 :active, 2023-01-01, 730d 2025-2026 牛市 :milestone, 2025-01-01, 580d

通过标准:

  • 每个窗口选出来的参数稳定性好(参数值不跳来跳去);
  • 每个窗口的样本外表现方向一致(不能这个窗口好那个窗口差);
  • 最终用最后一个窗口的参数做全周期展示,但调参依据是样本外表现的稳定性,不是全样本总收益。

对比指标

对比指标 现状(基准,复盘实测) 三层体系目标
---------
牛市品种(10个)超额收益 平均 -15~-20pp(严重踏空) 收敛到 -5pp 以内(基本跟上)
震荡/下跌品种(13个)超额收益 为正(已经跑赢) 保持为正,不恶化
最大回撤(中位数比大盘) 0.42× 保持 ≤ 现状(不能为了加仓牺牲回撤)
夏普比率 红利1.06/其他品种0~0.5 牛市品种夏普翻倍,其他品种不下降
状态切换次数/年 高频(量化待统计) 下降 60%+(震荡市不被磨的核心实证)
牛市参与度(牛市期平均仓位) ~40% 提升到 60~80%
触发 trailing stop 后 30 日内恢复率 (新指标) > 70%(避免"一次减仓永久踏空")

核心筛选指标(不是总收益):

  • 择时增值(复盘报告里的定义:策略收益 − 大盘收益 × 平均仓位)—— 这个指标衡量"仓位管理有没有创造纯 alpha",比总收益更能区分"运气"和"能力";
  • 跨周期稳定性(每个 Walk-Forward 窗口的择时增值都为正)。

---

九、落地顺序(三阶段渐进)

考虑到数据依赖和调试复杂度,不一次上三层,按依赖关系和风险递增排列:

flowchart LR subgraph 阶段1["🟦 阶段一 · L1+L3
纯价格数据,零外部依赖"] A1["L1: 组合层状态机
ADX+MA60+周线+缓冲+共识度"] A2["L3: 双触发trailing stop
ATR阈值+硬止损+再进入"] A3["⚠️ RS暂不加
(需跨品种循环改造)"] A4["L2 关闭"] A1 --> A2 end subgraph 阶段2["🟨 阶段二 · 加入L2
启用估值数据"] B1["接入 valuation_fetcher"] B2["PE/PB分位+品种类型适配"] B3["GLD/QQQ 先禁用L2"] B4["红利类放宽上限到80%"] B1 --> B2 --> B3 B2 --> B4 end subgraph 阶段3["🟩 阶段三 · 增强项
需要新建数据管道"] C1["加跨品种RS过滤"] C2["情绪/宽度数据管道
(moneyflow验证+涨跌家数)"] C3["Breadth预警"] C4["QQQ海外ETF估值补全"] end 阶段1 ==>|"Walk-Forward验证
切换次数↓ 回撤持平"| 阶段2 阶段2 ==>|"牛市超额收敛到-5pp"| 阶段3 style 阶段1 fill:#1a365d,stroke:#3b82f6,color:#fff style 阶段2 fill:#451a03,stroke:#f59e0b,color:#fff style 阶段3 fill:#14532d,stroke:#22c55e,color:#fff

阶段一:先做层 1 + 层 3(纯价格数据,零外部依赖)

  • 层 1:组合层 ADX + MA60 斜率 + 周线确认 + 滞回缓冲(自适应)+ 策略共识度。相对强度 RS 暂不加(需要跨品种循环改造)。
  • 层 3:双触发 trailing stop(指数 ATR 单位 + 策略净值硬止损)+ 再进入规则。vol targeting 做辅助(收紧阈值),不直接乘仓位。
  • 层 2 关闭。
  • 预计收益:状态切换次数下降 60%+ ,震荡市磨损减少;trailing stop 兜底最大回撤。
  • 验证方法:Walk-Forward 窗口 1~7,主要看状态切换次数和最大回撤。

阶段二:加入层 2(启用估值数据)

阶段三:增强项

---

十、开放问题

mindmap root((待决策问题)) 中性态底仓水平 20% 30% 货基替代
(511880银华日利) 共识度阈值 70% 是否过高
(牛市初期共识未形成) 软门槛方案
>60%打0.8折扣
>70%满1.0 估值分位口径 5年窗口是否合适 是否做显式品种映射表 波动率目标 12%年化是否合适 A股宽基波动20~25%
→平均仓位50~60% 止损后减仓幅度 减半? 直接砍到底仓? 估值极高档是否更激进 RS排名基准集 全部23个品种一起排 按类别分组排
(宽基/行业/海外)

---

附:一句话总结

> v1 的错误是把「牛市」当成一个开关(是/否),公式是简单乘法,缺了再进入规则,并且把部分实验性/不存在的组件当成已有。 > > v2 把三层改成「L1 主开关(带共识度+RS+自适应缓冲)+ L2/L3 单向 min 约束 + 事件驱动 trailing stop」,补全了再进入规则,修正了组件存在性事实错误,给出了三阶段落地路径和 Walk-Forward 验证方案。 > > v3 所有架构、流程、状态、计划全部改为 Mermaid 图表渲染。 > > 核心思想不变:不再追求「预测对牛熊」,而是追求「任何市场状态下,风险暴露都是对的」——并且任何一层错了,其他层能兜住。

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

e34e9f82026-08-20 10:30docs: 提交learn目录新增文档
🍃quant 知识库
by 伊甸园和枫

🔊 播放设置

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

分享图片

生成中...