第 6 章 · 03 高窄旗形:强动量后的极致收敛


文档摘要

第 6 章 · 03 高窄旗形:强动量后的极致收敛 本节摘要:本节精读 Sequoia-X 的「形态结构类」开篇策略——高窄旗形(High Tight Flag)。这是 William O'Neil(欧奈尔)在《笑傲股市》中提出的经典中继形态:「先大涨、再横盘、再放量突破」的「旗形」组合。具体到 A 股改良版,Sequoia-X 用四个条件刻画这种形态:40 日涨幅 ≥ 60%(强动量)+ 近 10 日振幅 30% | 振幅过大,仍在剧烈洗盘,不算「窄」 20%30% | 普通横盘,入选价值低 1.0 | 量能正常或放量,还在洗盘中 0.61.0 | 略缩,入选价值低 < 0.6 | 明显缩量,蓄势信号强 ✅ 注意 这个细节:不含当日的 20 日均量。

第 6 章 · 03 高窄旗形:强动量后的极致收敛

本节摘要:本节精读 Sequoia-X 的「形态结构类」开篇策略——高窄旗形(High Tight Flag)。这是 William O'Neil(欧奈尔)在《笑傲股市》中提出的经典中继形态:「先大涨、再横盘、再放量突破」的「旗形」组合。具体到 A 股改良版,Sequoia-X 用四个条件刻画这种形态:40 日涨幅 ≥ 60%(强动量)+ 近 10 日振幅 < 15%(极致收敛)+ 近 10 日不破 40 日最高点 80%(高位抗跌)+ 当日缩量(蓄势待发)。这套策略单日出票少而精(全市场通常 5~15 只),信号质量在所有策略中数一数二。本节会讲清旗形的形态逻辑、四个条件的工程含义、Sequoia-X 的实现细节,最后用一段可运行的代码让你亲手验证。

内容来源:原项目高窄旗形策略模块精读 + 欧奈尔《笑傲股市》公开文献。

⚠️ 风险提示:高窄旗形是「少而精」的信号,但配合大市判断更可靠——熊市中的旗形常是「下跌中继」。本节仅用于教学。

学习目标

阅读完本节,你应当能够:

  1. 用图示讲清「高窄旗形」的典型形态
  2. 解释四个条件的工程含义:强动量 + 极致收敛 + 高位抗跌 + 缩量。
  3. 读懂 Sequoia-X 的向量化实现细节。
  4. 用一段可运行的代码验证旗形识别。

一、什么是高窄旗形

高窄旗形(High Tight Flag)是欧奈尔在《笑傲股市》(How to Make Money in Stocks)中提出的经典中继形态

阶段 1:旗杆(强动量) 股价在 8 周内涨幅 ≥ 60%~100% ——「**旗杆**」快速拉升 │ ▼ 阶段 2:旗面(极致收敛) 之后股价在 2~3 周内振幅极小(≤ 15%)——「**旗面**」横向整理 │ ▼ 阶段 3:突破 当成交量重新放大、股价向上突破旗面 —— 趋势延续

形象化:

股价 ↑ │ /│ ← 旗杆(强动量 60%+) │ / │ │ / │ │ / ┌──┐ ← 旗面(极致收敛 振幅 15% 内) │ / │ │ │ / │ │ │ / └──┘ ← 突破点 │ └─────────────────────→ 时间 8 周 2~3 周

市场逻辑:第一波快速上涨是主力建仓,然后主力用窄幅震荡洗盘(让短期获利盘下车),蓄势后再拉升」保证动量足够,」保证洗盘充分——两者结合是高胜率的中继形态。

二、Sequoia-X 的四个条件

Sequoia-X 把它翻译成四条可量化的条件

# 条件 1:强动量(过去 40 天区间最高 / 区间最低 > 1.6,即涨幅 60%+) momentum = high40 / low40 > 1.6 # 条件 2:极致收敛(最近 10 天区间最高 / 区间最低 < 1.15,即振幅 < 15%) consolidation = high10 / low10 < 1.15 # 条件 3:高位抗跌(近 10 天最低点不得低于 40 天最高点的 80%) high_level = low10 >= high40 * 0.8 # 条件 4:缩量(今日 volume < 过去 20 日 volume 均值的 0.6 倍) vol_ma20 = df["volume"].iloc[-21:-1].mean() shrink = df["volume"].iloc[-1] < vol_ma20 * 0.6 if momentum and consolidation and high_level and shrink: selected.append(symbol)

条件 1:强动量

high40 = tail40["high"].max() low40 = tail40["low"].min() momentum = high40 / low40 > 1.6

为什么是 1.6?——意味着 40 日内最低到最高至少涨 60%。这是欧奈尔的「最低门槛」——不够 60% 不算「强动量」。

40 日涨幅 评级
< 30% 弱动量,不入选
30%~60% 中等动量,可能入选其他策略
60%+ 强动量,高窄旗形候选
100%+ 极强动量,信号最可靠

条件 2:极致收敛

high10 = tail10["high"].max() low10 = tail10["low"].min() consolidation = high10 / low10 < 1.15

为什么是 1.15?——意味着最近 10 日内最高最低之差小于 15%。这是「极致收敛」——洗盘充分、浮筹少

10 日振幅 评级
> 30% 振幅过大,仍在剧烈洗盘,不算「
20%~30% 普通横盘,入选价值低
< 15% 极致收敛主力蓄势信号

条件 3:高位抗跌

high_level = low10 >= high40 * 0.8

这是 Sequoia-X 在欧奈尔基础上加的一条——保证「旗面没有掉下去。如果最近 10 日最低点已经跌到 40 日最高点的 80% 以下,说明形态已经破坏(旗面下沿破了旗杆中段)——不算「高窄旗形」了。

10 日最低点 / 40 日最高点 含义
≥ 80% 旗面还在高位,形态完整 ✅
70%~80% 形态可疑,勉强入选
< 70% 旗面塌了,不算旗形

条件 4:缩量

vol_ma20 = df["volume"].iloc[-21:-1].mean() # 不含当日的 20 日均量 shrink = df["volume"].iloc[-1] < vol_ma20 * 0.6

为什么是 0.6(缩量 40%)?——旗面洗盘的标志是「量能萎缩」——没有抛压才能蓄势。

今日量 / 20 日均量 含义
> 1.0 量能正常或放量,还在洗盘中
0.6~1.0 略缩,入选价值低
< 0.6 明显缩量蓄势信号强 ✅

注意 df["volume"].iloc[-21:-1] 这个细节:不含当日的 20 日均量。理由和海龟的 shift(1) 类似——今日不应参与「今日 vs 自身」的比较

三、可运行教学代码

下面这段代码用合成数据演示「高窄旗形」的四个条件如何过滤:

# flag_demo.py # 可运行:高窄旗形策略验证 import numpy as np import pandas as pd def detect_high_flag(df: pd.DataFrame) -> tuple[bool, list[str]]: """返回 (是否旗形, 原因列表)""" reasons = [] if len(df) < 40: return False, [f"数据不足 40 根(当前 {len(df)})"] tail40 = df.tail(40) tail10 = df.tail(10) high40 = tail40["high"].max() low40 = tail40["low"].min() high10 = tail10["high"].max() low10 = tail10["low"].min() if low40 == 0 or low10 == 0: return False, ["区间最低为 0,数据异常"] momentum = high40 / low40 > 1.6 consolidation = high10 / low10 < 1.15 high_level = low10 >= high40 * 0.8 vol_ma20 = df["volume"].iloc[-21:-1].mean() shrink = df["volume"].iloc[-1] < vol_ma20 * 0.6 if not momentum: reasons.append(f"动量不足:40日涨 {high40/low40:.2f}(需 > 1.6)") if not consolidation: reasons.append(f"振幅过大:10日 {high10/low10:.2f}(需 < 1.15)") if not high_level: reasons.append(f"形态破坏:10日低/40日高 = {low10/high40:.2f}(需 ≥ 0.8)") if not shrink: reasons.append(f"量能未缩:今日/均量 = {df['volume'].iloc[-1]/vol_ma20:.2f}(需 < 0.6)") return momentum and consolidation and high_level and shrink, reasons def make_flag_case() -> pd.DataFrame: """造一个标准高窄旗形:40 日涨 80% + 10 日振幅 8% + 高位抗跌 + 缩量。""" np.random.seed(1) # 旗杆:前 30 日从 10 元涨到 18 元(涨 80%) flag_pole = np.linspace(10, 18, 30) # 旗面:最近 10 日 17.5~18.0 窄幅震荡(振幅 2.9%) flag_face = np.linspace(18.0, 17.5, 10) close = np.concatenate([flag_pole, flag_face]) # 给每日价格加小幅噪音 close = close + np.random.normal(0, 0.05, len(close)) high = close + 0.15 low = close - 0.15 # 成交量:旗杆期间放量,旗面期间缩量 volume = np.concatenate([ np.ones(20) * 2e6, # 旗杆前半段 np.ones(10) * 3e6, # 旗杆后半段(更放量) np.ones(10) * 0.5e6, # 旗面缩量 ]) return pd.DataFrame({"close": close, "high": high, "low": low, "volume": volume}) def make_non_flag_cases() -> dict[str, pd.DataFrame]: """造 3 个非旗形 case。""" np.random.seed(2) n = 40 return { "动量不足": pd.DataFrame({ "close": np.linspace(10, 11, n), # 40 日只涨 10% "high": np.linspace(10, 11, n) + 0.1, "low": np.linspace(10, 11, n) - 0.1, "volume": np.ones(n) * 1e6, }), "振幅过大": pd.DataFrame({ "close": np.concatenate([np.linspace(10, 18, 30), np.linspace(15, 16, 10)]), "high": np.concatenate([np.linspace(10, 18, 30), np.linspace(15, 17, 10)]), "low": np.concatenate([np.linspace(10, 18, 30), np.linspace(15, 14, 10)]), "volume": np.ones(n) * 1e6, }), "形态破坏(旗面塌了)": pd.DataFrame({ "close": np.concatenate([np.linspace(10, 18, 30), np.linspace(13, 14, 10)]), # 旗面 13~14,< 18*0.8=14.4 "high": np.concatenate([np.linspace(10, 18, 30), np.linspace(13, 14.5, 10)]), "low": np.concatenate([np.linspace(10, 18, 30), np.linspace(12, 13, 10)]), "volume": np.ones(n) * 1e6, }), } if __name__ == "__main__": # 标准旗形 passed, reasons = detect_high_flag(make_flag_case()) print(f"【标准高窄旗形】 {'✅ 入选' if passed else '❌ 不选'}") for r in reasons: print(f" - {r}") print() # 三个非旗形 for name, df in make_non_flag_cases().items(): passed, reasons = detect_high_flag(df) print(f"【{name}】 {'✅ 入选' if passed else '❌ 不选'}") for r in reasons: print(f" - {r}") print()

预期输出:

【标准高窄旗形】 ✅ 入选 【动量不足】 ❌ 不选 - 动量不足:40日涨 1.10(需 > 1.6) 【振幅过大】 ❌ 不选 - 振幅过大:10日 1.21(需 < 1.15) 【形态破坏(旗面塌了)】 ❌ 不选 - 形态破坏:10日低/40日高 = 0.72(需 ≥ 0.8)

四、为什么 Sequoia-X 用 tail() 而不是 rolling()

注意 Sequoia-X 的实现细节:

tail40 = df.tail(40) high40 = tail40["high"].max()

tail(40) 而不是 rolling(40).max()。为什么?

  • tail(40) 取「最近的 40 行」——绝对窗口
  • rolling(40) 是「滚动窗口」——每一行都算自己的前 40 行

对于「过去 40 日区间最高」这个需求,两者结果一样(因为 tail(40).max() == rolling(40).max().iloc[-1]),但 tail() 写法更直观——「我看最近 40 根」。

这是一个代码可读性 vs 一致性的权衡——Sequoia-X 选择可读性

五、边界与失效场景

市况 信号表现
牛市主升浪 信号最可靠——旗面后大概率继续上涨
震荡市 形态易被破坏——旗面常被「假突破下杀」打穿
熊市初跌 旗形」常是「下跌中继——旗面下杀后继续跌
个股黑天鹅 旗面中突发利空,形态直接破坏

高窄旗形是少而精的策略——单日出票少(5~15 只),但信号质量数一数二。配合大市判断更可靠。

六、本节在全景图的位置

高窄旗形 ✅ ← 本节 │ ▼ 下一节(第 04 节)涨停洗盘:另一种形态信号

本节要点回顾

  1. 高窄旗形:旗杆(40 日涨 60%+)+ 旗面(10 日振幅 < 15%)+ 高位抗跌 + 缩量,欧奈尔经典中继形态
  2. 四条件协同:单一条件都会失效,四条件叠加才可靠。
  3. tail() vs rolling():用 tail(40).max() 取最近 40 日最高,更直观
  4. iloc[-21:-1] 排除当日:20 日均量计算时排除当日,避免「自己比自己」。
  5. 适配市况牛市信号最可靠熊市初跌易误判为「下跌中继
  6. 可运行示例:1 个标准旗形 + 3 个非旗形,亲手验证四个条件

配套教学脚本:images/flag_demo.py——可用任意合成数据验证旗形识别。

下一节,我们看另一种「形态结构」——涨停洗盘:昨日涨停后今日放量收阴但不破昨收。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U