🚀 AI 一键生成 joinquant 策略代码

超越移动平均线:麦克金利动态指标(McGinley Dynamic)策略实现

joinquant | 发布时间: 2025/12/23
以下内容由 EasyQuant 生成。

问题描述

如何编写麦克金利动态指标 McGinley Dynamic 的策略?

解决方案

麦克金利动态指标 (McGinley Dynamic) 策略

麦克金利动态指标(McGinley Dynamic Indicator)是由 John R. McGinley 发明的一种平滑机制。它看起来像移动平均线(MA),但它是一个能够根据市场速度自动调整的平滑指标。相比于传统的 SMA 或 EMA,它能更紧密地追踪价格,最大限度地减少价格分离(Lag)并避免鞭锯效应(Whipsaw)。

1. 指标公式与逻辑

公式:
$$ MD_i = MD_{i-1} + \frac{Close_i - MD_{i-1}}{N \times (\frac{Close_i}{MD_{i-1}})^4} $$

其中:

  • $MD_i$: 当前周期的麦克金利值
  • $MD_{i-1}$: 上一周期的麦克金利值
  • $Close_i$: 当前收盘价
  • $N$: 周期参数(类似于移动平均线的周期,如 14, 20 等)

策略逻辑:
本策略采用最基础的趋势跟随逻辑:

  1. 买入信号:当收盘价 上穿 麦克金利动态指标线时,视为上升趋势,全仓买入。
  2. 卖出信号:当收盘价 下穿 麦克金利动态指标线时,视为下降趋势,清仓卖出。

2. JoinQuant 策略代码实现

以下是基于聚宽 API 实现的完整策略代码。代码中包含了一个计算函数,用于在策略初始化时“预热”指标,以及在每日交易中动态更新指标。

# -*- coding: utf-8 -*-
import numpy as np

def initialize(context):
    """
    初始化函数,设定基准、股票池、参数等
    """
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    # 过滤掉order系列API产生的比error级别低的log
    log.set_level('order', 'error')
    
    # --- 策略参数设置 ---
    # 要操作的标的:这里以平安银行为例
    g.security = '000001.XSHE'
    # McGinley Dynamic 的周期 N
    g.N = 14 
    
    # 全局变量,用于存储上一日的 McGinley Dynamic 值
    g.prev_md = None 

def calculate_initial_md(security, n, end_date):
    """
    辅助函数:用于在策略开始前计算初始的 MD 值(预热)
    """
    # 获取过去 3*N 天的数据来预热指标,确保初始值趋于稳定
    # 注意:这里获取的是策略开始前的数据
    count = n * 3
    h = attribute_history(security, count, '1d', ['close'], df=False)
    close_prices = h['close']
    
    if len(close_prices) == 0:
        return None
        
    # 初始 MD 值通常设为第一个收盘价
    md = close_prices[0]
    
    # 迭代计算直到最后一个数据点
    for i in range(1, len(close_prices)):
        close = close_prices[i]
        # 防止除以0错误
        if md == 0:
            md = close
        else:
            # McGinley Dynamic 公式
            md = md + (close - md) / (n * (close / md)**4)
            
    return md

def handle_data(context, data):
    """
    按天回测时,每天执行一次
    """
    security = g.security
    
    # 获取当前标的的数据
    current_data = data[security]
    current_price = current_data.close
    
    # 如果数据中有 NaN,则跳过
    if np.isnan(current_price):
        return

    # --- 1. 计算/更新 McGinley Dynamic 指标 ---
    
    # 如果是第一天运行,或者 g.prev_md 尚未初始化,先进行预热计算
    if g.prev_md is None:
        # 获取截止到昨天的历史数据进行预热
        g.prev_md = calculate_initial_md(security, g.N, context.previous_date)
        
        # 如果历史数据不足,直接用当前价格作为初始值,并跳过今日交易
        if g.prev_md is None:
            g.prev_md = current_price
            return

    # 根据今日收盘价,计算今日的 MD 值
    # 公式: MD = MD_prev + (Price - MD_prev) / (N * (Price / MD_prev)^4)
    try:
        current_md = g.prev_md + (current_price - g.prev_md) / (g.N * (current_price / g.prev_md)**4)
    except ZeroDivisionError:
        current_md = current_price

    # --- 2. 交易逻辑 ---
    
    # 获取当前现金
    cash = context.portfolio.available_cash
    # 获取当前持仓
    position = context.portfolio.positions[security]
    
    # 信号判断
    # 价格上穿 MD 线 -> 买入
    if current_price > current_md and position.closeable_amount == 0:
        log.info("价格(%.2f) 上穿 MD(%.2f),买入 %s" % (current_price, current_md, security))
        order_value(security, cash)
        
    # 价格下穿 MD 线 -> 卖出
    elif current_price < current_md and position.closeable_amount > 0:
        log.info("价格(%.2f) 下穿 MD(%.2f),卖出 %s" % (current_price, current_md, security))
        order_target(security, 0)

    # --- 3. 更新状态与绘图 ---
    
    # 更新 prev_md 供明天使用
    g.prev_md = current_md
    
    # 在回测图中绘制指标
    record(Price=current_price, McGinley_Dynamic=current_md)

3. 代码关键点解析

  1. 指标预热 (calculate_initial_md)

    • McGinley Dynamic 是一个递归指标,今天的计算依赖昨天的结果。
    • 在回测的第一天,我们没有“昨天”的指标值。因此,代码中获取了过去 3 * N 天的历史数据来迭代计算,使指标值收敛,避免策略启动初期的信号失真。
  2. 公式实现

    • 核心代码行:current_md = g.prev_md + (current_price - g.prev_md) / (g.N * (current_price / g.prev_md)**4)
    • 这里体现了该指标的特性:分母中的 (Price / MD_prev)^4 是调节因子。当价格大幅偏离指标时,调节因子变大或变小,从而加速指标对价格的追随速度。
  3. 交易执行

    • 使用 order_value 进行全仓买入。
    • 使用 order_target(security, 0) 进行清仓卖出。
    • 加入了 record() 函数,方便在回测界面的图表中直观对比股价与指标的走势。

Q&A

Q1: 麦克金利动态指标相比普通均线(MA)有什么优势?
A1: 普通均线存在固定的滞后性。麦克金利动态指标通过引入价格与指标比率的4次方作为调节因子,在市场剧烈波动(价格快速远离指标)时,指标会加速移动以追赶价格;在市场盘整时,指标移动变慢以减少错误信号。这使得它比 EMA 更平滑,同时比 SMA 反应更快。

Q2: 代码中的 g.N 参数应该如何设置?
A2: g.N 类似于移动平均线的周期。常见的设置是 10 到 20 之间(如 14)。数值越小,指标越敏感,交易越频繁;数值越大,指标越平滑,更适合捕捉长期趋势。您可以根据回测结果进行调整。

Q3: 这个策略可以直接用于实盘吗?
A3: 上述代码是一个基础的单因子择时策略,主要用于演示指标计算逻辑。在实盘前,建议您:

  1. 增加风控模块(如止损、止盈)。
  2. 考虑多只股票的轮动或资产配置,而非单吊一只股票。
  3. 注意滑点和手续费对高频震荡市的影响。