本文是豆包搓缠论的第二篇,由于我也是初学者,而且专职工作太忙了,只能有一搭没一搭的做。
我要搓缠论只是用来练习MQL5编程,我个人对缠论并不是太迷信,搓的过程中追求还原原汁原味的缠论,无必要不添加任何个人曲解。
本系列文只记录个人学习经历,同时给刚开始学的朋友一点借鉴,EA编程大神请自动略过。
我搓完第二个概念——顶底分型之后,我找到了MQL5编程的一条固定套路:
第一步永远都是先设一个结构体数组,用来储存处理结果。因为交易场景下的数据都是一大串一大串的,比如K线的OHCL四个价格,有多少个k线就有多少个对应的价格。MT5自带的数组:open[]、high[]、low[]、close[], 就是用来存储这些数据的。数组的特点是可以存一大串理论上无数个数字,我们一开盘,界面上就会加载几万十几万个K线,这些数据都是放在这些数组里的。同样,我们做k线合并、顶底分型,以及后面的笔、线段等,也会生成一大堆处理完的数据,放在自定义数组里最合适。同时这些数据不只有一个参数,会同时具备五六个参数,所以要用结构体数组来保存,也就是数组的每个成员都是一个结构体,每个结构体保存一个数据的多个参数。
第二步才是编具体的处理函数。比如判断两个k线是否是包含关系,三个处理过的k线组是否呈顶底分型结构,以及后续需要处理的顶底分型是否符合缠论“笔”的规则要求,这些具体的事儿都要通过具体的函数体现。
第三步是储存和读取数据。如果处理好的数据不储存起来,你每次打开指标软件,它都要重新分析一遍。而缠论是从k线到中枢,一步一步叠加上去的,且还有多级时间的K线互动环节,如果每次都要重算历史数据,我保证你的电脑会卡死。所以必须每步算完保存数据,下次只算增量,保证系统工作量最小。
第四步是绘图。学了编程才知道,所谓的数据,是摸不着看不到的,即使你把它保存在电脑中,也是用二进制表达方式,想看还得写脚本转化为字符文字。即使你看到了保存的数据文本,面对一堆数字,你也看不出个一二三。所以要在K线界面上设计对应的图形标识,这些标识必须和你处理过的数据一一对应,直接看标识就知道数据处理的对不对。
第五步是调用。在出程序中调用上面的处理函数、读写及数据检查验证函数、绘图函数,像搭积木一样,让各个部件摆好前后顺序,真正工作起来。
好啦,我们聊聊这次做的顶底分型。顶底分型不是直接用原始k线来判断,而是用上一章处理好的“合并K线组”来判断,判断规则比较简单:
1.分型是缠论中最小的走势转折单元,基于处理完包含关系的标准 K 线序列,由连续 3 根 K 线构成,不分阳 线阴线,只看高低点。
2.顶分型:连续 3 根 K 线中,中间 K 线的高点是三根中最高的,同时低点也是三根中最高的,即为顶分型,中间 K 线的高点,称为该顶分型的「顶」,是后续笔的上边界点位。
属于向下转折信号,对应下跌笔的起点、上涨笔的终点。
3.底分型:连续 3 根 K 线中,中间 K 线的低点是三根中最低的,同时高点也是三根中最低的,即为底分型,中间 K 线的低点,称为该顶分型的「底」,是后续笔的下边界点位。
属于向上转折信号,对应上涨笔的起点、下跌笔的终点。
4.分型识别的前置前提:包含关系处理,相邻两根 K 线,一根的高低点完全包裹住另一根的高低点(高点≤对方高点,且低点≥对方低点),即为包含关系,属于市场噪音,会干扰分型判断。
5.核心约束:分型是笔的端点,这是缠论结构的基础规则:
- 一笔 = 底分型 + 中间独立 K 线 + 顶分型(上涨笔)
- 一笔 = 顶分型 + 中间独立 K 线 + 底分型(下跌笔)
- 相邻的顶分型和底分型之间,必须至少有 1 根独立的 K 线(既不属于顶分型也不属于底分型),否则不能构成一笔。
6.分型的级别:分型的级别和 K 线周期一一对应,即1 分钟 K 线上的分型,是 1 分钟级别的分型;日 K 线上的分型,是日线级别的分型。
下面就是基于这几条规则写出的顶底分型全量处理函数,所谓全量,就是从第0号k线组开始一组一组处理过来,当系统第一次加载、修改软件、历史数据损坏等等情况时,才会运行:
- //+------------------------------------------------------------------+
- //| 壹、全量识别顶底分型
- //| 核心逻辑:基于合并后的标准K线序列判断分型结构,确认后回溯组内原始K线定位精准极值点
- //| 参数:原始K线的高低点、时间数组(仅用于极值点回溯,不参与分型核心判断)
- //+------------------------------------------------------------------+
- void FullFenxing(const double &high[],
- const double &low[],
- const datetime &time[])
- {
- int arraySize = ArraySize(g_mergeArray);
- if(arraySize < 3)
- {
- ArrayResize(g_fenxingArray,0);
- return; // 边界兜底:不足3组合并组无法构成分型
- }
-
- // 清空旧分型数据
- ArrayResize(g_fenxingArray,0);
- int fenxingCount = 0; // 分型计数,避免频繁调用ArraySize
-
- // 遍历合并数组:第1组 ~ 倒数第2组,每组作为中间K线判断
- for(int i = 1; i <= arraySize - 2; i++)
- {
- // ========== 第一步:仅用合并数组做分型核心判断 ==========
- double hL = g_mergeArray[i-1].high;
- double hM = g_mergeArray[i].high;
- double hR = g_mergeArray[i+1].high;
-
- double lL = g_mergeArray[i-1].low;
- double lM = g_mergeArray[i].low;
- double lR = g_mergeArray[i+1].low;
-
- // ========== 第二步:顶分型判断 ==========
- // 严格条件:中间高点最高,中间低点也最高
- if(hM > hL && hM > hR && lM > lL && lM > lR)
- {
- Fenxing fenxing;
- fenxing.type = FENXING_TOP;
- fenxing.mergeIdx = i;
-
- // 取出当前合并组的原始K线范围
- int i_startIdx = g_mergeArray[i].startIdx;
- int i_endIdx = g_mergeArray[i].endIdx;
- fenxing.klineCount = i_endIdx - i_startIdx + 1;
-
- // ========== 第三步:组内遍历找最高点(同价取首次出现) ==========
- int i_peakIdx = i_startIdx; //peak:山峰
- double i_peakPrice = high[i_startIdx];
-
- for(int k = i_startIdx + 1; k <= i_endIdx; k++)
- {
- // 严格大于才更新,同价保留更早的K线
- if(high[k] > i_peakPrice)
- {
- i_peakPrice = high[k];
- i_peakIdx = k;
- }
- }
-
- // 赋值精准点位信息
- fenxing.price = i_peakPrice;
- fenxing.peakIdx = i_peakIdx;
- fenxing.peakTime = time[i_peakIdx];
-
- // 加入分型数组
- ArrayResize(g_fenxingArray, fenxingCount + 1);
- g_fenxingArray[fenxingCount] = fenxing;
- fenxingCount++;
- }
- // ========== 第四步:底分型判断 ==========
- // 严格条件:中间低点最低,中间高点也最低
- else if(lM < lL && lM < lR && hM < hL && hM < hR)
- {
- Fenxing fenxing;
- fenxing.type = FENXING_BOTTOM;
- fenxing.mergeIdx = i;
-
- // 取出当前合并组的原始K线范围
- int i_startIdx = g_mergeArray[i].startIdx;
- int i_endIdx = g_mergeArray[i].endIdx;
- fenxing.klineCount = i_endIdx - i_startIdx + 1;
-
- // ========== 第五步:组内遍历找最低点(同价取首次出现) ==========
- int i_peakIdx = i_startIdx;
- double i_peakPrice = low[i_startIdx];
-
- for(int k = i_startIdx + 1; k <= i_endIdx; k++)
- {
- // 严格小于才更新,同价保留更早的K线
- if(low[k] < i_peakPrice)
- {
- i_peakPrice = low[k];
- i_peakIdx = k;
- }
- }
-
- // 赋值精准点位信息
- fenxing.price = i_peakPrice;
- fenxing.peakIdx = i_peakIdx;
- fenxing.peakTime = time[i_peakIdx];
-
- // 加入分型数组
- ArrayResize(g_fenxingArray, fenxingCount + 1);
- g_fenxingArray[fenxingCount] = fenxing;
- fenxingCount++;
- }
- }
- }
复制代码
下面是顶底分型增量处理函数,所谓增量,就是先检查历史数据和当前K线数据,如果有新的k线生成,只对新出现的部分进行处理,历史部分不用重算:
- //+------------------------------------------------------------------+
- //| 贰、增量识别顶底分型
- //| 核心逻辑:与全量识别一致,只是需要识别增量起点
- //+------------------------------------------------------------------+
- void IncrementFenxing(const double &high[],
- const double &low[],
- const datetime &time[])
- {
- int arraySize = ArraySize(g_mergeArray); //K线合并组会先更新,保证数组g_mergeArray里是最新数据
- int fenxingSize = ArraySize(g_fenxingArray); //分型数组中的数据并不一定是最新的,需要后面校验
-
- // --------------------------
- // 1. 边界兜底
- // --------------------------
- // 合并组不足3根,无法构成分型,清空并退出
- if(arraySize < 3)
- {
- ArrayFree(g_fenxingArray);
- return;
- }
- // 分型数组为空,直接执行全量分型兜底
- if(fenxingSize == 0)
- {
- FullFenxing(high, low, time);
- return;
- }
-
- // --------------------------
- // 2. 计算增量遍历起点
- // --------------------------
- int startMergeIdx = g_fenxingArray[fenxingSize - 1].mergeIdx; //取分型数组(即上次运行记录的)中最后一个分型对应的合并组下标作为起点,
- ArrayResize(g_fenxingArray, fenxingSize - 1); // 移除最后一个分型:防止末尾合并组被增量修改,导致原分型失效
- fenxingSize--;
-
- // 边界兜底:起点不小于1(分型需要左中右三根合并组)
- if(startMergeIdx < 1) startMergeIdx = 1; //起点指的是本次开始处理的合并K线组,其下标不可能是0
-
- // 计算遍历终点:倒数第二组合并组(最后一组无右侧K线,不参与分型)
- int endMergeIdx = arraySize - 2; //终点指的是本次最后处理的合并K线组,也就是更新后的合并K线组次新组,最新的合并组被舍弃
-
- // 无新增有效合并组,无需处理
- if(startMergeIdx > endMergeIdx)
- return;
-
- // --------------------------
- // 3. 增量主循环(逻辑与全量完全一致)
- // --------------------------
- for(int i = startMergeIdx; i <= endMergeIdx; i++)
- {
- // 核心判断:仅用合并数组
- double hL = g_mergeArray[i-1].high;
- double hM = g_mergeArray[i].high;
- double hR = g_mergeArray[i+1].high;
-
- double lL = g_mergeArray[i-1].low;
- double lM = g_mergeArray[i].low;
- double lR = g_mergeArray[i+1].low;
-
- // ========== 顶分型 ==========
- if(hM > hL && hM > hR && lM > lL && lM > lR)
- {
- Fenxing fenxing;
- fenxing.type = FENXING_TOP;
- fenxing.mergeIdx = i;
-
- int i_startIdx = g_mergeArray[i].startIdx;
- int i_endIdx = g_mergeArray[i].endIdx;
- fenxing.klineCount = i_endIdx - i_startIdx + 1;
-
- // 组内找最高点(同价取首次)
- int i_peakIdx = i_startIdx;
- double i_peakPrice = high[i_startIdx];
- for(int k = i_startIdx + 1; k <= i_endIdx; k++)
- {
- if(high[k] > i_peakPrice)
- {
- i_peakPrice = high[k];
- i_peakIdx = k;
- }
- }
-
- fenxing.price = i_peakPrice;
- fenxing.peakIdx = i_peakIdx;
- fenxing.peakTime = time[i_peakIdx];
-
- // 追加到分型数组
- ArrayResize(g_fenxingArray, fenxingSize + 1);
- g_fenxingArray[fenxingSize] = fenxing;
- fenxingSize++;
- }
- // ========== 底分型 ==========
- else if(lM < lL && lM < lR && hM < hL && hM < hR)
- {
- Fenxing fenxing;
- fenxing.type = FENXING_BOTTOM;
- fenxing.mergeIdx = i;
-
- int i_startIdx = g_mergeArray[i].startIdx;
- int i_endIdx = g_mergeArray[i].endIdx;
- fenxing.klineCount = i_endIdx - i_startIdx + 1;
-
- // 组内找最低点(同价取首次)
- int i_peakIdx = i_startIdx;
- double i_peakPrice = low[i_startIdx];
- for(int k = i_startIdx + 1; k <= i_endIdx; k++)
- {
- if(low[k] < i_peakPrice)
- {
- i_peakPrice = low[k];
- i_peakIdx = k;
- }
- }
-
- fenxing.price = i_peakPrice;
- fenxing.peakIdx = i_peakIdx;
- fenxing.peakTime = time[i_peakIdx];
-
- // 追加到分型数组
- ArrayResize(g_fenxingArray, fenxingSize + 1);
- g_fenxingArray[fenxingSize] = fenxing;
- fenxingSize++;
- }
- }
- }
复制代码
老规矩,再贴两张顶底分型处理的效果图:
看起来没啥毛病。顶分型用蓝色向下箭头指示,底分型用红色向上箭头指示。红蓝色框是k线合并组的指示,没有红蓝色框的k线就是单独一根形成的k线组。
本文只是为了展示学习成果,其他数据处理图形展示部分的代码就不贴了,等将来都做完了,再一起发出来,请各位朋友拭目以待。
虽然我只做完了缠论的第二步,但总代码行数已经超过一千五百行。网上有好多几百行代码就搞定的缠论指标,我真不知道是怎么弄出来的。
下一步是处理笔。我有个小小感悟,笔不是简单的把顶底分型一一连线就好了,它对分型还有两条基本要求。就这两条要求可以让缠论学习者吵一下午。所以不做程序,就不能做到细致且无异议地学习理论。如果理论本身就学的模棱两可,吵再多也没有用。 |