问题描述
解决方案
成交量加权 MACD(VW-MACD)是一种将成交量因素纳入考量的趋势指标。与传统 MACD 仅使用收盘价计算 EMA 不同,VW-MACD 通常使用**成交量加权移动平均(VWMA)**来替代普通的指数移动平均(EMA)。
这种方法认为,成交量大的价格变动比成交量小的变动更具市场意义。
策略设计思路
- 数据获取:获取收盘价(Close)和成交量(Volume)。
- 计算 PV:计算每日的
价格 * 成交量。 - 计算 VWMA:
- 分别计算短期(如12日)和长期(如26日)的 VWMA。
- 公式:$VWMA_n = \frac{\sum_{i=1}^{n}(Price_i \times Volume_i)}{\sum_{i=1}^{n}(Volume_i)}$
- 计算 DIF:$DIF = VWMA_{short} - VWMA_{long}$。
- 计算 DEA:对 DIF 进行平滑处理(通常使用 9日 EMA)。
- 计算 MACD 柱:$(DIF - DEA) \times 2$。
- 绘图:使用
ContextInfo.paint将指标绘制在副图上。
QMT 策略代码实现
以下是完整的 Python 代码,您可以直接复制到 QMT 的策略编辑器中运行。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
初始化函数
"""
# 定义 MACD 的周期参数
ContextInfo.short_len = 12 # 短期周期
ContextInfo.long_len = 26 # 长期周期
ContextInfo.mid_len = 9 # DEA 平滑周期
# 设置绘图位置为副图
# 注意:在 QMT 界面上运行模型时,请确保选择“副图”显示
print("VW-MACD 策略初始化完成")
def handlebar(ContextInfo):
"""
K线周期回调函数
"""
# 获取当前主图的股票代码和市场
# 格式如 '600000.SH'
stock_code = ContextInfo.stockcode + '.' + ContextInfo.market
# 获取历史数据
# 为了保证计算的准确性,我们需要获取足够多的历史数据来消除移动平均的滞后影响
# count=300 表示获取当前时刻往前推 300 根K线
data = ContextInfo.get_market_data_ex(
['close', 'volume'],
[stock_code],
period=ContextInfo.period,
count=300,
dividend_type=ContextInfo.dividend_type
)
# 校验数据是否获取成功
if stock_code not in data:
return
df = data[stock_code]
# 如果数据长度不足以计算长期均线,则直接返回
if len(df) < ContextInfo.long_len:
return
# --- 核心计算逻辑:成交量加权 MACD (VW-MACD) ---
# 1. 计算 PV (价格 * 成交量)
# 这是计算 VWMA 的分子部分
df['pv'] = df['close'] * df['volume']
# 2. 计算 VWMA (成交量加权移动平均)
# 公式:RollingSum(Price * Volume, N) / RollingSum(Volume, N)
# 计算短期 VWMA
df['vol_sum_short'] = df['volume'].rolling(window=ContextInfo.short_len).sum()
df['pv_sum_short'] = df['pv'].rolling(window=ContextInfo.short_len).sum()
df['vwma_short'] = df['pv_sum_short'] / df['vol_sum_short']
# 计算长期 VWMA
df['vol_sum_long'] = df['volume'].rolling(window=ContextInfo.long_len).sum()
df['pv_sum_long'] = df['pv'].rolling(window=ContextInfo.long_len).sum()
df['vwma_long'] = df['pv_sum_long'] / df['vol_sum_long']
# 3. 计算 DIF (快线 - 慢线)
# 这里使用 VWMA 替代了传统 MACD 中的 EMA
df['dif'] = df['vwma_short'] - df['vwma_long']
# 4. 计算 DEA (DIF 的 EMA 平滑)
# 信号线通常依然使用 EMA 算法进行平滑
df['dea'] = df['dif'].ewm(span=ContextInfo.mid_len, adjust=False).mean()
# 5. 计算 MACD 柱状值
df['macd'] = (df['dif'] - df['dea']) * 2
# --- 绘图输出 ---
# 获取当前 K 线(最新一根)的指标值
current_dif = df['dif'].iloc[-1]
current_dea = df['dea'].iloc[-1]
current_macd = df['macd'].iloc[-1]
# 处理 NaN 值(刚开始计算时可能为空),避免绘图报错
if np.isnan(current_dif) or np.isnan(current_dea) or np.isnan(current_macd):
return
# 绘制 DIF (白线)
# index=-1 表示在当前 K 线位置绘制
# line_style=0 表示曲线
ContextInfo.paint('VW_DIF', current_dif, -1, 0, 'white')
# 绘制 DEA (黄线)
ContextInfo.paint('VW_DEA', current_dea, -1, 0, 'yellow')
# 绘制 MACD 柱状图
# 根据正负值设置颜色:大于0为红色,小于0为绿色
color = 'red' if current_macd > 0 else 'green'
# line_style=42 表示柱状线
ContextInfo.paint('VW_MACD', current_macd, -1, 42, color)
# 可选:在图表左上角显示数值
# msg = f"VW-MACD: DIF={current_dif:.3f} DEA={current_dea:.3f} MACD={current_macd:.3f}"
# ContextInfo.draw_text(True, current_dif, msg)
代码关键点解析
-
VWMA 替代 EMA:
- 传统 MACD 使用
df['close'].ewm(span=N).mean()。 - 本代码使用
rolling(N).sum()计算周期内的总成交额和总成交量,相除得到 VWMA。这更能反映资金的真实平均成本。
- 传统 MACD 使用
-
数据预热 (
count=300):- MACD 类指标依赖历史数据。如果在
get_market_data_ex中获取的数据太少(例如只获取最近 1 根),会导致无法计算均线或计算结果不准确。建议至少获取long_len * 3长度的数据。
- MACD 类指标依赖历史数据。如果在
-
绘图函数 (
paint):ContextInfo.paint(name, value, index, line_style, color)是 QMT 专用的绘图接口。index=-1确保数值绘制在当前 K 线的时间点上。line_style=42专门用于绘制类似 MACD 红绿柱的柱状图。
如何使用
- 打开 QMT 客户端,进入模型研究/策略编辑器。
- 新建一个 Python 策略。
- 将上述代码粘贴进去。
- 在右侧属性栏中,将运行位置设置为 “副图”。
- 点击运行或回测,即可在下方副图看到成交量加权的 MACD 指标。
Q&A
Q: 为什么要用成交量加权 MACD?
A: 传统 MACD 仅基于价格,可能会在缩量上涨(诱多)或缩量下跌时发出虚假信号。成交量加权 MACD 考虑了成交量,只有在成交量配合下的价格变动才会显著推动指标,因此能更好地过滤市场噪音,确认趋势的有效性。
Q: 代码中的 get_market_data_ex 为什么使用 count=300?
A: 移动平均线(MA)和指数平滑(EMA)都需要一定长度的历史数据才能计算出结果。如果只取当前一根 K 线的数据,无法计算出 26 日均线。取 300 根是为了确保有足够的历史数据来生成稳定的指标值。
Q: 这个指标可以用于回测交易吗?
A: 可以。您可以在 handlebar 函数中添加交易逻辑。例如:
# 金叉买入
if current_dif > current_dea and df['dif'].iloc[-2] <= df['dea'].iloc[-2]:
passorder(23, 1101, ContextInfo.accid, stock_code, 5, -1, 100, ContextInfo)