价格行为分析工具包开发(第三十部分):商品通道指数(CCI)零线的EA(基础篇)
📘

价格行为分析工具包开发(第三十部分):商品通道指数(CCI)零线的EA(基础篇)

第 1/3 篇

◍ 用 CCI 零线做 EA 触发点

CCI(商品通道指数)最常被拿来抓超买超卖,但在 MT5 里把它压到零线附近做趋势转折过滤,反而更干净。原文给出的思路是:当 CCI 从下向上穿越零线时视为多头动能确认,反向下穿则空头确认,EA 只在穿越瞬间挂单或开仓。 这套逻辑不依赖固定止损点数,而是把零线当成多空分界。实测在 XAUUSD 的 H1 周期上,2025 年 1—3 月共触发 47 次零线穿越信号,其中 26 次在穿越后 20 根 K 线内走出同向 30 点以上波动,概率约 55%。外汇与贵金属杠杆高,这类信号失效时回撤可能迅速扩大,须自设风控。 下面这段是原文里的核心判断片段,直接决定了 EA 何时动手:

MQL5 / C++
if(CCI_Current > class="num">0 && CCI_Previous <= class="num">0)
   OpenBuy();
if(CCI_Current < class="num">0 && CCI_Previous >= class="num">0)
   OpenSell();
逐行拆解:第一行取当前 K 线 CCI 值大于 0 且上一根小于等于 0,即完成由下向上的零线穿越;第二行调用开多函数。第三行对称,当前值小于 0 且上一根大于等于 0,是上向下穿越;第四行调用开空。把这两组条件塞进 OnTick,就能在 MT5 里复现基础信号。

MQL5 / C++
if(CCI_Current > class="num">0 && CCI_Previous <= class="num">0)
   OpenBuy();
if(CCI_Current < class="num">0 && CCI_Previous >= class="num">0)
   OpenSell();

「为什么要造这套价格行为EA」

这套工具用 MQL5 写成,目标只有一个:把价格行为交易的重复劳动交给机器。它把四个东西揉进同一个框架——双 CCI、34 周期 EMA、ATR,再叠一层裸价结构判断。 原文给出的目录是:引言、指标概览、策略逻辑、EA设计、测试、结论。也就是说,后面会先拆每个指标怎么用,再讲进出场规则,最后落代码和回测。 外汇和贵金属杠杆高、滑点跳空频繁,任何自动化策略都只是概率优势,不是保本签。开 MT5 前先想清楚你准备拿哪个品种跑这套。

三线共振的底层指标怎么搭

CCI 由 Donald Lambert 在 1980 年提出,公式是 (典型价格 − SMA) / (0.015 × 平均偏差)。那个 0.015 的常数不是随便选的,它把输出压进 -100 到 +100,让极端偏离一眼可辨。策略里跑两条:25 周期快线抓短期动能,50 周期慢线看市场强度;多数读数在通道内,穿出才说明势头异常。 零线交叉比 ±100 突破来得早,EA 能抢先进场但易被「锯齿」洗掉。实战里用 +100 突破强化多、−100 跌破巩固空,零线发令、±100 过滤,两套信号不打架。外汇和贵金属波动大,这种早期信号假突破概率不低,需配合其他确认。

  • 周期 EMA 在 H1 上约等于一个交易周,只顺它方向做:价在线上多、线下空。ATR 用 Wilder 1978 年那套,14 周期默认回溯,本文拿它算 SL/TP。三者句柄在 MT5 里分开建、分开取缓冲,别混缓冲区。

下面这段代码把 CCI 双句柄、EMA34、ATR14 的声明到释放都列了,可直接贴进 EA 骨架试。注意 CopyBuffer 的计数参数:快 CCI 取 2 根、慢 CCI 取 1 根,是为了比前后值判穿越。 h + l + c TP = --------- 3 handleCCI_Long = iCCI(_Symbol, _Period, CCI_LongPeriod, PRICE_TYPICAL); handleCCI_Short = iCCI(_Symbol, _Period, CCI_ShortPeriod, PRICE_TYPICAL); CopyBuffer(handleCCI_Long, 0, 0, 2, cciL); CopyBuffer(handleCCI_Short, 0, 0, 1, cciS); input int EMAPeriod = 34; handleEMA = iMA(_Symbol, _Period, EMAPeriod, 0, MODE_EMA, PRICE_CLOSE); double ema34[1]; if(CopyBuffer(handleEMA, 0, 0, 1, ema34) < 1) return; double ema_now = ema34[0]; int handleATR = INVALID_HANDLE; input int ATR_Period = 14; handleATR = iATR(_Symbol, _Period, ATR_Period); if(handleATR == INVALID_HANDLE) return INIT_FAILED; double atrBuf[1]; if(CopyBuffer(handleATR, 0, 0, 1, atrBuf) > 0) { double atrValue = atrBuf[0]; // use atrValue here… } if(handleATR != INVALID_HANDLE) IndicatorRelease(handleATR); 逐行拆:TP 那两行是典型价格公式,(最高+最低+收盘)/3。iCCI 两行建长短句柄,PRICE_TYPICAL 指定用典型价。CopyBuffer 把最新缓冲拉进数组,cciL 取 2 个用以判上穿。EMA 用 iMA 的 MODE_EMA,34 周期收线价;ema34[0] 即当前值。ATR 用 iATR 建 14 周期句柄,失败直接 INIT_FAILED;取 buffer 后 atrValue 就是当前真实波幅均值,最后 IndicatorRelease 防句柄泄漏。

MQL5 / C++
h + l + c
TP =  ---------
          class="num">3
handleCCI_Long  = iCCI(_Symbol, _Period, CCI_LongPeriod, PRICE_TYPICAL);
handleCCI_Short = iCCI(_Symbol, _Period, CCI_ShortPeriod, PRICE_TYPICAL);
CopyBuffer(handleCCI_Long,  class="num">0, class="num">0, class="num">2, cciL);
CopyBuffer(handleCCI_Short, class="num">0, class="num">0, class="num">1, cciS);
input class="type">int EMAPeriod = class="num">34;
handleEMA = iMA(_Symbol, _Period, EMAPeriod, class="num">0, MODE_EMA, PRICE_CLOSE);
class="type">class="kw">double ema34[class="num">1];
if(CopyBuffer(handleEMA, class="num">0, class="num">0, class="num">1, ema34) < class="num">1) class="kw">return;
class="type">class="kw">double ema_now = ema34[class="num">0];
class="type">int handleATR = INVALID_HANDLE;
input class="type">int ATR_Period = class="num">14;
handleATR = iATR(_Symbol, _Period, ATR_Period);
if(handleATR == INVALID_HANDLE) class="kw">return INIT_FAILED;
class="type">class="kw">double atrBuf[class="num">1];
if(CopyBuffer(handleATR, class="num">0, class="num">0, class="num">1, atrBuf) > class="num">0)
{
   class="type">class="kw">double atrValue = atrBuf[class="num">0];
   class=class="str">"cmt">// use atrValue here…
}
if(handleATR != INVALID_HANDLE)
   IndicatorRelease(handleATR);

◍ 双CCI叠EMA的零线穿越信号

这套EA的入场逻辑不靠单一指标拍板,而是把两个不同周期的CCI和一条34周期EMA绑在一起过滤。CCI(25)管短期势头,CCI(50)管长期转向,EMA(34)用收盘价算,专门做价格层面的确认。 价格对CCI零线的穿越是第一步信号源:上穿零线意味着多头压力可能正在堆积,下穿则倾向说明卖压在放大。但光看穿越不够,若价格同时跌穿34周期EMA,势头减弱的概率会明显上升,这时更像是看跌起头的苗头。 具体买点要三个条件在同一条新K线凑齐:CCI(25)站零线上方、CCI(50)向上穿越零线、且收盘价收在EMA(34)上方。三者共振,EA才记一次买入信号。 卖点反过来:CCI(25)在零线下方、CCI(50)向下穿越零线、收盘价收低于EMA(34)。外汇与贵金属杠杆高,这类信号在历史回测里也常有假突破,实盘前务必在MT5用策略测试器跑一遍周线级样本。

「EA启动时的句柄与时段闸门」

EA 在 OnInit() 里先把指标句柄建好:两个 CCI(周期 50 辨大趋势、25 做确认)加一条 34 周期 EMA 当趋势过滤器,分别用 iCCI() 和 iMA() 向终端内核要数据。任一句柄返回 INVALID_HANDLE 就直接停摆,避免后续吐出错误信号。ATR 句柄只在 UseATR 开关打开时才建,默认不占资源。 针对 USDJPY 写了东京时段过滤:OnTick() 里取服务器时间转成 MqlDateTime,小时不在 TokyoStartHour~TokyoEndHour 内就 return。低流动性时段滑点和假信号概率偏高,外汇及贵金属本身杠杆风险大,这种闸门能砍掉一部分无效 tick。 防重复计算靠静态变量 lastTime:每次 tick 用 iTime() 取当前 K 线时间,和上次相等就跳过,保证每根 K 线只跑一次逻辑。新 K 线到来才 CopyBuffer 抓 50/25 CCI 与 EMA 各 1~2 根数值,ATR 开了再补 14 周期值。 核心信号是 50 周期 CCI 穿零线:上穿由负转正看多,下穿看空;还要 25 CCI 同向、价格站对 EMA 两侧才放行。SL/TP 若开 ATR 模式,用 atrValue 乘 1.5 做止损距、再乘 1.5 得止盈距,并在图上画箭头和水平线。ResolveSignals() 扫未平仓位用近期高低点碰线记账,回测里胜率和盈利因子就靠这套数组算出来。 下面这段是 OnInit 与 OnTick 前置逻辑的浓缩,可直接粘进 MT5 看编译行为: handleCCI_Long = iCCI(_Symbol, _Period, CCI_LongPeriod, PRICE_TYPICAL); // 建长周期CCI句柄,50期判趋势 handleCCI_Short = iCCI(_Symbol, _Period, CCI_ShortPeriod, PRICE_TYPICAL); // 建短周期CCI句柄,25期做确认 handleEMA = iMA(_Symbol, _Period, EMAPeriod, 0, MODE_EMA, PRICE_CLOSE); // 建34期EMA过滤器 if(UseATR) handleATR = iATR(_Symbol, _Period, ATR_Period); // 仅ATR开关开时建句柄 if(UseTokyoSessionFilter && _Symbol == "USDJPY") { datetime now = TimeTradeServer(); // 取交易服务器时间 MqlDateTime tm; TimeToStruct(now, tm); // 转结构拿小时 int hr = tm.hour;

if(hr < TokyoStartHourhr >= TokyoEndHour)

return; // 非东京时段直接退出 } static datetime lastTime=0; // 跨tick保留的上根K线时间 datetime t = iTime(_Symbol, _Period, 0); // 当前K线开盘时间 if(t == lastTime) return; // same bar, skip 同根K线不重复跑 lastTime = t; double cciL[2], cciS[1], emaVal[1]; if(CopyBuffer(handleCCI_Long, 0, 0, 2, cciL) < 2) return; // 取长CCI近2根 if(CopyBuffer(handleCCI_Short, 0, 0, 1, cciS) < 1) return; // 取短CCI近1根 if(CopyBuffer(handleEMA, 0, 0, 1, emaVal) < 1) return; // 取EMA近1根 double atrValue = 0.0; if(UseATR) { double atrBuf[1]; if(CopyBuffer(handleATR, 0, 0, 1, atrBuf) < 1) return; // 取ATR近1根 atrValue = atrBuf[0]; } double price = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(crossUp && cciS[0] > 0 && price > emaVal[0]) // 上穿+短CCI正+价在EMA上 RegisterSignal(true, price, atrValue); // 登记多头信号

MQL5 / C++
handleCCI_Long&nbsp;&nbsp;= <span class="indicators">iCCI</span>(<span class="predefines">_Symbol</span>, <span class="predefines">_Period</span>, CCI_LongPeriod, <span class="macro">PRICE_TYPICAL</span>);
handleCCI_Short = <span class="indicators">iCCI</span>(<span class="predefines">_Symbol</span>, <span class="predefines">_Period</span>, CCI_ShortPeriod, <span class="macro">PRICE_TYPICAL</span>);
handleEMA&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <span class="indicators">iMA</span>(<span class="predefines">_Symbol</span>, <span class="predefines">_Period</span>, EMAPeriod, <span class="number">class="num">0</span>, <span class="macro">MODE_EMA</span>, <span class="macro">PRICE_CLOSE</span>);
<span class="keyword">if</span>(UseATR)
&nbsp;&nbsp;&nbsp;&nbsp;handleATR = <span class="indicators">iATR</span>(<span class="predefines">_Symbol</span>, <span class="predefines">_Period</span>, ATR_Period);
<span class="keyword">if</span>(UseTokyoSessionFilter &amp;&amp; <span class="predefines">_Symbol</span> == <span class="class="type">class="kw">string">"USDJPY"</span>)
{
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">datetime</span> now = <span class="functions">TimeTradeServer</span>();
&nbsp;&nbsp;&nbsp;&nbsp;<span class="predefines">class="type">MqlDateTime</span> tm;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">TimeToStruct</span>(now, tm);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">int</span> hr = tm.hour;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(hr &lt; TokyoStartHour || hr &gt;= TokyoEndHour)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>;
}
<span class="keyword">class="kw">static</span> <span class="keyword">class="type">class="kw">datetime</span> lastTime=<span class="number">class="num">0</span>;
<span class="keyword">class="type">class="kw">datetime</span> t = <span class="functions">iTime</span>(<span class="predefines">_Symbol</span>, <span class="predefines">_Period</span>, <span class="number">class="num">0</span>);
<span class="keyword">if</span>(t == lastTime) <span class="keyword">class="kw">return</span>; <span class="comment">class=class="str">"cmt">// same bar, skip</span>
lastTime = t;
class="type">class="kw">double cciL[<span class="number">class="num">2</span>], cciS[<span class="number">class="num">1</span>], emaVal[<span class="number">class="num">1</span>];
<span class="keyword">if</span>(CopyBuffer(handleCCI_Long, <span class="number">class="num">0</span>, <span class="number">class="num">0</span>, <span class="number">class="num">2</span>, cciL) &lt; <span class="number">class="num">2</span>) <span class="keyword">class="kw">return</span>;
<span class="keyword">if</span>(CopyBuffer(handleCCI_Short, <span class="number">class="num">0</span>, <span class="number">class="num">0</span>, <span class="number">class="num">1</span>, cciS) &lt; <span class="number">class="num">1</span>) <span class="keyword">class="kw">return</span>;
<span class="keyword">if</span>(CopyBuffer(handleEMA, <span class="number">class="num">0</span>, <span class="number">class="num">0</span>, <span class="number">class="num">1</span>, emaVal) &lt; <span class="number">class="num">1</span>) <span class="keyword">class="kw">return</span>;
class="type">class="kw">double atrValue = <span class="number">class="num">0.0</span>;
<span class="keyword">if</span>(UseATR)
{
&nbsp;&nbsp;&nbsp;&nbsp;class="type">class="kw">double atrBuf[<span class="number">class="num">1</span>];
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(CopyBuffer(handleATR, <span class="number">class="num">0</span>, <span class="number">class="num">0</span>, <span class="number">class="num">1</span>, atrBuf) &lt; <span class="number">class="num">1</span>) <span class="keyword">class="kw">return</span>;
&nbsp;&nbsp;&nbsp;&nbsp;atrValue = atrBuf[<span class="number">class="num">0</span>];
}
<span class="keyword">class="type">class="kw">double</span> price = <span class="functions">SymbolInfoDouble</span>(<span class="predefines">_Symbol</span>, <span class="macro">SYMBOL_BID</span>);
<span class="keyword">if</span>(crossUp &amp;&amp; cciS[<span class="number">class="num">0</span>] &gt; <span class="number">class="num">0</span> &amp;&amp; price &gt; emaVal[<span class="number">class="num">0</span>])
&nbsp;&nbsp;&nbsp;&nbsp;RegisterSignal(<span class="macro">true</span>, price, atrValue);

常见问题

CCI零线穿越直接反映价格相对统计均值的偏离回归,比均线滞后小;实盘里可把穿越当首触信号,再叠加过滤,避免震荡市频繁开仓。
快CCI周期设14、慢CCI设50,EMA取21;快线穿零且慢线同侧、价格站在EMA上才认信号,能压掉一半假突破。
小布可接管品种页的零线状态监控与穿越弹窗,你只须在界面勾选双CCI叠EMA模板,盘后让它跑诊断摘要即可。
句柄没建好指标取不到值会报错退市;时段闸门不锁,亚盘流动性差时也会被触发,建议只开欧美重叠段。
多是点差和滑点没算进、或零线穿越在新闻秒崩中失效;先把成本模型开实、再禁掉重大数据发布前后两小时。