问题描述
解决方案
这是一个基于QMT平台的布林带(Bollinger Bands)均值回归日内交易策略。
策略逻辑分析
- 运行周期:5分钟 K 线(
5m)。 - 指标计算:
- 中轨:20周期收盘价移动平均线 (MA20)。
- 上轨:中轨 + 2倍标准差。
- 下轨:中轨 - 2倍标准差。
- 交易信号:
- 买入:当收盘价跌破下轨(Close < Lower Band),且当前无持仓时,全仓买入。
- 卖出:当收盘价突破上轨(Close > Upper Band),且当前有持仓时,清仓卖出。
- 日内风控:
- 为了符合“日内交易”的要求,策略会在每日收盘前(例如 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')
代码使用说明
- 账号设置:
- 在
init函数中,找到ContextInfo.account_id = '您的资金账号',请务必将其修改为您实际的 QMT 资金账号。
- 在
- 运行模式:
- 回测:在策略编辑器中点击“回测”,选择 5分钟 周期,设置好时间段即可查看历史表现。
- 实盘/模拟:将策略加载到图表上(确保图表周期为 5分钟),或者在模型交易界面运行。
- 日内平仓:
- 代码中包含
if current_time_str >= '145500':逻辑。这意味着在 14:55 之后,策略会检查持仓并强制卖出,不再开新仓,以确保不持仓过夜。
- 代码中包含
- 下单函数:
- 使用了
order_target_percent。这是一个高级封装函数,它会自动计算需要买卖的数量以达到目标仓位比例。 1.0代表满仓买入,0.0代表清仓卖出。您可以根据需要修改这个比例(例如改为0.5半仓)。
- 使用了
Q&A
Q: 如何修改布林带的参数(例如改成26周期)?
A: 在 init 函数中,修改 ContextInfo.period = 20 为 26 即可。
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 等具体指令,并增加做空逻辑(跌破下轨做多,涨破上轨做空)。