别再只看金叉死叉了!用Python实战DMI指标,教你量化识别趋势强度(附完整代码)
用Python实战DMI指标:量化识别趋势强度的进阶策略
在量化交易领域,技术指标的应用往往陷入两个极端:要么过度依赖简单的金叉死叉信号,要么陷入复杂的理论分析而缺乏可执行的代码实现。今天我们将打破这种局面,聚焦DMI指标(Directional Movement Index)的Python实战应用,从数据获取到策略回测,手把手教你构建一个能有效识别趋势强度的量化系统。
1. 环境准备与数据获取
工欲善其事,必先利其器。我们需要先搭建好Python量化分析环境,并获取高质量的金融数据。推荐使用Jupyter Notebook或VSCode作为开发环境,它们能提供良好的交互体验和代码调试能力。
首先安装必要的Python库:
pip install pandas numpy matplotlib akshare backtrader
这里我们选择akshare作为数据源,它提供了丰富且免费的金融数据接口。下面是一个获取股票历史数据的函数示例:
import akshare as ak
def get_stock_data(stock_code, start_date, end_date):
"""
获取股票历史数据
:param stock_code: 股票代码,如'sh600000'
:param start_date: 开始日期,格式'YYYY-MM-DD'
:param end_date: 结束日期,格式'YYYY-MM-DD'
:return: DataFrame格式的股票数据
"""
df = ak.stock_zh_a_hist(symbol=stock_code, period="daily",
start_date=start_date, end_date=end_date, adjust="hfq")
df['date'] = pd.to_datetime(df['日期'])
df.set_index('date', inplace=True)
df = df[['开盘', '最高', '最低', '收盘', '成交量']]
df.columns = ['open', 'high', 'low', 'close', 'volume']
return df
提示:在实际应用中,建议将获取的数据本地化存储,避免频繁请求接口。可以使用
df.to_csv()保存数据,再用pd.read_csv()读取。
2. DMI指标的核心计算逻辑
DMI指标由三个核心部分组成:+DI(正向指标)、-DI(负向指标)和ADX(平均趋向指数)。理解它们的计算原理对于后续的策略优化至关重要。
2.1 真实波动范围(TR)与方向运动(DM)
计算DMI的第一步是确定真实波动范围(True Range)和方向运动(Directional Movement)。以下是Python实现:
def calculate_tr(df):
"""计算真实波动范围(True Range)"""
tr1 = df['high'] - df['low']
tr2 = abs(df['high'] - df['close'].shift(1))
tr3 = abs(df['low'] - df['close'].shift(1))
df['tr'] = np.maximum.reduce([tr1, tr2, tr3])
return df
def calculate_dm(df):
"""计算方向运动(+DM和-DM)"""
up_move = df['high'] - df['high'].shift(1)
down_move = df['low'].shift(1) - df['low']
df['+dm'] = np.where((up_move > down_move) & (up_move > 0), up_move, 0)
df['-dm'] = np.where((down_move > up_move) & (down_move > 0), down_move, 0)
return df
2.2 平滑处理与DI值计算
DMI指标使用平滑移动平均而非简单移动平均,这是其独特之处。下面是平滑处理函数:
def smoothed_ma(series, window, prev_smoothed):
"""
计算平滑移动平均
:param series: 待计算序列
:param window: 窗口大小
:param prev_smoothed: 前一日平滑值
"""
return prev_smoothed - (prev_smoothed / window) + series
基于平滑值,我们可以计算+DI和-DI:
def calculate_dmi(df, window=14):
"""计算+DI和-DI"""
# 初始化平滑值
df['smoothed_tr'] = 0.0
df['smoothed_+dm'] = 0.0
df['smoothed_-dm'] = 0.0
# 计算前window天的简单移动平均作为初始值
df.loc[df.index[window-1], 'smoothed_tr'] = df['tr'].iloc[:window].mean()
df.loc[df.index[window-1], 'smoothed_+dm'] = df['+dm'].iloc[:window].mean()
df.loc[df.index[window-1], 'smoothed_-dm'] = df['-dm'].iloc[:window].mean()
# 计算平滑值
for i in range(window, len(df)):
prev_idx = df.index[i-1]
curr_idx = df.index[i]
df.loc[curr_idx, 'smoothed_tr'] = smoothed_ma(
df.loc[curr_idx, 'tr'], window, df.loc[prev_idx, 'smoothed_tr'])
df.loc[curr_idx, 'smoothed_+dm'] = smoothed_ma(
df.loc[curr_idx, '+dm'], window, df.loc[prev_idx, 'smoothed_+dm'])
df.loc[curr_idx, 'smoothed_-dm'] = smoothed_ma(
df.loc[curr_idx, '-dm'], window, df.loc[prev_idx, 'smoothed_-dm'])
# 计算DI值
df['+di'] = (df['smoothed_+dm'] / df['smoothed_tr']) * 100
df['-di'] = (df['smoothed_-dm'] / df['smoothed_tr']) * 100
return df
2.3 ADX的计算与趋势强度判断
ADX是判断趋势强度的关键指标,其计算需要先得到DX(趋向指数):
def calculate_adx(df, window=14):
"""计算ADX"""
# 计算DX
df['dx'] = (abs(df['+di'] - df['-di']) / (df['+di'] + df['-di'])) * 100
# 初始化ADX
df['adx'] = 0.0
df.loc[df.index[window*2-2], 'adx'] = df['dx'].iloc[window-1:window*2-1].mean()
# 计算ADX平滑值
for i in range(window*2-1, len(df)):
prev_idx = df.index[i-1]
curr_idx = df.index[i]
df.loc[curr_idx, 'adx'] = smoothed_ma(
df.loc[curr_idx, 'dx'], window, df.loc[prev_idx, 'adx'])
return df
3. 策略构建与信号生成
单纯的DI交叉信号容易产生大量假信号,我们需要结合ADX进行过滤,构建更可靠的交易策略。
3.1 基础信号生成
首先实现基本的DI交叉信号:
def generate_basic_signals(df):
"""生成基础交易信号"""
# DI交叉信号
df['+di_cross'] = (df['+di'] > df['-di']) & (df['+di'].shift(1) <= df['-di'].shift(1))
df['-di_cross'] = (df['-di'] > df['+di']) & (df['-di'].shift(1) <= df['+di'].shift(1))
# 初始化信号列
df['signal'] = 0 # 0表示无信号,1表示买入,-1表示卖出
# 生成信号
df.loc[df['+di_cross'], 'signal'] = 1
df.loc[df['-di_cross'], 'signal'] = -1
return df
3.2 使用ADX过滤假信号
加入ADX过滤可以显著提高信号质量:
def filter_signals_with_adx(df, adx_threshold=25):
"""使用ADX过滤信号"""
# 复制基础信号
df['filtered_signal'] = df['signal'].copy()
# 过滤条件:ADX必须大于阈值且呈上升趋势
weak_trend = (df['adx'] < adx_threshold) | (df['adx'] < df['adx'].shift(1))
# 应用过滤
df.loc[weak_trend & (df['signal'] == 1), 'filtered_signal'] = 0
df.loc[weak_trend & (df['signal'] == -1), 'filtered_signal'] = 0
return df
3.3 多时间周期参数优化
不同时间周期参数会显著影响信号质量,我们可以比较14日和28日参数的效果:
| 参数类型 | 信号频率 | 信号质量 | 适用场景 |
|---|---|---|---|
| 14日周期 | 较高 | 中等 | 趋势明显的市场 |
| 28日周期 | 较低 | 较高 | 震荡或趋势不明显的市场 |
实现多周期DMI计算:
def multi_period_dmi(df, periods=[14, 28]):
"""计算多周期DMI指标"""
df = calculate_tr(df)
df = calculate_dm(df)
for period in periods:
df = calculate_dmi(df, window=period)
df = calculate_adx(df, window=period)
# 重命名列以避免冲突
df.rename(columns={
'+di': f'+di_{period}',
'-di': f'-di_{period}',
'adx': f'adx_{period}'
}, inplace=True)
return df
4. 策略回测与绩效评估
策略构建完成后,我们需要通过历史数据回测来验证其有效性。这里使用backtrader回测框架。
4.1 回测策略实现
import backtrader as bt
class DMIStrategy(bt.Strategy):
params = (
('adx_threshold', 25),
('period', 14),
)
def __init__(self):
# 计算DMI指标
self.dmi = DMI(
self.data,
period=self.p.period,
adx_threshold=self.p.adx_threshold
)
def next(self):
if not self.position:
if self.dmi.buy_signal[0]:
self.buy()
elif self.dmi.sell_signal[0]:
self.close()
4.2 回测执行与结果分析
完整的回测执行代码:
def run_backtest(data, initial_cash=100000, commission=0.001, adx_threshold=25, period=14):
cerebro = bt.Cerebro()
cerebro.addstrategy(DMIStrategy, adx_threshold=adx_threshold, period=period)
# 添加数据
data_feed = bt.feeds.PandasData(dataname=data)
cerebro.adddata(data_feed)
# 设置初始资金和手续费
cerebro.broker.setcash(initial_cash)
cerebro.broker.setcommission(commission=commission)
# 添加分析器
cerebro.addanalyzer(bt.analyzers.Returns)
cerebro.addanalyzer(bt.analyzers.SharpeRatio)
cerebro.addanalyzer(bt.analyzers.DrawDown)
# 运行回测
results = cerebro.run()
# 打印结果
print(f'最终资产价值: {cerebro.broker.getvalue():.2f}')
print(f'年化收益率: {results[0].analyzers.returns.get_analysis()["rnorm100"]:.2f}%')
print(f'夏普比率: {results[0].analyzers.sharperatio.get_analysis()["sharperatio"]:.2f}')
print(f'最大回撤: {results[0].analyzers.drawdown.get_analysis()["max"]["drawdown"]:.2f}%')
# 绘制结果
cerebro.plot(style='candlestick')
4.3 参数优化与敏感性分析
通过网格搜索寻找最优参数组合:
def optimize_parameters(data):
# 定义参数范围
adx_thresholds = [20, 25, 30]
periods = [10, 14, 20, 28]
best_params = None
best_sharpe = -np.inf
for adx in adx_thresholds:
for period in periods:
cerebro = bt.Cerebro()
cerebro.addstrategy(DMIStrategy, adx_threshold=adx, period=period)
data_feed = bt.feeds.PandasData(dataname=data)
cerebro.adddata(data_feed)
cerebro.broker.setcash(100000)
cerebro.broker.setcommission(commission=0.001)
cerebro.addanalyzer(bt.analyzers.SharpeRatio)
results = cerebro.run()
sharpe = results[0].analyzers.sharperatio.get_analysis()['sharperatio']
if sharpe > best_sharpe:
best_sharpe = sharpe
best_params = (adx, period)
print(f'最优参数组合: ADX阈值={best_params[0]}, 周期={best_params[1]}')
print(f'最优夏普比率: {best_sharpe:.2f}')
return best_params
在实际应用中,我发现将DMI指标与简单的移动平均线结合使用效果更佳。例如,只有当价格在200日均线之上时才考虑做多信号,在200日均线之下时只考虑做空信号,这样可以进一步过滤掉不符合大趋势的信号。
更多推荐


所有评论(0)