问题描述
def MACD(security_list, fastperiod=12, slowperiod=26, signalperiod=9):
# 修复传入为单只股票的情况
if isinstance(security_list, str):
security_list = [security_list]
# 计算 MACD
security_data = history(slowperiod*20, '1d', 'close' , security_list, df=False, skip_paused=True)
macd_DIF = {}; macd_DEA = {}; macd_HIST = {}
for stock in security_list:
nan_count = list(np.isnan(security_data[stock])).count(True)
if nan_count == len(security_data[stock]):
log.info("股票 %s 输入数据全是 NaN,该股票可能已退市或刚上市,返回 NaN 值数据。" %stock)
macd_DIF[stock] = array([np.nan])
macd_DEA[stock] = array([np.nan])
macd_HIST[stock]= array([np.nan])
else:
macd_DIF[stock], macd_DEA[stock], macd = talib.MACDEXT(security_data[stock], fastperiod=fastperiod, fastmatype=1, slowperiod=slowperiod, slowmatype=1, signalperiod=signalperiod, signalmatype=1)
macd_HIST[stock] = macd * 2
return macd_DIF, macd_DEA, macd_HIST
macd_DIF, macd_DEA, macd_HIST=MACD(stocks, fastperiod=12, slowperiod=26, signalperiod=9)
print(macd_DIF, macd_DEA, macd_HIST)
Img
通达信同一天的值是-0.22.
用jqlib.technical_analysis 也试过,结果也是差非常多,望大家解惑,谢谢!
解决方案
这是一个在量化交易中非常经典且常见的问题。在使用 JoinQuant(或其他量化平台)调用 talib 或 jqlib 计算 MACD、EMA、WR 等技术指标时,结果与通达信、同花顺等传统看盘软件产生巨大差异,通常是由以下几个核心原因造成的:
1. 核心原因:EMA 算法的“长记忆性”与数据截断(最主要原因)
MACD 的核心是计算 EMA(指数移动平均线)。EMA 的计算公式是递归的:
EMA(今日) = α * 今日收盘价 + (1 - α) * EMA(昨日)
这意味着,今天的 EMA 值依赖于昨天的 EMA 值,而昨天的又依赖于前天的,一直追溯到这只股票上市的第一天。
- 通达信的做法:通达信在计算 MACD 时,通常是加载了该股票上市以来的所有历史 K 线数据,从第一天开始往后递推计算 EMA。
- 你的代码的做法:在你的代码中,
history(slowperiod*20, ...)大约只获取了过去 520 天的数据。talib在接收到这 520 天的数据时,只能把第 1 天的数据当作 EMA 的初始值。由于丢失了前几年的历史数据积累,初始值不同,导致后面计算出的 EMA、DIF、DEA 都会产生误差。
解决办法:虽然理论上需要所有历史数据,但 EMA 的权重是呈指数衰减的。通常,获取过去 250 天到 300 天以上的数据作为“预热期”,计算到今天的误差就已经小到可以忽略不计了。建议将获取数据的长度加大,例如 history(300, '1d', 'close', ...)。
2. 复权方式与基准日的差异
- 复权基准日:JoinQuant 默认使用前复权(
fq='pre')。前复权是以当前日期(回测的current_dt或当天的真实日期)为基准往回算。如果你在 JoinQuant 回测 2020 年的数据,它的前复权价格是基于 2020 年的基准;而你今天打开通达信看 2020 年的 K 线,它是基于今天的基准进行前复权的。如果这期间发生了分红派息,两边的历史价格就会完全不同。 - 解决办法:在对比数据时,建议将 JoinQuant 的
history参数设置为fq=None(不复权),同时把通达信也切换到“不复权”模式进行对比,这样排除了复权因子的干扰。
3. MACD 柱子的倍数问题
talib.MACD返回的第三个值(MACD histogram)是DIF - DEA。- 国内软件(通达信/同花顺)的 MACD 柱子公式是
(DIF - DEA) * 2。 - 注:你的代码中已经写了
macd * 2,这一步是正确的,但很多新手会忽略这一点。
4. 停牌数据的处理
- 你的代码中使用了
skip_paused=True,这与通达信的处理方式是一致的(通达信 K 线图上通常没有停牌日)。这一点保持即可。
修正后的代码示例
为了让结果尽可能贴近通达信,我们需要拉长获取数据的窗口,并确保对比时复权方式一致。以下是优化后的代码:
import talib
import numpy as np
def MACD_Optimized(security_list, fastperiod=12, slowperiod=26, signalperiod=9):
if isinstance(security_list, str):
security_list = [security_list]
# 关键修改1:获取足够长的数据(例如 300 天),让 EMA 充分“预热”衰减误差
# 关键修改2:对比时建议先用 fq=None 排除复权干扰。实盘/回测中可改回 fq='pre'
data_length = 300
security_data = history(data_length, '1d', 'close', security_list, df=True, skip_paused=True, fq='pre')
macd_DIF = {}
macd_DEA = {}
macd_HIST = {}
for stock in security_list:
close_prices = security_data[stock].values
# 剔除全 NaN 的情况
if np.isnan(close_prices).all():
macd_DIF[stock] = np.nan
macd_DEA[stock] = np.nan
macd_HIST[stock]= np.nan
continue
# 剔除前面的 NaN 值,保留有效数据
valid_closes = close_prices[~np.isnan(close_prices)]
# 如果有效数据太短,talib 会报错或全返回 nan
if len(valid_closes) < slowperiod + signalperiod:
macd_DIF[stock] = np.nan
macd_DEA[stock] = np.nan
macd_HIST[stock]= np.nan
continue
# 使用标准的 talib.MACD 即可,默认就是 EMA
dif, dea, macd = talib.MACD(valid_closes,
fastperiod=fastperiod,
slowperiod=slowperiod,
signalperiod=signalperiod)
# 取最后一个值作为当天的指标值
macd_DIF[stock] = dif[-1]
macd_DEA[stock] = dea[-1]
# 关键修改3:乘以 2 对齐通达信
macd_HIST[stock] = macd[-1] * 2
return macd_DIF, macd_DEA, macd_HIST
总结:
只要你把 history 获取的数据长度拉长到 250~300 天以上,并且确保 JoinQuant 和通达信处于相同的复权模式(强烈建议用不复权对比),你会发现计算出来的 MACD 值与通达信的误差会缩小到小数点后三位以内。