如何利用 MQL5 创建简单的多币种智能交易系统(第 3 部分):添加交易品种、前缀和/或后缀、以及交易时段·综合运用
📘

如何利用 MQL5 创建简单的多币种智能交易系统(第 3 部分):添加交易品种、前缀和/或后缀、以及交易时段·综合运用

第 3/3 篇

◍ 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 拐点灵敏度会同步变化,外汇与贵金属波动剧烈,参数误设可能放大回撤风险。

MQL5 / C++
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 后续下单循环就只扫这些品种。外汇与贵金属杠杆高,实盘前务必在策略测试器用样本经纪商后缀验证符号匹配。

MQL5 / C++
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 侧。 下面这段是原文逻辑骨架,逐行拆完你就能直接抄进脚本里验证:

MQL5 / C++
  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 策略测试器先跑单品种验证切图逻辑再扩。

MQL5 / C++
  {
      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 后段对照验证。

MQL5 / C++
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 代表的亏损金额可能差十几倍,实盘前务必按自己品种重算。

MQL5 / C++
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 提前或滞后进场。

MQL5 / C++
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 策略测试器核对一次。

MQL5 / C++
   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 初始化段,改几个时差常量就能验证你自己的盘面。

MQL5 / C++
  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 在错误窗口下单。

MQL5 / C++
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 验证。

MQL5 / C++
      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 策略测试器里按品种校准。

MQL5 / C++
  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 的周期改掉跑一眼就知差异。

MQL5 / C++
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 策略测试器用真实点差回测。

MQL5 / C++
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 强制刷新。

MQL5 / C++
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 处于静默等待段。外汇/贵金属交易带高杠杆风险,这类信息展示只辅助监控,不预示任何方向。

MQL5 / C++
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 这两个全局变量是否按经纪商冬夏令时赋值。

MQL5 / C++
   "\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 品种名」,点一下就能切图。 外汇和贵金属品种切换属于高风险操作,瞬间报价跳空可能让挂单成交在预期外价位,建议先在模拟盘验证面板逻辑再上实盘。

MQL5 / C++
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)里,你按自己的时区表和品种池改两行就能验证思路。外汇与贵金属杠杆高、滑点狠,改完先上策略测试器跑历史,别直接挂真仓。

常见问题

把品种名拆成主体、前缀、后缀三段存数组,初始化时按经纪商实际符号拼接,避免硬编码导致选不到合约。
先在OnInit里用iSAR逐个品种建句柄并判空,周期不匹配或品种未上市会返回无效句柄,需跳过并写日志。
可以,把代码贴给小布,它能指出前缀后缀处理漏洞并建议逐秒轮转的执行骨架改法。
在信号函数里先遍历持仓按ticket平仓,确认无同方向仓后再用反向订单类型发单,防止重复开仓。
用定时器或OnTimer控制每秒一次轻量轮询,只做品种切换不干重活,实测对常规账户负载可接受。