问题描述
解决方案
这是一个基于 QMT 平台的量化交易策略代码。
策略逻辑分析
- 数据获取:
- 获取 日线 数据:用于判断大趋势是否处于“超卖”状态。
- 获取 5分钟 数据:用于捕捉具体的入场点(金叉)。
- 指标计算:
- 使用
talib库计算 KDJ 指标(参数默认 9, 3, 3)。 - 计算公式:$J = 3K - 2D$。
- 使用
- 交易信号:
- 买入条件:
- 日线 K 值 < 20 且 日线 D 值 < 20(定义为超卖)。
- 5分钟 K 线出现金叉(即上一根 K 线 $K < D$,当前 K 线 $K > D$)。
- 卖出条件(为了策略完整性补充):
- 5分钟 K 线出现死叉(即上一根 K 线 $K > D$,当前 K 线 $K < D$)。
- 买入条件:
- 执行:
- 使用
order_shares进行下单。
- 使用
QMT 策略代码
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
import talib
def init(ContextInfo):
"""
初始化函数,策略启动时执行一次
"""
# 设置资金账号,请修改为您自己的资金账号
ContextInfo.account_id = '6000000000'
ContextInfo.set_account(ContextInfo.account_id)
# 策略参数设置
ContextInfo.kdj_n = 9
ContextInfo.kdj_m1 = 3
ContextInfo.kdj_m2 = 3
# 超卖阈值
ContextInfo.oversold_threshold = 20
# 每次交易数量
ContextInfo.trade_vol = 100
print("策略初始化完成")
def get_kdj(high, low, close, n, m1, m2):
"""
计算KDJ指标的辅助函数
"""
# 使用 talib 计算 K 和 D
# 注意:talib.STOCH 默认参数与国内常用的 KDJ 算法略有差异,这里使用通用设置
k, d = talib.STOCH(high, low, close,
fastk_period=n,
slowk_period=m1,
slowk_matype=0,
slowd_period=m2,
slowd_matype=0)
# 计算 J 值
j = 3 * k - 2 * d
return k, d, j
def handlebar(ContextInfo):
"""
K线处理函数,每根K线执行一次
"""
# 获取当前主图的股票代码
stock_code = ContextInfo.stockcode + '.' + ContextInfo.market
# ----------------------------------------------------------------
# 1. 获取日线数据 (用于判断超卖)
# ----------------------------------------------------------------
# 获取过去 100 天的数据以确保指标计算准确
daily_data = ContextInfo.get_market_data_ex(
['high', 'low', 'close'],
[stock_code],
period='1d',
count=100,
dividend_type='follow'
)
if stock_code not in daily_data or daily_data[stock_code].empty:
return
df_daily = daily_data[stock_code]
# 计算日线 KDJ
d_k, d_d, d_j = get_kdj(df_daily['high'].values,
df_daily['low'].values,
df_daily['close'].values,
ContextInfo.kdj_n,
ContextInfo.kdj_m1,
ContextInfo.kdj_m2)
# 获取最近一个完整的日线 K, D 值
# 注意:如果是盘中运行,-1 代表当天(未走完),-2 代表昨天。
# 策略逻辑:判断大趋势,通常看前一天的收盘状态,或者当天的实时状态。
# 这里取 -1 (最新值)
current_daily_k = d_k[-1]
current_daily_d = d_d[-1]
# 判断日线是否处于超卖区
is_daily_oversold = (current_daily_k < ContextInfo.oversold_threshold) and \
(current_daily_d < ContextInfo.oversold_threshold)
# ----------------------------------------------------------------
# 2. 获取 5分钟数据 (用于金叉买入)
# ----------------------------------------------------------------
# 策略应当运行在 5分钟 图上,或者显式获取 5分钟 数据
min5_data = ContextInfo.get_market_data_ex(
['high', 'low', 'close'],
[stock_code],
period='5m',
count=100,
dividend_type='follow'
)
if stock_code not in min5_data or min5_data[stock_code].empty:
return
df_5m = min5_data[stock_code]
# 计算 5分钟 KDJ
m_k, m_d, m_j = get_kdj(df_5m['high'].values,
df_5m['low'].values,
df_5m['close'].values,
ContextInfo.kdj_n,
ContextInfo.kdj_m1,
ContextInfo.kdj_m2)
# 获取 5分钟 KDJ 的当前值和前一值
curr_m_k = m_k[-1]
curr_m_d = m_d[-1]
prev_m_k = m_k[-2]
prev_m_d = m_d[-2]
# 判断 5分钟 金叉 (上根K线 K<D,当前K线 K>D)
is_5m_golden_cross = (prev_m_k < prev_m_d) and (curr_m_k > curr_m_d)
# 判断 5分钟 死叉 (用于卖出逻辑,可选)
is_5m_dead_cross = (prev_m_k > prev_m_d) and (curr_m_k < curr_m_d)
# ----------------------------------------------------------------
# 3. 交易逻辑执行
# ----------------------------------------------------------------
# 获取当前持仓 (简单示例,实际需根据 get_trade_detail_data 获取准确持仓)
# 这里仅做信号触发演示
# 信号 1: 买入
# 条件: 日线超卖 AND 5分钟金叉 AND 不是最后一根K线(回测时防止未来函数)
# 实盘时 ContextInfo.is_last_bar() 为 True,需要执行
if is_daily_oversold and is_5m_golden_cross:
print(f"时间: {ContextInfo.get_bar_timetag(ContextInfo.barpos)} - 触发买入信号: 日线超卖且5分钟金叉")
# 下单买入
order_shares(stock_code, ContextInfo.trade_vol, 'fix', df_5m['close'].iloc[-1], ContextInfo, ContextInfo.account_id)
# 信号 2: 卖出 (简单止盈/止损逻辑:5分钟死叉即卖出)
elif is_5m_dead_cross:
print(f"时间: {ContextInfo.get_bar_timetag(ContextInfo.barpos)} - 触发卖出信号: 5分钟死叉")
# 下单卖出
order_shares(stock_code, -ContextInfo.trade_vol, 'fix', df_5m['close'].iloc[-1], ContextInfo, ContextInfo.account_id)
使用说明
- 运行周期:请将此策略加载到 5分钟 周期的 K 线图上运行。
- 账号设置:代码中的
ContextInfo.account_id = '6000000000'需要修改为您实际的 QMT 资金账号。 - 编码格式:QMT 的 Python 编辑器强制要求文件头包含
# -*- coding: gbk -*-,请勿删除。 - 库依赖:代码使用了
talib库,QMT 内置环境中通常已包含此库。如果报错,可以使用 pandas 手动实现 KDJ 计算逻辑。 - 实盘注意:
handlebar在实盘模式下,最后一根 K 线(当前正在波动的 K 线)每变动一次 tick 都会触发一次。- 为了避免信号闪烁(即盘中金叉,收盘没金叉),建议结合
ContextInfo.is_new_bar()使用,或者使用do_order函数在下一根 K 线开盘时确认信号。上述代码使用的是即时信号逻辑。
常见问题 Q&A
Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是 QMT 较新的接口,返回的数据结构更清晰(字典包含 DataFrame),且支持订阅模式,效率更高。
Q: 如何避免实盘中信号闪烁导致的重复下单?
A: 可以在 handlebar 开头添加判断,只在 K 线走完时下单,或者使用全局变量记录当前 K 线是否已经交易过。最简单的方法是使用 passorder 的 quickTrade=0 参数(默认),它会在 K 线走完后才确认信号。
Q: 日线超卖的定义可以修改吗?
A: 可以。在 init 函数中修改 ContextInfo.oversold_threshold = 20 即可,例如改为 15 或 25。