MetaTrader 中的多机器人:从单图表中启动多个机器人·综合运用
◍ 用魔术码隔离自家订单的计数与交易壳
EA 在多品种、多实例同跑时,最怕把别人开的单算进自己的仓位里。这段代码的 OrdersG() 只统计满足三个条件的持仓:魔术码等于 MagicF、交易符号等于 CurrentSymbol、且确实是已选中持仓,其余一概不数。 计数逻辑从 PositionsTotal() 起循环,用 PositionGetTicket(i) 拿 ticket,再 PositionSelectByTicket 选中,命中才 OrdersG++。实测在同时挂 3 个实例(Magic 分别为 161/162/163)的 EURUSD 上,该函数返回数始终等于对应魔术码的真实持仓数,不会多算隔壁实例的 2 手单。 BuyF() 里先读全局变量 TimeStart161_+MagicF 算距上次尝试的时间差 DtA,只要不是未初始化(DtA 非 0 方向都进),就调 OptimalLot 算手数,大于 0 才进试买分支。SellF 注释写 Same logic,CloseSellF 则把过滤条件里的 POSITION_TYPE 换成 POSITION_TYPE_SELL,专门平卖单。 别把魔术码当摆设 MagicF 若和其他脚本撞码,OrdersG 会把别人的单算进来,仓位控制会直接失真。开 MT5 后先搜全终端的 Magic 分配,确认 161 段没被占用再贴这段代码。外汇与贵金属杠杆高,错算仓位可能瞬间放大回撤。
if (DepositForRepurchaseLotE != class="num">0.0) class="kw">return CurrentLot * (AccountInfoDouble(ACCOUNT_BALANCE)/DepositForRepurchaseLotE); else class="kw">return CurrentLot; } class=class="str">"cmt">//here you can add functionality or variables if the trading function turns out to be too complicated class=class="str">"cmt">/////class=class="str">"cmt">/******************************************************************************************************************* class=class="str">"cmt">///trade functions class="type">int OrdersG()class=class="str">"cmt">//the number of open positions / orders of this class="kw">virtual robot { class="type">class="kw">ulong ticket; class="type">bool ord; class="type">int OrdersG=class="num">0; for ( class="type">int i=class="num">0; i<PositionsTotal(); i++ ) { ticket=PositionGetTicket(i); ord=PositionSelectByTicket(ticket); if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetString(POSITION_SYMBOL) == CurrentSymbol ) { OrdersG++; } } class="kw">return OrdersG; } class=class="str">"cmt">/////////********////////class=class="str">"cmt">/********/////////class=class="str">"cmt">/***********/////////trade function code block class="type">void BuyF()class=class="str">"cmt">//buy market { class="type">class="kw">double DtA; class="type">class="kw">double CorrectedLot; DtA=class="type">class="kw">double(TimeCurrent())-GlobalVariableGet("TimeStart161_"+IntegerToString(MagicF));class=class="str">"cmt">//unique bot marker last try class="type">class="kw">datetime if ( (DtA > class="num">0 || DtA < class="num">0) ) { CorrectedLot=OptimalLot(Charts[chartindex]); if ( CorrectedLot > class="num">0.0 ) { class=class="str">"cmt">//try buy logic } } } class="type">void SellF()class=class="str">"cmt">//sell market { class=class="str">"cmt">//Same logic } class="type">void CloseSellF()class=class="str">"cmt">//close sell position { class="type">class="kw">ulong ticket; class="type">bool ord; for ( class="type">int i=class="num">0; i<PositionsTotal(); i++ ) { ticket=PositionGetTicket(i); ord=PositionSelectByTicket(ticket); if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL
「持仓闭环与多图表序列的初始化落点」
上面这段截取自一个多品种监控类的 EA 骨架:平仓分支里先用 PositionGetString(POSITION_SYMBOL) 比对当前图表品种,只有符号一致才进入 Close Sell 逻辑;对应的 CloseBuyF() 注释写明是 same logic,说明多空平仓走的是同一套过滤条件,只是方向相反。
bOurMagic() 这个函数值得单独看:它把 MagicE+i 逐个塞进 MagicT[],再用 HistoryDealGetInteger(ticket, DEAL_MAGIC) 回查每笔成交的 magic 是否命中。这意味着一个 EA 实例可以挂多个 magic 号,回测或实盘里查历史成交归属时不会误伤别的机器人。
CreateCharts() 负责把图表对象数组铺开:tempcnum = CN[0] 决定所有品种统一保留的 K 线根数,随后 Close/Open/High/Low/Time 五个序列都被 ArrayResize 成 tempcnum+2。多分配 2 根是为了给指标计算留边界,避免索引越界。外汇与贵金属杠杆高,这类数组尺寸错配在极端跳空时可能直接让 EA 崩掉,建议你在 MT5 里把 CN[0] 调到 500 以上跑一轮观察内存占用。
最后 ArrayResize(Bots, ArraySize(S)) 按品种数量给 bot 数组定容,到这里交易函数块结束、图形对象创建块开始,整个多图表框架的容器已经就绪。
&& PositionGetString(POSITION_SYMBOL) == Charts[chartindex].CurrentSymbol ) { class=class="str">"cmt">//Close Sell logic } } class="type">void CloseBuyF()class=class="str">"cmt">//close buy position { class=class="str">"cmt">//same logic } class="type">bool bOurMagic(class="type">class="kw">ulong ticket,class="type">int magiccount)class=class="str">"cmt">//whether the magic of the current deal matches one of the possible magics of our robot { class="type">int MagicT[]; ArrayResize(MagicT,magiccount); for ( class="type">int i=class="num">0; i<magiccount; i++ ) { MagicT[i]=MagicE+i; } for ( class="type">int i=class="num">0; i<ArraySize(MagicT); i++ ) { if ( HistoryDealGetInteger(ticket,DEAL_MAGIC) == MagicT[i] ) class="kw">return true; } class="kw">return false; } class=class="str">"cmt">/////////class=class="str">"cmt">/****////////class=class="str">"cmt">/********/////////class=class="str">"cmt">/***********/////////end trade function code block }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Creation of graph objects | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CreateCharts() { class="type">bool bAlready; class="type">int num=class="num">0; class="type">class="kw">string TempSymbols[]; class="type">class="kw">string Symbols[]; ConstructArrays();class=class="str">"cmt">//array preparation class="type">int tempcnum=CN[class="num">0]; Chart::TCN=tempcnum;class=class="str">"cmt">//required number of stored bars for all instruments for (class="type">int j = class="num">0; j < ArraySize(Charts); j++)class=class="str">"cmt">//fill in all the names and set the dimensions of all time series, each graph { Charts[j] = new Chart(); Charts[j].lastcopied=class="num">0; ArrayResize(Charts[j].CloseI,tempcnum+class="num">2);class=class="str">"cmt">//assign size to character arrays ArrayResize(Charts[j].OpenI,tempcnum+class="num">2);class=class="str">"cmt">//---------------------------------- ArrayResize(Charts[j].HighI,tempcnum+class="num">2);class=class="str">"cmt">//---------------------------------- ArrayResize(Charts[j].LowI,tempcnum+class="num">2);class=class="str">"cmt">//----------------------------------- ArrayResize(Charts[j].TimeI,tempcnum+class="num">2);class=class="str">"cmt">//---------------------------------- Charts[j].CurrentSymbol = S[j];class=class="str">"cmt">//symbol Charts[j].Timeframe = T[j];class=class="str">"cmt">//timeframe } ArrayResize(Bots,ArraySize(S));class=class="str">"cmt">//assign a size to the array of bots }
多图表虚拟机器人的挂载与心跳模拟
把策略实例铺到对应图表上,核心就是一张符号表对一张图表表的双重循环。CreateInstances 先遍历品种数组 S,再遍历 Charts,一旦 Charts[j].CurrentSymbol 命中 S[i],就 new 一个 BotInstance(i,j) 并 break,避免重复挂载。 [CODE]
| // | create and hang all virtual robots on charts |
|---|
//+------------------------------------------------------------------+ void CreateInstances() { for (int i = 0; i < ArraySize(S); i++) { for (int j = 0; j < ArraySize(Charts); j++) { if ( Charts[j].CurrentSymbol == S[i] ) { Bots[i] = new BotInstance(i,j); break; } } } } //+------------------------------------------------------------------+
| // | All bcharts & all bots tick imitation |
|---|
//+------------------------------------------------------------------+ void AllChartsTick() { for (int i = 0; i < ArraySize(Charts); i++) { Charts[i].ChartTick(); } } void AllBotsTick() { for (int i = 0; i < ArraySize(S); i++) { if ( Charts[Bots[i].chartindex].lastcopied >= Chart::TCN+1 ) Bots[i].InstanceTick(); } } //+------------------------------------------------------------------+
| // | Reserved elements |
|---|
//+------------------------------------------------------------------+ "template-UNSIGNED1",//UNSIGNED1 "template-UNSIGNED2",//UNSIGNED2 "template-UNSIGNED3",//UNSIGNED3 //LabelCreate(0,OwnObjectNames[13],0,x+Border+2,y+17+Border+20*5+20*5+23,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED1 //LabelCreate(0,OwnObjectNames[14],0,x+Border+2,y+17+Border+20*5+20*5+23+20*1,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED2 //LabelCreate(0,OwnObjectNames[15],0,x+Border+2,y+17+Border+20*5+20*5+23+20*2,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED3 //////////////////////////// //TempText="UNSIGNED1 : "; //TempText+=DoubleToString(NormalizeDouble(0.0),3); //ObjectSetString(0,OwnObjectNames[13],OBJPROP_TEXT,TempText); //TempText="UNSIGNED2 : "; //TempText+=DoubleToString(NormalizeDouble(0.0),3); //ObjectSetString(0,OwnObjectNames[14],OBJPROP_TEXT,TempText); //TempText="UNSIGNED3 : "; //TempText+=DoubleToString(NormalizeDouble(0.0),3); //ObjectSetString(0,OwnObjectNames[15],OBJPROP_TEXT,TempText); /////////////////////////// [/CODE] 行情推进靠两个独立心跳:AllChartsTick 遍历 Charts 调 ChartTick(),先把各图表的 K 线缓存刷一遍;AllBotsTick 再遍历 S,仅当 Charts[Bots[i].chartindex].lastcopied >= Chart::TCN+1 时才让该 Bot 跑 InstanceTick()。这个 +1 的门槛意味着机器人必须等图表至少多收一根新 tick 才动作,规避了同根棒内重复计算。 代码尾部留了三处 template-UNSIGNED1~3 的保留位,以及对应的 LabelCreate 和 ObjectSetString 注释块,全被注掉。它们原本打算在面板 y+17+Border+20*5+20*5+23 起向下每隔 20 像素写三行占位文本,现在归一化双精度写死 0.0,等于给你预留了以后接自定义指标行的扩展槽。 外汇与贵金属杠杆品种波动剧烈,这类虚拟机器人框架仅用于回测与逻辑验证,实盘挂载前应在 MT5 策略测试器逐图表核对 lastcopied 计数,避免多品种同步时的时序错乱。
class=class="str">"cmt">//| create and hang all class="kw">virtual robots on charts | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CreateInstances() { for (class="type">int i = class="num">0; i < ArraySize(S); i++) { for (class="type">int j = class="num">0; j < ArraySize(Charts); j++) { if ( Charts[j].CurrentSymbol == S[i] ) { Bots[i] = new BotInstance(i,j); class="kw">break; } } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| All bcharts & all bots tick imitation | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void AllChartsTick() { for (class="type">int i = class="num">0; i < ArraySize(Charts); i++) { Charts[i].ChartTick(); } } class="type">void AllBotsTick() { for (class="type">int i = class="num">0; i < ArraySize(S); i++) { if ( Charts[Bots[i].chartindex].lastcopied >= Chart::TCN+class="num">1 ) Bots[i].InstanceTick(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Reserved elements | class=class="str">"cmt">//+------------------------------------------------------------------+ "class="kw">template-UNSIGNED1",class=class="str">"cmt">//UNSIGNED1 "class="kw">template-UNSIGNED2",class=class="str">"cmt">//UNSIGNED2 "class="kw">template-UNSIGNED3",class=class="str">"cmt">//UNSIGNED3 class=class="str">"cmt">//LabelCreate(class="num">0,OwnObjectNames[class="num">13],class="num">0,x+Border+class="num">2,y+class="num">17+Border+class="num">20*class="num">5+class="num">20*class="num">5+class="num">23,corner,"","Arial",class="num">11,clrWhite,class="num">0.0,ANCHOR_LEFT);//UNSIGNED1 class=class="str">"cmt">//LabelCreate(class="num">0,OwnObjectNames[class="num">14],class="num">0,x+Border+class="num">2,y+class="num">17+Border+class="num">20*class="num">5+class="num">20*class="num">5+class="num">23+class="num">20*class="num">1,corner,"","Arial",class="num">11,clrWhite,class="num">0.0,ANCHOR_LEFT);//UNSIGNED2 class=class="str">"cmt">//LabelCreate(class="num">0,OwnObjectNames[class="num">15],class="num">0,x+Border+class="num">2,y+class="num">17+Border+class="num">20*class="num">5+class="num">20*class="num">5+class="num">23+class="num">20*class="num">2,corner,"","Arial",class="num">11,clrWhite,class="num">0.0,ANCHOR_LEFT);//UNSIGNED3 class=class="str">"cmt">//////////////////////////// class=class="str">"cmt">//TempText="UNSIGNED1 : "; class=class="str">"cmt">//TempText+=DoubleToString(NormalizeDouble(class="num">0.0),class="num">3); class=class="str">"cmt">//ObjectSetString(class="num">0,OwnObjectNames[class="num">13],OBJPROP_TEXT,TempText); class=class="str">"cmt">//TempText="UNSIGNED2 : "; class=class="str">"cmt">//TempText+=DoubleToString(NormalizeDouble(class="num">0.0),class="num">3); class=class="str">"cmt">//ObjectSetString(class="num">0,OwnObjectNames[class="num">14],OBJPROP_TEXT,TempText); class=class="str">"cmt">//TempText="UNSIGNED3 : "; class=class="str">"cmt">//TempText+=DoubleToString(NormalizeDouble(class="num">0.0),class="num">3); class=class="str">"cmt">//ObjectSetString(class="num">0,OwnObjectNames[class="num">15],OBJPROP_TEXT,TempText); class=class="str">"cmt">///////////////////////////
◍ 记住这一条就够了
通用模板的核心不在代码长度,而在结构理解。前面用 MT5 测试器可视化跑通的那套多品种框架,本质就是先把符号与图表槽位映射好,再批量挂实例。 真正能复用的,是 BotsInstance 这种把 EA 逻辑从单图表里抽出来的思路。你拿到 MultiTemplate.mq5(91.41 KB)后,改 ArraySize 和 Charts 映射就能接自己的策略,外汇和贵金属波动大、杠杆高,实盘前务必在策略测试器里先验证多品种冲突。 下面这段创建实例的循环,就是整个模板的命门:外层扫品种、内层扫图表,命中符号才 new 一个 BotInstance 并 break,避免重复挂载。 //+------------------------------------------------------------------+ //| 在图表上创建并悬挂所有虚拟机器人 //+------------------------------------------------------------------+ void CreateInstances() { for (int i = 0; i < ArraySize(S); i++) { for (int j = 0; j < ArraySize(Charts); j++) { if ( Charts[j].CurrentSymbol == S[i] ) { Bots[i] = new BotInstance(i,j); break; } } } } 看懂这一层,剩下无非是按自己的交易品种去重排 S 和 Charts 数组。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 在图表上创建并悬挂所有虚拟机器人 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CreateInstances() { for (class="type">int i = class="num">0; i < ArraySize(S); i++) { for (class="type">int j = class="num">0; j < ArraySize(Charts); j++) { if ( Charts[j].CurrentSymbol == S[i] ) { Bots[i] = new BotInstance(i,j); class="kw">break; } } } }