【缠论】用豆包从零开始搓缠论(二)

| 发表于 7 小时前 | 显示全部楼层 |复制链接
本文是豆包搓缠论的第二篇,由于我也是初学者,而且专职工作太忙了,只能有一搭没一搭的做。

我要搓缠论只是用来练习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线组开始一组一组处理过来,当系统第一次加载、修改软件、历史数据损坏等等情况时,才会运行:
  1. //+------------------------------------------------------------------+
  2. //| 壹、全量识别顶底分型
  3. //|  核心逻辑:基于合并后的标准K线序列判断分型结构,确认后回溯组内原始K线定位精准极值点
  4. //|  参数:原始K线的高低点、时间数组(仅用于极值点回溯,不参与分型核心判断)
  5. //+------------------------------------------------------------------+
  6. void FullFenxing(const double &high[],
  7.                  const double &low[],
  8.                  const datetime &time[])
  9. {
  10.     int arraySize = ArraySize(g_mergeArray);
  11.     if(arraySize < 3)
  12.     {
  13.         ArrayResize(g_fenxingArray,0);
  14.         return;      // 边界兜底:不足3组合并组无法构成分型
  15.     }
  16.    
  17.     // 清空旧分型数据
  18.     ArrayResize(g_fenxingArray,0);
  19.     int fenxingCount = 0; // 分型计数,避免频繁调用ArraySize
  20.    
  21.     // 遍历合并数组:第1组 ~ 倒数第2组,每组作为中间K线判断
  22.     for(int i = 1; i <= arraySize - 2; i++)
  23.     {
  24.         // ========== 第一步:仅用合并数组做分型核心判断 ==========
  25.         double hL = g_mergeArray[i-1].high;
  26.         double hM = g_mergeArray[i].high;
  27.         double hR = g_mergeArray[i+1].high;
  28.         
  29.         double lL = g_mergeArray[i-1].low;
  30.         double lM = g_mergeArray[i].low;
  31.         double lR = g_mergeArray[i+1].low;
  32.         
  33.         // ========== 第二步:顶分型判断 ==========
  34.         // 严格条件:中间高点最高,中间低点也最高
  35.         if(hM > hL && hM > hR && lM > lL && lM > lR)
  36.         {
  37.             Fenxing fenxing;
  38.             fenxing.type     = FENXING_TOP;
  39.             fenxing.mergeIdx = i;
  40.             
  41.             // 取出当前合并组的原始K线范围
  42.             int i_startIdx = g_mergeArray[i].startIdx;
  43.             int i_endIdx  = g_mergeArray[i].endIdx;
  44.             fenxing.klineCount = i_endIdx - i_startIdx + 1;
  45.             
  46.             // ========== 第三步:组内遍历找最高点(同价取首次出现) ==========
  47.             int i_peakIdx = i_startIdx; //peak:山峰
  48.             double i_peakPrice = high[i_startIdx];
  49.             
  50.             for(int k = i_startIdx + 1; k <= i_endIdx; k++)
  51.             {
  52.                 // 严格大于才更新,同价保留更早的K线
  53.                 if(high[k] > i_peakPrice)
  54.                 {
  55.                     i_peakPrice = high[k];
  56.                     i_peakIdx = k;
  57.                 }
  58.             }
  59.             
  60.             // 赋值精准点位信息
  61.             fenxing.price    = i_peakPrice;
  62.             fenxing.peakIdx  = i_peakIdx;
  63.             fenxing.peakTime = time[i_peakIdx];
  64.             
  65.             // 加入分型数组
  66.             ArrayResize(g_fenxingArray, fenxingCount + 1);
  67.             g_fenxingArray[fenxingCount] = fenxing;
  68.             fenxingCount++;
  69.         }
  70.         // ========== 第四步:底分型判断 ==========
  71.         // 严格条件:中间低点最低,中间高点也最低
  72.         else if(lM < lL && lM < lR && hM < hL && hM < hR)
  73.         {
  74.             Fenxing fenxing;
  75.             fenxing.type     = FENXING_BOTTOM;
  76.             fenxing.mergeIdx = i;
  77.             
  78.             // 取出当前合并组的原始K线范围
  79.             int i_startIdx = g_mergeArray[i].startIdx;
  80.             int i_endIdx   = g_mergeArray[i].endIdx;
  81.             fenxing.klineCount = i_endIdx - i_startIdx + 1;
  82.             
  83.             // ========== 第五步:组内遍历找最低点(同价取首次出现) ==========
  84.             int i_peakIdx = i_startIdx;
  85.             double i_peakPrice = low[i_startIdx];
  86.             
  87.             for(int k = i_startIdx + 1; k <= i_endIdx; k++)
  88.             {
  89.                 // 严格小于才更新,同价保留更早的K线
  90.                 if(low[k] < i_peakPrice)
  91.                 {
  92.                     i_peakPrice = low[k];
  93.                     i_peakIdx = k;
  94.                 }
  95.             }
  96.             
  97.             // 赋值精准点位信息
  98.             fenxing.price    = i_peakPrice;
  99.             fenxing.peakIdx  = i_peakIdx;
  100.             fenxing.peakTime = time[i_peakIdx];
  101.             
  102.             // 加入分型数组
  103.             ArrayResize(g_fenxingArray, fenxingCount + 1);
  104.             g_fenxingArray[fenxingCount] = fenxing;
  105.             fenxingCount++;
  106.         }
  107.     }
  108. }
复制代码

下面是顶底分型增量处理函数,所谓增量,就是先检查历史数据和当前K线数据,如果有新的k线生成,只对新出现的部分进行处理,历史部分不用重算:
  1. //+------------------------------------------------------------------+
  2. //| 贰、增量识别顶底分型
  3. //|  核心逻辑:与全量识别一致,只是需要识别增量起点
  4. //+------------------------------------------------------------------+
  5. void IncrementFenxing(const double &high[],
  6.                       const double &low[],
  7.                       const datetime &time[])
  8. {
  9.     int arraySize = ArraySize(g_mergeArray); //K线合并组会先更新,保证数组g_mergeArray里是最新数据
  10.     int fenxingSize = ArraySize(g_fenxingArray);  //分型数组中的数据并不一定是最新的,需要后面校验
  11.     // --------------------------
  12.     // 1. 边界兜底
  13.     // --------------------------
  14.     // 合并组不足3根,无法构成分型,清空并退出
  15.     if(arraySize < 3)
  16.     {
  17.         ArrayFree(g_fenxingArray);
  18.         return;
  19.     }
  20.     // 分型数组为空,直接执行全量分型兜底
  21.     if(fenxingSize == 0)
  22.     {
  23.         FullFenxing(high, low, time);
  24.         return;
  25.     }
  26.     // --------------------------
  27.     // 2. 计算增量遍历起点
  28.     // --------------------------
  29.     int startMergeIdx = g_fenxingArray[fenxingSize - 1].mergeIdx;    //取分型数组(即上次运行记录的)中最后一个分型对应的合并组下标作为起点,
  30.     ArrayResize(g_fenxingArray, fenxingSize - 1);                    // 移除最后一个分型:防止末尾合并组被增量修改,导致原分型失效
  31.     fenxingSize--;
  32.     // 边界兜底:起点不小于1(分型需要左中右三根合并组)
  33.     if(startMergeIdx < 1) startMergeIdx = 1;                    //起点指的是本次开始处理的合并K线组,其下标不可能是0
  34.     // 计算遍历终点:倒数第二组合并组(最后一组无右侧K线,不参与分型)
  35.     int endMergeIdx = arraySize - 2;                            //终点指的是本次最后处理的合并K线组,也就是更新后的合并K线组次新组,最新的合并组被舍弃
  36.    
  37.     // 无新增有效合并组,无需处理
  38.     if(startMergeIdx > endMergeIdx)
  39.         return;
  40.     // --------------------------
  41.     // 3. 增量主循环(逻辑与全量完全一致)
  42.     // --------------------------
  43.     for(int i = startMergeIdx; i <= endMergeIdx; i++)
  44.     {
  45.         // 核心判断:仅用合并数组
  46.         double hL = g_mergeArray[i-1].high;
  47.         double hM = g_mergeArray[i].high;
  48.         double hR = g_mergeArray[i+1].high;
  49.         double lL = g_mergeArray[i-1].low;
  50.         double lM = g_mergeArray[i].low;
  51.         double lR = g_mergeArray[i+1].low;
  52.         // ========== 顶分型 ==========
  53.         if(hM > hL && hM > hR && lM > lL && lM > lR)
  54.         {
  55.             Fenxing fenxing;
  56.             fenxing.type     = FENXING_TOP;
  57.             fenxing.mergeIdx = i;
  58.             int i_startIdx = g_mergeArray[i].startIdx;
  59.             int i_endIdx   = g_mergeArray[i].endIdx;
  60.             fenxing.klineCount = i_endIdx - i_startIdx + 1;
  61.             // 组内找最高点(同价取首次)
  62.             int i_peakIdx = i_startIdx;
  63.             double i_peakPrice = high[i_startIdx];
  64.             for(int k = i_startIdx + 1; k <= i_endIdx; k++)
  65.             {
  66.                 if(high[k] > i_peakPrice)
  67.                 {
  68.                     i_peakPrice = high[k];
  69.                     i_peakIdx = k;
  70.                 }
  71.             }
  72.             fenxing.price    = i_peakPrice;
  73.             fenxing.peakIdx  = i_peakIdx;
  74.             fenxing.peakTime = time[i_peakIdx];
  75.             // 追加到分型数组
  76.             ArrayResize(g_fenxingArray, fenxingSize + 1);
  77.             g_fenxingArray[fenxingSize] = fenxing;
  78.             fenxingSize++;
  79.         }
  80.         // ========== 底分型 ==========
  81.         else if(lM < lL && lM < lR && hM < hL && hM < hR)
  82.         {
  83.             Fenxing fenxing;
  84.             fenxing.type     = FENXING_BOTTOM;
  85.             fenxing.mergeIdx = i;
  86.             int i_startIdx = g_mergeArray[i].startIdx;
  87.             int i_endIdx   = g_mergeArray[i].endIdx;
  88.             fenxing.klineCount = i_endIdx - i_startIdx + 1;
  89.             // 组内找最低点(同价取首次)
  90.             int i_peakIdx = i_startIdx;
  91.             double i_peakPrice = low[i_startIdx];
  92.             for(int k = i_startIdx + 1; k <= i_endIdx; k++)
  93.             {
  94.                 if(low[k] < i_peakPrice)
  95.                 {
  96.                     i_peakPrice = low[k];
  97.                     i_peakIdx = k;
  98.                 }
  99.             }
  100.             fenxing.price    = i_peakPrice;
  101.             fenxing.peakIdx  = i_peakIdx;
  102.             fenxing.peakTime = time[i_peakIdx];
  103.             // 追加到分型数组
  104.             ArrayResize(g_fenxingArray, fenxingSize + 1);
  105.             g_fenxingArray[fenxingSize] = fenxing;
  106.             fenxingSize++;
  107.         }
  108.     }
  109. }
复制代码



老规矩,再贴两张顶底分型处理的效果图:
cc58a965-ebed-43e6-8a65-0d440ef376d0.png
d9705793-a81c-4af0-ba38-08ab9c7a89e1.png

看起来没啥毛病。顶分型用蓝色向下箭头指示,底分型用红色向上箭头指示。红蓝色框是k线合并组的指示,没有红蓝色框的k线就是单独一根形成的k线组。

本文只是为了展示学习成果,其他数据处理图形展示部分的代码就不贴了,等将来都做完了,再一起发出来,请各位朋友拭目以待。

虽然我只做完了缠论的第二步,但总代码行数已经超过一千五百行。网上有好多几百行代码就搞定的缠论指标,我真不知道是怎么弄出来的。

下一步是处理笔。我有个小小感悟,笔不是简单的把顶底分型一一连线就好了,它对分型还有两条基本要求。就这两条要求可以让缠论学习者吵一下午。所以不做程序,就不能做到细致且无异议地学习理论。如果理论本身就学的模棱两可,吵再多也没有用。
举报

评论 使用道具

发新帖
EA交易
您需要登录后才可以评论 登录 | 立即注册

麻爪

麻爪 D

如果你想让一个人下地狱,介绍他做交易。
如果你想让一个人在地狱里永不超生,介绍他做EA交易。

Fast跟单EA