🚀 AI 一键生成 qmt 策略代码

如何利用 Choppiness Index 识别震荡市并过滤假突破?

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

问题描述

怎么用代码判断当前是震荡市还是趋势市?

解决方案

判断当前市场处于“震荡市”(盘整)还是“趋势市”(单边上涨或下跌)是量化策略中至关重要的一环。没有一种绝对完美的方法,但通常使用以下几种技术指标或统计方法来进行量化判定:

  1. ADX (平均趋向指标):这是最常用的判断趋势强度的指标。通常 ADX > 25 视为趋势市,ADX < 20 视为震荡市。
  2. 均线排列与斜率:短期均线与长期均线的相对位置(多头/空头排列)以及均线的斜率(平坦则为震荡)。
  3. 布林带带宽 (Bandwidth):布林带收口通常意味着波动率降低,进入震荡;开口扩大通常意味着趋势开始。
  4. 考夫曼效率系数 (Efficiency Ratio):衡量价格变动的效率,效率高为趋势,效率低为震荡。

下面我将提供一个基于 QMT 平台 的完整 Python 策略代码示例。该代码主要使用 ADX 指标 结合 均线系统 来判断当前的市场状态。

QMT 策略代码示例

此代码可以直接在 QMT 的策略编辑器中运行。它会计算 ADX 和均线,并在日志中输出当前的判断结果。

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

def init(ContextInfo):
    # 设置要操作的股票,这里以沪深300ETF为例
    ContextInfo.stock = '510300.SH'
    ContextInfo.set_universe([ContextInfo.stock])
    
    # 策略参数设置
    ContextInfo.adx_period = 14       # ADX计算周期
    ContextInfo.adx_threshold = 25    # ADX趋势阈值,大于此值认为有趋势
    ContextInfo.ma_short_period = 20  # 短期均线
    ContextInfo.ma_long_period = 60   # 长期均线
    
    # 设置账号(仅作示例,实盘需替换真实账号)
    ContextInfo.account_id = 'test_account'
    ContextInfo.account_type = 'STOCK'

def handlebar(ContextInfo):
    # 获取当前K线位置
    index = ContextInfo.barpos
    
    # 获取当前主图代码(或者使用init中设置的代码)
    stock_code = ContextInfo.stock
    
    # 获取历史行情数据,长度需要满足计算指标的需求
    # 我们需要计算 MA60,且需要一定的缓冲,所以取 100 根 K 线
    data_len = 100
    
    # 使用 get_market_data_ex 获取数据 (推荐使用 ex 接口)
    # 注意:这里获取的是日线数据,如果在分钟线运行,period需改为 '1m', '5m' 等
    market_data = ContextInfo.get_market_data_ex(
        ['high', 'low', 'close'], 
        [stock_code], 
        period=ContextInfo.period, 
        count=data_len,
        dividend_type='front', # 前复权
        subscribe=True
    )
    
    if stock_code not in market_data:
        return

    df = market_data[stock_code]
    
    # 确保数据长度足够计算指标
    if len(df) < ContextInfo.ma_long_period:
        return

    # 提取 numpy 数组用于 talib 计算
    high_prices = df['high'].values
    low_prices = df['low'].values
    close_prices = df['close'].values
    
    # --- 方法 1:使用 ADX 指标判断趋势强度 ---
    # ADX 反映的是趋势的“强度”,而不是方向
    adx = talib.ADX(high_prices, low_prices, close_prices, timeperiod=ContextInfo.adx_period)
    current_adx = adx[-1]
    
    # --- 方法 2:使用均线排列判断趋势方向 ---
    ma_short = talib.SMA(close_prices, timeperiod=ContextInfo.ma_short_period)
    ma_long = talib.SMA(close_prices, timeperiod=ContextInfo.ma_long_period)
    
    current_ma_short = ma_short[-1]
    current_ma_long = ma_long[-1]
    current_close = close_prices[-1]
    
    # --- 综合判定逻辑 ---
    market_status = "未知"
    trend_direction = "无"
    
    # 判定 1: 震荡市判定
    # 如果 ADX 低于阈值,或者 均线纠缠(这里简化为收盘价在两根均线之间),视为震荡
    if current_adx < ContextInfo.adx_threshold:
        market_status = "震荡市 (ADX弱)"
    elif (current_close > min(current_ma_short, current_ma_long)) and (current_close < max(current_ma_short, current_ma_long)):
        market_status = "震荡市 (均线夹层)"
    else:
        market_status = "趋势市"
        
        # 如果是趋势市,进一步判断方向
        if current_close > current_ma_short and current_ma_short > current_ma_long:
            trend_direction = "上涨趋势 (多头排列)"
        elif current_close < current_ma_short and current_ma_short < current_ma_long:
            trend_direction = "下跌趋势 (空头排列)"
        else:
            trend_direction = "趋势不明 (均线未完全发散)"

    # --- 输出结果 ---
    # 仅在最后一根K线打印,避免历史回测刷屏
    if ContextInfo.is_last_bar():
        timetag = ContextInfo.get_bar_timetag(index)
        date_str = timetag_to_datetime(timetag, '%Y-%m-%d %H:%M:%S')
        
        print(f"时间: {date_str} | 标的: {stock_code}")
        print(f"ADX值: {current_adx:.2f} (阈值: {ContextInfo.adx_threshold})")
        print(f"收盘价: {current_close:.2f} | MA{ContextInfo.ma_short_period}: {current_ma_short:.2f} | MA{ContextInfo.ma_long_period}: {current_ma_long:.2f}")
        print(f"判定结果: 【{market_status}】 {trend_direction if market_status == '趋势市' else ''}")
        print("-" * 30)

    # --- 可视化绘图 (可选) ---
    # 在图表上绘制 ADX 指标,方便观察
    # 注意:ADX 值通常在 0-100 之间,与股价坐标轴不同,建议在副图查看或使用 limit='noaxis'
    # ContextInfo.paint('ADX', current_adx, -1, 0) 

代码逻辑详解

  1. 数据获取 (get_market_data_ex):

    • 我们获取了 high (最高价), low (最低价), close (收盘价)。
    • count=100 确保有足够的数据来计算 60 日均线和 ADX。
    • dividend_type='front' 使用前复权数据,这对于技术指标计算非常重要,可以消除分红配股造成的缺口。
  2. ADX 指标计算:

    • 使用 talib.ADX 函数。
    • 原理: ADX (Average Directional Index) 衡量趋势的强度。
    • 判定:
      • ADX < 25 (或 20) 时,通常意味着市场处于无趋势或弱趋势状态(震荡)。
      • ADX > 25 且 ADX 正在上升时,意味着趋势正在增强(无论是上涨还是下跌)。
  3. 均线系统 (MA):

    • 计算短期 (MA20) 和长期 (MA60) 均线。
    • 辅助判定: 即使 ADX 很高,我们也可以结合均线来确认方向。
      • 多头排列: 价格 > 短期均线 > 长期均线 (强上涨趋势)。
      • 空头排列: 价格 < 短期均线 < 长期均线 (强下跌趋势)。
      • 夹层: 价格位于两条均线之间,通常是震荡整理的表现。
  4. 综合判定:

    • 代码优先判断 ADX 是否过低。如果过低,直接定性为震荡。
    • 如果 ADX 较高,再看均线形态来确定是上涨趋势还是下跌趋势。

其他可选的判定方法(思路)

如果你觉得 ADX 指标滞后,可以尝试修改代码实现以下逻辑:

  • 布林带带宽法:

    upper, middle, lower = talib.BBANDS(close_prices, timeperiod=20)
    bandwidth = (upper[-1] - lower[-1]) / middle[-1]
    # 如果 bandwidth 处于历史低位(例如小于 0.05 或 5%),通常是极度缩量的震荡市
    
  • 近期波动率法 (ATR):

    atr = talib.ATR(high_prices, low_prices, close_prices, timeperiod=14)
    # 如果 ATR 持续走低,代表波动率下降,市场趋于震荡
    
  • 价格区间法 (Donchian Channel):
    计算过去 N 天的最高价和最低价。如果 (最高价 - 最低价) / 收盘价 非常小,说明价格被压缩在一个很窄的箱体内,即为震荡。

常见问题 (Q&A)

Q: 为什么我的 ADX 计算结果一直是 nan?
A: 通常是因为获取的数据长度不够。ADX 需要较长的预热数据。如果你设置周期为 14,建议至少获取 30-50 根 K 线数据。在上面的代码中,我设置了 count=100 来避免这个问题。

Q: 这个策略适合什么周期?
A: 震荡和趋势是相对于周期而言的。日线级别的震荡在 5 分钟级别可能是一个明显的趋势。代码中使用了 ContextInfo.period,这意味着它会自动适应你在 QMT 界面上选择的周期。

Q: 如何在震荡市中避免频繁开仓止损?
A: 在代码的 handlebar 中,你可以加入过滤逻辑:if market_status == "震荡市": return。这样策略在震荡市就会停止交易,或者你可以切换到专门做高抛低吸的网格策略逻辑。