如何利用 MQL5 创建简单的多币种智能交易系统(第 4 部分):三角移动平均线  指标信号·综合运用
📘

如何利用 MQL5 创建简单的多币种智能交易系统(第 4 部分):三角移动平均线 指标信号·综合运用

第 3/3 篇

◍ 给品种名自动补前缀后缀

在 MT5 里接不同经纪商时,品种名常被强加前缀(如 m.)或后缀(如 .pro),硬编码 EURUSD 会直接选不到合约。上面这段逻辑就是先拆出分隔符、把用户输入的品种串切分并转大写,再用 ValidatePairs 过滤掉非法项,最后统一拼上 pre / suf。 关键点在于:只要检测到 inpre>0 或 insuf>0,就对整个货币组数组(All30、USDs、EURs、GBPs、AUDs、NZDs、CADs 等)逐元素做 pre+原名+suf 的拼接;若交易模式设为 TrdWi 且用户自选数组 arspc>0,连自选品种 SPC 也一并处理。 实盘前你应当在 OnInit 里打印几次 ArraySize(SPC) 和拼接后的 SPC[0],确认 broker 的前后缀规则已被正确捕获,否则外汇与贵金属交易存在因选错合约而报错的高风险。

MQL5 / C++
u_sep=StringGetCharacter(sep,class="num">0);
class=class="str">"cmt">//--- Split the class="type">class="kw">string to substrings 
class="type">int p=StringSplit(to_split,u_sep,SPC);
if(p>class="num">0)
  {
   for(class="type">int i=class="num">0; i<p; i++) StringToUpper(SPC[i]);
   class=class="str">"cmt">//--
   for(class="type">int i=class="num">0; i<p; i++)
     {
      if(ValidatePairs(SPC[i])<class="num">0) ArrayRemove(SPC,i,class="num">1);
     }
  }
arspc=ArraySize(SPC);
 }
class=class="str">"cmt">//--
SetSymbolNamePS();      class=class="str">"cmt">// With this function we will detect whether the Symbol Name has a prefix and/or suffix
class=class="str">"cmt">//--
if(inpre>class="num">0 || insuf>class="num">0)
  {
   if(usepairs==TrdWi && arspc>class="num">0)
     {
      for(class="type">int t=class="num">0; t<arspc; t++)
        {
         SPC[t]=pre+SPC[t]+suf;
        }
     }
   class=class="str">"cmt">//--
   for(class="type">int t=class="num">0; t<sall; t++)
     {
      All30[t]=pre+All30[t]+suf;
     }
   for(class="type">int t=class="num">0; t<arusd; t++)
     {
      USDs[t]=pre+USDs[t]+suf;
     }
   for(class="type">int t=class="num">0; t<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++)
     {

「多币种数组落地与交易对筛选」

这段逻辑紧接前面的品种归类,把 CHF 与 JPY 两大组的后缀统一拼回原符号,再整体拷进 VSym 与 AS30 两个运行数组。ArrayResize 按 sall 长度重设 AS30,保证后面 SymbolSelect 能一次性把全部品种拉进市场报价窗。 若当前图表品种未通过 ValidatePairs 校验,symbfix 保持 false,EA 会连弹两条 Alert 并直接 ExpertRemove 退出——实盘加载前务必确认品种在白名单内,否则策略根本不会跑。 usepairs 开关决定 DIRI 最终装哪一组:0 为全部 30 个外汇对,1 为交易者自定义 SPC 数组,2 为 USD 相关对。每组都先按对应计数 arspc / arusd 重设 DIRI 尺寸,再 ArrayCopy 填充,pairs 字符串仅用于日志标识。 开 MT5 把 usepairs 改成 2,观察 DIRI 元素数是否等于 arusd 返回值;若返回 0 说明 USD 组为空,需回查 USDs 拼接段。外汇与贵金属杠杆品种波动剧烈,参数误配可能让 EA 在异常品种上尝试下单。

MQL5 / C++
   CHFs[t]=pre+CHFs[t]+suf;
      }
      for(class="type">int t=class="num">0; t<aretc; t++)
        {
         JPYs[t]=pre+JPYs[t]+suf;
        }
     }
   class=class="str">"cmt">//--
   ArrayCopy(VSym,All30,class="num">0,class="num">0,WHOLE_ARRAY);
   ArrayResize(AS30,sall,sall);
   ArrayCopy(AS30,All30,class="num">0,class="num">0,WHOLE_ARRAY);
   for(class="type">int x=class="num">0; x<sall; x++) {SymbolSelect(AS30[x],true);}
   if(ValidatePairs(Symbol())>=class="num">0) symbfix=true;
   if(!symbfix)
     {
      Alert("Expert Advisors will not trade on pairs "+Symbol());
      Alert("-- "+expname+" -- ",Symbol()," -- expert advisor will be Remove from the chart.");
      ExpertRemove();
     }
   class=class="str">"cmt">//--
   class="kw">switch(usepairs)
     {
      case class="num">0: class=class="str">"cmt">// All Forex class="num">30 Pairs
        {
         ArrayResize(DIRI,sall,sall);
         arrsymbx=sall;
         ArraySymbolResize();
         ArrayCopy(DIRI,All30,class="num">0,class="num">0,WHOLE_ARRAY);
         pairs="Multi Currency class="num">30 Pairs";
         class=class="str">"cmt">//--
         break;
        }
      case class="num">1: class=class="str">"cmt">// Trader wishes pairs
        {
         ArrayResize(DIRI,arspc,arspc);
         arrsymbx=arspc;
         ArraySymbolResize();
         ArrayCopy(DIRI,SPC,class="num">0,class="num">0,WHOLE_ARRAY);
         pairs="("+class="type">class="kw">string(arspc)+") Trader Wishes Pairs";
         class=class="str">"cmt">//--
         break;
        }
      case class="num">2: class=class="str">"cmt">// USD pairs
        {
         ArrayResize(DIRI,arusd,arusd);
         arrsymbx=arusd;
         ArraySymbolResize();
         ArrayCopy(DIRI,USDs,class="num">0,class="num">0,WHOLE_ARRAY);
         pairs="("+class="type">class="kw">string(arusd)+") Multi Currency USD Pairs";
         class=class="str">"cmt">//--
         break;
        }

按币种分流货币对数组

在 MT5 的 EA 或指标里做多币种扫描时,常需要按基础货币把品种池拆开。下面这段 switch 的 case 3~7 就是分别处理 EUR、GBP、AUD、NZD、CAD 五大类直盘/交叉盘的分流逻辑。 每个分支先做 ArrayResize(DIRI,aretc,aretc) 把目标数组 DIRI 扩到 aretc 长度,再把 arrsymbx 记为当前品种数,调用 ArraySymbolResize() 同步符号缓冲,最后用 ArrayCopy 把对应币种预存数组(EURs / GBPs / AUDs / NZDs / CADs)整块拷进 DIRI,并拼出带计数的标签如 "(N) Forex EUR Pairs"。 aretc 是前面统计出的该类有效品种数,若你的券商 MT5 里 EUR 相关符号有 7 个,标签就会显示 "(7) Forex EUR Pairs"。外汇和贵金属杠杆高、点差跳变频繁,这类批量数组操作前务必在策略测试器用真实品种池跑一遍,避免残留上一次品种的越界。

MQL5 / C++
case class="num">3: class=class="str">"cmt">// EUR pairs
  {
   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";
   class=class="str">"cmt">//--
   break;
  }
case class="num">4: class=class="str">"cmt">// GBP pairs
  {
   ArrayResize(DIRI,aretc,aretc);
   arrsymbx=aretc;
   ArraySymbolResize();
   ArrayCopy(DIRI,GBPs,class="num">0,class="num">0,WHOLE_ARRAY);
   pairs="("+class="type">class="kw">string(aretc)+") Forex GBP Pairs";
   class=class="str">"cmt">//--
   break;
  }
case class="num">5: class=class="str">"cmt">// AUD pairs
  {
   ArrayResize(DIRI,aretc,aretc);
   arrsymbx=aretc;
   ArraySymbolResize();
   ArrayCopy(DIRI,AUDs,class="num">0,class="num">0,WHOLE_ARRAY);
   pairs="("+class="type">class="kw">string(aretc)+") Forex AUD Pairs";
   class=class="str">"cmt">//--
   break;
  }
case class="num">6: class=class="str">"cmt">// NZD pairs
  {
   ArrayResize(DIRI,aretc,aretc);
   arrsymbx=aretc;
   ArraySymbolResize();
   ArrayCopy(DIRI,NZDs,class="num">0,class="num">0,WHOLE_ARRAY);
   pairs="("+class="type">class="kw">string(aretc)+") Forex NZD Pairs";
   class=class="str">"cmt">//--
   break;
  }
case class="num">7: class=class="str">"cmt">// CAD pairs
  {
   ArrayResize(DIRI,aretc,aretc);
   arrsymbx=aretc;
   ArraySymbolResize();
   ArrayCopy(DIRI,CADs,class="num">0,class="num">0,WHOLE_ARRAY);
   pairs="("+class="type">class="kw">string(aretc)+") Forex CAD Pairs";
   class=class="str">"cmt">//--

◍ 按币种分组装载与品种前缀剥离

这段逻辑落在 HandlingSymbolArrays() 的收尾分支里,用 switch 的 case 8、case 9 把瑞郎系与日元系外汇对分别塞进运行数组 DIRI。case 8 先按 aretc 扩容 DIRI,记录 arrsymbx=aretc,调用 ArraySymbolResize() 同步符号缓冲,再用 ArrayCopy 从 CHFs 整组搬入,pairs 字符串会拼出「(N) Forex CHF Pairs」这样的运行时标签,N 即 aretc 实际数量。 case 9 对 JPYs 做完全对称的处理,仅数据源换成日元对数组,pairs 变为「(N) Forex JPY Pairs」。两个分支都以 break 结束,不向下穿透,说明分组装载是互斥的单选路径。 SetSymbolNamePS() 负责把当前图表品种的前后缀拆出来。它先取 Symbol() 拿到原始名,量测 prefix 与 suffix 长度;若前后缀非空,连续调 StringTrimLeft / StringTrimRight 处理 suffix 变量(注意这里原代码对 suffix 做了两次修剪,pre 实际仍等于原始 prefix)。最后 posCur1 设为 inpre,posCur2 = inpre+3,相当于默认锁定「前缀之后紧接的 3 位基础币种代码」这一截取位。 开 MT5 把这段粘进 EA 类,在 EURCHF、USDJPY 上各挂一个实例,Print(posCur2) 就能验证截取起点是否如预期落在币种代码首字符;外汇与贵金属杠杆品种波动剧烈,此类字符串解析错一位就可能导致信号绑定到错误品种,回测前务必人工核对 pairs 输出。

MQL5 / C++
      break;
      }
      case class="num">8: class=class="str">"cmt">// CHF pairs
      {
         ArrayResize(DIRI,aretc,aretc);
         arrsymbx=aretc;
         ArraySymbolResize();
         ArrayCopy(DIRI,CHFs,class="num">0,class="num">0,WHOLE_ARRAY);
         pairs="("+class="type">class="kw">string(aretc)+") Forex CHF Pairs";
         class=class="str">"cmt">//--
         break;
      }
      case class="num">9: class=class="str">"cmt">// JPY pairs
      {
         ArrayResize(DIRI,aretc,aretc);
         arrsymbx=aretc;
         ArraySymbolResize();
         ArrayCopy(DIRI,JPYs,class="num">0,class="num">0,WHOLE_ARRAY);
         pairs="("+class="type">class="kw">string(aretc)+") Forex JPY Pairs";
         class=class="str">"cmt">//--
         break;
      }
      }
   class=class="str">"cmt">//--
   class="kw">return;
class=class="str">"cmt">//---
   } class=class="str">"cmt">//-end HandlingSymbolArrays()
class=class="str">"cmt">//---------//
class="type">void MCEA::SetSymbolNamePS(class="type">void)
  {
class=class="str">"cmt">//---
  symbfix=false;
  class="type">int ptriml;
  class="type">int ptrimr;
  class="type">class="kw">string insymbol=Symbol();
  class="type">int sym_Lenpre=StringLen(prefix);
  class="type">int sym_Lensuf=StringLen(suffix);
  if(sym_Lenpre>class="num">0)
   {
     ptriml=StringTrimLeft(suffix);
     ptriml=StringTrimRight(suffix);
   }
  if(sym_Lensuf>class="num">0)
   {
     ptrimr=StringTrimLeft(suffix);
     ptrimr=StringTrimRight(suffix);
   }
  class="type">class="kw">string sym_pre=prefix;
  class="type">class="kw">string sym_suf=suffix;
  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()

「全局变量里的交易时段与符号前缀拆解」

这段声明把所有跨函数要用的状态都摊在全局区,先看时间切片:x_day 到 x_sec 四个 int 分别吃日期、小时、分钟、秒,等于把当前 MT5 服务器时间拆成可比较的零件。 oBm、oSm、ldig 这三个整型后面通常用来算报价小数位和订单魔术号偏移,属于底层计数。posCur1、posCur2 配合 inpre、insuf 以及 pre、suf 字符串,是在做品种名前后缀的定位——比如 EURUSD 后面带 .pro 或前缀带 0.,靠这几个变量切。 交易时段部分最重:ishour、onhour 是开关小时,Ses01Op 到 Ses05Cl 共 10 个 datetime 把五个会话的开收都存下来,外加 SesCuOp/SesCuCl 当前会话与 SesNoOp/SesNoCl 无会话标记。rem、znop 等记录剩余时间与 zone 边界。 别把正态当圣经:全局塞这么多会话变量,意味着 EA 在每帧都重算时区边界,回测时若历史会话数超过 5 段会直接漏算,开 MT5 把 Ses 数组打到 10 段以上验证一下。

MQL5 / C++
class="type">int x_day; class=class="str">"cmt">// Day of the month
class="type">int x_hour; class=class="str">"cmt">// Hour in a day
class="type">int x_min; class=class="str">"cmt">// Minutes
class="type">int x_sec; class=class="str">"cmt">// Seconds
class=class="str">"cmt">//--
class="type">int oBm,
    oSm,
    ldig;
class=class="str">"cmt">//--- Variables used in prefix and suffix symbols
class="type">int posCur1,
    posCur2;
class="type">int inpre,
    insuf;
class="type">bool symbfix;
class="type">class="kw">string pre,suf;
class="type">class="kw">string prefix,suffix; class=class="str">"cmt">//--- Variables are used in Trading Time Zone
class="type">int ishour,
    onhour;
class="type">int tftrlst,
    tfcinws;
class="type">class="kw">datetime rem,
    znop,
    zncl,
    zntm;
class="type">class="kw">datetime SesCuOp,
    SesCuCl,
    Ses01Op,
    Ses01Cl,
    Ses02Op,
    Ses02Cl,
    Ses03Op,
    Ses03Cl,
    Ses04Op,
    Ses04Cl,
    Ses05Op,
    Ses05Cl,
    SesNoOp,
    SesNoCl;
class=class="str">"cmt">//--
class="type">class="kw">string tz_ses,
    tz_opn,
    tz_cls;
class=class="str">"cmt">//--

EA 全局变量的声明骨架

这段声明块定义了多品种半自动策略的核心结构体成员,开 MT5 把这段代码贴进 class 或全局作用域,就能直接看到编译器如何为后续逻辑分配内存。 前一组 string 变量 tmopcutmclcutmop05tmcl05 以及 tmopnotmclno,是用来缓存不同交易时段(currency / 01~05 / no-session)的开仓与平仓注释文本,共 14 个字符串槽位。 double 组的 LotPSslvtpvpipxpip 管手数与止损止盈价;floatprofitfixclprofit 分离了浮动盈利和固定平仓盈利,回测时这两值差可能直接决定平仓触发顺序。 行情容器 OPEN[]HIGH[]LOW[]CLOSE[]TIME[] 全为动态数组,配合 closetime 做跨根 K 线状态判断;最后 8 个函数原型里 Trade_session() 返回 bool,是时段过滤的总开关,改它返回值能立刻让 EA 在伦敦盘外停止下单。 外汇与贵金属波动剧烈,这类全局变量若未初始化就调用,MT5 策略测试器会报数组越界,实盘前务必用脚本跑一遍空窗期验证。

MQL5 / C++
class="type">class="kw">string tmopcu,
                  tmclcu,
                  tmop01,
                  tmcl01,
                  tmop02,
                  tmcl02,
                  tmop03,
                  tmcl03,
                  tmop04,
                  tmcl04,
                  tmop05,
                  tmcl05,
                  tmopno,
                  tmclno;                  
class=class="str">"cmt">//----------------------   
class=class="str">"cmt">//--
class="type">class="kw">double LotPS;
class="type">class="kw">double slv,
                  tpv,
                  pip,
                  xpip;                  
class="type">class="kw">double floatprofit,
                  fixclprofit;
class=class="str">"cmt">//--
class="type">class="kw">string pairs,
                  hariini,
                  daytrade,
                  trade_mode;
class=class="str">"cmt">//--
class="type">class="kw">double OPEN[],
                  HIGH[],
                  LOW[],
                  CLOSE[];
class="type">class="kw">datetime TIME[];
class="type">class="kw">datetime closetime;
class=class="str">"cmt">//--
class=class="str">"cmt">//------------

class=class="str">"cmt">//------------
class="type">void SetSymbolNamePS(class="type">void);
class="type">void HandlingSymbolArrays(class="type">void);
class="type">void Set_Time_Zone(class="type">void);
class="type">void Time_Zone(class="type">void);
class="type">bool Trade_session(class="type">void);
class="type">class="kw">string PosTimeZone(class="type">void);
class="type">int ThisTime(const class="type">int reqmode);

◍ 多周期三角均线类的接口与配置字段

下面这段声明来自一个多周期三角均线策略类(TriangularMA_MTF_MCEA)的内部结构,把时间请求、信号判断、订单风控和字符转换四类函数集中在一个私有区,方便 EA 在 MT5 里统一调用。 私有函数按返回类型分了四组:int 组负责时间换算与多周期信号,例如 ReqTime() 接收 datetime 与模式参数返回整数时间戳,TriaMASMTF() 和 GetTriaMASignalMTF() 则分别做跨周期三角均线计算与信号提取;double 组专管仓位与止损止盈,MLots() 按品种算手数,OrderSLSet() / SetOrderSL() 区分开仓与持仓两种场景来给 SL 定价。 字符类里 TF2Str() 把 ENUM_TIMEFRAMES 转成可读周期串,GetCommentForOrder() 直接返回 expname 变量作为订单备注,省去重复拼接。最后 public 下方暴露了 DIRI[]、AS30[]、VSym[]、SPC[]、USD[]、EUR[] 等字符串数组,用作品种分组与方向配置——外汇和贵金属波动剧烈、杠杆风险高,这些数组若填错品种名,信号会直接失效。 开 MT5 新建 EA 时,可把这段原样贴进类声明,先只接 EUR[] 与 USD[] 做欧美盘回测,验证多周期信号函数是否按预期返回 0/1。

MQL5 / C++
class="type">int               ReqTime(class="type">class="kw">datetime reqtime,const class="type">int reqmode);
class=class="str">"cmt">//--
class="type">int               DirectionMove(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES stf);
class="type">int               TriaMASMTF(const class="type">class="kw">string symbol,ENUM_TIMEFRAMES mtf);
class="type">int               GetTriaMASignalMTF(class="type">class="kw">string symbol);
class="type">int               TriaMASignalSTF(const class="type">class="kw">string symbol);
class="type">int               LotDig(const class="type">class="kw">string symbol);
class=class="str">"cmt">//--
class="type">class="kw">double            MLots(const class="type">class="kw">string symbx);
class="type">class="kw">double            NonZeroDiv(class="type">class="kw">double val1,class="type">class="kw">double val2);
class="type">class="kw">double            OrderSLSet(const class="type">class="kw">string xsymb,ENUM_ORDER_TYPE type,class="type">class="kw">double atprice);
class="type">class="kw">double            OrderTPSet(const class="type">class="kw">string xsymb,ENUM_ORDER_TYPE type,class="type">class="kw">double atprice);
class="type">class="kw">double            SetOrderSL(const class="type">class="kw">string xsymb,class="type">ENUM_POSITION_TYPE type,class="type">class="kw">double atprice);
class="type">class="kw">double            SetOrderTP(const class="type">class="kw">string xsymb,class="type">ENUM_POSITION_TYPE type,class="type">class="kw">double atprice);
class="type">class="kw">double            TSPrice(const class="type">class="kw">string xsymb,class="type">ENUM_POSITION_TYPE ptype,class="type">int TS_type);
class=class="str">"cmt">//--
class="type">class="kw">string            ReqDate(class="type">int d,class="type">int h,class="type">int m);
class="type">class="kw">string            TF2Str(ENUM_TIMEFRAMES period);
class="type">class="kw">string            timehr(class="type">int hr,class="type">int mn);
class="type">class="kw">string            TradingDay(class="type">void);
class="type">class="kw">string            AccountMode();
class="type">class="kw">string            GetCommentForOrder(class="type">void)                { class="kw">return(expname); }
class=class="str">"cmt">//------------
class="kw">public:
class=class="str">"cmt">//---

class=class="str">"cmt">//-- TriangularMA_MTF_MCEA Config --
class="type">class="kw">string            DIRI[],
                  AS30[],
                  VSym[];
class="type">class="kw">string            SPC[];
class="type">class="kw">string            USD[];
class="type">class="kw">string            EUR[];

「多币种与三角均线矩阵的变量骨架」

这段声明把跨币种扫描和三角均线(TriMA)的句柄都摊开了:GBP/AUD/NZD/CAD/CHF/JPY 六个字符串数组用来装各币种的衍生符号,hTriMAt/s/m 分别挂三条不同周期的 TriMA 指标句柄,hTriMAb 是 [][11] 的二维句柄数组,说明底层对单个币种要并行盯 11 个时间框架的均线状态。 ALO、dgts、arrsar、arrsymbx 这几个 int 控制报价精度与数组容量,slip 用 ulong 存滑点上限——外汇与贵金属点差跳变频繁,实盘里 slip 设太小容易全部拒单,设太宽又可能吃掉本来薄利的空间,建议在 MT5 策略测试器里按品种波动分段试 2~5 点。 profitb/profits 双数组分开记买/卖方向的累计浮盈,Buy/Sell 只计信号次数不涉仓位;year/mon/day/hour/min/sec 直接拆系统时间,省掉用 TimeToStruct 再转换的开销。把下面代码原样贴进 EA 全局区,编译后从「导航器—全局变量」里就能看到这些句柄是否被成功分配。

MQL5 / C++
class="type">class="kw">string GBP[];
class="type">class="kw">string AUD[];
class="type">class="kw">string NZD[];
class="type">class="kw">string CAD[];
class="type">class="kw">string CHF[];
class="type">class="kw">string JPY[];
class=class="str">"cmt">//--
class="type">class="kw">string expname;
class="type">class="kw">string indiname;
class=class="str">"cmt">//--
class="type">int hTriMAt[];
class="type">int hTriMAs[];
class="type">int hTriMAm[];
class="type">int hTriMAb[][class="num">11];
class="type">int ALO,
    dgts,
    arrsar,
    arrsymbx;
class="type">int sall,
    arusd,
    aretc,
    arspc,
    arper;
class="type">ulong slip;
class=class="str">"cmt">//--
class="type">class="kw">double profitb[],
       profits[];
class=class="str">"cmt">//--
class="type">int Buy,
    Sell;
class="type">int ccur,
    psec,
    xtto,
    TFArrays,
    checktml;
class="type">int OpOr[],xob[],xos[];
class=class="str">"cmt">//--
class="type">int year,  class=class="str">"cmt">// Year
    mon,   class=class="str">"cmt">// Month
    day,   class=class="str">"cmt">// Day
    hour,  class=class="str">"cmt">// Hour
    min,   class=class="str">"cmt">// Minutes
    sec;   class=class="str">"cmt">// Seconds

EA 类里的日期与周期字段声明

这一段是 MQL5 多周期 EA 类定义里的成员声明,把星期与年内天数单独拎出来,说明后续逻辑会按 dow(0=周日…6=周六)和 doy(1 月 1 日记为 0)做时间过滤。 周期方面用 ENUM_TIMEFRAMES 定义了 TFt、TFts、TFT05、TFCWS 四个具体周期,另用 TFTri[] 数组承载三角均线多周期调用的周期集合,数组长度由外部配置决定。 类里还挂了 PanelExtra 布尔开关控制面板扩展信息,以及 MCEA 构造/析构函数和一批交易动作方法:从 CurrentSymbolSet、Pips 到 CloseAllOrders、TradingToday,覆盖品种切换、点值计算、报警、开平仓与当日交易权限检查。 把这些声明直接贴进 MT5 的 .mqh 头文件,编译后可在 OnInit 里给 TFTri[0]=PERIOD_M15 赋值验证多周期数组是否生效。

MQL5 / C++
                  dow,   class=class="str">"cmt">// Day of week(class="num">0-Sunday, class="num">1-Monday, ... ,class="num">6-Saturday) 
                  doy;   class=class="str">"cmt">// Day number of the year(January 1st is assigned the number value of zero)
class=class="str">"cmt">//--
  ENUM_TIMEFRAMES   TFt,
                  TFts,
                  TFT05,
                  TFCWS;
  ENUM_TIMEFRAMES  TFTri[];
class=class="str">"cmt">//--
  class="type">bool              PanelExtra;
class=class="str">"cmt">//------------
                  MCEA(class="type">void);
                  ~MCEA(class="type">void);
class=class="str">"cmt">//------------
class=class="str">"cmt">//--
  class="kw">virtual class="type">void      TriangularMA_MTF_MCEA_Config(class="type">void);
  class="kw">virtual class="type">void      ExpertActionTrade(class="type">void);
class=class="str">"cmt">//--
  class="type">void              ArraySymbolResize(class="type">void);
  class="type">void              CurrentSymbolSet(const class="type">class="kw">string symbol);
  class="type">void              Pips(const class="type">class="kw">string symbol);
  class="type">void              TradeInfo(class="type">void);
  class="type">void              Do_Alerts(const class="type">class="kw">string symbx,class="type">class="kw">string msgText);
  class="type">void              CheckOpenPMx(const class="type">class="kw">string symbx);
  class="type">void              SetSLTPOrders(class="type">void);
  class="type">void              CloseBuyPositions(const class="type">class="kw">string symbol);
  class="type">void              CloseSellPositions(const class="type">class="kw">string symbol);
  class="type">void              CloseAllOrders(class="type">void);
  class="type">void              CheckClose(const class="type">class="kw">string symbx);
  class="type">void              TodayOrders(class="type">void);
  class="type">void              UpdatePrice(const class="type">class="kw">string symbol,ENUM_TIMEFRAMES xtf);
  class="type">void              RefreshPrice(const class="type">class="kw">string symbx,ENUM_TIMEFRAMES xtf,class="type">int bars);
class=class="str">"cmt">//--
  class="type">bool              RefreshTick(const class="type">class="kw">string symbx);
  class="type">bool              TradingToday(class="type">void);

◍ 多周期三角均线的类接口与配置落点

下面这段是 EA 类 MCEA 里暴露给外部调用的核心方法声明,做多周期三角均线策略时基本绕不开。开仓、改止损止盈、平盈利单、查信号都在这层接口里,想接小布盯盘做二次开发可以直接复用。 bool OpenBuy(const string symbol); bool OpenSell(const string symbol); bool ModifyOrderSLTP(double mStop,double ordtp); bool ModifySLTP(const string symbx,int TS_type); bool CloseAllProfit(void); bool ManualCloseAllProfit(void); //-- int PairsIdxArray(const string symbol); int ValidatePairs(const string symbol); int TFIndexArray(ENUM_TIMEFRAMES TF); int GetOpenPosition(const string symbol); int GetSignalMidTF(const string symbol); int GetCloseInWeakSignal(const string symbol,int exis); //-- string getUninitReasonText(int reasonCode); //------------ //--- }; //-end class MCEA //---------// void MCEA::TriangularMA_MTF_MCEA_Config(void) { //-- HandlingSymbolArrays(); // With this function we will handle all pairs that will be traded //-- int arstr=0, arend=0; TFT05=PERIOD_M5; ENUM_TIMEFRAMES TFs[]={PERIOD_M5,PERIOD_M15,PERIOD_M30,PERIOD_H1,PERIOD_H2,PERIOD_H3,PERIOD_H4,PERIOD_H6,PERIOD_H8,PERIOD_H12,PERIOD_D1}; int arTFs=ArraySize(TFs); //-- for(int x=0; x<arTFs; x++) { if(singletf==x) TFt=TFs[x]; // TF for single-timeframe if(tfstart==x) arstr=x; // multi-timeframe start calculation timeframe if(tfclose==x) arend=x; // multi-timeframe end calculation timeframe } //-- if(arstr>=arend) { Alert("Error selecting Start and End Timeframe, Start Timeframe must be smaller than End Timeframe"); 逐行拆一下配置函数:HandlingSymbolArrays() 先处理要交易的品种数组;arstr 与 arend 初始化为 0,用来记多周期计算的起止下标。TFT05 写死为 PERIOD_M5,说明底层参考帧锁在 5 分钟。TFs 数组列了 11 个周期,从 M5 到 D1,ArraySize 取到长度 11。 for 循环把单周期帧 singletf、多周期起点 tfstart、终点 tfclose 映射成对应下标。若 arstr>=arend,直接弹窗报警说起始周期必须小于结束周期——这是多周期三角均线回测前最容易配错的一点,外汇与贵金属波动大,周期区间设反了策略可能完全不触发。 实盘接这套接口前,建议先在 MT5 策略测试器里把 tfstart 设 M15、tfclose 设 H4 跑一遍,确认报警不弹、GetSignalMidTF 有返回值再上模拟盘。

MQL5 / C++
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 GetSignalMidTF(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">//-end class MCEA
class=class="str">"cmt">//---------//
class="type">void MCEA::TriangularMA_MTF_MCEA_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">//--
   class="type">int arstr=class="num">0,
       arend=class="num">0;
   TFT05=PERIOD_M5;
   ENUM_TIMEFRAMES TFs[]={PERIOD_M5,PERIOD_M15,PERIOD_M30,PERIOD_H1,PERIOD_H2,PERIOD_H3,PERIOD_H4,PERIOD_H6,PERIOD_H8,PERIOD_H12,PERIOD_D1};
   class="type">int arTFs=ArraySize(TFs);
class=class="str">"cmt">//--
   for(class="type">int x=class="num">0; x<arTFs; x++)
     {
      if(singletf==x) TFt=TFs[x]; class=class="str">"cmt">// TF for single-timeframe
      if(tfstart==x)  arstr=x;    class=class="str">"cmt">// multi-timeframe start calculation timeframe
      if(tfclose==x)  arend=x;    class=class="str">"cmt">// multi-timeframe end calculation timeframe
     }
class=class="str">"cmt">//--
   if(arstr>=arend)
     {
      Alert("Error selecting Start and End Timeframe, Start Timeframe must be smaller than End Timeframe");

「多周期与单周期下的指标句柄分配」

EA 初始化时若检测到致命参数冲突,会弹出带品种名与移除提示的弹窗并立即调用 ExpertRemove() 卸载自身,避免脏状态跑单。 时间框架模式由 tfinuse 切换:MTF 模式下把 TFs 数组整体拷入 TFTri,元素数 = arend-arstr+1;追踪止损看 TFts(取中间偏后元素 TFs[tftrlst+arstr-1]),弱信号平仓看 TFCWS(取 arstr+1 位)。STF 模式则直接用用户指定 TFt 作止损周期,弱信号周期回退到 TFt 前两位(下限锁 1)。 随后对 arrsymbx 个品种分别用 iCustom 挂三条三角均线句柄:TFT05、TFCWS、TFts 各一条;若走 MTF,再补挂 TFTri[i] 上全部周期句柄,STF 则命中 TFs[i]==TFt 后 break,不浪费句柄。 开 MT5 把这段塞进 OnInit 尾部,打印 hTriMAb 数组长度,能直接看出 MTF 比 STF 多耗多少自定义指标资源,外汇与贵金属杠杆品种请先于模拟盘验证,实盘波动可能使句柄失效概率升高。

MQL5 / C++
Alert("-- "+expname+" -- ",Symbol()," -- expert advisor will be Remove from the chart.");
ExpertRemove();
}
class=class="str">"cmt">//--
class="kw">switch(tfinuse)
  {
   case MTF:
     {
      TFArrays=arend-arstr+class="num">1;
      ArrayResize(TFTri,TFArrays,TFArrays);
      ArrayCopy(TFTri,TFs,class="num">0,class="num">0,WHOLE_ARRAY);
      tfcinws=arstr+class="num">1;
      tftrlst=(class="type">int)TFArrays/class="num">2;
      TFts=TFs[tftrlst+arstr-class="num">1];   class=class="str">"cmt">// TF for Trailing Stop
      TFCWS=TFs[tfcinws];          class=class="str">"cmt">// TF for Close Order in weak signal
      break;
     }
   case STF:
     {
      TFArrays=arTFs;
      ArrayResize(TFTri,TFArrays,TFArrays);
      ArrayCopy(TFTri,TFs,class="num">0,class="num">0,WHOLE_ARRAY);
      tfcinws=TFIndexArray(TFt)-class="num">2 <=class="num">0 ? class="num">1 : TFIndexArray(TFt)-class="num">2;
      TFts=TFt;                    class=class="str">"cmt">// TF for Trailing Stop
      TFCWS=TFs[tfcinws];   class=class="str">"cmt">// TF for Close Order in weak signal
      break;
     }
  }
class=class="str">"cmt">//--
class=class="str">"cmt">//-- Triangular MA Indicators handle for all symbol
for(class="type">int x=class="num">0; x<arrsymbx; x++)
  {
   hTriMAs[x]=iCustom(DIRI[x],TFT05,indiname,Trmaperiod,Trprice);
   hTriMAm[x]=iCustom(DIRI[x],TFCWS,indiname,Trmaperiod,Trprice);
   hTriMAt[x]=iCustom(DIRI[x],TFts,indiname,Trmaperiod,Trprice);
   class=class="str">"cmt">//--
   for(class="type">int i=class="num">0; i<TFArrays; i++)
     {
      if(tfinuse==MTF) class=class="str">"cmt">// MTF indicator handle
        {
         hTriMAb[x][i]=iCustom(DIRI[x],TFTri[i],indiname,Trmaperiod,Trprice);
        }
      if(tfinuse==STF)
        {
         if(TFs[i]==TFt) class=class="str">"cmt">// Single-TF indicator handle
           {
            hTriMAb[x][i]=iCustom(DIRI[x],TFs[i],indiname,Trmaperiod,Trprice);
            break;

EA 主循环里的品种轮询与终端校验

这段逻辑出现在三角均线多周期 EA 的 OnTick 调用链里,先由 mc.ExpertActionTrade() 接管每一跳的报价事件,再在内部做终端权限检查和多品种轮询。 进场前先用 MQLInfoInteger(MQL_TRADE_ALLOWED) 判断 MT5 是否放开算法交易;若被终端设置禁用且 checktml 为 0,则弹一次告警并把 checktml 置 1,保证同会话只提醒一次,不刷屏。外汇与贵金属杠杆高,终端禁交易时硬跑 EA 可能触发异常平仓或报价拒绝,这条守卫不能省。 轮询节奏由秒级时间控:取 mc.ThisTime(mc.sec) 得到当前秒 mcsec,仅当 fmod((double)mcsec, 5.0)==0 才更新 mc.ccur,也就是每 5 秒触发一次品种切换扫描。 切换时按 mc.arrsymbx 长度遍历 DIRI 数组,若某项等于当前图 Symbol() 则复用,否则切到该品种并调用 mc.CurrentSymbolSet(symbol) 重载上下文。下面这段是轮询主体的骨架,可直接贴进 MT5 看执行路径。

MQL5 / C++
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="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)
     {
      mc.Do_Alerts(Symbol(),"Trading Expert at "+Symbol()+" are NOT Allowed by Setting.");
      mc.checktml=class="num">1;
      class="kw">return;
     }
   class=class="str">"cmt">//--
   if(!DisplayManualButton("M","C","R")) DisplayManualButton();
   class=class="str">"cmt">//--
   if(trade_info_display==Yes) mc.TradeInfo();
   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;
      for(class="type">int x=class="num">0; x<mc.arrsymbx && !IsStopped(); x++)
        {
         if(mc.DIRI[x]==Symbol()) symbol=Symbol();
         else symbol=mc.DIRI[x];
         class=class="str">"cmt">//--
         mc.CurrentSymbolSet(symbol);
        }
     }
  }

◍ 开仓信号触发后的持仓与限额逻辑

这段逻辑只在当日允许交易且处于交易时段内才跑,先通过 GetOpenPosition 拿方向信号存进 OpOr[x],再分流处理买/卖。 若 OpOr[x] 返回 Buy(值=1),先查一遍当前持仓与挂单,若开启反向平仓且已有卖单(xos[x]>0)就先平卖;当没有买仓且总挂单/持仓数 xtto 小于上限 ALO 时直接 OpenBuy。 一旦 xtto 已达 ALO,系统不再盲目加仓,而是弹警报告知“订单数已达上限”,并二次核对持仓:若已有卖单且浮亏超过 -1.02 又无买仓,则平卖后反手买;否则在 SaveOnRev 开启时把所有盈利单落袋。 外汇与贵金属杠杆高、滑点大,ALO 与 -1.02 这类阈值直接决定回撤幅度,建议先在 MT5 策略测试器用历史数据跑一遍,确认 xtto 计数与 xos[x] 符号在你的品种上不会误触。

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);

「反向信号下的买仓回收逻辑」

这段 MT5 EA 片段处理的是:当反向卖出信号出现、但当前还挂着买仓时,如何按规则平掉买仓并可能反手。外汇与贵金属杠杆高,这类自动反转若参数不当,可能在一波震荡里连续吃滑点。 核心判断在 mc.xtto 与限额 mc.ALO 的关系:未达上限时直接开卖;达到上限则不再加仓,只做告警与持仓体检。若买仓浮亏超过 -1.02(即亏损大于约 1.02 个计价单位),且无可卖挂单,则平买并开卖,属于硬性反手。 若开启 SaveOnRev,在弱信号下会优先保利润:买仓盈利大于 0.02 且反向判定为卖时,直接平买并弹窗提醒。下面把关键几行拆开看。 别把 -1.02 当通用止损线 原文亏损阈值写死在代码里,不同品种点值差异大,黄金和欧美对 -1.02 的实际回撤完全不是一个概念,移植前必须按合约重新标定。

MQL5 / C++
if(Close_by_Opps==Yes && mc.xob[x]>class="num">0) mc.CloseBuyPositions(symbol);
if(mc.xos[x]==class="num">0 && mc.xtto<mc.ALO) mc.OpenSell(symbol);
else
if(mc.xtto>=mc.ALO)
  {
  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 ");
  mc.CheckOpenPMx(symbol);
  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();
  }
mc.CheckOpenPMx(symbol);
if(mc.xtto>class="num">0)
  {
  if(SaveOnRev==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.");
      }

弱信号下的卖单保护与时区交易窗口

当持仓利润超过 0.02 且交叉指标读数大于 0,同时弱信号接口判定买入方向占优时,EA 会主动平掉卖单并弹警报告知「保利润」。这段逻辑本质是给空头头寸装一道软止损,避免在信号衰减时被反向拖穿。 trailing 开关打开后,系统按自动/手动两种模式调用 ModifySLTP:参数 1 走自动追踪,参数 0 走人工设定的止盈止损线。实盘里建议先在策略测试器用 1 跑两周,观察 EURUSD 在伦敦段的平均回撤是否被压缩。 Trade_session() 用服务器时间比对各时段开盘收盘变量,新西兰、澳大利亚、东京、伦敦分别独立 case 判断。把 Ses04Op/Ses04Cl 改成你券商的 UTC+0 时点,才能和 MT5 下方时钟对齐,否则伦敦段可能整点错开 1 小时。

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=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;
       }

◍ 多周期信号如何汇成一张开仓指令

EA 把「纽约时段开关」和「多周期 TriMA 信号」分开算,再在 GetOpenPosition() 里做与逻辑交汇。只有 MTF 三角均线信号和中级周期信号同向上(rise=1)或同向下(down=-1),才返回非零开仓方向,否则 ret 保持 0,不出手。 GetSignalMidTF() 里有个硬阈值:TriMACI[0] 必须等于 2.0(多)或 1.0(空),且 DirectionMove() 在 TFCWS 周期上的方向一致,才给 rise / down。这个 2.0 和 1.0 是 TriMA 指标缓冲区的状态编码,不是价格,复制代码后要在 MT5 里用数据窗口确认自己加载的 TriMA 版本是否吐同样的值。 GetTriaMASignalMTF() 支持 MTF 模式:tfloop 取 TFArrays(多周期遍历)或单周期 1,循环内分别累加 trimup / trimdw 计数。外汇与贵金属杠杆高,多周期共振也只是提高概率,不代表方向必现,实盘前先用策略测试器跑 3 个月 tick 数据看信号触发频率。

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">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 trimOp=GetTriaMASignalMTF(symbol);
    class="type">int getmid=GetSignalMidTF(symbol);
    if(trimOp==rise && getmid==rise) ret=rise;
    if(trimOp==down && getmid==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::GetSignalMidTF(const class="type">class="kw">string symbol) class=class="str">"cmt">// Signal Indicator Position Close in profit
  {
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 TriMACI[];
    class=class="str">"cmt">//--
    ArrayResize(TriMACI,br,br);
    ArraySetAsSeries(TriMACI,true);
    class="type">int xx=PairsIdxArray(symbol);
    CopyBuffer(hTriMAm[xx],class="num">1,class="num">0,br,TriMACI);
    class=class="str">"cmt">//class="macro">#class="kw">property indicator_color1  clrDarkGray,clrDeepPink,clrMediumSeaGreen
    class=class="str">"cmt">//                    class="num">0           class="num">1               class="num">2
    class=class="str">"cmt">//--
    class="type">int dirmove=DirectionMove(symbol,TFCWS);
    class=class="str">"cmt">//--
    if(TriMACI[class="num">0]==class="num">2.0 && dirmove==rise) ret=rise;
    if(TriMACI[class="num">0]==class="num">1.0 && dirmove==down) ret=down;
    class=class="str">"cmt">//--
    class="kw">return(ret);
class=class="str">"cmt">//---
  } class=class="str">"cmt">//-end GetSignalMidTF()
class=class="str">"cmt">//---------//
class="type">int MCEA::GetTriaMASignalMTF(class="type">class="kw">string symbol)
  {
class=class="str">"cmt">//---
    class="type">int mv=class="num">0;
    class="type">int rise=class="num">1,
        down=-class="num">1;
    class="type">int tfloop=tfinuse==MTF ? TFArrays : class="num">1;
    class=class="str">"cmt">//--
    class="type">int trimup=class="num">0,
        trimdw=class="num">0;
    class=class="str">"cmt">//--  
    for(class="type">int x=class="num">0; x<tfloop; x++)
      {

「三角均线多周期信号的判定细节」

这段逻辑是 MTF 三角均线信号函数的收尾与独立实现。外层 GetTriaMASignalMTF() 在遍历多个时间周期后,用 trimup 与 trimdw 分别累计看涨、看跌周期数;当二者任一等于 tfloop(参与统计的周期总数)时,mv 被赋为 rise 或 down,意味着所有周期方向一致才出信号。 TriaMASMTF() 本身针对单个 symbol 与 mtf 取值。它先按 tfinuse 决定用传入周期还是默认 TFt,再从已建句柄 hTriMAb[xx][tx] 的 1 号缓冲拷贝最近 3 根(br=3)到 TriMACI 数组并设为时间序列。 MTF 模式下只看最新一根:TriMACI[0]==2.0 返回 1(rise),==1.0 返回 -1(down)。STF 模式则要求三根确认,例如 [2]==1.0 且 [1]==2.0 且 [0]==2.0 才判涨,反向对称判跌。 指标绘色分配也写死在编译指令里:0 号灰、1 号深粉、2 号海绿,对应不同状态线。外汇与贵金属波动剧烈,多周期一致信号只提高概率倾向,实盘仍需自测滑点与点差。

MQL5 / C++
if(TriaMASMTF(symbol,TFTri[x])>class="num">0) trimup++;
if(TriaMASMTF(symbol,TFTri[x])<class="num">0) trimdw++;
}
class=class="str">"cmt">//--
if(trimup==tfloop) mv=rise;
if(trimdw==tfloop) mv=down;
class=class="str">"cmt">//--
class="kw">return(mv);
class=class="str">"cmt">//---
} class=class="str">"cmt">//- end GetTriaMASignalMTF()
class=class="str">"cmt">//---------//
class="type">int MCEA::TriaMASMTF(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES mtf) class=class="str">"cmt">// formula Triangular MA on the requested Timeframe
  {
class=class="str">"cmt">//---
  class="type">int ret=class="num">0;
  class="type">int rise=class="num">1,
      down=-class="num">1;
  class="type">int br=class="num">3;
  ENUM_TIMEFRAMES TFUse=tfinuse==MTF ? mtf : TFt;
  class=class="str">"cmt">//--
  class="type">class="kw">double TriMACI[];
  ArrayResize(TriMACI,br,br);
  ArraySetAsSeries(TriMACI,true);
  class="type">int xx=PairsIdxArray(symbol);
  class="type">int tx=TFIndexArray(TFUse);
  CopyBuffer(hTriMAb[xx][tx],class="num">1,class="num">0,br,TriMACI);
  class=class="str">"cmt">//class="macro">#class="kw">property indicator_color1   clrDarkGray,clrDeepPink,clrMediumSeaGreen
  class=class="str">"cmt">//                     class="num">0           class="num">1           class="num">2
  class=class="str">"cmt">//Print("Symbol = "+symbol+" TF = "+EnumToString(mtf)+" TriMACI[class="num">0] = "+class="type">class="kw">string(TriMACI[class="num">0]));
  class=class="str">"cmt">//--
  class="kw">switch(tfinuse)
    {
    case MTF:
      {
      if(TriMACI[class="num">0]==class="num">2.0) ret=rise;
      if(TriMACI[class="num">0]==class="num">1.0) ret=down;
      class=class="str">"cmt">//--
      break;
      }
    case STF:
      {
      if(TriMACI[class="num">2]==class="num">1.0 && TriMACI[class="num">1]==class="num">2.0 && TriMACI[class="num">0]==class="num">2.0) ret=rise;
      if(TriMACI[class="num">2]==class="num">2.0 && TriMACI[class="num">1]==class="num">1.0 && TriMACI[class="num">0]==class="num">1.0) ret=down;
      class=class="str">"cmt">//--
      break;
      }
    }
  class=class="str">"cmt">//--
  class="kw">return(ret);
class=class="str">"cmt">//---
  } class=class="str">"cmt">//-end TriaMASMTF()
class=class="str">"cmt">//---------//
class="macro">#class="kw">property indicator_color1   clrDarkGray,clrDeepPink,clrMediumSeaGreen
class=class="str">"cmt">//                     class="num">0           class="num">1           class="num">2

图表按钮怎么驱动批量平仓与止损

在 MT5 自定义指标或 EA 里,靠 OnChartEvent 捕捉按钮对象点击,是脱离手动右键菜单做批量风控的常见做法。上面这段代码把 sparam 拿去比对按钮名,命中 "Close All Order" 就调 mc.CloseAllOrders(),命中 "Set SL/TP All Orders" 则调 mc.SetSLTPOrders(),并顺手用 ObjectSetInteger 把按钮状态复位、重绘手动面板。 颜色缓冲那两行值得单独看:SetIndexBuffer(1,valc,INDICATOR_COLOR_INDEX) 把 valc 数组绑成颜色索引缓冲;随后 valc[i] 用三元表达式判断 val[i] 与上一根的大小——大于给 2(对应 MediumSeaGreen)、小于给 1(DeepPink)、相等沿用前一根值、首根给 0(DarkGray)。这意味着指标线颜色直接反映动量方向切换,肉眼扫图就能分出段。 TriMACI 的取数路径也直白:先用 PairsIdxArray / TFIndexArray 把品种和周期映射成二维句柄下标,再 CopyBuffer(hTriMAb[xx][tx],1,0,br,TriMACI) 把跨品种三角均线缓冲拉进本地数组。实盘跑外汇或贵金属时,这种跨标的引用若有一端点差跳变或报价中断,CopyBuffer 可能返回 0 根,需 ResetLastError 自查。高风险品种建议先在模拟盘验证按钮响应延迟。

MQL5 / C++
SetIndexBuffer(class="num">1,valc,INDICATOR_COLOR_INDEX);
valc[i] = (i>class="num">0) ?(val[i]>val[i-class="num">1]) ? class="num">2 :(val[i]<val[i-class="num">1]) ? class="num">1 : valc[i-class="num">1]: class="num">0;
  class="type">class="kw">double TriMACI[];
  ArrayResize(TriMACI,br,br);
  ArraySetAsSeries(TriMACI,true);
  class="type">int xx=PairsIdxArray(symbol);
  class="type">int tx=TFIndexArray(TFUse);
  CopyBuffer(hTriMAb[xx][tx],class="num">1,class="num">0,br,TriMACI);
  class=class="str">"cmt">//class="macro">#class="kw">property indicator_color1   clrDarkGray,clrDeepPink,clrMediumSeaGreen
  class=class="str">"cmt">//                    class="num">0           class="num">1           class="num">2

class="type">void OnChartEvent(const class="type">int id,
                const class="type">long &lparam,
                const class="type">class="kw">double &dparam,
                const class="type">class="kw">string &sparam)
  {
  ResetLastError();
  ENUM_TIMEFRAMES CCS=mc.TFt;
  if(id==CHARTEVENT_OBJECT_CLICK)
    {
     class="type">int lensymbol=StringLen(Symbol());
     class="type">int lensparam=StringLen(sparam);
     if(sparam=="Set SL/TP All Orders")
       {
        mc.SetSLTPOrders();
        Alert("-- "+mc.expname+" -- ",Symbol()," -- Set SL/TP All Orders");
        ObjectSetInteger(class="num">0,"Set SL/TP All Orders",OBJPROP_STATE,false);
        ObjectSetInteger(class="num">0,"Set SL/TP All Orders",OBJPROP_ZORDER,class="num">0);
        CreateManualPanel();
       }
     if(sparam=="Close All Order")
       {
        mc.CloseAllOrders();
        Alert("-- "+mc.expname+" -- ",Symbol()," -- Close All Orders");
       }
    }
  }

◍ 面板按钮的点击事件分流

EA 挂在图表上后,手动控制面板的按钮靠 ChartEvent 的 sparam 字符串来区分。sparam 等于按钮名时,就进对应的 if 分支做平仓或显隐处理。 以「Close All Profit」为例,命中后先调 mc.ManualCloseAllProfit() 平掉所有浮盈单,再用 Alert 弹出「-- 策略名 -- 品种 -- Close All Profit」提示。随后把该按钮的 OBJPROP_STATE 设回 false、ZORDER 归 0,并重建面板,按钮视觉上自动弹起。 「X」按钮负责清场:连续用 ObjectsDeleteAll 按 OBJ_BUTTON、OBJ_LABEL、OBJ_RECTANGLE_LABEL 三类删光图表对象,再置 PanelExtra=false 并调 DisplayManualButton 退回到基础按钮。 「M」与「C」只切换面板状态:M 把 PanelExtra 置 true 展开扩展面板,C 同样复位自身 STATE 与 ZORDER,具体动作由后续代码接管。外汇与贵金属交易自带高杠杆风险,这类一键脚本若逻辑有误可能误平仓位,上 MT5 前务必在策略测试器用历史数据跑一遍。

MQL5 / C++
ObjectSetInteger(class="num">0,"Close All Order",OBJPROP_STATE,false);
ObjectSetInteger(class="num">0,"Close All Order",OBJPROP_ZORDER,class="num">0);
CreateManualPanel();
}
class=class="str">"cmt">//--- if "Close All Profit" button is click
if(sparam=="Close All Profit")
  {
   mc.ManualCloseAllProfit();
   Alert("-- "+mc.expname+" -- ",Symbol()," -- Close All Profit");
   class=class="str">"cmt">//--- unpress the button 
   ObjectSetInteger(class="num">0,"Close All Profit",OBJPROP_STATE,false);
   ObjectSetInteger(class="num">0,"Close All Profit",OBJPROP_ZORDER,class="num">0);
   CreateManualPanel();
   }
class=class="str">"cmt">//--- if "X" button is click
if(sparam=="X")
  {
   ObjectsDeleteAll(class="num">0,class="num">0,OBJ_BUTTON);
   ObjectsDeleteAll(class="num">0,class="num">0,OBJ_LABEL);
   ObjectsDeleteAll(class="num">0,class="num">0,OBJ_RECTANGLE_LABEL);
   class=class="str">"cmt">//--- unpress the button 
   ObjectSetInteger(class="num">0,"X",OBJPROP_STATE,false);
   ObjectSetInteger(class="num">0,"X",OBJPROP_ZORDER,class="num">0);
   class=class="str">"cmt">//--
   DeleteButtonX();
   mc.PanelExtra=false;
   DisplayManualButton();
   }
class=class="str">"cmt">//--- if "M" button is click
if(sparam=="M")
  {
   class=class="str">"cmt">//--- unpress the button 
   ObjectSetInteger(class="num">0,"M",OBJPROP_STATE,false);
   ObjectSetInteger(class="num">0,"M",OBJPROP_ZORDER,class="num">0);
   mc.PanelExtra=true;
   CreateManualPanel();
   }
class=class="str">"cmt">//--- if "C" button is click
if(sparam=="C")
  {
   class=class="str">"cmt">//--- unpress the button 
   ObjectSetInteger(class="num">0,"C",OBJPROP_STATE,false);
   ObjectSetInteger(class="num">0,"C",OBJPROP_ZORDER,class="num">0);

「图表事件里的卸载与切换逻辑」

在 MT5 的 OnChartEvent 回调里,面板按钮的交互靠 sparam 与 lensparam 区分。当 sparam 等于 "R" 时,脚本先弹 Alert 提示 EA 即将从图表移除,随后调用 ExpertRemove() 终止自身,并把 "R" 按钮的 OBJPROP_STATE 设回 false、ZORDER 归零,避免按钮卡在按下态。 接着用 ChartSetSymbolPeriod(0,Symbol(),Period()) 强制重刷当前图表的品种与周期,失败则再调一次;DeletePanelButton() 清掉面板按钮后 ChartRedraw(0) 重绘。这套顺序能保证 EA 退出时图表不残留半透明控件。 点品种按钮时(lensparam==lensymbol),先用 mc.ValidatePairs(sparam) 拿到序号 sx,再 ChangeChartSymbol(mc.AS30[sx],CCS) 切到目标品种,同时把 mc.PanelExtra 置 false 收起扩展面板。外汇与贵金属品种切换伴随点差跳变,属高风险操作,切换后建议手动核一眼报价。 ChangeChartSymbol 内部会批量删 OBJ_BUTTON、OBJ_LABEL、OBJ_RECTANGLE_LABEL 三类图形对象,再设品种周期并重绘。注意 ObjectsDeleteAll 的第三参用了具体类型常量,若你自绘了其他类型标签不会被误删。 TradeInfo 函数里用 SymbolInfoInteger(Symbol(),SYMBOL_SPREAD)/xpip 算点差点数,xpip 由 Pips(Symbol()) 按品种小数位得出;rem=zntm-TimeCurrent() 是距下一时区的秒数。这类注释写法直接贴在图表上,回测时不可见,只在实盘图有效。

MQL5 / C++
 mc.PanelExtra=true;
 CreateSymbolPanel();
 }
 class=class="str">"cmt">//--- if "R" button is click
 if(sparam=="R")
 {
 Alert("-- "+mc.expname+" -- ",Symbol()," -- expert advisor will be Remove from the chart.");
 ExpertRemove();
 class=class="str">"cmt">//--- unpress the button 
 ObjectSetInteger(class="num">0,"R",OBJPROP_STATE,false);
 ObjectSetInteger(class="num">0,"R",OBJPROP_ZORDER,class="num">0);
 if(!ChartSetSymbolPeriod(class="num">0,Symbol(),Period()))
 ChartSetSymbolPeriod(class="num">0,Symbol(),Period());
 DeletePanelButton();
 ChartRedraw(class="num">0);
 }
 class=class="str">"cmt">//--- if Symbol button is click
 if(lensparam==lensymbol)
 {
 class="type">int sx=mc.ValidatePairs(sparam);
 ChangeChartSymbol(mc.AS30[sx],CCS);
 mc.PanelExtra=false;
 }
 class=class="str">"cmt">//--
 }
 class=class="str">"cmt">//--
 class="kw">return;
class=class="str">"cmt">//---
 } class=class="str">"cmt">//-end OnChartEvent()
class=class="str">"cmt">//---------//
class="type">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,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;
class=class="str">"cmt">//---
 } class=class="str">"cmt">//-end ChangeChartSymbol()
class=class="str">"cmt">//---------//
class="type">void MCEA::TradeInfo(class="type">void) class=class="str">"cmt">// function: write comments on the chart
 {
class=class="str">"cmt">//----
 Pips(Symbol());
 class="type">class="kw">double spread=SymbolInfoInteger(Symbol(),SYMBOL_SPREAD)/xpip;
 rem=zntm-TimeCurrent();
 class="type">class="kw">string postime=PosTimeZone();
 class="type">class="kw">string eawait=" - Waiting for active time..!";
 class=class="str">"cmt">//--
 class="type">class="kw">string tradetf=tfinuse==MTF ? EnumToString(Period()) : EnumToString(TFts);
 class="type">class="kw">string eamode=tfinuse==MTF ? "Multi-Timeframe" : "Single-Timeframe";

把EA运行状态拼成一条可读的日报

EA在每次心跳里先把当日订单统计跑一遍(TodayOrders()),再把账户、品种、挂单情况拼进一个 comm 字符串,用来推送到日志或通知。这样做的好处是:不用切终端窗口,也能在手机通知里一眼看穿今天到底下了几单、净值多少、还剩多少时间到禁交易点。 下面这段代码就是拼接核心。注意它先用 ThisTime() 取本地年/月/日,再用 TimeCurrent() 取服务器秒级时间,两者并排显示能帮你发现本地与经纪商时区偏差——外汇和贵金属跨时区交易时,这种偏差可能引发误判开仓窗口,属于高风险细节。 如果当前时间还没到禁交易时刻 zntm,就追加一段剩余时间;这段用 ReqTime(rem,hour/min/sec) 拆出时分秒,eawait 是等待提示后缀。最后补上交易对、BUY/SELL 市价单数量(oBm、oSm)和浮动盈亏 floatprofit,两位小数输出。 开 MT5 把这段塞进你的 EA 的 OnTick 或定时器,改一下终端公司名和魔法码字段,就能直接产出每日盯盘速报。

MQL5 / C++
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      :  "+ tradetf+
      "\n        :: Trade Mode       :  "+ eamode+
      "\n        :: Today Trading    :  "+ TradingDay()+" : "+hariini+
      "\n        :: Trading Session :  "+ tz_ses+
      "\n        :: Trading Time     :  "+ postime;
      if(TimeCurrent()<zntm)
        {
          comm=comm+
          "\n        :: Time Remaining :  "+(class="type">class="kw">string)ReqTime(rem,hour)+":"+(class="type">class="kw">string)ReqTime(rem,min)+":"+(class="type">class="kw">string)ReqTime(rem,sec) + eawait;
        }
      comm=comm+
      "\n      ------------------------------------------------------------"+
      "\n        :: Trading Pairs     :  "+pairs+
      "\n        :: BUY Market        :  "+class="type">class="kw">string(oBm)+
      "\n        :: SELL Market       :  "+class="type">class="kw">string(oSm)+
      "\n        :: Total Order       :  "+class="type">class="kw">string(oBm+oSm)+
      "\n        :: Order Profit      :  "+DoubleToString(floatprofit,class="num">2)+

◍ 持仓时段跨日标记的判定逻辑

这段 MQL5 代码负责把浮盈、固定盈利和净值打印到图表,并判断当前持仓属于「今日」还是「跨日至次日」的时段。TradeInfo() 里用 DoubleToString(...,2) 把 fixclprofit、floatprofit 及两者之和保留两位小数后拼进 Comment 文本,ChartRedraw(0) 强制重绘,能让交易者在图表左上角实时读到净值数字。 PosTimeZone() 的核心是比较服务器请求时间 ReqTime() 与本地 ThisTime() 以及 TimeCurrent() 与znop/zncl 边界。若请求的开仓时间大于当前时间,直接标记跨日;若当前早于开仓点且收仓时间晚于当前,则标为「Today to Next day」;处于开收区间时同理按边界切换。 在 MT5 里把这段代码挂到 EA 的 OnTick,开 EURUSD 或 XAUUSD 这类高波动品种观察左上角文字:跨日时段提示可能在非农或亚盘跳空时改变持仓归类。外汇与贵金属杠杆高,跨日持仓面临隔夜利息与跳空风险,时段判定仅作展示参考,不构成方向建议。

MQL5 / C++
   "\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">//---------//

「面板上的 M、C、R 三个隐藏开关」

TriangularMA_MTF_MCEA 加载到图表后,名称栏下方会出现 M、C、R 三个按钮,它们各自接管一类高频操作,不必去菜单里翻设置。 点 M 会弹出手工管理面板,里面直接给出现成指令:Set SL/TP All Orders 给当前所有订单批量挂止损止盈,Close All Orders 一键平掉全部持仓,Close All Profits 只挑盈利单清仓。跑多单时想锁利润又怕手速慢,这三行比手动逐个点快得多。 C 按钮展开的是 30 个品种名的快捷面板,单击其中任意一个,主图品种立刻切换过去,不用在 Market Watch 里拖拽查找。做跨品种三角套利的,来回切 EURUSD、GBPUSD、USDJPY 这类组合会顺手很多。 R 按钮则是卸载开关,按一下智能系统直接从图表移除,省得右键找「删除 EA」。外汇与贵金属杠杆高、滑点随时来,这类一键清仓和切换动作前,先确认不是误触。

在 MT5 测试器里跑多币种三角均线 EA

MT5 策略测试器原生支持跨品种、跨时间帧的批量回测,不必手动切图表。这意味着像 TriangularMA_MTF_MCEA 这种同时吃多周期信号的智能交易系统,可以直接在测试器里一次性验证全市场表现。 具体落地时分两种跑法。第一种开多时间帧模式,EA 会按预设的各周期参数联合决策,适合观察周期共振下的信号质量。第二种切到单一时间帧模式,只用主图周期计算,用来隔离多周期干扰、确认基础逻辑是否站得住。 两种模式各跑一遍,对比成交次数与回撤曲线,就能判断多时间帧叠加到底是过滤了噪音还是引入了滞后。外汇与贵金属杠杆高、滑点随机,回测结果仅代表历史概率,实盘可能显著偏离。

◍ 多时间帧多币种的实测落差

TriangularMA_MTF_MCEA 这个示例在策略测试器里跑出来的结果并不好看,单一时间帧版本的净值曲线反而压过多时间帧版本。外汇与贵金属市场高杠杆、高波动,回测劣化不代表策略破产,只说明当前信号计算和周期组合还太粗糙。 把多币种塞进一个 EA 的价值在于少开图表、用跨品种盈亏互抵摊薄风险,但实测中一个货币的亏损是否被另一些补偿,高度依赖你后面接的信号质量。 若换用更准的入场逻辑、挑更不合群的时间帧搭配,多时间帧多币种的表现有可能反超。思路先跑通,参数留给你在 MT5 里自己碾一遍。

常见问题

用字符串拼接函数把券商给的前缀后缀变量和裸品种名组合,循环写入数组即可,避免人工漏改。
遍历数组用市场报价可见性和点差阈值做判断,不可交易或点差过大的直接剔除出交易列表。
小布可读取你的EA全局变量与数组结构,自动标出前缀剥离不一致或分组错位的品种,省去肉眼排查。
多是前缀剥离不彻底或匹配用了包含而非等号,建议用严格首段比对并打印日志核对分组结果。
用平台全局变量存储会跨重启保留,但要确认退出时未调用删除函数,否则下次加载前缀就空了。