🚀 AI 一键生成 qmt 策略代码

QMT 择时策略:如何构建一个市场状态(牛/熊/震荡)识别模型?

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

问题描述

如何编写一个能判断当前是牛市、熊市还是震荡市,并据此调整交易行为的策略?

解决方案

这是一个非常经典的量化交易场景。要实现一个能区分“牛市”、“熊市”和“震荡市”的策略,我们需要结合趋势指标(判断方向)和波动率/趋势强度指标(判断状态)。

策略设计思路

  1. 市场状态定义

    • 趋势判断(方向):使用双均线系统(MA)。
      • 短期均线 > 长期均线 $\rightarrow$ 潜在多头(牛市)。
      • 短期均线 < 长期均线 $\rightarrow$ 潜在空头(熊市)。
    • 趋势强度判断(状态):使用 ADX (平均趋向指数)
      • ADX > 阈值(如25):代表当前处于强趋势状态(确认为牛市或熊市)。
      • ADX < 阈值(如25):代表当前处于无趋势/震荡状态(震荡市)。
  2. 交易行为调整

    • 牛市 (Bull):执行趋势跟随策略,全仓买入或持有。
    • 熊市 (Bear):执行风控策略,清仓卖出或空仓观望。
    • 震荡市 (Sideways):执行均值回归策略(如布林带),高抛低吸,或者为了安全起见选择空仓。

QMT 策略代码实现

以下是一个完整的 Python 策略代码。该策略使用 talib 库计算指标,使用 get_market_data_ex 获取数据。

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

def init(ContextInfo):
    """
    策略初始化函数
    """
    # 1. 设置交易账号 (请替换为您自己的资金账号)
    ContextInfo.account_id = '您的资金账号'
    ContextInfo.account_type = 'STOCK' # 股票账户
    ContextInfo.set_account(ContextInfo.account_id)
    
    # 2. 设置标的股票 (示例:浦发银行)
    ContextInfo.stock_code = '600000.SH'
    
    # 3. 策略参数设置
    ContextInfo.ma_short_period = 20   # 短期均线周期
    ContextInfo.ma_long_period = 60    # 长期均线周期
    ContextInfo.adx_period = 14        # ADX周期
    ContextInfo.adx_threshold = 25     # ADX阈值,大于此值认为有强趋势
    
    # 4. 运行周期设置 (日线)
    ContextInfo.period = '1d'
    
    # 5. 震荡市参数 (布林带)
    ContextInfo.boll_period = 20
    ContextInfo.boll_dev = 2

def handlebar(ContextInfo):
    """
    K线周期运行函数
    """
    # 获取当前K线位置,如果是回测模式,避免在数据不足时计算
    index = ContextInfo.barpos
    if index < ContextInfo.ma_long_period + 5:
        return

    # 1. 获取历史行情数据 (多取一些数据以保证指标计算准确)
    # 使用 get_market_data_ex 接口获取数据
    data_count = 100 
    market_data = ContextInfo.get_market_data_ex(
        ['high', 'low', 'close'], 
        [ContextInfo.stock_code], 
        period=ContextInfo.period, 
        count=data_count, 
        dividend_type='front' # 前复权
    )
    
    if ContextInfo.stock_code not in market_data:
        return
        
    df = market_data[ContextInfo.stock_code]
    
    # 转换数据格式为 numpy array,供 talib 使用
    high_prices = df['high'].values
    low_prices = df['low'].values
    close_prices = df['close'].values
    
    # 2. 计算技术指标
    # 计算均线
    ma_short = talib.SMA(close_prices, timeperiod=ContextInfo.ma_short_period)
    ma_long = talib.SMA(close_prices, timeperiod=ContextInfo.ma_long_period)
    
    # 计算 ADX (趋势强度)
    adx = talib.ADX(high_prices, low_prices, close_prices, timeperiod=ContextInfo.adx_period)
    
    # 计算布林带 (用于震荡市)
    upper, middle, lower = talib.BBANDS(close_prices, timeperiod=ContextInfo.boll_period, nbdevup=ContextInfo.boll_dev, nbdevdn=ContextInfo.boll_dev)
    
    # 获取最新一根K线的指标值
    current_ma_short = ma_short[-1]
    current_ma_long = ma_long[-1]
    current_adx = adx[-1]
    current_close = close_prices[-1]
    current_upper = upper[-1]
    current_lower = lower[-1]
    
    # 3. 判断市场状态
    market_state = "UNKNOWN"
    
    # 逻辑:ADX > 阈值 代表有趋势,否则为震荡
    if current_adx > ContextInfo.adx_threshold:
        if current_ma_short > current_ma_long:
            market_state = "BULL" # 牛市 (强趋势 + 多头排列)
        else:
            market_state = "BEAR" # 熊市 (强趋势 + 空头排列)
    else:
        market_state = "SIDEWAYS" # 震荡市 (无明显趋势)
        
    # 4. 获取当前持仓
    positions = ContextInfo.get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
    current_holding = 0
    for pos in positions:
        if pos.m_strInstrumentID + '.' + pos.m_strExchangeID == ContextInfo.stock_code:
            current_holding = pos.m_nVolume
            break

    # 5. 根据市场状态调整交易行为
    print(f"时间: {df.index[-1]}, 状态: {market_state}, ADX: {current_adx:.2f}, Close: {current_close}")

    # --- 场景 A: 牛市 (趋势跟随) ---
    if market_state == "BULL":
        # 策略:全仓买入或持有
        if current_holding == 0:
            print(">>> 牛市确立,执行买入")
            # 示例:按最新价买入 1000 股
            passorder(23, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, 1000, ContextInfo)
    
    # --- 场景 B: 熊市 (风险控制) ---
    elif market_state == "BEAR":
        # 策略:清仓止损
        if current_holding > 0:
            print(">>> 熊市确立,执行清仓")
            passorder(24, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, current_holding, ContextInfo)
            
    # --- 场景 C: 震荡市 (高抛低吸) ---
    elif market_state == "SIDEWAYS":
        # 策略:布林带回归
        # 价格跌破下轨 -> 买入 (博反弹)
        if current_close < current_lower and current_holding == 0:
            print(">>> 震荡市触及下轨,执行低吸")
            passorder(23, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, 1000, ContextInfo)
            
        # 价格突破上轨 -> 卖出 (止盈)
        elif current_close > current_upper and current_holding > 0:
            print(">>> 震荡市触及上轨,执行高抛")
            passorder(24, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, current_holding, ContextInfo)

代码核心逻辑解析

  1. 数据获取 (get_market_data_ex)

    • 我们获取了 high, low, close 三种价格,因为计算 ADX 指标需要最高价和最低价,而不仅仅是收盘价。
    • count=100 确保有足够的历史数据来计算 60 日均线和 ADX。
  2. 指标计算 (talib)

    • SMA (均线):用于判断大方向。
    • ADX (平均趋向指数):这是区分“单边市”和“震荡市”的关键。ADX 数值越高,代表趋势越强(无论涨跌);ADX 数值低,代表市场处于无序波动。
    • BBANDS (布林带):仅在震荡市逻辑中使用,利用价格在上下轨之间的回归特性进行交易。
  3. 状态机逻辑

    • 代码通过 if-elif-else 结构构建了一个简单的状态机。
    • 优先级:首先判断是否有趋势 (ADX),再判断趋势方向 (MA)。
  4. 交易执行 (passorder)

    • 23 代表买入,24 代表卖出。
    • 1101 代表单股、单账号、普通模式下单。
    • 5 代表使用最新价(对手价)下单,保证成交速度。

如何优化此策略?

  1. 参数调优:ADX 的阈值(默认25)和均线周期(20/60)对不同标的敏感度不同,可以通过回测寻找最优参数。
    • 大盘股可能适合较低的 ADX 阈值(如 20)。
    • 妖股/高波动股可能需要更高的阈值(如 30)来过滤假突破。
  2. 仓位管理
    • 当前代码是简单的固定股数(1000股)。
    • 进阶:牛市时仓位可设为 80%-100%,震荡市限制在 30%-50%,熊市 0%。
  3. 增加过滤器
    • 在震荡市中,可以加入 RSI 或 KDJ 指标辅助判断超买超卖,避免在布林带开口扩大(趋势形成初期)时逆势操作。

Q&A

Q: 为什么选择 ADX 而不是仅仅看均线缠绕来判断震荡?
A: 均线缠绕是滞后指标,且很难量化“缠绕”的程度。ADX 是专门设计用来衡量趋势强度的指标,它能给出一个具体的数值(0-100),非常适合量化策略进行阈值判断(例如 ADX < 20 明确定义为弱趋势)。

Q: 代码中的 passorder 函数在回测和实盘中都能用吗?
A: 是的。passorder 是 QMT 的核心下单函数。在回测模式下,它会模拟撮合;在实盘模式下(需绑定实盘账号),它会发送真实的委托指令。

Q: 如何将此策略应用到一篮子股票(如沪深300成分股)?
A: 你需要在 init 中使用 ContextInfo.get_sector('000300.SH') 获取成分股列表,然后在 handlebar 中使用 for 循环遍历每只股票,分别获取数据、计算指标并下单。注意要控制循环内的计算量,避免超时。