🚀 AI 一键生成 qmt 策略代码

利用 Mass Index 预测趋势反转点的量化策略

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

问题描述

怎么实现梅斯线 (Mass Index) 的趋势反转策略?

解决方案

梅斯线 (Mass Index) 趋势反转策略

梅斯线(Mass Index)是由 Donald Dorsey 开发的一种波动率指标,用于通过测量高低价范围的扩大和收缩来识别趋势反转。

该策略的核心逻辑是寻找**“反转凸起”(Reversal Bulge)**:

  1. 计算原理:基于最高价与最低价之差(波动幅度)的指数移动平均。
  2. 反转信号:当梅斯线向上突破 27(表示波动率扩大),随后又跌破 26.5(表示波动率收缩)时,即形成“凸起”,预示趋势可能反转。
  3. 方向确认:梅斯线本身不指示方向。通常结合 9 日均线(EMA)来判断方向:
    • 买入:出现“凸起”信号,且价格收盘于 9 日 EMA 之上(看涨反转)。
    • 卖出:出现“凸起”信号,且价格收盘于 9 日 EMA 之下(看跌反转)。

以下是基于 QMT 平台的完整 Python 策略代码。

策略代码

# -*- coding: gbk -*-
import pandas as pd
import numpy as np

def init(ContextInfo):
    """
    策略初始化函数
    """
    # 设置资金账号 (请替换为您自己的资金账号)
    ContextInfo.accID = '6000000000' 
    ContextInfo.set_account(ContextInfo.accID)
    
    # 策略参数设置
    ContextInfo.ema_period = 9        # 计算波动率EMA的周期
    ContextInfo.sum_period = 25       # 梅斯线求和周期
    ContextInfo.trend_ema_period = 9  # 确认趋势方向的价格EMA周期
    
    # 阈值设置 (Donald Dorsey 推荐值)
    ContextInfo.upper_threshold = 27.0  # 凸起上限
    ContextInfo.lower_threshold = 26.5  # 凸起下限
    
    # 交易数量
    ContextInfo.trade_vol = 100
    
    # 设定股票池 (示例:平安银行)
    ContextInfo.target = '000001.SZ'
    ContextInfo.set_universe([ContextInfo.target])

def get_mass_index(high, low, ema_p, sum_p):
    """
    计算梅斯线指标
    Formula: Sum(EMA(H-L, 9) / EMA(EMA(H-L, 9), 9), 25)
    """
    # 1. 计算高低价差
    hl_range = high - low
    
    # 2. 计算单重指数移动平均 (SEMA)
    sema = hl_range.ewm(span=ema_p, adjust=False).mean()
    
    # 3. 计算双重指数移动平均 (DEMA)
    dema = sema.ewm(span=ema_p, adjust=False).mean()
    
    # 4. 计算比率
    # 注意:避免除以0
    ratio = sema / dema.replace(0, np.nan)
    
    # 5. 计算梅斯线 (滚动求和)
    mass_index = ratio.rolling(window=sum_p).sum()
    
    return mass_index

def handlebar(ContextInfo):
    """
    K线周期运行函数
    """
    # 获取当前主图代码
    stock_code = ContextInfo.target
    
    # 避免在历史K线未走完时重复计算,提高回测效率
    # 如果是实时行情,is_last_bar() 为 True
    if not ContextInfo.is_last_bar():
        return

    # 获取历史行情数据
    # 需要足够的长度来计算 EMA 和 Rolling Sum
    # 长度需求 approx: sum_period + ema_period * 3 (为了EMA收敛)
    count = 100 
    data = ContextInfo.get_market_data_ex(
        ['high', 'low', 'close'], 
        [stock_code], 
        period=ContextInfo.period, 
        count=count, 
        dividend_type='front'
    )
    
    if stock_code not in data or len(data[stock_code]) < count:
        print(f"数据不足: {stock_code}")
        return

    df = data[stock_code]
    
    # --- 指标计算 ---
    
    # 1. 计算梅斯线 (Mass Index)
    df['mass_index'] = get_mass_index(
        df['high'], 
        df['low'], 
        ContextInfo.ema_period, 
        ContextInfo.sum_period
    )
    
    # 2. 计算趋势确认均线 (9-period EMA of Close)
    df['trend_ema'] = df['close'].ewm(span=ContextInfo.trend_ema_period, adjust=False).mean()
    
    # 获取最新和前一根K线的数据
    current_mass = df['mass_index'].iloc[-1]
    prev_mass = df['mass_index'].iloc[-2]
    
    current_close = df['close'].iloc[-1]
    current_trend_ema = df['trend_ema'].iloc[-1]
    
    # --- 绘图 (可选,用于界面观察) ---
    ContextInfo.paint('Mass_Index', current_mass, -1, 0)
    ContextInfo.paint('Threshold_Up', ContextInfo.upper_threshold, -1, 0, 'red')
    ContextInfo.paint('Threshold_Down', ContextInfo.lower_threshold, -1, 0, 'green')

    # --- 交易逻辑 ---
    
    # 判定“反转凸起” (Reversal Bulge)
    # 逻辑:前一根K线梅斯线大于27,且当前K线梅斯线跌破26.5
    # 注意:实际交易中,这个形态可能跨越几根K线,这里简化为两根K线的穿越判断
    is_bulge_pattern = (prev_mass >= ContextInfo.upper_threshold) and \
                       (current_mass < ContextInfo.lower_threshold)
    
    # 获取当前持仓
    positions = ContextInfo.get_trade_detail_data(ContextInfo.accID, 'stock', 'position')
    current_pos = 0
    for pos in positions:
        if pos.m_strInstrumentID + '.' + pos.m_strExchangeID == stock_code:
            current_pos = pos.m_nVolume
            break

    # 生成信号
    if is_bulge_pattern:
        print(f"检测到梅斯线凸起形态: 前值={prev_mass:.2f}, 现值={current_mass:.2f}")
        
        # 1. 买入信号:凸起 + 收盘价在均线之上
        if current_close > current_trend_ema:
            if current_pos == 0:
                print(f"趋势反转向上,触发买入: {stock_code}, 价格: {current_close}")
                passorder(23, 1101, ContextInfo.accID, stock_code, 5, -1, ContextInfo.trade_vol, ContextInfo)
        
        # 2. 卖出信号:凸起 + 收盘价在均线之下
        elif current_close < current_trend_ema:
            if current_pos > 0:
                print(f"趋势反转向下,触发卖出: {stock_code}, 价格: {current_close}")
                passorder(24, 1101, ContextInfo.accID, stock_code, 5, -1, current_pos, ContextInfo)

代码详解

  1. 数据获取 (get_market_data_ex):

    • 我们获取了 high, low, close 数据。
    • count=100 是为了确保有足够的数据来计算 EMA 和 25 周期的滚动求和。如果数据太短,指标初期值为 NaN。
  2. 梅斯线计算 (get_mass_index):

    • SEMA: 计算 (High - Low) 的 9 日指数移动平均。
    • DEMA: 对 SEMA 再次计算 9 日指数移动平均。
    • Ratio: SEMA / DEMA。
    • Mass Index: 对 Ratio 进行 25 周期的滚动求和 (rolling(window=25).sum())。
  3. 趋势确认:

    • 代码中计算了收盘价的 9 日 EMA (trend_ema)。这是梅斯线策略中至关重要的一步,因为梅斯线只告诉你“要变盘了”,但不告诉你“往哪变”。
  4. 交易信号逻辑:

    • 凸起判定 (is_bulge_pattern): 严格遵循定义,检测指标是否从上方穿越 27 后跌破 26.5。
      • prev_mass >= 27.0
      • current_mass < 26.5
    • 买入: 满足凸起条件 且 Close > EMA
    • 卖出: 满足凸起条件 且 Close < EMA
  5. 绘图 (paint):

    • 代码包含了 ContextInfo.paint 函数,运行策略时,你可以在 QMT 的副图指标栏看到梅斯线的走势以及红绿两条阈值线,方便验证策略逻辑。

注意事项

  • 频率选择: 梅斯线通常用于日线级别,但在分钟级别(如 60分钟)也可能有效。请在 ContextInfo.period 中确认周期。
  • 参数调整: 27 和 26.5 是经典参数,针对不同的市场或个股,可能需要调整这些阈值以过滤噪音或捕捉更多信号。
  • 止损: 该策略属于反转策略,风险较高,建议在实盘中加入止损逻辑(例如价格跌破最近低点)。

Q&A

Q: 为什么梅斯线需要计算两次 EMA?
A: 计算两次 EMA (即 DEMA 的分母部分) 是为了平滑数据,消除随机波动,从而更准确地反映波动率的扩缩趋势。

Q: 这个策略适合震荡市还是趋势市?
A: 梅斯线主要用于捕捉趋势反转。它最适合在一段明显的趋势末端使用。如果在窄幅震荡市中,波动率变化不明显,可能很难触发 27 的阈值。

Q: 如何在回测中查看效果?
A: 将代码复制到 QMT 的策略编辑器中,点击“回测”,设置好时间段和标的。观察副图中的指标线是否在价格反转点附近出现了“凸起”形态。