4. 金叉死叉:MA金叉/死叉策略的实战实现与回测
金叉死叉,这名字听着挺玄乎,对吧?
其实说白了,就是两根移动平均线(MA)互相交叉。一根短期均线,一根长期均线。短期线上穿长期线,叫「金叉」,看涨信号。反过来,短期线下穿长期线,叫「死叉」,看跌信号。
嗯,就是这么简单。但简单不代表没用。我入行那会儿,第一个实盘策略就是基于金叉死叉的。虽然现在看很粗糙,但当时确实帮我赚到了第一桶金。
4.1 核心逻辑:金叉与死叉的定义
我们先明确一下数学定义。
- 金叉(Golden Cross):短期MA
上穿长期MA。比如 MA5 上穿 MA20。 - 死叉(Death Cross):短期MA
下穿长期MA。比如 MA5 下穿 MA20。
这里有个细节:什么叫「上穿」?
不是当前时刻的值大于长期线就算。而是 前一时刻短期线 ≤ 长期线,且当前时刻短期线 > 长期线。这才是真正的交叉。
核心公式:
金叉条件:MA_short[t-1] ≤ MA_long[t-1] 且 MA_short[t] > MA_long[t]
死叉条件:MA_short[t-1] ≥ MA_long[t-1] 且 MA_short[t] < MA_long[t]
为什么强调这个?因为很多人直接用当前值比较,结果信号满天飞,回测曲线漂亮得不行,一实盘就崩。我踩过这个坑,后来才明白——交叉信号必须用前后两个时刻判断。
4.2 实战实现:从数据到信号
我们直接用 Python 实现。假设你已经有 OHLC 数据,存在 DataFrame 里。
import pandas as pd
import numpy as np
def generate_ma_cross_signals(df, short_window=5, long_window=20):
"""
生成金叉死叉信号
df: 包含 'close' 列的 DataFrame
short_window: 短期均线窗口,默认5
long_window: 长期均线窗口,默认20
"""
# 计算均线
df['MA_short'] = df['close'].rolling(window=short_window).mean()
df['MA_long'] = df['close'].rolling(window=long_window).mean()
# 初始化信号列
df['signal'] = 0
# 金叉:短期上穿长期
# 条件:前一刻短期 ≤ 长期,且当前短期 > 长期
cross_up = (df['MA_short'].shift(1) <= df['MA_long'].shift(1)) & \
(df['MA_short'] > df['MA_long'])
# 死叉:短期下穿长期
cross_down = (df['MA_short'].shift(1) >= df['MA_long'].shift(1)) & \
(df['MA_short'] < df['MA_long'])
# 赋值信号
df.loc[cross_up, 'signal'] = 1 # 买入
df.loc[cross_down, 'signal'] = -1 # 卖出
# 生成持仓信号(1表示持仓,0表示空仓)
df['position'] = df['signal'].replace(to_replace=0, method='ffill').fillna(0)
return df
代码很简单,但有几个点要注意:
shift(1)取前一个时刻的值,这是判断交叉的关键。rolling().mean()计算均线,窗口大小自己调。position列用前向填充,因为信号只在交叉时出现,但持仓是连续的。
我的习惯:
我个人习惯把 short_window 和 long_window 设成参数,方便后面做参数优化。比如 MA10/MA30、MA20/MA60,不同品种适合不同参数。
4.3 回测框架:验证策略有效性
光有信号不行,得回测。我们写一个简单的回测函数。
def backtest_ma_cross(df, initial_capital=100000, commission=0.001):
"""
回测金叉死叉策略
"""
df = df.copy()
# 计算每日收益率
df['daily_return'] = df['close'].pct_change()
# 策略收益率 = 持仓信号 * 每日收益率
df['strategy_return'] = df['position'].shift(1) * df['daily_return']
# 扣除手续费(双边)
df.loc[df['signal'] != 0, 'strategy_return'] -= commission
# 计算净值曲线
df['nav'] = initial_capital * (1 + df['strategy_return']).cumprod()
# 计算绩效指标
total_return = df['nav'].iloc[-1] / initial_capital - 1
sharpe_ratio = np.sqrt(252) * df['strategy_return'].mean() / df['strategy_return'].std()
max_drawdown = (df['nav'] / df['nav'].cummax() - 1).min()
return df, {
'total_return': total_return,
'sharpe_ratio': sharpe_ratio,
'max_drawdown': max_drawdown
}
回测结果怎么看?我一般关注三个指标:
| 指标 | 含义 | 合格线 |
|---|---|---|
| 总收益率 | 策略最终赚了多少 | 跑赢基准即可 |
| 夏普比率 | 风险调整后收益 | 大于1算不错 |
| 最大回撤 | 最惨的时候亏多少 | 最好控制在20%以内 |
避坑指南:
我曾经在回测时忘记处理 shift(1),导致信号和收益率错位。结果回测曲线漂亮得不像话,年化收益50%+。后来发现是用了未来数据。记住:交易信号必须用前一个时刻的持仓,去乘当前时刻的收益率。否则就是作弊。
4.4 核心逻辑流程图
下面这张图,把整个流程串起来了。从原始数据到最终绩效,一步不落。
你看,整个流程其实就四步:拿数据 → 算均线 → 找交叉 → 跑回测。但每一步都有坑。数据要干净,均线参数要调,交叉判断要严谨,回测不能有未来函数。
4.5 实战中的注意事项
最后,分享几个实战经验:
- 参数不是万能的:别想着找到一个万能参数组合。不同品种、不同周期,参数差异很大。我习惯先用默认参数跑一遍,再根据回测结果微调。
- 震荡行情别用:金叉死叉在趋势行情里好用,但在震荡市里会被反复打脸。我一般会加一个过滤器,比如只在大盘趋势向上时做多。
- 别迷信历史回测:回测漂亮不代表实盘能赚钱。我见过太多人拿着回测曲线去募资,结果实盘三个月就亏光了。回测只是参考,不是圣杯。
一句话总结:
金叉死叉策略,简单但不简陋。理解其核心逻辑,做好回测验证,控制好风险,它就能成为你交易工具箱里的一把好刀。
公众号:蓝海资料掘金营,微信deep3321