3. MACD金叉死叉调仓策略
任务目标
实现一个基于MACD金叉/死叉信号的调仓策略:当DIF上穿DEA(金叉)时买入,当DIF下穿DEA(死叉)时卖出,并在历史行情数据上完成回测,输出策略收益与基准收益的对比。
环境与依赖
需要 pandas、numpy、matplotlib。数据使用本地CSV文件(包含 date、close 两列即可),若没有真实数据,可用代码内置的随机价格序列演示完整流程。
核心实现
import pandas as pd
import numpy as np
def load_data(path=None, n=300, seed=42):
"""加载CSV或生成模拟价格序列"""
if path:
df = pd.read_csv(path, parse_dates=['date'])
else:
np.random.seed(seed)
# 模拟几何布朗运动价格序列
ret = np.random.normal(0.0005, 0.02, n)
price = 100 * np.exp(np.cumsum(ret))
df = pd.DataFrame({'date': pd.date_range('2023-01-01', periods=n, freq='D'),
'close': price})
return df
def macd_cross_strategy(df, fast=12, slow=26, signal=9):
"""计算MACD并生成金叉死叉交易信号"""
df = df.copy()
# 计算MACD指标
ema_fast = df['close'].ewm(span=fast, adjust=False).mean()
ema_slow = df['close'].ewm(span=slow, adjust=False).mean()
df['DIF'] = ema_fast - ema_slow
df['DEA'] = df['DIF'].ewm(span=signal, adjust=False).mean()
df['MACD'] = 2 * (df['DIF'] - df['DEA'])
# 生成信号:金叉=1(买入),死叉=-1(卖出)
df['signal'] = 0
df.loc[(df['DIF'] > df['DEA']) & (df['DIF'].shift(1) <= df['DEA'].shift(1)), 'signal'] = 1
df.loc[(df['DIF'] < df['DEA']) & (df['DIF'].shift(1) >= df['DEA'].shift(1)), 'signal'] = -1
# 持仓状态:1表示持有,0表示空仓
df['position'] = df['signal'].replace(0, np.nan).ffill().fillna(0)
df['position'] = df['position'].shift(1).fillna(0) # 次日开盘执行
return df
def backtest(df, initial_capital=100000):
"""执行回测,返回策略与基准的净值序列"""
df = df.copy()
df['daily_ret'] = df['close'].pct_change().fillna(0)
df['strategy_ret'] = df['daily_ret'] * df['position']
df['strategy_net'] = initial_capital * (1 + df['strategy_ret']).cumprod()
df['benchmark_net'] = initial_capital * (1 + df['daily_ret']).cumprod()
return df
# ========== 运行与验证 ==========
if __name__ == '__main__':
# 1. 加载数据(无CSV时自动生成模拟数据)
data = load_data() # 传入CSV路径可加载真实数据
# 2. 生成信号并回测
result = backtest(macd_cross_strategy(data))
# 3. 输出关键结果
final_strategy = result['strategy_net'].iloc[-1]
final_benchmark = result['benchmark_net'].iloc[-1]
print(f'策略最终净值: {final_strategy:.2f}')
print(f'基准最终净值: {final_benchmark:.2f}')
print(f'策略收益率: {(final_strategy/100000-1)*100:.2f}%')
print(f'基准收益率: {(final_benchmark/100000-1)*100:.2f}%')
# 4. 交易次数统计
trades = result[result['signal'] != 0]
print(f'总交易信号数: {len(trades)} (买入: {(trades["signal"]==1).sum()}, 卖出: {(trades["signal"]==-1).sum()})')
# 5. 可视化净值对比
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 5))
plt.plot(result['date'], result['strategy_net'], label='MACD策略')
plt.plot(result['date'], result['benchmark_net'], label='买入持有基准')
plt.legend()
plt.title('MACD金叉死叉策略回测')
plt.xlabel('日期')
plt.ylabel('净值')
plt.grid(True, alpha=0.3)
plt.show()
运行方式
- 直接运行脚本:
python macd_strategy.py(使用内置模拟数据) - 使用真实数据:将CSV文件路径传入
load_data('/path/to/your_data.csv'),CSV需包含date和close列
验证方法与通过条件
- 信号正确性:打印的交易信号数应大于0,且买入信号与卖出信号交替出现(或最多连续同向信号间隔出现)
- 策略有效性:策略最终净值与基准净值不同,说明调仓产生了影响;若模拟数据下策略净值低于基准,属正常现象(MACD在震荡行情中可能产生亏损交易),重点验证逻辑正确性而非盈利性
- 持仓逻辑:检查
position列,金叉后应为1,死叉后应为0,且信号执行延迟一天(次日开盘调仓) - 可视化确认:净值曲线图正常显示两条曲线,策略曲线在信号切换处出现斜率变化
常见问题
- 信号延迟:代码中
position.shift(1)模拟次日开盘执行,避免未来函数 - 数据不足:MACD需要至少26+9个数据点才能产生首个有效信号,模拟数据默认300天足够
- 手续费未计入:本案例为简化教学,未包含交易成本;实际应用中需在
strategy_ret中扣除