双均线交叉是最经典的趋势跟踪策略之一,原理简单但能有效捕捉中长期趋势。本文用 Python 从零实现一个 MA5/MA20 的金叉死叉策略,包括信号生成、回测和收益可视化。
策略原理
双均线策略使用两条不同周期的移动平均线:
- 短期均线(MA5):反映近期价格趋势
- 长期均线(MA20):反映中期价格趋势
交易信号:
- 金叉(买入信号):短期均线从下方穿越长期均线,表明短期趋势转强
- 死叉(卖出信号):短期均线从上方穿越长期均线,表明短期趋势转弱
这本质上是一个动量策略——当近期涨势超过中期均值时入场,反之离场。
数据准备
使用 tushare 获取 A 股数据(也可以换成其他数据源):
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 生成模拟数据(实际使用时替换为真实数据)
np.random.seed(42)
dates = pd.date_range('2020-01-01', '2021-12-01', freq='B')
price = 100 * np.exp(np.cumsum(np.random.randn(len(dates)) * 0.02))
df = pd.DataFrame({'date': dates, 'close': price})
df.set_index('date', inplace=True)
计算均线和生成信号
def compute_signals(df, short_window=5, long_window=20):
"""计算均线并生成交易信号"""
data = df.copy()
data['ma_short'] = data['close'].rolling(window=short_window).mean()
data['ma_long'] = data['close'].rolling(window=long_window).mean()
# signal: 1 表示持仓,0 表示空仓
data['signal'] = 0
data.loc[data['ma_short'] > data['ma_long'], 'signal'] = 1
# position 变化点就是交易点
data['position'] = data['signal'].diff()
# position == 1 -> 金叉买入
# position == -1 -> 死叉卖出
return data.dropna()
data = compute_signals(df)
回测引擎
一个简化的回测框架,计算策略收益和基准收益:
def backtest(data, initial_capital=100000, commission=0.001):
"""回测计算"""
result = data.copy()
# 每日收益率
result['daily_return'] = result['close'].pct_change()
# 策略收益:前一天的信号决定今天是否持仓
result['strategy_return'] = result['signal'].shift(1) * result['daily_return']
# 扣除交易成本
result['trade'] = result['position'].abs()
result['strategy_return'] -= result['trade'] * commission
# 累计收益
result['cumulative_market'] = (1 + result['daily_return']).cumprod()
result['cumulative_strategy'] = (1 + result['strategy_return']).cumprod()
# 资金曲线
result['portfolio_value'] = initial_capital * result['cumulative_strategy']
return result
result = backtest(data)
收益分析
def analyze_performance(result):
"""计算关键指标"""
strategy_returns = result['strategy_return'].dropna()
total_return = result['cumulative_strategy'].iloc[-1] - 1
annual_return = (1 + total_return) ** (252 / len(result)) - 1
sharpe_ratio = strategy_returns.mean() / strategy_returns.std() * np.sqrt(252)
# 最大回撤
cummax = result['cumulative_strategy'].cummax()
drawdown = (result['cumulative_strategy'] - cummax) / cummax
max_drawdown = drawdown.min()
# 交易次数
trades = (result['position'].abs() > 0).sum()
print(f"总收益率: {total_return:.2%}")
print(f"年化收益率: {annual_return:.2%}")
print(f"Sharpe比率: {sharpe_ratio:.2f}")
print(f"最大回撤: {max_drawdown:.2%}")
print(f"交易次数: {trades}")
return {
'total_return': total_return,
'annual_return': annual_return,
'sharpe_ratio': sharpe_ratio,
'max_drawdown': max_drawdown,
'trades': trades,
}
stats = analyze_performance(result)
可视化
def plot_strategy(result):
"""绘制策略图表"""
fig, axes = plt.subplots(3, 1, figsize=(14, 10), sharex=True)
# 价格和均线
ax1 = axes[0]
ax1.plot(result.index, result['close'], label='Price', alpha=0.7)
ax1.plot(result.index, result['ma_short'], label='MA5', linewidth=1)
ax1.plot(result.index, result['ma_long'], label='MA20', linewidth=1)
# 标记买卖点
buy_signals = result[result['position'] == 1]
sell_signals = result[result['position'] == -1]
ax1.scatter(buy_signals.index, buy_signals['close'],
marker='^', color='red', s=80, label='Buy')
ax1.scatter(sell_signals.index, sell_signals['close'],
marker='v', color='green', s=80, label='Sell')
ax1.set_title('Price & Moving Averages')
ax1.legend()
# 累计收益对比
ax2 = axes[1]
ax2.plot(result.index, result['cumulative_market'], label='Buy & Hold')
ax2.plot(result.index, result['cumulative_strategy'], label='Strategy')
ax2.set_title('Cumulative Returns')
ax2.legend()
# 回撤
ax3 = axes[2]
cummax = result['cumulative_strategy'].cummax()
drawdown = (result['cumulative_strategy'] - cummax) / cummax
ax3.fill_between(result.index, drawdown, 0, alpha=0.3, color='red')
ax3.set_title('Drawdown')
plt.tight_layout()
plt.savefig('strategy_result.png', dpi=100)
plt.close()
plot_strategy(result)
策略局限性
双均线策略在趋势明显的行情中表现不错,但在震荡市中会频繁产生假信号,导致反复止损。改进方向:
- 加入过滤条件:比如要求金叉时成交量放大,或者 MACD 也确认
- 参数优化:用网格搜索或遗传算法寻找最优的均线周期组合
- 动态止损:加入 ATR 止损,控制单笔亏损
- 仓位管理:根据信号强度和波动率调整持仓比例
双均线虽然简单,但它是理解趋势跟踪策略的好起点。在这个基础上可以逐步叠加更多因子,演化出更复杂的策略。