维克 · 量化笔记系列 第65篇
Why:RSI告诉你"超买",然后市场继续涨了三个月
我见过最惨的交易员,不是不懂技术分析的那种。
恰恰相反------他太懂了。
小王。 CFA持证人。技术分析玩了六年。均线、MACD、RSI、布林带,信手拈来。
2024年初,他盯上了一只票。
RSI = 75。超买区。
"这票涨太多了,该回调了。"
做空。
一周后,RSI = 82。继续涨。
"更超买了。肯定得跌。"
加仓做空。
两周后,RSI = 88。
再两周,RSI = 91。
一个月后,那只票又涨了40%。
小王爆了仓。
他后来跟我说了一句话,我到现在还记得------
"RSI告诉我该卖了。但市场没听RSI的。"
这句话,说对了一半。
RSI确实告诉他"超买"了。但"超买"不等于"该卖"。
就像温度计告诉你38度------你发烧了。但温度计没告诉你该吃什么药。
这就是动量指标的问题。
人人都在用。但大多数人的用法------是错的。
今天这一篇。把动量指标家族从头到尾拆一遍。
RSI、ROC、CCI、Momentum。
四个指标。每个都讲透。
最后告诉你------怎么把它们组合起来用,才不会像小王一样被市场教做人。
What:动量指标到底在衡量什么
在拆具体指标之前,先搞清楚一件事------
动量(Momentum)是什么?
物理学里的定义------
动量 = 质量 × 速度
翻译到金融市场------
价格动量 = 价格变化的速度和力度
说白了:
价格涨得快、涨得猛 → 正动量大
价格跌得快、跌得猛 → 负动量大
价格不怎么动 → 动量接近零
所有动量指标,本质上都在回答同一个问题------
"价格现在跑得多快?"
注意:只问速度,不问方向。
速度告诉你车开得多快。但车是在上坡还是下坡?那是另一回事。
好。搞清楚了动量的本质,我们逐个拆解。
一、RSI:相对强弱指标
发明人 :J. Welles Wilder(1978年)
全称 :Relative Strength Index
核心问题 :最近的涨幅,在总波动中占多大比例?
计算公式
RSI = 100 - 100 / (1 + RS)
其中:
RS = 平均上涨幅度 / 平均下跌幅度
拆开看------
-
算每天的涨跌幅
-
把涨的天和跌的天分开
-
分别求平均值(用指数平滑)
-
涨的平均 ÷ 跌的平均 = RS
-
套公式 → RSI
结果范围 :0 到 100
经典解读 :
• RSI > 70 → 超买区(涨太多了?)
• RSI < 30 → 超卖区(跌太多了?)
• RSI = 50 → 多空分界线
数学直觉
假设一个股票过去14天------涨了10天,每天平均涨2块。跌了4天,每天平均跌1块。
RS = (10 × 2) / (4 × 1) = 20 / 4 = 5
RSI = 100 - 100 / (1 + 5) = 100 - 16.67 = 83.33
RSI = 83。超买。
但注意------这只代表过去14天里,涨的力度远大于跌的力度。
它没说"该跌了"。
它只说"目前涨得猛"。
"涨得猛"和"该跌了"是两件事。
这就是小王踩的坑。
参数选择
默认参数:14天
• 短周期(7天):更敏感,信号多但假信号也多
• 中周期(14天):经典值,大多数人的选择
• 长周期(21天/25天):更平滑,信号少但更可靠
没有"最好"的参数。 参数取决于你的交易周期和市场特征。
二、ROC:变动率指标
全称 :Rate of Change
核心问题 :现在的价格,比N天前变了多少?
计算公式
ROC = (今日收盘价 - N日前收盘价) / N日前收盘价 × 100
就这么简单。
比如------
今天收盘价 110块。12天前收盘价 100块。
ROC(12) = (110 - 100) / 100 × 100 = 10%
12天涨了10%。就这么直白。
和RSI的区别
|----------|----------------|---------------|
| 维度 | RSI | ROC |
| 衡量什么 | 涨跌的相对比例 | 价格变化的绝对幅度 |
| 值域 | 0~100(固定) | -∞ ~ +∞(无界) |
| 对极端行情的反应 | 容易钝化(到顶/底区不动了) | 不会钝化(可以一直涨/跌) |
| 适用场景 | 判断超买超卖 | 判断趋势强度和转折 |
ROC的核心优势 :不会钝化。
RSI到了80以上就"卡住了"------你分不清80和90的区别,因为都在超买区。
但ROC可以。ROC = 15% 和 ROC = 30%,差距一目了然。
经典用法
• ROC > 0 → 上升趋势
• ROC < 0 → 下降趋势
• ROC从正变负 → 可能的卖出信号
• ROC从负变正 → 可能的买入信号
注意 :ROC对趋势的判断很直接,但假信号也多。特别是在震荡市里,ROC在零轴附近反复穿越,给你一堆没用的信号。
三、CCI:顺势指标
发明人 :Donald Lambert(1980年)
全称 :Commodity Channel Index
最初目的 :发现商品期货中的周期性转折点
核心问题 :当前价格偏离统计平均值的程度有多大?
计算公式
CCI = (典型价格 - 典型价格的N日简单平均) / (0.015 × 平均绝对偏差)
其中:
典型价格 = (最高价 + 最低价 + 收盘价) / 3
平均绝对偏差 = N日内 |每个典型价格 - 平均值| 的平均值
拆开看------
-
算典型价格(TP)
-
算TP的N日均值
-
算每个TP偏离均值的绝对距离,再求平均(MAD)
-
(当前TP - TP均值) / (0.015 × MAD)
那个0.015是个常数------Lambert选的,为了让70%~80%的值落在-100到+100之间。
值域特征
CCI没有固定范围。但大多数值在 -100 到 +100 之间。
• CCI > +100 → 强势区(价格大幅高于均值)
• CCI < -100 → 弱势区(价格大幅低于均值)
• CCI在-100~+100之间 → 震荡区
和RSI的关键区别
RSI的值固定在0~100之间------有天花板和地板。
CCI没有天花板和地板------可以突破+100、跌破-100继续走。
这意味着------
CCI比RSI更适合捕捉极端行情。
当市场出现剧烈波动时,RSI可能已经卡在70不动了(钝化),但CCI可以继续飙到+200、+300,给你更清晰的信号。
经典用法
• CCI从-100以下回升到-100以上 → 买入信号
• CCI从+100以上回落到+100以下 → 卖出信号
• CCI > +200 → 极强趋势(但别急着做空------可能是主升浪)
• CCI < -200 → 极弱趋势(但别急着抄底------可能是主跌浪)
四、Momentum:动量线
最朴素的动量指标。没有之一。
计算公式
MOM(N) = 今日收盘价 - N日前收盘价
对。就是减法。
今天110,12天前100。MOM = 10。
就这么简单。
ROC是百分比版本,MOM是绝对值版本。
ROC = MOM / N日前收盘价 × 100
为什么还要学MOM?
因为它最直观。
ROC说"涨了10%"------你知道比例。
MOM说"涨了10块"------你知道绝对金额。
在实盘里,有时候你更关心"我赚了多少块"而不是"涨了几个百分点"。
另外,MOM的一些衍生用法------比如MOM的均线、MOM的背离------在实战中有独特价值。
经典用法
• MOM > 0 → 价格在N日高点之上
• MOM < 0 → 价格在N日低点之下
• MOM由负转正 → 买入信号
• MOM由正转负 → 卖出信号
注意 :MOM和ROC的信号几乎完全一致------只是尺度不同。所以在实际策略中,选一个用就行。大多数人选ROC,因为它标准化了。
五大指标横向对比
|--------|--------|----------|----------|------------|
| 指标 | 值域 | 核心优势 | 核心弱点 | 最适合的场景 |
| RSI | 0~100 | 标准化、好理解 | 极端行情钝化 | 震荡市的超买超卖 |
| ROC | 无界 | 不钝化、直观 | 震荡市假信号多 | 趋势强度判断 |
| CCI | 无界 | 捕捉极端行情 | 参数敏感 | 期货/周期转折 |
| MOM | 无界 | 最简单、最直观 | 未标准化 | 辅助确认 |
How:Python实操 + 组合策略 + 避坑指南
第一步:环境准备
// python
安装依赖
pip install numpy pandas ta-lib matplotlib
如果talib装不上(Windows常见),可以用pandas_ta替代
pip install pandas_ta
第二步:准备数据
// python
import pandas as pd
import numpy as np
生成模拟数据(实盘中替换为你的真实数据)
np.random.seed(42)
n_days = 500
模拟一个有趋势的股价
returns = np.random.normal(0.001, 0.02, n_days) # 日均收益0.1%,日波动率2%
close = 100 * np.cumprod(1 + returns)
生成OHLC数据
high = close * (1 + np.abs(np.random.normal(0, 0.01, n_days)))
low = close * (1 - np.abs(np.random.normal(0, 0.01, n_days)))
open_price = close * (1 + np.random.normal(0, 0.005, n_days))
df = pd.DataFrame({
'open': open_price,
'high': high,
'low': low,
'close': close,
'volume': np.random.randint(1000000, 10000000, n_days)
})
print(df.tail())
第三步:计算RSI
// python
def calc_rsi(close, period=14):
"""
手动计算RSI(不依赖talib)
参数:
close: 收盘价序列
period: RSI周期,默认14
返回:
RSI序列
"""
delta = close.diff() # 每日涨跌
gain = delta.where(delta > 0, 0) # 只保留涨的部分
loss = (-delta).where(delta < 0, 0) # 只保留跌的部分(取正值)
指数移动平均(Wilder平滑法)
avg_gain = gain.ewm(alpha=1/period, min_periods=period).mean()
avg_loss = loss.ewm(alpha=1/period, min_periods=period).mean()
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
return rsi
df'rsi_14' = calc_rsi(df'close', 14)
df'rsi_7' = calc_rsi(df'close', 7) # 短周期
df'rsi_21' = calc_rsi(df'close', 21) # 长周期
查看结果
print(df\['close', 'rsi_7', 'rsi_14', 'rsi_21'].dropna().tail(10))
逐行讲解 :
• close.diff():算每天的涨跌额。今天100,昨天98,diff = +2
• gain.where(delta > 0, 0):只保留涨的天,跌的天填0
• (-delta).where(delta < 0, 0):只保留跌的天并取正值(跌2块 → loss = 2)
• ewm(alpha=1/period):Wilder的指数平滑法。alpha = 1/14 ≈ 0.071,意味着最新一天权重7.1%,之前的权重递减
• rs = avg_gain / avg_loss:平均涨幅 ÷ 平均跌幅
• rsi = 100 - 100 / (1 + rs):转换为0~100的值
第四步:计算ROC
// python
def calc_roc(close, period=12):
"""
计算ROC变动率
参数:
close: 收盘价序列
period: ROC周期,默认12
返回:
ROC序列(百分比)
"""
roc = (close - close.shift(period)) / close.shift(period) * 100
return roc
df'roc_12' = calc_roc(df'close', 12)
df'roc_6' = calc_roc(df'close', 6) # 短周期
df'roc_25' = calc_roc(df'close', 25) # 长周期
print(df\['close', 'roc_6', 'roc_12', 'roc_25'].dropna().tail(10))
逐行讲解 :
• close.shift(period):把收盘价序列往下移period天。shift(12)就是12天前的价格
• (close - close.shift(12)):今天价格 - 12天前价格 = 绝对涨跌
• / close.shift(12) * 100:除以12天前价格,转成百分比
就这么简单。一行核心代码。
第五步:计算CCI
// python
def calc_cci(high, low, close, period=20):
"""
计算CCI顺势指标
参数:
high: 最高价序列
low: 最低价序列
close: 收盘价序列
period: CCI周期,默认20
返回:
CCI序列
"""
第一步:算典型价格
typical_price = (high + low + close) / 3
第二步:算典型价格的N日简单移动平均
tp_sma = typical_price.rolling(window=period).mean()
第三步:算平均绝对偏差(MAD)
每个典型价格偏离均值的绝对距离,然后求平均
mad = typical_price.rolling(window=period).apply(
lambda x: np.mean(np.abs(x - x.mean())), raw=True
)
第四步:套公式
cci = (typical_price - tp_sma) / (0.015 * mad)
return cci
df'cci_20' = calc_cci(df'high', df'low', df'close', 20)
df'cci_14' = calc_cci(df'high', df'low', df'close', 14)
print(df\['close', 'cci_14', 'cci_20'].dropna().tail(10))
逐行讲解 :
• typical_price = (high + low + close) / 3:典型价格 = 日内三个关键价格的平均
• tp_sma = typical_price.rolling(window=period).mean():N日简单移动平均
• mad = ...:平均绝对偏差。rolling + apply,对每个窗口内的数据算 |每个值 - 均值| 的平均
• 0.015 * mad:乘以常数0.015,这是Lambert设定的,让大多数CCI值落在-100到+100之间
• (typical_price - tp_sma) / (0.015 * mad):当前典型价格偏离均值的程度,标准化后就是CCI
第六步:计算Momentum
// python
def calc_momentum(close, period=12):
"""
计算Momentum动量线
参数:
close: 收盘价序列
period: 动量周期,默认12
返回:
Momentum序列(绝对值)
"""
mom = close - close.shift(period)
return mom
df'mom_12' = calc_momentum(df'close', 12)
print(df\['close', 'mom_12', 'roc_12'].dropna().tail(10))
对比一下MOM和ROC------
• MOM = 今天价格 - N天前价格(绝对值,单位是"块")
• ROC = (今天价格 - N天前价格) / N天前价格 × 100(百分比)
一个是绝对涨跌,一个是相对涨跌。
第七步:可视化------四个指标同屏对比
// python
import matplotlib.pyplot as plt
plt.rcParams'font.sans-serif' = 'SimHei', 'Arial Unicode MS', 'DejaVu Sans'
plt.rcParams'axes.unicode_minus' = False
fig, axes = plt.subplots(5, 1, figsize=(14, 16), sharex=True)
图1:价格走势
axes0.plot(df.index, df'close', color='#2196F3', linewidth=1)
axes0.set_title('价格走势', fontsize=12)
axes0.set_ylabel('价格')
axes0.grid(True, alpha=0.3)
图2:RSI
axes1.plot(df.index, df'rsi_14', color='#FF9800', linewidth=1, label='RSI(14)')
axes1.axhline(y=70, color='red', linestyle='--', alpha=0.5, label='超买线(70)')
axes1.axhline(y=30, color='green', linestyle='--', alpha=0.5, label='超卖线(30)')
axes1.axhline(y=50, color='gray', linestyle=':', alpha=0.3)
axes1.fill_between(df.index, 70, 100, alpha=0.1, color='red')
axes1.fill_between(df.index, 0, 30, alpha=0.1, color='green')
axes1.set_title('RSI(14)', fontsize=12)
axes1.set_ylabel('RSI')
axes1.legend(loc='upper left', fontsize=8)
axes1.grid(True, alpha=0.3)
图3:ROC
axes2.plot(df.index, df'roc_12', color='#4CAF50', linewidth=1, label='ROC(12)')
axes2.axhline(y=0, color='gray', linestyle='-', alpha=0.5)
axes2.fill_between(df.index, 0, df'roc_12',
where=df'roc_12' > 0, alpha=0.2, color='green')
axes2.fill_between(df.index, 0, df'roc_12',
where=df'roc_12' < 0, alpha=0.2, color='red')
axes2.set_title('ROC(12)', fontsize=12)
axes2.set_ylabel('ROC(%)')
axes2.legend(loc='upper left', fontsize=8)
axes2.grid(True, alpha=0.3)
图4:CCI
axes3.plot(df.index, df'cci_20', color='#9C27B0', linewidth=1, label='CCI(20)')
axes3.axhline(y=100, color='red', linestyle='--', alpha=0.5, label='+100')
axes3.axhline(y=-100, color='green', linestyle='--', alpha=0.5, label='-100')
axes3.axhline(y=0, color='gray', linestyle=':', alpha=0.3)
axes3.fill_between(df.index, 100, df'cci_20'.clip(lower=100),
alpha=0.2, color='red')
axes3.fill_between(df.index, -100, df'cci_20'.clip(upper=-100),
alpha=0.2, color='green')
axes3.set_title('CCI(20)', fontsize=12)
axes3.set_ylabel('CCI')
axes3.legend(loc='upper left', fontsize=8)
axes3.grid(True, alpha=0.3)
图5:Momentum
axes4.plot(df.index, df'mom_12', color='#F44336', linewidth=1, label='MOM(12)')
axes4.axhline(y=0, color='gray', linestyle='-', alpha=0.5)
axes4.fill_between(df.index, 0, df'mom_12',
where=df'mom_12' > 0, alpha=0.2, color='green')
axes4.fill_between(df.index, 0, df'mom_12',
where=df'mom_12' < 0, alpha=0.2, color='red')
axes4.set_title('Momentum(12)', fontsize=12)
axes4.set_ylabel('MOM')
axes4.set_xlabel('交易日')
axes4.legend(loc='upper left', fontsize=8)
axes4.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('momentum_indicators_comparison.png', dpi=150, bbox_inches='tight')
plt.show()
第八步:组合策略------多指标共振
单独用一个指标,假信号太多。
核心思路 :多个动量指标同时发出信号时,可信度大幅提升。
这叫"多指标共振"。
// python
def momentum_composite_strategy(df):
"""
动量指标组合策略
买入条件(全部满足):
- RSI(14) 从30以下回升到30以上(超卖恢复)
- ROC(12) > 0(价格在上涨)
- CCI(20) 从-100以下回升到-100以上(弱势恢复)
卖出条件(全部满足):
- RSI(14) 从70以上回落到70以下(超买回落)
- ROC(12) < 0(价格在下跌)
- CCI(20) 从+100以上回落到+100以下(强势回落)
"""
计算指标
df'rsi' = calc_rsi(df'close', 14)
df'roc' = calc_roc(df'close', 12)
df'cci' = calc_cci(df'high', df'low', df'close', 20)
===== 买入信号 =====
RSI从30以下回升
rsi_buy = (df'rsi' > 30) & (df'rsi'.shift(1) <= 30)
ROC > 0
roc_buy = df'roc' > 0
CCI从-100以下回升
cci_buy = (df'cci' > -100) & (df'cci'.shift(1) <= -100)
三个信号同时满足(共振)
注意:三个信号很难在同一天同时触发
实际中可以用"窗口期"------5天内三个信号都出现过
buy_strict = rsi_buy & roc_buy & cci_buy
宽松版:5天窗口内三个信号都出现过
buy_window = (
rsi_buy.rolling(5).max().astype(bool) &
roc_buy.rolling(5).max().astype(bool) &
cci_buy.rolling(5).max().astype(bool)
)
===== 卖出信号 =====
rsi_sell = (df'rsi' < 70) & (df'rsi'.shift(1) >= 70)
roc_sell = df'roc' < 0
cci_sell = (df'cci' < 100) & (df'cci'.shift(1) >= 100)
sell_strict = rsi_sell & roc_sell & cci_sell
sell_window = (
rsi_sell.rolling(5).max().astype(bool) &
roc_sell.rolling(5).max().astype(bool) &
cci_sell.rolling(5).max().astype(bool)
)
df'buy_strict' = buy_strict
df'buy_window' = buy_window
df'sell_strict' = sell_strict
df'sell_window' = sell_window
return df
df = momentum_composite_strategy(df)
统计信号
print("=" * 50)
print("信号统计")
print("=" * 50)
print(f"严格买入信号(同日共振):{df'buy_strict'.sum()} 次")
print(f"窗口买入信号(5日共振):{df'buy_window'.sum()} 次")
print(f"严格卖出信号(同日共振):{df'sell_strict'.sum()} 次")
print(f"窗口卖出信号(5日共振):{df'sell_window'.sum()} 次")
第九步:避坑指南------六个实战要点
坑1:RSI超买 ≠ 该做空
强势趋势中,RSI可以在70以上停留很久。
2020-2021年的新能源赛道,RSI连续3个月在70以上。
你去做空?三个月亏光。
正确做法 :RSI超买 + 趋势向下确认(比如跌破20日均线)才考虑做空。
坑2:参数越调越差(过拟合)
你在回测里发现RSI(7)在你的数据上效果最好。
然后你换了RSI(7)。
结果实盘------亏。
因为RSI(7)只是对历史数据的最佳拟合。不代表对未来有效。
正确做法 :用经典参数(RSI-14, ROC-12, CCI-20)。这些参数经过了几十年、无数市场的验证。别为了回测好看去调参数。
坑3:忽视市场环境
RSI在震荡市里好用------超买超卖来回切换,信号准。
但在趋势市里------RSI会持续给出错误信号。
正确做法 :先用ADX判断市场是趋势市还是震荡市。趋势市用趋势指标(均线、MACD),震荡市用动量指标(RSI、CCI)。
坑4:多指标共线性
你同时看了RSI、ROC、MOM。三个都发出买入信号。你觉得"三重确认,稳了"。
但问题是------RSI、ROC、MOM本质上衡量的都是动量。它们高度相关。
这不是"三重确认",这是"同一个信号看了三遍"。
正确做法 :组合不同类别的指标。比如:
• 动量类:RSI(判断超买超卖)
• 趋势类:均线/MACD(判断方向)
• 波动类:ATR/布林带(判断空间)
三类指标各给一个维度的信息,才是真正的多维度确认。
坑5:忽视背离
动量指标最有价值的用法不是超买超卖,而是背离 。
什么是背离?
价格创新高,但RSI没有创新高 → 顶背离 → 可能见顶
价格创新低,但RSI没有创新低 → 底背离 → 可能见底
背离 = 价格还在走,但动量已经在衰减。就像车还在前进,但油门已经松了。
// python
def detect_divergence(close, rsi, lookback=20):
"""
检测RSI背离
顶背离:价格创新高,RSI没创新高
底背离:价格创新低,RSI没创新低
"""
divergence = \[\]
for i in range(lookback, len(close)):
window_close = closei-lookback:i+1
window_rsi = rsii-lookback:i+1
顶背离:最近价格创窗口新高,但RSI没创新高
if closei == window_close.max() and rsii < window_rsi.max():
divergence.append(('顶背离', i, closei, rsii))
底背离:最近价格创窗口新低,但RSI没创新低
if closei == window_close.min() and rsii > window_rsi.min():
divergence.append(('底背离', i, closei, rsii))
return divergence
检测背离
divergences = detect_divergence(df'close', df'rsi')
print(f"检测到 {len(divergences)} 个背离信号")
for div_type, idx, price, rsi_val in divergences-5::
print(f" 第{idx}天: {div_type}, 价格={price:.2f}, RSI={rsi_val:.1f}")
坑6:动量指标不适合做唯一决策依据
再说一遍------
动量指标是辅助工具,不是交易系统的核心。
它可以告诉你"现在涨得快/跌得快"。但不能告诉你"该买/该卖"。
完整的交易系统需要:
-
方向判断(趋势指标)
-
入场时机(动量指标)
-
仓位管理(资金管理)
-
风险控制(止损止盈)
动量指标只负责第2步。
实用速查表
|---------|-------------|----------|------------------|
| 场景 | 推荐指标 | 参数 | 怎么用 |
| 震荡市判断顶底 | RSI | 14 | <30超卖买,>70超买卖 |
| 判断趋势强度 | ROC | 12 | >0上升,<0下降 |
| 捕捉极端行情 | CCI | 20 | <-100弱势,>100强势 |
| 快速判断方向 | MOM | 12 | >0上涨,<0下跌 |
| 多指标共振入场 | RSI+ROC+CCI | 14/12/20 | 三个信号5日内共振 |
| 发现趋势衰竭 | RSI背离 | 14 | 价格新高/低但RSI不配合 |
小结
动量指标家族四个成员,各有特点:
• RSI :最经典,适合震荡市的超买超卖判断,但趋势市会钝化
• ROC :不钝化,适合判断趋势强度和转折点
• CCI :无界指标,擅长捕捉极端行情和周期转折
• Momentum :最朴素,最直观,适合辅助确认
但请记住------
动量指标衡量的是"速度",不是"方向"。
车跑得快,不代表它不会撞墙。
把动量指标当成你车上的速度表------它告诉你跑多快,但方向盘(趋势判断)和刹车(风险控制)得靠你自己。
量化笔记系列
下一篇:波动率指标------ATR、Bollinger Bands、KDJ的正确打开方式