Momentum:SQUEEZE(挤压动量指标)技术指标详解
一、SQUEEZE的定义
SQUEEZE(挤压动量指标,全称TTM Squeeze) 是由知名交易员John Carter在其著作《Mastering the Trade》(第11章)中正式推出的一种高级波动率与动量复合指标。该指标通过结合**布林带(Bollinger Bands)与肯特纳通道(Keltner Channels)**的特性,精准捕捉趋势启动前的蓄势阶段,旨在识别波动率收缩之后的爆发性行情。
核心思想
在海啸来临之前,最前端的波浪会短暂退潮,海面看似风平浪静,实则正暗中积蓄力量。挤压动量指标的核心理念正源于此------当市场进入低波动率"挤压"状态时,往往是重大趋势行情爆发的前奏。
具体而言,SQUEEZE指标的判断逻辑基于两类通道的相对位置:
- 布林带:基于标准差构建,波动增强时变宽,波动减弱时收窄
- 肯特纳通道:基于平均真实波幅(ATR)构建,反映价格的实际波动范围
当市场波动率降低至极低水平时,布林带的上下轨会完全收窄至肯特纳通道的内部------此时即为"挤压开启"(Squeeze On)状态,表明多空双方处于短暂均衡,价格随时可能因资金或消息驱动而突破平衡区间,引发强趋势行情。当布林带重新张开并突破肯特纳通道的边界时,说明"挤压解除"(Squeeze Off),市场进入动量释放阶段。
SQUEEZE的核心特征
| 特征 | 说明 |
|---|---|
| 理论基础 | 波动率收缩-扩张周期理论(类似"弹簧压缩"效应) |
| 核心组件 | 布林带 + 肯特纳通道 + 动量震荡柱 |
| 指标类型 | 波动率/动量复合型指标 |
| 开发者 | John Carter(TTM Squeeze) |
| TradingView排名 | 点赞数最多的技术指标 |
SQUEEZE的三大组成部分
SQUEEZE指标由三部分协同构成,分别反映不同维度的市场信息:
| 组成部分 | 名称 | 表现形式 | 功能描述 |
|---|---|---|---|
| SQZ_ON/SQZ_OFF | 挤压状态指示灯 | 零轴线上的红色/绿色圆点 | 红灯(挤压开启)=低波动率蓄势;绿灯(挤压解除)=高波动率爆发 |
| 动量柱(Momentum Histogram) | 挤压动量值 | 围绕零轴波动的柱状图 | 正值=多头动能,负值=空头动能,绝对值越大动能越强 |
| SQZ_NO | 无挤压状态 | 无声区 | 市场处于正常波动,两通道未完全重叠 |
同一根K线只会满足上述三种挤压状态中的一种------SQZ_ON、SQZ_OFF或SQZ_NO在单根K线上最多只有一个为1,其余两个为0。
二、SQUEEZE的计算方法
SQUEEZE的计算逻辑分为三个核心步骤:布林带与肯特纳通道的构建、挤压状态判断、动量值的平滑处理。
1. 布林带(Bollinger Bands)的计算
设周期 n=20n=20n=20,标准差倍数为 k=2k=2k=2:
中轨(SMA线):
BB_MIDt=SMA20(Close)t \mathrm{BB\_MID}t = \mathrm{SMA}{20}(\mathrm{Close})t BB_MIDt=SMA20(Close)t
上轨和下轨:
BB_HIGHt=BB_MIDt+k×σtBB_LOWt=BB_MIDt−k×σt \begin{aligned} \mathrm{BB\_HIGH}_t &= \mathrm{BB\_MID}_t + k \times \sigma_t \\ \mathrm{BB\_LOW}_t &= \mathrm{BB\_MID}_t - k \times \sigma_t \end{aligned} BB_HIGHtBB_LOWt=BB_MIDt+k×σt=BB_MIDt−k×σt
其中 σt\sigma_tσt 为过去20日收盘价的标准差。当市场波动增强时,标准差增大,布林带变宽;波动减弱时,布林带收窄。
2. 肯特纳通道(Keltner Channels)的计算
肯特纳通道以指数移动平均线(EMA)为中轴,基于平均真实波幅(ATR)构建通道宽度:
中轨(EMA线):
KC_MIDt=EMA20(Close)t \mathrm{KC\_MID}t = \mathrm{EMA}{20}(\mathrm{Close})_t KC_MIDt=EMA20(Close)t
首先计算真实波幅(True Range, TR):
TRt=max(Ht−Lt,∣Ht−Ct−1∣,∣Lt−Ct−1∣) \mathrm{TR}t = \max(H_t - L_t, |H_t - C{t-1}|, |L_t - C_{t-1}|) TRt=max(Ht−Lt,∣Ht−Ct−1∣,∣Lt−Ct−1∣)
对 TR\mathrm{TR}TR 取 202020 周期 EMA 得到 ATR 值:
ATR20,t=EMA20(TR)t \mathrm{ATR}{20,t} = \mathrm{EMA}{20}(\mathrm{TR})_t ATR20,t=EMA20(TR)t
上轨和下轨(乘数为 m=1.5m = 1.5m=1.5):
KC_HIGHt=KC_MIDt+m×ATR20,tKC_LOWt=KC_MIDt−m×ATR20,t \begin{aligned} \mathrm{KC\_HIGH}_t &= \mathrm{KC\_MID}t + m \times \mathrm{ATR}{20,t} \\ \mathrm{KC\_LOW}_t &= \mathrm{KC\_MID}t - m \times \mathrm{ATR}{20,t} \end{aligned} KC_HIGHtKC_LOWt=KC_MIDt+m×ATR20,t=KC_MIDt−m×ATR20,t
ATR基于真实波幅计算,不仅考虑了当日的价格波动,也将跳空缺口的剧烈波动纳入衡量范围,因此肯特纳通道对极端价格跳变更为敏感。
3. 挤压状态的数学判定
挤压状态的判定通过比较布林带与肯特纳通道的相对位置实现:
挤压开启(SQZ_ON = 1) :
SQZ_ONt=1{BB_HIGHt<KC_HIGHt∧BB_LOWt>KC_LOWt} \mathrm{SQZ\_ON}t = \mathbf{1}{\{\mathrm{BB\_HIGH}_t < \mathrm{KC\_HIGH}_t \land \mathrm{BB\_LOW}_t > \mathrm{KC\_LOW}_t\}} SQZ_ONt=1{BB_HIGHt<KC_HIGHt∧BB_LOWt>KC_LOWt}
即布林带的上轨低于 肯特纳上轨,且布林带的下轨高于肯特纳下轨时,表明布林带被完全"套在"肯特纳通道内部,波动率处于极低水平。
挤压解除(SQZ_OFF = 1) :
SQZ_OFFt=1{BB_HIGHt>KC_HIGHt∨BB_LOWt<KC_LOWt} \mathrm{SQZ\_OFF}t = \mathbf{1}{\{\mathrm{BB\_HIGH}_t > \mathrm{KC\_HIGH}_t \lor \mathrm{BB\_LOW}_t < \mathrm{KC\_LOW}_t\}} SQZ_OFFt=1{BB_HIGHt>KC_HIGHt∨BB_LOWt<KC_LOWt}
即布林带上轨高于肯特纳上轨,或布林带下轨低于肯特纳下轨时,表明布林带已突破肯特纳通道边界,波动率扩张,行情即将爆发。
无挤压状态(SQZ_NO = 1) :
SQZ_NOt=1{−SQZ_ON∧−SQZ_OFF} \mathrm{SQZ\_NO}t = \mathbf{1}{\{-\mathrm{SQZ\_ON} \land -\mathrm{SQZ\_OFF}\}} SQZ_NOt=1{−SQZ_ON∧−SQZ_OFF}
即不属于SQZ_ON也不属于SQZ_OFF的"中间态"------两通道交错但未完全重叠,市场处于正常波动状态。
4. 动量柱(Momentum Histogram)的计算
动量柱的计算过程为:
第一步:计算价格动量
MOM=Close−REF(Close,mom_length−1) \mathrm{MOM} = \mathrm{Close} - \mathrm{REF}(\mathrm{Close}, \mathrm{mom\_length} - 1) MOM=Close−REF(Close,mom_length−1)
其中mom_length为动量周期(默认12)。
第二步:对MOM取平滑
若mamode == "ema":
SQZ=EMA(MOM,mom_smooth) \mathrm{SQZ=EMA(MOM,mom\_smooth)} SQZ=EMA(MOM,mom_smooth)
若mamode == "sma":
SQZ=SMA(MOM,mom_smooth) \mathrm{SQZ = SMA(MOM, mom\_smooth)} SQZ=SMA(MOM,mom_smooth)
其中mom_smooth为动量平滑周期(默认6)。
第三步:输出动量柱
动量柱即是平滑后的SQZ值。正值 表示收盘价相对于过去12日持续走高、买盘增强,未来上涨动能概率较大;负值表示收盘价走低、卖盘增强,未来下跌动能概率较大。柱子的高度反映动能大小。
5. 参数说明(标准版)
| 参数 | 默认值 | 说明 |
|---|---|---|
bb_length |
20 | 布林带计算周期 |
bb_std |
2.0 | 布林带标准差倍数 |
kc_length |
20 | 肯特纳通道计算周期 |
kc_scalar |
1.5 | 肯特纳通道ATR乘数 |
mom_length |
12 | 动量计算周期 |
mom_smooth |
6 | 动量平滑周期 |
use_tr |
True | 是否使用真实波幅(否则用High-Low) |
lazybear |
False | 是否使用LazyBear版本算法 |
三、SQUEEZE的使用方法
1. 三阶段交易法------核心用法
SQUEEZE指标的使用遵循一套系统的流程:识别挤压 → 等待解除 → 跟随动量。
| 阶段 | 指标状态 | 市场含义 | 操作策略 |
|---|---|---|---|
| ① 挤压开启 | SQZ_ON = 1(红点) | 波动率收缩至低点,多空力量平衡,正在积蓄能量 | 关注预警,准备交易计划 |
| ② 挤压解除 | SQZ_OFF = 1(绿点) | 波动率扩张,突破即将发生 | 准备建仓,等待方向确认 |
| ③ 方向确认 | 动量柱为正/为负 | 多方/空方开始主导市场 | 执行顺势交易 |
挤压开启阶段------市场处于低波动蓄势期,此时不宜提前入场,而应将此阶段视为预警信号。当红点出现后,需耐心等待"点火"信号的到来。
2. 动量柱的方向与强度判断
动量柱(Momentum Histogram)是判断趋势方向和动能强度的核心工具:
| 动量柱状态 | 市场含义 | 操作倾向 |
|---|---|---|
| 正值且持续扩大(绿色) | 多头动能增强,买方主导 | 做多为主,顺势持仓 |
| 正值但持续缩小 | 多头动能减弱,上涨乏力 | 考虑减仓、锁定利润 |
| 负值且持续扩大(红色) | 空头动能增强,卖方主导 | 做空为主,顺势持仓 |
| 负值但持续缩小 | 空头动能减弱,下跌放缓 | 考虑空头平仓、关注反弹 |
| 绝对值极大 | 极端动能释放 | 趋势加速,但需警惕物极必反 |
动量柱的横截面还可以反映多空力量的对比:上穿零轴(从负转正)是多头信号,下穿零轴(从正转负)是空头信号。
3. 完整的四步交易流程
步骤一:识别挤压状态(SQZ_ON = 1)
当图表上出现连续的红点(挤压开启)时,意味着市场进入低波动蓄势阶段,应保持高度警惕。
步骤二:等待挤压解除(SQZ_OFF = 1)
挤压开启状态的终结,往往以突然的一根"放大K线"为标志。此时布林带突破肯特纳通道,红点转变为绿点(挤压解除)。
步骤三:跟随动量柱方向
- 若挤压解除时动量柱为正值(绿色),预示着向上突破------买入
- 若挤压解除时动量柱为负值(红色),预示着向下突破------卖出
步骤四:持仓管理与离场
只要动量柱保持同向(正值维持正值,负值维持负值),趋势大概率会延续。当动能减弱(柱子高度明显缩小或由正转负)时,应考虑平仓。
4. 多周期信号共振增强
SQUEEZE在较高时间框架(如日线、4小时)上识别出的挤压突破,往往比小周期(如15分钟)的信号更具持续性和可靠性。可以结合多周期共振确认:
| 共振类型 | 确认方式 | 信号含义 |
|---|---|---|
| 大周期(日线)+ 小周期(4H)同时SQZ_OFF | 双重挤压解除 | 信号可靠性大幅提升 |
| 大周期动量柱为正值 + 小周期同步 | 多周期动量共振 | 趋势加速概率增加 |
5. 常见误区与实战技巧
误区一:在挤压开启时提前入场
许多初学者见到"挤压开启"信号就急于买入,认为波动即将爆发。但这只是蓄势的开始,挤压状态可能持续数周。在没有确认突破方向之前提前入场,可能面临长时间的资金占用甚至被"假突破"来回洗盘。
误区二:忽视挤压解除时的动量柱方向
挤压解除本身只代表波动率将扩张,但扩张方向并不确定。必须结合动量柱的颜色来判断方向:红点转绿点时若动量柱为正做多,为负做空。
误区三:在强趋势中频繁交易
挤压动量策略本质上是趋势跟踪策略,在强趋势行情中应减少不必要的开仓频率,待趋势确立后顺势持仓。
6. 适用品种与局限性
最佳适用品种:在周期性较为明确、趋势延续性强的品种上,SQUEEZE表现出较高的准确率,例如:
- 主流货币对(如EUR/USD)
- 指数(如标普500、纳斯达克)
- 大宗商品(如黄金、原油)
局限性:
- 在价格经常快速反转、震荡频繁且缺乏明显趋势的品种上,挤压可能被反复触发,导致假突破信号较多
- 在高频交易(如15分钟级别)中,手续费成本可能严重影响策略的净利润,即使信号准确率高也可能因频繁开仓而亏损
- 信号具有滞后性:当挤压解除确认信号在图表上出现时,价格往往已启动一段距离,可能导致在局部高位追入、随后面临回调
改善措施:建议引入ADX指标(趋势强度指数)作为过滤器------当ADX > 25时再考虑交易,避免在震荡市中因频繁开仓消耗本金。
四、使用pandas_ta计算SQUEEZE(附示例代码)
1. pandas_ta中的SQUEEZE函数
pandas_ta库内置了SQUEEZE指标的完整实现,函数位于momentum模块中。该库是基于Pandas DataFrame的Python技术分析库,提供130多个指标和实用程序函数。
2. 函数完整参数
python
pandas_ta.momentum.squeeze(
high, low, close,
bb_length=None, bb_std=None,
kc_length=None, kc_scalar=None,
mom_length=None, mom_smooth=None,
use_tr=None, mamode=None,
offset=None, **kwargs
)
参数详解:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
high |
pd.Series | 必需 | 最高价序列 |
low |
pd.Series | 必需 | 最低价序列 |
close |
pd.Series | 必需 | 收盘价序列 |
bb_length |
int | 20 | 布林带计算周期 |
bb_std |
float | 2.0 | 布林带标准差倍数 |
kc_length |
int | 20 | 肯特纳通道计算周期 |
kc_scalar |
float | 1.5 | 肯特纳通道ATR乘数 |
mom_length |
int | 12 | 动量计算周期 |
mom_smooth |
int | 6 | 动量平滑周期 |
use_tr |
bool | True | 是否使用真实波幅(否则用High-Low) |
mamode |
str | "ema" | 移动平均类型('ema'或'sma') |
lazybear |
bool | False | 是否使用LazyBear版本 |
offset |
int | 0 | 结果偏移周期数 |
返回值 :pd.DataFrame------包含以下列:
SQZ_{bb_length}_{bb_std}_{kc_length}_{kc_scalar}:挤压动量值(动量柱)SQZ_ON:挤压开启状态(1=开启,0=未开启)SQZ_OFF:挤压解除状态(1=解除,0=未解除)SQZ_NO:无挤压状态(1=无挤压,0=有挤压)
3. 示例代码
python
import pandas as pd
import pandas_ta as ta
import numpy as np
import matplotlib.pyplot as plt
import yfinance as yf # 可选:用于获取真实数据
# ========== 第一步:准备数据 ==========
# 方式一:使用yfinance获取真实数据
# df = yf.download("BTC-USD", start="2023-01-01", end="2024-12-31")
# 方式二:生成示例数据(演示用)
np.random.seed(42)
dates = pd.date_range(start='2023-01-01', end='2024-12-31', freq='D')
n = len(dates)
# 生成带趋势和周期波动的价格序列(包含低波动区间)
trend = np.linspace(0, 35, n)
cycle = np.sin(np.linspace(0, 4 * np.pi, n)) * 12
noise = np.random.randn(n) * 2
price_series = 100 + trend + cycle + noise
# 添加一个低波动率区间(模拟挤压状态)
low_vol_start = 300
low_vol_end = 400
price_series[low_vol_start:low_vol_end] = price_series[low_vol_start] + np.random.randn(low_vol_end - low_vol_start) * 1
df = pd.DataFrame(index=dates[:n])
df['close'] = price_series[:n]
df['high'] = df['close'] + np.abs(np.random.randn(n)) * 2 + 1
df['low'] = df['close'] - np.abs(np.random.randn(n)) * 2 - 1
df['volume'] = np.random.randint(5000000, 30000000, n)
print("=" * 60)
print("数据预览:")
print(df.head())
print("\n" + "=" * 60 + "\n")
# ========== 第二步:计算SQUEEZE(基础用法) ==========
# 使用默认参数
squeeze_df = ta.squeeze(df['high'], df['low'], df['close'])
# 查看返回的DataFrame结构
print("SQUEEZE返回列名称:")
print(squeeze_df.columns.tolist())
print("\n" + "=" * 60 + "\n")
# 将计算结果添加到原DataFrame
# 动量值列名通常为 SQZ_{bb_length}_{bb_std}_{kc_length}_{kc_scalar}
momentum_col = [col for col in squeeze_df.columns if col.startswith('SQZ_') and not col.endswith('_ON') and not col.endswith('_OFF') and not col.endswith('_NO')]
sqz_on_col = [col for col in squeeze_df.columns if col.endswith('_ON')]
sqz_off_col = [col for col in squeeze_df.columns if col.endswith('_OFF')]
sqz_no_col = [col for col in squeeze_df.columns if col.endswith('_NO')]
df['SQZ'] = squeeze_df[momentum_col[0]] if momentum_col else squeeze_df.iloc[:, 0]
df['SQZ_ON'] = squeeze_df[sqz_on_col[0]] if sqz_on_col else squeeze_df.iloc[:, 1]
df['SQZ_OFF'] = squeeze_df[sqz_off_col[0]] if sqz_off_col else squeeze_df.iloc[:, 2]
df['SQZ_NO'] = squeeze_df[sqz_no_col[0]] if sqz_no_col else squeeze_df.iloc[:, 3]
print("SQUEEZE计算结果(最近10行):")
print(df[['close', 'SQZ', 'SQZ_ON', 'SQZ_OFF', 'SQZ_NO']].tail(10))
print("\n" + "=" * 60 + "\n")
# ========== 第三步:不同参数对比 ==========
# 标准参数(默认)
squeeze_std = ta.squeeze(df['high'], df['low'], df['close'])
df['SQZ_std'] = squeeze_std.iloc[:, 0]
# 短线参数(更敏感)
squeeze_fast = ta.squeeze(df['high'], df['low'], df['close'],
bb_length=10, kc_length=10, mom_length=8, mom_smooth=4)
df['SQZ_fast'] = squeeze_fast.iloc[:, 0]
# 长线参数(更平滑)
squeeze_slow = ta.squeeze(df['high'], df['low'], df['close'],
bb_length=30, kc_length=30, mom_length=14, mom_smooth=8)
df['SQZ_slow'] = squeeze_slow.iloc[:, 0]
print("不同参数SQUEEZE对比(最近5行):")
print(df[['close', 'SQZ_std', 'SQZ_fast', 'SQZ_slow']].tail())
print("\n" + "=" * 60 + "\n")
# ========== 第四步:手动验证SQUEEZE核心逻辑 ==========
def manual_squeeze(high, low, close, bb_length=20, bb_std=2.0,
kc_length=20, kc_scalar=1.5, mom_length=12, mom_smooth=6):
"""手动验证SQUEEZE核心计算逻辑"""
# 1. 计算布林带
sma = close.rolling(window=bb_length).mean()
std = close.rolling(window=bb_length).std()
bb_high = sma + bb_std * std
bb_low = sma - bb_std * std
# 2. 计算肯特纳通道
ema = close.ewm(span=kc_length, adjust=False).mean()
# 计算ATR
tr1 = high - low
tr2 = abs(high - close.shift(1))
tr3 = abs(low - close.shift(1))
tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
atr = tr.ewm(span=kc_length, adjust=False).mean()
kc_high = ema + kc_scalar * atr
kc_low = ema - kc_scalar * atr
# 3. 判断挤压状态
sqz_on = ((bb_high < kc_high) & (bb_low > kc_low)).astype(int)
sqz_off = ((bb_high > kc_high) | (bb_low < kc_low)).astype(int)
sqz_no = ((sqz_on == 0) & (sqz_off == 0)).astype(int)
# 4. 计算动量
mom = close - close.shift(mom_length - 1)
sqz_momentum = mom.ewm(span=mom_smooth, adjust=False).mean()
return sqz_momentum, sqz_on, sqz_off, sqz_no
df['SQZ_manual'], df['SQZ_ON_manual'], df['SQZ_OFF_manual'], df['SQZ_NO_manual'] = manual_squeeze(
df['high'], df['low'], df['close']
)
# 验证一致性
diff_sqz = (df['SQZ'] - df['SQZ_manual']).abs().max()
print(f"pandas_ta与手动计算动量值的最大差异:{diff_sqz:.10f}")
print("\n" + "=" * 60 + "\n")
# ========== 第五步:生成交易信号 ==========
# 定义交易信号:挤压解除 + 动量柱方向确认
# 买入信号:SQZ_OFF = 1 且 SQZ > 0(动量柱为正)
df['buy_signal'] = (df['SQZ_OFF'] == 1) & (df['SQZ'] > 0)
# 卖出信号:SQZ_OFF = 1 且 SQZ < 0(动量柱为负)
df['sell_signal'] = (df['SQZ_OFF'] == 1) & (df['SQZ'] < 0)
# 动量趋势强度分类
df['momentum_strength'] = ''
df.loc[df['SQZ'] > 2, 'momentum_strength'] = '强多头动能'
df.loc[(df['SQZ'] > 0) & (df['SQZ'] <= 2), 'momentum_strength'] = '弱多头动能'
df.loc[(df['SQZ'] < 0) & (df['SQZ'] >= -2), 'momentum_strength'] = '弱空头动能'
df.loc[df['SQZ'] < -2, 'momentum_strength'] = '强空头动能'
print("挤压状态统计:")
print(f"挤压开启(蓄势)天数:{df['SQZ_ON'].sum()}")
print(f"挤压解除(爆发)天数:{df['SQZ_OFF'].sum()}")
print(f"无挤压状态天数:{df['SQZ_NO'].sum()}")
print("\n交易信号统计:")
print(f"买入信号(挤压解除+多头动量)数量:{df['buy_signal'].sum()}")
print(f"卖出信号(挤压解除+空头动量)数量:{df['sell_signal'].sum()}")
print("\n最近10个交易信号:")
signals = df[df['buy_signal'] | df['sell_signal']].tail(10)
if not signals.empty:
for idx, row in signals.iterrows():
signal_type = "买入" if row['buy_signal'] else "卖出"
print(f"{idx.date()} - {signal_type}信号 - SQZ: {row['SQZ']:.2f} - 状态: {row['SQZ_OFF']}")
print("\n" + "=" * 60 + "\n")
# ========== 第六步:策略回测(挤压突破策略) ==========
# 策略:当买入信号出现时买入,持有至卖出信号或动量转弱
df['position'] = 0
in_position = False
entry_price = 0
for i in range(1, len(df)):
if not in_position and df['buy_signal'].iloc[i]:
df.loc[df.index[i], 'position'] = 1
in_position = True
entry_price = df['close'].iloc[i]
elif in_position and (df['sell_signal'].iloc[i] or df['momentum_strength'].iloc[i] == '弱多头动能'):
df.loc[df.index[i], 'position'] = 0
in_position = False
else:
df.loc[df.index[i], 'position'] = df['position'].iloc[i-1]
# 计算收益
df['returns'] = df['close'].pct_change()
df['strategy_returns'] = df['position'].shift(1) * df['returns']
total_return_buyhold = (1 + df['returns']).prod() - 1
total_return_strategy = (1 + df['strategy_returns']).prod() - 1
win_rate = (df['strategy_returns'] > 0).sum() / (df['strategy_returns'] != 0).sum() if (df['strategy_returns'] != 0).sum() > 0 else 0
print("=" * 60)
print("策略绩效统计(SQUEEZE挤压突破策略回测):")
print(f"买入持有策略总收益率:{total_return_buyhold:.2%}")
print(f"SQUEEZE策略总收益率:{total_return_strategy:.2%}")
print(f"策略胜率:{win_rate:.2%}")
print("\n注意:此为简化回测,仅供参考;实盘需考虑手续费、滑点等因素")
print("=" * 60 + "\n")
# ========== 第七步:可视化 ==========
plt.figure(figsize=(14, 12))
# 子图1:价格走势 + 挤压状态标记
plt.subplot(3, 1, 1)
plt.plot(df.index[-200:], df['close'][-200:], label='Close Price',
linewidth=1.5, color='black')
# 标记挤压开启区域(红点位置)
squeeze_on_points = df[df['SQZ_ON'] == 1]
plt.scatter(squeeze_on_points.index[-200:], squeeze_on_points['close'][-200:],
color='red', s=20, label='Squeeze On(挤压开启)', alpha=0.7)
plt.title('Price Chart with Squeeze Signals (Last 200 days)', fontsize=14)
plt.ylabel('Price')
plt.legend()
plt.grid(True, alpha=0.3)
# 子图2:挤压状态(SQZ_ON/SQZ_OFF)
plt.subplot(3, 1, 2)
# 挤压开启用红色点表示,挤压解除用绿色点表示
plt.plot(df.index[-200:], df['SQZ_ON'][-200:], 'ro', markersize=3, label='Squeeze On(蓄势)', alpha=0.7)
plt.plot(df.index[-200:], df['SQZ_OFF'][-200:], 'go', markersize=3, label='Squeeze Off(爆发)', alpha=0.7)
plt.ylim(-0.1, 1.1)
plt.title('Squeeze State: Red=Squeeze On (Low Volatility), Green=Squeeze Off (Breakout)', fontsize=12)
plt.ylabel('State')
plt.legend()
plt.grid(True, alpha=0.3)
# 子图3:动量柱(SQZ值)
plt.subplot(3, 1, 3)
# 动量柱:正值绿色,负值红色
colors = ['green' if val >= 0 else 'red' for val in df['SQZ'][-200:]]
plt.bar(df.index[-200:], df['SQZ'][-200:], color=colors, alpha=0.7, width=0.8)
plt.axhline(y=0, color='gray', linestyle='-', linewidth=1)
plt.title('Squeeze Momentum Histogram: Green=向上动能, Red=向下动能', fontsize=12)
plt.xlabel('Date')
plt.ylabel('SQZ Momentum')
plt.grid(True, alpha=0.3)
# 标记买入/卖出信号
buy_signals = df[df['buy_signal']]
sell_signals = df[df['sell_signal']]
plt.scatter(buy_signals.index[-20:], [0.5] * min(20, len(buy_signals)),
color='green', marker='^', s=80, label='Buy Signal', alpha=0.8)
plt.scatter(sell_signals.index[-20:], [0.5] * min(20, len(sell_signals)),
color='red', marker='v', s=80, label='Sell Signal', alpha=0.8)
plt.tight_layout()
plt.show()
# ========== 第八步:数据清洗提示 ==========
nan_count = df['SQZ'].isna().sum()
print(f"\nSQUEEZE初始NaN数量:{nan_count}")
print("原因:SQUEEZE需要至少max(bb_length, kc_length, mom_length)个周期的数据")
print("处理建议:df_clean = df.iloc[20:].copy()")
print("\n" + "=" * 60)
print("SQUEEZE使用提示:")
print("1. SQZ_ON(红点)= 低波动蓄势 → 保持警惕,不盲目进场")
print("2. SQZ_OFF(绿点)+ 动量柱为正 → 多头突破信号")
print("3. SQZ_OFF(绿点)+ 动量柱为负 → 空头突破信号")
print("4. 建议结合ADX指标过滤震荡市假信号")
print("5. 多周期共振可显著提高信号可靠性")
print("=" * 60)
五、总结
SQUEEZE(挤压动量指标)是一种将波动率收缩周期与动量爆发相结合的复合型技术指标,其核心价值与定位如下:
| 维度 | 特点 |
|---|---|
| 核心创新 | 结合布林带与肯特纳通道,量化"波动率压缩 → 爆发"的完整周期 |
| 核心公式 | SQZ_ON:BB完全落入KC内部;动量柱:平滑MOM值 |
| 三组件系统 | 挤压指示灯(红/绿点)+ 动量柱(红/绿柱)+ 状态标识 |
| 三大核心信号 | 挤压开启(预警)、挤压解除(方向确认)、动量柱(动能验证) |
| 默认参数 | (20,2,20,1.5,12,6) |
| 最佳应用场景 | 趋势明确的品种、波动率周期性明显的市场、中长期交易 |
| 主要局限 | 震荡市假信号多、手续费敏感、信号有滞后性 |
实战使用三原则:
- 挤压开启预警,挤压解除执行:SQZ_ON提示蓄势阶段,不急于入场;SQZ_OFF出现后再结合动量柱方向确认开仓方向
- 动量柱决定方向:挤压解除时动量柱为正值做多,为负值做空;动量柱的绝对值反映趋势强度
- 引入ADX过滤器:在ADX < 20的震荡市中,SQUEEZE产生的假信号较多。建议仅当ADX > 25(趋势明确)时才启用此策略
最后提醒 :SQUEEZE指标的核心价值在于帮助交易者找到"波动率压缩后即将爆发"的关键窗口。它回答了交易中最核心的两个问题:什么时候可能有大行情?方向是什么? 然而,如John Carter本人所强调,SQUEEZE不是"圣杯"------它本质上是滞后指标,挤压解除信号出现时价格往往已启动一段距离,可能面临局部回调的风险。因此,建议配合多周期分析、ADX趋势强度过滤以及严格的风控管理,同时务必注意交易成本(尤其是在高频交易场景中)对净利润的侵蚀。将SQUEEZE作为完整交易系统中的一个组件,而非唯一的决策依据,才能发挥其最大价值。