🚀 AI 一键生成 qmt 策略代码

如何利用布林带在5分钟K线上构建一个日内均值回归策略?(附 Python 源码)

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

问题描述

请帮我写一个日内交易策略,在5分钟图上,当价格跌破布林带下轨时买入,涨破上轨时卖出。

解决方案

这是一个基于QMT平台的布林带(Bollinger Bands)均值回归日内交易策略。

策略逻辑分析

  1. 运行周期:5分钟 K 线(5m)。
  2. 指标计算
    • 中轨:20周期收盘价移动平均线 (MA20)。
    • 上轨:中轨 + 2倍标准差。
    • 下轨:中轨 - 2倍标准差。
  3. 交易信号
    • 买入:当收盘价跌破下轨(Close < Lower Band),且当前无持仓时,全仓买入。
    • 卖出:当收盘价突破上轨(Close > Upper Band),且当前有持仓时,清仓卖出。
  4. 日内风控
    • 为了符合“日内交易”的要求,策略会在每日收盘前(例如 14:55)强制平仓,避免隔夜风险。

策略代码实现

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

def init(ContextInfo):
    """
    初始化函数,策略启动时调用一次
    """
    # 设置账号,请替换为您自己的资金账号
    # 格式如 '6000000248',如果是模拟盘请确保账号正确
    ContextInfo.account_id = '您的资金账号'
    ContextInfo.account_type = 'STOCK' # 账号类型:'STOCK'股票, 'FUTURE'期货
    
    # 设置交易账号
    ContextInfo.set_account(ContextInfo.account_id)
    
    # 策略参数设置
    ContextInfo.period = 20      # 布林带周期
    ContextInfo.std_dev = 2      # 标准差倍数
    ContextInfo.freq = '5m'      # 运行周期 5分钟
    
    # 设置股票池,这里默认取当前图表的股票
    # 如果需要固定股票,可以使用 ContextInfo.set_universe(['600000.SH'])
    ContextInfo.stock_code = ContextInfo.stockcode + '.' + ContextInfo.market
    
    print("策略初始化完成,运行标的:", ContextInfo.stock_code)

def get_bollinger_bands(prices, period, num_std):
    """
    计算布林带
    """
    # 使用pandas计算移动平均和标准差
    ma = prices.rolling(window=period).mean()
    std = prices.rolling(window=period).std()
    
    upper = ma + (std * num_std)
    lower = ma - (std * num_std)
    
    return upper, lower

def timetag_to_datetime(timetag, format_str):
    """
    将毫秒时间戳转换为指定格式的字符串
    """
    import time
    # timetag是毫秒,需要除以1000转换成秒
    time_local = time.localtime(timetag / 1000)
    return time.strftime(format_str, time_local)

def handlebar(ContextInfo):
    """
    K线运行函数,每根K线或Tick调用一次
    """
    # 获取当前K线索引
    index = ContextInfo.barpos
    
    # 获取当前时间
    timetag = ContextInfo.get_bar_timetag(index)
    current_time_str = timetag_to_datetime(timetag, '%H%M%S')
    
    # 获取标的代码
    stock_code = ContextInfo.stock_code
    
    # --- 1. 日内强制平仓逻辑 (14:55之后强制平仓) ---
    if current_time_str >= '145500':
        # 获取当前持仓
        positions = get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
        for pos in positions:
            if pos.m_strInstrumentID + '.' + pos.m_strExchangeID == stock_code and pos.m_nVolume > 0:
                print(f"日内收盘强制平仓: {stock_code}")
                # 目标仓位设为0,即清仓
                order_target_percent(stock_code, 0, ContextInfo, ContextInfo.account_id)
        return # 强制平仓后不再执行后续开仓逻辑

    # --- 2. 获取历史行情数据 ---
    # 获取足够数量的K线以计算指标,这里取 30 根,保证能算出 20 周期的均线
    count = ContextInfo.period + 5 
    data_map = ContextInfo.get_market_data_ex(
        ['close'], 
        [stock_code], 
        period=ContextInfo.freq, 
        count=count, 
        dividend_type='front' # 前复权
    )
    
    if stock_code not in data_map:
        return
        
    df = data_map[stock_code]
    
    # 如果数据不足,直接返回
    if len(df) < ContextInfo.period:
        return

    # --- 3. 计算布林带指标 ---
    close_prices = df['close']
    upper_band, lower_band = get_bollinger_bands(close_prices, ContextInfo.period, ContextInfo.std_dev)
    
    # 获取最新一根K线的数据值
    current_close = close_prices.iloc[-1]
    current_upper = upper_band.iloc[-1]
    current_lower = lower_band.iloc[-1]
    
    # --- 4. 获取当前持仓状态 ---
    # 这里的逻辑简化为:是否有持仓。
    # 注意:回测模式下可用 ContextInfo.get_position(stock_code) 等简便方法,
    # 实盘建议使用 get_trade_detail_data 查实际持仓。
    # 这里使用 order_target_percent 配合 passorder 逻辑,简化判断。
    
    # 为了演示清晰,我们假设全仓买入(1.0)或空仓(0.0)
    
    # --- 5. 交易信号判断与执行 ---
    
    # 信号1:价格跌破下轨 -> 买入
    if current_close < current_lower:
        # 只有在不是最后一根K线(回测时)或者实盘的最新K线时才打印信号
        if ContextInfo.is_last_bar():
            print(f"时间:{current_time_str} 价格:{current_close:.2f} < 下轨:{current_lower:.2f},触发买入信号")
        
        # 目标仓位调整为 100% (全仓买入)
        # 注意:实盘中请根据风险偏好调整比例,例如 0.5
        order_target_percent(stock_code, 1.0, ContextInfo, ContextInfo.account_id)

    # 信号2:价格涨破上轨 -> 卖出
    elif current_close > current_upper:
        if ContextInfo.is_last_bar():
            print(f"时间:{current_time_str} 价格:{current_close:.2f} > 上轨:{current_upper:.2f},触发卖出信号")
        
        # 目标仓位调整为 0% (清仓)
        order_target_percent(stock_code, 0.0, ContextInfo, ContextInfo.account_id)

    # 可选:在图表上画出布林带 (仅在回测或界面运行时有效)
    ContextInfo.paint('Upper', current_upper, -1, 0, 'red')
    ContextInfo.paint('Lower', current_lower, -1, 0, 'green')
    ContextInfo.paint('Mid', (current_upper+current_lower)/2, -1, 0, 'white')

代码使用说明

  1. 账号设置
    • init 函数中,找到 ContextInfo.account_id = '您的资金账号',请务必将其修改为您实际的 QMT 资金账号。
  2. 运行模式
    • 回测:在策略编辑器中点击“回测”,选择 5分钟 周期,设置好时间段即可查看历史表现。
    • 实盘/模拟:将策略加载到图表上(确保图表周期为 5分钟),或者在模型交易界面运行。
  3. 日内平仓
    • 代码中包含 if current_time_str >= '145500': 逻辑。这意味着在 14:55 之后,策略会检查持仓并强制卖出,不再开新仓,以确保不持仓过夜。
  4. 下单函数
    • 使用了 order_target_percent。这是一个高级封装函数,它会自动计算需要买卖的数量以达到目标仓位比例。
    • 1.0 代表满仓买入,0.0 代表清仓卖出。您可以根据需要修改这个比例(例如改为 0.5 半仓)。

Q&A

Q: 如何修改布林带的参数(例如改成26周期)?
A: 在 init 函数中,修改 ContextInfo.period = 2026 即可。

Q: 为什么使用 get_market_data_ex 而不是 get_market_data
A: get_market_data_ex 是 QMT 推荐的新版数据接口,它返回的数据结构更清晰(字典包含 DataFrame),且在处理复权和多股数据时更稳定。

Q: 这个策略可以用于期货吗?
A: 逻辑上可以,但需要修改下单函数。期货涉及开仓(Open)和平仓(Close),且有做空机制。目前的 order_target_percent 主要适用于股票或单向做多的逻辑。如果是期货,建议使用 buy_open, sell_close 等具体指令,并增加做空逻辑(跌破下轨做多,涨破上轨做空)。