麻爪 发表于 2026-10-5 17:25:24

【缠论指标(第一部分)】这是缠论指标的第一步,包含K线合并的代码,并带有方框指示效果

最后由 麻爪 于 2026-10-7 09:41 编辑

(一)先把缠论中关于“包含关系K线合并”的要求写明白1.看缠论中对”包含K线“的定义:两根相邻 K 线中,如果一根 K 线的最高价 ≥ 另一根的最高价,且最低价 ≤ 另一根的最低价,也就是一根 K 线的高低点完全包裹住另一根,就称这两根 K 线存在「包含关系」。2.缠论中对包含处理顺序的规定:从左到右,依次合并。必须按照 K 线出现的时间顺序,从最老到最新(从左到右)依次处理,先合并前面的,再用合并后的新 K 线和后面的 K 线比较,不能跳着处理、不能从右往左处理。3.缠论中对包含处理合并方向的规定:不是看包含的两根 K 线谁高谁低,而是由「这组包含关系之前,最近一根没有包含关系的 K 线」来决定:取包含组左侧最近的一根独立 K 线,和包含组的第一根 K 线比较:(1)如果立 K 线高点 < 包含组第一根的高点,属于上升趋势中的包含,按向上合并处理;(2)如果 独立 K 线高点 > 包含组第一根的高点,属于下降趋势中的包含,按向下合并处理。4.缠论中对合并取值的规定:(1)向上合并(高高原则):合并后的新 K 线,高点取两根 K 线高点的最大值,低点取两根 K 线低点的最大值。也就是:`新高 = max(高1, 高2)`,`新低 = max(低1, 低2)`;(2)向下合并(低低原则):合并后的新 K 线,高点取两根 K 线高点的最小值,低点取两根 K 线低点的最小值。5.缠论中对于连续包含的处理规定:如果出现三根及以上连续的包含 K 线,就从左到右滚动合并:(1)先合并前两根,得到一根新的 K 线;(2)用这根新 K 线和第三根比较,如果还是包含关系,继续按同一个方向合并;(3)直到遇到不包含的 K 线,输出最终合并后的 K 线,继续往后遍历。(4)连续包含的方向,由第一组包含的前序方向决定,中间不会变方向。6.关于起始边界的处理:如果图表最开头的前两根 K 线就存在包含,没有更前面的独立 K 线来定方向,禅师没说怎么弄,但我们可以给出最简单的处理方法,把这两个K线舍弃掉,把第三根K线作为起始K线,如果第三根和第四根还是包含关系(这种可能性也不是没有,一直处于窄幅震荡中,就会出现连续的包含K线)那就继续舍弃好了。我们算了最近两千根K线,头几根丢掉也无所谓,没什么影响。7.补充规定:缠论中并没有特殊要求,呈现包含关系的两个K线是左边的长一点,还是右边的长一点,所以我们默认无论是左K包右K,还是右K包左K,都是包含关系,都按上面的要求处理。8.显示要求:这一分析步骤,在代码中最终只形成了一个处理后的K线数据组,我们是看不到的。为了让我们可以看到代码分析的成果,评判它是否正确,需要进行可视化处理。简单来说,把向上合并的K线组用蓝色方框框起来,向下合并的K线组用红色方框框起来。这些要求罗列清楚无歧义后,就可以丢给豆包,让它试着生成一版代码了。(二)包含关系K线合并程序代码实例下面是具体代码,我每一句都加了简单的注释,但想要彻底读懂它,必须要学会MQL5语言的基础语法,同时还要有一点数据结构、算法方面的基础原理知识://+------------------------------------------------------------------+
//|                                       chanlundingdifenxing.mq5 |
//|                                             Copyright 2026,ZHAO. |
//|                                                      13360027244 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026,ZHAO."
#property link      "13360027244"
#property version   "1.00"
#property strict            //严格语法声明
#property indicator_chart_window //指标显示在当前窗口声明

input int MaxAnalyzeBars = 2000; //K线最大处理数量限制

//+------------------------------------------------------------------+
//| 经过“包含处理”后的K线单元组(可以为1根或多根K线)数据结构体      |
//+------------------------------------------------------------------+
struct MergedKline   
{
    double high;            // 合并后K线单元组的最高价
    double low;               // 合并后K线单元组的最低价
    int    start_bar;         // 对应原始K线的起始索引(包含组中最老的K线)
    int    end_bar;         // 对应原始K线的结束索引(包含组中最新的K线)
    int    trend;             // 合并方向:1=向上合并,-1=向下合并,0=初始未确定
};                            //注意,该结构体有五个元素

//+------------------------------------------------------------------+
//| 函数声明:K线包含处理 ,先告诉系统,有这么个函数               |
//+------------------------------------------------------------------+
int ProcessInclusion(double &highArr[], double &lowArr[], int total, MergedKline &result[]);

//+------------------------------------------------------------------+
//| Custom indicator initialization function 加载初始化函数          |
//+------------------------------------------------------------------+
int OnInit()
{
//--- indicator buffers mapping
   
//---
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function “自定义指标”迭代函数         |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,             // 1. 当前图表总共有多少根K线
                const int32_t prev_calculated,         // 2. 上一次已经计算完成的K线数量(增量核心),含上次结束时未收盘的那根
                const datetime &time[],                // 3. 每根K线的开盘时间数组
                const double &open[],                  // 4. 开盘价数组
                const double &high[],                  // 5. 最高价数组
                const double &low[],                   // 6. 最低价数组
                const double &close[],               // 7. 收盘价数组
                const long &tick_volume[],             // 8. tick成交量数组
                const long &volume[],                  // 9. 标准成交量数组
                const int32_t &spread[])               // 10. 点差数组
{
if(rates_total < 2)return rates_total;//至少要2根k线才能做分析,少于二根直接结束

// 数组下标反转:MQL5原生倒序(越早下标越大,0=最新) → 统一正序(越早下标越小,0=最老) ==========
    double arr_high[];   //设一个新的数组arr_high[],用来存储颠倒顺序后的high[]数组的内容
    double arr_low[];    //设一个新的数组arr_low[],用来存储颠倒顺序后的low[]数组的内容
    ArrayResize(arr_high, rates_total);//给数组arr_high[]设定长度,有 rates_total个元素
    ArrayResize(arr_low, rates_total);   //给数组arr_low[]设定长度,有 rates_total个元素
   
    for(int i = 0; i < rates_total; i++)   
    {
      arr_high = high;
      arr_low= low;
    }
    /*比如一个数组里有10、9、8···1,十个数字,其编号是0、1、2···9,它的反序列数组里第0个数字就是原序列的第9个数字,
    rates_total就是总数十个,减1再减序号0,就是9,反序列里第1个数字,就是原序列的第8个数字,十减1再减序号1,就是8,
    用数组给数组赋值,操作的是数组里各元素的序号,而不是元素本身的数值*/
            
int start = prev_calculated - 1; //正常增量场景下的精度修正,把上一次最后那根「未收盘的临时 K 线」重新算一遍

if(start < 0)start = 0;      //边界兜底 + 无效场景的强制全量重置

if(MaxAnalyzeBars > 0)          //现K线数量限制语句,相当于检查OnCalculate函数上次更新加载的K线数量,如果设为0或负数,相当于没有限制
   {
      int limit_bar = rates_total - MaxAnalyzeBars;
      if(limit_bar > start) start = limit_bar;
      if(start < 0) start = 0;
   }
   
   //执行K线包含处理 ==========
   MergedKline merged_units[];   //设一个结构体数组merged_units[]来储存K线包含处理的结果数据,每个元素都是一个结构体数据
   int unit_count = ProcessInclusion(arr_high, arr_low, rates_total, merged_units); //调用K线包含处理函数
   
   // 调试输出:可删除
    Print("原始K线数:", rates_total, " → 合并后单元数:", unit_count);
   
   //绘制合并K线矩形框 ==========
    // 先删除上一轮绘制的所有合并框,避免重叠
    ObjectsDeleteAll(0, "merge_box_", -1, OBJ_RECTANGLE); //内置函数,批量删除当前图表上的指标

    // 遍历每个合并单元,逐个画框
    for(int i = 0; i < unit_count; i++)
    {
      string obj_name = "merge_box_" + IntegerToString(i);

      // 正序下标 → MQL5原生倒序下标转换(用于取对应时间)
      int native_start_idx = rates_total - 1 - merged_units.start_bar;
      int native_end_idx   = rates_total - 1 - merged_units.end_bar;

      // 矩形的时间边界(左=最早K线时间,右=最晚K线时间)
      datetime left_time= time;
      datetime right_time = time;
      
      // 矩形的价格边界
      double top_price    = merged_units.high;
      double bottom_price = merged_units.low;

      // 创建矩形对象
      if(!ObjectCreate(0, obj_name, OBJ_RECTANGLE, 0, left_time, top_price, right_time, bottom_price))
      {
            Print("绘制合并框失败:", obj_name, " 错误码:", GetLastError());
            continue;
      }

      // 按趋势设置颜色
      if(merged_units.trend == 1)
      {
            // 向上合并:蓝色边框 + 淡蓝透明填充
            ObjectSetInteger(0, obj_name, OBJPROP_COLOR, clrBlue);
            ObjectSetInteger(0, obj_name, OBJPROP_FILL, true);
            ObjectSetInteger(0, obj_name, OBJPROP_BGCOLOR, 0x1EFF0000);
      }
      else
      {
            // 向下合并:红色边框 + 淡红透明填充
            ObjectSetInteger(0, obj_name, OBJPROP_COLOR, clrRed);
            ObjectSetInteger(0, obj_name, OBJPROP_FILL, true);
            ObjectSetInteger(0, obj_name, OBJPROP_BGCOLOR, 0x1E0000FF);
      }

      // 边框宽度 + 绘制在K线前方
      ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, 1);
      ObjectSetInteger(0, obj_name, OBJPROP_BACK, false);
    }

   // 后续调用分型、画笔等函数,统一基于 merged_units 和 arr_high/arr_low 开发
   // ...
   
//--- return value of prev_calculated for next call下次调用prev_calculated(进度标记参数)时的返回值
   return(rates_total);               //`OnCalculate` 的返回值本质是本次计算完成的 K 线数量(计算进度游标)
}

//+------------------------------------------------------------------+
//| 功能:对正序K线数组进行包含处理(高高/低低原则)                  |
//| 输入:highArr 正序高点数组,lowArr 正序低点数组,total K线总数    |
//| 输出:result 输出合并单元数组                                     |
//| 返回值:合并后的K线总数量                                       |
//+------------------------------------------------------------------+
int ProcessInclusion(double &highArr[], //引用k线最高价数据常量,输入参数
                     double &lowArr[],//引用k线最低价数据常量,输入参数
                     int total,      //本函数处理的K线数量,输入参数
                     MergedKline &result[])//把处理后的K线组数据存入结构体数组result[],输出参数
{//合并K线函数主体的开始花括号,注意它什么时候结束
   // 边界初始化
   if(total <= 0)   //0或者负值根k线,完全无数据,直接返回0
{   
    return 0;
}   
   // 当有2根级以上K线时,开始主循环判断过程
   // --------- 初始化 ---------
   ArrayResize(result, total);// 预分配最大空间(最大不超过原始K线数量),避免多次扩容
   int merged_count = 0;      // 设置一个整型变量merged_count做合并单元(也就是本函数)计数器
   /* 特别说明:K线经合并后得到的K线组数,肯定会比原始k线数量少,而且跟原始K线数量没有必然的对应关系,
   属于单独的数据,所以必须要单独设个变量来计数*/
   int trend = 1;             // 趋势方向标记:1=向上,-1=向下,0=未确定(开头连续包含场景)

   // 初始化当前合并单元:从最左侧(最老)第一根K线开始,用作比较的起点K线
   MergedKline current;       //创建一个函数内部使用的结构体current,意思是当前的
   current.high      = highArr;    //给结构体结构体current初始化,即用0号K线它的五个元素赋值
   current.low       = lowArr;
   current.start_bar = 0;
   current.end_bar   = 0;
   current.trend   = 1;      //人为设定第一个k线的趋势方向是向上的,如果头两个k线就要合并,那就向上合并   
   //前面定义的结构体resultKline有五个成员,必须每个成员都要赋值

   // 函数主循环:从第二根K线开始(下面的i指的是K线下标号),从左到右(老到新)依次遍历
   for(int i = 1; i < total; i++) //for循环(起始序号,循环上限,自动加1计数器)
   { //for循环的起始花括号,注意它在哪里结束
      double ki_high = highArr;   //设一个变量ki_high,等于本轮处理的下标i的k线最高价
      double ki_low= lowArr;    //设一个变量ki_low,等于本轮处理的下标i的k线最低价

      // 1. 判断包含关系 , 左包右、右包左均属于包含,统一规则处理
      //通过把两K线最高价、最低价的关系运算结果再进行布尔运算,最终的得到是否包含的布尔值
      bool is_inclusion = (current.high >= ki_high && current.low <= ki_low)   
                        || (ki_high >= current.high && ki_low <= current.low);

      // 2. 无包含关系:当前单元定型,即i之前那个被比较的K线
      if(is_inclusion == false)   //如果不存在包含关系
      {
         // 存入结果数组
         result = current;//把被比较的K线或者K线组参数赋值给结构体数组result[]
         /*merged_count代表i号K线左边的K线或K线序号,第一次循环,该变量为0,就把初始化的0号K线
         数据(也就是初始化的结构体current)赋值给它*/
         result.trend = trend;//强行修正一下数组result[]元素中的趋势方向元素
         merged_count++;                  //merged_count变量开始计数,初始值为0,第一次for循环后,变成1,以此类推

         // 更新趋势方向:由前后两根独立K线的高点关系决定
         trend = (current.high < ki_high) ? 1 : -1;   //三元条件运算,括号里的判断成立则返回1,不成立返回-
         /*这里有点绕脑子,假设i号K线不能跟它前面的K线合并,但有可能可以跟它后面的K线合并,所以用i号K线和它前面的k
         线先判断出一个方向来,供后面新K线出来后可能的合并使用,如果后面新K线也不能合并,那这个结果就舍弃好了,没什
         可惜的*/

         // 重置当前K线为新的K线单元,下一轮合并比较时,本轮的i号K线就成了被比较的current
         current.high      = ki_high;
         current.low       = ki_low;
         current.start_bar = i;
         current.end_bar   = i;
         current.trend   = trend;   
         /*需要理解,i号K线是下次合并判断中“被比较的K线”,也就是缠论里说的合并K线组里面最左边的k线,而本次赋的方向值
         trend,正是i号K线和它前面的k线比较出来的方向,所以完全符合缠论规定*/
       }
      // 3. 有包含关系:按趋势方向合并 ==========
      else         //如果存在包含关系,与人类思维相反,先处理不存在,再处理存在,因为不存在包含的状态是存在包含的先决条件
      {
         // 按方向执行合并
         if(trend == 1)//1代表向上趋势,也就是i号K线比它前面的高
         {
            // 向上合并:高高原则
            current.high = MathMax(current.high, ki_high); //二者取大
            current.low = MathMax(current.low, ki_low);    //二者取大
         }
         else         //也就是-1,trend的三元关系运算没有其他结果,代表向下趋势,也就是i号K线比它前面的低
         {
            // 向下合并:低低原则
            current.high = MathMin(current.high, ki_high); //二者取小
            current.low = MathMin(current.low, ki_low);    //二者取小
         }

         // 更新合并单元的结束索引
         current.end_bar = i;
         //这里为什么只更新了合并单元结束k线索引,而没有更新开始K线索引,这牵扯到if else分支的边界处理,请自行研究
      }
   }//for循环的结束花括号

   /* 遍历结束,存入最后一个合并单元。for循环是通过i号K线来判断上一组k线组是否要存档,所以处理完最后一根K线i时,它自
   身就不能靠for循环去存档了,只能额外再专门写一句存档代码*/
   result = current;
   result.trend = trend;
   merged_count++;

   // 缩容数组至实际大小,释放多余内存
   ArrayResize(result, merged_count);

   return merged_count;//函数返回值,也就是最终处理了多少组合并K线,(不能合并的单根k线也算一组)
}//合并K线函数主体的结束花括号


//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
{
//---
   
}
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
//---
   
}
//+------------------------------------------------------------------+
我加的注释较多,希望能够方便初学者理解。写这段代码,豆包只用了五秒钟,我理解捋清这段代码,用了一天半。想要学明白MQL5语言,不如自己上手做一个缠论分析程序出来,你几乎可以遇到所有的MQL5语言知识,从最基础的语法到函数,再到数据结构、基本算法,图形表达等等方面,可以说一通百通。这段代码直接复制进你的编译器编译即可使用。然后在MT5-插入-指标-自定义中就可以找到。它的运行效果如下图所示:换一个品种试试,效果一样可以。可以看到该合并都合并了,且合并的方向也没问题,下一步就可以继续编写顶底分型的函数,然后再设计一个图例显示它找到的顶底分型,其他的线段、中枢之类的,只要能明确条件要求,都能做出来。下一篇文章将实现顶底分型的代码和效果展示。

我第二天调试,发现一个问题,MQL5中的自带数组,比如high[]这样的,里面的成员本来就是从老到新排序的,不需要颠倒顺序,故这代码实际上把合并方向搞反了,现在免费给朋友们拿来参考着玩吧,等我修改完再重新发出来正确版。

csby78 发表于 2026-10-6 11:10:56

已有0人购买, 当前内容需向作者支付 100 H币 才能浏览,搞笑

江河998866 发表于 2026-10-6 11:45:54

[呲牙][呲牙][呲牙]

jj644719356 发表于 2026-10-6 14:32:59

缠论太难了

blackbrume 发表于 2026-10-6 14:48:09

中枢很牛逼!我很喜欢

麻爪 发表于 2026-10-7 09:00:00

csby78 发表于 2026-10-6 11:10
已有0人购买, 当前内容需向作者支付 100 H币 才能浏览,搞笑

这代码我设了个高价就是为了只给大家看看,不卖,这一版刚弄出来,问题很多,只是演示一下怎么用AI做EA,还不能用的。
页: [1]
查看完整版本: 【缠论指标(第一部分)】这是缠论指标的第一步,包含K线合并的代码,并带有方框指示效果