问题描述
解决方案
这是一个基于 ADX (Average Directional Index) 和 DMI (Directional Movement Index) 指标的趋势跟踪策略。
策略逻辑说明
ADX 指标主要用于判断趋势的强度,而不是方向。通常结合 DMI 指标中的 +DI (PDI) 和 -DI (MDI) 来判断方向。
-
指标定义:
- ADX:平均趋向指标,数值越大代表趋势越强(无论涨跌)。一般认为 ADX > 25 代表有趋势。
- +DI (PDI):上升方向线,代表买方力量。
- -DI (MDI):下降方向线,代表卖方力量。
-
交易信号:
- 买入条件:
- ADX > 25(趋势确立)。
- +DI > -DI(多头占优)。
- 当前无持仓。
- 卖出条件:
- ADX < 25(趋势减弱/盘整)。
- 或者 +DI < -DI(空头反转)。
- 当前有持仓。
- 买入条件:
-
参数设置:
- 计算周期:14(标准设置)。
- ADX 阈值:25。
策略代码
# -*- coding: utf-8 -*-
import jqdata
import talib
import numpy as np
def initialize(context):
"""
初始化函数,设定基准、股票池、参数等
"""
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# 设定操作的股票,这里以平安银行为例
g.security = '000001.XSHE'
# 策略参数
g.N = 14 # ADX计算周期
g.adx_threshold = 25 # ADX趋势强度阈值
# 每天开盘时运行
run_daily(market_open, time='09:30')
def market_open(context):
"""
每日交易逻辑
"""
security = g.security
# 1. 获取历史数据
# talib计算ADX通常需要比周期更多的数据以保证平滑后的准确性,这里取 N * 3 天
lookback = g.N * 3
# 获取最高价、最低价、收盘价
data = attribute_history(security, lookback, '1d', ['high', 'low', 'close'])
# 如果数据不足,直接返回
if len(data) < lookback:
return
# 转换数据格式为 numpy array,talib 需要 float 类型
high_prices = data['high'].values.astype(float)
low_prices = data['low'].values.astype(float)
close_prices = data['close'].values.astype(float)
# 2. 计算指标
# 计算 ADX
adx = talib.ADX(high_prices, low_prices, close_prices, timeperiod=g.N)
# 计算 +DI (PDI)
pdi = talib.PLUS_DI(high_prices, low_prices, close_prices, timeperiod=g.N)
# 计算 -DI (MDI)
mdi = talib.MINUS_DI(high_prices, low_prices, close_prices, timeperiod=g.N)
# 获取最新的指标值(数组的最后一个元素)
current_adx = adx[-1]
current_pdi = pdi[-1]
current_mdi = mdi[-1]
# 获取当前持仓
position = context.portfolio.positions[security]
# 3. 交易逻辑
# 买入逻辑:ADX大于阈值(趋势强) 且 PDI大于MDI(多头趋势)
if current_adx > g.adx_threshold and current_pdi > current_mdi:
if position.total_amount == 0:
# 全仓买入
order_value(security, context.portfolio.available_cash)
log.info("买入信号触发: ADX={:.2f}, +DI={:.2f}, -DI={:.2f}".format(current_adx, current_pdi, current_mdi))
# 卖出逻辑:ADX小于阈值(趋势减弱) 或者 PDI小于MDI(空头反转)
elif current_adx < g.adx_threshold or current_pdi < current_mdi:
if position.closeable_amount > 0:
# 清仓卖出
order_target(security, 0)
log.info("卖出信号触发: ADX={:.2f}, +DI={:.2f}, -DI={:.2f}".format(current_adx, current_pdi, current_mdi))
# 4. 记录数据用于画图
record(ADX=current_adx, PDI=current_pdi, MDI=current_mdi)
代码解析
- 库导入:
- 导入
talib库,这是金融量化中计算技术指标最常用的库,计算效率高且准确。
- 导入
- 数据获取 (
attribute_history):- ADX 计算依赖于最高价 (
high)、最低价 (low) 和收盘价 (close)。 - 我们获取了
g.N * 3天的数据。虽然参数是 14 天,但 ADX 算法内部包含平滑移动平均,获取更多历史数据可以减少计算初期的误差,使指标值更稳定。
- ADX 计算依赖于最高价 (
- 指标计算:
talib.ADX(...): 计算趋势强度。talib.PLUS_DI(...): 计算正向指标(多头力量)。talib.MINUS_DI(...): 计算负向指标(空头力量)。
- 交易执行:
- 使用
order_value进行全仓买入。 - 使用
order_target调整仓位至 0 进行平仓。 - 加入了
position.closeable_amount > 0判断,确保有可卖出的持仓(T+1 规则)。
- 使用
常见问题 (Q&A)
Q1: 为什么 ADX 指标有时候反应比较滞后?
A: ADX 是对价格波动的平滑处理,本质上是均线的一种衍生。任何基于均线的指标都会有滞后性。为了减少滞后,可以尝试减小计算周期 g.N(例如改为 10 或 7),但这可能会增加假信号(噪音)。
Q2: 如何在多只股票上运行这个策略?
A: 可以将 g.security 改为一个列表(例如 get_index_stocks('000300.XSHG')),然后在 market_open 函数中使用 for 循环遍历每只股票,分别计算指标并独立下单。同时需要注意资金分配(例如每只股票分配总资金的 1/10)。
Q3: 为什么代码中要获取 g.N * 3 天的数据?
A: talib 计算 ADX 时使用了 Wilder's Smoothing(威尔德平滑),这种算法对起始数据敏感。如果只获取 14 天数据,计算出的第一个 ADX 值可能不准确。获取更多历史数据可以让指标“预热”,使最终的计算结果与看盘软件一致。
Q4: 这个策略适合震荡市吗?
A: 不适合。ADX 本身就是为了区分趋势和震荡而设计的。当 ADX < 25 时,策略会选择空仓观望,从而避免在震荡市中反复止损。这是该策略的核心优势之一。