如何利用 MQL5 创建简单的多币种智能交易系统(第 2 部分):指标信号:多时间帧抛物线 SAR 指标·综合运用
(3/3)·把M15到D1的抛物线SAR拧成一条多币种开平信号链,30个品种一键跑通
不少交易者把SAR只挂在当前图表周期,结果信号被噪音来回洗。多币种EA若只盯单周期,跨品种节奏错位会让平仓滞后。本文把M15至D1五层SAR织成统一信号网,顺带用M15疲软点做盈利平仓。
◍ 多周期 SAR 句柄与交易终端的初始化落点
这段初始化收尾把多时间框架的抛物线转向指标句柄一次性挂好,再顺手把账户级风控参数算出来。外层 for 循环按 TFArrays 个数给 hPSAR[x][i] 赋值,iSAR 用的是 DIRI[x] 标的、TFSAR[i] 周期,步长 SARstep 和极值 SARmaxi 由外部传入,MT5 里指标句柄必须在 OnInit 阶段拿稳,否则后续 OnTick 取缓冲会直接爆数组越界错误。 ALO 这行很直白:取账户允许的限价单总数 LimitOrders(),若大于监控币种数 arrsymbx 就截断到 arrsymbx,否则用账户返回值。LotPS 直接把 ALO 转成 double 当作每品种手数基准,意味着你监控 8 个货币对但账户只放行 5 张限价单,实际铺开的手数逻辑会被 5 这个上限卡住。 mc_trade 三连设置把魔法码、滑点点数 slip、保证金模式写死,之后 return 结束配置函数。OnTick 极薄,只调 mc.ExpertActionTrade() 然后返回,真正的轮子都在类方法里转。 ExpertActionTrade 开头先用 MQLInfoInteger(MQL_TRADE_ALLOWED) 探终端是否放开算法交易,mc.checktml 这个标记保证警告弹一次就停,避免每跳都刷屏。后面用 fmod((double)mcsec,5.0)==0 做 5 秒节拍器,只有 ccur 不等于 psec 才进币种轮询,for 循环里把 DIRI[x] 或当前图 Symbol() 喂给 CurrentSymbolSet,逐一对品种跑策略。外汇与贵金属杠杆高,这类自动轮询若 slip 设太小,在美盘流动性突变时可能频繁拒单。
for(class="type">int i=class="num">0; i<TFArrays; i++) { hPSAR[x][i]=iSAR(DIRI[x],TFSAR[i],SARstep,SARmaxi); class=class="str">"cmt">// Handle for iSAR Indicator array sequence of the requested timeframe } } class=class="str">"cmt">//-- ALO=(class="type">int)mc_account.LimitOrders()>arrsymbx ? arrsymbx : (class="type">int)mc_account.LimitOrders(); class=class="str">"cmt">//-- LotPS=(class="type">class="kw">double)ALO; class=class="str">"cmt">//-- mc_trade.SetExpertMagicNumber(magicEA); mc_trade.SetDeviationInPoints(slip); mc_trade.SetMarginMode(); class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end FXSAR_MTF_MCEA_Config() class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick(class="type">void) { class=class="str">"cmt">//--- mc.ExpertActionTrade(); class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end OnTick() class="type">void MCEA::ExpertActionTrade(class="type">void) { class=class="str">"cmt">//--- class=class="str">"cmt">//Check Trading Terminal ResetLastError(); class=class="str">"cmt">//-- if(!MQLInfoInteger(MQL_TRADE_ALLOWED) && mc.checktml==class="num">0) class=class="str">"cmt">//-- Check whether MT5 Algorithmic trading is Allow or Prohibit { mc.Do_Alerts(Symbol(),"Trading Expert at "+Symbol()+" are NOT Allowed by Setting."); mc.checktml=class="num">1; class=class="str">"cmt">//-- Variable checktml is given a value of class="num">1, so that the alert is only done once. class="kw">return; } class=class="str">"cmt">//-- if(!DisplayManualButton("M","C","R")) DisplayManualButton(); class=class="str">"cmt">//-- Show the expert manual button panel class=class="str">"cmt">//-- if(trade_info_display==Yes) mc.TradeInfo(); class=class="str">"cmt">//-- Displayed Trading Info on Chart class=class="str">"cmt">//--- class=class="str">"cmt">//-- class="type">int mcsec=mc.ThisTime(mc.sec); class=class="str">"cmt">//-- if(fmod((class="type">class="kw">double)mcsec,class="num">5.0)==class="num">0) mc.ccur=mcsec; class=class="str">"cmt">//-- if(mc.ccur!=mc.psec) { class="type">class="kw">string symbol; class=class="str">"cmt">//-- Here we start with the rotation of the name of all symbol or pairs to be traded for(class="type">int x=class="num">0; x<mc.arrsymbx && !IsStopped(); x++) { class=class="str">"cmt">//-- if(mc.DIRI[x]==Symbol()) symbol=Symbol(); else symbol=mc.DIRI[x]; class=class="str">"cmt">//-- mc.CurrentSymbolSet(symbol);
「开仓信号触发后的持仓与限额逻辑」
这段逻辑只在一个前提成立后才跑:当天允许交易(TradingToday 返回真)。此时先调用 GetOpenPosition 拿到该品种的方向信号,结果写进 OpOr[x],等于 1 是买、等于 -1 是卖。 若信号为 Buy,先 CheckOpenPMx 刷新当前持仓统计;当 Close_by_Opps 开启且已有卖单数 xos[x] 大于 0,就平掉所有卖仓。若当前买仓数为 0 且总挂单与持仓 xtto 小于上限 ALO,直接 OpenBuy 开多。 一旦 xtto 已经达到 ALO,不再无脑加仓,而是弹警报告知“订单数已达上限 = ALO”。随后复查持仓:若卖仓盈利 profits[x] 小于 -1.02(即浮亏超约 1.02 个单位)且买仓为 0,则反手平卖开买;否则在 SaveOnRev 开启时锁掉所有盈利单。外汇与贵金属波动剧烈,这种反手条件若参数过紧可能在震荡市频繁触发,建议先在 MT5 策略测试器用历史数据验证 ALO 与 -1.02 阈值。
if(mc.TradingToday()) { mc.OpOr[x]=mc.GetOpenPosition(symbol); class=class="str">"cmt">//-- Get trading signals to open positions 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) { mc.CheckOpenPMx(symbol); if(Close_by_Opps==Yes && mc.xos[x]>class="num">0) mc.CloseSellPositions(symbol); if(mc.xob[x]==class="num">0 && mc.xtto<mc.ALO) mc.OpenBuy(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.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) { mc.CheckOpenPMx(symbol);
正文
<span class="comment">//--</span> <span class="keyword">if</span>(Close_by_Opps==Yes && mc.xob[x]><span class="number">0</span>) mc.CloseBuyPositions(symbol); <span class="comment">//--</span> <span class="keyword">if</span>(mc.xos[x]==<span class="number">0</span> && mc.xtto<mc.ALO) mc.OpenSell(symbol); <span class="keyword">else</span> <span class="keyword">if</span>(mc.xtto>=mc.ALO) { <span class="comment">//--</span>
◍ 弱信号下的卖单保护平仓
这段逻辑处理的是当持仓卖单已经累积一定浮盈、且市场出现弱势信号时,主动平仓保利润的场景。条件里写死了 profits[x] > 0.02,也就是单笔盈利超过 0.02(账户货币单位,具体取决于杠杆与手数)才触发,避免过早平掉刚转正的单子。 xos[x] > 0 代表该品种当前无反向加仓冲突,GetCloseInWeakSignal 返回 Buy 则说明弱信号模型判定应回补买入。三者同时成立,才调用 CloseSellPositions 平掉卖仓,并弹 alert 提示“弱信号保利润”。外汇与贵金属波动剧烈,这类硬阈值在跳空时可能滑点失效,实际触发价倾向偏离 0.02。 后面紧接的是移动止损开关:TrailingSLTP 为 Yes 时,按 autotrl 选自动或手动 ModifySLTP;最后统一跑 CheckClose 做收尾检查。开 MT5 把 profits 阈值改成 0.05 对比回测,能直接看出过早平仓对曲线回撤的影响。
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()
「开仓信号的双层过滤逻辑」
想拿到开仓信号,核心入口是 GetOpenPosition(symbol)。它内部只做一件事:把 DirectionMove 和 GetPSARSignalMTF 两个函数的返回值叠加判断,只有两者同向才输出 +1(多头)或 -1(空头),否则返回 0 表示无信号。 GetPSARSignalMTF 并不是只看当前周期,而是用 for 循环扫了 TFArrays 个时间帧(代码里写死为 5,覆盖 M1 到 H1)。每个周期都跑一遍 PARSARMTF,统计 sarup 和 sardw 的数量;必须 5 个周期全部看涨才给 rise,全部看跌才给 down。这种多周期一致性的约束,能过滤掉单一小周期假突破带来的噪音。 PARSARMTF 里真正的 iSAR 判定很直接:取请求时间帧第 0 根 K 线的开高低收,若 PSAR[0] 低于最低价且收盘价大于开盘价,判为多;若 PSAR[0] 高于最高价且收盘价小于开盘价,判为空。PairsIdxArray 和 TFIndexArray 只是把品种名和时间帧翻译成数组下标,再去 hPSAR[xx][tx] 句柄取缓冲区。 外汇与贵金属杠杆高、滑点随机,这套信号在 EURUSD 的 M5 上回测可能出现连续 3~4 根同向 K 线才触发一次信号的现象,实盘前务必在 MT5 策略测试器里用 Tick 级数据跑一遍验证。
class="type">int MCEA::GetOpenPosition(const class="type">class="kw">string symbol) class=class="str">"cmt">// Signal Open Position { class=class="str">"cmt">//--- class="type">int ret=class="num">0; class="type">int rise=class="num">1, down=-class="num">1; class=class="str">"cmt">//-- class="type">int dirmov=DirectionMove(symbol); class="type">int parsOp=GetPSARSignalMTF(symbol); class=class="str">"cmt">//-- if(parsOp==rise && dirmov==rise) ret=rise; if(parsOp==down && dirmov==down) ret=down; class=class="str">"cmt">//-- class="kw">return(ret); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end GetOpenPosition() class="type">int MCEA::GetPSARSignalMTF(class="type">class="kw">string symbol) class=class="str">"cmt">// iSAR MTF signal calculation { class=class="str">"cmt">//--- class="type">int mv=class="num">0; class="type">int rise=class="num">1, down=-class="num">1; class=class="str">"cmt">//-- class="type">int sarup=class="num">0, sardw=class="num">0; class=class="str">"cmt">//-- for(class="type">int x=class="num">0; x<TFArrays; x++) class=class="str">"cmt">// The TFArrays variable has a value of class="num">5 which is taken from the number of time frames from TF_M1 to TF_H1. { if(PARSARMTF(symbol,TFSAR[x])>class="num">0) sarup++; if(PARSARMTF(symbol,TFSAR[x])<class="num">0) sardw++; } class=class="str">"cmt">//-- if(sarup==TFArrays) mv=rise; if(sardw==TFArrays) mv=down; class=class="str">"cmt">//-- class="kw">return(mv); class=class="str">"cmt">//--- } class=class="str">"cmt">//- end GetPSARSignalMTF() class="type">int MCEA::PARSARMTF(const class="type">class="kw">string symbol,ENUM_TIMEFRAMES mtf) class=class="str">"cmt">// formula Parabolic iSAR 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=class="str">"cmt">//-- class="type">int br=class="num">2; class=class="str">"cmt">//-- class="type">class="kw">double PSAR[]; ArrayResize(PSAR,br,br); ArraySetAsSeries(PSAR,true); class="type">int xx=PairsIdxArray(symbol); class="type">int tx=TFIndexArray(mtf); CopyBuffer(hPSAR[xx][tx],class="num">0,class="num">0,br,PSAR); class=class="str">"cmt">//-- class="type">class="kw">double OPN0=iOpen(symbol,TFSAR[tx],class="num">0); class="type">class="kw">double HIG0=iHigh(symbol,TFSAR[tx],class="num">0); class="type">class="kw">double LOW0=iLow(symbol,TFSAR[tx],class="num">0); class="type">class="kw">double CLS0=iClose(symbol,TFSAR[tx],class="num">0); class=class="str">"cmt">//-- if(PSAR[class="num">0]<LOW0 && CLS0>OPN0) ret=rise; if(PSAR[class="num">0]>HIG0 && CLS0<OPN0) ret=down; class=class="str">"cmt">//-- class="kw">return(ret); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end PARSARMTF() class="type">int MCEA::PairsIdxArray(const class="type">class="kw">string symbol) { class=class="str">"cmt">//--- class="type">int pidx=class="num">0; class=class="str">"cmt">//-- for(class="type">int x=class="num">0; x<arrsymbx; x++) { if(DIRI[x]==symbol) { pidx=x; break; } } class=class="str">"cmt">//-- class="kw">return(pidx); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end PairsIdxArray()
把时间框架映射到数组下标
多周期 SAR 类里,不同周期的数据存在并行数组,真正干活前得先知道当前周期对应哪一根槽位。下面这个函数干的就是查表活:传入 ENUM_TIMEFRAMES,返回它在 TFSAR[] 里的索引,查不到就给 -1。 [CODE]int MCEA::TFIndexArray(ENUM_TIMEFRAMES TF) { //--- int res=-1; //-- for(int x=0;x<TFArrays; x++) { if(TF==TFSAR[x]) { res=x; break; } } //-- return(res); //--- } //-end TFIndexArray() [/CODE] 逐行拆一下:第 1 行定义成员函数,接收周期枚举 TF;res 初始化为 -1,意味着默认“没匹配上”。for 循环从 0 跑到 TFArrays(即已注册周期总数),逐个比 TFSAR[x] 是否等于传入周期;命中就记下 x 并 break 跳出,避免无谓遍历。最后返回 res,调用方拿到 -1 就该走异常处理分支而不是继续算 SAR。 实盘意义在于:若你往 TFSAR 里塞了 5 个周期(M1/M5/M15/H1/H4),传个 PERIOD_D1 进来必然得 -1,这时硬喂给后续缓冲区会越界报错。开 MT5 在 OnInit 里 Print(TFIndexArray(PERIOD_M15)),对照你填的周期表,能直接验证映射对不对。外汇与贵金属杠杆高,多周期逻辑出错可能瞬间放大滑点风险,验证通过再上模拟盘。
class="type">int MCEA::TFIndexArray(ENUM_TIMEFRAMES TF) { class=class="str">"cmt">//--- class="type">int res=-class="num">1; class=class="str">"cmt">//-- for(class="type">int x=class="num">0;x<TFArrays; x++) { if(TF==TFSAR[x]) { res=x; break; } } class=class="str">"cmt">//-- class="kw">return(res); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end TFIndexArray()
◍ 多币种面板的手动按钮怎么接管订单
做多币种智能交易系统,最怕图表切来切去还手忙脚乱平不了仓。FXSAR_MTF_MCEA 在图表上放了 M、C、R 三个实体按钮,把跨品种管理和图表切换压成了一键动作。 点 M 会弹出手动面板,能一次性给所有订单设止损止盈、全平、或只平盈利单;点 C 弹出 30 个品种名,点哪个图表立刻换那个品种;点 R 直接把 EA 从图表摘掉。若 EA 属性里“在图表上显示交易信息”设为“是”,TradeInfo() 还会把持仓信息画在图上。 OnChartEvent() 里靠 CHARTEVENT_OBJECT_CLICK 分辨被点的对象名。下面这段是核心分发逻辑,注意 sparam 比对的是按钮对象名而非币种: 别把按钮名当品种名 M、C 面板里的对象名(如 "Close All Profit")和 Symbol() 返回的品种名是两回事,用 StringLen 比长度是常见防误触写法,直接拿 sparam 去 ChangeChartSymbol 会崩。
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| ChartEvent function |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">void</span> <span class="functions">OnChartEvent</span>(<span class="keyword">const</span> <span class="keyword">class="type">int</span> id, <span class="keyword">const</span> <span class="keyword">class="type">long</span> &lparam, <span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> &dparam, <span class="keyword">const</span> <span class="keyword">class="type">class="kw">string</span> &sparam) { <span class="comment">class=class="str">"cmt">//---</span> <span class="comment">class=class="str">"cmt">//--- handling CHARTEVENT_CLICK event("Clicking the chart")</span> <span class="functions">ResetLastError</span>(); <span class="comment">class=class="str">"cmt">//--</span> <span class="macro">ENUM_TIMEFRAMES</span> CCS=mc.TFt; <span class="comment">class=class="str">"cmt">//--</span> <span class="keyword">if</span>(id==<span class="macro">CHARTEVENT_OBJECT_CLICK</span>) { <span class="keyword">class="type">int</span> lensymbol=<span class="functions">StringLen</span>(<span class="functions">Symbol</span>()); <span class="keyword">class="type">int</span> lensparam=<span class="functions">StringLen</span>(sparam); <span class="comment">class=class="str">"cmt">//--</span> <span class="comment">class=class="str">"cmt">//--- if "Set SL All Orders" button is click</span> <span class="keyword">if</span>(sparam==<span class="class="type">class="kw">string">"Set SL/TP All Orders"</span>) { mc.SetSLTPOrders(); <span class="functions">Alert</span>(<span class="class="type">class="kw">string">"-- "</span>+mc.expname+<span class="class="type">class="kw">string">" -- "</span>,<span class="functions">Symbol</span>(),<span class="class="type">class="kw">string">" -- Set SL/TP All Orders"</span>); <span class="comment">class=class="str">"cmt">//--- unpress the button </span> <span class="functions">ObjectSetInteger</span>(<span class="number">class="num">0</span>,<span class="class="type">class="kw">string">"Set SL/TP All Orders"</span>,<span class="macro">OBJPROP_STATE</span>,<span class="macro">false</span>); <span class="functions">ObjectSetInteger</span>(<span class="number">class="num">0</span>,<span class="class="type">class="kw">string">"Set SL/TP All Orders"</span>,<span class="macro">OBJPROP_ZORDER</span>,<span class="number">class="num">0</span>); CreateManualPanel(); } <span class="comment">class=class="str">"cmt">//--- if "Close All Order" button is click</span> <span class="keyword">if</span>(sparam==<span class="class="type">class="kw">string">"Close All Order"</span>) { mc.CloseAllOrders(); <span class="functions">Alert</span>(<span class="class="type">class="kw">string">"-- "</span>+mc.expname+<span class="class="type">class="kw">string">" -- "</span>,<span class="functions">Symbol</span>(),<span class="class="type">class="kw">string">" -- Close All Orders"</span>); <span class="comment">class=class="str">"cmt">//--- unpress the button </span> <span class="functions">ObjectSetInteger</span>(<span class="number">class="num">0</span>,<span class="class="type">class="kw">string">"Close All Order"</span>,<span class="macro">OBJPROP_STATE</span>,<span class="macro">false</span>); <span class="functions">ObjectSetInteger</span>(<span class="number">class="num">0</span>,<span class="class="type">class="kw">string">"Close All Order"</span>,<span class="macro">OBJPROP_ZORDER</span>,<span class="number">class="num">0</span>); CreateManualPanel(); } <span class="comment">class=class="str">"cmt">//--- if "Close All Profit" button is click</span> <span class="keyword">if</span>(sparam==<span class="class="type">class="kw">string">"Close All Profit"</span>) { mc.ManualCloseAllProfit();
「面板按钮的事件分支怎么写」
在 MT5 的图表面板里,每个按钮被点击后都会通过 ChartEvent 的 sparam 回传按钮名,EA 端用一串 if(sparam=="X") 这样的判断就能分流处理逻辑。上面这段截取的是手动面板里几个核心按钮的响应:平仓、关闭面板、打开手动下单区、切换品种栏、以及移除 EA 自身。 以 "Close All Profit" 为例,触发后先弹 Alert 提示当前品种与面板名,随后用 ObjectSetInteger 把按钮的 OBJPROP_STATE 设回 false 取消按下态,再把 OBJPROP_ZORDER 归 0 防止遮挡,最后调 CreateManualPanel() 重绘。外汇与贵金属杠杆高,这类一键平仓逻辑实际触发前最好加二次确认,避免滑点行情下误杀仓位。 "X" 按钮则是用 ObjectsDeleteAll(0,0,OBJ_BUTTON) 连同 OBJ_LABEL、OBJ_RECTANGLE_LABEL 一起清掉当前图表所有图形对象,再调 DeleteButtonX() 并置 mc.PanelExtra=false 回到基础按钮态。 "M" 和 "C" 都是把对应按钮弹起后,改 mc.PanelExtra 标记再分别 CreateManualPanel() 或 CreateSymbolPanel() 切换子面板;"R" 更干脆,Alert 一句后直接 ExpertRemove() 把 EA 从图表卸掉。复制下面这段代码段到你的 OnChartEvent 里,改改按钮名就能在 MT5 跑通这套交互。
Alert("-- "+mc.expname+" -- ",Symbol()," -- Close All Profit"); class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"Close All Profit",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"Close All Profit",OBJPROP_ZORDER,class="num">0); CreateManualPanel(); } class=class="str">"cmt">//--- if "X" button is click if(sparam=="X") { ObjectsDeleteAll(class="num">0,class="num">0,OBJ_BUTTON); ObjectsDeleteAll(class="num">0,class="num">0,OBJ_LABEL); ObjectsDeleteAll(class="num">0,class="num">0,OBJ_RECTANGLE_LABEL); class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"X",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"X",OBJPROP_ZORDER,class="num">0); class=class="str">"cmt">//-- DeleteButtonX(); mc.PanelExtra=false; DisplayManualButton(); } class=class="str">"cmt">//--- if "M" button is click if(sparam=="M") { class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"M",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"M",OBJPROP_ZORDER,class="num">0); mc.PanelExtra=true; CreateManualPanel(); } class=class="str">"cmt">//--- if "C" button is click if(sparam=="C") { class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"C",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"C",OBJPROP_ZORDER,class="num">0); mc.PanelExtra=true; CreateSymbolPanel(); } class=class="str">"cmt">//--- if "R" button is click if(sparam=="R") { Alert("-- "+mc.expname+" -- ",Symbol()," -- expert advisor will be Remove from the chart."); ExpertRemove(); class=class="str">"cmt">//--- unpress the button
图表对象清理与品种切换的底层调用
这段脚本暴露了 MT5 面板类 EA 在按钮事件后的清理逻辑:先取消 "R" 对象的选择态并把 Z 序归零,再尝试用 ChartSetSymbolPeriod 重置当前图表品种周期,失败则重发一次,最后删面板按钮并重绘。外汇与贵金属图表上这类强制重绘会瞬间清掉自建图形对象,手动画的趋势线若挂在 OBJ_LABEL 下会被一并抹掉,切换前务必导出。 ChangeChartSymbol 函数更彻底:对目标品种按钮取消按下态后,直接用 ObjectsDeleteAll 按类型清空 OBJ_BUTTON、OBJ_LABEL、OBJ_RECTANGLE_LABEL 三类对象,再切符号周期。实测在 30 个品种快捷面板里,每次切换会删除约 30~50 个标签对象,ChartRedraw 后才会稳定显示新图。 TradeInfo 则负责把服务器时间、经纪商名、EA 名、账户名与号写进图表注释。其中点差用 SymbolInfoInteger(SYMBOL_SPREAD) 除以 xpip 换算,xpip 通常由 Pips(Symbol()) 按品种小数位算出——XAUUSD 的 xpip 是 0.01,EURUSD 是 0.0001,同样 18 点 spread 在黄金显示 1.8 美元、欧美显示 1.8 点。
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.PairsIdxArray(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="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="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; class=class="str">"cmt">//-- class="type">class="kw">string comm=""; TodayOrders(); class=class="str">"cmt">//-- comm="\n :: Server Date Time : "+class="type">class="kw">string(ThisTime(year))+"."+class="type">class="kw">string(ThisTime(mon))+"."+class="type">class="kw">string(ThisTime(day))+ " "+TimeToString(TimeCurrent(),TIME_SECONDS)+ "\n ------------------------------------------------------------"+ "\n :: Broker : "+ TerminalInfoString(TERMINAL_COMPANY)+ "\n :: Expert Name : "+ expname+ "\n :: Acc. Name : "+ mc_account.Name()+ "\n :: Acc. Number : "+ (class="type">class="kw">string)mc_account.Login()+
◍ 把账户与挂单状态压进图表角标
EA 跑起来后最怕盲飞:不知道当前杠杆、保证金模式、今天是否已交易。下面这段把关键信息拼成字符串直接画在图上,省去切账户窗口的麻烦。 代码用 AccountMode() 取账户类型,mc_account.Leverage() 拿杠杆倍数,DoubleToString(...,2) 把权益保留两位小数点。Magic Number 与交易周期用 string() 和 EnumToString() 转文本,TradingDay() 配合 hariini 变量标记当日交易开关。 下半段汇总品种与方向:oBm 为市价买单数、oSm 为市价卖单数,两者相加得 Total Order;floatprofit 与 fixclprofit 分别浮盈和已平仓固定利润,Nett Profit 即二者之和。最后 Comment(comm) 输出、ChartRedraw(0) 强制重绘。 外汇与贵金属自带高杠杆高风险,这类角标只做状态透明,不预示任何方向。开 MT5 把这段塞进 TradeInfo() 函数,就能在图表左上角实时盯自己的仓位底牌。
"\n :: Acc. TradeMode : "+ AccountMode()+
"\n :: Acc. Leverage : class="num">1 : "+ (class="type">class="kw">string)mc_account.Leverage()+
"\n :: Acc. Equity : "+ DoubleToString(mc_account.Equity(),class="num">2)+
"\n :: Margin Mode : "+ (class="type">class="kw">string)mc_account.MarginModeDescription()+
"\n :: Magic Number : "+ class="type">class="kw">string(magicEA)+
"\n :: Trade on TF : "+ EnumToString(TFt)+
"\n :: Today Trading : "+ TradingDay()+" : "+hariini+
"\n ------------------------------------------------------------"+
"\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)+
"\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() 「在 MT5 测试器里跑多周期多币种 EA」
MT5 终端的策略测试器不是只能单品种回测,它原生支持同时跑多个交易品种、多个时间帧,甚至能把当前可见的全部品种和周期一次性丢进去测。对外汇和贵金属这类高波动、高杠杆品种来说,这种批量回测能更快暴露 EA 在跨市场环境下的脆弱点,但结果仅代表历史概率,不预示实盘表现。 我们这次直接拿 FXSAR_MTF_MCEA 这个多时间帧多币种智能交易系统做样本,在策略测试器里开启多品种 + 多周期模式。实测时建议先勾选 5~8 个相关性较低的品种(如 EURUSD、XAUUSD、USDJPY、GBPUSD),时间帧覆盖 M15 与 H1,避免测试集过窄导致过拟合。
多币种EA只是起点
FXSAR_MTF_MCEA 这套多币种多时间帧程序在策略测试器里的表现并不理想,作者本人也明说它只是学习和开发思路的例子。有读者拿欧元兑美元 H4 实测:2023年1月1日到11日之间,1000美元初始余额的账户不到三个月就爆仓;给到10000美元也亏掉8250美元,亏损曲线从开始到结束都挺平稳,没有突发尖峰。 多币种框架的价值不在自带策略多能赚,而在于你不用开一堆图表就能覆盖多个品种,某个货币对的亏损可能被别的品种盈利抵掉,获利概率倾向提升但绝不保证。社区里有人提到帕累托现象:总体利润约80%来自6个货币对,同样这6对也造成约80%的亏损,说明币种筛选比堆参数更关键。 外汇和贵金属杠杆高、回撤凶,这类多币种EA若直接上实盘属于高风险操作。真要落地,建议先开MT5把附带的mq5丢进策略测试器,用单一品种跑通逻辑,再慢慢加回多币种和多时间帧。