构建和测试肯特纳通道交易系统·综合运用
(3/3)·把波动概念转成可跑的系统,并用实测数据挑出更适配品种与参数
◍ 肯特纳通道的穿越开仓判定
这段逻辑把肯特纳通道的三条线(上、中、下轨)用 CopyBuffer 取出来后,立刻用 ArraySetAsSeries 把数组倒序,让 [1] 代表上一根 K 线、[2] 代表前一根。注意 NormalizeDouble(...,5) 把价格统一压到 5 位精度,避免黄金或外汇跨品种时浮点尾差导致条件误触。 核心判定分两组:第一组是价格从通道外反穿回通道内——前收在下水道下方、现收回到下轨之上,倾向视为多头回归,按 ask 市价 Buy,SL 放在下轨减去 slPips 点、TP 放在中轨加上 tpPips 点;反之对称做空。外汇与贵金属杠杆高,穿越信号失败概率不低,实盘前先用策略测试器跑 EURUSD 15M 看回撤。 第二组是价格突破通道外轨:前收在上轨内、现收突破上轨,直接沿突破方向 Buy,SL/TP 都以突破轨为基准加减 pip。这里做空分支里 TP 写的是 upperValue 而非 lowerValue,代码疑似笔误,复制时务必核对。 下面把关键片段原样贴出,方便你直接挪进 EA 的 OnTick 里改参数验证。
CopyBuffer(keltner,class="num">2,class="num">0,class="num">3,lower); ArraySetAsSeries(upper,true); ArraySetAsSeries(middle,true); ArraySetAsSeries(lower,true); class="type">class="kw">double prevUpperValue = NormalizeDouble(upper[class="num">2], class="num">5); class="type">class="kw">double prevMiddleValue = NormalizeDouble(middle[class="num">2], class="num">5); class="type">class="kw">double prevLowerValue = NormalizeDouble(lower[class="num">2], class="num">5); class="type">class="kw">double upperValue = NormalizeDouble(upper[class="num">1], class="num">5); class="type">class="kw">double middleValue = NormalizeDouble(middle[class="num">1], class="num">5); class="type">class="kw">double lowerValue = NormalizeDouble(lower[class="num">1], class="num">5); class="type">class="kw">double lastClose=iClose(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double prevLastClose=iClose(_Symbol,PERIOD_CURRENT,class="num">2); if(prevLastClose<prevLowerValue && lastClose>lowerValue) { class="type">class="kw">double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); class="type">class="kw">double slVal = lowerValue - slPips*_Point; class="type">class="kw">double tpVal = middleValue + tpPips*_Point; trade.Buy(lotSize,_Symbol,ask,slVal,tpVal); } if(prevLastClose>prevUpperValue && lastClose<upperValue) { class="type">class="kw">double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); class="type">class="kw">double slVal = upperValue + slPips*_Point; class="type">class="kw">double tpVal = middleValue - tpPips*_Point; trade.Sell(lotSize,_Symbol,bid,upperValue,tpVal); } if(prevLastClose<prevUpperValue && lastClose>upperValue) { class="type">class="kw">double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); class="type">class="kw">double slVal = upperValue - slPips*_Point; class="type">class="kw">double tpVal = upperValue + tpPips*_Point; trade.Buy(lotSize,_Symbol,ask,slVal,tpVal); } if(prevLastClose>prevLowerValue && lastClose<lowerValue) { class="type">class="kw">double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); class="type">class="kw">double slVal = lowerValue + slPips*_Point; class="type">class="kw">double tpVal = lowerValue - tpPips*_Point; trade.Sell(lotSize,_Symbol,bid,upperValue,tpVal); }
「用 Keltner 通道搭一个裸 EA 骨架」
下面这段 MQL5 代码把 Keltner 通道的通道线直接拉进 EA,不做任何过滤,只在每根新 K 线出现时重算上中下轨。它用 iCustom 挂载名为 Custom_Keltner_Channel 的指标,参数里 maPeriod=10、multiInp=2.0、maMode=MODE_EMA、priceType=PRICE_TYPICAL,意味着通道中轨是 10 周期典型价的 EMA,上下轨在中轨±2 倍波动宽度处。 外汇与贵金属杠杆高,这段代码若直接挂实盘,slPips=150、tpPips=500 的硬止损止盈在 XAUUSD 上可能一晚就被扫,先上 MT5 策略测试器用 Tick 数据回测再谈。 OnInit 里先记 barsTotal,OnTick 每次比对 bars 数量,只有新柱才跑 CopyBuffer 取最近 3 根的上中下轨并转成系列数组。prevUpperValue 取索引 2(前前根),upperValue 取索引 1(前一根),后面就是拿这两组值做穿越判断的原材料。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Keltner_Trading_System2.mq5 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <trade/trade.mqh> class="kw">input class="type">int maPeriod = class="num">10; class=class="str">"cmt">// Moving Average Period class="kw">input class="type">class="kw">double multiInp = class="num">2.0; class=class="str">"cmt">// Channel multiplier class="kw">input class="type">bool isAtr = false; class=class="str">"cmt">// ATR class="kw">input ENUM_MA_METHOD maMode = MODE_EMA; class=class="str">"cmt">// Moving Average Mode class="kw">input ENUM_APPLIED_PRICE priceType = PRICE_TYPICAL;class=class="str">"cmt">// Price Type class="kw">input class="type">class="kw">double lotSize=class="num">1; class="kw">input class="type">class="kw">double slPips = class="num">150; class="kw">input class="type">class="kw">double tpPips = class="num">500; class="type">int keltner; class="type">int barsTotal; CTrade trade; class="type">int OnInit() { barsTotal=iBars(_Symbol,PERIOD_CURRENT); keltner = iCustom(_Symbol,PERIOD_CURRENT,"Custom_Keltner_Channel",maPeriod,multiInp, isAtr, maMode, priceType); class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { Print("EA is removed"); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class="type">int bars=iBars(_Symbol,PERIOD_CURRENT); if(barsTotal < bars) { barsTotal=bars; class="type">class="kw">double upper[], middle[], lower[]; CopyBuffer(keltner,class="num">0,class="num">0,class="num">3,upper); CopyBuffer(keltner,class="num">1,class="num">0,class="num">3,middle); CopyBuffer(keltner,class="num">2,class="num">0,class="num">3,lower); ArraySetAsSeries(upper,true); ArraySetAsSeries(middle,true); ArraySetAsSeries(lower,true); class="type">class="kw">double prevUpperValue = NormalizeDouble(upper[class="num">2], class="num">5); class="type">class="kw">double prevMiddleValue = NormalizeDouble(middle[class="num">2], class="num">5); class="type">class="kw">double prevLowerValue = NormalizeDouble(lower[class="num">2], class="num">5); class="type">class="kw">double upperValue = NormalizeDouble(upper[class="num">1], class="num">5); class="type">class="kw">double middleValue = NormalizeDouble(middle[class="num">1], class="num">5);
通道突破的进出场触发写法
这段逻辑只做一件事:判断价格是否刚穿越通道边界,然后按突破方向挂市价单。它比较的是前一根 K 线(索引 2)和上一根 K 线(索引 1)的收盘价与对应边界,避免用当前未收线价格误触发。 向上突破的条件是前收在通道上轨下方、昨收站上上轨,此时取卖价 ask 做多,止损设在 upperValue 减 slPips 个点、止盈设在 upperValue 加 tpPips 个点。向下突破则镜像处理,用买价 bid 做空,止损在下轨上方、止盈在下轨下方。 外汇与贵金属杠杆高,这种突破单在震荡市容易被上下轨来回扫损,实盘前务必在 MT5 策略测试器用至少 3 个月 Tick 数据回测,把 slPips / tpPips 调到与品种波动率匹配。
class="type">class="kw">double lowerValue = NormalizeDouble(lower[class="num">1], class="num">5); class="type">class="kw">double lastClose=iClose(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double prevLastClose=iClose(_Symbol,PERIOD_CURRENT,class="num">2); if(prevLastClose<prevUpperValue && lastClose>upperValue) { class="type">class="kw">double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); class="type">class="kw">double slVal = upperValue - slPips*_Point; class="type">class="kw">double tpVal = upperValue + tpPips*_Point; trade.Buy(lotSize,_Symbol,ask,slVal,tpVal); } if(prevLastClose>prevLowerValue && lastClose<lowerValue) { class="type">class="kw">double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); class="type">class="kw">double slVal = lowerValue + slPips*_Point; class="type">class="kw">double tpVal = lowerValue - tpPips*_Point; trade.Sell(lotSize,_Symbol,bid,upperValue,tpVal); } } } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 别急着下结论
肯特纳通道的两种基础玩法——区间反弹(触下轨买、触上轨卖)与区间突破(破上轨买、破下轨卖),在 XAUUSD、EURUSD、GBPUSD 上跑过回测后,优劣并没有一边倒。原测试文件里 Custom_Keltner_Channel.mq5 仅 4.29 KB,两个交易系统 mq5 各约 2.68 KB,说明逻辑很轻,但轻不等于能直接实盘。 外汇与贵金属杠杆高、滑点跳空频繁,这两种策略若不做通道周期与倍数优化,胜率倾向随品种波动特性漂移。文章给的只是打开思路的骨架,不是成品系统。 真要落地,先把自己 MT5 的回测里通道参数从默认 20/1.5 往 10/2.0 调一轮,看 EURUSD 的突破单是否还亏在震荡段。剩下的优化空间,得靠你自己的盘感和代码去填。