原文基础定义:一笔,是相邻的顶分型和底分型之间构成的结构,一顶一底,交替出现。
完整硬性规则:
1. 交替原则:序列必须底 → 顶 → 底 → 顶…… 或者 顶 → 底 → 顶 → 底……
不允许:底底、顶顶连续。
2. 顶底分型之间要有独立 K 线:顶分型和底分型,不能共用一根 K 线,中间至少要隔开一根不属于这两个分型的 K 线。
需要特别注意:是指两个分型之间要有独立K线,而不是顶底点所在的K线之间要有独立K线。
(缠论:顶底分型之间,至少有一根独立 K 线;这是标准笔的必要条件,叫 “不共用 K、中间有独立 K”)
等价条件:在包含处理后的K线序列上,从顶分型的最高K线到底分型的最低K线(包含端点),至少存在5根K线。
放宽定义(新笔/宽笔):缠师后来对定义做了小幅放宽,以适应更多实际走势。在满足“不共用K线”的前提下,不要求顶底之间必须有“独立K线”。
新的等价条件:在包含处理后的序列上,从顶分型的最高K线到底分型的最低K线(包含端点),至少有4根K线;如果还原包含关系(即考虑原始K线),则至少有5根K线。
本程序实现采用新的定义。即顶底分型之间至少不共用K线,可以没有独立K线。
3. 高低点约束:
(1)向上笔:起点底分型低点 < 终点顶分型高点(由低点到高点向上)
(2)向下笔:起点顶分型高点 > 终点底分型低点(由高点到低点向下)
4. 极值取舍规则:连续出现多个同方向分型的时候,要保留极值,抛弃次值,例如,向上寻找笔的时候,连续出现多个底 分型,保留最低的那个底;连续多个顶分型,保留最高的那个顶。这个就是常说的“顶中取高、底中取低”,过滤掉小的、 被包含的分型。
5. 一笔的两端:端点就是分型的极值价格。
6. 空间要求:顶必须高于底。这是保证笔具有有效趋势空间的硬性规定。在同一笔中,顶分型里最高的K线,其价格区间至 少要有一部分高于底分型里最低的K线的价格区间。简单来说,向上笔的终点(顶)必须高于起点(底)的价格范围,向下笔的终点(底)必须低于起点(顶)的价格范围。如果顶和底的区间完全重叠或嵌套,则不能构成一笔。
所以绘制笔的话,需要几个预加工函数:
第一:分型清理函数。先要把连续同向分型按照保留极值的原则清理一遍。实际上,按照前面合并包含k线——顶底分型的流程处理完之后,所有的分型已经是严格的交错分布了,正常情况下不可能存在连续同向分型,但为了,还是要做这么一个函数,哪怕只是为了保证边界。下面是分型清理函数:
int CleanFenxingArray()
{
Print("开始【分型清洗】,合并相邻同向分型。");
int arraysize_2 = ArraySize(g_fenxingArray); //获得当前分型数组的分型个数
ArrayResize(g_fenxingCleanArray, 0); // 清空上次结果
if(arraysize_2 < 1) return 0; // 如果分型数据少于一个,直接退出
int consecutiveSame = 0; //同向分型查找计数。
for(int i = 1; i < arraysize_2; i++)
if(g_fenxingArray[i].type == g_fenxingArray[i-1].type)
consecutiveSame++;
if(consecutiveSame >= 0)
Print("【分型清洗】输入中有 ", consecutiveSame, " 处相邻同向,清洗将合并");
// ---------- 跳过开头的 NONE,找第一个有效分型 ----------
int firstIdx = 0;
while(firstIdx < arraysize_2 && g_fenxingArray[firstIdx].type == FENXING_NONE)
firstIdx++;
// ---------- 全是 NONE:需要报警的情况 ----------
if(firstIdx >= arraysize_2)
{
Print("【分型清洗】异常:全部 ", arraysize_2, " 个分型均为 NONE,分型数据不可用");
return 0;
}
ArrayResize(g_fenxingCleanArray, arraysize_2);// 预分配最大容量,避免循环内频繁 ArrayResize
g_fenxingCleanArray[0] = g_fenxingArray[firstIdx]; // 第一个有效分型作为起点
int keptFX = 1; // 新增变量,用来代表已保留的有效分型数量(也是 g_fenxingCleanArray 的写入位置)
for(int i = firstIdx+1; i < arraysize_2; i++)
{
// -------- 跳过无效分型 --------
// 分型识别函数不应产出 NONE,这里作为防御:
// 缓存损坏、ArrayResize 未填充区默认 0、未来扩展"待确认"状态
if(g_fenxingArray[i].type == FENXING_NONE)
continue;
// -------- 3. 与已保留序列的最后一个分型比较 -------- 同向:只保留更极端者
if(g_fenxingArray[i].type == g_fenxingCleanArray[keptFX - 1].type)//如果要比较的(i)和被比较的(keptFX - 1)同类
{
bool replace = false; //先设定不替换,replace是替换的意思,即(i)相对于(keptFX - 1)没有创新高或新低,原来的(keptFX - 1)继续保留
if(g_fenxingArray[i].type == FENXING_TOP)//如果同为顶分型
{
// 顶:更高者胜;价格相等时更晚者胜
replace = (g_fenxingArray[i].price > g_fenxingCleanArray[keptFX - 1].price) ||
(g_fenxingArray[i].price == g_fenxingCleanArray[keptFX - 1].price &&
g_fenxingArray[i].mergeIdx > g_fenxingCleanArray[keptFX - 1].mergeIdx);//这句判断条件在正常情况下恒为真,属于针对异常数据的防御性设计。
}
else // FENXING_BOTTOM
{
// 底:更低者胜;价格相等时更晚者胜
replace = (g_fenxingArray[i].price < g_fenxingCleanArray[keptFX - 1].price) ||
(g_fenxingArray[i].price == g_fenxingCleanArray[keptFX - 1].price &&
g_fenxingArray[i].mergeIdx > g_fenxingCleanArray[keptFX - 1].mergeIdx);
}
if(replace) //如果计算结果为真
g_fenxingCleanArray[keptFX - 1] = g_fenxingArray[i];
// 否则丢弃当前(不是更极端,保留原极值)
}
else
{
// 反向:直接追加,形成顶底交替
g_fenxingCleanArray[keptFX] = g_fenxingArray[i];
keptFX++;
}
}
// 缩到实际大小,释放预分配的多余内存
ArrayResize(g_fenxingCleanArray, keptFX);
Print("分型清洗已完成","原始分型共:", arraysize_2, "个,清洗后还剩:", keptFX,
"个,去重=", (arraysize_2 - keptFX), " 个,保留率=",
(arraysize_2 > 0 ? DoubleToString(100.0 * keptFX / arraysize_2, 1) : "0"), "%");
return keptFX;
} 复制代码
第二:笔的终点分型判断函数。通过顶底交替 + 三组齐全 + 不共用K线 + 价格方向正确等几个方面去判断备选的终点分型能否当作当前笔的终点,如果可以,就此封存当前笔,如果不可以,那就等待下一个分型。下面是判断函数:
bool CanFormPen(const Fenxing &fenxing_old, const Fenxing &fenxing_new)
{
// 1. 顶底必须交替
if(fenxing_old.type == fenxing_new.type) return false; //两个分型相同,判断不能形成笔
if(fenxing_old.type == FENXING_NONE || fenxing_new.type == FENXING_NONE) return false; //任意分型类型为空,判断不能形成笔
// 2. 分型三组校核
int arraysize_1 = ArraySize(g_mergeArray); //获得合并K线组数量
if(arraysize_1 < 3) return false; //获得合并K线组少于三个,没有分型,没有笔
if(fenxing_old.mergeIdx < 1 || fenxing_old.mergeIdx >= arraysize_1 - 1) return false; //峰值不能在合并序列最左/最右端
if(fenxing_new.mergeIdx < 1 || fenxing_new.mergeIdx >= arraysize_1 - 1) return false; //峰值不能在合并序列最左/最右端
// 3. 不共用K线:合并K线索引差 >= 3
// 等价于"顶底之间至少跨越4根合并K线"(新笔标准)
if(MathAbs(fenxing_new.mergeIdx - fenxing_old.mergeIdx) < 3) return false;
// 4. 价格方向:上升笔顶必须更高,下降笔底必须更低,即不接受水平笔
if(fenxing_old.type == FENXING_BOTTOM && fenxing_new.type == FENXING_TOP && fenxing_new.price <= fenxing_old.price) return false;//a低b高,但b≤a,不能成笔
if(fenxing_old.type == FENXING_TOP && fenxing_new.type == FENXING_BOTTOM && fenxing_new.price >= fenxing_old.price) return false;//a高b低,但b≥a,不能成笔
return true;//除了上述不能成笔的情况,其他情况都可以判断成笔。
} 复制代码
第三:同向分型极端性判断函数。当我们忽略掉一个反向分型时,下一个分型一定是和起点分型同向的,这时候需要判断二者哪个更极端,便保留哪个。这种情况与前面的同向分型合并函数是同一个原理,但不是同一个用处。它只出现在系统刚开始加载的时候,当第二个分型不能做终点时,需要判断第三个分型与第一个分型的极端性。下面的判断函数:
bool IsMoreExtreme(const Fenxing &fenxing_new, const Fenxing &fenxing_old)
{
if(fenxing_new.type != fenxing_old.type) return false; //如果不同向,本项判断为假
if(fenxing_new.type == FENXING_TOP)//如果是顶分型
{
if(fenxing_new.price > fenxing_old.price) return true; //新的高过旧的,需要更新,
if(fenxing_new.price == fenxing_old.price && fenxing_new.mergeIdx > fenxing_old.mergeIdx) return true;//相等情况也需要更新
}
else if(fenxing_new.type == FENXING_BOTTOM)//如果是底分型
{
if(fenxing_new.price < fenxing_old.price) return true;//新的低过旧的,需要更新,
if(fenxing_new.price == fenxing_old.price && fenxing_new.mergeIdx > fenxing_old.mergeIdx) return true;//相等情况也需要更新
}
return false;//其他情况(正常也没其他情况了)返回假
}
复制代码
第四,最后才是笔的处理函数。
int FullPen()
{
//第一步:清空 + 兜底:清空 g_penArray,检查基础数据是否够用;
ArrayResize(g_penArray, 0); //先把g_penArray数组归零
int arraysize_1 = ArraySize(g_mergeArray); //基础数据兜底,合并组少于三个,直接退出
if(arraysize_1 < 3)
{
Print("【笔构建】K线合并数组未就绪(数量=", arraysize_1, "),跳过");
return 0;
}
int arraysize_3 = ArraySize(g_fenxingCleanArray);//基础数据兜底,清理后的顶底分型少于二个,直接退出
if(arraysize_3 < 2)
{
Print("【笔构建】有效分型不足(", arraysize_3, "),跳过");
return 0;
}
//第二步:状态机遍历:从第 2 个分型(即下标1)开始,逐个"喂"给状态机,决定"起点更新 / 终点延伸 / 确认一笔 / 忽略";
int penStart = 0; //代表g_fenxingCleanArray数组中的成员下标,当前笔的起点分型,
int penEnd = -1; //代表g_fenxingCleanArray数组中的成员下标,还未确定的新分型,当前笔的待确认终点分型,用-1表示非法成员,确定后再改
for(int i = 1; i < arraysize_3; i++)
{
if(penEnd == -1) //无候选终点,只有penStart起点,先考虑起点分型是否需要更新,再考虑终点分型是否合规
{
if(g_fenxingCleanArray[i].type == g_fenxingCleanArray[penStart].type) //如果i元素和start元素类型相同,尝试更新起点,先判断新分型是否更极端
{
if(IsMoreExtreme(g_fenxingCleanArray[i], g_fenxingCleanArray[penStart]))//调用极端判断函数,校对起点,参数先新后旧
penStart = i; //如果判断为真,起点更新为i下标分型
}
else //如果i元素和start元素类型不相同,尝试设为终点,先判断新分型是否能做终点
{
if(CanFormPen(g_fenxingCleanArray[penStart], g_fenxingCleanArray[i])) //调用新笔判断函数,参数先旧后新
penEnd = i;
}
}
else //有候选终点,即penEnd≠-1,从penEnd开始,先考虑终点是否需要更新,再考虑能否就此星辰新笔
{
if(g_fenxingCleanArray[i].type == g_fenxingCleanArray[penEnd].type) //如果i元素和End元素类型相同,尝试更新终点,先判断新分型是否更极端
{
// 与候选终点同向:尝试延伸
if(IsMoreExtreme(g_fenxingCleanArray[i], g_fenxingCleanArray[penEnd]) &&//调用极端判断函数,校对起点,参数先新后旧
CanFormPen(g_fenxingCleanArray[penStart], g_fenxingCleanArray[i])) //调用新笔判断函数,参数先旧后新
{
penEnd = i; //两个判断函数均为真,即新分型更极端,且可以成笔,则更新终点
}
}
else //如果i元素和End元素类型不相同,尝试成笔,判断新分型(即i)是否能做终点
{
// 与候选终点反向:尝试确认 pendingStart→pendingEnd
if(CanFormPen(g_fenxingCleanArray[penEnd], g_fenxingCleanArray[i])) //调用新笔判断函数,参数先旧后新
{
AddPen(penStart, penEnd, true);
penStart = penEnd;
penEnd = i;
}
// 不能成笔则忽略
}
}
}
// 第三步:收尾 + 统计:把最后未确认的笔作为"待定笔"加入,打印统计
if(penEnd != -1)
AddPen(penStart, penEnd, false); //false代表最后一笔是未确定笔
int confirmedCount = 0;
for(int k = 0; k < ArraySize(g_penArray); k++)
if(g_penArray[k].confirmed) confirmedCount++;
Print("【笔构建】分型=", arraysize_3, " 总笔数=", ArraySize(g_penArray),
"(已确认=", confirmedCount, " 待定=", ArraySize(g_penArray) - confirmedCount, ")");
return ArraySize(g_penArray);
} 复制代码
之前有朋友们说这些函数怎么用,其实我发的不全,直接复制走也不能起作用。但单个函数本身是全的,仅供初学者参考学习。等我把整个缠论都编出来,再整体的发出来给大家。缠论太复杂了,对于初学者,即使给你全部的构建,你也很难把它们完整且正确的拼在一起。下面照老规矩,贴两张笔画线的图,看看效果。