量化笔记系列 · 第63篇

Why:为什么你用MACD总是亏钱?
2015年A股牛市,我一个朋友小李,刚入市三个月。
他在某股票论坛上看到一个"MACD金叉必胜法"------DIF上穿DEA就买,死叉就卖。帖子里晒了十几张截图,每张都精准抄底逃顶,回测年化收益80%。
小李如获至宝,拿了20万积蓄照着做。
结果呢?
2015年6月,MACD在5000点附近给出金叉信号,小李全仓杀入。然后股灾来了,千股跌停。DIF在高位掉头向下,死叉信号出现时,他的账户已经亏了35%。
他割肉了。
然后7月,MACD又给出金叉,他以为反弹来了,又冲进去。结果又是一波暴跌,再亏20%。
前后三个月,20万亏到10万出头。
他跟我说:"MACD就是个骗子指标。"
我说:"MACD没骗你。是你根本不知道MACD算的是什么。"
这不是小李一个人的问题。90%用技术指标的散户,都不知道MA、EMA、MACD、RSI背后的数学公式到底在算什么。他们只知道"金叉买、死叉卖",却不知道:
• 均线本质上是过去N天收盘价的平均值,它天然是滞后的
• EMA给近期价格更高权重,但它仍然是过去价格的加权平均
• MACD只是两条EMA的差值再做一次平均,它没有任何预测能力
• RSI衡量的是过去N天涨幅占总波动的比例,超买不代表一定会跌
你拿着一个描述过去的工具,去预测未来,这本身就有问题。
但问题不在于指标本身------问题在于你不理解它。一个你不理解的工具,比没有工具更危险。因为你会对它产生虚假的信心。
所以今天,我们把技术指标的数学底裤扒下来。从均线到MACD到RSI,一个个讲清楚:它们到底在算什么?为什么有效?什么时候失效?
What:技术指标到底是什么?
一句话定义
技术指标 = 对价格和成交量数据做数学变换后得到的衍生数据序列。
关键词是"数学变换"和"衍生"。指标不是独立于价格的新信息,它是从价格(和成交量)中加工出来的。原材料就是开高低收量,指标只是换了个角度看同一组数据。
就像一块猪肉------你可以切成肉丝、肉片、肉末、肉丸。但不管怎么切,它还是那块猪肉。技术指标就是把价格数据这块"肉"切成不同形状,让你更容易看出某些特征。
技术指标的三层本质
第一层:指标是过滤器。
原始价格数据噪音很大,今天涨2%明天跌1.5%,你很难看出趋势。均线把N天的价格平均一下,日常波动被平滑掉了,趋势就显现出来了。这就像你给照片磨皮------痣和斑还在那里,但看起来更光滑了。
第二层:指标是视角转换器。
K线图看的是价格本身,MACD看的是两条均线的距离变化,RSI看的是涨跌力度的对比,布林带看的是价格偏离均值的程度。同一个市场,不同指标给你不同的视角。就像看一座山------正面看雄伟,侧面看险峻,空中看又是另一回事。
第三层:指标是统计描述,不是预测工具。
这是最关键的一点。所有技术指标都是对历史数据的统计描述。MA告诉你"过去N天平均成本是多少",MACD告诉你"短期趋势相对于长期趋势是加速还是减速",RSI告诉你"过去N天涨的天数和幅度占比是多少"。
它们都在描述过去。没有任何一个指标能告诉你未来会怎样。
如果有人跟你说某个指标能精准预测买卖点,他不是蠢就是坏。
四大核心指标的数学拆解
1. MA(移动平均线):最简单也最基础
MA的公式小学生都能算:
MA(N) = (C1 + C2 + ... + CN) / N
C是每天的收盘价,N是你选的周期。比如MA5就是最近5天收盘价的算术平均。
每天往前移动一天,就把最早一天的价格去掉,加上最新一天的价格,重新算平均。这就是"移动"平均的含义。
直觉理解: MA(N)就是过去N天所有买入者的平均持仓成本。价格在均线上方,说明最近N天买入的人整体赚钱;价格在均线下方,说明最近N天买入的人整体亏钱。
优点: 简单、直观、平滑噪音。
缺点:: 严重滞后。N越大越平滑,但滞后越严重。MA20在趋势行情中好用,在震荡市中反复被打脸------价格频繁穿越均线,发出大量假信号。
关键认知: MA给每天的价格相同权重。10天前的价格和昨天的价格对MA的影响是一样的。这合理吗?显然不合理------昨天的价格显然比10天前的价格更能反映当前市场状态。于是就有了EMA。
2. EMA(指数移动平均线):给近期价格更高权重
EMA的公式稍微复杂一点:
EMA(t) = α × C(t) + (1 - α) × EMA(t-1)
其中 α = 2 / (N + 1),这是平滑系数。
这个公式看起来简单,但理解它的关键在于展开递归。把EMA(t-1)继续展开:
EMA(t) = α×C(t) + α(1-α)×C(t-1) + α(1-α)²×C(t-2) + α(1-α)³×C(t-3) + ...
你会发现,EMA其实是过去所有价格的加权平均,只是权重随着时间推移指数级衰减。越近的价格权重越大,越远的价格权重越小,但永远不会变成零。
直觉理解: MA像民主投票------每天一票,票票等值。EMA像精英投票------越近期的价格"话语权"越大,远期价格的声音越来越小但永远存在。
以EMA12为例,α = 2/(12+1) ≈ 0.154。也就是说,今天的价格占15.4%的权重,昨天占约13%,前天约11%......以此类推。而MA12每天都是1/12 ≈ 8.3%。
关键区别: EMA比MA反应更快,但也更容易被短期波动影响。在趋势行情中EMA能让你更早入场,但在震荡市中EMA也会更早地发出假信号。
3. MACD:均线差值的差值
MACD是被使用最多也被误解最深的指标。它的计算分三步:
第一步:算DIF(快线)
DIF = EMA(12) - EMA(26)
DIF就是12天EMA减去26天EMA。它衡量的是短期趋势和长期趋势之间的距离。DIF为正,说明短期均价高于长期均价,多头占优;DIF为负,说明空头占优。
第二步:算DEA(慢线)
DEA = EMA(DIF, 9)
DEA是DIF的9天指数移动平均。它就是DIF的平滑版,比DIF慢半拍。
第三步:算MACD柱状图
MACD柱 = 2 × (DIF - DEA)
柱状图就是DIF和DEA的差值乘以2。正数画红柱(多头),负数画绿柱(空头)。
直觉理解: MACD的本质是"趋势的加速度"。DIF是短期和长期趋势的距离(相当于速度),DIF和DEA的差值(MACD柱)是这个距离的变化率(相当于加速度)。
金叉(DIF上穿DEA)意味着什么?意味着短期趋势相对于长期趋势正在变强------速度在增加。但注意,这只是描述"已经发生了什么",不是"将要发生什么"。
金叉买入法为什么在震荡市亏钱? 因为在震荡市中,DIF围绕DEA上下翻飞,频繁金叉死叉。你每次金叉买入,价格已经反弹了一段;每次死叉卖出,价格已经下跌了一段。来回打脸,反复止损。
MACD的致命缺陷: 它是双重滞后的------EMA本身滞后于价格,DEA又滞后于DIF。等你看到金叉,行情可能已经走完一半了。在强趋势中这个滞后可以接受(因为趋势会持续),但在震荡市中这个滞后是致命的。
4. RSI(相对强弱指标):涨跌力度的温度计
RSI的公式:
RSI(N) = 100 - 100 / (1 + RS)
其中:
RS = N天平均涨幅 / N天平均跌幅
具体计算:
• 取过去N天,每天的收盘价变化(涨了还是跌了)
• 涨幅的平均值 / 跌幅的平均值 = RS
• RSI = 100 - 100/(1+RS)
RSI的取值范围是0到100:
• RSI = 50:涨跌力度均衡
• RSI > 70:超买区,涨的力度远大于跌
• RSI < 30:超卖区,跌的力度远大于涨
直觉理解: RSI就像一个弹簧温度计。价格连续上涨,RSI升高,弹簧被压紧,有回归的趋势;价格连续下跌,RSI降低,弹簧被拉伸,也有回归的趋势。
但这里有个巨大的误区:超买不等于会跌,超卖不等于会涨。
在强趋势中,RSI可以长期处于超买区(牛市中天天涨,RSI持续80+),你以为超买了要做空,结果价格继续涨,你被轧空。反过来也一样,熊市中RSI可以长期在20以下,你以为超卖了要抄底,结果底下还有底。
RSI真正有用的场景是震荡市 ------价格在区间内来回波动,RSI在30-70之间摆动,这时候高抛低吸有一定参考价值。但在趋势行情中,RSI的超买超卖信号几乎是反向指标。
5. 布林带(Bollinger Bands):波动率的可视化
布林带由三条线组成:
中轨 = MA(N),通常N=20
上轨 = 中轨 + K × σ,通常K=2
下轨 = 中轨 - K × σ
σ是过去N天收盘价的标准差,K通常取2。
直觉理解: 布林带就是给均线加了一个"波动范围"。如果价格服从正态分布(实际上不完全是),价格落在上下轨之间的概率约95%。带宽变窄说明波动率在收缩(暴风雨前的宁静),带宽变宽说明波动率在放大(趋势正在展开)。
布林带最有价值的信号不是"碰到上轨就卖、碰到下轨就买"------这在趋势市中会死得很惨。真正有价值的是带宽收缩 :当布林带收窄到极致,往往意味着大行情即将来临。但方向不确定,需要结合其他信号判断。
How:用Python从零计算技术指标
理论讲完了,下面手把手教你用Python实现这些指标。不依赖talib,纯numpy/pandas手写,让你彻底理解每一步在算什么。
第一步:准备数据和环境
// python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
读取行情数据
数据需要包含:date, open, high, low, close, volume
df = pd.read_csv('your_stock_data.csv')
df'date' = pd.to_datetime(df'date')
df.set_index('date', inplace=True)
df.sort_index(inplace=True)
确保数据没有缺失值
df = df.dropna(subset='close')
print(f"数据范围:{df.index0.date()} 到 {df.index-1.date()}")
print(f"共 {len(df)} 个交易日")
这段代码做了三件事:
-
导入pandas(数据处理)、numpy(数学计算)、matplotlib(画图)
-
读取CSV行情数据并按日期排序
-
清理缺失值,打印数据概况
你需要准备一个包含date/open/high/low/close/volume列的CSV文件。可以从Tushare、AKShare等免费数据源获取。
第二步:计算MA移动平均线
// python
def calc_ma(close, period):
"""
计算简单移动平均线MA
close: 收盘价Series
period: 周期(如5, 10, 20, 60)
"""
return close.rolling(window=period).mean()
计算常用均线
df'MA5' = calc_ma(df'close', 5)
df'MA10' = calc_ma(df'close', 10)
df'MA20' = calc_ma(df'close', 20)
df'MA60' = calc_ma(df'close', 60)
rolling(window=period).mean()是pandas的滚动窗口函数。它做的事情就是:取最近period天的close值,求平均。前period-1天因为数据不足,结果是NaN(空值),这是正常的。
实操要点:
• MA5/MA10是短期均线,反映近期趋势
• MA20是中期均线,一个月大约20个交易日
• MA60是季线,反映中长期趋势
• 多头排列:MA5 > MA10 > MA20 > MA60,短期到长期依次向上
• 空头排列反过来
第三步:计算EMA指数移动平均线
// python
def calc_ema(close, period):
"""
计算指数移动平均线EMA
使用pandas的ewm函数
"""
return close.ewm(span=period, adjust=False).mean()
计算EMA12和EMA26(MACD用)
df'EMA12' = calc_ema(df'close', 12)
df'EMA26' = calc_ema(df'close', 26)
ewm(span=period, adjust=False)就是pandas的指数加权移动平均函数。span=period对应公式中的α=2/(period+1)。adjust=False表示使用递归公式计算(即EMA(t) = α×C(t) + (1-α)×EMA(t-1)),和行情软件的计算方式一致。
为什么不用adjust=True? adjust=True会用递推展开的方式精确计算所有历史数据的加权平均,结果更精确但和大多数行情软件不一致。adjust=False使用递归近似,和同花顺、东方财富等软件的结果一致。
第四步:计算MACD
// python
def calc_macd(close, fast=12, slow=26, signal=9):
"""
计算MACD指标
fast: 快线周期,默认12
slow: 慢线周期,默认26
signal: 信号线周期,默认9
返回:DIF, DEA, MACD柱
"""
第一步:计算快慢EMA
ema_fast = close.ewm(span=fast, adjust=False).mean()
ema_slow = close.ewm(span=slow, adjust=False).mean()
第二步:DIF = 快线EMA - 慢线EMA
dif = ema_fast - ema_slow
第三步:DEA = DIF的signal日EMA
dea = dif.ewm(span=signal, adjust=False).mean()
第四步:MACD柱 = 2 * (DIF - DEA)
macd_hist = 2 * (dif - dea)
return dif, dea, macd_hist
计算MACD
df'DIF', df'DEA', df'MACD_hist' = calc_macd(df'close')
逐行解释:
-
先算EMA12(短期)和EMA26(长期)
-
DIF就是两条EMA的差------短期趋势偏离长期趋势的程度
-
DEA是DIF的9天EMA------DIF的平滑版
-
MACD柱是DIF和DEA差值的2倍------柱状图的红绿柱
MACD的三个核心信号:
• 金叉:DIF从下方上穿DEA → 短期趋势转强信号
• 死叉:DIF从上方下穿DEA → 短期趋势转弱信号
• 顶背离:价格创新高但DIF不创新高 → 上涨动能衰竭
• 底背离:价格创新低但DIF不创新低 → 下跌动能衰竭
注意: 背离信号的可靠性高于金叉死叉,但仍然不是100%准确。背离之后还可以再背离,在强趋势中背离可以持续很久。
第五步:计算RSI
// python
def calc_rsi(close, period=14):
"""
计算RSI相对强弱指标
period: 计算周期,默认14
"""
计算每日价格变化
delta = close.diff()
分离涨幅和跌幅
where(条件, 不满足时的值):满足条件保留原值,不满足替换为0
gain = delta.where(delta > 0, 0.0) # 涨的日子保留涨幅,跌的日子填0
loss = (-delta).where(delta < 0, 0.0) # 跌的日子保留跌幅(取正),涨的日子填0
计算平均涨幅和平均跌幅(使用EMA方式,和Wilder原版一致)
avg_gain = gain.ewm(alpha=1/period, adjust=False).mean()
avg_loss = loss.ewm(alpha=1/period, adjust=False).mean()
计算RS和RSI
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
return rsi
计算RSI(常用6日和14日)
df'RSI6' = calc_rsi(df'close', 6)
df'RSI14' = calc_rsi(df'close', 14)
逐行解释:
-
diff()计算每天收盘价相对于前一天的变化(今天-昨天)
-
where(delta > 0, 0.0)把负的变化(下跌)替换为0,只保留涨幅;跌幅取负值后同样处理
-
用EMA方式计算平均涨幅和平均跌幅。这里用alpha=1/period而不是span=period,因为RSI的发明者Wilder使用的是特殊的平滑方式,alpha=1/14
-
RS就是平均涨幅除以平均跌幅
-
RSI = 100 - 100/(1+RS),把RS映射到0-100的区间
为什么用alpha=1/period而不是span=period? RSI的原版计算方法由Welles Wilder提出,他使用的平滑系数是1/N,而不是EMA标准的2/(N+1)。两者略有差异,用alpha=1/N更接近原版和行情软件的结果。
第六步:计算布林带
// python
def calc_bollinger(close, period=20, num_std=2):
"""
计算布林带
period: 均线周期,默认20
num_std: 标准差倍数,默认2
"""
mid = close.rolling(window=period).mean() # 中轨 = MA20
std = close.rolling(window=period).std() # 20日标准差
upper = mid + num_std * std # 上轨
lower = mid - num_std * std # 下轨
return upper, mid, lower
计算布林带
df'BOLL_upper', df'BOLL_mid', df'BOLL_lower' = calc_bollinger(df'close')
布林带的计算最简单:
-
中轨就是20日均线
-
计算20天收盘价的标准差
-
上下轨 = 中轨 ± 2倍标准差
标准差是什么? 标准差衡量的是数据偏离平均值的程度。标准差大,说明价格波动大;标准差小,说明价格波动小。布林带的宽度实际上就是4倍标准差,直接反映波动率大小。
第七步:可视化------把指标画出来
// python
fig, axes = plt.subplots(4, 1, figsize=(16, 14),
gridspec_kw={'height_ratios': 3, 1, 1, 1})
子图1:K线 + 均线 + 布林带
ax1 = axes0
ax1.plot(df.index, df'close', label='收盘价', color='black', linewidth=1)
ax1.plot(df.index, df'MA5', label='MA5', color='blue', linewidth=0.8)
ax1.plot(df.index, df'MA20', label='MA20', color='orange', linewidth=0.8)
ax1.plot(df.index, df'MA60', label='MA60', color='purple', linewidth=0.8)
ax1.fill_between(df.index, df'BOLL_upper', df'BOLL_lower',
alpha=0.1, color='gray', label='布林带')
ax1.set_title('价格 + 均线 + 布林带')
ax1.legend(loc='upper left')
ax1.grid(True, alpha=0.3)
子图2:成交量
ax2 = axes1
colors = 'red' if df\['close'i >= df'open'i else 'green'
for i in range(len(df))]
ax2.bar(df.index, df'volume', color=colors, width=0.8)
ax2.set_title('成交量')
ax2.grid(True, alpha=0.3)
子图3:MACD
ax3 = axes2
ax3.plot(df.index, df'DIF', label='DIF', color='blue', linewidth=0.8)
ax3.plot(df.index, df'DEA', label='DEA', color='orange', linewidth=0.8)
macd_colors = 'red' if v \>= 0 else 'green' for v in df\['MACD_hist']
ax3.bar(df.index, df'MACD_hist', color=macd_colors, width=0.8)
ax3.axhline(y=0, color='black', linewidth=0.5)
ax3.set_title('MACD')
ax3.legend(loc='upper left')
ax3.grid(True, alpha=0.3)
子图4:RSI
ax4 = axes3
ax4.plot(df.index, df'RSI14', label='RSI14', color='purple', linewidth=0.8)
ax4.axhline(y=70, color='red', linestyle='--', linewidth=0.8, label='超买线(70)')
ax4.axhline(y=30, color='green', linestyle='--', linewidth=0.8, label='超卖线(30)')
ax4.axhline(y=50, color='gray', linestyle='--', linewidth=0.5)
ax4.fill_between(df.index, 70, 100, alpha=0.05, color='red')
ax4.fill_between(df.index, 0, 30, alpha=0.05, color='green')
ax4.set_ylim(0, 100)
ax4.set_title('RSI')
ax4.legend(loc='upper left')
ax4.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('technical_indicators.png', dpi=150, bbox_inches='tight')
plt.show()
这段代码画了一个四合一的技术分析图:
-
最上面是收盘价、MA5/MA20/MA60均线和布林带
-
第二行是成交量(红涨绿跌)
-
第三行是MACD的DIF线、DEA线和柱状图
-
最下面是RSI,标注了70超买线和30超卖线
你可以一眼看出价格、趋势、动量和波动率的关系。
第八步:指标的正确使用方式
计算出指标只是第一步,关键是怎么用。以下是我多年实盘总结的几条原则:
1. 不要单指标决策。 任何单一指标都会在特定行情中失效。MA在趋势市好用但在震荡市被打脸,RSI在震荡市好用但在趋势市是反向指标。至少用两个不同类型的指标交叉验证:一个趋势指标(MA/MACD)+ 一个震荡指标(RSI/KDJ)。
2. 先判断行情类型,再选指标。 用ADX或者布林带宽度判断当前是趋势市还是震荡市。趋势市用MA/MACD顺势而为,震荡市用RSI/布林带高抛低吸。用错了场景,再好的指标也是亏钱机器。
3. 指标是过滤器不是触发器。 不要"金叉就买、死叉就卖"。正确的用法是:指标发出信号 → 你去检查这个信号是否有其他因素支撑(成交量、支撑阻力位、基本面)→ 综合判断后再决策。指标是帮你筛选机会的,不是替你做决策的。
4. 理解参数的含义。 MACD默认参数12/26/9是由来已久的设定(以前一周6个交易日,12天=2周,26天=1月),但在A股一周5天的交易制度下,这些参数是否仍然最优?不同品种、不同周期,最优参数可能不同。不要盲目使用默认参数,回测验证后再用。
5. 所有指标都滞后,接受这个事实。 技术指标是对已发生数据的加工,它不可能领先于价格。如果你的策略需要"领先指标",那技术指标帮不了你。你应该去研究基本面数据、资金流向、市场情绪等可能领先于价格的信息。
写在最后
技术指标不是圣杯,也不是骗术。它是一个工具------一个帮你从不同角度观察市场的工具。
锤子不能保证你造出好房子,但理解锤子的人比拿锤子乱砸的人强。
MA让你看到趋势,EMA让你更敏感地感知趋势变化,MACD让你看到趋势的加速度,RSI让你感知涨跌力度,布林带让你看到波动率。每个工具都有它擅长的场景和失效的场景。
重要的不是你用了多少指标,而是你是否理解每个指标在算什么、为什么有效、什么时候会失效。
一个被充分理解的RSI,比十个你只会看金叉死叉的MACD组合有用得多。
下期我们讲如何用Python的talib库快速计算技术指标,以及实盘中那些官方文档不会告诉你的坑。
如果对你有帮助,点赞关注,我们下期见。
量化笔记系列 · 第63篇
模块3:金融数据分析
关键词:#技术指标 #MACD #均线 #RSI #指标计算