简介:这是一份通达信缠论笔线段画线公式源码的主图文档,专为使用通达信软件进行缠论技术分析的投资者和指标开发者准备。文档详细讲解了基于分型与笔划分的画线公式实现方法,包含DINGFEN、DIFEN、ZHUANGZHE等核心变量和函数定义,并明确说明了公式使用向后引用未来函数的原因及边界——仅用于走势标注与中枢观察,不可直接作为选股依据。压缩包仅含1个doc文件,大小215KB,文件结构精简、便于直接查阅和复制源码。目前已有1841人学习/下载,适合希望通过公式源码直观理解缠论笔、线段及分型判定的初中级使用者。阅读这份文档,读者可以快速掌握如何用近似方式识别笔的上下分型顶点,理解关键变量对走势状态的判定逻辑,并在此基础上结合其他指标完善自己的分析系统。 缠论笔线段自动画线这个需求,在通达信用户里一直很旺盛。但市面上流传的“公式源码”绝大多数只能画个大概,要么是未来函数重灾区,要么是画出来的笔和实际走势对不上。我自己折腾这套主图画线公式有一年多,从纯公式语言到DLL插件都试过,踩了不少坑,这里把我最终落地的方案和完整思路写出来,给想自己动手的人一个参考。
先说清楚这套东西能干什么:它能在通达信主图上自动识别K线的包含关系、划分顶底分型、确认笔的成立,再基于笔递归出线段,最后把笔、线段、中枢直接画在价格图上,红笔绿笔、中枢矩形框一目了然。适合做缠论复盘研究、买卖点标注参考,也适合想搞清楚缠论算法到底怎么用代码表达的人。
1. 缠论笔线段的工程化难题:从读图到画图
1.1 为什么靠肉眼画线不靠谱
缠论里最基础的操作就是分型、笔、线段。刚开始学的时候,拿笔在图上画,画完一张图觉得“哦原来走势是这样的”。但画上几十张图之后就会发现问题:不同的人对同一段走势可以画出完全不同的笔,差一个分型,后面线段划分全变。这不是学得不好,而是纯视觉判断本身没有可复现的判定标准,人在看图的时候会不自觉地“找自己想要的结构”。
如果要把缠论用在更系统的复盘里,第一步就是把“看图找结构”变成“程序算结构”。也就是说,给定一段K线数据,程序输出的笔、线段、中枢必须是确定的——同样的输入永远得到同样的输出。这也是我决定自己写画线工具的核心动机:让判定标准统一,让复盘结果可复现。
1.2 缠论自动画线到底要解决哪几个问题
把需求拆开看,一套可用的自动画线系统要依次解决四个问题:
- K线包含关系的合并。相邻两根K线有包含关系时,必须先按方向合并成一根,否则后面的分型判断全是错的。
- 顶底分型的识别。合并之后的K线序列里,找出所有顶分型和底分型,但分型之间不能共用K线。
- 笔的确认。相邻的顶底分型之间满足最低K线数要求,且中间没有更极端的价格,笔才成立。
- 线段与中枢。用笔作为基本组件,按特征序列划分线段,再在重叠区间里圈出中枢。
这四个问题一环扣一环。包含关系合并不对,分型就错;分型错,笔就错;笔错,线段和中枢就是空中楼阁。所以写代码的时候必须逐层验证,不能跳步。
2. 公式语言和DLL的边界:选型决定成败
2.1 通达信公式语言做不到什么
通达信自带的公式语言设计目标是技术指标,不是面向对象编程。它能做均线、MACD、KDJ这类基于单根K线的计算,也能用HHV、LLV这类函数做滚动窗口统计,但缠论笔线段这种需要“回溯、递归、状态记录”的逻辑,公式语言写起来非常别扭。
具体的问题有三个:
- 没有真正的循环和数组。公式语言里虽然有些函数能模拟迭代,但模式是固定的,很难实现“动态向前找分型”这种需要按需回溯的逻辑。
- 未来函数陷阱。很多人为了在图上画出连续的笔,直接在公式里用了ZIG之类的函数,这类函数会重绘历史信号,你看到的线是包含了未来信息的。复盘时觉得“真准”,实盘一看完全不是那么回事。
- 状态管理能力极弱。缠论的笔需要维持“当前是否在向上笔/向下笔”的状态,公式语言的函数式思路做这件事很别扭。
所以,想在通达信里做一套真正可靠的缠论画线,纯公式方案基本可以放弃。当然,如果你只是想画个视觉参考,那用现成的ZIG函数改改也能看,但不是本文要讲的正路。
2.2 DLL方案的技术栈与整体架构
我的最终方案是用通达信的DLL插件机制,把缠论计算全部放在C++代码里完成,公式语言只负责把K线数据传给DLL、把DLL返回的画线数据画出来。
整体架构是这样的:
- K线数据来源:通达信主图公式通过REF(H, N)、REF(L, N)等函数,把当前股票最近500根K线的高低价、开收盘价打包成一维数组,传给DLL。
- DLL计算引擎:C++代码拿到K线数组后,执行包含关系合并、分型识别、笔确认、线段划分、中枢计算,最终输出笔的每个端点的价格和序号,以及中枢的上下沿区间。
- 主图画线:DLL返回结果放在数组里,通达信这边用DRAWLINE、STICKLINE、DRAWKLINE之类的绘图函数,把笔段和中枢画到主图上。
这里最核心的设计思路是:DLL负责算,公式语言负责画。DLL返回的是纯数据,比如“第12根K线是笔的起点,价格是3.26,第35根K线是笔的终点,价格是3.18,方向是向下”,画线完全交给通达信原生绘图能力,这样既保持了计算精度,又最大限度利用通达信的图表渲染。
2.3 为什么选DLL而不是外部程序
可能有人会问,为什么不直接在Python里算好,再把结果导入通达信?两种路子我都试过。
外部程序方案的问题在于K线数据同步。Python要拿到和通达信完全一致的行情数据,需要另接数据源,而且除权数据、复权方式要仔细对齐,否则同一只股票在两边看到的图形都对不上。另外“算好再导入”就意味着你得不断生成数据文件、刷新图表,整个流程是割裂的。
DLL方案的优点在于它跑在通达信内部,天然拿到的是通达信当前显示的这幅图的K线数据。K线准,计算就准;显示什么,算的就是什么。复盘的时候切换股票、切换周期,DLL自动重新计算,体验是实时无缝的。
当然DLL方案的入门门槛也更高,得会C++、得了解通达信DLL接口规范,还要会调通公式和DLL的对接。这部分我后面会细说。
3. 笔的判定:包含关系、分型与顶底确认
3.1 K线包含关系合并的算法实现
这是整个算法的地基。包含关系的定义很明确:一根K线的高低范围完全被另一根K线的高低范围覆盖,就说明两根K线存在包含关系。
处理包含关系时有两个关键点:一是根据“当前方向”决定合并方式,二是方向本身来自最近一个非包含关系的方向。
具体代码逻辑是这样写的:
// K线包含处理 void MergeContainBars(vector<KLine>& klines) { vector<KLine> merged; bool direction_up = true; // 当前方向,默认向上 for (auto& k : klines) { if (merged.empty()) { merged.push_back(k); continue; } KLine& prev = merged.back(); // 判断是否包含 bool contains = (k.high >= prev.high && k.low <= prev.low) || (prev.high >= k.high && prev.low <= k.low); if (contains) { // 方向向上时,合并取高高、低高 // 方向向下时,合并取低低、高低 if (direction_up) { prev.high = max(prev.high, k.high); prev.low = max(prev.low, k.low); } else { prev.high = min(prev.high, k.high); prev.low = min(prev.low, k.low); } // 合并后不改变方向 } else { // 非包含关系,更新方向 direction_up = k.high > prev.high; merged.push_back(k); } } // merged 就是合并后的K线序列 }这里有一个容易写错的细节:合并之后,方向不是重新判断,而是保持不变。只有遇到非包含K线时,才会根据新高/新低来更新方向。原因在于缠论里方向是“延续”的,不是每根K线独立判断的。
3.2 顶底分型的严格定义
合并完K线之后,分型判断就简单了。顶分型的定义是:中间K线的最高价是三者中最高的,且中间K线的最低价也是三者中最高的。底分型则反过来。
严格实现时还要处理一个边界问题:分型之间的K线不能共用。也就是说,第i根K线可以是顶分型的中间K线,那它就不能再作为底分型的组成部分。这层约束如果漏掉,会出现“一根K线又是顶又是底”的荒谬情况。
// 判断分型 struct Fenxing { int index; // 在原始K线上的索引 double value; // 分型对应的价格 bool is_top; // true=顶分型,false=底分型 }; vector<Fenxing> FindFenxing(const vector<Fenxing>& merged) { vector<Fenxing> result; for (int i = 1; i < merged.size() - 1; i++) { bool is_top = merged[i].high > merged[i-1].high && merged[i].high > merged[i+1].high; bool is_bottom = merged[i].low < merged[i-1].low && merged[i].low < merged[i+1].low; // 分型有效性检查:相邻分型不能共用K线 if (!result.empty()) { if (abs(i - result.back().index) < 2) { continue; } } if (is_top) { result.push_back({i, merged[i].high, true}); } else if (is_bottom) { result.push_back({i, merged[i].low, false}); } } return result; }3.3 笔的成立条件与严格代码
有了分型序列,笔的判定核心就是两个条件:相邻分型必须一顶一底交替出现,且两者之间的K线数足够。
K线数要求我通常设为5根以上,这个是网上大多数缠论实现里默认的参数,处理的是“分型之间隔得太近,不具备笔的意义”的问题。这个参数建议开放出来,后面画线的时候可以在通达信里直接调。
// 笔的确认 struct Bi { int start_index; // 起点K线索引 int end_index; // 终点K线索引 double start_price; // 起点价格 double end_price; // 终点价格 bool direction_up; // true=向上笔 }; vector<Bi> BuildBi(const vector<Fenxing>& fenxings, int min_bars) { vector<Bi> bis; if (fenxings.size() < 2) return bis; const Fenxing* prev = &fenxings[0]; for (int i = 1; i < fenxings.size(); i++) { const Fenxing& curr = fenxings[i]; // 必须顶底交替 if (curr.is_top == prev->is_top) { // 同类型分型,按更极端的价格替换 if (curr.is_top) { if (curr.value > prev->value) prev = &curr; } else { if (curr.value < prev->value) prev = &curr; } continue; } // 检查K线根数是否足够 int bar_gap = curr.index - prev->index; if (bar_gap >= min_bars) { Bi bi; bi.start_index = prev->index; bi.end_index = curr.index; bi.start_price = prev->value; bi.end_price = curr.value; bi.direction_up = curr.is_top; // 底->顶为向上笔 bis.push_back(bi); prev = &curr; } } return bis; }这段代码里还有一个重要的处理逻辑:当连续出现两个顶分型时,保留价格更高的那个;连续出现两个底分型时,保留价格更低的那个。这个“取极值”规则直接决定了后续线段划分的准确性。
3.4 一笔未完的处理策略
实际走势中,最后一笔往往是没有走完的——当前价格就在最后一根K线上,顶分型或底分型还没确认。这时候不能生硬地画一笔到最后一个分型,因为图上会多出一段误导性的线段。
我的处理方式是:最后一笔的终点不固定,把它指向最新K线。也就是说,程序会返回一个“未完成笔”的标记,通达信侧画出来是虚线,等下一根K线数据刷新时再重新计算。这样图面上看起来更干净,也如实反映了“当前这笔尚未确认”的状态。
4. 线段与中枢:递归逻辑的落地
4.1 线段的特征序列与划分
笔确认之后,线段划分就是缠论里最难的部分。严格来说,线段是由至少三笔构成的,但它不是简单地把三笔连起来——线段本身需要满足特征序列的顶底分型要求。
特征序列的思路是这样的:在一段向上线段中,每一笔向下笔的“低点”构成一个序列,这个序列如果出现了顶分型,就说明向上线段可能要被终结。向下线段同理。
划线的实用做法是先找出“特征序列分型”,然后以分型为分界点,将笔序列切割成线段。这部分的C++实现比笔要复杂很多,因为存在多义性处理,我最终的方案是采用“线段破坏需第二特征序列分型确认”的方式。
代码没法在这里完全展开,但核心思路可以概括为几句话:
- 用已经确认的笔序列作为输入。
- 按方向把笔分成“向上线段”和“向下线段的特征序列”。
- 对特征序列做分型判断,遇到分型就认为前一个线段结束,新线段从分型点开始。
- 如果分型还没确认,保持当前线段延续。
4.2 中枢的区间计算
中枢的定义比较清晰:连续三个次级别走势类型的重叠区间。在笔线段体系里,我们简化为“连续三笔的重叠区间”。但要注意,真正的缠论中枢应该基于线段来定义,用笔来定义属于简化用法,复盘时作为参考够用。
中枢区间的计算逻辑是:
- 取连续三段走势类型,比如线段1、2、3。
- 中枢区间的高点是三段中“最低高点”的那一个,也就是min(高点1, 高点2, 高点3)。
- 中枢区间的低点是三段中“最高低点”的那一个,也就是max(低点1, 低点2, 低点3)。
- 如果高点大于低点,说明三段有重叠,中枢成立。
表达式是:中枢高点 = min(线段高点列表);中枢低点 = max(线段低点列表)。
4.3 递归与二次确认
这里要特别提醒:缠论本身是递归定义的。笔构成线段,线段构成更高级别的笔,更高级别的笔又构成更高级别的线段。没有递归,你的画线永远停留在最低级别。
但完全按递归去实现,在通达信里会碰到性能问题。500根K线做一两次递归还行,做五六次就会明显卡顿。我的做法是:默认只做两层结构,即“分型→笔→线段”,中枢基于线段计算。如果你想做更高级别的中枢,可以外接更高周期的K线再算一次,比如在30分钟图上画出的线段,拿到日线图上就是日线级别的笔。这种跨周期组合比单图递归效率高得多。
5. 主图画线输出:DLL与绘图API的配合
5.1 主图数据输出的格式
DLL算完的数据要传回通达信公式。通达信DLL插件常规的做法是设置多个输出数组,每个数组对应一个序列。我封装成这样的输出:
输出1:笔方向数组 (1=向上笔起点,-1=向下笔起点,0=无) 输出2:笔起点价格 输出3:笔终点价格 输出4:中枢上沿 输出5:中枢下沿公式侧只需要按这些数组画线即可。比如输出1的第i个值为1,输出2和输出3的第i个值分别是该笔的起点价和终点价,那在K线i的位置就能画出一条从起点到终点的线段。
这只是我的设计方式,你也可以把数据揉在同一个数组里,用约定好的值来区分类型,看个人习惯。
5.2 画线数据的组织与颜色区分
通达信主图画线,我推荐用DRAWLINE和STICKLINE配合的方式。
笔的部分,用DRAWLINE从起点画到终点,红线表示向上笔,绿线表示向下笔。线段用更粗的线,比如黄色或者白色,和笔区分开。中枢矩形用DRAWKLINE或者自定义的STICKLINE组合。
公式侧大致长这样:
// 通达信公式部分示意 上行笔:DRAWLINE(起笔条件, 起笔价格, 终笔条件, 终笔价格, 0) COLORRED; 下行笔:DRAWLINE(起笔条件, 起笔价格, 终笔条件, 终笔价格, 0) COLORGREEN;DLL返回的数据里,要把“起笔条件”和“终笔条件”写成布尔值。当第i根K线是某笔起点时,起笔条件为TRUE,价格为该笔起点价;当第i根K线是某笔终点时,终笔条件为TRUE,价格为该笔终点价。剩下的工作交给DRAWLINE去连线。
5.3 参数可调节设计
缠论算法里有很多参数会影响结果:分型K线根数、笔的最低K线数、是否允许分型共用K线等。硬编码在DLL里的话,每次调参都得重新编译,很麻烦。
我选择了“参数外部化”的方案:在通达信公式里设置PARAM参数,通过DLL的接口传入计算引擎。公式侧看起来是这样:
笔最少K线数: 5;用户在通达信里拖动参数条,就能实时调整笔的灵敏度,DLL收到新参数后自动重算并重绘。这个交互方式在实际使用中非常舒服,复盘的时候可以快速对比不同参数下画线的差异。
6. 实战中的坑与调试体会
6.1 未来函数是最大的敌人
前面反复强调过未来函数,但实际写代码时太容易踩进去了。
最常见的写法来自对“已知答案”的依赖。比如你知道这只股票后面涨了,在代码里用HHV(高点, 10)去找“这一段的高点”,表面上是找10根K线内的最高点,实际却隐藏了“我先知道答案再倒推出高点”的问题。在含未来函数的公式上,你永远看到的是“最完美的笔”,这种图复盘再漂亮也没有实战参考价值。
DLL方案之所以能绕开这个问题,是因为你可以严格地按顺序遍历K线:从第0根到第N根,每一根只使用当前及以前的信息做判断。只要循环顺序不乱,就天然不含未来函数。
判断一个画线工具是否可信,最简单的办法是看第N根K线之后能不能输出第N+1根才该有的信息,如果能,就是未来函数。
6.2 数据对齐与除权复权问题
DLL拿到的K线数据必须和通达信当前显示的K线一一对应。用REF(H, N)取历史高低点时,N的偏移如果错一位,所有分型位置都会偏一格,笔画出来看着没毛病,实际全错位。
另外复权方式也会影响画线结果。前复权、后复权、不复权三种模式下,K线的高低价是完全不同的。最稳妥的做法是在计算前明确记录当前使用的复权类型,复盘时固定用一种复权方式,不要来回切换。
6.3 不同周期的一致性
同一只股票在1分钟、5分钟、30分钟图上,用同一套参数算出来的笔是不一样的。这不是bug,而是缠论本身的分级特性——不同级别反映的是不同的走势结构。
如果你的画线工具在1分钟图和日线图上画出来的笔完全一致,那反而要警惕,说明算法很可能没有按级别递归,只是在用同一套参数做机械扫描。真正的缠论实现,不同级别之间应该有一种“自相似”的结构关系,这是验证算法是否正确的一个隐含标准。
我在写完后做了一组对比测试:用同一套源码,分别在5分钟和30分钟图上跑同一只股票,观察线段划分是否呈现“大级别笔由小级别线段构成”的递进关系。能对上,算法基本就是对的。
7. 最后的落地体验与调试建议
整个项目折腾下来,最深的体会有三个。
第一,缠论自动画线的价值不在“自动”两个字,而在可复现。以前和人讨论走势,各画各的,谁也说服不了谁。有了这套工具之后,同样的数据和参数,任何人跑出来都是同一张图,讨论才有共同基础。
第二,不要追求一次画对全部结构。先只输出笔,验证笔的准确率;再把笔连成线段;最后才加中枢矩形。每加一层都会暴露前面一层的问题,一层层稳扎稳打比憋大招靠谱得多。
第三,参数别迷信默认值。网上的缠论画线工具默认参数各不相同,有的要求分型之间至少隔3根K线,有的要5根,还有的用百分比过滤毛刺。这些差异不是谁对谁错,而是适用场景不同。实盘研究的时候,把参数调低可以得到更灵敏的笔,调高可以得到更稳健的大级别结构,没有绝对标准。
如果你是从零开始,建议不要直接拿现成的源码跑,那样出了问题也不知道怎么改。先自己把包含关系合并写出来,用一小段真实的日K数据手工验证一遍,再往上加分型、加笔。基础算法自己亲手写过一遍之后,后面所有扩展都顺了,这是我在这套代码上最大的收获。
本文还有配套的精品资源,点击获取