🚀 AI 一键生成 qmt 策略代码

如何用 Python 在 QMT 中实现 SuperTrend 超级趋势策略?

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

问题描述

请帮我用 Python 实现一个 SuperTrend 指标交易策略

解决方案

这是一个基于 QMT (Quantitative Trading Platform) API 实现的 SuperTrend (超级趋势) 指标交易策略。

策略逻辑说明

SuperTrend 是一种趋势跟踪指标,类似于移动止损线。它结合了价格和波动率(ATR)来确定趋势方向。

  1. 计算 ATR (平均真实波幅):用于衡量市场波动率。
  2. 计算上下轨
    • 上轨 (Upper Band) = (最高价 + 最低价) / 2 + (倍数 * ATR)
    • 下轨 (Lower Band) = (最高价 + 最低价) / 2 - (倍数 * ATR)
  3. SuperTrend 计算逻辑
    • 当收盘价高于 SuperTrend 线时,趋势为多头(显示为绿色/下轨支撑)。
    • 当收盘价低于 SuperTrend 线时,趋势为空头(显示为红色/上轨压力)。
  4. 交易信号
    • 买入:当价格突破 SuperTrend 线转为多头趋势时。
    • 卖出/平仓:当价格跌破 SuperTrend 线转为空头趋势时。

Python 策略代码

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

def init(ContextInfo):
    """
    策略初始化函数
    """
    # --- 策略参数设置 ---
    ContextInfo.stock_code = '600000.SH'  # 交易标的:浦发银行
    ContextInfo.account_id = '600000248'  # 请替换为您的资金账号
    ContextInfo.account_type = 'STOCK'    # 账号类型:STOCK-股票, FUTURE-期货
    
    ContextInfo.period = '1d'             # 运行周期:1d 代表日线
    ContextInfo.atr_period = 10           # ATR 计算周期
    ContextInfo.factor = 3.0              # SuperTrend 倍数因子 (通常为 3.0)
    ContextInfo.trade_vol = 100           # 每次交易数量 (股)
    
    # 设置股票池 (必须设置,否则无法获取历史数据)
    ContextInfo.set_universe([ContextInfo.stock_code])
    
    # 绑定账号 (实盘/模拟盘必须)
    ContextInfo.set_account(ContextInfo.account_id)

def calculate_supertrend(df, period, factor):
    """
    计算 SuperTrend 指标
    """
    # 1. 计算 TR (真实波幅)
    df['hl'] = df['high'] - df['low']
    df['hc'] = abs(df['high'] - df['close'].shift(1))
    df['lc'] = abs(df['low'] - df['close'].shift(1))
    df['tr'] = df[['hl', 'hc', 'lc']].max(axis=1)
    
    # 2. 计算 ATR
    df['atr'] = df['tr'].rolling(window=period).mean()
    
    # 3. 计算基础上下轨
    df['basic_upper'] = (df['high'] + df['low']) / 2 + factor * df['atr']
    df['basic_lower'] = (df['high'] + df['low']) / 2 - factor * df['atr']
    
    # 4. 初始化最终上下轨和趋势
    df['final_upper'] = df['basic_upper']
    df['final_lower'] = df['basic_lower']
    df['supertrend'] = 0.0
    df['trend'] = True # True 为多头, False 为空头
    
    # 5. 迭代计算最终轨道 (因为当前值依赖于前一个值,难以完全向量化)
    # 注意:为了效率,实盘中通常只计算最近的一段数据
    for i in range(period, len(df)):
        # 计算 Final Upper
        if (df['basic_upper'].iloc[i] < df['final_upper'].iloc[i-1]) or \
           (df['close'].iloc[i-1] > df['final_upper'].iloc[i-1]):
            df.at[df.index[i], 'final_upper'] = df['basic_upper'].iloc[i]
        else:
            df.at[df.index[i], 'final_upper'] = df['final_upper'].iloc[i-1]
            
        # 计算 Final Lower
        if (df['basic_lower'].iloc[i] > df['final_lower'].iloc[i-1]) or \
           (df['close'].iloc[i-1] < df['final_lower'].iloc[i-1]):
            df.at[df.index[i], 'final_lower'] = df['basic_lower'].iloc[i]
        else:
            df.at[df.index[i], 'final_lower'] = df['final_lower'].iloc[i-1]
            
        # 计算 SuperTrend 和 Trend 方向
        prev_trend = df['trend'].iloc[i-1]
        curr_close = df['close'].iloc[i]
        
        if prev_trend: # 之前是多头
            if curr_close <= df['final_lower'].iloc[i]:
                df.at[df.index[i], 'trend'] = False # 转空
                df.at[df.index[i], 'supertrend'] = df['final_upper'].iloc[i]
            else:
                df.at[df.index[i], 'trend'] = True # 保持多
                df.at[df.index[i], 'supertrend'] = df['final_lower'].iloc[i]
        else: # 之前是空头
            if curr_close >= df['final_upper'].iloc[i]:
                df.at[df.index[i], 'trend'] = True # 转多
                df.at[df.index[i], 'supertrend'] = df['final_lower'].iloc[i]
            else:
                df.at[df.index[i], 'trend'] = False # 保持空
                df.at[df.index[i], 'supertrend'] = df['final_upper'].iloc[i]
                
    return df

def get_position(ContextInfo, stock_code):
    """
    获取当前持仓数量
    """
    # 获取持仓对象列表
    positions = get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
    for pos in positions:
        if pos.m_strInstrumentID == stock_code:
            return pos.m_nVolume
    return 0

def handlebar(ContextInfo):
    """
    K线周期回调函数
    """
    # 过滤掉未完成的K线 (如果是日线策略,通常在收盘或次日开盘交易)
    # 如果是回测模式,ContextInfo.is_last_bar() 为 True 时才代表当前K线走完
    # 如果是实盘,通常建议使用定时器或在尾盘触发,这里演示标准逻辑
    
    index = ContextInfo.barpos
    realtime = ContextInfo.get_bar_timetag(index)
    
    # 1. 获取历史行情数据
    # 获取足够长的数据以计算 ATR 和 趋势迭代,这里取 100 根
    count = 100 
    data_dict = ContextInfo.get_market_data_ex(
        ['open', 'high', 'low', 'close'], 
        [ContextInfo.stock_code], 
        period=ContextInfo.period, 
        count=count,
        dividend_type='front' # 前复权
    )
    
    if ContextInfo.stock_code not in data_dict:
        return
        
    df = data_dict[ContextInfo.stock_code]
    
    # 数据长度不足无法计算
    if len(df) < ContextInfo.atr_period + 2:
        return

    # 2. 计算 SuperTrend
    df = calculate_supertrend(df, ContextInfo.atr_period, ContextInfo.factor)
    
    # 获取最新一根K线的数据(当前 bar)
    current_bar = df.iloc[-1]
    # 获取前一根K线的数据(用于判断交叉)
    prev_bar = df.iloc[-2]
    
    # 3. 获取当前持仓
    curr_pos = get_position(ContextInfo, ContextInfo.stock_code)
    
    # 4. 生成交易信号
    # 信号逻辑:
    # 趋势由 False 变为 True (价格上穿 SuperTrend) -> 买入
    # 趋势由 True 变为 False (价格下穿 SuperTrend) -> 卖出
    
    signal_buy = (prev_bar['trend'] == False) and (current_bar['trend'] == True)
    signal_sell = (prev_bar['trend'] == True) and (current_bar['trend'] == False)
    
    # 5. 执行交易
    # 买入逻辑
    if signal_buy and curr_pos == 0:
        print(f"[{timetag_to_datetime(realtime, '%Y-%m-%d %H:%M:%S')}] 触发买入信号: 价格 {current_bar['close']}")
        passorder(23, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, ContextInfo.trade_vol, ContextInfo)
        
    # 卖出逻辑
    elif signal_sell and curr_pos > 0:
        print(f"[{timetag_to_datetime(realtime, '%Y-%m-%d %H:%M:%S')}] 触发卖出信号: 价格 {current_bar['close']}")
        passorder(24, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, curr_pos, ContextInfo)

    # 6. (可选) 在图表上画线,方便回测观察
    ContextInfo.paint('SuperTrend', current_bar['supertrend'], -1, 0)

代码关键点解析

  1. calculate_supertrend 函数

    • 这是策略的核心。由于 QMT 内置库(如 talib)通常没有直接的 SuperTrend 函数,我们需要手动实现。
    • 迭代计算:SuperTrend 的一个特性是当前的轨道值依赖于前一个时间点的轨道值(为了保持轨道平滑且不反向移动),因此代码中使用了 for 循环从 period 开始遍历数据。
    • 趋势判断:通过比较 closefinal_upper/final_lower 来动态更新 trend 状态。
  2. 数据获取 (get_market_data_ex)

    • 我们获取了 count=100 的数据。这是为了确保 ATR 计算稳定,并且有足够的历史数据来让 SuperTrend 的迭代逻辑收敛。
    • 使用了 'front' (前复权),这在股票回测中非常重要,避免因分红派息导致的价格跳空触发错误信号。
  3. 交易执行 (passorder)

    • 23 代表买入,24 代表卖出。
    • 1101 代表单股、单账号、普通交易。
    • 5 代表使用最新价下单(市价成交的近似模拟),价格填 -1
  4. 持仓检查 (get_position)

    • 在开仓前检查 curr_pos == 0 防止重复开仓。
    • 在平仓前检查 curr_pos > 0 确保有仓可平。

如何在 QMT 中使用

  1. 打开 QMT 客户端,进入【模型研究】或【策略编辑器】。
  2. 新建一个 Python 策略。
  3. 将上述代码完整复制粘贴进去。
  4. 修改参数
    • ContextInfo.account_id 修改为您实际的资金账号。
    • 根据需要修改 stock_code(标的)和 period(周期)。
  5. 回测:点击“回测”按钮,设置时间区间,观察策略表现。
  6. 实盘:在实盘运行前,请务必先在模拟盘验证逻辑,并确保账号已登录。

Q&A

Q: 为什么 SuperTrend 计算部分要用循环,不能直接用 Pandas 向量化吗?
A: SuperTrend 的定义中包含递归逻辑(即:当前的 Final Upper Band 依赖于前一个时间点的 Final Upper Band)。虽然可以通过复杂的 NumPy 逻辑部分向量化,但在 Python 策略中,使用显式的循环逻辑更清晰、更不容易出错,且对于 100 行左右的数据量,性能影响可以忽略不计。

Q: 这个策略可以用于期货吗?
A: 可以。需要做以下修改:

  1. ContextInfo.account_type 改为 'FUTURE'
  2. passorder 的参数需要调整为期货的开平仓指令(如 0 开多, 1 平昨多 等)。
  3. 标的代码需要改为期货合约代码(如 'IF2306.IF')。

Q: 如何调整策略的灵敏度?
A: 修改 init 函数中的:

  • ContextInfo.atr_period:数值越小,ATR 变化越快,指标越灵敏。
  • ContextInfo.factor:数值越小(如 2.0),轨道离价格越近,交易信号越频繁;数值越大(如 4.0),轨道越远,过滤震荡效果越好,但信号会滞后。