问题描述
解决方案
这是一个基于布林带(Bollinger Bands)通道突破的量化交易策略。
策略逻辑
- 指标计算:
- 中轨 (MB):过去 N 根 K 线的收盘价移动平均线(通常 N=20)。
- 上轨 (UP):中轨 + K 倍标准差(通常 K=2)。
- 下轨 (DN):中轨 - K 倍标准差。
- 交易信号:
- 买入:当收盘价突破上轨(Close > UP),且当前无持仓时,全仓买入。
- 卖出:当收盘价跌破下轨(Close < DN),且当前有持仓时,清仓卖出。
代码实现
该代码使用了 ContextInfo.get_market_data_ex 获取数据,并使用 pandas 计算指标。下单函数使用了 order_target_percent,这在回测中非常方便(自动计算股数),实盘中同样适用(需确保账号设置正确)。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
初始化函数,策略启动时调用一次
"""
# --- 策略参数设置 ---
ContextInfo.M = 20 # 布林带周期
ContextInfo.K = 2.0 # 标准差倍数
ContextInfo.account_id = '6000000000' # 请替换为您真实的资金账号
ContextInfo.account_type = 'STOCK' # 账号类型:'STOCK'股票, 'FUTURE'期货
# 设置股票池(此处示例为平安银行,实际使用时可在界面设置或此处修改)
ContextInfo.set_universe(['000001.SZ'])
# 绑定账号(实盘/模拟盘必须)
ContextInfo.set_account(ContextInfo.account_id)
print("策略初始化完成:布林带突破策略 (N={}, K={})".format(ContextInfo.M, ContextInfo.K))
def handlebar(ContextInfo):
"""
K线周期回调函数,每根K线执行一次
"""
# 获取当前正在运行的K线索引
index = ContextInfo.barpos
# 获取当前图表的主图代码(如果是多股回测,建议遍历 ContextInfo.get_universe())
# 这里为了演示清晰,我们只针对当前主图代码或股票池中的第一个代码进行操作
stock_code = ContextInfo.get_universe()[0]
# --- 1. 获取历史行情数据 ---
# 我们需要计算MA20,所以至少需要前20根K线,为了安全起见取 M + 5 根
# subscribe=True 确保实盘时数据自动更新
data_map = ContextInfo.get_market_data_ex(
['close'],
[stock_code],
period=ContextInfo.period,
count=ContextInfo.M + 5,
dividend_type='front', # 前复权
subscribe=True
)
if stock_code not in data_map:
return
df = data_map[stock_code]
# 数据长度不足以计算指标时直接返回
if len(df) < ContextInfo.M:
return
# --- 2. 计算布林带指标 ---
# 计算移动平均线 (中轨)
df['ma'] = df['close'].rolling(window=ContextInfo.M).mean()
# 计算标准差
df['std'] = df['close'].rolling(window=ContextInfo.M).std()
# 计算上轨
df['upper'] = df['ma'] + ContextInfo.K * df['std']
# 计算下轨
df['lower'] = df['ma'] - ContextInfo.K * df['std']
# 获取最新一根K线的数据(iloc[-1])
# 注意:在回测模式下,handlebar是在K线结束时调用的,所以[-1]就是当前K线
# 在实盘模式下,如果希望K线走完再确认信号,应判断 ContextInfo.is_last_bar() 并结合时间逻辑
# 这里采用标准回测逻辑,取最新值
current_close = df['close'].iloc[-1]
current_upper = df['upper'].iloc[-1]
current_lower = df['lower'].iloc[-1]
# 打印调试信息 (可选)
# print(f"代码:{stock_code} 收盘:{current_close:.2f} 上轨:{current_upper:.2f} 下轨:{current_lower:.2f}")
# --- 3. 获取当前持仓 ---
# 使用 get_trade_detail_data 获取持仓对象列表
positions = get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
current_volume = 0
for pos in positions:
if pos.m_strInstrumentID + '.' + pos.m_strExchangeID == stock_code:
current_volume = pos.m_nVolume
break
# --- 4. 交易逻辑 ---
# 信号1:价格突破上轨 -> 买入
if current_close > current_upper:
# 如果当前没有持仓,则全仓买入
if current_volume == 0:
print(f"{stock_code} 触发买入信号: 价格 {current_close} > 上轨 {current_upper}")
# order_target_percent(代码, 目标仓位比例, ...)
# 1.0 代表调整至 100% 仓位
order_target_percent(stock_code, 1.0, ContextInfo, ContextInfo.account_id)
# 信号2:价格跌破下轨 -> 卖出
elif current_close < current_lower:
# 如果当前有持仓,则清仓
if current_volume > 0:
print(f"{stock_code} 触发卖出信号: 价格 {current_close} < 下轨 {current_lower}")
# 0.0 代表调整至 0% 仓位(即清仓)
order_target_percent(stock_code, 0.0, ContextInfo, ContextInfo.account_id)
使用说明与注意事项
-
账号设置:
- 代码中的
ContextInfo.account_id = '6000000000'必须修改为您实际的 QMT 资金账号。 - 如果是回测模式,QMT 会自动模拟一个账号,通常不需要修改,但在实盘或模拟交易界面运行时必须匹配。
- 代码中的
-
数据获取:
- 使用了
get_market_data_ex接口,这是 QMT 推荐的高效数据接口。 dividend_type='front'设置为前复权,这对于技术指标策略非常重要,避免因分红除权导致的价格跳空触发错误信号。
- 使用了
-
下单函数:
- 使用了
order_target_percent。这个函数非常智能,它会自动计算需要买入多少股才能达到目标仓位(例如1.0表示满仓)。 - 如果需要固定数量下单(例如每次买100股),可以将
order_target_percent替换为order_shares(stock_code, 100, ContextInfo, ContextInfo.account_id)。
- 使用了
-
运行模式:
- 回测:在策略编辑器中点击“回测”,设置好时间区间和基准即可。
- 实盘/模拟:在策略交易界面运行。注意,实盘中
handlebar会随行情 Tick 推送触发(如果设置为逐Tick运行)或 K线生成时触发。上述代码逻辑是基于 K 线收盘价的,建议在设置中将运行频率设为“K线生成”或在代码中增加ContextInfo.is_new_bar()判断以避免盘中闪烁信号导致频繁交易。
Q&A
Q: 如何修改布林带的参数?
A: 在代码的 init 函数中,修改 ContextInfo.M (周期) 和 ContextInfo.K (标准差倍数) 的值即可。
Q: 这个策略可以用于期货吗?
A: 可以,但需要做两处调整:
- 将
ContextInfo.account_type改为'FUTURE'。 - 期货通常支持做空。目前的逻辑是“跌破下轨卖出平仓”。如果需要“跌破下轨开空单”,需要修改卖出部分的逻辑,将
order_target_percent的目标设为负数(如果API支持)或使用passorder函数发送开空指令(opType=3)。
Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是 QMT 新版推荐的接口,支持更灵活的参数(如复权、订阅开关),且返回的数据结构更规范(字典包含 DataFrame),处理多股数据时效率更高。