问题描述
解决方案
这是一个基于QMT Python API编写的量化策略。
策略逻辑分析
- 运行周期:策略需要在 5分钟 周期上运行。
- 数据获取:获取当前股票过去 21 根 K 线的数据(当前 1 根 + 过去 20 根)。
- 计算指标:
- 平均成交量:计算过去 20 根 K 线(不含当前)的成交量平均值。
- 最高价:计算过去 20 根 K 线(不含当前)的最高价的最大值。
- 交易信号:
- 条件 A:当前 K 线成交量 > 3 * 过去 20 根平均成交量。
- 条件 B:当前价格 > 过去 20 根最高价。
- 执行动作:当 A 和 B 同时满足,且当前 K 线未开仓过,执行买入。
策略代码
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
初始化函数,策略启动时执行一次
"""
# 设置资金账号,请修改为您自己的实盘或模拟账号
ContextInfo.accid = '6000000000'
ContextInfo.set_account(ContextInfo.accid)
# 策略参数设置
ContextInfo.lookback_period = 20 # 回溯周期:20根K线
ContextInfo.vol_multiplier = 3.0 # 成交量倍数:3倍
ContextInfo.trade_period = '5m' # 运行周期:5分钟
ContextInfo.buy_amount = 100 # 每次买入数量(股)
# 用于记录最近一次买入的K线位置,防止同一根K线重复下单
ContextInfo.last_buy_barpos = -1
def handlebar(ContextInfo):
"""
行情事件函数,每根K线或每个Tick执行一次
"""
# 获取当前图表上的K线索引
barpos = ContextInfo.barpos
# 获取当前主图的股票代码
stock_code = ContextInfo.stockcode
# 1. 获取行情数据
# 我们需要获取过去20根 + 当前1根,共21根数据
# 注意:dividend_type='front' 表示使用前复权数据,这在处理价格突破时很重要
count = ContextInfo.lookback_period + 1
data_map = ContextInfo.get_market_data_ex(
['volume', 'high', 'close'], # 需要的字段:成交量、最高价、收盘价(当前价)
[stock_code], # 股票代码列表
period=ContextInfo.trade_period,
count=count,
dividend_type='front',
fill_data=False,
subscribe=True
)
# 检查数据是否获取成功
if stock_code not in data_map:
return
df = data_map[stock_code]
# 确保数据长度足够进行计算
if len(df) < count:
return
# 2. 数据切片与计算
# 历史数据:不包含当前这根K线(即前20根)
# iloc[-21:-1] 取倒数第21个到倒数第2个
history_df = df.iloc[-(ContextInfo.lookback_period + 1) : -1]
# 当前数据:最后一根K线
current_bar = df.iloc[-1]
# 计算过去20根K线的平均成交量
avg_volume = history_df['volume'].mean()
# 计算过去20根K线的最高价中的最大值
max_high = history_df['high'].max()
# 获取当前成交量和当前价格
current_volume = current_bar['volume']
current_price = current_bar['close']
# 3. 信号判断
# 条件1:当前成交量是过去20根平均成交量的3倍以上
# 注意:防止分母为0
if avg_volume == 0:
return
condition_vol = current_volume > (avg_volume * ContextInfo.vol_multiplier)
# 条件2:当前价格突破过去20根K线的最高点
condition_price = current_price > max_high
# 4. 执行交易
# 如果满足条件,且当前这根K线还没有买入过
if condition_vol and condition_price:
if ContextInfo.last_buy_barpos != barpos:
print(f"触发买入信号: {stock_code}, 时间: {df.index[-1]}")
print(f"当前量: {current_volume}, 均量: {avg_volume:.2f}, 当前价: {current_price}, 前高: {max_high}")
# 执行下单:买入,操作类型23,下单方式1101(单股单账号普通),最新价下单(报价类型5)
# 注意:实盘中请确保账号已登录且资金充足
passorder(23, 1101, ContextInfo.accid, stock_code, 5, -1, ContextInfo.buy_amount, ContextInfo)
# 更新买入标记,防止同根K线重复下单
ContextInfo.last_buy_barpos = barpos
# 在图表上画出买入信号(可选)
ContextInfo.draw_text(True, current_price, '买')
使用说明与注意事项
-
运行设置:
- 在QMT界面运行此策略时,请务必将策略的运行周期设置为 5分钟(或者在策略编辑器基本信息中设置默认周期为5分钟)。
- 如果策略运行在日线图上,
handlebar每天只触发一次,无法捕捉盘中5分钟级别的突破。
-
账号配置:
- 代码第 9 行
ContextInfo.accid = '6000000000'需要修改为您实际的资金账号。
- 代码第 9 行
-
防抖动处理:
- 代码中使用了
ContextInfo.last_buy_barpos变量。这是为了防止在实盘或模拟盘中,当一根 5 分钟 K 线尚未走完(实时 Tick 不断推送)时,信号反复触发导致重复下单。该逻辑保证了同一根 5 分钟 K 线内最多只买入一次。
- 代码中使用了
-
成交量逻辑:
- 策略使用的是
current_volume(当前累积成交量)。在盘中(K线未走完时),这个成交量是随着时间递增的。一旦累积量超过均量的3倍且价格突破,就会立即触发信号,不需要等到 K 线收盘。
- 策略使用的是
-
下单函数:
- 使用了
passorder函数,参数23代表买入,1101代表单股单账号下单,5代表使用最新价报价。您可以根据需要修改为order_value(按金额买入)或其他下单方式。
- 使用了
Q&A
Q: 如何将买入数量改为按金额买入(例如每次买1万元)?
A: 可以将 passorder 行替换为 order_value 函数:
# 需要先定义 order_value 函数或直接使用 passorder 的金额模式
# 简单方式是使用 QMT 封装好的 order_value (如果环境支持) 或者修改 passorder 参数
# passorder 参数 orderType 改为 1102 (金额下单)
passorder(23, 1102, ContextInfo.accid, stock_code, 5, -1, 10000, ContextInfo)
Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是 QMT 推荐的新版接口,效率更高,且返回的数据结构(Pandas DataFrame)更易于进行数学计算和逻辑处理。
Q: 策略在回测和实盘中有什么区别?
A: 在回测模式下,handlebar 通常在 K 线结束时调用一次。在实盘/模拟交易模式下,如果是最新的一根 K 线,handlebar 会随着每一个 Tick(分笔数据)的到来而被调用,因此必须加入 last_buy_barpos 这样的逻辑来控制下单频率。