问题描述
解决方案
这是一个基于亚历山大·埃尔德(Alexander Elder)博士提出的**三重滤网(Triple Screen)**交易系统的 QMT 策略实现代码。
策略逻辑说明
三重滤网系统通过三个不同的时间维度来过滤交易信号,提高胜率:
- 第一层滤网(市场潮流 - 长期趋势):
- 周期:周线(Weekly)。
- 指标:MACD 柱状图(MACD Histogram)。
- 规则:当周线 MACD 柱状图向上倾斜(数值大于前一根)时,定义为多头趋势;反之为空头趋势。本策略仅演示做多逻辑。
- 第二层滤网(市场波浪 - 中期回调):
- 周期:日线(Daily)。
- 指标:随机指标(KDJ 或 Stochastic)。
- 规则:当第一层滤网为多头趋势时,寻找日线 KDJ 的超卖区域(例如 K < 30)作为回调买入机会。
- 第三层滤网(盘中突破 - 入场点):
- 执行:在 QMT 量化中,为了简化执行,当满足前两层条件时,我们在下一根 K 线开盘时直接买入(或者使用当前 K 线收盘价模拟)。
QMT 策略代码
请在 QMT 的策略编辑器中新建一个 Python 策略,并将以下代码复制进去。
注意:
- 请确保您的 QMT 客户端已安装
pandas和talib库(QMT 自带环境通常已包含)。 - 策略运行的主图周期建议设置为 日线 (1d)。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
import talib
def init(ContextInfo):
"""
策略初始化函数
"""
# 设置账号(请替换为您自己的资金账号)
ContextInfo.account_id = '6000000000'
ContextInfo.account_type = 'STOCK'
ContextInfo.set_account(ContextInfo.account_id)
# 策略参数设置
ContextInfo.long_period = '1w' # 第一层滤网周期:周线
ContextInfo.short_period = '1d' # 第二层滤网周期:日线 (即主图周期)
# MACD 参数 (用于第一层滤网)
ContextInfo.macd_fast = 12
ContextInfo.macd_slow = 26
ContextInfo.macd_signal = 9
# KDJ 参数 (用于第二层滤网)
ContextInfo.kdj_n = 9
ContextInfo.kdj_m1 = 3
ContextInfo.kdj_m2 = 3
ContextInfo.oversold_threshold = 30 # 超卖阈值
ContextInfo.overbought_threshold = 80 # 超买阈值 (用于止盈)
# 每次交易数量
ContextInfo.trade_vol = 100
def get_macd_trend(ContextInfo, stock_code):
"""
计算第一层滤网:周线 MACD 趋势
返回: 1 (趋势向上), -1 (趋势向下), 0 (数据不足)
"""
# 获取周线数据,取最近 50 根即可
data = ContextInfo.get_market_data_ex(
['close'],
[stock_code],
period=ContextInfo.long_period,
count=50,
dividend_type='front'
)
if stock_code not in data or data[stock_code].empty:
return 0
df = data[stock_code]
close_prices = df['close'].values
# 计算 MACD
# talib.MACD 返回三个数组: macd, signal, hist(柱状图)
diff, dea, hist = talib.MACD(
close_prices,
fastperiod=ContextInfo.macd_fast,
slowperiod=ContextInfo.macd_slow,
signalperiod=ContextInfo.macd_signal
)
# 检查数据长度
if len(hist) < 2:
return 0
# 埃尔德规则:MACD 柱状图斜率向上(当前柱 > 上一根柱)即为多头趋势
# 注意:这里比较的是柱状图的高度变化,不仅仅是正负
current_hist = hist[-1]
prev_hist = hist[-2]
if current_hist > prev_hist:
return 1 # 趋势向上
elif current_hist < prev_hist:
return -1 # 趋势向下
else:
return 0
def get_kdj_signal(ContextInfo, stock_code):
"""
计算第二层滤网:日线 KDJ 状态
返回: 'oversold' (超卖), 'overbought' (超买), 'neutral' (中性)
"""
# 获取日线数据 (主图周期)
data = ContextInfo.get_market_data_ex(
['high', 'low', 'close'],
[stock_code],
period=ContextInfo.short_period,
count=50,
dividend_type='front'
)
if stock_code not in data or data[stock_code].empty:
return 'neutral'
df = data[stock_code]
high = df['high'].values
low = df['low'].values
close = df['close'].values
# 计算 KDJ (Stochastic)
# talib.STOCH 对应 KDJ 的算法
k, d = talib.STOCH(
high,
low,
close,
fastk_period=ContextInfo.kdj_n,
slowk_period=ContextInfo.kdj_m1,
slowk_matype=0,
slowd_period=ContextInfo.kdj_m2,
slowd_matype=0
)
# 获取最新的 K 值
current_k = k[-1]
if current_k < ContextInfo.oversold_threshold:
return 'oversold'
elif current_k > ContextInfo.overbought_threshold:
return 'overbought'
else:
return 'neutral'
def handlebar(ContextInfo):
"""
K线逐根运行函数
"""
# 获取当前主图的代码
stock_code = ContextInfo.stockcode + '.' + ContextInfo.market
# 过滤掉未上市或数据不足的情况
if ContextInfo.is_last_bar():
# 仅在最后一根K线或回测模式下运行逻辑
pass
else:
# 如果为了加快回测速度,非必要逻辑可以跳过,但为了指标计算连续性,通常不建议完全跳过
# 这里为了演示简单,我们对每一根K线都进行计算
pass
# 1. 获取持仓状态
positions = get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
current_pos = 0
for pos in positions:
if pos.m_strInstrumentID == stock_code:
current_pos = pos.m_nVolume
break
# 2. 执行第一层滤网:判断周线趋势
trend_direction = get_macd_trend(ContextInfo, stock_code)
# 3. 执行第二层滤网:判断日线震荡
oscillator_status = get_kdj_signal(ContextInfo, stock_code)
# 4. 交易逻辑 (第三层滤网:执行)
# 开仓逻辑:周线趋势向上 且 日线超卖
if current_pos == 0:
if trend_direction == 1 and oscillator_status == 'oversold':
print(f"[{ContextInfo.get_bar_timetag(ContextInfo.barpos)}] 触发买入: 周线趋势向上, 日线超卖")
# 使用 passorder 下单
passorder(23, 1101, ContextInfo.account_id, stock_code, 5, -1, ContextInfo.trade_vol, ContextInfo)
# 平仓逻辑:日线超买 (或者可以加入周线趋势反转作为平仓条件)
elif current_pos > 0:
# 止盈/离场条件:日线进入超买区,或者周线趋势转弱
if oscillator_status == 'overbought' or trend_direction == -1:
print(f"[{ContextInfo.get_bar_timetag(ContextInfo.barpos)}] 触发卖出: 日线超买或趋势反转")
passorder(24, 1101, ContextInfo.account_id, stock_code, 5, -1, current_pos, ContextInfo)
代码关键点解析
-
跨周期数据获取 (
get_market_data_ex):- 策略核心在于同时获取 周线 (
1w) 和 日线 (1d) 数据。 get_macd_trend函数中指定period='1w'来获取周线数据计算 MACD。get_kdj_signal函数中指定period='1d'来获取日线数据计算 KDJ。
- 策略核心在于同时获取 周线 (
-
指标计算 (
talib):- 使用了
talib.MACD计算趋势。埃尔德的规则是看 MACD 柱状图(Histogram)的斜率,即hist[-1] > hist[-2]代表多头力量增强。 - 使用了
talib.STOCH计算随机指标。当 K 值低于 30 时视为超卖(回调到位),是顺大势逆小势的买点。
- 使用了
-
交易执行 (
passorder):opType=23:股票买入。opType=24:股票卖出。prType=5:使用最新价下单(模拟市价成交)。
如何使用
-
回测:
- 在 QMT 策略编辑器界面,点击“模型回测”。
- 主图周期选择 日线。
- 选择一个标的(如 600000.SH 浦发银行)和回测时间段。
- 点击开始回测,查看结果。
-
实盘/模拟盘:
- 修改
init函数中的ContextInfo.account_id为您真实的资金账号。 - 将策略加载到运行界面,确保主图是日线周期。
- 点击运行。
- 修改
常见问题 (Q&A)
Q: 为什么回测时没有交易?
A:
- 检查回测时间段是否足够长(因为需要计算周线 MACD,至少需要几十周的数据)。
- 检查
ContextInfo.account_id是否设置正确(回测模式下通常不需要真实账号,但代码中保留了设置)。 - 这种策略是趋势跟踪+回调买入,如果市场一直单边下跌或没有明显回调,可能不会触发信号。
Q: 如何修改为做空逻辑(期货)?
A:
- 在
get_macd_trend中判断trend_direction == -1(周线向下)。 - 在
get_kdj_signal中判断oscillator_status == 'overbought'(日线超买)。 - 修改
passorder的opType为期货对应的开空(如 0 开多,3 开空,具体参考 QMT 文档)。
Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是 QMT 推荐的新版接口,返回的数据结构更清晰(字典包含 DataFrame),且支持更灵活的参数配置,处理多股多周期数据更稳定。