如何利用 MQL5 创建简单的多币种智能交易系统(第 4 部分):三角移动平均线 指标信号·综合运用
- 给品种名自动补前缀后缀
- 多币种数组落地与交易对筛选
- 按币种分流货币对数组
- 按币种分组装载与品种前缀剥离
- 全局变量里的交易时段与符号前缀拆解
- EA 全局变量的声明骨架
- 多周期三角均线类的接口与配置字段
- 多币种与三角均线矩阵的变量骨架
- EA 类里的日期与周期字段声明
- 多周期三角均线的类接口与配置落点
- 多周期与单周期下的指标句柄分配
- EA 主循环里的品种轮询与终端校验
- 开仓信号触发后的持仓与限额逻辑
- 反向信号下的买仓回收逻辑
- 弱信号下的卖单保护与时区交易窗口
- 多周期信号如何汇成一张开仓指令
- 三角均线多周期信号的判定细节
- 图表按钮怎么驱动批量平仓与止损
- 面板按钮的点击事件分流
- 图表事件里的卸载与切换逻辑
- 把EA运行状态拼成一条可读的日报
- 持仓时段跨日标记的判定逻辑
- 面板上的 M、C、R 三个隐藏开关
- 在 MT5 测试器里跑多币种三角均线 EA
- 多时间帧多币种的实测落差
◍ 给品种名自动补前缀后缀
在 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 的前后缀规则已被正确捕获,否则外汇与贵金属交易存在因选错合约而报错的高风险。
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 在异常品种上尝试下单。
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"。外汇和贵金属杠杆高、点差跳变频繁,这类批量数组操作前务必在策略测试器用真实品种池跑一遍,避免残留上一次品种的越界。
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 输出。
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 段以上验证一下。
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 变量 tmopcu、tmclcu 到 tmop05、tmcl05 以及 tmopno、tmclno,是用来缓存不同交易时段(currency / 01~05 / no-session)的开仓与平仓注释文本,共 14 个字符串槽位。
double 组的 LotPS、slv、tpv、pip、xpip 管手数与止损止盈价;floatprofit 与 fixclprofit 分离了浮动盈利和固定平仓盈利,回测时这两值差可能直接决定平仓触发顺序。
行情容器 OPEN[]、HIGH[]、LOW[]、CLOSE[]、TIME[] 全为动态数组,配合 closetime 做跨根 K 线状态判断;最后 8 个函数原型里 Trade_session() 返回 bool,是时段过滤的总开关,改它返回值能立刻让 EA 在伦敦盘外停止下单。
外汇与贵金属波动剧烈,这类全局变量若未初始化就调用,MT5 策略测试器会报数组越界,实盘前务必用脚本跑一遍空窗期验证。
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。
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 全局区,编译后从「导航器—全局变量」里就能看到这些句柄是否被成功分配。
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 赋值验证多周期数组是否生效。
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 有返回值再上模拟盘。
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 多耗多少自定义指标资源,外汇与贵金属杠杆品种请先于模拟盘验证,实盘波动可能使句柄失效概率升高。
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 看执行路径。
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] 符号在你的品种上不会误触。
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 的实际回撤完全不是一个概念,移植前必须按合约重新标定。
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 小时。
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 数据看信号触发频率。
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 号海绿,对应不同状态线。外汇与贵金属波动剧烈,多周期一致信号只提高概率倾向,实盘仍需自测滑点与点差。
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 自查。高风险品种建议先在模拟盘验证按钮响应延迟。
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 前务必在策略测试器用历史数据跑一遍。
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() 是距下一时区的秒数。这类注释写法直接贴在图表上,回测时不可见,只在实盘图有效。
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 或定时器,改一下终端公司名和魔法码字段,就能直接产出每日盯盘速报。
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 这类高波动品种观察左上角文字:跨日时段提示可能在非农或亚盘跳空时改变持仓归类。外汇与贵金属杠杆高,跨日持仓面临隔夜利息与跳空风险,时段判定仅作展示参考,不构成方向建议。
"\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 里自己碾一遍。