价格行为分析工具包开发(第七部分):信号脉冲智能交易系统(EA)·进阶篇
(2/3)· 当M15、M30、H1三线共振时才出手,这套MQL5多周期EA把虚假信号压到最低
「三周期布林带叠随机的入场箭头逻辑」
这段核心片段干的事很直接:先抓三个周期(Timeframe1/2/3)的布林带下轨 lower1~3、上轨 upper1~3,以及各自的随机指标 K 值 k1~k3;任意一项取数失败就 Print 报错并 return,不往下走。 close1~3 用 iClose 取当前柱收盘价,买信号要求是三个周期收盘价同时≤各自下轨,且 k1、k2、k3 全部 < 5(超卖);卖信号反过来,收盘价≥上轨且 K 值全 > 95(超买)。 箭头落盘前用 ArrowExists(close1) 挡重:同一价位区域已有箭头就不再画。买箭头 arrowcode=241 绿色宽2,卖箭头 arrowcode=242 红色,名字拼了 TimeCurrent() 做去重。 信号一旦成立,除了画对象,还顺手把时间、价格、方向塞进三个动态数组(signalTimes/Prices/BuySignals)留底。外汇与贵金属杠杆高,这种多周期共振只在概率上倾向过滤假突破,实盘请先在 MT5 策略测试器跑一遍。
Print("Error retrieving Bollinger Bands data."); class="kw">return; } if(!GetStochastic(Timeframe1, k1, d1) || !GetStochastic(Timeframe2, k2, d2) || !GetStochastic(Timeframe3, k3, d3)) { Print("Error retrieving Stochastic data."); class="kw">return; } class=class="str">"cmt">// Retrieve the close prices close1 = iClose(Symbol(), Timeframe1, class="num">0); close2 = iClose(Symbol(), Timeframe2, class="num">0); close3 = iClose(Symbol(), Timeframe3, class="num">0); class="type">bool buySignal = (close1 <= lower1 && close2 <= lower2 && close3 <= lower3) && (k1 < class="num">5 && k2 < class="num">5 && k3 < class="num">5); class=class="str">"cmt">// Oversold condition class="type">bool sellSignal = (close1 >= upper1 && close2 >= upper2 && close3 >= upper3) && (k1 > class="num">95 && k2 > class="num">95 && k3 > class="num">95); class=class="str">"cmt">// Overbought condition class=class="str">"cmt">// Check if an arrow already exists in the same region before placing a new one if(buySignal && !ArrowExists(close1)) { Print("Buy signal detected on all timeframes with Stochastic confirmation!"); class="type">class="kw">string arrowName = "BuySignal" + IntegerToString(TimeCurrent()); ObjectCreate(class="num">0, arrowName, OBJ_ARROW, class="num">0, TimeCurrent(), close1); ObjectSetInteger(class="num">0, arrowName, OBJPROP_ARROWCODE, class="num">241); ObjectSetInteger(class="num">0, arrowName, OBJPROP_COLOR, clrGreen); ObjectSetInteger(class="num">0, arrowName, OBJPROP_WIDTH, class="num">2); class=class="str">"cmt">// Store signal data ArrayResize(signalTimes, ArraySize(signalTimes) + class="num">1); ArrayResize(signalPrices, ArraySize(signalPrices) + class="num">1); ArrayResize(signalBuySignals, ArraySize(signalBuySignals) + class="num">1); signalTimes[ArraySize(signalTimes) - class="num">1] = TimeCurrent(); signalPrices[ArraySize(signalPrices) - class="num">1] = close1; signalBuySignals[ArraySize(signalBuySignals) - class="num">1] = true; } if(sellSignal && !ArrowExists(close1)) { Print("Sell signal detected on all timeframes with Stochastic confirmation!"); class="type">class="kw">string arrowName = "SellSignal" + IntegerToString(TimeCurrent()); ObjectCreate(class="num">0, arrowName, OBJ_ARROW, class="num">0, TimeCurrent(), close1); ObjectSetInteger(class="num">0, arrowName, OBJPROP_ARROWCODE, class="num">242); ObjectSetInteger(class="num">0, arrowName, OBJPROP_COLOR, clrRed);
信号落库与同区箭头去重的实现细节
信号触发后,先把箭头线宽设为 2,再把时间、价格、方向三个数组各扩容 1 个元素,用 ArraySize-1 下标写入当前时间、收盘价和 false 标记(此处为卖向信号)。这种动态扩容写法在 MT5 脚本里常见,但每来一个信号就 Resize 一次,高频 tick 下可能有轻微开销,实盘前建议在策略测试器里跑一轮看内存曲线。 ArrowExists 函数用 MathAbs(signalPrices[i] - price) <= MinArrowDistance 判断同价位区域是否已有箭头。MinArrowDistance 是外部定义的容差,若设为 0 则只在完全同价时拦截,设大一点(比如 10 点)就能避免密集成交区里箭头叠成一坨。 OnTick 只做一件事:调用 CheckAndGenerateSignal,逻辑极薄,方便你后续把信号函数换成别的指标组合。OnDeinit 里用 ObjectsTotal 倒序遍历,凡名字含 "Signal" 的对象全部删除,防止切换周期或重加载时旧箭头残留——外汇与贵金属波动剧烈,这类残留物容易误导肉眼判势,属高风险环境下的基础卫生。
ObjectSetInteger(class="num">0, arrowName, OBJPROP_WIDTH, class="num">2); class=class="str">"cmt">// Store signal data ArrayResize(signalTimes, ArraySize(signalTimes) + class="num">1); ArrayResize(signalPrices, ArraySize(signalPrices) + class="num">1); ArrayResize(signalBuySignals, ArraySize(signalBuySignals) + class="num">1); signalTimes[ArraySize(signalTimes) - class="num">1] = TimeCurrent(); signalPrices[ArraySize(signalPrices) - class="num">1] = close1; signalBuySignals[ArraySize(signalBuySignals) - class="num">1] = false; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check if an arrow already exists within the MinArrowDistance | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool ArrowExists(class="type">class="kw">double price) { for(class="type">int i = class="num">0; i < ArraySize(signalPrices); i++) { if(MathAbs(signalPrices[i] - price) <= MinArrowDistance) { class="kw">return true; class=class="str">"cmt">// Arrow exists in the same price region } } class="kw">return false; class=class="str">"cmt">// No arrow exists in the same region } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| OnTick Event | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { CheckAndGenerateSignal(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| OnDeinit Function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { class=class="str">"cmt">// Clean up the objects class="type">long chart_id = class="num">0; for(class="type">int i = ObjectsTotal(chart_id) - class="num">1; i >= class="num">0; i--) { class="type">class="kw">string name = ObjectName(chart_id, i); if(StringFind(name, "Signal") != -class="num">1) { ObjectDelete(chart_id, name); } } Print("Multitimeframe Bollinger-Stochastic Analyzer deinitialized."); } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 信号脉冲EA的模块拆解与触发阈值
这套EA把多周期布林带与随机震荡器拧成一条信号线,输入参数里写死了 M15、M30、H1 三个周期,布林带周期 20、偏差 2.0,随机%K 周期 14、%D 周期 3、Slowing 3。交易者直接改这几个数,就能把策略从激进调到保守。 SignalInfo 结构体配 signalTimes、signalPrices、signalBuySignals 三个动态数组,负责把每次信号的时间、价格、方向记下来。好处是图表上叠再多发散信号也不会乱,回测时也能逐条翻。 GetBollingerBands() 和 GetStochastic() 都是先建指标句柄、查错、再把轨值和%K/%D拷进数组。句柄创建失败就直接断流,不会拿空数据去跑逻辑——这点比很多开源EA粗糙的容错稳。 核心在 CheckAndGenerateSignal():它把三个周期的最新收盘价都取一遍。买入要同时满足价格≤布林下轨且随机%K<5;卖出要价格≥布林上轨且%K>95。阈值卡到 5 和 95,意味着只在极端超卖/超买才动手,盘整期基本不吐箭头。 ArrowExists() 拿新信号价和 signalPrices 里的旧价比距离,重叠就不画。OnTick 每次报价重算信号,OnDeinit 撤掉所有箭头对象。外汇和贵金属波动大、滑点狠,这种极端阈值也只是降低假信号概率,实盘仍属高风险。 下面这段是头部与输入参数的原码,逐行看: // 版权与链接信息,仅元数据,不影响逻辑 #property copyright "Christian Benjamin" // 作者主页链接 #property link "[MQL5官方文档] // EA版本号 #property version "1.00" // 三个可调周期:M15/M30/H1 input ENUM_TIMEFRAMES Timeframe1 = PERIOD_M15; input ENUM_TIMEFRAMES Timeframe2 = PERIOD_M30; input ENUM_TIMEFRAMES Timeframe3 = PERIOD_H1; // 布林周期20、偏差2.0 input int BB_Period = 20; input double BB_Deviation = 2.0; // 随机%K14、%D3、Slowing3 input int K_Period = 14; input int D_Period = 3; input int Slowing = 3; // 箭头偏移10点,避免贴着K线 input double SignalOffset = 10.0;
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| Signal Pulse EA.mq5 |</span> <span class="comment">class=class="str">"cmt">//| Copyright class="num">2024, MetaQuotes Ltd. |</span> <span class="comment">class=class="str">"cmt">//| [MQL5官方文档] |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">copyright</span> <span class="class="type">class="kw">string">"Christian Benjamin"</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">link</span> <span class="class="type">class="kw">string">"[MQL5官方文档] <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">version</span> <span class="class="type">class="kw">string">"class="num">1.00"</span> <span class="comment">class=class="str">"cmt">// Input parameters</span> <span class="keyword">input</span> <span class="macro">ENUM_TIMEFRAMES</span> Timeframe1 = <span class="macro">PERIOD_M15</span>; <span class="comment">class=class="str">"cmt">// M15 timeframe</span> <span class="keyword">input</span> <span class="macro">ENUM_TIMEFRAMES</span> Timeframe2 = <span class="macro">PERIOD_M30</span>; <span class="comment">class=class="str">"cmt">// M30 timeframe</span> <span class="keyword">input</span> <span class="macro">ENUM_TIMEFRAMES</span> Timeframe3 = <span class="macro">PERIOD_H1</span>; <span class="comment">class=class="str">"cmt">// H1 timeframe</span> <span class="keyword">input</span> <span class="keyword">class="type">int</span> BB_Period = <span class="number">class="num">20</span>; <span class="comment">class=class="str">"cmt">// Bollinger Bands period</span> <span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span> BB_Deviation = <span class="number">class="num">2.0</span>; <span class="comment">class=class="str">"cmt">// Bollinger Bands deviation</span> <span class="keyword">input</span> <span class="keyword">class="type">int</span> K_Period = <span class="number">class="num">14</span>; <span class="comment">class=class="str">"cmt">// Stochastic %K period</span> <span class="keyword">input</span> <span class="keyword">class="type">int</span> D_Period = <span class="number">class="num">3</span>; <span class="comment">class=class="str">"cmt">// Stochastic %D period</span> <span class="keyword">input</span> <span class="keyword">class="type">int</span> Slowing = <span class="number">class="num">3</span>; <span class="comment">class=class="str">"cmt">// Stochastic slowing</span> <span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span> SignalOffset = <span class="number">class="num">10.0</span>; <span class="comment">class=class="str">"cmt">// Offset in points for signal arrow</span>
「把布林与随机指标拉进同一函数」
多周期共振策略里,最烦的就是每个时间框架都手写一遍指标读取。这段代码的做法是用两个独立函数把布林带和随机指标分别封装,入参只给时间框架,出参用引用把数值带出来。 TestBars=10 意味着信号出现后只观察随后 10 根 K 线内的胜负;MinArrowDistance=5.0 则点明箭头间距至少 5 个点,避免视觉重叠。这两个 input 直接决定你回测时的判定窗口和图表干净度。 GetBollingerBands 里 iBands 取 BB_Period 和 BB_Deviation,CopyBuffer 分别拷 1/0/2 号缓冲得到上轨、中轨、下轨,任何一步失败就释放句柄返回 false。GetStochastic 同理,iStochastic 用 K_Period、D_Period、Slowing 三参数,拷 0 和 1 号缓冲拿 %K 与 %D。外汇与贵金属杠杆高,这类跨周期调用若周期选错可能频繁重绘,实盘前务必在 MT5 用不同品种跑一遍句柄返回。 下方代码逐行拆开看,能直接抄进 EA 的自定义模块里改周期用。
input class="type">int TestBars = class="num">10; class=class="str">"cmt">// Number of bars after signal to test win condition input class="type">class="kw">double MinArrowDistance = class="num">5.0; class=class="str">"cmt">// Minimum distance in points between arrows to avoid overlapping class=class="str">"cmt">// Signal tracking structure class="kw">struct SignalInfo { class="type">class="kw">datetime time; class="type">class="kw">double price; class="type">bool isBuySignal; }; class=class="str">"cmt">// Arrays to store signal information class="type">class="kw">datetime signalTimes[]; class="type">class="kw">double signalPrices[]; class="type">bool signalBuySignals[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Retrieve Bollinger Band Levels | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool GetBollingerBands(ENUM_TIMEFRAMES timeframe, class="type">class="kw">double &upper, class="type">class="kw">double &lower, class="type">class="kw">double &middle) { class="type">int handle = iBands(Symbol(), timeframe, BB_Period, class="num">0, BB_Deviation, PRICE_CLOSE); if(handle == INVALID_HANDLE) { Print("Error creating iBands for timeframe: ", timeframe); class="kw">return false; } class="type">class="kw">double upperBand[], middleBand[], lowerBand[]; if(!CopyBuffer(handle, class="num">1, class="num">0, class="num">1, upperBand) || !CopyBuffer(handle, class="num">0, class="num">0, class="num">1, middleBand) || !CopyBuffer(handle, class="num">2, class="num">0, class="num">1, lowerBand)) { Print("Error copying iBands buffer for timeframe: ", timeframe); IndicatorRelease(handle); class="kw">return false; } upper = upperBand[class="num">0]; middle = middleBand[class="num">0]; lower = lowerBand[class="num">0]; IndicatorRelease(handle); class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Retrieve Stochastic Levels | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool GetStochastic(ENUM_TIMEFRAMES timeframe, class="type">class="kw">double &k_value, class="type">class="kw">double &d_value) { class="type">int handle = iStochastic(Symbol(), timeframe, K_Period, D_Period, Slowing, MODE_SMA, STO_CLOSECLOSE); if(handle == INVALID_HANDLE) { Print("Error creating iStochastic for timeframe: ", timeframe); class="kw">return false; } class="type">class="kw">double kBuffer[], dBuffer[]; if(!CopyBuffer(handle, class="num">0, class="num">0, class="num">1, kBuffer) || class=class="str">"cmt">// %K line !CopyBuffer(handle, class="num">1, class="num">0, class="num">1, dBuffer)) { class=class="str">"cmt">// %D line Print("Error copying iStochastic buffer for timeframe: ", timeframe); IndicatorRelease(handle);
三周期布林带与随机指标共振的信号落地
把多周期数据收口后,真正产生交易提示的环节在 CheckAndGenerateSignal 里。它先并行抓取三个周期(Timeframe1/2/3)的布林带上下轨与中轨,以及随机指标的 K、D 值;任意一项取数失败就直接 Print 报错并退出,避免用脏数据画信号。 close 价格用 iClose 取当前柱收盘价,随后定义 buySignal:三个周期收盘价同时 ≤ 各自下轨,且三个周期的 K 值均 < 5,属于极端超卖共振;sellSignal 则反过来,收盘价 ≥ 上轨且 K 值全部 > 95。这两个阈值(5 与 95)是原文硬写死的,调参时可直接改数字观察触发频率变化。 信号不是无脑画:buySignal 成立还需 ArrowExists(close1) 为假,即在同价位区域没有旧箭头才允许新建。创建时用 OBJ_ARROW、箭头代码 241、绿色、宽度 2,并以 TimeCurrent 拼接名称防重名。 落地的同时把信号时间、价格、方向写进动态数组(ArrayResize 逐个加长),这部分结构让后续做统计或接小布分析有了原始流水。外汇与贵金属杠杆高,这类共振只表示概率倾斜,实盘前请在 MT5 策略测试器跑多周期样本核验触发密度。
class="type">void CheckAndGenerateSignal() { class="type">class="kw">double upper1, lower1, middle1, close1; class="type">class="kw">double upper2, lower2, middle2, close2; class="type">class="kw">double upper3, lower3, middle3, close3; class="type">class="kw">double k1, d1, k2, d2, k3, d3; if(!GetBollingerBands(Timeframe1, upper1, lower1, middle1) || !GetBollingerBands(Timeframe2, upper2, lower2, middle2) || !GetBollingerBands(Timeframe3, upper3, lower3, middle3)) { Print("Error retrieving Bollinger Bands data."); class="kw">return; } if(!GetStochastic(Timeframe1, k1, d1) || !GetStochastic(Timeframe2, k2, d2) || !GetStochastic(Timeframe3, k3, d3)) { Print("Error retrieving Stochastic data."); class="kw">return; } class=class="str">"cmt">// Retrieve the close prices close1 = iClose(Symbol(), Timeframe1, class="num">0); close2 = iClose(Symbol(), Timeframe2, class="num">0); close3 = iClose(Symbol(), Timeframe3, class="num">0); class="type">bool buySignal = (close1 <= lower1 && close2 <= lower2 && close3 <= lower3) && (k1 < class="num">5 && k2 < class="num">5 && k3 < class="num">5); class=class="str">"cmt">// Oversold condition class="type">bool sellSignal = (close1 >= upper1 && close2 >= upper2 && close3 >= upper3) && (k1 > class="num">95 && k2 > class="num">95 && k3 > class="num">95); class=class="str">"cmt">// Overbought condition class=class="str">"cmt">// Check if an arrow already exists in the same region before placing a new one if(buySignal && !ArrowExists(close1)) { Print("Buy signal detected on all timeframes with Stochastic confirmation!"); class="type">class="kw">string arrowName = "BuySignal" + IntegerToString(TimeCurrent()); ObjectCreate(class="num">0, arrowName, OBJ_ARROW, class="num">0, TimeCurrent(), close1); ObjectSetInteger(class="num">0, arrowName, OBJPROP_ARROWCODE, class="num">241); ObjectSetInteger(class="num">0, arrowName, OBJPROP_COLOR, clrGreen); ObjectSetInteger(class="num">0, arrowName, OBJPROP_WIDTH, class="num">2); class=class="str">"cmt">// Store signal data ArrayResize(signalTimes, ArraySize(signalTimes) + class="num">1); ArrayResize(signalPrices, ArraySize(signalPrices) + class="num">1); ArrayResize(signalBuySignals, ArraySize(signalBuySignals) + class="num">1); signalTimes[ArraySize(signalTimes) - class="num">1] = TimeCurrent();