问题描述
解决方案
这是一个基于 DMA (Different of Moving Average) 平均线差指标 的聚宽(JoinQuant)交易策略。
策略原理
DMA 指标利用两条不同期间的移动平均线(通常为短期和长期)的差值来判断趋势。
- DIF (差值线) = 短期均线 (MA1) - 长期均线 (MA2)
- DIFMA (平均值线) = DIF 的 M 日移动平均
- 买入信号 (金叉):DIF 向上突破 DIFMA。
- 卖出信号 (死叉):DIF 向下跌破 DIFMA。
策略代码
# -*- coding: utf-8 -*-
import jqdata
import pandas as pd
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'
# DMA指标参数
g.N1 = 10 # 短期均线周期
g.N2 = 50 # 长期均线周期
g.M = 10 # DIF的移动平均周期 (AMA)
# 设定交易费用:买入万分之三,卖出万分之三加千分之一印花税
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 每天开盘时运行
run_daily(market_open, time='09:30')
def market_open(context):
"""
每日交易逻辑
"""
security = g.security
# 1. 获取历史数据
# 我们需要计算 N2 日均线,且需要计算 DIF 的 M 日均线,
# 还需要前一日的数据来判断交叉,所以获取的数据长度要足够长
# 长度至少为 N2 + M + 2
history_count = g.N2 + g.M + 10
# 获取收盘价数据
h_data = attribute_history(security, history_count, '1d', ['close'])
# 如果数据不足,直接返回
if len(h_data) < history_count:
return
close_prices = h_data['close']
# 2. 计算 DMA 指标
# 计算短期均线 MA(N1)
ma_short = close_prices.rolling(window=g.N1).mean()
# 计算长期均线 MA(N2)
ma_long = close_prices.rolling(window=g.N2).mean()
# 计算 DIF = MA_short - MA_long
dif = ma_short - ma_long
# 计算 DIFMA = DIF 的 M 日简单移动平均
difma = dif.rolling(window=g.M).mean()
# 3. 获取当前和前一日的指标值用于判断交叉
# 当前值 (-1)
curr_dif = dif.iloc[-1]
curr_difma = difma.iloc[-1]
# 前一日值 (-2)
prev_dif = dif.iloc[-2]
prev_difma = difma.iloc[-2]
# 检查是否包含 NaN 值(数据计算初期可能产生NaN),若有则跳过
if np.isnan(curr_dif) or np.isnan(curr_difma) or np.isnan(prev_dif) or np.isnan(prev_difma):
return
# 4. 交易逻辑
# 获取当前仓位
curr_position = context.portfolio.positions[security].closeable_amount
# 获取可用资金
cash = context.portfolio.available_cash
# 信号判断
# 金叉:前一日 DIF < DIFMA,且 当前 DIF > DIFMA
golden_cross = (prev_dif < prev_difma) and (curr_dif > curr_difma)
# 死叉:前一日 DIF > DIFMA,且 当前 DIF < DIFMA
death_cross = (prev_dif > prev_difma) and (curr_dif < curr_difma)
# 执行交易
if golden_cross and cash > 0:
# 全仓买入
order_value(security, cash)
log.info(f"DMA金叉买入: {security}, DIF: {curr_dif:.3f}, DIFMA: {curr_difma:.3f}")
elif death_cross and curr_position > 0:
# 清仓卖出
order_target(security, 0)
log.info(f"DMA死叉卖出: {security}, DIF: {curr_dif:.3f}, DIFMA: {curr_difma:.3f}")
# 绘制图表(仅在回测中有效)
record(DIF=curr_dif, DIFMA=curr_difma)
代码说明
- 参数设置:
g.N1 = 10:短期均线周期。g.N2 = 50:长期均线周期。g.M = 10:DIFMA 的平滑周期。- 这些参数可以根据需要在
initialize中修改。
- 数据获取:
- 使用
attribute_history获取收盘价。 - 获取的数据长度必须大于
N2 + M,否则无法计算出有效的 DIFMA 值。
- 使用
- 指标计算:
- 利用 Pandas 的
rolling().mean()方法快速计算移动平均线。 DIF是两条均线的差。DIFMA是DIF的移动平均。
- 利用 Pandas 的
- 信号判断:
- 通过比较
iloc[-1](当前) 和iloc[-2](前一天) 的数值关系来判断是否发生了“金叉”或“死叉”。
- 通过比较
- 交易执行:
- 金叉时使用
order_value全仓买入。 - 死叉时使用
order_target将仓位调整为 0(清仓)。
- 金叉时使用
Q&A
Q: DMA指标和MACD指标有什么区别?
A: 两者逻辑相似,都是利用快慢均线的差离。区别在于:DMA通常使用简单移动平均(SMA),且参数设置较大(如10, 50),对趋势反应较稳健但滞后;MACD使用指数移动平均(EMA),且经过了更复杂的平滑处理,对近期价格变化更敏感。
Q: 如何优化这个策略?
A: 1. 参数优化:可以通过聚宽的研究模块遍历不同的 N1, N2, M 组合寻找最优参数。2. 增加过滤条件:仅靠 DMA 交叉容易在震荡市中频繁止损,可以结合成交量(如放量金叉)或大盘趋势(如沪深300在20日均线上方)作为过滤条件。3. 止损止盈:加入固定的百分比止损或移动止损逻辑。
Q: 为什么代码中要获取 g.N2 + g.M + 10 这么长的数据?
A: 计算长期均线需要前 N2 天的数据,计算 DIFMA 又需要前 M 个 DIF 值(即依赖更早的数据)。为了确保计算出的最新指标不是 NaN(空值),必须获取足够长的历史窗口。