如何利用 MQL5 创建简单的多币种智能交易系统(第 3 部分):添加交易品种、前缀和/或后缀、以及交易时段·综合运用
- EA 类接口与多周期 SAR 初始化
- 多币种数组与经纪商前缀拼接
- 从品种名里抠出交叉盘主体
- 品种后缀清洗与逐秒轮转的执行骨架
- 开仓信号触发后的持仓与反手逻辑
- 反向信号下的买单止损与反手逻辑
- 盈利保护与时区交易窗口的实盘切法
- 把服务器时差折算成各市场本地开收盘
- 多时段交易窗口的本地与GMT换算
- 时段切换里的本地时与UTC换算坑
- 伦敦纽约时区与交易日映射
- 把交易日判定和时间拼装塞进类里
- 多周期 SAR 信号类的取时与配置骨架
- 多周期抛物转向的信号合成逻辑
- 图表按钮的事件路由怎么写
- 把账户与交易状态写进图表注释
- 订单面板与跨日时段标记的拼装逻辑
- 用代码拼出可点击的切换品种面板
- 时区切换下的回测差异
- 画得少,看得清
◍ EA 类接口与多周期 SAR 初始化
这段声明定义了一个名为 MCEA 的 EA 类尾部接口与配置函数,把交易动作、查询工具和反初始化原因文本都收口在一个类里。TradingToday、OpenBuy、OpenSell 负责当日交易开关和下单;ModifyOrderSLTP、ModifySLTP 管止损止盈重设;CloseAllProfit 与 ManualCloseAllProfit 区分自动和手动平盈。 配置函数 FXSAR_MTF_MCEA_sptz_Config 里先把 M5 固定为 TFT05,再声明 M15 到 D1 共 5 个周期放进数组 TFA,用 ArraySize 取到 TFArrays=5,随后 ArrayResize 把 TFSAR 扩到 5 个元素并 ArrayCopy 整组拷贝。TFt 取 TFSAR[2],也就是 H1 周期作为主判断 timeframe。 接着双重循环给每个交易对 x 和每个周期 i 创建 iSAR 句柄:hPar05[x] 是 M5 的 SAR,hPSAR[x][i] 覆盖其余多周期。SARstep 与 SARmaxi 为类内成员,若你在 MT5 里改这两个参数,所有周期 SAR 拐点灵敏度会同步变化,外汇与贵金属波动剧烈,参数误设可能放大回撤风险。
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=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 TFIndexArray(ENUM_TIMEFRAMES TF); 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">//---------// class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert Configuration | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void MCEA::FXSAR_MTF_MCEA_sptz_Config(class="type">void) { 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; ENUM_TIMEFRAMES TFA[]={PERIOD_M15,PERIOD_M30,PERIOD_H1,PERIOD_H4,PERIOD_D1}; TFArrays=ArraySize(TFA); ArrayResize(TFSAR,TFArrays,TFArrays); ArrayCopy(TFSAR,TFA,class="num">0,class="num">0,WHOLE_ARRAY); class=class="str">"cmt">//-- TFt=TFSAR[class="num">2]; class=class="str">"cmt">//-- class=class="str">"cmt">//-- iSAR Indicators handle for all symbol for(class="type">int x=class="num">0; x<arrsymbx; x++) { hPar05[x]=iSAR(DIRI[x],TFT05,SARstep,SARmaxi); class=class="str">"cmt">//-- for(class="type">int i=class="num">0; i<TFArrays; i++) { hPSAR[x][i]=iSAR(DIRI[x],TFSAR[i],SARstep,SARmaxi); }
多币种数组与经纪商前缀拼接
多币种 EA 在初始化时先硬编码了 30 个标准符号:7 个直盘美元对加 EURGBP 等交叉盘,以及 XAUUSD、XAGUSD 两个贵金属。按基础货币再拆出 USD、EUR、GBP、AUD、NZD、CAD、CHF、JPY 八组子数组,每组 7~9 个品种,方便后续按板块切换交易范围。 HandlingSymbolArrays() 里先用 ArraySize(All30) 拿到 sall=30,再把 All30 整体拷进 VSym。若用户在参数里填了自定义逗号分隔列表(usepairs==TrdWi 且不含 'eg.' 占位),就用 StringSplit 拆串、转大写、ValidatePairs 过滤非法名,得到 arspc 个有效自选对。 经纪商常在真实符号前后加前缀/后缀(如 m.EURUSD 或 EURUSD.sv)。SetSymbolNamePS() 探测到 inpre>0 或 insuf>0 后,会把 pre/suf 拼到 All30、USDs、EURs 等全部数组元素上,否则 MT5 用原始名 SymbolSelect 会选不到品种。 最后按 usepairs 的 0~9 分支决定 DIRI 交易池:case 0 装全部 30 对,case 2 只装 arusd=9 个美元对,case 3~9 各装 aretc=7 个对应基础货币交叉盘。arrsymbx 同步设为池大小,ArraySymbolResize() 跟着扩缓冲,EA 后续下单循环就只扫这些品种。外汇与贵金属杠杆高,实盘前务必在策略测试器用样本经纪商后缀验证符号匹配。
class="type">void MCEA::HandlingSymbolArrays(class="type">void) { class=class="str">"cmt">//--- class="type">class="kw">string All30[]={"EURUSD","GBPUSD","AUDUSD","NZDUSD","USDCAD","USDCHF","USDJPY","EURGBP", "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); aretc=ArraySize(EURs); 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="type">class="kw">string sep=","; class="type">class="kw">ushort u_sep; 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]); 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); } class=class="str">"cmt">//-- SetSymbolNamePS(); 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; } } 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<aretc; 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++) { 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);} class=class="str">"cmt">//-- class="kw">switch(usepairs) { case class="num">0: { ArrayResize(DIRI,sall,sall); arrsymbx=sall; ArraySymbolResize(); ArrayCopy(DIRI,All30,class="num">0,class="num">0,WHOLE_ARRAY); pairs="Multi Currency class="num">30 Pairs"; break; } case class="num">1: { 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"; break; } case class="num">2: { 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"; break; } case class="num">3: { ArrayResize(DIRI,aretc,aretc); arrsymbx=aretc; ArraySymbolResize(); ArrayCopy(DIRI,EURs,class="num">0,class="num">0,WHOLE_ARRAY); pairs="("+class="type">class="kw">string(aretc)+") Forex EUR Pairs"; break; } case class="num">4: { 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"; break; } case class="num">5: { 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"; break; } case class="num">6: { 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"; break; } case class="num">7: { ArrayResize(DIRI,aretc,aretc); arrsymbx=aretc; ArraySymbolResize(); ArrayCopy(DIRI,CADs,class="num">0,class="num">0,WHOLE_ARRAY); pairs="("+class="type">class="kw">string(aretc)+") Forex CAD Pairs"; break; } case class="num">8: { 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: { 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="kw">return; }
「从品种名里抠出交叉盘主体」
MT5 里 Symbol() 返回的品种名不总是干净的 EURUSD,可能是 XAUUSD.s 或 EURUSD.f 这类带后缀、带前缀的变体。写 EA 时若直接拿完整字符串去比对汇率表,往往会漏掉真实货币对,这段代码就是专门做『剥壳』用的。 核心思路是先拿 7 个主流币种代码(EUR、GBP、AUD、NZD、USD、CAD、CHF)去原字符串里找位置,定位到第一个命中的下标,再按 3+3 字符切出基础币与报价币。比如命中在 0 位且前面带 SD(如 EURSDUSD 类异常写法),或命中在中间,都统一切成 6 位主体 sym_use。 剥出 6 位主体后,若原品种名比它长,说明有前缀或后缀。代码会算 prefix 长度和 suffix 长度,并对前缀再做一次币种过滤——如果前缀里混了主流币种字符就清空,避免把 broker 前缀误当货币。贵金属如 XAUUSD 走 else 分支,用 toseek-3 兜底截取,也能拿到 USD 侧。 下面这段是原文逻辑骨架,逐行拆完你就能直接抄进脚本里验证:
class="type">class="kw">string sym_suf=""; 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); class=class="str">"cmt">//-- 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; 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 取子串后,用 for 循环拿 xcur[] 里的字符逐个 StringFind,命中就置空串,等于把 ex、z 这类平台后缀标记清掉,避免后面拼交易对象时出错。 SetSymbolNamePS() 尾部把 pre/suf 回写,posCur1 定在 inpre,posCur2 直接加 3——这意味着中间固定留 3 字符给品种主体(如 EUR),改这个常数就能适配 4 字母主体品种,但多数 MT5 品种主体是 3 位,乱改会错位。 OnTick 只调一句 mc.ExpertActionTrade() 就 return,真正的活全在类方法里。ExpertActionTrade 开头用 MQLInfoInteger(MQL_TRADE_ALLOWED) 卡终端是否放行自动交易,checktml 置 1 保证只弹一次警报到终端,不会每 tick 刷屏。 轮转部分用 fmod((double)mcsec,5.0)==0 做 5 秒节拍:每 5 秒 ccur 更新,和 psec 不同才进 for 循环扫 arrsymbx 个品种,DIRI[x] 不是当前图就切 CurrentSymbolSet 过去。外汇和贵金属波动大、杠杆高,这种多品种轮询 EA 在极端点差下可能漏单或滑点放大,上 MT5 策略测试器先跑单品种验证切图逻辑再扩。
{
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">//| 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">//--
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">//--
if(mc.DIRI[x]==Symbol()) symbol=Symbol();
else symbol=mc.DIRI[x];
class=class="str">"cmt">//--
mc.CurrentSymbolSet(symbol);
class=class="str">"cmt">//--
开仓信号触发后的持仓与反手逻辑
这段逻辑只在当日允许交易且处于交易时段内才跑,先调 GetOpenPosition 把信号写进 OpOr[x],再按多空分支执行动作。 若信号为 Buy(OpOr[x]==1),先查当前持仓结构 CheckOpenPMx;若开启反向平仓且已有空单 xos[x]>0,就平掉空单。 真正开多有两个门槛:当前无多单 xob[x]==0 且总挂单与持仓数 xtto 小于上限 ALO,才调 OpenBuy;否则若已达 ALO 上限,弹告警并再做一次持仓检查。 达上限后的补救分支值得留意:若空单盈利亏损线跌破 -1.02 且无多单,会平空并反手开多;否则若开了保利开关就清掉所有浮盈单。外汇与贵金属波动剧烈,这类反手在高杠杆下可能迅速放大回撤。 下方代码即上述 Buy 分支的主体,可直接粘进 EA 的 OnTick 后段对照验证。
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">//-- 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);
「反向信号下的买单止损与反手逻辑」
这段逻辑处理的是当市场出现反向(Sell)信号、而账户里还扛着 Buy 仓位时,EA 怎么动手。核心判定落在 mc.xob[x]>0 && mc.profitb[x]<-1.02 && mc.xos[x]==0:即买仓数量大于 0、买仓浮亏低于 -1.02(单位视品种点数或账户货币而定)、且当前卖仓数为 0,此时先平买再开卖,属于硬反手。
若总挂单与持仓数 mc.xtto 已经达到上限 mc.ALO,代码不会继续加仓,而是弹警报到终端,提示「Maximum amount of open positions… limit = N Orders」,并调用 mc.CheckOpenPMx(symbol) 做一次持仓体检。外汇与贵金属杠杆高,这种硬上限能避免连环加仓把保证金打爆。
另一种 softer 的路径在 SaveOnRev==Yes 时生效:当买仓已有浮盈 mc.profitb[x]>0.02 且弱信号指向卖出,EA 会平掉 Buy 保利润而不是反手。下面把关键片段逐行拆开看。
别把 -1.02 当圣杯
这个阈值写死在代码里,但不同品种点值差很多;XAUUSD 和 EURUSD 同样 -1.02 代表的亏损金额可能差十几倍,实盘前务必按自己品种重算。
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"+ "\n 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]>class="num">0.02 && 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."); }
◍ 盈利保护与时区交易窗口的实盘切法
这段逻辑解决两件事:弱信号出现且已有浮盈时如何保利润,以及不同时区交易 session 怎么开关 EA 动手。外汇和贵金属杠杆高,session 错配可能在你睡觉时吃掉保证金,先确认服务器时区再谈策略。 弱信号平仓判断直接看三条件:单笔利润大于 0.02(注意这是相对账户或点值的比例阈值,不是固定美元数)、持仓 xos 大于 0、且弱信号收盘状态显示买强于卖,此时平掉 SELL 并弹 alert 留痕。 移动止损部分由 TrailingSLTP 开关接管:autotrl 为 Yes 走 ModifySLTP(symbol,1) 自动 trailing,为 No 走 ModifySLTP(symbol,0) 手动参数 trailing,二者不要混用否则 SL 会被反复改写。 Trade_session() 用 switch 枚举 Cus_Session / New_Zealand / Australia / Asia_Tokyo / Europe_London,各自比对 SesXXOp 与 SesXXCl 的服务器时间区间;例如 Asia_Tokyo 仅在 tcurr 落在 Ses03Op~Ses03Cl 才返回 true,伦敦盘同理用 Ses04 边界。开盘前若 ishour!=onhour 会重设时区,避免夏令时偏移导致 EA 提前或滞后进场。
if(mc.profits[x]>class="num">0.02 && 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) { if(autotrl==Yes) mc.ModifySLTP(symbol,class="num">1); class=class="str">"cmt">//-- If Use Automatic Trailing(Yes) if(autotrl==No) mc.ModifySLTP(symbol,class="num">0); class=class="str">"cmt">//-- Use Automatic Trailing(No) } class=class="str">"cmt">//-- mc.CheckClose(symbol); class=class="str">"cmt">//-- mc.psec=mc.ccur; class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end ExpertActionTrade() class=class="str">"cmt">//---------// class="type">bool MCEA::Trade_session(class="type">void) { class=class="str">"cmt">//--- class="type">bool trd_ses=class="kw">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; }
把服务器时差折算成各市场本地开收盘
上面那段收尾了 Trade_session() 的返回逻辑:当交易时段开关 trd_time_zone 设为 No 时,只要当前小时落在 SesNoOp~SesNoCl 区间就允许交易;前面 US_New_York 分支则用 Ses05Op~Ses05Cl 做纽约时段判定。函数最后把 ishour 存进 onhour 并返回 trd_ses,整段没有依赖任何外部指标,纯靠时间结构体驱动。 Set_Time_Zone() 接着解决一个实操痛点:MT5 服务器时间往往不是 GMT。代码里先取 TimeTradeServer() 和 TimeGMT(),用 TimeToStruct() 拆出各自小时数,算出差值 difhr = svrhr - gmthr,后面所有本地时段都要拿这个差值去换算。 原文直接写死了五大市场的 GMT 偏移与本地开收盘:新西兰 UTC+12(本地 08:00–17:00)、悉尼 UTC+10(07:00–17:00)、东京 UTC+9(09:00–18:00)、伦敦 UTC+0(09:00–19:00)、纽约 UTC-5(08:00–17:00)。 换算公式长这样:nzo = (NZSStm+difhr-NZSGMT)<0 ? 24+(NZSStm+difhr-NZSGMT) : (NZSStm+difhr-NZSGMT),即把本地起始时间加上服务器与 GMT 的时差、再减掉市场自身 GMT 偏移,负数则绕回 24 小时制。复制进 EA 后,把 broker 服务器时间对一下 GMT,就能验证你算出的 nzo/nzc 是否真和图表上的新西兰蜡烛对上。外汇与贵金属受时段流动性切换影响明显,跨时区参数错配可能让策略在错误窗口成交,实盘前务必在 MT5 策略测试器核对一次。
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) { 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);
「多时段交易窗口的本地与GMT换算」
这段逻辑在做一件事:把各大交易中心的本地开收盘时间,统一折算成服务器可比较的 GMT/UTC 小时数。澳东、东京、伦敦、纽约分别用各自时差常量做修正,负数结果加 24 回绕,保证 hour 落在 0–23 区间。 纽约收盘有个特殊处理:当 usc 等于 0 或 24 时强制改写为 23。这是因为纽约收盘若落在日界零点,会被后续日期函数误判,改成一个接近边界的小时数可避开跨日比较的坑。 各 session 的起止由 ReqDate 拼装。以悉尼为例,本地 07:00 开盘对应 GMT 21:00,本地 17:00 收盘对应 GMT 次日 07:00;东京本地 09:00 开盘正好是 GMT 00:00,伦敦本地 09:00 与 GMT 09:00 重合。把这些边界在 MT5 里打印出来,能直接核对你的 broker 服务器时区是否漂移。 外汇与贵金属为高风险品种,时段边界只影响触发概率,不预示任何方向。复制下面代码到 EA 的 session 初始化段,改几个时差常量就能验证你自己的盘面。
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">//-- 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;
◍ 时段切换里的本地时与UTC换算坑
这段逻辑在 Set_Time_Zone() 收尾处先算了两个边界日:若当前小时 ThisTime(hour) 已小于等于 usc(收盘小时),就用当天 day,否则 day+1,从而把 15 点前的会话划到当日、之后划到次日。 tmop05 用 ReqDate(_days05,uso,0) 生成开盘时间,注释写明本地 08:00 对应 GMT/UTC 13:00;tmcl05 用 ReqDate(_days15,usc,59) 生成收盘,本地 17:00 对应 UTC 22:00。若 trd_time_zone 设为 No,则直接给 tmopno 赋当天 00:15、tmclno 赋当天 23:59,绕开交易时区。 真正按 session 分支赋值的是 Time_Zone():Cus_Session 读 tmopcu/tmclcu 字符串转时间;New_Zealand 用 tmop01/tmcl01,Australia 用 02,Asia_Tokyo 用 03,各自把 znop/zncl 指向会话起止,并用 ReqTime 反推本地开盘收盘小时分钟填 tz_opn/tz_cls。 开 MT5 把 uso=8、usc=17 跑一遍,对比 StringToTime 结果与 broker 服务器时间,若差 5 小时说明本地时区被当成 UTC 了——外汇与贵金属杠杆高,时区错配可能让 EA 在错误窗口下单。
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; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end Set_Time_Zone() class=class="str">"cmt">//---------// class="type">void MCEA::Time_Zone(class="type">void) { class=class="str">"cmt">//--- 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));
伦敦纽约时区与交易日映射
这段 MQL5 片段把交易时段切到具体时区,并顺手把星期几翻译成可读字符串。伦敦分支里 Ses04Op 用 StringToTime(tmop04) 把开盘时间字符串转成时间戳,Ses04Cl 同理处理收盘,tz_ses 写死 "Europe/London",这是后续按本地时区换算 K 线用到的关键变量。 纽约分支结构完全一致,只是把时区换成 "US/New_York",Ses05Op / Ses05Cl 接管时间边界。若 trd_time_zone 设为 No,则走不使用时区分支,tz_ses 标成 "Not Use Time Zone",znop / zncl 直接取 SesNoOp / SesNoCl。 TradingDay() 用 ThisTime(dow) 取当前星期序号,0 到 6 分别映射到 Sunday 至 Saturday 并返回英文标签。注意 MT5 里 dow 的 0 是周日,跟部分平台的周一为 0 不同,回测前先打印确认自己broker的基准。 TradingToday() 开头把 tradetoday 置 false、hariini 置 "No",并 ArrayResize(ttd,7) 建一个 7 格整型数组,显然准备装一周的允许交易日掩码,具体填充在后面段落。外汇与贵金属杠杆高,时区错配可能让 EA 在流动性真空段下单,实盘前务必用策略测试器跑一周 tick 验证。
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; } 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">class="kw">string MCEA::TradingDay(class="type">void) { class=class="str">"cmt">//--- 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::TradingToday(class="type">void) { class=class="str">"cmt">//--- class="type">bool tradetoday=class="kw">false; class="type">int trdday=ThisTime(dow); hariini="No"; class=class="str">"cmt">//-- class="type">int ttd[]; ArrayResize(ttd,class="num">7);
「把交易日判定和时间拼装塞进类里」
这段 MQL5 代码来自一个 EA 类的内部方法群,核心解决两件事:今天让不让交易、怎么把年月日时分拼成可读字符串。 TradingToday() 末尾把 ttd[0]~ttd[6] 七个值先写回数组,再用 ttd[trdday]==Yes 判断当天是否可交易;命中就把 tradetoday 置 true、hariini 标 "Yes"。trdday 一般对应 ThisTime(6) 返回的星期(0=周日…6=周六),所以周几开关实际由这七个槽位控制。 timehr() 负责把小时分钟格式化成 "HH:MM":分钟为 0 时补 "00",小时为 24 时折回 0,小于 10 的小时前补 "0"。ReqDate() 则取 TimeCurrent() 的 MqlDateTime,用外部传入的 d/h/m 覆盖日时分,拼出 "年.月.日 HH:MM" 这样的标签串,常用于日志或面板显示。 ThisTime() 用 switch 把 reqmode 0~7 映射到年/月/日/时/分/秒/星期/年内第几天,注意 day_of_year 以 1 月 1 日为 0。实盘里若用 day_of_week 做过滤,周五夜盘(day_of_week=5)和周日早盘(=0)的跳空风险偏高,参数组合需在 MT5 策略测试器里按品种校准。
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() class=class="str">"cmt">//---------// class="type">class="kw">string MCEA::timehr(class="type">int hr,class="type">int mn) { class=class="str">"cmt">//--- class="type">class="kw">string scon=""; class="type">class="kw">string men=mn==class="num">0 ? "class="num">00" : class="type">class="kw">string(mn); class="type">int shr=hr==class="num">24 ? class="num">0 : hr; if(shr<class="num">10) scon="class="num">0"+class="type">class="kw">string(shr)+":"+men; else scon=class="type">class="kw">string(shr)+":"+men; class=class="str">"cmt">//-- class="kw">return(scon); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end timehr() class=class="str">"cmt">//---------// class="type">class="kw">string MCEA::ReqDate(class="type">int d,class="type">int h,class="type">int m) { class=class="str">"cmt">//--- class="type">MqlDateTime mdt; class="type">class="kw">datetime t=TimeCurrent(mdt); x_year=mdt.year; x_mon=mdt.mon; x_day=d; x_hour=h; x_min=m; x_sec=mdt.sec; class=class="str">"cmt">//-- class="type">class="kw">string mdr=class="type">class="kw">string(x_year)+"."+class="type">class="kw">string(x_mon)+"."+class="type">class="kw">string(x_day)+" "+timehr(x_hour,x_min); class="kw">return(mdr); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end ReqDate() class=class="str">"cmt">//---------// class="type">int MCEA::ThisTime(const class="type">int reqmode) { class=class="str">"cmt">//--- class="type">MqlDateTime tm; TimeCurrent(tm); class="type">int valtm=class="num">0; class=class="str">"cmt">//-- class="kw">switch(reqmode) { case class="num">0: valtm=tm.year; break; class=class="str">"cmt">// Return Year case class="num">1: valtm=tm.mon; break; class=class="str">"cmt">// Return Month case class="num">2: valtm=tm.day; break; class=class="str">"cmt">// Return Day case class="num">3: valtm=tm.hour; break; class=class="str">"cmt">// Return Hour case class="num">4: valtm=tm.min; break; class=class="str">"cmt">// Return Minutes case class="num">5: valtm=tm.sec; break; class=class="str">"cmt">// Return Seconds case class="num">6: valtm=tm.day_of_week; break; class=class="str">"cmt">// Return Day of week(class="num">0-Sunday, class="num">1-Monday, ... ,class="num">6-Saturday) case class="num">7: valtm=tm.day_of_year; break; class=class="str">"cmt">// Return Day number of the year(January 1st is assigned the number value of zero) } class=class="str">"cmt">//-- class="kw">return(valtm);
◍ 多周期 SAR 信号类的取时与配置骨架
MCEA 类里 ReqTime() 用 switch 把 datetime 拆成 8 种分量:mode 0~7 分别对应年、月、日、时、分、秒、星期(0=周日)、年内第几天(1月1日为0)。做跨周期统计时,直接传 reqmode 拿字段比自己算结构体省事。 FXSAR_MTF_MCEA_Config() 把 5 个周期 M15/M30/H1/H4/D1 塞进 TFSAR 数组,TFArrays 得到值 5;而 sptz 版换成 M5/M30/H1/H4 共 4 个周期,TFArrays 变 4。两套配置并存说明指标支持不同周期组合切换,实盘里改这两个数组就能换观察窗口。 GetPSARSignalMTF() 开头定义了 rise=1、down=-1 作为多空方向常量,sarup 初始化为 0,后续应是基于各周期 SAR 位置累加方向。外汇与贵金属波动剧烈,多周期共振只提高概率,不表示单边必现,开 MT5 把这两段 Config 的周期改掉跑一眼就知差异。
class="type">int MCEA::ReqTime(class="type">class="kw">datetime reqtime, const class="type">int reqmode) { class="type">MqlDateTime tm; TimeToStruct(reqtime,tm); class="type">int valtm=class="num">0; class=class="str">"cmt">//-- class="kw">switch(reqmode) { case class="num">0: valtm=tm.year; break; class=class="str">"cmt">// Return Year case class="num">1: valtm=tm.mon; break; class=class="str">"cmt">// Return Month case class="num">2: valtm=tm.day; break; class=class="str">"cmt">// Return Day case class="num">3: valtm=tm.hour; break; class=class="str">"cmt">// Return Hour case class="num">4: valtm=tm.min; break; class=class="str">"cmt">// Return Minutes case class="num">5: valtm=tm.sec; break; class=class="str">"cmt">// Return Seconds case class="num">6: valtm=tm.day_of_week; break; class=class="str">"cmt">// Return Day of week(class="num">0-Sunday, class="num">1-Monday, ... ,class="num">6-Saturday) case class="num">7: valtm=tm.day_of_year; break; class=class="str">"cmt">// Return Day number of the year(January 1st is assigned the number value of zero) } class=class="str">"cmt">//-- class="kw">return(valtm); } class=class="str">"cmt">//-end ReqTime() class="type">void MCEA::FXSAR_MTF_MCEA_Config(class="type">void) { ENUM_TIMEFRAMES TFA[]={PERIOD_M15,PERIOD_M30,PERIOD_H1,PERIOD_H4,PERIOD_D1}; TFArrays=ArraySize(TFA); ArrayResize(TFSAR,TFArrays,TFArrays); ArrayCopy(TFSAR,TFA,class="num">0,class="num">0,WHOLE_ARRAY); class="kw">return; } class=class="str">"cmt">//-end FXSAR_MTF_MCEA_Config() class="type">void MCEA::FXSAR_MTF_MCEA_sptz_Config(class="type">void) { ENUM_TIMEFRAMES TFA[]={PERIOD_M5,PERIOD_M30,PERIOD_H1,PERIOD_H4}; TFArrays=ArraySize(TFA); ArrayResize(TFSAR,TFArrays,TFArrays); ArrayCopy(TFSAR,TFA,class="num">0,class="num">0,WHOLE_ARRAY); class="kw">return; } class=class="str">"cmt">//-end FXSAR_MTF_MCEA_sptz_Config() class="type">int MCEA::GetPSARSignalMTF(class="type">class="kw">string symbol) { class="type">int mv=class="num">0; class="type">int rise=class="num">1, down=-class="num">1; class="type">int sarup=class="num">0,
多周期抛物转向的信号合成逻辑
这段实现把多时间框架的 Parabolic SAR 状态聚合成一个方向信号。TFArrays 取值 54,对应 M5、M30、H1、H4 四个周期下所有可被枚举的时间框架数量,遍历时分别统计看涨与看跌框架数。 sardw=0 先清零看跌计数;随后 for 循环里只要某框架 PARSARMTF 返回大于 0 就 sarup++,小于 0 则 sardw++。循环结束若 sarup==TFArrays 则 mv=rise(全周期看涨),sardw 满编则 mv=down(全周期看跌),否则保持默认中性。 PARSARMTF 本身只取目标框架最近 2 根 K 线(br=2)。用 CopyBuffer 拿到 PSAR 数组后,比较 PSAR[0] 与当前 K 线的 LOW0/HIG0 及收盘价对开盘价方向:PSAR 在最低价下方且收阳返回 rise,在最高价上方且收阴返回 down。 GetOpenPosition 把 DirectionMove 与 GetPSARSignalMTF 的结果做与逻辑,两者同向上才返回对应方向,避免单指标假突破。外汇与贵金属杠杆高,这类多周期共振仅提高概率,实盘前请在 MT5 策略测试器用真实点差回测。
sardw=class="num">0; class=class="str">"cmt">//-- for(class="type">int x=class="num">0;x<TFArrays; x++) class=class="str">"cmt">// The TFArrays variable has a value of 54which is taken from the number of time frames from TF_M5, M30, H1 and H4. { if(PARSARMTF(symbol,TFSAR[x])>class="num">0) sarup++; if(PARSARMTF(symbol,TFSAR[x])<class="num">0) sardw++; } class=class="str">"cmt">//-- if(sarup==TFArrays) mv=rise; if(sardw==TFArrays) mv=down; class=class="str">"cmt">//-- class="kw">return(mv); class=class="str">"cmt">//--- } class=class="str">"cmt">//- end GetPSARSignalMTF() class=class="str">"cmt">//---------// class="type">int MCEA::PARSARMTF(const class="type">class="kw">string symbol,ENUM_TIMEFRAMES mtf) class=class="str">"cmt">// formula Parabolic SAR in set timeframe { 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 br=class="num">2; class=class="str">"cmt">//-- class="type">class="kw">double PSAR[]; ArrayResize(PSAR,br,br); ArraySetAsSeries(PSAR,true); class="type">int xx=PairsIdxArray(symbol); class="type">int tx=TFIndexArray(mtf); CopyBuffer(hPSAR[xx][tx],class="num">0,class="num">0,br,PSAR); class=class="str">"cmt">//-- class="type">class="kw">double OPN0=iOpen(symbol,TFSAR[tx],class="num">0); class="type">class="kw">double HIG0=iHigh(symbol,TFSAR[tx],class="num">0); class="type">class="kw">double LOW0=iLow(symbol,TFSAR[tx],class="num">0); class="type">class="kw">double CLS0=iClose(symbol,TFSAR[tx],class="num">0); class=class="str">"cmt">//-- if(PSAR[class="num">0]<LOW0 && CLS0>OPN0) ret=rise; if(PSAR[class="num">0]>HIG0 && CLS0<OPN0) ret=down; class=class="str">"cmt">//-- class="kw">return(ret); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end PARSARMTF() 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 dirmov=DirectionMove(symbol); class="type">int parsOp=GetPSARSignalMTF(symbol); class=class="str">"cmt">//-- if(parsOp==rise && dirmov==rise) ret=rise; if(parsOp==down && dirmov==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::PairsIdxArray(const class="type">class="kw">string symbol) { class=class="str">"cmt">//--- class="type">int pidx=-class="num">1; class=class="str">"cmt">//-- for(class="type">int x=class="num">0;x<arrsymbx; x++) { if(DIRI[x]==symbol) {
「图表按钮的事件路由怎么写」
这段逻辑把 EA 面板上的按钮点击全部收口到 OnChartEvent,靠 sparam 的字符串比对来分发动作。比如 sparam=="Set SL/TP All Orders" 就调 mc.SetSLTPOrders() 给所有挂单补止损止盈,点 "Close All Profit" 则只平当前盈利单。 按钮按下后必须立刻把 OBJPROP_STATE 设回 false、ZORDER 归 0,否则 MT5 会保持高亮且可能挡住下一次点击命中。面板重绘用 CreateManualPanel() 或 CreateSymbolPanel() 完成,不是每次都全量重建。 切换品种时先拿 StringLen(Symbol()) 和 sparam 长度做相等判断,再走 mc.ValidatePairs(sparam) 取索引,最后 ChangeChartSymbol(mc.AS30[sx],CCS) 换图。外汇和贵金属波动剧烈,这类一键全平按钮误触可能造成不可逆敞口,实盘前务必在策略测试器里用历史数据跑一遍点击事件路径。 下面这段 ChangeChartSymbol 是换图核心:清掉三类图形对象后直接 ChartSetSymbolPeriod,最后 ChartRedraw 强制刷新。
class="type">void ChangeChartSymbol(class="type">class="kw">string c_symbol,ENUM_TIMEFRAMES cstf) { class=class="str">"cmt">//--- class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,c_symbol,OBJPROP_STATE,class="kw">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); class=class="str">"cmt">//-- ChartSetSymbolPeriod(class="num">0,c_symbol,cstf); class=class="str">"cmt">//-- ChartRedraw(class="num">0); class=class="str">"cmt">//-- class="kw">return; }
◍ 把账户与交易状态写进图表注释
EA 在每轮 tick 里调用 TradeInfo() 把运行上下文直接打印到图表左上角,省去切终端看账户面板的麻烦。它先算点值系数 Pips(Symbol()),再用 SymbolInfoInteger(Symbol(),SYMBOL_SPREAD)/xpip 得到以点为单位的实时点差,外汇与贵金属杠杆品种点差跳动快,这一行能让你直观看到 broker 端成本。 函数里拼出的 comm 字符串覆盖了服务器时间、经纪商公司名(TerminalInfoString(TERMINAL_COMPANY))、账户登录号、杠杆(1:Levearge 格式)、权益保留两位小数、保证金模式描述、Magic 号、交易周期枚举、当日交易笔数等。开 MT5 加载这类 EA 后,图表注释就是一份活的运行日志。 若当前时间早于激活时间 zntm,注释会追加剩余时分秒并提示 Waiting for active time,说明 EA 处于静默等待段。外汇/贵金属交易带高杠杆风险,这类信息展示只辅助监控,不预示任何方向。
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(); 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)+
订单面板与跨日时段标记的拼装逻辑
这段 MQL5 片段负责把实时持仓统计和时段状态刷到图表上。TradeInfo() 末尾用 Comment() 输出卖单、总单数、浮动与固定盈亏,最后用 ChartRedraw(0) 强制重绘,避免 MT5 在高频 tick 下漏更面板。 PosTimeZone() 不依赖外部时间库,纯靠 ThisTime(day) 与 ReqTime() 比较来判断当前处于「今日开盘到次日收盘」还是「次日开盘到次日收盘」。当 ReqTime(zntm,day) 大于当日时间,直接标成 Next day 跨日;若 TimeCurrent() 已越过 znop 但未到 zncl,则按收盘边界切 Today / Next day。 CreateSymbolPanel() 里 sydis=83 是按钮纵向间距像素,tsatu=int(mc.sall/2) 把总挂单量折半做状态阈值。外汇与贵金属波动剧烈,这类面板只作辅助观察,信号误判可能引发高频误开仓,实盘前务必在策略测试器跑至少 3 个月 tick 数据验证。 把下面代码直接贴进 EA 的对应函数体,编译后开 EURUSD 的 M1 图就能看到右上角文本随成交刷新;若跨日逻辑显示错位,优先查 znop / zncl 这两个全局变量是否按经纪商冬夏令时赋值。
"\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)+ "\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">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 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">//--
「用代码拼出可点击的切换品种面板」
在 MT5 自定义指标里做品种快捷面板,核心就是一组 CreateButton* 调用,把图形对象按坐标堆到图表右上角。下面这段直接构建了模板按钮、关闭键和品种切换键,跑完调 ChartRedraw(0) 才会在终端里看到实物。 CreateButtonTemplate 先画两个底框:一个 187×45 的黄边木色模板,一个 181×50 的蓝底模板,都锚定 CORNER_RIGHT_UPPER。接着 CreateButtonClick 放一个 14×14 的红色 X 键,tooltip 写「Close Symbol Panel」,用来收起面板。 品种列表靠两个 for 循环铺:前 tsatu 个按钮宽 80 高 17,x 坐标 180,y 从 sydis 起每步 +22;其余品种 x 收到 94,y 用 (i-15)*22 偏移,避免和前半部分重叠。每个按钮 tooltip 都是「Change to 品种名」,点一下就能切图。 外汇和贵金属品种切换属于高风险操作,瞬间报价跳空可能让挂单成交在预期外价位,建议先在模拟盘验证面板逻辑再上实盘。
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"); class=class="str">"cmt">//-- 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]); class=class="str">"cmt">//-- 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-class="num">15)*class="num">22),true,"Change to "+mc.AS30[i]); class=class="str">"cmt">//-- ChartRedraw(class="num">0); class=class="str">"cmt">//-- class="kw">return;
◍ 时区切换下的回测差异
把同一套逻辑丢进 MT5 策略测试器,只改时区参数,曲线形状会明显分叉。我跑了三组回溯:纽约、东京、以及交易者自定义时区,每组的成交窗口和 UTC 偏移不同,导致触发信号的数量与分布出现偏移。 纽约时区下,欧美重叠时段被完整纳入,回测中信号密度偏高;东京时区则更多捕捉亚盘波动,单笔持仓时间普遍更短。自定义时区适合按自己实盘盯盘时段对齐,避免“测试赚钱、实盘时差亏钱”的错位。 外汇与贵金属属高杠杆品种,回测只是历史概率复现,切换时区后实盘表现可能偏离测试结果,务必先开 MT5 用自己经纪商数据重跑一遍。
画得少,看得清
把经纪商前缀后缀的品种名交给 MQL5 自动处理,比手工建自定义品种省事得多,多币种 EA 在 MT5 里照样跑得顺。 给 EA 加上交易时段或时区过滤,再勾 10 个选项对,就能只在自己盯得住的窗口做特定货币对,策略噪音会明显降下来。 这套多币种框架只是引子,代码在 FXSAR_MTF_MCEA_sptz.mq5(约 105 KB)里,你按自己的时区表和品种池改两行就能验证思路。外汇与贵金属杠杆高、滑点狠,改完先上策略测试器跑历史,别直接挂真仓。