简介:一份通达信缠论K线包含处理插件源码,面向通达信二次开发者和缠论量化研究者。插件基于VC++编写,利用通达信计算函数接口,将相邻K线的高低价格包含关系自动合并,进而支撑笔、线段与中枢的后续构建,解决人工手动处理K线包含时效率低、易出错的问题。完整源码包共24个文件、约693KB,以头文件、C++源文件、工程配置和编译产物(dll/lib/obj)为主,核心实现文件负责缠论K线包含逻辑,辅助工具文件提供包含判断与合并函数,另有使用说明文档可帮助快速上手。已有5689人学习下载,适合想深入理解缠论算法落地,或学习通达信插件开发流程的读者。通过阅读源码可掌握K线包含的判定与合并规则,了解插件如何通过TCalcFunc接口与通达信交互,同时也可参考VC++工程组织方式,迁移到自己的技术分析工具中。 写这篇东西之前,先交代下背景。缠论里第一步就是把K线做包含处理,也就是把相邻两根有包含关系的K线合并成一根,让走势图变成“无包含”的标准K线序列,后面才能正确画分型、笔、线段。这一步要是错了,后面全盘皆输。我最早是用手工在一张张截图上去标,眼睛都快瞎了,后来才转向写插件和公式来做自动化识别。这篇文章就把我折腾的经验、思路、源码框架和一些坑,一次说清楚。
说明:我不做任何破解、破解版公式或绕过验证的东西,只讨论在通达信官方公式系统允许范围内写通用处理逻辑,以及如何用自绘K线、排序、区间统计等功能辅助验证。
1. 为什么要先做K线包含处理
很多新手一开始就盯着“笔”和“线段”去研究,结果发现同一段走势,不同人画出来的笔完全不一样。原因通常不是笔的定义有分歧,而是最底层的K线包含关系处理就不一致。包含处理是缠论里最基础、最前置的步骤,它的作用是把主力用大阳线、长上影、十字星等K线形态制造的“视觉噪音”过滤掉,让真实的方向意图浮出来。
什么叫包含关系?简单说就是两根相邻K线,其中一根的最高价和最低价完全“包住”另一根,无论是高高包含低低,还是后一根完全在前一根的区间里面,都属于包含。处理规则只有两条:上升趋势中按高高处理,也就是取两根K线较高最高价和较高最低价;下降趋势中按低低处理,取较低最高价和较低最低价。方向怎么判断?看这两根K线之前的一根“非包含”K线是向上还是向下。
听上去很简单,但真实行情的难点在于:包含处理不是一次就能结束的。第二根和第三根处理完之后,合并出的新K线可能又和第四根形成包含,这时候需要继续合并,直到无法合并为止。我在处理上证指数日线数据时遇到过连续六七根K线全部互相包含的情况,如果不写循环处理,只做一轮两两合并,后面画出来的分型点至少要偏两三个K线周期,放到实际操作里可能就是完全不同的买卖点了。
这也是为什么我强烈建议用程序化方式而不是肉眼去处理。人眼在十几根K线里做方向判断和循环合并,不仅慢,还特别容易在连续包含的时候迷路。而且每根K线的处理结果会直接影响后续所有结构划分,一步错步步错。用一套固定规则来跑,至少结果可复现、可验证,不会因为看盘状态不同而得出两套结论。
2. 插件还是公式:理清方案边界
标题里提到“插件”,但这里要先把边界划清楚。通达信官方公式系统是支持编写自定义公式的,通常叫“通达信公式”或者“指标公式”,它不允许直接操作底层行情数据文件,也不支持外部DLL做任意内存读写。所以如果你想要的是那种完全黑盒式的插件,比如自动把处理后的K线画在副图或者主图上、还能导出到外部软件,这在公式系统里能做到很大一部分,但它不是通常意义上可以随意加载的动态链接库插件。
我自己实际采用的方案是分两层:第一层是纯公式层,用通达信公式语言把包含处理的算法写成指标,输出处理之后的合并K线高低点;第二层是数据验证层,用Python脚本把通达信导出的历史数据重新算一遍,对比两端结果是否一致。第二层不是必须的,但对确认你的公式逻辑是否正确非常有用,尤其是在处理除权、停牌、上市首日无涨跌幅这些边界情况的时候。
选型的核心考量有两个:一是你需不需要实时刷新,如果你是要盘中盯盘用,那必须走通达信公式,因为它能随行情实时计算;二是你需不需要批量回测多只股票,如果需要处理大量历史数据,那Python脚本的效率要远高于在通达信里逐股翻看。我实际工作流是:通达信公式负责盘中监控,把疑似分型、疑似笔的标记实时显示出来,盘后用Python对当天数据和历史数据做批量重算,确认标注准确后,再反过来修正公式里的细节。
如果你完全没接触过通达信公式语言,也不用担心,它的语法很像C语言和JavaScript的混合体,支持变量定义、条件判断、循环、数组引用。难点不在于语言本身,而在于对行情序列的“滚动处理”要特别小心,因为公式系统是按每根K线顺序计算的,你不能像写普通程序一样直接定义一个大数组从头跑到尾,必须利用通达信自带的逐K线计算机制。
3. 公式化包含处理的核心逻辑拆解
在写代码之前,先把逻辑拆成几个确定的步骤。这也是整个插件/公式源码里最核心的部分。
第一步,定义“包含关系”。通达信里每根K线的基本数据是:最高价HIGH、最低价LOW、开盘价OPEN、收盘价CLOSE。包含关系只需要用到最高价和最低价,开收盘价在合并时要保留下来,因为后续分型要用到。两个相邻K线A和B,如果(A.HIGH >= B.HIGH 且 A.LOW <= B.LOW) 或者 (A.HIGH <= B.HIGH 且 A.LOW >= B.LOW),就说明它们存在包含关系。注意这里用的是“大于等于”和“小于等于”,因为有些K线的最高价或最低价恰好相等,也算包含。
第二步,确定方向。方向的依据是A之前的那一根“有效K线”,也就是已经经过包含处理、确定不再与更早K线合并的K线。如果前一根有效K线是向上的,即它的最高价高于更早一根有效K线的最高价,那么当前处理方向就是向上;反之就是向下。实际操作中,这一步最容易被忽略,很多人直接在A和B里看谁高谁低来判断方向,这在连续包含非常密集的区域会出错。
第三步,执行合并。向上方向时,新K线最高价取MAX(A.HIGH, B.HIGH),新K线最低价取MAX(A.LOW, B.LOW);向下方向时,新K线最高价取MIN(A.HIGH, B.HIGH),新K线最低价取MIN(A.LOW, B.LOW)。合并后的新K线仍然带有最高价和最低价,但它不是一个真实存在的交易日K线,只是一个中间计算结果。
第四步,循环处理。合并后的新K线要和下一根K线继续比较,如果又构成包含,则重复第二步和第三步,直到遇到一根不包含的K线才停止。这个“循环直到不再包含”的逻辑,是很多初版源码容易出错的地方。有些人只写了一轮合并,遇到多根连续包含时就漏处理了。通达信公式里可以用WHILE或者递归思路实现,但更稳定的做法是直接用“多次迭代”方式,因为通达信公式对循环的支持不是特别灵活,需要小心使用。
下面给出一段简化版的核心伪代码(不是完整可直接运行的公式,是帮助理解逻辑的框架):
// 输入:K线序列 H[], L[], O[], C[] // 输出:合并后的K线高度 (high_merged, low_merged) // 伪代码,仅说明处理流程 i = 2 while i < n: // 找到前一根有效K线的索引 prev prev = i - 1 while prev > 0 and merged_flag[prev]: prev = prev - 1 // 判断当前是否有包含 if (H[i] >= H[prev] AND L[i] <= L[prev]) OR (H[i] <= H[prev] AND L[i] >= L[prev]): // 判断方向 if H[prev] > H[prev-1] AND L[prev] > L[prev-1]: direction = up else: direction = down if direction == up: H[merged] = MAX(H[prev], H[i]) L[merged] = MAX(L[prev], L[i]) else: H[merged] = MIN(H[prev], H[i]) L[merged] = MIN(L[prev], L[i]) // 标记 prev 和 i 都已经参与合并,结果赋予 prev 合并位 // 继续与下一根K线比较 merged_flag[prev] = TRUE // 注意:这里需要对合并后的K线继续和下一根K线做包含判断 i = i + 1 else: // 没有包含,移动到下一根 prev = i i = i + 1这已经是最关键的核心逻辑。你把它转写成任何语言都可以。我自己就在Python里按这段逻辑处理过几千只股票日线,处理速度非常快。
4. 通达信公式具体实现:逐步带你搭一个可视化副图
说明一下,我不给那种加密的、别人看不懂的公式,只给一个思路清晰的版本,你可以在这个基础上继续扩展。
先明确我们想实现什么:在一张副图里,把原始K线的包含合并结果画出来。表现形式可以有两种,第一种是用“合并后的最高价”和“合并后的最低价”画两条线,第二条是直接在K线主图里画合并后的实体,但这个比较难实现,因为通达信主图K线是由软件控制的,公式只能叠加指标线。我选择副图画两条线:一条是合并后的HIGH线,一条是合并后的LOW线。这两条线能直观展示包含处理后的高低点轮廓。
第一步,建立最基本的公式框架。通达信公式中,直接用HIGH和LOW是每根K线自身的最高价最低价,我们自定义两个变量MH和ML来存放合并后的值。每一根K线都尝试判断它和前一根有效合并K线是否存在包含,如果存在,则按方向合并;如果不存在,则直接把当前K线的最高价和最低价赋给MH和ML。
一个比较经典的处理方式是直接利用通达信内部的循环机制,配合REF函数来引用前一周期数据。比如:
// 判断当前K线与前一周期合并K线是否存在包含 UP1 := REF(ML, 1); // 前一根合并后的最低 UP2 := REF(MH, 1); // 前一根合并后的最高 CONTAIN := (HIGH >= UP2 AND LOW <= UP1) OR (HIGH <= UP2 AND LOW >= UP1);然后判断方向,这里要引入“前一根有效K线的方向”。简化版可以用前面若干根K线的趋势来近似,更精确的话需要自己写递归标记。这里我先给一个相对好用的近似方案:
DIR := IF(REF(CLOSE, 2) > REF(CLOSE, 3), 1, -1);这个仅作示意,实际当中应该用处理后的K线方向,而不是原始收盘价方向。不过很多时候,近似的方向已经能覆盖大部分情况了。
然后合并逻辑这样写:
NEWHIGH := IF(CONTAIN, IF(DIR > 0, MAX(HIGH, UP2), MIN(HIGH, UP2)), HIGH); NEWLOW := IF(CONTAIN, IF(DIR > 0, MAX(LOW, UP1), MIN(LOW, UP1)), LOW);但这里有个问题:因为你无法在一次公式计算中完成“循环直到不再包含”,所以真正写成通达信公式时,通常需要把这段逻辑重复多遍(比如重复5次或10次),也就是“迭代展开法”。这就是为什么市面上很多缠论指标的源码特别长,其实里面很大一部分是在重复合并逻辑,不是代码冗余,而是在规避公式系统对循环支持不强的限制。
我自己的做法是:把上面这一段“判断->合并且生成新HIGH/LOW”整体复制三到五遍,每一遍都把上一次的结果当作输入。大部分连续包含在4到5次之内就能处理完。如果遇到极端情况,比如连续8根K线包含,那确实需要更多迭代,但在通达信公式里做太多层迭代会让公式运行变慢,而且有些版本有公式语句长度限制,所以要在效率和完整性之间做取舍。
实际公式大致框架如下:
// 第一轮合并 CONTAIN1 := (HIGH >= REF(HIGH,1) AND LOW <= REF(LOW,1)) OR (HIGH <= REF(HIGH,1) AND LOW >= REF(LOW,1)); DIR1 := REF(HIGH,1) >= REF(HIGH,2); // 前一根K线方向近似 MH1 := IF(CONTAIN1, IF(DIR1, MAX(HIGH, REF(HIGH,1)), MIN(HIGH, REF(HIGH,1))), HIGH); ML1 := IF(CONTAIN1, IF(DIR1, MAX(LOW, REF(LOW,1)), MIN(LOW, REF(LOW,1))), LOW); // 第二轮合并,基于第一轮结果 CONTAIN2 := (MH1 >= REF(MH1,1) AND ML1 <= REF(ML1,1)) OR (MH1 <= REF(MH1,1) AND ML1 >= REF(ML1,1)); DIR2 := REF(MH1,1) >= REF(MH1,2); MH2 := IF(CONTAIN2, IF(DIR2, MAX(MH1, REF(MH1,1)), MIN(MH1, REF(MH1,1))), MH1); ML2 := IF(CONTAIN2, IF(DIR2, MAX(ML1, REF(ML1,1)), MIN(ML1, REF(ML1,1))), ML1); // 依此类推,第三轮、第四轮...画线输出时,我习惯输出最终两轮的结果:
最终高: MH4, COLORRED; 最终低: ML4, COLORGREEN;这个副图指标建好后,你可以直接看到一条偏高点和一条偏低点的曲线,它们就是包含处理后的走势轮廓。把它们和原始K线对比,能明显看到十字星、长上下影线所带来的毛刺被平滑掉了,结构清晰很多。
5. Python辅助验证:批量回测与边界情况
这里必须强调一点:通达信公式在逐K线机制下写循环展开,虽然实用,但一旦遇到连续包含特别多的走势,或者复权后数据有跳变,公式结果可能和“理论标准结果”不完全一致。所以我强烈建议建立一个Python验证流程。
Python脚本做的事情很简单:读取通达信导出的日线数据(通常是TXT或者CSV,包含日期、开盘、收盘、最高、最低),用你在第3节看到的那个循环逻辑完整计算一遍,输出每根K线的合并后高点和低点,再和通达信公式的输出结果对比。两边的结果如果一致,说明公式在大多数情况下是可靠的;如果不一致,多数是迭代层数不够,或者是方向判断的条件不一样。
我遇到的一个典型坑是:通达信导出数据的日期顺序是正序的(老版本可能是倒序),如果脚本没注意顺序,处理出来的K线序列完全是反的。这个在我第一次跑回测时就踩过,校正的时候很头大。另外就是停牌日的数据:通达信在停牌日可能会把最高最低等于开盘价,甚至全部等于上一个交易日收盘价,这种“一字线”看起来很像包含,但实际上应该跳过。我在脚本里加了停牌过滤条件,凡是最高等于最低且开盘等于收盘的数据直接标记为无效K线,不参与合并。
另一个边界是上市首日和除权除息日。上市首日没有前一根K线做方向参照,我直接把它当作有效K线,不参与合并。除权除息日会有价格跳空,但经过复权处理后基本平缓,可以正常处理。如果你用的是前复权数据,要注意最后一段历史数据的复权因子可能不准,导致极早期K线高低点和真实值偏差较大,但这不影响结构判断,因为结构判断用的是相对高低关系,而不是绝对价格。
Python脚本还有一个优势:你可以把合并后的K线画成图,直观比较原始K线和处理后K线的差别。我通常用matplotlib同时画两幅图,上面是原始K线,下面是合并后的K线轮廓。在连续包含较多的区域,会明显看到下面的轮廓线更简洁,顶底点的位置更清晰,这就是后面画分型和笔的基础。
整个验证流程也不复杂,核心代码不长。下面是精简版的Python核心逻辑,用于说明如何处理连续包含:
def merge_bars(bars): # bars: list of (high, low) merged = [] for h, l in bars: if not merged: merged.append([h, l]) continue while merged: prev_h, prev_l = merged[-1] # 判断包含 if (h >= prev_h and l <= prev_l) or (h <= prev_h and l >= prev_l): # 需要判断方向,这里用合并序列倒数第二根做判断 if len(merged) >= 2: prev2_h, prev2_l = merged[-2] direction_up = prev_h >= prev2_h else: direction_up = True if direction_up: new_h = max(h, prev_h) new_l = max(l, prev_l) else: new_h = min(h, prev_h) new_l = min(l, prev_l) # 用合并结果替换最后一根 merged.pop() h, l = new_h, new_l else: break merged.append([h, l]) return merged这个版本比通达信公式更直观,也更好调试。我在实际验证中还加了几个统计量,比如合并前后的K线总数、包含处理触发次数、连续包含最大长度等,用来评估某只股票K线的复杂程度。连续包含最大长度超过5的股票,说明当期走势非常犹豫,主力在反复拉锯,这种时候用笔划分要特别谨慎,因为一个小幅波动就可能改变方向判定。
6. 方向判断的细节:决定成败的隐藏规则
如果说包含处理里只有一个地方容易引发争议,那就是方向判断。我在前面的公式里用了“前一根有效合并K线相对更早一根有效K线”的高低关系,但实际使用中,有一个更细的规则值得注意。
按照缠论的原始定义,方向的判断应该基于“已经处理好的包含关系之前的那根K线”的方向,而不是原始K线方向。这句话听起来绕,实际操作的意思是:假设你合并了三根K线,得到新K线D,接下来要判断D和E是否包含,这时要看D相对于D之前那一根有效合并K线C的方向,而不是去看原始K线B或A的方向。否则,包含关系层层叠加后,方向会被前面的噪声干扰。
举个例子:原始K线序列中,A向下,B和C包含后合并成D,合并方向是向下的。现在D和E看起来又包含,如果只看D和E本身,可能觉得D的最高价比E高,好像方向向上,但正确做法是看D的前一根有效K线C(或者合并它的前身)的方向。如果C对再前一根K线是向下延续,那么D和E合并时仍然应该按向下处理。这个细微差别决定了合并后的低点是取较高还是较低最低价,进而影响后续底分型的定位。
我在实际写通达信公式时,解决这个问题的方式是给每根K线增加两个“状态变量”:一个是合并后的方向状态,一个是合并后的层数。每轮迭代里,方向状态不是简单地用当前HIGH比REF(HIGH)来判断,而是传递一个上一轮已经确定的方向标志。这样即使进行了多轮合并,方向依然来自最早的有效K线趋势。这个状态变量在通达信公式里可以用自定义变量PERSIST或者直接引用REF的方式传递,但确实会让源码变长不少。
如果你不想把公式搞得那么复杂,也可以采用一个实用的近似方案:直接用合并后的最高价和最低价的5周期均线方向作为趋势方向预判。这个方案不保证100%符合理论定义,但实测在日线级别下,准确率大概在90%以上,而且公式代码可以大幅简化。对大多数看盘场景来说,90%的准确率已经足够帮你锁定典型的买卖点了,剩下10%的边界情况放到人工复核里处理就行。
不过我还是要提醒:如果你想深入学习缠论,或者打算拿K线包含结果去做严谨的量化回测,那最好还是用Python那一套完整实现,不要依赖近似方案。因为回测里1%的误差都可能造成策略信号的偏移,长期累积下来回测结果会和实盘差异很大。我自己在早期就是用近似方案跑回测,结果回测收益曲线非常漂亮,一上实盘就变形,后来排查才发现是包含处理的方向判断在某些票上和标准规则不一致,导致分型和笔的位置偶尔偏了一两根K线。
7. 最终可视化:把处理结果接到分型与笔的识别上
包含处理只是第一步,但也是最容易验证的一步。当你有了合并后的高点序列和低点序列,就可以在这个基础上做分型识别了。分型的定义很简单:顶分型就是中间一根K线的最高价最高、最低价也最高;底分型就是中间一根K线的最低价最低、最高价也最低。这里说的K线,指的是经过包含处理后的K线。
我在通达信的副图指标里,通常把识别到的顶分型和底分型用“DRAWICON”函数画在对应的高低点位置,这样一眼就能看明白哪些位置可能成为笔的转折点。再往后,笔的识别算法就是把连续的分型按规则连接起来,中间至少隔一根独立K线,且顶分型的高点必须高于底分型的高点等等。这部分代码比包含处理更复杂,但基础依然是干净的K线序列。
写到这里,分享一个个人经验:不管你最终用缠论做什么级别的操作,日线、30分钟、5分钟,包含处理的逻辑是完全一样的,但在不同级别上,连续包含出现的频率差异很大。5分钟图上,主力撮合造成的上下影线很多,包含关系触发次数明显高于日线;而在月线级别,几乎很少出现连续包含。所以如果你主要做短线,建议把迭代层数调高一点,否则在5分钟级别很容易出现合并不彻底的情况,导致笔的划分有偏差。我用过8层迭代处理5分钟数据,仍然遇到过个别极端走势需要第9层才能完全合并的情况,但概率很低,可以接受。
最后再分享一个检查公式是否存在逻辑错误的小技巧:你可以在通达信里找一只走势非常简单的股票,比如一只长期横盘后缓慢爬升的票,然后打开这个副图指标。如果合并后的高点和低点两条线整体形态与原始K线轮廓一致,只在包含处变得平滑,说明大概率正确;如果出现合并后的高点反而低于原始K线的某个明显高点,或者合并后的低点高于原始K线的某个明显低点,那一定是你方向判断写反了,或者迭代层数不够,需要回头排查。
这套方案做完之后,后续你完全可以在这个基础上继续扩展:自动画笔、自动标中枢、甚至做一笔一笔的回测统计。关键是把最底层的K线包含处理做到逻辑严密、结果可验证,否则上面建的每一层都是空中楼阁。我自己的经验是,在这个环节多花两天时间把算法打磨干净,后面画分型和笔会省下十倍的时间,而且结果稳定得多。
本文还有配套的精品资源,点击获取