Python批量生成波浪理论图解:ZigZag、斐波那契与PDF输出

Python批量生成波浪理论图解:ZigZag、斐波那契与PDF输出 简介这份《波浪理论图解5.pdf》面向股票、期货等市场的技术分析初学者与进阶交易者聚焦艾略特波浪理论的形态识别与实战解读。文档从浪型、幅度、时间三要素切入逐浪拆解理想化波浪序列第1浪的启动与量能变化、第2浪的深度回撤、第3浪的资金涌入与延伸、第4浪的规范调整直至第5浪的泡沫化以及A、B、C三浪构成的调整结构与各自的市场心理特征帮助读者建立对推动浪与调整浪的整体认知框架。资源包体量轻便仅含1个PDF文件约242KB适合在电脑或移动端随时查阅、打印作笔记。目前已有178人学习内容偏重波浪特性与浪型优先的判别思路可作为复盘行情、标注浪级的参考手册并配合其他技术指标与基本面分析使用以降低单一工具预测带来的偏差。1. 把波浪理论图解做成程序输出难点不在画图很多人第一次想用代码处理波浪理论第一反应是找一张标准八浪示意图照着在行情上描点标注。真上手就会发现同一段日线走势换个人来数能数出完全不同的浪级和起点。卡住工程的从来不是绘图而是三件事转折点用什么规则定死、推动浪的三条铁律怎么写成一串可执行的布尔判断、斐波那契比率给多宽的容差才算合理。把这三件事固定成参数批量输出像 wave_diagram_5.pdf 这样的图解集才有意义——它不再是一张手工描过的示意图而是一条参数确定、可重跑、可横向对比的流水线。下面按数据基座、结构识别、比率校验、PDF 出图的顺序走一遍中间给的代码都能直接落地跑。2. 波浪理论图解的数据基座从 K 线到 ZigZag 折线2.1 为什么原始 K 线不能直接拿来数浪波浪理论描述的是结构不是每一根 K 线的涨跌。日线上一根长上影线就能让高点的位置来回跳直接在高开低收四个价格上找浪结果会被噪声吃满。行业内比较稳的做法是先把价格序列压成一条只保留显著转折的折线也就是 ZigZag它把小于阈值的反向波动全部抹平只留下幅度足够大的高低点。压完之后浪的端点就从成百上千根 K 线收缩到几十个转折点枚举和校验的复杂度立刻降下来。这一步的另一个作用是统一浪级。同一段行情阈值取 3% 和取 8%得到的转折点数量可能差三倍对应的就是次级浪和主浪的差别。所以阈值不是一个可以随手填的数字它直接决定了后面识别出来的是哪一浪级。2.2 ZigZag 的两个必调参数反转阈值与最小跨度参数含义参考取值日线调大的后果调小的后果pct反向波动确认阈值0.03 ~ 0.08浪级抬升细碎转折被吞掉转折密集容易把调整浪内部结构拆散min_span相邻转折点最小间隔K 线数3 ~ 5过滤高频假转折但会漏掉急涨急跌的小浪相邻点过近后续比率计算分母趋零field取价字段high/low 成对或 close用 high/low 更贴近真实极值用 close 更平滑抗插针pct和min_span要一起调。只放大pct不设min_span遇到连续跳空时仍会产出一串相邻点只设min_span不放大pct则会在震荡区间里留下大量等距锯齿。我一般的起点是pct0.05、min_span4再看折线是否贴合肉眼判断的主浪结构。2.3 用 pandas 实现一个可调阈值的最小 ZigZagimport numpy as np import pandas as pd def zigzag(prices, pct0.05, min_span4): prices: 一维价格序列建议用 close或用 (highlow)/2 pct: 反转确认阈值0.05 表示反向 5% 才确认转折 min_span: 相邻转折点之间至少间隔多少根 K 线 返回: [(index_position, price, H|L), ...] n len(prices) if n 3: return [] pivots [] direction 0 # 0 未定1 向上找高点-1 向下找低点 ext_i, ext_p 0, prices[0] # 当前极值的位置与价格 for i in range(1, n): p prices[i] if direction 0: # 向上段追高点 if p ext_p: ext_i, ext_p i, p elif p ext_p * (1 - pct): # 回撤够深确认高点 if not pivots or i - pivots[-1][0] 1: pivots.append((ext_i, ext_p, H)) direction -1 ext_i, ext_p i, p if direction 0 and direction ! 0 or direction -1: if p ext_p: # 向下段追低点 ext_i, ext_p i, p elif p ext_p * (1 pct): # 反弹够深确认低点 pivots.append((ext_i, ext_p, L)) direction 1 ext_i, ext_p i, p # 后置过滤剔除间隔过近的转折点 cleaned [] for pt in pivots: if not cleaned or pt[0] - cleaned[-1][0] min_span: cleaned.append(pt) else: # 同向保留幅度更大的那个 if pt[2] cleaned[-1][2]: if (pt[2] H and pt[1] cleaned[-1][1]) or \ (pt[2] L and pt[1] cleaned[-1][1]): cleaned[-1] pt return cleaned逻辑说明主循环维护一个方向状态和当前极值锚点。价格持续创出同向新高或新低时只更新锚点不产生折点一旦反向幅度超过pct才把锚点固化为一个转折点并翻转方向。这样保证每个转折点都是被后续行情确认过的避免最后一根 K 线造成的假信号——代价是最后一个转折点总要滞后一根 K 线才成立这在实时标注里需要额外处理。参数说明pct决定浪级是最需要反复试的一个min_span是后置保险用来兜住跳空行情返回的元组里带的是位置索引而不是时间戳后续做比率计算和绘图时都用得上。实际跑的时候可以先打印len(zigzag(...))看 250 根日线是否落在 15 到 40 个转折点这个区间太多或太少就调pct。2.4 折线清洗去掉同向重复点与平台横盘ZigZag 输出里偶尔会出现连续两个同向标签比如一个低点后面又跟一个更低的低点中间没有高点。这通常出现在阈值边界附近。处理办法是在进入识别之前做一次压缩相邻同向点只保留更极端的那个。同时在横盘区做一次判断如果连续三个转折点的价格差都小于pct的一半就把这一段整体标记为无结构直接跳过不要硬凑成调整浪。这一步看起来琐碎但对后面识别准确率的影响比换算法更大。3. 推动浪与调整浪的规则化识别3.1 三条铁律的布尔化表达艾略特对推动浪的约束是硬的可以直接翻译成代码。约定向上推动浪的六个端点依次为 0 低、1 高、2 低、3 高、4 低、5 高价格分别是 p0 到 p5。铁律内容代码表达常见误判原因规则一浪 2 不能跌破浪 1 起点p2 p0起点取在最低收盘价而非最低价规则二浪 3 不能是 1、3、5 中最短的p3-p2 min(p1-p0, p5-p4)把延长浪的第五浪算成第三浪规则三浪 4 不能进入浪 1 的价格区间p4 p1用收盘价判定导致跨越不明显def check_impulse(p): p [p0, p1, p2, p3, p4, p5]依次为向上推动浪的六个端点价格 返回 (是否成立, 原因标签) p0, p1, p2, p3, p4, p5 p if p2 p0: return False, rule1_wave2_break_origin l1, l3, l5 p1 - p0, p3 - p2, p5 - p4 if l3 min(l1, l5): return False, rule2_wave3_is_shortest if p4 p1: return False, rule3_wave4_overlap_wave1 return True, ok逻辑说明函数只做硬约束的过滤不做打分。三条规则任意一条不满足就返回原因标签而不是简单返回 False。原因是排查阶段需要知道是规则一还是规则三挡掉了候选结构从而判断是价格字段选得不对还是浪级阈值偏小。比率约束放在下一章和硬规则分开方便单独调容差。参数说明p的长度必须是 6且已经按价格方向排好。如果要做向下推动浪把序列取负后复用同一个函数即可不用写第二套逻辑。3.2 从 ZigZag 序列枚举 5 浪结构有了硬规则剩下的是枚举。ZigZag 输出的转折点是有顺序的可以按固定步长滑窗。def find_impulses(pivots, max_span60): pivots: zigzag() 的输出 max_span: 一组 5 浪结构允许跨越的最大 K 线数 返回: 所有通过硬规则的候选结构 hits [] m len(pivots) for i in range(m - 5): window pivots[i:i 6] labels [w[2] for w in window] # 方向过滤必须是 L H L H L H if labels ! [L, H, L, H, L, H]: continue # 跨度过滤整段不能拖太长 if window[-1][0] - window[0][0] max_span: continue prices [w[1] for w in window] ok, reason check_impulse(prices) if ok: hits.append({start: window[0][0], end: window[-1][0], prices: prices}) return hits逻辑说明滑窗长度固定为 6标签必须严格交替且以 L 开头这两条过滤能砍掉绝大部分无效候选。max_span是经验值日线上一个完整的推动浪很少超过 60 根 K 线超过之后要么是浪级选错了要么这一段根本不该按推动浪处理。参数说明max_span可以按标的波动率自适应比如取近 60 日平均真实波幅的倍数。固定 60 在波动率差异大的品种上会产生偏差做跨品种对比时要留出调整空间。3.3 调整浪形态的容错处理调整浪比推动浪麻烦因为形态多而且允许的变体多。工程上的务实做法是只区分三类锯齿形、平台形、三角形。锯齿形的特征是 B 浪回撤浅于 A 浪起点C 浪明显超过 A 浪终点平台形的特征是 B 浪几乎回到 A 浪起点附近三角形则是五个互相重叠的转折点越收越窄。判断时给一个 0.05 到 0.1 的容差带落在带内就算命中。调整浪还有一个绕不开的问题它出现在浪 2 和浪 4 位置时是推动浪结构的一部分单独出现在下跌段时又可能是更大一级的 A-B-C。同一段折线可以有两种解读这不是算法缺陷而是波浪理论本身的模糊性。解法是在候选结果里按浪级分层而不是强行选一个唯一答案。4. 斐波那契比率校验与打分排序4.1 常见比率区间表硬规则只能筛掉不合法的结构剩下的候选可能还有十几个需要比率来排序。常用区间如下。关系计算式常见区间放宽后的区间浪 2 回撤浪 1(p1-p2)/(p1-p0)0.382 ~ 0.6180.236 ~ 0.786浪 3 相对浪 1(p3-p2)/(p1-p0)1.618 ~ 2.6181.236 ~ 3.618浪 4 回撤浪 3(p3-p4)/(p3-p2)0.236 ~ 0.3820.146 ~ 0.5浪 5 相对浪 1(p5-p4)/(p1-p0)0.618 ~ 1.6180.382 ~ 2.618区间放宽会让命中率上升但排序区分度下降收窄则相反。做批量图解时我偏向用中间一档宁可少标几个也不要满屏都是候选。4.2 把比率写成分数函数RATIOS { w2: (0.382, 0.618), w3: (1.618, 2.618), w4: (0.236, 0.382), w5: (0.618, 1.618), } def ratio_hit(value, lo, hi, slack0.15): 落在区间内得 1 分区间外按超出比例线性衰减到 0 if lo value hi: return 1.0 width hi - lo if value lo: gap (lo - value) / max(width, 1e-6) else: gap (value - hi) / max(width, 1e-6) return max(0.0, 1.0 - gap * slack * 6) def wave_score(p): p0, p1, p2, p3, p4, p5 p s {} s[w2] ratio_hit((p1 - p2) / (p1 - p0), *RATIOS[w2]) s[w3] ratio_hit((p3 - p2) / (p1 - p0), *RATIOS[w3]) s[w4] ratio_hit((p3 - p4) / (p3 - p2), *RATIOS[w4]) s[w5] ratio_hit((p5 - p4) / (p1 - p0), *RATIOS[w5]) s[total] sum(s.values()) / 4 return s逻辑说明ratio_hit用线性衰减替代硬性截断好处是候选结构之间能排出先后顺序而不是只有命中/不命中两种状态。四个子分数取平均得到总分也可以按重要性加权比如把浪 3 的权重提到 0.4因为浪 3 的比率最稳定。参数说明slack控制衰减速度值越大越宽松。分母用max(width, 1e-6)是为了防止区间宽度为零时除零。当p3-p2接近零时w4的计算会不稳定这类结构应该在进入打分前就用最小幅度过滤掉。4.3 参数怎么调、过拟合怎么防最容易踩的坑是在一批特定标的上反复调pct把总分刷到很高换一批标的效果立刻崩掉。验证办法是留出样本用一半标的调参另一半只看结果不做任何调整。如果两组的平均总分差超过 0.15说明参数已经在拟合噪声了需要把slack放大、区间放宽退回到更保守的配置。另一个坑是只保留最高分的那一个结构。实际上一段行情在同一浪级下常常有两三个合理读数全部保留、在图解上分层显示比强行去重更有信息量。5. 批量输出波浪理论图解 PDF 与稳健性验证5.1 用 PdfPages 把多个标的的图解合并成一份import matplotlib matplotlib.use(Agg) # 无 GUI 环境必须放在 pyplot 之前 import matplotlib.pyplot as plt from matplotlib.backends.backend_pdf import PdfPages def render(universe, out_pathwave_diagram_5.pdf): universe: {标的代码: (df, pivots, hits)} df 需要 DatetimeIndex 和 close 列 with PdfPages(out_path) as pdf: for code, (df, pivots, hits) in universe.items(): fig, ax plt.subplots(figsize(11, 6)) ax.plot(df.index, df[close], lw0.9, color#3a3a3a) # 折线 px [df.index[i] for i, _, _ in pivots] py [p for _, p, _ in pivots] ax.plot(px, py, lw1.6, color#c0392b, alpha0.8) # 只标注得分最高的那组结构 if hits: best max(hits, keylambda h: h.get(score, 0)) for k, (idx, price, _) in enumerate( [w for w in pivots if best[start] w[0] best[end]][:6]): ax.annotate(str(k), (df.index[idx], price), textcoordsoffset points, xytext(0, 10), hacenter, fontsize11, color#1f4e79) ax.set_title(code, locleft, fontsize13) ax.grid(alpha0.25, linestyle:) pdf.savefig(fig, bbox_inchestight) plt.close(fig)逻辑说明matplotlib.use(Agg)必须在导入pyplot之前调用否则在服务器或容器里会因为找不到显示设备报错。每个标的单独建 figure画完立刻plt.close(fig)不这么做在批量渲染上百张图时内存会一直涨。PDF 里每一页对应一个标的标注只画得分最高的一组避免页面被数字淹没。参数说明figsize建议宽高比保持 11:6 左右横轴是时间太窄会让标注数字互相压住。xytext(0, 10)是标注相对端点的像素偏移价格波动大的标的可以调大到 14。5.2 稳健性验证换一组阈值再看一次判断一份图解可不可信最省事的办法是换参数重跑看结构是否稳定。具体做三组pct取 0.04、0.05、0.06其余参数不动比较同一个标的被标出的浪 3 端点位置。如果三次结果的时间位置落在同一根 K 线附近说明这个结构是硬的如果位置来回跳说明当前浪级本身就不清晰这份图解不应该拿来指导操作。这个检验还有一个副产品它能把阈值敏感度量化出来。我通常在 PDF 首页放一个汇总表列出每个标的在三种阈值下命中结构的时间差差值超过 5 根 K 线的标灰显示。跑完一轮之后哪些标的适合数浪、哪些只适合放一边一眼就能看出来。本文还有配套的精品资源点击获取