3. MACD金叉死叉调仓策略

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()

运行方式

  1. 直接运行脚本:python macd_strategy.py(使用内置模拟数据)
  2. 使用真实数据:将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 中扣除