🚀 AI 一键生成 joinquant 策略代码
立即体验
👉 AI 生成代码 👈

JoinQuant 平台 MACD 背离策略代码解析与新手答疑

joinquant | 发布时间: 2026/6/2
以下内容由 EasyQuant 生成。

问题描述

小白对MACD背离代码的几点疑问,求指导,感谢

各位前辈好,

我看函数库里关于MACD背离的策略基本上都是copy的如下版本


def doDecide(stock):
fast = 11
slow = 26
sign = 5
rows = (fast + slow + sign) * 5
suit = {'dif':0, 'dea':0, 'macd':0, 'gold':False, 'dead':False}
grid = attribute_history(stock, rows, fields=['close']).dropna()
stock = '600010.XSHG.XSHE' #自己设定的
try:
grid['dif'], grid['dea'], grid['macd'] = talib.MACD(grid['close'].values, fast, slow, sign)
grid = grid.dropna()
# 底背离----------------------------------------------------------------
mask = grid['macd']>0 #金叉
mask = mask[mask==True][mask.shift(1)==False]
key2 = mask.keys()[-2]
key1 = mask.keys()[-1]
#1.本次(key1指定的日期)金叉股价低于上一次(不是昨天,是key2指定的日期)金叉股价
#2.本次(key1指定的日期)金叉Dif值大于上一次(不是昨天,是key2指定的日期)金叉Dif值
#3.金叉出现,即MACD由前天的负值变为昨天的正值
suit['gold'] = grid.close[key2]>grid.close[key1] and \
grid.dif[key2]< grid.dif[key1]< 0 and \
grid.macd[-2]< 0< grid.macd[-1]
# 顶背离----------------------------------------------------------------
mask = grid['macd']< 0 #死叉
mask = mask[mask==True][mask.shift(1)==False]
key2 = mask.keys()[-2]
key1 = mask.keys()[-1]
suit['dead'] = grid.close[key2]< grid.close[key1] and \
grid.dif[key2]>grid.dif[key1]>0 and \
grid.macd[-2]>0>grid.macd[-1]

except:
#如果找不到金叉,则执行key2 = mask.keys()[-2]或会抛出异常,
#说明这么多行【rows = (fast + slow + sign) * 5】数据中找不到金叉
#直接返回suit
pass
return suit
code
在下小白刚刚接触量化,仅在网上看了不到一个周的python教程
看了上面代码之后有不少问题,总结了以下几条,希望各位大神可以不吝赐教,非常感谢!

Q1

rows = (fast + slow + sign) * 5
code
请问为什么要取这些天数的历史数据?是基于经验的结果吗?

===
Q2

suit = {'dif':0, 'dea':0, 'macd':0, 'gold':False, 'dead':False}
grid = attribute_history(stock, rows, fields=['close']).dropna()

try:
grid['dif'], grid['dea'], grid['macd'] = talib.MACD(grid['close'].values, fast, slow, sign)
code
第一行创建了一个suit字典,这只了dif、dea、macd等key的默认值
第二行把某股票rows天数的历史收盘价赋予了grid
请问最后一行对grid的再次赋值怎么理解?
是把第二行里rows天收盘价对应的dif、dea和MACD值,通过talib.MACD函数又给了grid吗?
这里我理解是对上面suit字典里各个key重新赋value,但之前的字典不是suit吗?为什么要用grid['dif']这种形式而不是suit['dif']?
还是用grid添加到之前suit这个字典里?

======
Q3

mask = grid['macd']>0 #金叉
mask = mask[mask==True][mask.shift(1)==False]
code
请问第二行表代码语法意思是什么?
两个中括号并列是and判断条件是否同时成立吗?
也就是先判断mask(也就是grid['macd'])是否大于零,并且mask下一个的值是否不大于零?
另外我百度.shift()函数代表的意思是一个矩阵里向下移动1行,这里的矩阵就是指grid在attribute_history里形成的以天为单位(省略了参数‘1d’)收盘价形成的矩阵吗?

===========
Q4

key2 = mask.keys()[-2]
key1 = mask.keys()[-1]
code
请问这里[-1]和[-2]的位置是指mask这个矩阵(也就是grid以attribute_history按天赋予的收盘价矩阵)的倒数第一/二行,还是倒数第一/二列?

==========
Q5

suit['gold'] = grid.close[key2]>grid.close[key1] and \
grid.dif[key2]< grid.dif[key1]< 0 and \
grid.macd[-2]< 0< grid.macd[-1]
code
请问为什么这里 grid.close和grid.dif用的是key1、key2来确定的位置,而grid.macd是直接用的[-1][-2]来确认的位置?
二者有什么区别?
而且这里-1、-2不就是grid这个集合里倒数第二个和倒数第三个的值吗?为什么在代码的文字说明部分却说“不是昨天,是key2指定的日期”呢?
如果我理解是对的话,请问有代码可以判断并找出“上一个低点(不是第二小的值)”的位置吗?

=======
Q6
如果我想看看 grid['dif'], grid['dea'], grid['macd']和mask的值各是什么(以600010或其他任意股票为背景),请问如何实现?
我输入print (grid['dif'])之后,点击编译运行,系统会报错……

感谢各位大神前辈

解决方案

你好!欢迎来到量化交易的世界。这段代码结合了 pandas 数据处理和 talib 技术指标库,对于刚接触 Python 不久的新手来说,确实包含了一些进阶的用法。下面为你逐一详细解答这 6 个问题:

Q1:为什么要取 rows = (fast + slow + sign) * 5 这么多天的历史数据?

解答:
这是基于数学计算原理和经验的结果。MACD 的核心是 EMA(指数移动平均线)。EMA 的计算公式会包含历史上所有的数据,越久远的数据权重越小。理论上需要无限长的数据才能算出绝对准确的 EMA,但在实际编程中,只要取周期之和的 3 到 5 倍长度的数据,早期数据的权重就会衰减到几乎为 0,从而保证算出来的 MACD 值与同花顺、通达信等行情软件上的数值基本一致。如果取的数据太短,算出来的 MACD 会有较大的初始误差。

Q2:grid['dif'] 是对 suit 字典赋值吗?

解答:
不是的。这里涉及两个完全不同的数据结构:

  1. suit 是一个 Python 字典 (dict),它的作用是作为这个函数的最终返回值,告诉策略这只股票当前的状态(是否金叉、是否背离等)。
  2. grid 是一个 Pandas DataFrame(数据框,类似于 Excel 表格)attribute_history 返回的就是一个 DataFrame。

grid['dif'] = ... 的意思是:在 grid 这个表格中,新增一列,列名叫做 'dif',并把 talib.MACD 计算出来的一整列 DIF 数据填进去。这和 suit 字典没有任何关系,grid 只是用来做中间计算的草稿纸。

Q3:mask = mask[mask==True][mask.shift(1)==False] 是什么意思?

解答:
这行代码的目的是找出发生“金叉”的那一天

  1. mask = grid['macd'] > 0:这会生成一列布尔值(True/False),MACD 大于 0 的天数是 True,否则是 False。
  2. mask.shift(1)shift(1) 是 Pandas 的函数,意思是把数据往下平移一行。所以 mask.shift(1) 代表的是昨天的 MACD 是否大于 0。
  3. mask[mask==True]:选出今天 MACD > 0 的日子。
  4. [mask.shift(1)==False]:在上面的基础上,进一步筛选出昨天 MACD <= 0 的日子。

总结: 今天 MACD > 0 且 昨天 MACD <= 0,这就是 MACD 由负转正的瞬间,也就是金叉日。两个中括号连在一起是 Pandas 的链式索引筛选,相当于“满足条件 A 的数据中,再筛选出满足条件 B 的数据”。

Q4:key2 = mask.keys()[-2] 中的 [-1][-2] 是指什么?

解答:
经过 Q3 的筛选,mask 里现在只剩下发生过金叉的那些天了。mask.keys() 获取的是这些金叉日的日期索引(时间戳)

  • [-1] 表示 Python 列表/数组中的倒数第一个元素,也就是**最近一次(本次)**发生金叉的日期。
  • [-2] 表示倒数第二个元素,也就是上一次发生金叉的日期。

Q5:grid.close[key2]grid.macd[-2] 的区别是什么?

解答:
这是两种不同的定位方式:

  1. 按日期标签定位: key1key2 是具体的日期(比如 2023-10-15)。grid.close[key2] 的意思是:去 grid 表格里,把 key2 这一天(上一次金叉日)的收盘价取出来。
  2. 按绝对位置定位: grid.macd[-1]grid.macd[-2] 中的 -1-2 是指 grid 这个表格的最后一行(通常是今天/昨天)倒数第二行(昨天/前天)

为什么文字说明说“不是昨天,是key2指定的日期”?
文字说明解释的是 grid.close[key2] > grid.close[key1] 这一句,意思是比较“上一次金叉日”和“本次金叉日”的收盘价,而不是比较昨天和今天的收盘价。
grid.macd[-2] < 0 < grid.macd[-1] 这一句,确实是在判断最近两天(倒数第二天和最后一天)是否刚刚发生了金叉。

Q6:如何打印 grid['dif']mask 的值?系统报错怎么办?

解答:
系统报错很可能是因为你在代码里加了这一句:
stock = '600010.XSHG.XSHE'
股票代码的后缀写错了,JoinQuant 里的上汽集团代码应该是 '600010.XSHG',多了一个 .XSHE 会导致 attribute_history 找不到数据从而报错。

正确的打印方法:
把那行错误的 stock 赋值删掉(或者改成正确的 '600010.XSHG'),然后在 try 语句块里面使用 print 或者 log.info

# 修正股票代码
stock = '600010.XSHG'
grid = attribute_history(stock, rows, fields=['close']).dropna()

try:
    grid['dif'], grid['dea'], grid['macd'] = talib.MACD(grid['close'].values, fast, slow, sign)
    grid = grid.dropna()
    
    # 打印查看数据
    print("DIF数据:\n", grid['dif'].tail()) # tail() 只打印最后5行,避免数据太长
    print("MACD数据:\n", grid['macd'].tail())
    
    mask = grid['macd']>0
    mask = mask[mask==True][mask.shift(1)==False]
    print("金叉日期:\n", mask)

在 JoinQuant 的研究环境(Jupyter Notebook)或回测日志中,你就可以看到打印出来的具体数值了。

祝你量化学习之路顺利!多用 print 打印中间变量是学习 Python 数据处理最好的方法。