问题描述
解决方案
编写 DMI(Directional Movement Index,动向指标)策略通常涉及计算 +DI(上升方向线)、-DI(下降方向线)和 ADX(平均趋向指标)。
DMI 策略的核心逻辑通常是:
- 买入信号:+DI 上穿 -DI(金叉),且 ADX 处于一定阈值(如 20)以上,表示趋势走强。
- 卖出信号:+DI 下穿 -DI(死叉),或者 -DI 上穿 +DI。
以下是一个完整的 QMT Python DMI 策略示例代码。
DMI 指标策略代码
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
import talib
def init(ContextInfo):
"""
策略初始化函数
"""
# 设置要交易的股票列表
ContextInfo.stock_list = ['600000.SH', '000001.SZ']
# 设置交易账号(请替换为您自己的资金账号)
ContextInfo.account_id = '您的资金账号'
ContextInfo.account_type = 'STOCK' # 股票账号
# 设置策略参数
ContextInfo.N = 14 # DMI计算周期
ContextInfo.M = 6 # ADX平滑周期
ContextInfo.ADX_THRESHOLD = 20 # ADX 阈值,用于确认趋势强度
# 设置周期
ContextInfo.period = '1d'
# 设置基准和费率(回测用)
ContextInfo.set_universe(ContextInfo.stock_list)
ContextInfo.set_account(ContextInfo.account_id)
def handlebar(ContextInfo):
"""
K线周期运行函数
"""
# 获取当前K线位置
index = ContextInfo.barpos
# 获取当前时间
realtime = ContextInfo.get_bar_timetag(index)
# 遍历股票池
for stock in ContextInfo.stock_list:
# 获取历史行情数据 (多取一些数据以保证指标计算准确)
# 注意:talib计算通常需要一定长度的前置数据才能稳定
count = 100
data = ContextInfo.get_market_data_ex(
['high', 'low', 'close'],
[stock],
period=ContextInfo.period,
count=count,
dividend_type='front' # 前复权
)
if stock not in data or data[stock].empty:
continue
df = data[stock]
# 确保数据量足够计算指标
if len(df) < ContextInfo.N + ContextInfo.M + 5:
continue
# 提取 numpy 数组供 talib 使用
high_prices = df['high'].values
low_prices = df['low'].values
close_prices = df['close'].values
# ----------------------------------------------------------------
# 计算 DMI 指标
# ----------------------------------------------------------------
# 计算 +DI (PDI)
pdi = talib.PLUS_DI(high_prices, low_prices, close_prices, timeperiod=ContextInfo.N)
# 计算 -DI (MDI)
mdi = talib.MINUS_DI(high_prices, low_prices, close_prices, timeperiod=ContextInfo.N)
# 计算 ADX
adx = talib.ADX(high_prices, low_prices, close_prices, timeperiod=ContextInfo.N)
# 获取最新和上一个周期的指标值
# -1 代表当前K线,-2 代表上一根K线
current_pdi = pdi[-1]
current_mdi = mdi[-1]
current_adx = adx[-1]
last_pdi = pdi[-2]
last_mdi = mdi[-2]
# ----------------------------------------------------------------
# 交易逻辑
# ----------------------------------------------------------------
# 获取当前持仓
positions = ContextInfo.get_position(stock)
current_holding = 0
if positions:
# 注意:get_position 返回的是对象列表,通常取第一个
# 实盘中建议使用 get_trade_detail_data 获取更准确持仓
pass
# 这里为了简化回测逻辑,我们使用 order_target_percent 来控制仓位
# 买入条件:
# 1. +DI 上穿 -DI (金叉)
# 2. ADX 大于阈值 (趋势确认)
buy_condition = (last_pdi <= last_mdi) and (current_pdi > current_mdi) and (current_adx > ContextInfo.ADX_THRESHOLD)
# 卖出条件:
# 1. +DI 下穿 -DI (死叉)
sell_condition = (last_pdi >= last_mdi) and (current_pdi < current_mdi)
# 执行交易
if buy_condition:
# 满仓买入 (目标仓位调整为 100%)
# 注意:实盘请做好资金管理,不要轻易满仓
print(f"{stock} 触发买入信号: PDI={current_pdi:.2f}, MDI={current_mdi:.2f}, ADX={current_adx:.2f}")
order_target_percent(stock, 1.0, ContextInfo, ContextInfo.account_id)
elif sell_condition:
# 清仓卖出 (目标仓位调整为 0%)
print(f"{stock} 触发卖出信号: PDI={current_pdi:.2f}, MDI={current_mdi:.2f}")
order_target_percent(stock, 0.0, ContextInfo, ContextInfo.account_id)
代码详解
-
初始化 (
init):- 定义了股票池
stock_list和交易账号。 - 设置了 DMI 指标的参数:
N=14是标准周期,ADX_THRESHOLD=20用于过滤震荡行情。 - 使用
ContextInfo.set_universe设定股票池。
- 定义了股票池
-
数据获取 (
handlebar):- 使用
ContextInfo.get_market_data_ex获取历史的高、低、收盘价。 - 注意:获取的数据长度
count必须大于指标计算所需的周期(例如 100),否则talib计算出的结果可能是NaN。
- 使用
-
指标计算:
- 利用 QMT 内置的
talib库进行高效计算。 talib.PLUS_DI: 计算 +DI。talib.MINUS_DI: 计算 -DI。talib.ADX: 计算 ADX。
- 利用 QMT 内置的
-
交易逻辑:
- 金叉判断:
last_pdi <= last_mdi且current_pdi > current_mdi。这意味着上一根 K 线 -DI 在上方或相等,当前 K 线 +DI 跑到了上方。 - 趋势过滤:加入
current_adx > ContextInfo.ADX_THRESHOLD条件,避免在无趋势的震荡市中频繁开仓。 - 死叉判断:
last_pdi >= last_mdi且current_pdi < current_mdi。
- 金叉判断:
-
下单执行:
- 使用
order_target_percent函数。这是一个非常方便的函数,它会自动计算需要买卖的数量,将该股票的持仓调整到总资产的指定比例(例如1.0代表满仓,0.0代表清仓)。
- 使用
注意事项
- Talib 依赖:QMT 自带 Python 环境通常已包含
talib库。如果您的环境报错找不到模块,可能需要联系管理员或手动安装。 - 数据长度:DMI 指标对数据长度比较敏感,如果数据太短,ADX 的值可能不准确。建议
count设置为N的 3-5 倍以上。 - 实盘账号:代码中的
ContextInfo.account_id必须修改为您实际登录的资金账号,否则无法下单。 - 频率:此策略默认在日线 (
1d) 级别运行。如果需要在分钟级别运行,请修改ContextInfo.period并确保下载了对应的分钟数据。
Q&A
Q: 如何调整 DMI 的灵敏度?
A: 修改 init 函数中的 ContextInfo.N。减小 N 值(如改为 7)会使指标更灵敏,但假信号增多;增大 N 值(如改为 20)会使指标更平滑,但信号会有滞后。
Q: 为什么要用 ADX 过滤?
A: DMI 是一个趋势指标。在盘整(横盘)行情中,+DI 和 -DI 会频繁交叉,导致频繁止损。ADX 代表趋势的强度,只有当 ADX 大于一定数值(如 20 或 25)时,才认为当前有足够的趋势去进行交易。
Q: order_target_percent 在回测和实盘都能用吗?
A: 是的,这个函数在回测和实盘模式下都支持。在实盘中,它会根据账户的总资产计算目标市值,然后下单调整持仓。