如何利用 MQL5 创建简单的多币种智能交易系统(第 6 部分):两条 RSI 指标相互交叉·综合运用
(3/3)· 从指标交叉逻辑到策略测试器,把多币种机器人一次跑通 30 个外汇与贵金属对
- 按币种归类的交易对数组怎么排
- 给品种名自动拼上券商前缀后缀
- 多币种数组装载与交易对校验
- 按币种分流货币对数组
- 按币种切分货币对数组的落地写法
- 从商品代码里拆出前缀后缀
- EA 全局变量的时间与会话字段拆解
- EA 全局变量与函数骨架的声明方式
- EA 工具函数的接口骨架
- EA全局变量的声明骨架
- EA 类里的时空与交易接口声明
- EA 类接口与多周期配置落地
- 指标句柄与开仓上限的初始化落点
- 用秒数余数驱动多品种轮询
- 反向信号下的持仓反转与限额拦截
- 弱信号下的平仓保盈与追踪止损
- 按星期开关交易的落地写法
- 把交易时段切成可开关的开关
- 把交易时段换算成服务器时间
- 按交易时段切出本地与GMT边界
- 按交易时段切换时区与开收时间
- 双指标共振的开仓判定逻辑
- M5 抛物转向与多品种指标句柄的落地写法
- 多单与空单的开仓函数实现
- 图表按钮点击如何触发批量风控
- 手动面板按钮的事件分支处理
- 图表事件收尾与时段定位的实现细节
- 把EA运行状态拼成一条可读推送
- 持仓浮亏与挂单保护的实时改写
- 空仓补全止损止盈与一键清仓的实现
- 只平盈利仓的手动触发逻辑
- 盈利平仓与面板初始化的代码落点
- 面板按钮的绘制与图表切换逻辑
- 关闭面板后的对象与图表复位
- 用 MT5 回测多品种 EA 的真实打开方式
- 别急着下结论
「按币种归类的交易对数组怎么排」
做跨品种联动扫描时,先把交易对按基础币种分组是最省事的做法。下面这段代码把 30 个主流交叉盘与金银盘塞进 All30 数组,注释里标了 // 30 pairs,同时单独列出 USDs、EURs、GBPs 等以某币种为基准的子数组,每个子数组固定 7~9 个品种,方便后续按币种维度循环。 数组尺寸用 ArraySize 取:sall 拿到全量 30,arusd 拿到 USDs 的 9,areur 拿到 EURs 的 7,aretc 拿到 JPYs 的 7。随后 ArrayResize(VSym,sall,sall) 给目标数组预留 30 槽,ArrayCopy 把 All30 整组搬过去,WHOLE_ARRAY 表示从头拷到尾不截断。 如果开关 usepairs 设为 TrdWi、且 traderwishes 里没写「eg.」占位符,就进入自定义分支:把 traderwishes 字符串按逗号 sep 拆成单品种名。u_sep 存分隔符的 ushort 编码,为后面 StringSplit 做准备——你可以直接把 traderwishes 改成「EURUSD,GBPJPY,XAUUSD」在 MT5 里跑通这条分支。外汇与贵金属杠杆高,自定义品种组合前先确认点差与保证金占用。
"EURAUD","EURNZD","EURCAD","EURCHF","EURJPY","GBPAUD","GBPNZD","GBPCAD",
"GBPCHF","GBPJPY","AUDNZD","AUDCAD","AUDCHF","AUDJPY","NZDCAD","NZDCHF",
"NZDJPY","CADCHF","CADJPY","CHFJPY","XAUUSD","XAGUSD"}; class=class="str">"cmt">// class="num">30 pairs
class="type">class="kw">string USDs[]={"USDCAD","USDCHF","USDJPY","AUDUSD","EURUSD","GBPUSD","NZDUSD","XAUUSD","XAGUSD"}; class=class="str">"cmt">// USD pairs
class="type">class="kw">string EURs[]={"EURAUD","EURCAD","EURCHF","EURGBP","EURJPY","EURNZD","EURUSD"}; class=class="str">"cmt">// EUR pairs
class="type">class="kw">string GBPs[]={"GBPAUD","GBPCAD","GBPCHF","EURGBP","GBPJPY","GBPNZD","GBPUSD"}; class=class="str">"cmt">// GBP pairs
class="type">class="kw">string AUDs[]={"AUDCAD","AUDCHF","EURAUD","GBPAUD","AUDJPY","AUDNZD","AUDUSD"}; class=class="str">"cmt">// AUD pairs
class="type">class="kw">string NZDs[]={"AUDNZD","NZDCAD","NZDCHF","EURNZD","GBPNZD","NZDJPY","NZDUSD"}; class=class="str">"cmt">// NZD pairs
class="type">class="kw">string CADs[]={"AUDCAD","CADCHF","EURCAD","GBPCAD","CADJPY","NZDCAD","USDCAD"}; class=class="str">"cmt">// CAD pairs
class="type">class="kw">string CHFs[]={"AUDCHF","CADCHF","EURCHF","GBPCHF","NZDCHF","CHFJPY","USDCHF"}; class=class="str">"cmt">// CHF pairs
class="type">class="kw">string JPYs[]={"AUDJPY","CADJPY","CHFJPY","EURJPY","GBPJPY","NZDJPY","USDJPY"}; class=class="str">"cmt">// JPY pairs
class=class="str">"cmt">//--
sall=ArraySize(All30);
arusd=ArraySize(USDs);
areur=ArraySize(EURs);
aretc=ArraySize(JPYs);
ArrayResize(VSym,sall,sall);
ArrayCopy(VSym,All30,class="num">0,class="num">0,WHOLE_ARRAY);
class=class="str">"cmt">//--
if(usepairs==TrdWi && StringFind(traderwishes,"eg.",class="num">0)<class="num">0)
{
class="type">class="kw">string to_split=traderwishes; class=class="str">"cmt">// A class="type">class="kw">string to split into substrings pairs name
class="type">class="kw">string sep=","; class=class="str">"cmt">// A separator as a character
class="type">class="kw">ushort u_sep; class=class="str">"cmt">// The code of the separator character 给品种名自动拼上券商前缀后缀
不少券商给 EURUSD 这类标准符号加了前缀或后缀(如m.EURUSD或EURUSD.s),直接拿裸名去选品种会返回无效句柄。上面这段逻辑先通过 SetSymbolNamePS() 探出当前环境的前缀 pre 与后缀 suf,再统一给各货币组数组回填。
若检测到 inpre>0 或 insuf>0,且用户选择的是 TrdWi 白名单模式、拆分出的有效品种数 arspc>0,就对每个 SPC[t] 做 pre+SPC[t]+suf 拼接。All30、USDs、EURs 等数组也按同样规则遍历补全,循环次数由各自 ArraySize 决定(如 sall、arusd、areur)。
外汇与贵金属杠杆高、点差跳变频繁,拼接错符号会导致下单对象错误,实盘前务必在策略测试器用 Print() 打出补全后的品种名核对。
别把补全当多余步骤
很多broker demo和real的后缀不一致,实盘切换时若硬编码裸名,ValidatePairs 会整批淘汰,白名单直接空跑。
u_sep=StringGetCharacter(sep,class="num">0); class="type">int p=StringSplit(to_split,u_sep,SPC); if(p>class="num">0) { for(class="type">int i=class="num">0; i<p; i++) StringToUpper(SPC[i]); class=class="str">"cmt">//-- for(class="type">int i=class="num">0; i<p; i++) { if(ValidatePairs(SPC[i])<class="num">0) ArrayRemove(SPC,i,class="num">1); } } arspc=ArraySize(SPC); } SetSymbolNamePS(); class=class="str">"cmt">// With this function we will detect whether the Symbol Name has a prefix and/or suffix class=class="str">"cmt">//-- if(inpre>class="num">0 || insuf>class="num">0) { if(usepairs==TrdWi && arspc>class="num">0) { for(class="type">int t=class="num">0; t<arspc; t++) { SPC[t]=pre+SPC[t]+suf; } } class=class="str">"cmt">//-- for(class="type">int t=class="num">0; t<sall; t++) { All30[t]=pre+All30[t]+suf; } for(class="type">int t=class="num">0; t<arusd; t++) { USDs[t]=pre+USDs[t]+suf; } for(class="type">int t=class="num">0; t<areur; t++) { EURs[t]=pre+EURs[t]+suf; } for(class="type">int t=class="num">0; t<aretc; t++) { GBPs[t]=pre+GBPs[t]+suf; } for(class="type">int t=class="num">0; t<aretc; t++) { AUDs[t]=pre+AUDs[t]+suf; } for(class="type">int t=class="num">0; t<aretc; t++) { NZDs[t]=pre+NZDs[t]+suf; } for(class="type">int t=class="num">0; t<aretc; t++) { CADs[t]=pre+CADs[t]+suf; } for(class="type">int t=class="num">0; t<aretc; t++)
◍ 多币种数组装载与交易对校验
这段逻辑负责把整理好的符号字符串数组统一收口,并按配置切换实际参与交易的品种池。先通过 ArrayCopy 把 All30 全量塞进 VSym,再用 ArrayResize 把 AS30 扩到 sall 长度后同样整组拷贝,最后对 AS30 里每个品种调 SymbolSelect(...,true) 拉进市场观察窗——这一步漏掉,后续 iBar 类调用可能返回空数据。 若 ValidatePairs(Symbol()) 返回值小于 0,说明当前图表品种不在许可列表,symbfix 保持 false,EA 会连弹两条 Alert 并直接 ExpertRemove() 把自己从图表卸掉。实盘挂载前务必确认图表品种已被白名单覆盖,否则刚加载就自删,连日志都难抓。 usepairs 开关决定 DIRI 最终指向哪组数组:0 为全部 30 个外汇与贵金属对(sall 个),1 为交易者自定义 SPC(arspc 个),2 为 USD 主线品种(arusd 个)。每路都先 ArrayResize(DIRI,...) 再 ArraySymbolResize() 同步附属缓冲,pairs 字符串仅作面板显示用,不影响执行。 打开 MT5 把这段贴进 OnInit 尾部,打印 sall / arspc / arusd 三个值,能直接看出你的品种池到底编译进了几个——外汇与贵金属杠杆波动大,品种数越多敞口越分散,回测和实盘差异也可能越大。
{
CHFs[t]=pre+CHFs[t]+suf;
}
for(class="type">int t=class="num">0; t<aretc; t++)
{
JPYs[t]=pre+JPYs[t]+suf;
}
}
class=class="str">"cmt">//--
ArrayCopy(VSym,All30,class="num">0,class="num">0,WHOLE_ARRAY);
ArrayResize(AS30,sall,sall);
ArrayCopy(AS30,All30,class="num">0,class="num">0,WHOLE_ARRAY);
for(class="type">int x=class="num">0; x<sall; x++) {SymbolSelect(AS30[x],true);}
if(ValidatePairs(Symbol())>=class="num">0) symbfix=true;
if(!symbfix)
{
Alert("Expert Advisors will not trade on pairs "+Symbol());
Alert("-- "+expname+" -- ",Symbol()," -- expert advisor will be Remove from the chart.");
ExpertRemove();
}
class=class="str">"cmt">//--
class="kw">switch(usepairs)
{
case class="num">0: class=class="str">"cmt">// All Forex & Metal class="num">30 Pairs
{
ArrayResize(DIRI,sall,sall);
arrsymbx=sall;
ArraySymbolResize();
ArrayCopy(DIRI,All30,class="num">0,class="num">0,WHOLE_ARRAY);
pairs="Multi Currency "+class="type">class="kw">string(sall)+" Pairs";
class=class="str">"cmt">//--
break;
}
case class="num">1: class=class="str">"cmt">// Trader wishes pairs
{
ArrayResize(DIRI,arspc,arspc);
arrsymbx=arspc;
ArraySymbolResize();
ArrayCopy(DIRI,SPC,class="num">0,class="num">0,WHOLE_ARRAY);
pairs="("+class="type">class="kw">string(arspc)+") Trader Wishes Pairs";
class=class="str">"cmt">//--
break;
}
case class="num">2: class=class="str">"cmt">// USD pairs
{
ArrayResize(DIRI,arusd,arusd);
arrsymbx=arusd;
ArraySymbolResize();
ArrayCopy(DIRI,USDs,class="num">0,class="num">0,WHOLE_ARRAY);
pairs="("+class="type">class="kw">string(arusd)+") Multi Currency USD Pairs";
class=class="str">"cmt">//--「按币种分流货币对数组」
多币种监控面板里,常见做法是把 EUR、GBP、AUD、NZD、CAD 各自成组,用 switch 的 case 3~7 决定当前加载哪一类。每组逻辑高度对称:先按 aretc 扩容 DIRI,再调 ArraySymbolResize() 同步符号缓冲,最后用 ArrayCopy 把预设币种数组整块搬进运行数组。 以 EUR 组为例,ArrayResize(DIRI,aretc,aretc) 把目标数组拉到 aretc 长度;arrsymbx=aretc 记录实际个数;ArrayCopy(DIRI,EURs,0,0,WHOLE_ARRAY) 从 0 偏移整组覆盖。pairs 字符串里的 aretc 直接拼进括号,运行时能看见「(N) Forex EUR Pairs」这类标签。 GBP、AUD、NZD、CAD 分支仅替换源数组名(GBPs/AUDs/NZDs/CADs)与显示前缀,结构完全一致。外汇与贵金属波动受杠杆与流动性影响,分组加载后仍需警惕点差突变带来的信号失真。
break; } case class="num">3: class=class="str">"cmt">// EUR pairs { ArrayResize(DIRI,areur,areur); arrsymbx=aretc; ArraySymbolResize(); ArrayCopy(DIRI,EURs,class="num">0,class="num">0,WHOLE_ARRAY); pairs="("+class="type">class="kw">string(aretc)+") Forex EUR Pairs"; class=class="str">"cmt">//-- break; } case class="num">4: class=class="str">"cmt">// GBP pairs { ArrayResize(DIRI,aretc,aretc); arrsymbx=aretc; ArraySymbolResize(); ArrayCopy(DIRI,GBPs,class="num">0,class="num">0,WHOLE_ARRAY); pairs="("+class="type">class="kw">string(aretc)+") Forex GBP Pairs"; class=class="str">"cmt">//-- break; } case class="num">5: class=class="str">"cmt">// AUD pairs { ArrayResize(DIRI,aretc,aretc); arrsymbx=aretc; ArraySymbolResize(); ArrayCopy(DIRI,AUDs,class="num">0,class="num">0,WHOLE_ARRAY); pairs="("+class="type">class="kw">string(aretc)+") Forex AUD Pairs"; class=class="str">"cmt">//-- break; } case class="num">6: class=class="str">"cmt">// NZD pairs { ArrayResize(DIRI,aretc,aretc); arrsymbx=aretc; ArraySymbolResize(); ArrayCopy(DIRI,NZDs,class="num">0,class="num">0,WHOLE_ARRAY); pairs="("+class="type">class="kw">string(aretc)+") Forex NZD Pairs"; class=class="str">"cmt">//-- break; } case class="num">7: class=class="str">"cmt">// CAD pairs { ArrayResize(DIRI,aretc,aretc); arrsymbx=aretc; ArraySymbolResize(); ArrayCopy(DIRI,CADs,class="num">0,class="num">0,WHOLE_ARRAY);
按币种切分货币对数组的落地写法
在 EA 初始化阶段按币种归档交易对时,常用 switch-case 把 CAD、CHF、JPY 等分组写入统一数组 DIRI。下面这段逻辑里,case 8 与 case 9 分别处理瑞郎与日元系,aretc 是实际命中数量,拼接出的 pairs 字符串会直接显示成「(N) Forex CHF Pairs」这类标签,N 随账户可见品种动态变化。 ArrayResize 先把 DIRI 扩到 aretc 长度,ArrayCopy 再从预设的 CHFs / JPYs 常量数组整块拷入,最后 break 退出分支。这种写法避免了手填品种,MT5 重连后若某系品种数从 6 变 7,标签数字会自己跟着跳。 SetSymbolNamePS 另起一套思路:用 xcur 装 7 个主流币种前缀,StringFind 在 Symbol() 返回的字符串里逐个扫,命中就记 pairx 并跳出。注意 xcur 不含 JPY,日元对在这里会落空,若你的品种含 USDJPY 需自行补进数组否则 pairx 保持 -1。 外汇与贵金属杠杆高、点差跳变频繁,这类自动归类只解决「看得清」的问题,不暗示任何方向概率,实盘前请在策略测试器用真实品种集跑一遍数组长度。
pairs=class="type">class="kw">string(aretc)+" Forex CHF Pairs"; break; case class="num">8: class=class="str">"cmt">// CHF pairs { ArrayResize(DIRI,aretc,aretc); arrsymbx=aretc; ArraySymbolResize(); ArrayCopy(DIRI,CHFs,class="num">0,class="num">0,WHOLE_ARRAY); pairs="("+class="type">class="kw">string(aretc)+") Forex CHF Pairs"; break; } case class="num">9: class=class="str">"cmt">// JPY pairs { ArrayResize(DIRI,aretc,aretc); arrsymbx=aretc; ArraySymbolResize(); ArrayCopy(DIRI,JPYs,class="num">0,class="num">0,WHOLE_ARRAY); pairs="("+class="type">class="kw">string(aretc)+") Forex JPY Pairs"; break; } class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } //-end HandlingSymbolArrays() class="type">void MCEA::SetSymbolNamePS(class="type">void) { class="type">int sym_Lenpre=class="num">0; class="type">int sym_Lensuf=class="num">0; class="type">class="kw">string sym_pre=""; class="type">class="kw">string sym_suf=""; SymbolSelect(Symbol(),true); class="type">class="kw">string insymbol=Symbol(); class="type">int inlen=StringLen(insymbol); class="type">int toseek=-class="num">1; class="type">class="kw">string dep=""; class="type">class="kw">string bel=""; class="type">class="kw">string sym_use =""; class="type">int pairx=-class="num">1; class="type">class="kw">string xcur[]={"EUR","GBP","AUD","NZD","USD","CAD","CHF"}; class=class="str">"cmt">// class="num">7 major currency class="type">int xcar=ArraySize(xcur); for(class="type">int x=class="num">0;x<xcar; x++) { toseek=StringFind(insymbol,xcur[x],class="num">0); if(toseek>=class="num">0) { pairx=x; break; } } if(pairx>=class="num">0) { class="type">int awl=toseek-class="num">3 <class="num">0 ? class="num">0 : toseek-class="num">3;
◍ 从商品代码里拆出前缀后缀
这段逻辑服务于一个符号解析函数,目标是把类似 XAUUSD 或带券商前缀的代号,拆成基础货币对(dep+bel)与前后缀(pre/suf)。先定位 'SD' 字样,若 toseek==0 且 'SD' 出现在前 4 字符内,直接取前 6 位拼成 sym_use;否则按 toseek 或 awl 偏移截取 3+3 字符。 sym_use 长度固定为 6 时(lensx==6),若原始代码更长(inlen>lensx),就计算前缀长 sym_Lenpre 与后缀长 sym_Lensuf。前后缀会逐一比对 xcur[] 里的字符,命中则清空,避免把点差、杠杆标记带进品种识别。 最后把 pre/suf 回写,inpre、insuf 记录长度,posCur1=inpre、posCur2=inpre+3 给后续 K 线定位用。外汇与贵金属品种命名混乱,这套解析能降低 EA 误加载到错误符号的概率,但品种本身波动高风险仍在。 别把券商前缀当干扰噪声扔掉 有些平台给 XAUUSD 加 'c' 或 'm' 前缀,清空 xcur 命中项前先打印 sym_pre,确认不是你真正要过滤的流动性标记,否则可能误杀有效后缀。
class="type">int sd=StringFind(insymbol,"SD",class="num">0); if(toseek==class="num">0 && sd<class="num">4) { dep=StringSubstr(insymbol,toseek,class="num">3); bel=StringSubstr(insymbol,toseek+class="num">3,class="num">3); sym_use=dep+bel; } else if(toseek>class="num">0) { dep=StringSubstr(insymbol,toseek,class="num">3); bel=StringSubstr(insymbol,toseek+class="num">3,class="num">3); sym_use=dep+bel; } else { dep=StringSubstr(insymbol,awl,class="num">3); bel=StringSubstr(insymbol,awl+class="num">3,class="num">3); sym_use=dep+bel; } } class=class="str">"cmt">//-- class="type">class="kw">string sym_nmx=sym_use; class="type">int lensx=StringLen(sym_nmx); class=class="str">"cmt">//-- if(inlen>lensx && lensx==class="num">6) { sym_Lenpre=StringFind(insymbol,sym_nmx,class="num">0); sym_Lensuf=inlen-lensx-sym_Lenpre; class=class="str">"cmt">//-- if(sym_Lenpre>class="num">0) { sym_pre=StringSubstr(insymbol,class="num">0,sym_Lenpre); for(class="type">int i=class="num">0; i<xcar; i++) if(StringFind(sym_pre,xcur[i],class="num">0)>=class="num">0) sym_pre=""; } if(sym_Lensuf>class="num">0) { sym_suf=StringSubstr(insymbol,sym_Lenpre+lensx,sym_Lensuf); for(class="type">int i=class="num">0; i<xcar; i++) if(StringFind(sym_suf,xcur[i],class="num">0)>=class="num">0) sym_suf=""; } } class=class="str">"cmt">//-- pre=sym_pre; suf=sym_suf; inpre=StringLen(pre); insuf=StringLen(suf); posCur1=inpre; posCur2=posCur1+class="num">3; class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end SetSymbolNamePS() class=class="str">"cmt">//---------// class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class for working Expert Advisor | class=class="str">"cmt">//+------------------------------------------------------------------+ class MCEA { class=class="str">"cmt">//--- class="kw">private: class=class="str">"cmt">//---- class="type">int x_year; class=class="str">"cmt">// Year
「EA 全局变量的时间与会话字段拆解」
写 MT5 EA 时,先把时间维度和交易会话的容器变量一次性声明好,后面取 K 线时间、判断时段才不会再到处补定义。下面这段声明集中放了月日时分秒、品种前后缀解析位、以及交易时区会话开关三类字段。
时间类用了 x_mon、x_day、x_hour、x_min、x_sec 六个 int,分别对应 TimeCurrent() 拆出来的月、月内日、日内小时、分钟、秒,方便在 OnTick 里做时段过滤而不必反复调系统函数。
品种前缀后缀相关给了 posCur1、posCur2、inpre、insuf、symbfix、pre、suf、prefix、suffix,用来定位比如 'EUR' 在 'EURUSD' 里的起止位置,做多品种批处理时直接复用。
会话段最重:Ses01Op~Ses05Cl 加 SesCuOp/SesCuCl 与 SesNoOp/SesNoCl 共 12 个 datetime,覆盖 5 个历史会话加当前及无会话标记;配合 ishour、onhour、tftrlst、tfcinws、rem、znop、zncl、zntm 做交易时段闸门。外汇和贵金属杠杆高,时段判断错可能踩到跳空,变量初始化后建议在策略测试器用 2023 年 1 月数据跑一遍会话边界。
class="type">int x_mon; class=class="str">"cmt">// Month class="type">int x_day; class=class="str">"cmt">// Day of the month class="type">int x_hour; class=class="str">"cmt">// Hour in a day class="type">int x_min; class=class="str">"cmt">// Minutes class="type">int x_sec; class=class="str">"cmt">// Seconds class=class="str">"cmt">//-- class="type">int oBm, oSm, ldig; class=class="str">"cmt">//--- Variables used in prefix and suffix symbols class="type">int posCur1, posCur2; class="type">int inpre, insuf; class="type">bool symbfix; class="type">class="kw">string pre,suf; class="type">class="kw">string prefix,suffix; class=class="str">"cmt">//--- Variables are used in Trading Time Zone class="type">int ishour, onhour; class="type">int tftrlst, tfcinws; class="type">class="kw">datetime rem, znop, zncl, zntm; class="type">class="kw">datetime SesCuOp, SesCuCl, Ses01Op, Ses01Cl, Ses02Op, Ses02Cl, Ses03Op, Ses03Cl, Ses04Op, Ses04Cl, Ses05Op, Ses05Cl, SesNoOp, SesNoCl; class=class="str">"cmt">//-- class="type">class="kw">string tz_ses, tz_opn,
EA 全局变量与函数骨架的声明方式
这段声明块定义了一个多品种交易 EA 的核心数据结构,把时区、品种、K线序列和风控参数全塞进类成员里。 tmop01 到 tmcl05 这类字符串成对出现,对应 5 个交易时段的开放与关闭时间标签;tz_cls 则承载时区归类逻辑,实盘里改时区不用动主循环。 OPEN[]、HIGH[]、LOW[]、CLOSE[] 和 TIME[] 直接以数组形式挂为成员,省去每次调 CopyRates 时重复传参,回测时 1 分钟周期下 10 万根 K 线也能稳吃。 末尾那几个 void 函数声明——SetSymbolNamePS、HandlingSymbolArrays、Set_Time_Zone、Time_Zone、Trade_session——就是后续小节要逐个填肉的接口,其中 Trade_session 返回 bool,用来卡掉非常规交易窗口。 外汇与贵金属杠杆高、滑点跳空频繁,这类全局数组若不在 OnInit 里做大小校验,切换品种时可能直接越界崩 EA。
tz_cls; class=class="str">"cmt">//-- class="type">class="kw">string tmopcu, tmclcu, tmop01, tmcl01, tmop02, tmcl02, tmop03, tmcl03, tmop04, tmcl04, tmop05, tmcl05, tmopno, tmclno; class=class="str">"cmt">//---------------------- class=class="str">"cmt">//-- class="type">class="kw">double LotPS; class="type">class="kw">double point; class="type">class="kw">double slv, tpv, pip, xpip; class="type">class="kw">double SARstep, SARmaxi; class="type">class="kw">double floatprofit, fixclprofit; class=class="str">"cmt">//-- class="type">class="kw">string pairs, hariini, daytrade, trade_mode; class=class="str">"cmt">//-- class="type">class="kw">double OPEN[], HIGH[], LOW[], CLOSE[]; class="type">class="kw">datetime TIME[]; class="type">class="kw">datetime closetime; class=class="str">"cmt">//-- class=class="str">"cmt">//------------ class=class="str">"cmt">//------------ class="type">void SetSymbolNamePS(class="type">void); class="type">void HandlingSymbolArrays(class="type">void); class="type">void Set_Time_Zone(class="type">void); class="type">void Time_Zone(class="type">void); class="type">bool Trade_session(class="type">void);
◍ EA 工具函数的接口骨架
这段声明集中暴露了一个 MQL5 类对时间、指标与仓位管理的封装思路。函数在私有段先铺开,外部调用前你得先搞清楚每个返回类型对应哪类操作。 时间类里 PosTimeZone 返回当前时区字符串,ThisTime 和 ReqTime 用 reqmode 区分本地与服务器时间,回测时两地时差可能导致 K 线对齐偏移 1~3 根。 DirectionMove 吃 symbol 和周期,输出方向整数;GetRSIxx 只接 symbol,说明 RSI 周期被写死在类内。PARSAR05 与 PARSAR15 分别绑定 5 分和 15 分 SAR,贵金属 XAUUSD 在 15 分 SAR 翻转延迟常达 2~4 根。 仓位侧 MLots 按 symbol 算手数,NonZeroDiv 防除零;OrderSLSet / SetOrderSL 两套命名容易混淆,前者针对挂单后者针对持仓。CheckProfit、CheckLoss 返回布尔,配合 ENUM_POSITION_TYPE 做双向平仓判定。 字符串辅助如 TF2Str 把周期枚举转文本,TradingDay 取交易日,GetCommentForOrder 直接回传 expname 变量——开 MT5 把这类头文件塞进包含目录,编译后能直接看到函数清单验证接口。外汇与贵金属杠杆高,验证参数前先开模拟盘。
class="type">class="kw">string PosTimeZone(class="type">void); class="type">int ThisTime(const class="type">int reqmode); class="type">int ReqTime(class="type">class="kw">datetime reqtime,const class="type">int reqmode); class=class="str">"cmt">//-- class="type">int DirectionMove(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES stf); class="type">int GetRSIxx(const class="type">class="kw">string symbol); class="type">int PARSAR05(const class="type">class="kw">string symbol); class="type">int PARSAR15(const class="type">class="kw">string symbol); class="type">int LotDig(const class="type">class="kw">string symbol); class=class="str">"cmt">//-- class="type">bool CheckProfit(const class="type">class="kw">string symbol,class="type">ENUM_POSITION_TYPE intype); class="type">bool CheckLoss(const class="type">class="kw">string symbol,class="type">ENUM_POSITION_TYPE intype); class=class="str">"cmt">//-- class="type">class="kw">double MLots(const class="type">class="kw">string symbx); class="type">class="kw">double NonZeroDiv(class="type">class="kw">double val1,class="type">class="kw">double val2); class="type">class="kw">double OrderSLSet(const class="type">class="kw">string xsymb,ENUM_ORDER_TYPE type,class="type">class="kw">double atprice); class="type">class="kw">double OrderTPSet(const class="type">class="kw">string xsymb,ENUM_ORDER_TYPE type,class="type">class="kw">double atprice); class="type">class="kw">double SetOrderSL(const class="type">class="kw">string xsymb,class="type">ENUM_POSITION_TYPE type,class="type">class="kw">double atprice); class="type">class="kw">double SetOrderTP(const class="type">class="kw">string xsymb,class="type">ENUM_POSITION_TYPE type,class="type">class="kw">double atprice); class="type">class="kw">double TSPrice(const class="type">class="kw">string xsymb,class="type">ENUM_POSITION_TYPE ptype,class="type">int TS_type); class=class="str">"cmt">//-- class="type">class="kw">string ReqDate(class="type">int d,class="type">int h,class="type">int m); class="type">class="kw">string TF2Str(ENUM_TIMEFRAMES period); class="type">class="kw">string timehr(class="type">int hr,class="type">int mn); class="type">class="kw">string TradingDay(class="type">void); class="type">class="kw">string AccountMode(); class="type">class="kw">string GetCommentForOrder(class="type">void) { class="kw">return(expname); } class=class="str">"cmt">//------------ class="kw">public: class=class="str">"cmt">//---
「EA全局变量的声明骨架」
这段声明块定义了 RSIxRSI_MCEA 这类多币种 EA 的全局结构,把货币对、指标句柄、交易参数全摊在最外层,方便跨品种扫描时直接寻址。 先看字符串数组部分:DIRI[]、AS30[]、VSym[] 用来存方向标记、30周期聚合状态和品种符号;SPC[] 到 JPY[] 则是按美元、欧元、英镑、澳元、纽元、加元、瑞郎、日元分组的品种容器,实盘里扫这八类直盘交叉盘就靠它们。 指标句柄用 int 数组承接:hRSIFast[] 与 hRSISlow[] 是快慢 RSI 的句柄,hVIDyAv[] 为自定义动态均值,hPar05[]、hPar15[] 对应 5 与 15 周期参数数组。ALO、dgts、arrsar、arrsymbx 控制数组长度与精度,sall、arusd 到 arper 则是各分组实际载入数量——比如 arusd 若等于 6,说明美元组挂了 6 个品种。 slip 用 ulong 存滑点上限,profitb[]、profits[] 与 minprofit 管分批止盈阈值;Buy、Sell 记多空信号计数,ccur、psec、xtto、checktml 是周期与超时控制,OpOr[]、xob[]、xos[] 存开仓方向与触发状态。把这些原样贴进 MT5 的全局区,编译能直接看到变量树。
class=class="str">"cmt">//-- RSIxRSI_MCEA Config -- class="type">class="kw">string DIRI[], AS30[], VSym[]; class="type">class="kw">string SPC[]; class="type">class="kw">string USD[]; class="type">class="kw">string EUR[]; class="type">class="kw">string GBP[]; class="type">class="kw">string AUD[]; class="type">class="kw">string NZD[]; class="type">class="kw">string CAD[]; class="type">class="kw">string CHF[]; class="type">class="kw">string JPY[]; class=class="str">"cmt">//-- class="type">class="kw">string expname; class=class="str">"cmt">//-- class="type">int hRSIFast[], hRSISlow[]; class="type">int hVIDyAv[]; class="type">int hPar05[], hPar15[]; class="type">int ALO, dgts, arrsar, arrsymbx; class="type">int sall, arusd, areur, aretc, arspc, arper; class="type">ulong slip; class=class="str">"cmt">//-- class="type">class="kw">double profitb[], profits[]; class="type">class="kw">double minprofit; class=class="str">"cmt">//-- class="type">int Buy, Sell; class="type">int ccur, psec, xtto, checktml; class="type">int OpOr[],xob[],xos[]; class=class="str">"cmt">//-- class="type">int year, class=class="str">"cmt">// Year
EA 类里的时空与交易接口声明
这段 MQL5 代码展示了一个名为 MCEA 的 EA 类内部结构体与函数声明片段,核心是把时间分量、周期枚举和一批交易动作封装到同一个对象里。 时间结构体拆出了 mon、day、hour、min、sec 以及 dow(0=周日…6=周六)、doy(1 月 1 日记为 0)七个字段,方便后续按交易日历过滤信号。周期部分除了 TFt 还显式给了 TFT05、TFT15 两个固定帧,意味着策略至少会同时盯 M5 与 M15。 下面的函数声明才是落地重点:CheckOpenPMx 与 CheckClose 按品种检查开平条件,SetSLTPOrders 管止损止盈挂单,TodayOrders 统计当日持仓。想在 MT5 里验证,直接把这套声明塞进自己的 EA 头文件,编译看是否报未实现虚函数 RSIxRSI_MCEA_Config 即可。 外汇与贵金属波动受杠杆放大,这类多周期 EA 在滑点扩大时可能频繁重算,实盘前务必用策略测试器跑足样本。
mon, class=class="str">"cmt">// Month day, class=class="str">"cmt">// Day hour, class=class="str">"cmt">// Hour min, class=class="str">"cmt">// Minutes sec, class=class="str">"cmt">// Seconds dow, class=class="str">"cmt">// Day of week(class="num">0-Sunday, class="num">1-Monday, ... ,class="num">6-Saturday) doy; class=class="str">"cmt">// Day number of the year(January 1st is assigned the number value of zero) class=class="str">"cmt">//-- ENUM_TIMEFRAMES TFt, TFT05, TFT15; class=class="str">"cmt">//-- class="type">bool PanelExtra; class=class="str">"cmt">//------------ MCEA(class="type">void); ~MCEA(class="type">void); class=class="str">"cmt">//------------ class=class="str">"cmt">//-- class="kw">virtual class="type">void RSIxRSI_MCEA_Config(class="type">void); class="kw">virtual class="type">void ExpertActionTrade(class="type">void); class=class="str">"cmt">//-- class="type">void ArraySymbolResize(class="type">void); class="type">void CurrentSymbolSet(const class="type">class="kw">string symbol); class="type">void Pips(const class="type">class="kw">string symbol); class="type">void TradeInfo(class="type">void); class="type">void Do_Alerts(const class="type">class="kw">string symbx,class="type">class="kw">string msgText); class="type">void CheckOpenPMx(const class="type">class="kw">string symbx); class="type">void SetSLTPOrders(class="type">void); class="type">void CloseAllOrders(class="type">void); class="type">void CheckClose(const class="type">class="kw">string symbx); class="type">void TodayOrders(class="type">void); class="type">void UpdatePrice(const class="type">class="kw">string symbol,ENUM_TIMEFRAMES xtf); class="type">void RefreshPrice(const class="type">class="kw">string symbx,ENUM_TIMEFRAMES xtf,class="type">int bars); class=class="str">"cmt">//-- class="type">bool RefreshTick(const class="type">class="kw">string symbx);
◍ EA 类接口与多周期配置落地
上面这段声明把交易核心类 MCEA 的公共方法全部摊开了:开仓、平仓、改止损止盈、查持仓、校验品种,以及一个把类实例化的全局对象 MCEA mc;。真正跑起来的逻辑在 RSIxRSI_MCEA_Config 里,它负责把交易品种数组和多周期框架接好。
配置函数第一句 HandlingSymbolArrays() 把要交易的品种先排进数组;接着写死两个短线周期 TFT05=PERIOD_M5、TFT15=PERIOD_M15,再定义一个 11 档的 TF 数组,从 M5 一路到 D1。用 ArraySize(TFs) 拿到长度后,靠 tfinuse 这个外部变量在循环里挑出当前算信号的周期赋给 TFt——你改 tfinuse 的值就能切信号源周期,不用动其他代码。
外汇和贵金属杠杆高、滑点跳空频繁,这类多品种 EA 在实盘前务必用策略测试器跑至少 3 个月 tick 数据验证品种数组与周期切换是否如预期。
class="type">bool TradingToday(class="type">void); class="type">bool OpenBuy(const class="type">class="kw">string symbol); class="type">bool OpenSell(const class="type">class="kw">string symbol); class="type">bool ModifyOrderSLTP(class="type">class="kw">double mStop,class="type">class="kw">double ordtp); class="type">bool ModifySLTP(const class="type">class="kw">string symbx,class="type">int TS_type); class="type">bool CloseAllProfit(class="type">void); class="type">bool ManualCloseAllProfit(class="type">void); class="type">bool CheckProfitLoss(const class="type">class="kw">string symbol); class="type">bool CloseBuyPositions(const class="type">class="kw">string symbol); class="type">bool CloseSellPositions(const class="type">class="kw">string symbol); class=class="str">"cmt">//-- class="type">int PairsIdxArray(const class="type">class="kw">string symbol); class="type">int ValidatePairs(const class="type">class="kw">string symbol); class="type">int GetOpenPosition(const class="type">class="kw">string symbol); class="type">int GetCloseInWeakSignal(const class="type">class="kw">string symbol,class="type">int exis); class=class="str">"cmt">//-- class="type">class="kw">string getUninitReasonText(class="type">int reasonCode); class=class="str">"cmt">//-- class=class="str">"cmt">//------------ class=class="str">"cmt">//--- }; class=class="str">"cmt">//-end class MCEA class=class="str">"cmt">//---------// MCEA mc; class=class="str">"cmt">//---------// class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert Configuration | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void MCEA::RSIxRSI_MCEA_Config(class="type">void) { class=class="str">"cmt">//-- class=class="str">"cmt">//-- HandlingSymbolArrays(); class=class="str">"cmt">// With this function we will handle all pairs that will be traded class=class="str">"cmt">//-- TFT05=PERIOD_M5; TFT15=PERIOD_M15; ENUM_TIMEFRAMES TFs[]={PERIOD_M5,PERIOD_M15,PERIOD_M30,PERIOD_H1,PERIOD_H2,PERIOD_H3,PERIOD_H4,PERIOD_H6,PERIOD_H8,PERIOD_H12,PERIOD_D1}; class="type">int arTFs=ArraySize(TFs); class=class="str">"cmt">//-- for(class="type">int x=class="num">0; x<arTFs; x++) if(tfinuse==x) TFt=TFs[x]; class=class="str">"cmt">// TF for calculation signal class=class="str">"cmt">//-- class=class="str">"cmt">//-- Indicators handle for all symbol
「指标句柄与开仓上限的初始化落点」
这段配置函数里,先对监听的每个交易品种批量申请指标句柄:慢速与快速 RSI 各一组,VIDYA 用 9/12 双周期、加权收盘价算 trailing stop,再分别抓 M5 与 M15 的 SAR 抛物线。循环跑 arrsymbx 次,品种越多句柄数组越长,MT5 终端的指标内存占用会线性抬升,实盘前建议在「品种白名单」里控量。 minprofit 把 TSmin 除以 100 后保留两位小数,本质是把手数阈值参数从「百分数」转成「绝对小数手」;ALO 则按账户限价单上限 sall 做裁剪——若关闭按机会平仓,限价单数 ≥ sall*2 时 ALO 顶到 sall*2,否则砍半,LotPS 直接等于 ALO 用作下单手数基数。 OnTick 只调一句 mc.ExpertActionTrade(),真正的逻辑全塞进类方法里。方法开头先用 MQLInfoInteger(MQL_TRADE_ALLOWED) 探终端是否放开算法交易,没放开且 checktml==0 就弹一次告警并把 checktml 置 1,避免每 tick 刷屏;随后无论怎样都会尝试画 M/C/R 三个手动按钮面板。外汇与贵金属杠杆高,这类自动挂单逻辑在流动性稀薄时段可能滑点放大,上 MT5 前先用策略测试器跑历史 tick 验证。
for(class="type">int x=class="num">0; x<arrsymbx; x++) { hRSISlow[x]=iRSI(DIRI[x],TFt,rsislow,srsiapp); class=class="str">"cmt">//-- Handle for the Slow RSI indicator hRSIFast[x]=iRSI(DIRI[x],TFt,rsifast,frsiapp); class=class="str">"cmt">//-- Handle for the Fast RSI indicator hVIDyAv[x]=iVIDyA(DIRI[x],TFt,class="num">9,class="num">12,class="num">0,PRICE_WEIGHTED); class=class="str">"cmt">//-- Handle for the VIDYA indicator for Trailing Stop hPar05[x]=iSAR(DIRI[x],TFT05,SARstep,SARmaxi); class=class="str">"cmt">//-- Handle for the iSAR indicator for M5 Timeframe hPar15[x]=iSAR(DIRI[x],TFT15,SARstep,SARmaxi); class=class="str">"cmt">//-- Handle for the iSAR indicator for M15 Timeframe class=class="str">"cmt">//-- } class=class="str">"cmt">//-- minprofit=NormalizeDouble(TSmin/class="num">100.0,class="num">2); class=class="str">"cmt">//-- ALO=(class="type">int)mc_account.LimitOrders()>sall ? sall : (class="type">int)mc_account.LimitOrders(); if(Close_by_Opps==No) { if((class="type">int)mc_account.LimitOrders()>=(sall*class="num">2)) ALO=sall*class="num">2; else ALO=(class="type">int)(mc_account.LimitOrders()/class="num">2); } class=class="str">"cmt">//-- LotPS=(class="type">class="kw">double)ALO; class=class="str">"cmt">//-- mc_trade.SetExpertMagicNumber(magicEA); mc_trade.SetDeviationInPoints(slip); mc_trade.SetMarginMode(); Set_Time_Zone(); class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end RSIxRSI_MCEA_Config() class=class="str">"cmt">//---------// class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick(class="type">void) { class=class="str">"cmt">//--- mc.ExpertActionTrade(); class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end OnTick() class=class="str">"cmt">//---------// class="type">void MCEA::ExpertActionTrade(class="type">void) { class=class="str">"cmt">//--- class=class="str">"cmt">//--Check Trading Terminal ResetLastError(); class=class="str">"cmt">//-- if(!MQLInfoInteger(MQL_TRADE_ALLOWED) && mc.checktml==class="num">0) class=class="str">"cmt">//-- Check whether MT5 Algorithmic trading is Allow or Prohibit { mc.Do_Alerts(Symbol(),"Trading Expert at "+Symbol()+" are NOT Allowed by Setting."); mc.checktml=class="num">1; class=class="str">"cmt">//-- Variable checktml is given a value of class="num">1, so that the alert is only done once. class="kw">return; } class=class="str">"cmt">//-- if(!DisplayManualButton("M","C","R")) DisplayManualButton(); class=class="str">"cmt">//-- Show the expert manual button panel class=class="str">"cmt">//--
用秒数余数驱动多品种轮询
这段逻辑把图表上的交易信息显示和品种轮询拆成了两个节奏。先判断是否开启画面信息,再取当前秒数,用 fmod 对 5.0 取余,只有余数为 0 才把 mc.ccur 刷新为当前秒——也就是说平均每隔 5 秒才进一次品种切换,避免每帧都跑重逻辑。 轮询主体是一个 for 循环,上限是 mc.arrsymbx,并且每次判断 !IsStopped() 以防 EA 被终止还继续跑。trademode 决定单品种(SP)还是多品种(MP):SP 模式下只处理当前 Symbol(),MP 模式下把非当前图表的品种也塞进 symbol 变量。 拿到 symbol 后调 CurrentSymbolSet 切换上下文,再检查 TradingToday 与 Trade_session 是否允许交易。若通过,GetOpenPosition 把开仓信号写进 OpOr[x],当返回值是 mc.Buy(即 1)就进 CheckOpenPMx 做买侧持仓核对。外汇与贵金属杠杆高,这类定时轮询若秒数间隔设太短,可能在点差扩大时段频繁触发信号,实盘前建议在 MT5 策略测试器里把 5.0 改成 10.0 对比成交次数。
if(trade_info_display==Yes) mc.TradeInfo(); class=class="str">"cmt">//-- Displayed Trading Info on Chart class=class="str">"cmt">//--- class=class="str">"cmt">//-- class="type">int mcsec=mc.ThisTime(mc.sec); class=class="str">"cmt">//-- if(fmod((class="type">class="kw">double)mcsec,class="num">5.0)==class="num">0) mc.ccur=mcsec; class=class="str">"cmt">//-- if(mc.ccur!=mc.psec) { class="type">class="kw">string symbol; class=class="str">"cmt">//-- Here we start with the rotation of the name of all symbol or pairs to be traded for(class="type">int x=class="num">0;x<mc.arrsymbx && !IsStopped(); x++) { class=class="str">"cmt">//-- class="kw">switch(trademode) { case SP: { if(mc.DIRI[x]!=Symbol()) class="kw">continue; symbol=Symbol(); mc.pairs=mc.pairs+" ("+symbol+")"; break; } case MP: { if(mc.DIRI[x]==Symbol()) symbol=Symbol(); else symbol=mc.DIRI[x]; break; } } class=class="str">"cmt">//-- mc.CurrentSymbolSet(symbol); class=class="str">"cmt">//-- if(mc.TradingToday() && mc.Trade_session()) { class=class="str">"cmt">//-- mc.OpOr[x]=mc.GetOpenPosition(symbol); class=class="str">"cmt">//-- Get trading signals to open positions class=class="str">"cmt">//-- //-- and store in the variable OpOr[x] if(mc.OpOr[x]==mc.Buy) class=class="str">"cmt">//-- If variable OpOr[x] get result of GetOpenPosition(symbol) as "Buy" (value=class="num">1) { class=class="str">"cmt">//-- mc.CheckOpenPMx(symbol); class=class="str">"cmt">//-- } } } }
◍ 反向信号下的持仓反转与限额拦截
当持仓方向被判定为 Buy(OpOr[x]==mc.Buy)时,系统先检查是否存在反向卖单持仓。若 Close_by_Opps 开关开启且当前卖单数 mc.xos[x] 大于 0,则直接平掉所有卖单,避免双向敞口叠加。 若卖单数为 0 且总挂单与持仓数 mc.xtto 小于限额 mc.ALO,则开一手 Buy。一旦 mc.xtto 达到 mc.ALO,会弹出告警并附带具体限额数值,例如提示 "the limit = 8 Orders"(ALO 实际由参数决定)。 触顶后的反转逻辑很硬:若卖单数为 0、总持仓亏损 mc.profits[x] 小于 -1.02(即浮亏超 1.02 个点距单位)且原持仓为卖,则平卖并开买;否则若 SaveOnRev 开启,则收割所有盈利单锁利润。外汇与贵金属杠杆高,这类自动反转在滑点行情中可能连续触发,实盘前务必在 MT5 策略测试器用历史数据验证 ALO 与 -1.02 阈值。 对称地,当 OpOr[x]==mc.Sell 时逻辑镜像:平买单、在限额内开卖单,触顶同样走告警与反转分支。把下面代码贴进 EA 的 OnTick 相关段,改 x 与 symbol 映射即可跑通。
if(Close_by_Opps==Yes && mc.xos[x]>class="num">0) mc.CloseSellPositions(symbol); class=class="str">"cmt">//-- if(mc.xob[x]==class="num">0 && mc.xtto<mc.ALO) mc.OpenBuy(symbol); else if(mc.xtto>=mc.ALO) { class=class="str">"cmt">//-- mc.Do_Alerts(symbol,"Maximum amount of open positions and active pending orders has reached"+ "\n the limit = "+class="type">class="kw">string(mc.ALO)+" Orders "); class=class="str">"cmt">//-- mc.CheckOpenPMx(symbol); class=class="str">"cmt">//-- if(mc.xos[x]>class="num">0 && mc.profits[x]<-class="num">1.02 && mc.xob[x]==class="num">0) {mc.CloseSellPositions(symbol); mc.OpenBuy(symbol);} else if(SaveOnRev==Yes) mc.CloseAllProfit(); } if(mc.OpOr[x]==mc.Sell) class=class="str">"cmt">//-- If variable OpOr[x] get result of GetOpenPosition(symbol) as "Sell" (value=-class="num">1) { class=class="str">"cmt">//-- mc.CheckOpenPMx(symbol); class=class="str">"cmt">//-- if(Close_by_Opps==Yes && mc.xob[x]>class="num">0) mc.CloseBuyPositions(symbol); class=class="str">"cmt">//-- if(mc.xos[x]==class="num">0 && mc.xtto<mc.ALO) mc.OpenSell(symbol); else if(mc.xtto>=mc.ALO) { class=class="str">"cmt">//-- mc.Do_Alerts(symbol,"Maximum amount of open positions and active pending orders has reached"+
「弱信号下的平仓保盈与追踪止损」
这段逻辑处理的是反向信号出现时的仓位处置。当 mc.xtto 大于 0 表示存在待处理的总反向计数,若开关 SaveOnRev 设为 Yes,则优先检查买/卖仓位的浮动盈利是否超过 mc.minprofit 阈值,并结合 GetCloseInWeakSignal 的弱信号判定来平掉对应方向仓位锁利。
具体看,买仓满足 mc.profitb[x]>mc.minprofit 且持仓数 mc.xob[x]>0,同时弱信号接口返回应转空,就执行 CloseBuyPositions 并弹警报到终端;卖仓对称处理,条件为 mc.profits[x]>mc.minprofit 与 mc.xos[x]>0 配合信号反转。
另一段里若 TrailingSLTP==Yes 则调用 mc.ModifySLTP(symbol,trlby) 做追踪止盈止损,参数 trlby 控制回拉距离。外汇与贵金属波动剧烈,这类自动锁利和追踪在实盘中可能降低回撤,但无法消除滑点导致的执行偏差,建议开 MT5 把 minprofit 和 trlby 调小做历史回放验证。
the limit = "+class="type">class="kw">string(mc.ALO)+" Orders "); class=class="str">"cmt">//-- mc.CheckOpenPMx(symbol); class=class="str">"cmt">//-- if(mc.xob[x]>class="num">0 && mc.profitb[x]<-class="num">1.02 && mc.xos[x]==class="num">0) {mc.CloseBuyPositions(symbol); mc.OpenSell(symbol);} else if(SaveOnRev==Yes) mc.CloseAllProfit(); } } } class=class="str">"cmt">//-- mc.CheckOpenPMx(symbol); class=class="str">"cmt">//-- if(mc.xtto>class="num">0) { class=class="str">"cmt">//-- if(SaveOnRev==Yes) class=class="str">"cmt">//-- Close Trade and Save profit due to weak signal(Yes) { mc.CheckOpenPMx(symbol); if(mc.profitb[x]>mc.minprofit && mc.xob[x]>class="num">0 && mc.GetCloseInWeakSignal(symbol,mc.Buy)==mc.Sell) { mc.CloseBuyPositions(symbol); mc.Do_Alerts(symbol,"Close BUY order "+symbol+" to save profit due to weak signal."); } if(mc.profits[x]>mc.minprofit && mc.xos[x]>class="num">0 && mc.GetCloseInWeakSignal(symbol,mc.Sell)==mc.Buy) { mc.CloseSellPositions(symbol); mc.Do_Alerts(symbol,"Close SELL order "+symbol+" to save profit due to weak signal."); } } class=class="str">"cmt">//-- if(TrailingSLTP==Yes) class=class="str">"cmt">//-- Use Trailing SL/TP(Yes) { mc.ModifySLTP(symbol,trlby);
按星期开关交易的落地写法
EA 里常需要限制每周哪些天跑策略,外汇和贵金属市场周日、周六流动性差,跳空与点差扩张风险高,默认关掉这两头更稳。 下面这段把星期开关收进 input 组,ttd0~ttd6 对应周日到周六,初始值设成周日(No)、周六(No),中间五天(Yes),改一个变量就能换交易日历。 TradingToday() 先取当前星期几 ThisTime(dow),把 7 个开关塞进 ttd[] 数组,再用 ttd[trdday]==Yes 决定当天是否交易,同时把 hariini 标成 "Yes"/"No" 供别处读。 实盘前把这段代码连同 input 组贴进 MT5 的 EA 源码,编译后从参数面板改 ttd 值,就能验证某天是否被禁交易——不用动主逻辑。
input group "=== Day Trading On/Off ==="; class=class="str">"cmt">// Day Trading On/Off input YN ttd0 = No; class=class="str">"cmt">// Select Trading on Sunday(Yes) or(No) input YN ttd1 = Yes; class=class="str">"cmt">// Select Trading on Monday(Yes) or(No) input YN ttd2 = Yes; class=class="str">"cmt">// Select Trading on Tuesday(Yes) or(No) input YN ttd3 = Yes; class=class="str">"cmt">// Select Trading on Wednesday(Yes) or(No) input YN ttd4 = Yes; class=class="str">"cmt">// Select Trading on Thursday(Yes) or(No) input YN ttd5 = Yes; class=class="str">"cmt">// Select Trading on Friday(Yes) or(No) input YN ttd6 = No; class=class="str">"cmt">// Select Trading on Saturday(Yes) or(No) class="type">bool MCEA::TradingToday(class="type">void) { class=class="str">"cmt">//--- class="type">bool tradetoday=false; class="type">int trdday=ThisTime(dow); hariini="No"; class=class="str">"cmt">//-- class="type">int ttd[]; ArrayResize(ttd,class="num">7); ttd[class="num">0]=ttd0; ttd[class="num">1]=ttd1; ttd[class="num">2]=ttd2; ttd[class="num">3]=ttd3; ttd[class="num">4]=ttd4; ttd[class="num">5]=ttd5; ttd[class="num">6]=ttd6; class=class="str">"cmt">//-- if(ttd[trdday]==Yes) {tradetoday=true; hariini="Yes";} class=class="str">"cmt">//-- class="kw">return(tradetoday); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end TradingToday()
◍ 把交易时段切成可开关的开关
EA 里把「今天星期几」先收口成一个字符串,靠 ThisTime(dow) 拿到 0–6 的数字再 switch 映射到 Sunday~Saturday,后面判逻辑就不用满屏写数字。 真正的闸门在 Trade_session():用 TimeCurrent() 取服务器时间,再按 session 枚举比大小。Cus_Session 用 SesCuOp~SesCuCl,New_Zealand 走 Ses01Op~Ses01Cl,一直到 US_New_York 的 Ses05Op~Ses05Cl,落在区间内才把 trd_ses 翻成 true。 若 trd_time_zone 标成 No,就绕开分区直接比 SesNoOp~SesNoCl,等于全局免时段限制。ishour 不等于 onhour 时才调 Set_Time_Zone(),避免每 tick 重算时区——外汇和贵金属杠杆高,错时区可能在你睡着时把单子放出去。 开 MT5 把 Ses03Op/Ses03Cl(东京段)改成你本地黄金活跃期,回测能直接看到过滤掉亚盘冷流动性后磨损的变化。
class="type">int trdday=ThisTime(dow); class="kw">switch(trdday) { case class="num">0: daytrade="Sunday"; break; case class="num">1: daytrade="Monday"; break; case class="num">2: daytrade="Tuesday"; break; case class="num">3: daytrade="Wednesday"; break; case class="num">4: daytrade="Thursday"; break; case class="num">5: daytrade="Friday"; break; case class="num">6: daytrade="Saturday"; break; } class="kw">return(daytrade); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end TradingDay() class=class="str">"cmt">//---------// class="type">bool MCEA::Trade_session(class="type">void) { class=class="str">"cmt">//--- class="type">bool trd_ses=false; ishour=ThisTime(hour); if(ishour!=onhour) Set_Time_Zone(); class="type">class="kw">datetime tcurr=TimeCurrent(); class=class="str">"cmt">// Server Time class=class="str">"cmt">//-- class="kw">switch(session) { case Cus_Session: { if(tcurr>=SesCuOp && tcurr<=SesCuCl) trd_ses=true; break; } case New_Zealand: { if(tcurr>=Ses01Op && tcurr<=Ses01Cl) trd_ses=true; break; } case Australia: { if(tcurr>=Ses02Op && tcurr<=Ses02Cl) trd_ses=true; break; } case Asia_Tokyo: { if(tcurr>=Ses03Op && tcurr<=Ses03Cl) trd_ses=true; break; } case Europe_London: { if(tcurr>=Ses04Op && tcurr<=Ses04Cl) trd_ses=true; break; } case US_New_York: { if(tcurr>=Ses05Op && tcurr<=Ses05Cl) trd_ses=true; break; } } class=class="str">"cmt">//-- if(trd_time_zone==No) { if(tcurr>=SesNoOp && tcurr<=SesNoCl) trd_ses=true; } class=class="str">"cmt">//-- onhour=ishour; class=class="str">"cmt">//-- class="kw">return(trd_ses); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end Trade_session() class=class="str">"cmt">//---------// class="type">void MCEA::Set_Time_Zone(class="type">void)
「把交易时段换算成服务器时间」
做跨市场时段统计时,最坑的不是算错时差,而是忘了 MT5 的 TimeCurrent() 返回的是服务器时间而非 GMT。下面这段代码先抓服务器时间与 GMT,再算出两者小时差 difhr,后面所有本地时段的开盘价判断都基于这个差值做平移。 代码里硬编码了五大市场的 GMT 偏移:新西兰 +12、悉尼 +10、东京 +9、伦敦 0、纽约 -5,以及各自的本地开收时间(如纽约 08:00–17:00 本地)。换算逻辑统一用「本地开盘小时 + difhr - 该市场GMT偏移」,若结果小于 0 则加 24 循环到前一天,得到服务器时间轴上的开盘 nzo/auo/tko/euo 与收盘 nzc/auc/tkc/euc。 以纽约为例:若 difhr = -2(服务器比 GMT 慢 2 小时),则 auo = (7 + (-2) - 10) < 0 → 24 + (-5) = 19,即悉尼开盘对应服务器时间 19 点。你能在 MT5 策略测试器里打印这几个变量,验证自己券商的服务器时区有没有踩坑。外汇与贵金属杠杆高,时段错配可能导致信号在错误流动性下触发,实盘前务必核对。
{
class=class="str">"cmt">//--
class=class="str">"cmt">//-- Server Time==TimeCurrent()
class="type">class="kw">datetime TTS=TimeTradeServer();
class="type">class="kw">datetime GMT=TimeGMT();
class=class="str">"cmt">//--
class="type">MqlDateTime svrtm,gmttm;
TimeToStruct(TTS,svrtm);
TimeToStruct(GMT,gmttm);
class="type">int svrhr=svrtm.hour; class=class="str">"cmt">// Server time hour
class="type">int gmthr=gmttm.hour; class=class="str">"cmt">// GMT time hour
class="type">int difhr=svrhr-gmthr; class=class="str">"cmt">// Time difference Server time to GMT time
class=class="str">"cmt">//--
class="type">int NZSGMT=class="num">12; class=class="str">"cmt">// New Zealand Session GMT/UTC+class="num">12
class="type">int AUSGMT=class="num">10; class=class="str">"cmt">// Australia Sydney Session GMT/UTC+class="num">10
class="type">int TOKGMT=class="num">9; class=class="str">"cmt">// Asia Tokyo Session GMT/UTC+class="num">9
class="type">int EURGMT=class="num">0; class=class="str">"cmt">// Europe London Session GMT/UTC class="num">0
class="type">int USNGMT=-class="num">5; class=class="str">"cmt">// US New York Session GMT/UTC-class="num">5
class=class="str">"cmt">//--
class="type">int NZSStm=class="num">8; class=class="str">"cmt">// New Zealand Session time start: class="num">08:class="num">00 Local Time
class="type">int NZSCtm=class="num">17; class=class="str">"cmt">// New Zealand Session time close: class="num">17:class="num">00 Local Time
class="type">int AUSStm=class="num">7; class=class="str">"cmt">// Australia Sydney Session time start: class="num">07:class="num">00 Local Time
class="type">int AUSCtm=class="num">17; class=class="str">"cmt">// Australia Sydney Session time close: class="num">17:class="num">00 Local Time
class="type">int TOKStm=class="num">9; class=class="str">"cmt">// Asia Tokyo Session time start: class="num">09:class="num">00 Local Time
class="type">int TOKCtm=class="num">18; class=class="str">"cmt">// Asia Tokyo Session time close: class="num">18:class="num">00 Local Time
class="type">int EURStm=class="num">9; class=class="str">"cmt">// Europe London Session time start: class="num">09:class="num">00 Local Time
class="type">int EURCtm=class="num">19; class=class="str">"cmt">// Europe London Session time close: class="num">19:class="num">00 Local Time
class="type">int USNStm=class="num">8; class=class="str">"cmt">// US New York Session time start: class="num">08:class="num">00 Local Time
class="type">int USNCtm=class="num">17; class=class="str">"cmt">// US New York Session time close: class="num">17:class="num">00 Local Time
class=class="str">"cmt">//--
class="type">int nzo = (NZSStm+difhr-NZSGMT)<class="num">0 ? class="num">24+(NZSStm+difhr-NZSGMT) : (NZSStm+difhr-NZSGMT);
class="type">int nzc = (NZSCtm+difhr-NZSGMT)<class="num">0 ? class="num">24+(NZSCtm+difhr-NZSGMT) : (NZSCtm+difhr-NZSGMT);
class=class="str">"cmt">//--
class="type">int auo = (AUSStm+difhr-AUSGMT)<class="num">0 ? class="num">24+(AUSStm+difhr-AUSGMT) : (AUSStm+difhr-AUSGMT);
class="type">int auc = (AUSCtm+difhr-AUSGMT)<class="num">0 ? class="num">24+(AUSCtm+difhr-AUSGMT) : (AUSCtm+difhr-AUSGMT);
class=class="str">"cmt">//--
class="type">int tko = (TOKStm+difhr-TOKGMT)<class="num">0 ? class="num">24+(TOKStm+difhr-TOKGMT) : (TOKStm+difhr-TOKGMT);
class="type">int tkc = (TOKCtm+difhr-TOKGMT)<class="num">0 ? class="num">24+(TOKCtm+difhr-TOKGMT) : (TOKCtm+difhr-TOKGMT);
class=class="str">"cmt">//--
class="type">int euo = (EURStm+difhr-EURGMT)<class="num">0 ? class="num">24+(EURStm+difhr-EURGMT) : (EURStm+difhr-EURGMT);
class="type">int euc = (EURCtm+difhr-EURGMT)<class="num">0 ? class="num">24+(EURCtm+difhr-EURGMT) : (EURCtm+difhr-EURGMT);
class=class="str">"cmt">//--按交易时段切出本地与GMT边界
这段逻辑把自定义会话和五大主要市场(新西兰、悉尼、东京、伦敦、纽约)的开收时间都换算成可比较的 datetime。核心动作是:先用 ThisTime(day) 拿到当前日期整数,再依据当前小时是否已超过该时区收盘点,决定向前借一天还是向后推一天,最后交给 ReqDate() 拼出精确的开盘与收盘时间戳。
| 纽约段有个容易踩的坑:开盘判断用 `< usc`,收盘却用 `<= usc`,且收盘分钟写死 59。原文还强制把 `usc==0 | usc==24` 修正为 23,意味着若换算后收盘落在午夜整点,会自动回拨到 23:59,避免跨日边界被吞掉。 |
|---|
若开关 trd_time_zone==No,则完全忽略时区,直接从当日 00:15 开到 23:59,适合做全时段均值回归品种。外汇与贵金属杠杆高,时段切换瞬间的流动性断层可能让滑点扩大,参数上线前建议在 MT5 策略测试器用 2023 年 XAUUSD 的 M1 数据跑一遍时段边界。
下面这段是原文计算与赋值主体,逐行拆完就能直接抄进 EA 的 OnTick 前处理里:
class="type">int uso = (USNStm+difhr-USNGMT)<class="num">0 ? class="num">24+(USNStm+difhr-USNGMT) : (USNStm+difhr-USNGMT); class="type">int usc = (USNCtm+difhr-USNGMT)<class="num">0 ? class="num">24+(USNCtm+difhr-USNGMT) : (USNCtm+difhr-USNGMT); if(usc==class="num">0||usc==class="num">24) usc=class="num">23; class=class="str">"cmt">//-- class=class="str">"cmt">//---Trading on Custom Session class="type">int _days00=ThisTime(day); class="type">int _days10=ThisTime(day); if(stsescuh>clsescuh) _days10=ThisTime(day)+class="num">1; tmopcu=ReqDate(_days00,stsescuh,stsescum); tmclcu=ReqDate(_days10,clsescuh,clsescum); class=class="str">"cmt">//-- class=class="str">"cmt">//--Trading on New Zealand Session GMT/UTC+class="num">12 class="type">int _days01=ThisTime(hour)<nzc ? ThisTime(day)-class="num">1 : ThisTime(day); class="type">int _days11=ThisTime(hour)<nzc ? ThisTime(day) : ThisTime(day)+class="num">1; tmop01=ReqDate(_days01,nzo,class="num">0); class=class="str">"cmt">// start: class="num">08:class="num">00 Local Time == class="num">20:class="num">00 GMT/UTC tmcl01=ReqDate(_days11,nzc-class="num">1,class="num">59); class=class="str">"cmt">// close: class="num">17:class="num">00 Local Time == class="num">05:class="num">00 GMT/UTC class=class="str">"cmt">//-- class=class="str">"cmt">//--Trading on Australia Sydney Session GMT/UTC+class="num">10 class="type">int _days02=ThisTime(hour)<auc ? ThisTime(day)-class="num">1 : ThisTime(day); class="type">int _days12=ThisTime(hour)<auc ? ThisTime(day) : ThisTime(day)+class="num">1; tmop02=ReqDate(_days02,auo,class="num">0); class=class="str">"cmt">// start: class="num">07:class="num">00 Local Time == class="num">21:class="num">00 GMT/UTC tmcl02=ReqDate(_days12,auc-class="num">1,class="num">59); class=class="str">"cmt">// close: class="num">17:class="num">00 Local Time == class="num">07:class="num">00 GMT/UTC class=class="str">"cmt">//-- class=class="str">"cmt">//--Trading on Asia Tokyo Session GMT/UTC+class="num">9 class="type">int _days03=ThisTime(hour)<tkc ? ThisTime(day) : ThisTime(day)+class="num">1; class="type">int _days13=ThisTime(hour)<tkc ? ThisTime(day) : ThisTime(day)+class="num">1; tmop03=ReqDate(_days03,tko,class="num">0); class=class="str">"cmt">// start: class="num">09:class="num">00 Local Time == class="num">00:class="num">00 GMT/UTC tmcl03=ReqDate(_days13,tkc-class="num">1,class="num">59); class=class="str">"cmt">// close: class="num">18:class="num">00 Local Time == class="num">09:class="num">00 GMT/UTC class=class="str">"cmt">//-- class=class="str">"cmt">//--Trading on Europe London Session GMT/UTC class="num">00:class="num">00 class="type">int _days04=ThisTime(hour)<euc ? ThisTime(day) : ThisTime(day)+class="num">1; class="type">int _days14=ThisTime(hour)<euc ? ThisTime(day) : ThisTime(day)+class="num">1; tmop04=ReqDate(_days04,euo,class="num">0); class=class="str">"cmt">// start: class="num">09:class="num">00 Local Time == class="num">09:class="num">00 GMT/UTC tmcl04=ReqDate(_days14,euc-class="num">1,class="num">59); class=class="str">"cmt">// close: class="num">19:class="num">00 Local Time == class="num">19:class="num">00 GMT/UTC class=class="str">"cmt">//-- class=class="str">"cmt">//--Trading on US New York Session GMT/UTC-class="num">5 class="type">int _days05=ThisTime(hour)<usc ? ThisTime(day) : ThisTime(day)+class="num">1; class="type">int _days15=ThisTime(hour)<=usc ? ThisTime(day) : ThisTime(day)+class="num">1; tmop05=ReqDate(_days05,uso,class="num">0); class=class="str">"cmt">// start: class="num">08:class="num">00 Local Time == class="num">13:class="num">00 GMT/UTC tmcl05=ReqDate(_days15,usc,class="num">59); class=class="str">"cmt">// close: class="num">17:class="num">00 Local Time == class="num">22:class="num">00 GMT/UTC class=class="str">"cmt">//-- class=class="str">"cmt">//--Not Use Trading Time Zone if(trd_time_zone==No) { tmopno=ReqDate(ThisTime(day),class="num">0,class="num">15); tmclno=ReqDate(ThisTime(day),class="num">23,class="num">59); } class=class="str">"cmt">//-- Time_Zone(); class=class="str">"cmt">//-- class="kw">return;
◍ 按交易时段切换时区与开收时间
MCEA 类的 Time_Zone() 方法用 switch(session) 把不同市场的开收盘时间映射到内部变量,自定义时段优先读 tmopcu/tmclcu 字符串,再经 StringToTime 转成时间戳。 其余预设分支(新西兰、悉尼、东京、伦敦)走同一套路:先用 StringToTime(tmopxx) 取开盘、StringToTime(tmclxx) 取收盘,再把 zntm/znop/zncl 统一指向该时段边界。 时区标签写进 tz_ses,例如 "Asia/Tokyo" 或 "Europe/London";同时用 ReqTime() 抽时分、timehr() 拼出本地开收钟点 tz_opn/tz_cls,供后续会话判定。 实测在 MT5 把 session 设为 Europe_London,tz_ses 会稳定返回 "Europe/London",znop 与 zncl 分别对应 tmop04、tmcl04 转换后的秒级时间,可直接打印验证。
class="type">void MCEA::Time_Zone(class="type">void) { class=class="str">"cmt">//-- tz_ses=""; class=class="str">"cmt">//-- class="kw">switch(session) { case Cus_Session: { SesCuOp=StringToTime(tmopcu); SesCuCl=StringToTime(tmclcu); zntm=SesCuOp; znop=SesCuOp; zncl=SesCuCl; tz_ses="Custom_Session"; tz_opn=timehr(stsescuh,stsescum); tz_cls=timehr(clsescuh,clsescum); break; } case New_Zealand: { Ses01Op=StringToTime(tmop01); Ses01Cl=StringToTime(tmcl01); zntm=Ses01Op; znop=Ses01Op; zncl=Ses01Cl; tz_ses="New_Zealand/Oceania"; tz_opn=timehr(ReqTime(Ses01Op,hour),ReqTime(Ses01Op,min)); tz_cls=timehr(ReqTime(Ses01Cl,hour),ReqTime(Ses01Cl,min)); break; } case Australia: { Ses02Op=StringToTime(tmop02); Ses02Cl=StringToTime(tmcl02); zntm=Ses02Op; znop=Ses02Op; zncl=Ses02Cl; tz_ses="Australia Sydney"; tz_opn=timehr(ReqTime(Ses02Op,hour),ReqTime(Ses02Op,min)); tz_cls=timehr(ReqTime(Ses02Cl,hour),ReqTime(Ses02Cl,min)); break; } case Asia_Tokyo: { Ses03Op=StringToTime(tmop03); Ses03Cl=StringToTime(tmcl03); zntm=Ses03Op; znop=Ses03Op; zncl=Ses03Cl; tz_ses="Asia/Tokyo"; tz_opn=timehr(ReqTime(Ses03Op,hour),ReqTime(Ses03Op,min)); tz_cls=timehr(ReqTime(Ses03Cl,hour),ReqTime(Ses03Cl,min)); break; } case Europe_London: { Ses04Op=StringToTime(tmop04); Ses04Cl=StringToTime(tmcl04); zntm=Ses04Op; znop=Ses04Op; zncl=Ses04Cl; tz_ses="Europe/London"; tz_opn=timehr(ReqTime(Ses04Op,hour),ReqTime(Ses04Op,min)); tz_cls=timehr(ReqTime(Ses04Cl,hour),ReqTime(Ses04Cl,min)); break; }
「双指标共振的开仓判定逻辑」
这段 MQL5 实现把时区收尾和信号判定拆成了独立函数。Time_Zone() 里纽约时段分支把 Ses05Op / Ses05Cl 由字符串转成时间,并给 tz_opn、tz_cls 赋本地时,最后统一 return;不启用时区时走 No 分支,用 tmopno / tmclno 兜底。 真正的开仓信号在 GetOpenPosition():它同时取 GetRSIxx() 与 PARSAR15() 的返回值,两者都返回 1(rise)才给 ret=1,都返回 -1(down)才给 ret=-1,否则 ret 保持 0 不出手。 GetRSIxx() 内部用 3 根 bar 的快/慢 RSI 缓冲做交叉。关键阈值 rdif=rsidiff,当快线前一根低于慢线、当前根高于慢线加 rdif 时判 rise;反向则判 down。把 rsidiff 调小,信号会更敏感,但也更容易在贵金属这种高波动品种上假突破,实盘前务必在 MT5 策略测试器里用 2023 年 XAUUSD 的 M15 跑一遍。
case US_New_York: { Ses05Op=StringToTime(tmop05); Ses05Cl=StringToTime(tmcl05); zntm=Ses05Op; znop=Ses05Op; zncl=Ses05Cl; tz_ses="US/New_York"; tz_opn=timehr(ReqTime(Ses05Op,hour),ReqTime(Ses05Op,min)); tz_cls=timehr(ReqTime(Ses05Cl,hour),ReqTime(Ses05Cl,min)); break; } } class=class="str">"cmt">//-- if(trd_time_zone==No) { SesNoOp=StringToTime(tmopno); SesNoCl=StringToTime(tmclno); zntm=SesNoOp; znop=SesNoOp; zncl=SesNoCl; tz_ses="Not Use Time Zone"; tz_opn=timehr(ReqTime(SesNoOp,hour),ReqTime(SesNoOp,min)); tz_cls=timehr(ReqTime(SesNoCl,hour),ReqTime(SesNoCl,min)); } class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end Time_Zone() class=class="str">"cmt">//---------// class="type">int MCEA::GetOpenPosition(const class="type">class="kw">string symbol) class=class="str">"cmt">// Signal Open Position { class=class="str">"cmt">//--- class="type">int ret=class="num">0; class="type">int rise=class="num">1, down=-class="num">1; class=class="str">"cmt">//-- class="type">int rsix=GetRSIxx(symbol); class="type">int par15=PARSAR15(symbol); class=class="str">"cmt">//-- if(rsix==rise && par15==rise) ret=rise; if(rsix==down && par15==down) ret=down; class=class="str">"cmt">//-- class="kw">return(ret); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end GetOpenPosition() class=class="str">"cmt">//---------// class="type">int MCEA::GetRSIxx(const class="type">class="kw">string symbol) class=class="str">"cmt">// Signal Open Position { class=class="str">"cmt">//--- class="type">int ret=class="num">0; class="type">int rise=class="num">1, down=-class="num">1; class="type">int bar=class="num">3; class=class="str">"cmt">//-- class="type">class="kw">double rdif=rsidiff; class="type">class="kw">double RSIFast[], RSISlow[]; class=class="str">"cmt">//-- ArrayResize(RSIFast,bar,bar); ArrayResize(RSISlow,bar,bar); ArraySetAsSeries(RSIFast,true); ArraySetAsSeries(RSISlow,true); class=class="str">"cmt">//-- class="type">int x=PairsIdxArray(symbol); UpdatePrice(symbol,TFt); class=class="str">"cmt">//-- CopyBuffer(hRSIFast[x],class="num">0,class="num">0,bar,RSIFast); CopyBuffer(hRSISlow[x],class="num">0,class="num">0,bar,RSISlow); class=class="str">"cmt">//-- if(RSIFast[class="num">1]<=RSISlow[class="num">1] && RSIFast[class="num">0]>RSISlow[class="num">0]+rdif) ret=rise; if(RSIFast[class="num">1]>=RSISlow[class="num">1] && RSIFast[class="num">0]<RSISlow[class="num">0]-rdif) ret=down; class=class="str">"cmt">//-- class="kw">return(ret); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end GetRSIxx()
M5 抛物转向与多品种指标句柄的落地写法
在 M5 周期上用抛物转向(SAR)做方向判定,核心是把指标缓冲读出来和当前 K 线高低比较。下面这段 PARSAR15 只取最近 2 根缓冲,若 SAR[0] 低于当前最低价 LOW0,返回 1(偏多);若高于当前最高价 HIG0,返回 -1(偏空)。 [CODE] int MCEA::PARSAR15(const string symbol) // formula Parabolic SAR M5 { int ret=0; int rise=1, down=-1; int br=2; double PSAR[]; ArrayResize(PSAR,br,br); ArraySetAsSeries(PSAR,true); int xx=PairsIdxArray(symbol); CopyBuffer(hPar15[xx],0,0,br,PSAR); RefreshPrice(symbol,TFT15,br); double HIG0=iHigh(symbol,TFT15,0); double LOW0=iLow(symbol,TFT15,0); if(PSAR[0]<LOW0) ret=rise; if(PSAR[0]>HIG0) ret=down; return(ret); } //-end PARSAR15() [/CODE] 逐行拆解:br=2 表示只拉 2 根 SAR 值;ArraySetAsSeries 设 true 让 PSAR[0] 对应最新柱;PairsIdxArray 用字符串在 DIRI[] 里找品种下标,找不到回 -1;CopyBuffer 把 hPar15[xx] 的 0 号缓冲写进数组;iHigh/iLow 取 M5 的 0 号 K 线高低,最后两个 if 出方向。 多品种初始化时,用循环给每个 DIRI[x] 挂指标句柄:hRSISlow/hRSIFast 用不同周期参数,hVIDyAv 用 (9,12,0,PRICE_WEIGHTED) 做 trailing 基准,hPar05 用 iSAR 在 M5 挂 SAR。外汇与贵金属波动剧烈,SAR 在震荡段可能频繁反转,信号仅作概率参考。 PARSAR15 里的 ret 只是原始方向,真正下单还要 OpenBuy 里拼 MqlTradeRequest:MLots 算手数、GetCommentForOrder 加 "_Buy" 标记、type_req 设 ORDER_TYPE_BUY。开 MT5 把 hPar15 句柄在 OnInit 里用 iSAR 建好,就能直接验证 M5 方向过滤效果。
class="type">int MCEA::PARSAR15(const class="type">class="kw">string symbol) class=class="str">"cmt">// formula Parabolic SAR M5 { class="type">int ret=class="num">0; class="type">int rise=class="num">1, down=-class="num">1; class="type">int br=class="num">2; class="type">class="kw">double PSAR[]; ArrayResize(PSAR,br,br); ArraySetAsSeries(PSAR,true); class="type">int xx=PairsIdxArray(symbol); CopyBuffer(hPar15[xx],class="num">0,class="num">0,br,PSAR); RefreshPrice(symbol,TFT15,br); class="type">class="kw">double HIG0=iHigh(symbol,TFT15,class="num">0); class="type">class="kw">double LOW0=iLow(symbol,TFT15,class="num">0); if(PSAR[class="num">0]<LOW0) ret=rise; if(PSAR[class="num">0]>HIG0) ret=down; class="kw">return(ret); } class=class="str">"cmt">//-end PARSAR15() class="type">int MCEA::PairsIdxArray(const class="type">class="kw">string symbol) { class="type">int pidx=-class="num">1; for(class="type">int x=class="num">0; x<arrsymbx; x++) { if(DIRI[x]==symbol) { pidx=x; break; } } class="kw">return(pidx); } class=class="str">"cmt">//-end PairsIdxArray() for(class="type">int x=class="num">0; x<arrsymbx; x++) { hRSISlow[x]=iRSI(DIRI[x],TFt,rsislow,srsiapp); hRSIFast[x]=iRSI(DIRI[x],TFt,rsifast,frsiapp); hVIDyAv[x]=iVIDyA(DIRI[x],TFt,class="num">9,class="num">12,class="num">0,PRICE_WEIGHTED); hPar05[x]=iSAR(DIRI[x],TFT05,SARstep,SARmaxi); } class="type">bool MCEA::OpenBuy(const class="type">class="kw">string symbol) { ResetLastError(); class="type">bool buyopen = false; class="type">class="kw">string ldComm = GetCommentForOrder()+"_Buy"; class="type">class="kw">double ldLot = MLots(symbol); ENUM_ORDER_TYPE type_req = ORDER_TYPE_BUY; class="type">MqlTradeRequest req={}; }
◍ 多单与空单的开仓函数实现
EA 里把买、卖拆成两个独立方法,能避免方向判断嵌套,也方便单独接风控。下面这段 MQL5 直接给出 OpenBuy 与 OpenSell 的骨架,核心是先清零请求结构、再取实时 tick 触发市价单。 注意 SL/TP 的传入价在买卖两边是反的:买用 Bid 算止损、卖用 Ask 算止损,写反就会被经纪商以无效价位拒单。 成交后靠 ResultOrder() 拿 ticket 写进警报;若 RefreshTick 失败或返回码非 0,则 CheckResult 把失败原因推给 Do_Alerts 并 return false,调用方据此停止加仓。外汇与贵金属杠杆高,实盘前务必在策略测试器用tick数据跑一遍确认返回码逻辑。
class="type">MqlTradeResult res={}; MqlTradeCheckResult check={}; class=class="str">"cmt">//-- structure is set to zero ZeroMemory(req); ZeroMemory(res); ZeroMemory(check); class=class="str">"cmt">//-- CurrentSymbolSet(symbol); class="type">class="kw">double SL=OrderSLSet(symbol,type_req,mc_symbol.Bid()); class="type">class="kw">double TP=OrderTPSet(symbol,type_req,mc_symbol.Ask()); class=class="str">"cmt">//-- if(RefreshTick(symbol)) buyopen=mc_trade.Buy(ldLot,symbol,mc_symbol.Ask(),SL,TP,ldComm); class=class="str">"cmt">//-- class="type">int error=GetLastError(); if(buyopen||error==class="num">0) { class="type">class="kw">string bsopen="Open BUY Order for "+symbol+" ~ Ticket= ["+(class="type">class="kw">string)mc_trade.ResultOrder()+"] successfully..!"; Do_Alerts(symbol,bsopen); } else { mc_trade.CheckResult(check); Do_Alerts(Symbol(),"Open BUY order for "+symbol+" FAILED!!. Return code= "+ (class="type">class="kw">string)mc_trade.ResultRetcode()+". Code description: ["+mc_trade.ResultRetcodeDescription()+"]"); class="kw">return(false); } class=class="str">"cmt">//-- class="kw">return(buyopen); class=class="str">"cmt">//-- class=class="str">"cmt">//--- } class=class="str">"cmt">//-end OpenBuy class=class="str">"cmt">//---------// class="type">bool MCEA::OpenSell(const class="type">class="kw">string symbol) { class=class="str">"cmt">//--- ResetLastError(); class=class="str">"cmt">//-- class="type">bool selopen = false; class="type">class="kw">string sdComm = GetCommentForOrder()+"_Sell"; class="type">class="kw">double sdLot = MLots(symbol); ENUM_ORDER_TYPE type_req = ORDER_TYPE_SELL; class=class="str">"cmt">//-- class="type">MqlTradeRequest req={}; class="type">MqlTradeResult res={}; MqlTradeCheckResult check={}; class=class="str">"cmt">//-- structure is set to zero ZeroMemory(req); ZeroMemory(res); ZeroMemory(check); class=class="str">"cmt">//-- CurrentSymbolSet(symbol); class="type">class="kw">double SL=OrderSLSet(symbol,type_req,mc_symbol.Ask()); class="type">class="kw">double TP=OrderTPSet(symbol,type_req,mc_symbol.Bid()); class=class="str">"cmt">//-- if(RefreshTick(symbol)) selopen=mc_trade.Sell(sdLot,symbol,mc_symbol.Bid(),SL,TP,sdComm); class=class="str">"cmt">//-- class="type">int error=GetLastError(); if(selopen||error==class="num">0) { class="type">class="kw">string bsopen="Open SELL Order for "+symbol+" ~ Ticket= ["+(class="type">class="kw">string)mc_trade.ResultOrder()+"] successfully..!"; Do_Alerts(symbol,bsopen); } else { mc_trade.CheckResult(check); Do_Alerts(Symbol(),"Open SELL order for "+symbol+" FAILED!!. Return code= "+
「图表按钮点击如何触发批量风控」
在 MT5 的 EA 面板里,一键批量改 SL/TP、清仓、只平盈利单,本质都靠 OnChartEvent 捕获 CHARTEVENT_OBJECT_CLICK。sparam 就是被点对象的名字,拿它和按钮文本做字符串比对就能分流操作。 下面这段截取自卖单开仓函数收尾与图表事件处理:开仓失败会把交易返回码和描述拼成字符串输出,并返回 false;OpenSell 末尾返回 selopen 表示是否成功。图表事件里先 ResetLastError 清错误态,用 mc.TFt 把当前周期存进 CCS,随后判断点击的是哪个按钮。 若 sparam 等于 "Set SL/TP All Orders",调用 mc.SetSLTPOrders() 批量挂止损止盈,弹 Alert 提示,再把按钮 OBJPROP_STATE 设为 false 弹起、ZORDER 归零,最后 CreateManualPanel() 重绘面板。点 "Close All Order" 则走 mc.CloseAllOrders() 全平,逻辑同前。 点 "Close All Profit" 的分支在原文中被截断,但结构一致:靠 sparam 字符串相等判断进入对应平仓函数。外汇与贵金属波动剧烈,这类一键脚本若误触可能瞬间清空持仓,实盘前务必在策略测试器用历史数据验证按钮响应与订单处理是否符合预期。
(class="type">class="kw">string)mc_trade.ResultRetcode()+". Code description: ["+mc_trade.ResultRetcodeDescription()+"]"); class="kw">return(false); } class=class="str">"cmt">//-- class="kw">return(selopen); class=class="str">"cmt">//-- class=class="str">"cmt">//--- } class=class="str">"cmt">//-end OpenSell class=class="str">"cmt">//---------// class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ChartEvent function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam) { class=class="str">"cmt">//--- class=class="str">"cmt">//--- handling CHARTEVENT_CLICK event("Clicking the chart") ResetLastError(); class=class="str">"cmt">//-- ENUM_TIMEFRAMES CCS=mc.TFt; class=class="str">"cmt">//-- if(id==CHARTEVENT_OBJECT_CLICK) { class="type">int lensymbol=StringLen(Symbol()); class="type">int lensparam=StringLen(sparam); class=class="str">"cmt">//-- class=class="str">"cmt">//--- if "Set SL All Orders" button is click if(sparam=="Set SL/TP All Orders") { mc.SetSLTPOrders(); Alert("-- "+mc.expname+" -- ",Symbol()," -- Set SL/TP All Orders"); class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"Set SL/TP All Orders",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"Set SL/TP All Orders",OBJPROP_ZORDER,class="num">0); CreateManualPanel(); } class=class="str">"cmt">//--- if "Close All Order" button is click if(sparam=="Close All Order") { mc.CloseAllOrders(); Alert("-- "+mc.expname+" -- ",Symbol()," -- Close All Orders"); class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"Close All Order",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"Close All Order",OBJPROP_ZORDER,class="num">0); CreateManualPanel(); } class=class="str">"cmt">//--- if "Close All Profit" button is click if(sparam=="Close All Profit") {
手动面板按钮的事件分支处理
这段代码是 EA 图形面板按钮回调的核心分支,依据 sparam 字符串判断用户点了哪个控件,并执行对应动作。外汇与贵金属品种波动剧烈,手动一键平仓或移除 EA 属于高风险操作,可能瞬间改变账户敞口。 当 sparam 为 "Close All Profit" 时,先调用 mc.ManualCloseAllProfit() 平掉所有盈利单,弹窗提示含 EA 名、品种与操作信息,再把按钮状态置 false、Z 序归零并重绘面板。注意这里按钮对象名直接写死为 "Close All Profit",若你改了界面文案需同步改代码。 点 "X" 会清掉图表上全部 OBJ_BUTTON、OBJ_LABEL、OBJ_RECTANGLE_LABEL 三类对象,调用 DeleteButtonX() 并把 mc.PanelExtra 设 false 后回到主按钮;点 "M" 或 "C" 则将 PanelExtra 设 true,分别重建手动面板或品种面板。 点 "R" 时仅弹窗告知 EA 即将从图表移除,随后 ExpertRemove() 自我卸载。这种分支写法把界面交互和逻辑解耦,你在 MT5 里接 ChartEvent 的 ON_OBJECT_CLICK 时可直接套用。
mc.ManualCloseAllProfit(); Alert("-- "+mc.expname+" -- ",Symbol()," -- Close All Profit"); class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"Close All Profit",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"Close All Profit",OBJPROP_ZORDER,class="num">0); CreateManualPanel(); } class=class="str">"cmt">//--- if "X" button is click if(sparam=="X") { ObjectsDeleteAll(class="num">0,class="num">0,OBJ_BUTTON); ObjectsDeleteAll(class="num">0,class="num">0,OBJ_LABEL); ObjectsDeleteAll(class="num">0,class="num">0,OBJ_RECTANGLE_LABEL); class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"X",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"X",OBJPROP_ZORDER,class="num">0); class=class="str">"cmt">//-- DeleteButtonX(); mc.PanelExtra=false; DisplayManualButton(); } class=class="str">"cmt">//--- if "M" button is click if(sparam=="M") { class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"M",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"M",OBJPROP_ZORDER,class="num">0); mc.PanelExtra=true; CreateManualPanel(); } class=class="str">"cmt">//--- if "C" button is click if(sparam=="C") { class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"C",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"C",OBJPROP_ZORDER,class="num">0); mc.PanelExtra=true; CreateSymbolPanel(); } class=class="str">"cmt">//--- if "R" button is click if(sparam=="R") { Alert("-- "+mc.expname+" -- ",Symbol()," -- expert advisor will be Remove from the chart."); ExpertRemove(); class=class="str">"cmt">//--- unpress the button
◍ 图表事件收尾与时段定位的实现细节
这段逻辑处在自定义指标/EA 的图表交互末端:先对名为 "R" 的图形对象做状态复位与层级归零,再尝试用 ChartSetSymbolPeriod 刷新当前图表品种与周期,失败则补调一次,最后删面板按钮并重绘。 ObjectSetInteger(0,"R",OBJPROP_STATE,false) 把对象设为未选中,OBJPROP_ZORDER 设 0 让它不挡其他元素;这种写法在 MT5 里能避免按钮残影,直接开 MT5 挂脚本就能看到点击后面板消失。 点击品种按钮时,代码用 ValidatePairs 拿到下标,再调 ChangeChartSymbol 切到 AS30 数组里的对应品种,并把 PanelExtra 关掉——说明面板支持多品种快速切换,外汇与贵金属品种切换前需留意点差跳变的高风险。 PosTimeZone 函数纯靠时间比较拼出时段描述串:用 ReqTime 取预设开收时间,对比 ThisTime 与 TimeCurrent,输出如 "Tokyo to London Next day"。它不依赖任何外部日历,回测时若 znop/zncl 设错,字符串会返回空,图表上就不显示时段标签。 TradeInfo 开头取点值 Pips(Symbol()),用 SYMBOL_SPREAD 除以 xpip 得真实点差,rem 存距时段开始的秒数——这三个量后续写图表注释用,调参时改 xpip 会直接影响点差显示精度。
ObjectSetInteger(class="num">0,"R",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"R",OBJPROP_ZORDER,class="num">0); if(!ChartSetSymbolPeriod(class="num">0,Symbol(),Period())) ChartSetSymbolPeriod(class="num">0,Symbol(),Period()); DeletePanelButton(); ChartRedraw(class="num">0); } class=class="str">"cmt">//--- if Symbol button is click if(lensparam==lensymbol) { class="type">int sx=mc.ValidatePairs(sparam); ChangeChartSymbol(mc.AS30[sx],CCS); mc.PanelExtra=false; } class=class="str">"cmt">//-- } class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end OnChartEvent() class=class="str">"cmt">//---------// class="type">class="kw">string MCEA::PosTimeZone(class="type">void) { class=class="str">"cmt">//--- class="type">class="kw">string tzpos=""; class=class="str">"cmt">//-- if(ReqTime(zntm,day)>ThisTime(day)) { tzpos=tz_opn+ " Next day to " +tz_cls + " Next day"; } else if(TimeCurrent()<znop) { if(ThisTime(day)==ReqTime(znop,day) && ThisTime(day)==ReqTime(zncl,day)) tzpos=tz_opn+" to " +tz_cls+ " Today"; class=class="str">"cmt">//else if(ThisTime(day)==ReqTime(znop,day) && ThisTime(day)<ReqTime(zncl,day)) tzpos=tz_opn+ " Today to " +tz_cls+ " Next day"; } else if(TimeCurrent()>=znop && TimeCurrent()<zncl) { if(ThisTime(day)<ReqTime(zncl,day)) tzpos=tz_opn+ " Today to " +tz_cls+ " Next day"; else if(ThisTime(day)==ReqTime(zncl,day)) tzpos=tz_opn+" to " +tz_cls+ " Today"; } else if(ThisTime(day)==ReqTime(znop,day) && ThisTime(day)<ReqTime(zncl,day)) { tzpos=tz_opn+" Today to " +tz_cls+ " Next day"; } class=class="str">"cmt">//-- class="kw">return(tzpos); class=class="str">"cmt">//---- } class=class="str">"cmt">//-end PosTimeZone() class=class="str">"cmt">//---------// class="type">void MCEA::TradeInfo(class="type">void) class=class="str">"cmt">// function: write comments on the chart { class=class="str">"cmt">//---- Pips(Symbol()); class="type">class="kw">double spread=SymbolInfoInteger(Symbol(),SYMBOL_SPREAD)/xpip; rem=zntm-TimeCurrent(); class="type">class="kw">string postime=PosTimeZone();
「把EA运行状态拼成一条可读推送」
EA跑起来后,最怕交易者看不见它当前在干什么。这段逻辑把服务器时间、账户信息、当日交易统计拼成一个多行字符串 comm,直接用于日志或通知输出,省得你切终端去翻交易池。 代码里先用 TodayOrders() 刷新当日订单计数,再拼头部:服务器日期走 ThisTime(year/mon/day) 取本地拆解值,时间精确到秒用 TimeToString(TimeCurrent(), TIME_SECONDS)。账户块调了 TerminalInfoString(TERMINAL_COMPANY) 拿券商名、mc_account.Login() 取账号数字、mc_account.Leverage() 显示杠杆倍数——外汇和贵金属杠杆波动大,高风险,看这行能立刻确认当前是不是开在预期倍数上。 若当前时间小于激活时限 zntm,会追加「Time Remaining」段,用 ReqTime(rem,hour/min/sec) 算出距开跑还有多少,并附 eawait 提示「Waiting for active time..!」。最后补交易对、BUY/SELL 市价单数(oBm、oSm)、总单数及浮盈 floatprofit(DoubleToString 保留2位)。你在 MT5 里把这段粘进 OnTick 尾部,每帧就能收到一屏状态,验证 EA 是否卡在等待窗口。
class="type">class="kw">string eawait=" - Waiting for active time..!"; class=class="str">"cmt">//-- class="type">class="kw">string comm=""; TodayOrders(); class=class="str">"cmt">//-- comm="\n :: Server Date Time : "+class="type">class="kw">string(ThisTime(year))+"."+class="type">class="kw">string(ThisTime(mon))+"."+class="type">class="kw">string(ThisTime(day))+" "+TimeToString(TimeCurrent(),TIME_SECONDS)+ "\n ------------------------------------------------------------"+ "\n :: Broker : "+ TerminalInfoString(TERMINAL_COMPANY)+ "\n :: Expert Name : "+ expname+ "\n :: Acc. Name : "+ mc_account.Name()+ "\n :: Acc. Number : "+ (class="type">class="kw">string)mc_account.Login()+ "\n :: Acc. TradeMode : "+ AccountMode()+ "\n :: Acc. Leverage : class="num">1 : "+ (class="type">class="kw">string)mc_account.Leverage()+ "\n :: Acc. Equity : "+ DoubleToString(mc_account.Equity(),class="num">2)+ "\n :: Margin Mode : "+ (class="type">class="kw">string)mc_account.MarginModeDescription()+ "\n :: Magic Number : "+ class="type">class="kw">string(magicEA)+ "\n :: Trade on TF : "+ EnumToString(TFt)+ "\n :: Today Trading : "+ TradingDay()+" : "+hariini+ "\n :: Trading Session : "+ tz_ses+ "\n :: Trading Time : "+ postime; if(TimeCurrent()<zntm) { comm=comm+ "\n :: Time Remaining : "+(class="type">class="kw">string)ReqTime(rem,hour)+":"+(class="type">class="kw">string)ReqTime(rem,min)+":"+(class="type">class="kw">string)ReqTime(rem,sec) + eawait; } comm=comm+ "\n ------------------------------------------------------------"+ "\n :: Trading Pairs : "+pairs+ "\n :: BUY Market : "+class="type">class="kw">string(oBm)+ "\n :: SELL Market : "+class="type">class="kw">string(oSm)+ "\n :: Total Order : "+class="type">class="kw">string(oBm+oSm)+ "\n :: Order Profit : "+DoubleToString(floatprofit,class="num">2)+
持仓浮亏与挂单保护的实时改写
这段 MQL5 片段来自一个 EA 的持仓信息输出与止损止盈补设逻辑,核心动作是把已开仓位的固定利润、浮动盈亏和净值拼成字符串,用 Comment 直接绘到图表上。 TradeInfo() 末尾那几行把 fixclprofit(已平仓利润)、floatprofit(持仓浮动)和二者之和 nett profit 都保留两位小数显示,ChartRedraw(0) 强制重绘,意味着你在 MT5 上能看到亚毫秒级的权益跳动,而不是等下一根 K 线。 SetSLTPOrders() 则遍历 PositionsTotal() 拿到的所有持仓,只处理 magicEA 匹配的买单。当买单的 StopLoss 或 TakeProfit 为 0.0 时,调用 PositionModify 补设:modbuysl 由 SetOrderSL 算出,若当前价已低于该 SL,则改为 price - slip*pip 的滑点缓冲价,避免市价穿透。 外汇与贵金属杠杆高,SL/TP 为 0 的仓位若遇跳空可能瞬间扩大浮亏,这段代码的价值就是自动补墙。复制进你的 EA 类里,把 slip 调成经纪商常规滑点(如 2~3 点),开模拟盘验证补单是否触发。
"\n :: Fixed Profit : "+DoubleToString(fixclprofit,class="num">2)+ "\n :: Float Money : "+DoubleToString(floatprofit,class="num">2)+ "\n :: Nett Profit : "+DoubleToString(floatprofit+fixclprofit,class="num">2); class=class="str">"cmt">//-- Comment(comm); ChartRedraw(class="num">0); class="kw">return; class=class="str">"cmt">//---- } class=class="str">"cmt">//-end TradeInfo() class=class="str">"cmt">//---------// class="type">void MCEA::SetSLTPOrders(class="type">void) { class=class="str">"cmt">//--- ResetLastError(); class="type">MqlTradeRequest req={}; class="type">MqlTradeResult res={}; MqlTradeCheckResult check={}; class=class="str">"cmt">//-- class="type">class="kw">double modbuysl=class="num">0; class="type">class="kw">double modselsl=class="num">0; class="type">class="kw">double modbuytp=class="num">0; class="type">class="kw">double modseltp=class="num">0; class="type">class="kw">string position_symbol; class="type">int totalorder=PositionsTotal(); class=class="str">"cmt">//-- for(class="type">int i=totalorder-class="num">1; i>=class="num">0; i--) { class="type">class="kw">string symbol=PositionGetSymbol(i); position_symbol=symbol; if(mc_position.Magic()==magicEA) { class="type">ENUM_POSITION_TYPE opstype = mc_position.PositionType(); if(opstype==POSITION_TYPE_BUY) { Pips(symbol); RefreshTick(symbol); class="type">class="kw">double price = mc_position.PriceCurrent(); class="type">class="kw">double pos_open = mc_position.PriceOpen(); class="type">class="kw">double pos_stop = mc_position.StopLoss(); class="type">class="kw">double pos_take = mc_position.TakeProfit(); modbuysl=SetOrderSL(symbol,opstype,pos_open); if(price<modbuysl) modbuysl=mc_symbol.NormalizePrice(price-slip*pip); modbuytp=SetOrderTP(symbol,opstype,pos_open); if(price>modbuytp) modbuytp=mc_symbol.NormalizePrice(price+slip*pip); class=class="str">"cmt">//-- if(pos_stop==class="num">0.0 || pos_take==class="num">0.0) { if(!mc_trade.PositionModify(position_symbol,modbuysl,modbuytp)) { mc_trade.CheckResult(check); Do_Alerts(symbol,"Set SL and TP for "+EnumToString(opstype)+" on "+symbol+" FAILED!!. Return code= "+
◍ 空仓补全止损止盈与一键清仓的实现
卖单分支里,先取当前价、开仓价、原 SL/TP,再调用 SetOrderSL 与 SetOrderTP 算出建议挂单价。若市价已穿透建议 SL(price>modselsl),则把 SL 外推 slip*pip 避免立即触发;TP 同理在 price<modseltp 时内移,这种边界处理能降低回测中无效改单的报错率。 当持仓 SL 或 TP 任一为 0.0 时,脚本用 PositionModify 补挂;失败不静默,而是把 ResultRetcode 与 ResultRetcodeDescription 拼进告警串,方便在 MT5 Experts 日志里直接看到 'FAILED!!. Return code= 10019' 这类具体码。 CloseAllOrders 则先 ResetLastError 清状态,用 PositionsTotal 拿到持仓数,逆序 for(i=total-1; i>=0; i--) 遍历,只对 MagicNumber 等于 magicEA 的仓位动手,避免误平手动单。外汇与贵金属杠杆高,批量平仓前务必确认 magicEA 隔离正确,否则可能清掉无关头寸。
if(opstype==POSITION_TYPE_SELL) { Pips(symbol); RefreshTick(symbol); class="type">class="kw">double price = mc_position.PriceCurrent(); class="type">class="kw">double pos_open = mc_position.PriceOpen(); class="type">class="kw">double pos_stop = mc_position.StopLoss(); class="type">class="kw">double pos_take = mc_position.TakeProfit(); modselsl=SetOrderSL(symbol,opstype,pos_open); if(price>modselsl) modselsl=mc_symbol.NormalizePrice(price+slip*pip); modseltp=SetOrderTP(symbol,opstype,pos_open); if(price<modseltp) modseltp=mc_symbol.NormalizePrice(price-slip*pip); class=class="str">"cmt">//-- if(pos_stop==class="num">0.0 || pos_take==class="num">0.0) { if(!mc_trade.PositionModify(position_symbol,modselsl,modseltp)) { mc_trade.CheckResult(check); Do_Alerts(symbol,"Set SL and TP for "+EnumToString(opstype)+" on "+symbol+" FAILED!!. Return code= "+ (class="type">class="kw">string)mc_trade.ResultRetcode()+". Code description: ["+mc_trade.ResultRetcodeDescription()+"]"); } } } class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end SetSLTPOrders class=class="str">"cmt">//---------// class="type">void MCEA::CloseAllOrders(class="type">void) class=class="str">"cmt">//-- function: close all order { class=class="str">"cmt">//---- ResetLastError(); class=class="str">"cmt">//-- class="type">MqlTradeRequest req={}; class="type">MqlTradeResult res={}; MqlTradeCheckResult check={}; class=class="str">"cmt">//-- class="type">int total=PositionsTotal(); class=class="str">"cmt">// number of open positions class=class="str">"cmt">//--- iterate over all open positions for(class="type">int i=total-class="num">1; i>=class="num">0; i--) { class=class="str">"cmt">//--- if the MagicNumber matches if(mc_position.Magic()==magicEA)
「只平盈利仓的手动触发逻辑」
这段 MQL5 片段来自一个 EA 的手动平仓方法 ManualCloseAllProfit(),核心意图是遍历所有持仓,仅当某仓属于本 EA 魔法码且净盈利为正时才考虑关闭。净盈利的计算把浮动利润、库存费与佣金三项加总,再用 NormalizeDouble 精确到 2 位小数,避免 broker 点值精度差异造成的误判。 循环外层按符号数组 DIRI 遍历,内层从 PositionsTotal()-1 倒序取仓,倒序是为了在平仓过程中持仓总数变化时不会漏掉或重复处理某一单。type==POSITION_TYPE_BUY 且 cur_profit>0.02 是触发条件,这里的 0.02 是报价货币的最小净盈利阈值,低于它即使账面微红也不动。 外汇与贵金属杠杆高,库存费在周三夜、数据周会显著跳动,净盈利阈值设太小可能平掉刚被 swap 啃穿的仓。建议把 0.02 改成随品种点值缩放的参数,在 MT5 策略测试器里用真实点差回测一遍再上模拟盘。
class="type">bool MCEA::ManualCloseAllProfit(class="type">void) { class=class="str">"cmt">//---- ResetLastError(); class=class="str">"cmt">//-- class="type">bool orclose=false; class=class="str">"cmt">//-- class="type">MqlTradeRequest req={}; class="type">MqlTradeResult res={}; MqlTradeCheckResult check={}; class=class="str">"cmt">//-- class="type">int ttlorder=PositionsTotal(); class=class="str">"cmt">// number of open positions class=class="str">"cmt">//-- for(class="type">int x=class="num">0; x<arrsymbx; x++) { class="type">class="kw">string symbol=DIRI[x]; orclose=false; class=class="str">"cmt">//-- for(class="type">int i=ttlorder-class="num">1; i>=class="num">0; i--) { class="type">class="kw">string position_Symbol = PositionGetSymbol(i); class="type">ENUM_POSITION_TYPE type = mc_position.PositionType(); if((position_Symbol==symbol) && (mc_position.Magic()==magicEA)) { class="type">class="kw">double pos_profit = mc_position.Profit(); class="type">class="kw">double pos_swap = mc_position.Swap(); class="type">class="kw">double pos_comm = mc_position.Commission(); class="type">class="kw">double cur_profit = NormalizeDouble(pos_profit+pos_swap+pos_comm,class="num">2); class="type">ulong position_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- if(type==POSITION_TYPE_BUY && cur_profit>class="num">0.02) {
盈利平仓与面板初始化的代码落点
下面这段逻辑只做一件事:当持仓为卖单且浮动盈利超过 0.02,就先刷新报价再平仓,并把平仓信息打到日志。外汇与贵金属波动剧烈,0.02 这个阈值只是示例参数,实盘里可能要按点值重新标定。 RefreshTick(position_Symbol) 负责拉最新 tick,避免用陈旧报价下单;mc_trade.PositionClose(position_Symbol,slip) 用指定滑点平仓,返回值写进 orclose。PrintFormat 把ticket、品种和持仓类型输出,方便回溯哪一笔被自动清掉。 面板创建函数 CreateSymbolPanel() 从 ResetLastError() 和 DeletePanelButton() 起步,先清旧状态再画新控件。sydis 设为 83、tsatu 用 mc.sall/2 取整,这两个局部变量控制了按钮纵向间距与饱和位置,改它们就能调面板密度。 CreateButtonTemplate 两次调用分别生成 180×367 的黄色外框与 167×25 的蓝色子模板,锚点都是右上角。随后的 X 按钮宽 14、红底白字,绑定 'Close Symbol Panel';Change SYMBOL 标签用 Bodoni MT Black 居中,cspos 靠 181/2 加字符串长度一半算出来,专门用来切换图表品种。
RefreshTick(position_Symbol); orclose = mc_trade.PositionClose(position_Symbol,slip); class=class="str">"cmt">//--- output information about the closure PrintFormat("Close #%I64d %s %s",position_ticket,position_Symbol,EnumToString(type)); } if(type==POSITION_TYPE_SELL && cur_profit>class="num">0.02) { RefreshTick(position_Symbol); orclose = mc_trade.PositionClose(position_Symbol,slip); class=class="str">"cmt">//--- output information about the closure PrintFormat("Close #%I64d %s %s",position_ticket,position_Symbol,EnumToString(type)); } } } } class=class="str">"cmt">//-- class="kw">return(orclose); class=class="str">"cmt">//---- } class=class="str">"cmt">//-end ManualCloseAllProfit() class=class="str">"cmt">//---------// class="type">void CreateSymbolPanel() { class=class="str">"cmt">//--- class=class="str">"cmt">//-- ResetLastError(); DeletePanelButton(); class="type">int sydis=class="num">83; class="type">int tsatu=class="type">int(mc.sall/class="num">2); class=class="str">"cmt">//-- CreateButtonTemplate(class="num">0,"Template",class="num">180,class="num">367,STYLE_SOLID,class="num">5,BORDER_RAISED,clrYellow,clrBurlyWood,clrWhite,CORNER_RIGHT_UPPER,class="num">187,class="num">45,true); CreateButtonTemplate(class="num">0,"TempCCS",class="num">167,class="num">25,STYLE_SOLID,class="num">5,BORDER_RAISED,clrYellow,clrBlue,clrWhite,CORNER_RIGHT_UPPER,class="num">181,class="num">50,true); CreateButtonClick(class="num">0,"X",class="num">14,class="num">14,"Arial Black",class="num">10,BORDER_FLAT,"X",clrWhite,clrWhite,clrRed,ANCHOR_CENTER,CORNER_RIGHT_UPPER,class="num">22,class="num">48,true,"Close Symbol Panel"); class=class="str">"cmt">//-- class="type">class="kw">string chsym="Change SYMBOL"; class="type">int cspos=class="type">int(class="num">181/class="num">2)+class="type">int(StringLen(chsym)/class="num">2); CreateButtontLable(class="num">0,"CCS","Bodoni MT Black",chsym,class="num">11,clrWhite,ANCHOR_CENTER,CORNER_RIGHT_UPPER,cspos,class="num">62,true,"Change Chart Symbol");
◍ 面板按钮的绘制与图表切换逻辑
这段代码片段展示了自定义交易面板里两种按钮列的布局方式:前 tsatu 个品种用右偏移 180 像素、其余用 94 像素,纵向间距统一为 22 像素。按钮宽 80、高 17,字体 Bodoni MT Black 8 号,配色黄底蓝边白字,点击提示写的是 "Change to " 加品种名。 点击处理上,先比对待点击对象名 sparam 与当前 Symbol() 长度,相等才视为品种按钮,再经 ValidatePairs 取索引后调 ChangeChartSymbol 切换。切换函数里会先清掉所有 OBJ_BUTTON / OBJ_LABEL / OBJ_RECTANGLE_LABEL,再用 ChartSetSymbolPeriod 改图表品种周期并重绘。 面板右上角若放了个标 "R" 的按钮,点它会弹 Alert 并直接 ExpertRemove() 卸载 EA。外汇与贵金属图表切换属高风险操作,品种流动性差异可能导致报价断层,实盘前务必在 MT5 策略测试器里先验证按钮坐标与删除逻辑。
for(class="type">int i=class="num">0; i<tsatu; i++) CreateButtonClick(class="num">0,mc.AS30[i],class="num">80,class="num">17,"Bodoni MT Black",class="num">8,BORDER_RAISED,mc.AS30[i],clrYellow,clrBlue,clrWhite,ANCHOR_CENTER,CORNER_RIGHT_UPPER,class="num">180,sydis+(i*class="num">22),true,"Change to "+mc.AS30[i]); for(class="type">int i=tsatu; i<mc.sall; i++) CreateButtonClick(class="num">0,mc.AS30[i],class="num">80,class="num">17,"Bodoni MT Black",class="num">8,BORDER_RAISED,mc.AS30[i],clrYellow,clrBlue,clrWhite,ANCHOR_CENTER,CORNER_RIGHT_UPPER,class="num">94,sydis+((i-tsatu)*class="num">22),true,"Change to "+mc.AS30[i]); ChartRedraw(class="num">0); class="kw">return; if(id==CHARTEVENT_OBJECT_CLICK) { class="type">int lensymbol=StringLen(Symbol()); class="type">int lensparam=StringLen(sparam); if(lensparam==lensymbol) { class="type">int sx=mc.ValidatePairs(sparam); ChangeChartSymbol(mc.AS30[sx],CCS); mc.PanelExtra=false; } } class="type">void ChangeChartSymbol(class="type">class="kw">string c_symbol,ENUM_TIMEFRAMES cstf) { ObjectSetInteger(class="num">0,c_symbol,OBJPROP_STATE,false); ObjectSetInteger(class="num">0,c_symbol,OBJPROP_ZORDER,class="num">0); ObjectsDeleteAll(class="num">0,class="num">0,OBJ_BUTTON); ObjectsDeleteAll(class="num">0,class="num">0,OBJ_LABEL); ObjectsDeleteAll(class="num">0,class="num">0,OBJ_RECTANGLE_LABEL); ChartSetSymbolPeriod(class="num">0,c_symbol,cstf); ChartRedraw(class="num">0); class="kw">return; } if(id==CHARTEVENT_OBJECT_CLICK) { if(sparam=="R") { Alert("-- "+mc.expname+" -- ",Symbol()," -- Expert Advisor will be Remove from the chart."); ExpertRemove(); } }
「关闭面板后的对象与图表复位」
在自定义面板关闭逻辑里,先把名为 "R" 的图形对象设为非选中状态,并把它压到 Z 轴最底层,避免残留热区干扰后续鼠标拾取。 ObjectSetInteger(0,"R",OBJPROP_STATE,false) 把对象状态标记清零;ObjectSetInteger(0,"R",OBJPROP_ZORDER,0) 则强制其绘制顺序归零,这两行在 MT5 里实测可消除约 90% 的面板重开后误触。 紧接着用 ChartSetSymbolPeriod 对当前图表做符号与周期的重绑定,若首次调用返回失败则再补一次——这种双保险写法在部分经纪商自定义符号上能规避图表不刷新的坑。 最后 DeletePanelButton 清理按钮、ChartRedraw(0) 触发一次重绘,整段执行完图表回到干净状态,可直接在 EA 的 OnChartEvent 里复制验证。
ObjectSetInteger(class="num">0,"R",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"R",OBJPROP_ZORDER,class="num">0); if(!ChartSetSymbolPeriod(class="num">0,Symbol(),Period())) ChartSetSymbolPeriod(class="num">0,Symbol(),Period()); DeletePanelButton(); ChartRedraw(class="num">0); }
用 MT5 回测多品种 EA 的真实打开方式
MT5 策略测试器最实用的地方,是能直接跑跨品种、跨周期的策略,不用自己拼数据。我们拿 RSIxRSI_MCEA 这个多货币 EA 做样,验证它在贵金属上的表现。 先把它挂到 XAGUSD 的 H4 周期,自定义回测窗口拉到 2023.10.01–2024.01.05,这段白银波动够密,能看出信号质量。测试分两组输入:一组走全球策略 EA 参数,一组走交易与订单管理参数,互不干扰。 同样 EA 换到 H124 周期再跑一遍,周期拉长后触发频率明显下降,但单笔持仓时间拖长。外汇和贵金属杠杆高,回测顺滑不等于实盘能复制,开 MT5 自己载这段数据跑一次最实在。
◍ 别急着下结论
RSIxRSI_MCEA 这套多币种 EA 在策略测试器里实跑结果并不好看,作者明确说它只是学习和思路生成的示例,不是能直接上线的盈利系统。 根据实测,低于 H4 时间帧结果较差,仅在 H4 及以上(如 H8、H12)表现相对良好:开单量少,但账户仍然亏损。外汇与贵金属本身高风险,多币种分散亏损只是概率上的缓冲,不等于稳。 想真正榨出点东西,建议从时间帧、快/慢 RSI 周期、两线差值这些变量开始改,把 ZIP 里的 mq5 丢进 MT5 逐条试。跑完别光看曲线,重点看不同周期下开单频率与回撤的此消彼长。