🚀 AI 一键生成 qmt 策略代码

如何在 QMT 中实现斐波那契回调交易策略并绘制回调线?

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

问题描述

请帮我编写一个斐波那契回调交易策略,并能在图表上画出回调线

解决方案

这是一个基于 斐波那契回调(Fibonacci Retracement) 的交易策略。

策略逻辑说明

  1. 趋势识别:策略使用一个滚动窗口(例如过去 60 根 K 线)来寻找最高价(High)和最低价(Low)。
    • 如果 最高价出现的时间晚于最低价,判定为 上升趋势(低点 -> 高点),我们关注价格回调时的支撑位。
    • 如果 最低价出现的时间晚于最高价,判定为 下降趋势(高点 -> 低点),我们关注价格反弹时的压力位。
  2. 斐波那契点位:计算关键比例位置:0.0, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0。
    • 重点关注 0.618 (黄金分割位)0.5 作为进场信号。
  3. 绘图功能:使用 ContextInfo.paint() 函数在主图上实时画出这些关键支撑/压力线,方便可视化观察。
  4. 交易信号
    • 做多:在上升趋势中,当价格回调触及 0.618 支撑位时买入。
    • 平仓:当价格跌破 0.786(止损)或 反弹回 0.236(止盈)时平仓。

QMT 策略代码

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

def init(ContextInfo):
    # --- 策略参数设置 ---
    ContextInfo.lookback = 100       # 回溯周期,用于寻找高低点
    ContextInfo.account_id = '6000000000' # 请替换为您的资金账号
    ContextInfo.account_type = 'STOCK'    # 账号类型:'STOCK'股票, 'FUTURE'期货
    
    # 斐波那契数列比例
    ContextInfo.fib_ratios = [0.0, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0]
    
    # 设置股票池(示例)
    ContextInfo.set_universe(['600000.SH']) 
    
    # 绑定账号(实盘必须)
    ContextInfo.set_account(ContextInfo.account_id)

def handlebar(ContextInfo):
    # 获取当前K线位置
    index = ContextInfo.barpos
    
    # 确保有足够的数据进行回溯计算
    if index < ContextInfo.lookback:
        return

    # 获取当前主图品种代码
    stock_code = ContextInfo.stockcode + '.' + ContextInfo.market
    
    # 获取历史行情数据 (High, Low, Close)
    # 注意:count=ContextInfo.lookback + 1 确保取到当前K线
    data = ContextInfo.get_market_data_ex(
        ['high', 'low', 'close'], 
        [stock_code], 
        period=ContextInfo.period, 
        count=ContextInfo.lookback,
        dividend_type='follow'
    )
    
    if stock_code not in data:
        return
        
    df = data[stock_code]
    if df.empty:
        return

    # 计算窗口内的最高价和最低价及其位置
    # argmax/argmin 返回的是相对位置,需要结合索引判断先后
    high_prices = df['high'].values
    low_prices = df['low'].values
    close_prices = df['close'].values
    
    max_price = np.max(high_prices)
    min_price = np.min(low_prices)
    
    max_idx = np.argmax(high_prices) # 最高价在窗口内的索引
    min_idx = np.argmin(low_prices)  # 最低价在窗口内的索引
    
    current_price = close_prices[-1]
    
    # --- 斐波那契计算逻辑 ---
    # 定义趋势方向:
    # 如果 最高价索引 > 最低价索引,说明是先低后高,为上升趋势(寻找回调支撑)
    # 如果 最高价索引 < 最低价索引,说明是先高后低,为下降趋势(寻找反弹压力)
    
    is_uptrend = max_idx > min_idx
    price_range = max_price - min_price
    
    fib_levels = {}
    
    if is_uptrend:
        # 上升趋势:0 是最高点,1 是最低点,回调是向下走
        # Level = Max - Range * Ratio
        for ratio in ContextInfo.fib_ratios:
            level_price = max_price - (price_range * ratio)
            fib_levels[ratio] = level_price
            
            # 绘图:在主图绘制线条
            # 0.618 和 0.5 重点显示为黄色和洋红色,其他为白色
            color = 'white'
            if ratio == 0.618: color = 'yellow'
            if ratio == 0.5: color = 'magenta'
            
            # paint(指标名, 数值, 位置(-1为主图), 线型(0为曲线), 颜色)
            ContextInfo.paint(f'Fib_{ratio}', level_price, -1, 0, color)
            
    else:
        # 下降趋势:0 是最低点,1 是最高点,反弹是向上走
        # Level = Min + Range * Ratio
        for ratio in ContextInfo.fib_ratios:
            level_price = min_price + (price_range * ratio)
            fib_levels[ratio] = level_price
            
            color = 'white'
            if ratio == 0.618: color = 'yellow'
            if ratio == 0.5: color = 'magenta'
            
            ContextInfo.paint(f'Fib_{ratio}', level_price, -1, 0, color)

    # --- 简单的交易逻辑示例 (仅针对上升趋势的回调做多) ---
    # 实际交易请务必增加风控和仓位管理
    
    if not ContextInfo.is_last_bar():
        # 回测或历史K线只计算不交易,或者使用 passorder 的 quickTrade=0
        # 这里为了演示逻辑,仅在最后一根K线判断
        pass
    
    # 获取当前持仓
    positions = ContextInfo.get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
    long_pos = 0
    for pos in positions:
        if pos.m_strInstrumentID == ContextInfo.stockcode:
            long_pos = pos.m_nVolume
            
    # 交易信号:上升趋势中,价格回调触及 0.618 支撑位
    if is_uptrend:
        buy_level = fib_levels[0.618]
        stop_loss = fib_levels[0.786]
        take_profit = fib_levels[0.236]
        
        # 信号:当前价格跌破 0.618 但仍在 0.786 之上 (视为回调到位)
        # 且当前没有持仓
        if long_pos == 0 and current_price <= buy_level * 1.005 and current_price >= stop_loss:
            print(f"触发买入信号: 价格 {current_price} 接近 Fib 0.618 {buy_level}")
            # 全仓买入示例 (请根据实际情况调整 volume)
            passorder(23, 1101, ContextInfo.account_id, stock_code, 5, -1, 100, ContextInfo)
            
        # 止损/止盈逻辑
        elif long_pos > 0:
            if current_price < stop_loss:
                print(f"触发止损: 价格 {current_price} 跌破 Fib 0.786 {stop_loss}")
                passorder(24, 1101, ContextInfo.account_id, stock_code, 5, -1, long_pos, ContextInfo)
            elif current_price > take_profit:
                print(f"触发止盈: 价格 {current_price} 超过 Fib 0.236 {take_profit}")
                passorder(24, 1101, ContextInfo.account_id, stock_code, 5, -1, long_pos, ContextInfo)

代码关键点解析

  1. ContextInfo.paint

    • 这是 QMT 特有的绘图函数。
    • f'Fib_{ratio}':给每条线起一个唯一的名字(如 Fib_0.618),这样在图表指标栏可以看到数值。
    • -1:表示画在主图上(和 K 线叠加)。
    • color:我将 0.618 设为黄色,0.5 设为洋红色,以便视觉上突出重点支撑位。
  2. 滚动窗口 (lookback)

    • 斐波那契回调依赖于“高点”和“低点”的定义。代码中使用了 ContextInfo.lookback = 100,意味着它会动态计算过去 100 根 K 线内的最高和最低点。
    • 注意:随着 K 线推进,高低点可能会发生变化,导致斐波那契线随之移动。这是动态策略的特性。
  3. 趋势判断 (is_uptrend)

    • 通过比较最高价索引 (max_idx) 和最低价索引 (min_idx) 的大小来判断当前是处于上升波段后的回调,还是下降波段后的反弹。
  4. 交易执行 (passorder)

    • 代码中包含了一个简单的做多逻辑:在上升趋势中,当价格回调到 0.618 附近(buy_level * 1.005 范围内)且未跌破 0.786 时买入。
    • 止损设在 0.786,止盈设在 0.236。

如何使用

  1. 打开 QMT 客户端,进入 模型研究/策略编辑器
  2. 新建一个 Python 策略。
  3. 将上述代码复制粘贴进去。
  4. 修改 ContextInfo.account_id 为你的实际资金账号。
  5. 点击 运行 或在图表上 叠加指标。你会看到主图上出现了多条斐波那契水平线,随着行情波动而变化。

常见问题 (Q&A)

Q: 为什么画出来的线是变动的?
A: 因为代码使用的是滚动窗口(过去100根K线)。当新的K线产生,旧的K线移出窗口,或者产生了新的最高/最低点时,计算基准就会改变。如果你需要固定某个日期的最高低点,需要修改代码指定固定的 start_timeend_time 来获取数据。

Q: 如何只画线不交易?
A: 你可以注释掉 passorder 相关的代码块,或者直接删除 if long_pos == 0 ... 之后的交易逻辑部分,这样策略就变成了一个纯粹的技术分析指标工具。

Q: get_market_data_ex 获取不到数据怎么办?
A: 请确保在 QMT 的“数据管理”中下载了对应品种和周期的历史数据。如果没有下载数据,API 返回的 DataFrame 可能是空的。