【缠论指标(第一部分)】这是缠论指标的第一步,包含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[]这样的,里面的成员本来就是从老到新排序的,不需要颠倒顺序,故这代码实际上把合并方向搞反了,现在免费给朋友们拿来参考着玩吧,等我修改完再重新发出来正确版。
已有0人购买, 当前内容需向作者支付 100 H币 才能浏览,搞笑 [呲牙][呲牙][呲牙] 缠论太难了 中枢很牛逼!我很喜欢 csby78 发表于 2026-10-6 11:10
已有0人购买, 当前内容需向作者支付 100 H币 才能浏览,搞笑
这代码我设了个高价就是为了只给大家看看,不卖,这一版刚弄出来,问题很多,只是演示一下怎么用AI做EA,还不能用的。
页:
[1]