问题描述
解决方案
这是一个基于 DMI (Directional Movement Index, 趋向指标) 的量化交易策略源码。
策略原理
DMI 指标主要由四条线组成:
- PDI (+DI):上升方向线,代表上涨趋势的强度。
- MDI (-DI):下降方向线,代表下跌趋势的强度。
- ADX:趋向平均值,代表趋势的强度(不分上涨或下跌)。
- ADXR:ADX 的评估数值(本策略主要使用前三者)。
交易逻辑:
- 买入信号(多头):
- PDI (+DI) 上穿 MDI (-DI):形成金叉,表明多头力量开始强于空头。
- ADX > 阈值 (如 20):确保当前处于有趋势的状态,避免在盘整震荡期频繁开仓。
- 卖出信号(空头/平仓):
- PDI (+DI) 下穿 MDI (-DI):形成死叉,表明空头力量开始强于多头。
- 或者持仓股票发生亏损/止盈(本策略演示基础的死叉平仓)。
PTrade 策略源码
import talib
import numpy as np
import pandas as pd
def initialize(context):
"""
策略初始化函数,只在开始时运行一次
"""
# 设置我们要操作的股票,这里以恒生电子为例
g.security = '600570.SS'
set_universe(g.security)
# DMI 指标参数设置
g.N = 14 # 计算 DI 的周期,通常为 14
g.M = 6 # 计算 ADX 的周期,通常为 6
# ADX 趋势强度阈值,低于此值认为震荡,不交易
g.adx_threshold = 20
# 设置滑点和佣金(回测用,实盘可忽略)
set_slippage(slippage=0.002)
set_commission(commission_ratio=0.0003, min_commission=5.0)
def handle_data(context, data):
"""
策略主要逻辑,按频率(分钟或日线)执行
"""
security = g.security
# 1. 获取历史数据
# DMI计算需要 High, Low, Close 数据
# 获取过去 60 根 K 线,保证 talib 计算有足够的数据预热
# 注意:count 必须大于 g.N + g.M 很多,否则计算出的 ADX 前期可能是 NaN
h = get_history(60, frequency='1d', field=['high', 'low', 'close'], security_list=security)
# 如果数据不足,直接返回
if len(h['close']) < 60:
return
# 将数据转换为 numpy 数组,供 talib 使用
high_prices = h['high'].values
low_prices = h['low'].values
close_prices = h['close'].values
# 2. 计算 DMI 指标
# PLUS_DI (+DI)
p_di = talib.PLUS_DI(high_prices, low_prices, close_prices, timeperiod=g.N)
# MINUS_DI (-DI)
m_di = talib.MINUS_DI(high_prices, low_prices, close_prices, timeperiod=g.N)
# ADX
adx = talib.ADX(high_prices, low_prices, close_prices, timeperiod=g.N)
# 获取当前和前一时刻的指标值(用于判断交叉)
# -1 代表当前最新值,-2 代表上一周期值
current_p_di = p_di[-1]
last_p_di = p_di[-2]
current_m_di = m_di[-1]
last_m_di = m_di[-2]
current_adx = adx[-1]
# 检查计算结果是否包含 NaN (数据不足时可能发生)
if np.isnan(current_p_di) or np.isnan(current_adx):
return
# 3. 获取当前持仓和资金
position = get_position(security)
cash = context.portfolio.cash
# 4. 交易逻辑判断
# 买入条件:
# 1. +DI 上穿 -DI (金叉)
# 2. ADX 大于阈值 (趋势够强)
# 3. 当前无持仓
cross_over = (last_p_di <= last_m_di) and (current_p_di > current_m_di)
trend_strong = current_adx > g.adx_threshold
if cross_over and trend_strong and position.amount == 0:
# 全仓买入
order_value(security, cash)
log.info("买入信号触发: +DI金叉-DI且ADX强于阈值。当前ADX: %.2f" % current_adx)
# 卖出条件:
# 1. +DI 下穿 -DI (死叉)
# 2. 当前有持仓
cross_under = (last_p_di >= last_m_di) and (current_p_di < current_m_di)
if cross_under and position.amount > 0:
# 清仓卖出
order_target(security, 0)
log.info("卖出信号触发: +DI死叉-DI。")
def before_trading_start(context, data):
"""
盘前处理,可选
"""
pass
def after_trading_end(context, data):
"""
盘后处理,可选
"""
pass
代码关键点解析
-
库的引用:
- 使用了
talib库。PTrade 环境内置了 TA-Lib,这是计算技术指标最高效、最标准的方法。 talib.PLUS_DI对应 +DI。talib.MINUS_DI对应 -DI。talib.ADX对应 ADX。
- 使用了
-
数据获取 (
get_history):- DMI 计算依赖最高价 (
high)、最低价 (low) 和收盘价 (close)。 count=60:虽然周期是 14,但 ADX 的计算涉及平滑移动平均,需要较长的历史数据来消除计算初期的不稳定(预热),因此获取 60 天数据比较稳妥。
- DMI 计算依赖最高价 (
-
交叉判断:
- 通过比较
[-1](当前值) 和[-2](上一周期值) 来判断是否发生了“上穿”或“下穿”。 last_p_di <= last_m_di且current_p_di > current_m_di意味着刚刚发生了金叉。
- 通过比较
-
ADX 过滤:
- DMI 策略的一个常见弱点是在震荡市中频繁发出假信号。代码中加入了
if current_adx > g.adx_threshold判断,只有当趋势强度大于 20 时才允许开仓,这能有效过滤掉一部分无效波动。
- DMI 策略的一个常见弱点是在震荡市中频繁发出假信号。代码中加入了
如何在 PTrade 中使用
- 打开 PTrade 客户端。
- 进入 “量化” -> “研究” 或 “回测” 界面。
- 新建一个策略文件。
- 将上述代码完整复制粘贴进去。
- 点击 “回测”,设置回测时间段(例如最近一年),即可查看策略表现。
Q&A
Q: 为什么不直接用 PTrade 自带的指标函数?
A: PTrade 的 API 文档中提供了 get_MACD, get_KDJ 等函数,但并没有直接提供 get_DMI。因此,使用 Python 标准金融库 talib 是最通用且准确的实现方式。
Q: 如何调整策略的灵敏度?
A: 修改 initialize 函数中的 g.N。
- 减小
g.N(如改为 7):指标更敏感,信号更多,但假信号也更多。 - 增大
g.N(如改为 20):指标更迟钝,信号更稳,但可能错过行情前半段。
Q: 这个策略可以用于分钟线吗?
A: 可以。只需在回测配置时将频率选为“分钟”,或者在 get_history 中将 frequency='1d' 改为 frequency='1m' (或其他分钟周期) 即可。