🚀 AI 一键生成 qmt 策略代码

如何用 Python 在 QMT 平台实现 MACD 交易策略?

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

问题描述

请帮我用 Python 编写一个 MACD 指标金叉买入,死叉卖出的交易策略。

解决方案

这是一个基于 QMT 平台的 Python 策略,实现了 MACD 指标金叉买入、死叉卖出 的逻辑。

策略逻辑说明

  1. 指标计算:使用 talib 库计算 MACD 指标(快线 DIF、慢线 DEA、柱状图 MACD)。参数默认为 12, 26, 9。
  2. 金叉(买入信号):当 DIF 线 上穿 DEA 线时。
  3. 死叉(卖出信号):当 DIF 线 下穿 DEA 线时。
  4. 交易执行
    • 产生金叉且当前无持仓 -> 全仓买入。
    • 产生死叉且当前有持仓 -> 清仓卖出。

策略代码

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

def init(ContextInfo):
    """
    初始化函数,策略启动时调用一次
    """
    # 设置资金账号 (请修改为您自己的资金账号)
    ContextInfo.accid = '6000000000' 
    # 设置账号类型:'STOCK'股票, 'FUTURE'期货
    ContextInfo.accountType = 'STOCK'
    
    # 绑定账号,用于接收回报
    ContextInfo.set_account(ContextInfo.accid)
    
    # 设置MACD参数
    ContextInfo.fast_period = 12
    ContextInfo.slow_period = 26
    ContextInfo.signal_period = 9
    
    print("策略初始化完成,MACD参数: {}, {}, {}".format(
        ContextInfo.fast_period, ContextInfo.slow_period, ContextInfo.signal_period))

def handlebar(ContextInfo):
    """
    K线处理函数,每根K线执行一次
    """
    # 获取当前主图的股票代码
    stock_code = ContextInfo.stockcode
    # 获取当前主图的周期
    period = ContextInfo.period
    
    # 1. 获取历史行情数据
    # 获取足够多的数据以确保MACD计算准确,这里取过去100根K线
    data_len = 100
    # 使用 get_market_data_ex 获取数据 (推荐方式)
    # dividend_type='front' 表示前复权,计算技术指标通常使用前复权
    market_data = ContextInfo.get_market_data_ex(
        ['close'], 
        [stock_code], 
        period=period, 
        count=data_len, 
        dividend_type='front'
    )
    
    # 如果数据获取失败或数据量不足,直接返回
    if stock_code not in market_data or len(market_data[stock_code]) < 35:
        return

    # 提取收盘价序列
    close_price = market_data[stock_code]['close']
    # 转为numpy数组,方便talib计算
    close_np = np.array(close_price)
    
    # 2. 计算 MACD 指标
    # diff: 快线, dea: 慢线, macd: 柱状图
    diff, dea, macd = talib.MACD(
        close_np, 
        fastperiod=ContextInfo.fast_period, 
        slowperiod=ContextInfo.slow_period, 
        signalperiod=ContextInfo.signal_period
    )
    
    # 获取当前K线和上一根K线的指标值
    # -1 代表当前最新一根K线(如果是盘中,则是正在跳动的K线)
    # -2 代表上一根K线
    current_diff = diff[-1]
    current_dea = dea[-1]
    last_diff = diff[-2]
    last_dea = dea[-2]
    
    # 检查是否计算出有效值 (剔除NaN)
    if np.isnan(current_diff) or np.isnan(last_diff):
        return

    # 3. 判断金叉和死叉
    # 金叉:上一根K线 DIF < DEA,且当前K线 DIF > DEA
    golden_cross = (last_diff < last_dea) and (current_diff > current_dea)
    
    # 死叉:上一根K线 DIF > DEA,且当前K线 DIF < DEA
    death_cross = (last_diff > last_dea) and (current_diff < current_dea)
    
    # 4. 获取当前持仓情况
    # 注意:回测模式下,ContextInfo.get_position() 可能不可用,需用 get_trade_detail_data
    # 这里为了兼容实盘和回测,简单判断持仓
    positions = ContextInfo.get_trade_detail_data(ContextInfo.accid, ContextInfo.accountType, 'POSITION')
    current_vol = 0
    for pos in positions:
        if pos.m_strInstrumentID == stock_code:
            current_vol = pos.m_nVolume
            break

    # 5. 交易逻辑执行
    # 获取最新价格用于下单
    last_price = close_price.iloc[-1]
    
    # 只有在K线走完时才下单(避免盘中信号闪烁),或者使用 ContextInfo.is_last_bar() 判断
    # 这里演示简单逻辑:如果是回测,每根bar都会执行;如果是实盘,通常在bar结束或tick级判断
    
    # --- 买入逻辑 ---
    if golden_cross:
        if current_vol == 0:
            # 记录日志
            print(f"[{ContextInfo.barpos}] {stock_code} 触发金叉,买入")
            # 绘制信号到图表
            ContextInfo.draw_text(True, last_price, '金叉买入')
            
            # 下单:全仓买入 (这里简单演示买入1000股,实际需根据资金计算)
            # opType=23(买入), orderType=1101(单股单账号普通下单), priceType=5(最新价)
            passorder(23, 1101, ContextInfo.accid, stock_code, 5, -1, 1000, ContextInfo)
            
    # --- 卖出逻辑 ---
    elif death_cross:
        if current_vol > 0:
            # 记录日志
            print(f"[{ContextInfo.barpos}] {stock_code} 触发死叉,卖出")
            # 绘制信号到图表
            ContextInfo.draw_text(True, last_price, '死叉卖出')
            
            # 下单:卖出所有持仓
            # opType=24(卖出)
            passorder(24, 1101, ContextInfo.accid, stock_code, 5, -1, current_vol, ContextInfo)

    # 6. 在副图画出MACD指标 (可选,方便回测观察)
    ContextInfo.paint('DIFF', current_diff, -1, 0, 'white')
    ContextInfo.paint('DEA', current_dea, -1, 0, 'yellow')
    # 画柱状图
    macd_color = 'red' if macd[-1] > 0 else 'green'
    ContextInfo.paint('MACD', macd[-1] * 2, -1, 42, macd_color) # talib算出的macd通常是diff-dea,国内软件习惯显示(diff-dea)*2

使用步骤与注意事项

  1. 新建策略:在 QMT 策略编辑器中新建一个 Python 策略。
  2. 复制代码:将上述代码完整复制进去。
  3. 修改账号:找到 init 函数中的 ContextInfo.accid = '6000000000',将 '6000000000' 修改为您实际的资金账号。
  4. 补充数据
    • 点击 QMT 菜单栏的【数据管理】->【补充数据】。
    • 下载您要回测或交易的股票的历史日线数据(如果跑分钟线则下载分钟数据)。
  5. 运行/回测
    • 回测:点击编辑器上方的“回测”,设置好时间区间、基准和费率,点击开始。
    • 实盘/模拟:在行情图上加载该策略,或者在“模型交易”中运行。

代码关键点解析

  • get_market_data_ex:这是 QMT 获取行情的推荐接口。我们使用了 dividend_type='front'(前复权),因为 MACD 等技术指标在除权除息日如果不复权会出现断层,导致信号失真。
  • talib.MACD:QMT 内置了 talib 库,计算指标非常快。注意 talib 返回的 macd 序列通常是 diff - dea 的值,而国内看盘软件(如通达信、同花顺)显示的 MACD 柱状图通常是 (diff - dea) * 2,所以在画图时我做了 * 2 处理。
  • passorder:这是 QMT 最核心的下单函数。
    • 23: 买入, 24: 卖出。
    • 1101: 代表单股、单账号、按股数下单。
    • 5: 代表使用最新价下单(在回测中通常以收盘价成交,实盘中为最新tick价格)。

常见问题 Q&A

Q: 为什么回测没有交易?
A: 1. 请检查是否下载了对应的历史数据。2. 请检查 ContextInfo.accid 是否设置正确(回测模式下虽然是虚拟账号,但代码逻辑中获取持仓依赖于账号ID)。

Q: 如何调整 MACD 参数?
A: 修改 init 函数中的 ContextInfo.fast_period 等变量即可。

Q: 实盘中如何避免信号闪烁?
A: 上述代码在 handlebar 中运行。在实盘中,handlebar 会随最新的 Tick 跳动。为了避免 K 线没走完就下单(信号闪烁),可以使用 ContextInfo.is_last_bar() 结合时间判断,或者在代码中加入逻辑只在 K 线即将结束时(例如 14:59:50)下单。