问题描述
解决方案
这是一个基于 QMT 平台的 Python 策略,实现了 MACD 指标金叉买入、死叉卖出 的逻辑。
策略逻辑说明
- 指标计算:使用
talib库计算 MACD 指标(快线 DIF、慢线 DEA、柱状图 MACD)。参数默认为 12, 26, 9。 - 金叉(买入信号):当 DIF 线 上穿 DEA 线时。
- 死叉(卖出信号):当 DIF 线 下穿 DEA 线时。
- 交易执行:
- 产生金叉且当前无持仓 -> 全仓买入。
- 产生死叉且当前有持仓 -> 清仓卖出。
策略代码
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
import talib
def init(ContextInfo):
"""
初始化函数,策略启动时调用一次
"""
# 设置资金账号 (请修改为您自己的资金账号)
ContextInfo.accid = '6000000000'
# 设置账号类型:'STOCK'股票, 'FUTURE'期货
ContextInfo.accountType = 'STOCK'
# 绑定账号,用于接收回报
ContextInfo.set_account(ContextInfo.accid)
# 设置MACD参数
ContextInfo.fast_period = 12
ContextInfo.slow_period = 26
ContextInfo.signal_period = 9
print("策略初始化完成,MACD参数: {}, {}, {}".format(
ContextInfo.fast_period, ContextInfo.slow_period, ContextInfo.signal_period))
def handlebar(ContextInfo):
"""
K线处理函数,每根K线执行一次
"""
# 获取当前主图的股票代码
stock_code = ContextInfo.stockcode
# 获取当前主图的周期
period = ContextInfo.period
# 1. 获取历史行情数据
# 获取足够多的数据以确保MACD计算准确,这里取过去100根K线
data_len = 100
# 使用 get_market_data_ex 获取数据 (推荐方式)
# dividend_type='front' 表示前复权,计算技术指标通常使用前复权
market_data = ContextInfo.get_market_data_ex(
['close'],
[stock_code],
period=period,
count=data_len,
dividend_type='front'
)
# 如果数据获取失败或数据量不足,直接返回
if stock_code not in market_data or len(market_data[stock_code]) < 35:
return
# 提取收盘价序列
close_price = market_data[stock_code]['close']
# 转为numpy数组,方便talib计算
close_np = np.array(close_price)
# 2. 计算 MACD 指标
# diff: 快线, dea: 慢线, macd: 柱状图
diff, dea, macd = talib.MACD(
close_np,
fastperiod=ContextInfo.fast_period,
slowperiod=ContextInfo.slow_period,
signalperiod=ContextInfo.signal_period
)
# 获取当前K线和上一根K线的指标值
# -1 代表当前最新一根K线(如果是盘中,则是正在跳动的K线)
# -2 代表上一根K线
current_diff = diff[-1]
current_dea = dea[-1]
last_diff = diff[-2]
last_dea = dea[-2]
# 检查是否计算出有效值 (剔除NaN)
if np.isnan(current_diff) or np.isnan(last_diff):
return
# 3. 判断金叉和死叉
# 金叉:上一根K线 DIF < DEA,且当前K线 DIF > DEA
golden_cross = (last_diff < last_dea) and (current_diff > current_dea)
# 死叉:上一根K线 DIF > DEA,且当前K线 DIF < DEA
death_cross = (last_diff > last_dea) and (current_diff < current_dea)
# 4. 获取当前持仓情况
# 注意:回测模式下,ContextInfo.get_position() 可能不可用,需用 get_trade_detail_data
# 这里为了兼容实盘和回测,简单判断持仓
positions = ContextInfo.get_trade_detail_data(ContextInfo.accid, ContextInfo.accountType, 'POSITION')
current_vol = 0
for pos in positions:
if pos.m_strInstrumentID == stock_code:
current_vol = pos.m_nVolume
break
# 5. 交易逻辑执行
# 获取最新价格用于下单
last_price = close_price.iloc[-1]
# 只有在K线走完时才下单(避免盘中信号闪烁),或者使用 ContextInfo.is_last_bar() 判断
# 这里演示简单逻辑:如果是回测,每根bar都会执行;如果是实盘,通常在bar结束或tick级判断
# --- 买入逻辑 ---
if golden_cross:
if current_vol == 0:
# 记录日志
print(f"[{ContextInfo.barpos}] {stock_code} 触发金叉,买入")
# 绘制信号到图表
ContextInfo.draw_text(True, last_price, '金叉买入')
# 下单:全仓买入 (这里简单演示买入1000股,实际需根据资金计算)
# opType=23(买入), orderType=1101(单股单账号普通下单), priceType=5(最新价)
passorder(23, 1101, ContextInfo.accid, stock_code, 5, -1, 1000, ContextInfo)
# --- 卖出逻辑 ---
elif death_cross:
if current_vol > 0:
# 记录日志
print(f"[{ContextInfo.barpos}] {stock_code} 触发死叉,卖出")
# 绘制信号到图表
ContextInfo.draw_text(True, last_price, '死叉卖出')
# 下单:卖出所有持仓
# opType=24(卖出)
passorder(24, 1101, ContextInfo.accid, stock_code, 5, -1, current_vol, ContextInfo)
# 6. 在副图画出MACD指标 (可选,方便回测观察)
ContextInfo.paint('DIFF', current_diff, -1, 0, 'white')
ContextInfo.paint('DEA', current_dea, -1, 0, 'yellow')
# 画柱状图
macd_color = 'red' if macd[-1] > 0 else 'green'
ContextInfo.paint('MACD', macd[-1] * 2, -1, 42, macd_color) # talib算出的macd通常是diff-dea,国内软件习惯显示(diff-dea)*2
使用步骤与注意事项
- 新建策略:在 QMT 策略编辑器中新建一个 Python 策略。
- 复制代码:将上述代码完整复制进去。
- 修改账号:找到
init函数中的ContextInfo.accid = '6000000000',将'6000000000'修改为您实际的资金账号。 - 补充数据:
- 点击 QMT 菜单栏的【数据管理】->【补充数据】。
- 下载您要回测或交易的股票的历史日线数据(如果跑分钟线则下载分钟数据)。
- 运行/回测:
- 回测:点击编辑器上方的“回测”,设置好时间区间、基准和费率,点击开始。
- 实盘/模拟:在行情图上加载该策略,或者在“模型交易”中运行。
代码关键点解析
get_market_data_ex:这是 QMT 获取行情的推荐接口。我们使用了dividend_type='front'(前复权),因为 MACD 等技术指标在除权除息日如果不复权会出现断层,导致信号失真。talib.MACD:QMT 内置了talib库,计算指标非常快。注意talib返回的macd序列通常是diff - dea的值,而国内看盘软件(如通达信、同花顺)显示的 MACD 柱状图通常是(diff - dea) * 2,所以在画图时我做了* 2处理。passorder:这是 QMT 最核心的下单函数。23: 买入,24: 卖出。1101: 代表单股、单账号、按股数下单。5: 代表使用最新价下单(在回测中通常以收盘价成交,实盘中为最新tick价格)。
常见问题 Q&A
Q: 为什么回测没有交易?
A: 1. 请检查是否下载了对应的历史数据。2. 请检查 ContextInfo.accid 是否设置正确(回测模式下虽然是虚拟账号,但代码逻辑中获取持仓依赖于账号ID)。
Q: 如何调整 MACD 参数?
A: 修改 init 函数中的 ContextInfo.fast_period 等变量即可。
Q: 实盘中如何避免信号闪烁?
A: 上述代码在 handlebar 中运行。在实盘中,handlebar 会随最新的 Tick 跳动。为了避免 K 线没走完就下单(信号闪烁),可以使用 ContextInfo.is_last_bar() 结合时间判断,或者在代码中加入逻辑只在 K 线即将结束时(例如 14:59:50)下单。