EA指标条件搭建基础篇:把1500行代码拆库并接上三种信号源(基础篇)
(1/3)·当EA主文件膨胀到无法维护,如何用包含文件重构并让指标参与建仓判断
◍ 用指标给EA立交易规矩
在 MT5 里写 EA,最朴素的做法不是预测涨跌,而是先让指标替你划出能开仓的边界。比如用 iMA 拿均线值,再和当前 Bid 比大小,条件成立才进场,这样逻辑可回测、可审计。 原文示例里用一条 13 周期均线做过滤:价格站上均线才允许买,下穿才允许卖。这类写法在 2013 年的示例中被 4723 次查看、12 条讨论,说明基础指标条件仍是多数自建 EA 的起点。 外汇与贵金属杠杆高,指标条件只是概率过滤,不代表胜率有保障;真要上线,先在策略测试器用 2020—2023 年 XAUUSD 的 M15 跑一遍,看最大回撤再决定手数。
给EA挂上指标开关与执行模式
这一节要动刀的是前文那套超过 1500 行的 EA:不再只用固定条件开仓,而是接上指标值做建仓判断。我们在外部参数里塞一个下拉框,交易者能直接选三个交易指标里的任意一个,省得改代码重编译。 顺带把建仓逻辑拆出一层——新写一个函数专门查「这单现在能不能下」,原来的下单函数改成能认两种模式:即时执行和市场执行。MT5 里经纪商对贵金属和外汇的执行方式不一样,不区分就容易报 4756 之类的错。 原文提醒过,这套 EA 是在 MQL5 Cookbook 前几篇基础上改的,最新底包得去那篇讲交易历史和仓位属性函数库的文章里拿。代码体量上来后,继续堆在主文件里只会越来越难维护,所以这一节起手就把功能按类拆成独立库文件。 外汇与贵金属杠杆高、滑点跳空频繁,拆库和改执行模式只是降低程序风险,不预示任何收益。开 MT5 把前文 EA 加载好,先确认外部参数面板能不能看到新增的下拉项,再往下走。
「把条件判断拆进指标库」
做 MT5 EA 时,把主文件里越堆越长的指标判断挪到 Include 子目录下的 *.mqh 里,是避免单文件失控的直接办法。主文件同目录下放 Enums.mqh、TradeSignals.mqh 等七个头文件,用 #include "Include\XXX.mqh" 挂进来;同层引用只写文件名,回上层目录要在反斜杠前加两个点。 本例外部参数 Indicator 用枚举切换 MA / CCI / PCH 三个指标,IndicatorPeriod 默认 5 根柱。旧版 NumberOfBars 改名 IndicatorSegments,含义变成「指标须连续向上或向下满足开仓的柱数」。若给它赋 1,内部 AllowedNumberOfSegments 会被强制置 3——因为判买入至少要读最后三根已完成柱的指标值做比对。 取数前先写 CheckTradingPermission() 拦掉禁交易时段,返回 0 就等下一根柱再试。指标句柄用 iMA()、iCCI() 或自定义指标 iCustom() 拿,存进 indicator_handle;之后 GetDataIndicators() 里调 CopyBuffer() 按缓冲区索引抄值,标准指标索引查文档,自定义没源码就开可视化回测反推。 外汇与贵金属杠杆高,这类多指标 EA 在实盘可能出现滑点放大或执行拒单,参数未经压力测试前勿直接上真实账户。 市场执行模式早版不允许开仓同时挂止损止盈,build 803 之后才放开;OpenPosition() 里要按 ENUM_SYMBOL_PROPERTIES 里的执行模式分支处理。编译只从主 mq5 走,头文件改完别忘了回主文件点编译。
class="macro">#include "Include\Enums.mqh" class="macro">#include "Include\InfoPanel.mqh" class="macro">#include "Include\Errors.mqh" class="macro">#include "Include\TradeSignals.mqh" class="macro">#include "Include\TradeFunctions.mqh" class="macro">#include "Include\ToString.mqh" class="macro">#include "Include\Auxiliary.mqh" class="macro">#include "..\TestIndicatorConditions.mq5" class="macro">#include "Enums.mqh" class="macro">#include "InfoPanel.mqh" class="macro">#include "Errors.mqh" class="macro">#include "TradeSignals.mqh" class="macro">#include "ToString.mqh" class="macro">#include "Auxiliary.mqh" enum ENUM_INDICATORS { MA = class="num">0, class=class="str">"cmt">// 移动平均 CCI = class="num">1, class=class="str">"cmt">// CCI PCH = class="num">2 class=class="str">"cmt">// 价格通道 }; input ENUM_INDICATORS Indicator=MA; input class="type">int IndicatorPeriod=class="num">5;
◍ 参数边界与实时交易权限的硬约束
下面这段输入参数定义了网格类 EA 的基础骨架:单方向指标段数设为 2,初始手数 0.1,每段加仓 0.1 手,加仓步长 10 点,止损 50、止盈 100、跟踪止损 10,且默认开启仓位反转。这些数字直接决定风控阈值,在 MT5 里改一个值,回测曲线就会明显变形。 CorrectInputParameters() 做了一件事:当 AllowedNumberOfSegments 没手动设(≤0)时,按 IndicatorSegments 推导开仓所需最少 K 线数。段数 ≤1 时强制给 3 根,≥5 时封顶 5 根,中间档则取段数 +1。也就是说,系统倾向至少用 3 根柱确认结构,但不会因为段数多就无限放宽。 CheckTradingPermission() 则在实时模式先卡两道关:终端未连服务器直接返回 1,MQL5 层禁止交易返回 2,终端层再查一次权限。外汇与贵金属杠杆高,实盘前务必确认这两层都放行,否则策略在 demo 跑得顺、真账号却一单都发不出去。
input class="type">int IndicatorSegments=class="num">2; class=class="str">"cmt">// 一个方向上的指标段数 input class="type">class="kw">double Lot=class="num">0.1; class=class="str">"cmt">// 手数 input class="type">class="kw">double VolumeIncrease=class="num">0.1; class=class="str">"cmt">// 仓位交易量增加值 input class="type">class="kw">double VolumeIncreaseStep=class="num">10; class=class="str">"cmt">// 交易量增加步长 input class="type">class="kw">double StopLoss=class="num">50; class=class="str">"cmt">// 止损 input class="type">class="kw">double TakeProfit=class="num">100; class=class="str">"cmt">// 获利 input class="type">class="kw">double TrailingStop=class="num">10; class=class="str">"cmt">// 跟踪止损 input class="type">bool Reverse=true; class=class="str">"cmt">// 仓位反转 sinput class="type">bool ShowInfoPanel=true; class=class="str">"cmt">// 是否显示信息面板 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 调整输入参数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CorrectInputParameters() { class=class="str">"cmt">//--- 调整开启仓位条件中柱的数量 if(AllowedNumberOfSegments<=class="num">0) { if(IndicatorSegments<=class="num">1) AllowedNumberOfSegments=class="num">3; class=class="str">"cmt">// 至少需要3个柱 if(IndicatorSegments>=class="num">5) AllowedNumberOfSegments=class="num">5; class=class="str">"cmt">// 但是不能超过7 else AllowedNumberOfSegments=IndicatorSegments+class="num">1; class=class="str">"cmt">// 总是大于2 } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 检查是否允许交易 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CheckTradingPermission() { class=class="str">"cmt">//--- 对于实时模式 if(IsRealtime()) { class=class="str">"cmt">//--- 检查服务器连接 if(!TerminalInfoInteger(TERMINAL_CONNECTED)) class="kw">return(class="num">1); class=class="str">"cmt">//--- 在运行程序级别上的交易许可 if(!MQL5InfoInteger(MQL5_TRADE_ALLOWED)) class="kw">return(class="num">2); class=class="str">"cmt">//--- 终端级别上的交易许可
EA 启动前的交易权限与指标句柄校验
实盘跑 EA 前,先把终端和账户两层开关查清楚。下面这段在 OnInit 收尾处做硬拦截:终端禁止交易直接 return(3),账户禁止交易 return(4),账户禁 EA 自动交易 return(5);三层都过才走到 return(0) 完成初始化。外汇与贵金属杠杆高,这类权限漏检可能让策略在禁交易时段空转或报错。 指标句柄的获取按类型分流:MA 走 iMA(_Symbol,Period(),IndicatorPeriod,0,MODE_SMA,PRICE_CLOSE),CCI 走 iCCI 同参数仅少偏移项,PCH 则走 iCustom 加载本地 'MultiRange_PCH'。任一句柄拿到 INVALID_HANDLE 就 Print 报警,不抛异常但后续取数会全废。 取数函数 GetIndicatorsData 里,MA/CCI 分支先用 ArraySetAsSeries 把索引翻成 0 为最新,再用 CopyBuffer 一次性拉 AllowedNumberOfSegments 根 K 线。若实际复制条数不足该值,打印含品种、周期、错误码的明细并 return(false)——这意味着你设的 AllowedNumberOfSegments 若大于当前图表可用历史,EA 会安静地拿不到数据。
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) class="kw">return(class="num">3); class=class="str">"cmt">//--- 当前账户的交易许可 if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED)) class="kw">return(class="num">4); class=class="str">"cmt">//--- 当前账户的自动交易许可 if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT)) class="kw">return(class="num">5); } class=class="str">"cmt">//--- class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 取得指标句柄 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void GetIndicatorHandle() { class=class="str">"cmt">//--- 如果选择了移动平均指标 if(Indicator==MA) indicator_handle=iMA(_Symbol,Period(),IndicatorPeriod,class="num">0,MODE_SMA,PRICE_CLOSE); class=class="str">"cmt">//--- 如果选择了CCI指标 if(Indicator==CCI) indicator_handle=iCCI(_Symbol,Period(),IndicatorPeriod,PRICE_CLOSE); class=class="str">"cmt">//--- 如果选择了MultiRange_PCH指标 if(Indicator==PCH) indicator_handle=iCustom(_Symbol,Period(),"MultiRange_PCH",IndicatorPeriod); class=class="str">"cmt">//--- 如果指标句柄无法获得 if(indicator_handle==INVALID_HANDLE) Print("获取指标句柄失败!"); } class=class="str">"cmt">//--- 指标值数组 class="type">class="kw">double indicator_buffer1[]; class="type">class="kw">double indicator_buffer2[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 取得指标值 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool GetIndicatorsData() { class=class="str">"cmt">//--- 如果已经获得了指标句柄 if(indicator_handle!=INVALID_HANDLE) { class=class="str">"cmt">//--- 对于移动平均或者CCI指标 if(Indicator==MA || Indicator==CCI) { class=class="str">"cmt">//--- 反转索引顺序 (... class="num">3 class="num">2 class="num">1 class="num">0) ArraySetAsSeries(indicator_buffer1,true); class=class="str">"cmt">//--- 取得指标值 if(CopyBuffer(indicator_handle,class="num">0,class="num">0,AllowedNumberOfSegments,indicator_buffer1)<AllowedNumberOfSegments) { Print("复制数值失败 ("+ _Symbol+"; "+TimeframeToString(Period())+") (至 indicator_buffer1 数组)错误 ("+ IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); class="kw">return(class="kw">false); } } class=class="str">"cmt">//--- 对于 MultiRange_PCH 指标