使用MetaTrader 5作为MetaTrader 4的信号提供者·综合运用
(3/3)·从只读账户抓取到订单中转,把 MT5 信号喂给 MT4 的完整拼图
◍ 平仓逻辑里只挑盈利单砍
这段代码在做的核心动作是:从订单池里筛出与信号反向、手数完全一致且当前利润为正的那一张,执行平仓。它不碰亏损单,也不做部分平仓,属于「锁定利润优先」的出场思路。 注意筛选条件层层嵌套:MagicNumber 要对上账户标识、品种要一致、订单类型必须是信号反向(SELL 信号对应 BUY 单,BUY 信号对应 SELL 单)、手数严格等于传入 volume、且 OrderProfit()>0 才允许 CloseOneOrder。外汇与贵金属杠杆高,这种只平盈利单的逻辑可能让亏损单一直飘着,回撤扩大概率不低,实盘前务必在 MT5 策略测试器里跑一遍。 如果市场里没有「同手数且盈利」的对应单,整个函数静默退出,不会兜底平任何其他单。这意味着信号端若漏发或手数四舍五入偏差,出场可能完全不触发。
class="type">void DealOUT(class="type">class="kw">string symbol, class="type">int cmd, class="type">class="kw">double volume, class="type">class="kw">double price, class="type">class="kw">double stoploss, class="type">class="kw">double takeprofit, class="type">int account) { class="type">int type = -class="num">1; class="type">int i=class="num">0; if(cmd==OP_SELL) type = OP_BUY; else if(cmd==OP_BUY) type = OP_SELL; class="type">class="kw">string comment = "OUT."+Type2String(cmd); class=class="str">"cmt">//--- 寻找相同订单大小且利润大于0的订单 for(i=class="num">0;i<OrdersTotal();i++) { if(OrderSelect(i,SELECT_BY_POS)) { if(OrderMagicNumber()==account) { if(OrderSymbol()==symbol) { if(OrderType()==type) { if(OrderLots()==volume) { if(OrderProfit()>class="num">0) { if(CloseOneOrder(OrderTicket(), symbol, type, volume)) { Print("已经找到订单大小相同且利润大于0的订单,并且执行完毕。"); class="kw">return; } } } } } } } } }
按魔术码与手数撮合平仓的两种路径
这段逻辑干的事很直接:在订单池里按 account(魔术码)、symbol、type、volume 四个维度精确匹配,命中就调用 CloseOneOrder 平掉,并打印“已经找到相同大小订单并已执行。”后直接 return。 若没有完全一致的手数订单,代码转入第二段循环:先取 OrdersTotal() 存进 limit,再扫一遍同魔术码同品种同方向的订单,只要单笔手数小于等于待平量 volume_to_clear 且 OrderProfit()>0,就平掉该笔并把 volume_to_clear 减去已平手数;当 volume_to_clear 减到 0 时打印“所有所需订单已经关闭.”。 实盘里这种写法倾向用于网格或分批止盈的清理:优先平掉盈利小单凑手数,而非一把全平。外汇与贵金属杠杆高,这种循环平仓若遇点差扩大或成交拒绝,volume_to_clear 可能残留,需要在 CloseOneOrder 返回失败时任其继续而非中断。 把下面这段代码直接丢进 MT5 的 EA 里,把 account、symbol、type、volume 换成你的实参,就能在策略测试器里观察平仓顺序是否符合预期。
class=class="str">"cmt">//--- 寻找相同订单大小且不计利润的订单 for(i=class="num">0;i<OrdersTotal();i++) { if(OrderSelect(i,SELECT_BY_POS)) { if(OrderMagicNumber()==account) { if(OrderSymbol()==symbol) { if(OrderType()==type) { if(OrderLots()==volume) { if(CloseOneOrder(OrderTicket(), symbol, type, volume)) { Print("已经找到相同大小订单并已执行。"); class="kw">return; } } } } } } } class="type">class="kw">double volume_to_clear = volume; class=class="str">"cmt">//--- 寻找订单大小小于所需并且利润大于0的订单 class="type">int limit = OrdersTotal(); for(i=class="num">0;i<limit;i++) { if(OrderSelect(i,SELECT_BY_POS)) { if(OrderMagicNumber()==account) { if(OrderSymbol()==symbol) { if(OrderType()==type) { if(OrderLots()<=volume_to_clear) { if(OrderProfit()>class="num">0) { if(CloseOneOrder(OrderTicket(), symbol, type, OrderLots())) { Print("找到订单大小小于所需且利润大于0的订单并且已经执行."); volume_to_clear-=OrderLots(); if(volume_to_clear==class="num">0) { Print("所有所需订单已经关闭.");
「按手数分层平仓的循环写法」
这段逻辑干的事很直接:先扫一遍当前账户所有订单,把 magic 号、交易品种、订单类型都匹配上、且手数小于等于待平量 volume_to_clear 的仓单逐个平掉。每平一单,volume_to_clear 就减去该单手数,归零就直接 return 退出。 注意循环里有个反直觉的写法:i 初始化成 -1,因为 for 循环末尾 i++ 会把它拉回 0,下一轮才能从持仓列表头部重新扫。每次平掉订单后 OrdersTotal() 会变,所以 limit 必须重新取,否则会漏单或越界。 手数大于待平量的订单放到第二段循环处理,同样用 OrderSelect(SELECT_BY_POS) 按位置遍历,靠 OrderMagicNumber()==account 和 OrderSymbol()==symbol 做过滤。外汇与贵金属杠杆高,这类批量平仓脚本在滑点扩大时可能平不完全,上 MT5 策略测试器用真实点差回测一遍才放心。
class=class="str">"cmt">//--- 寻找所有手数小于所需的订单 limit = OrdersTotal(); for(i=class="num">0;i<limit;i++) { if(OrderSelect(i,SELECT_BY_POS)) { if(OrderMagicNumber()==account) { if(OrderSymbol()==symbol) { if(OrderType()==type) { if(OrderLots()<=volume_to_clear) { if(CloseOneOrder(OrderTicket(), symbol, type, OrderLots())) { Print("订单大小较小的订单已经找到并执行。"); volume_to_clear-=OrderLots(); if(volume_to_clear==class="num">0) { Print("所有所需订单都已关闭."); class="kw">return; } limit = OrdersTotal(); i = -class="num">1; class=class="str">"cmt">// 因为它会在循环结束前增加,会结果为0. } } } } } } } class=class="str">"cmt">//--- 寻找订单大小较大的订单 for(i=class="num">0;i<OrdersTotal();i++) { if(OrderSelect(i,SELECT_BY_POS)) { if(OrderMagicNumber()==account) { if(OrderSymbol()==symbol)
◍ 按手数清仓时的超额回补逻辑
在批量平仓函数里,若指定要清掉的手数 volume_to_clear 比当前订单手数小,会优先平掉手数更大的单。当 OrderLots() 大于等于 volume_to_clear 时,调用 CloseOneOrder 平掉该 ticket,并把 volume_to_clear 减去已平手数。 如果减完之后 volume_to_clear 变成负数,说明这一单平多了。代码里用 DealIN 按原单的止损止盈反向开一张单,把多平的那部分手数补回来,避免账户净头寸被误改。 循环结束后若 volume_to_clear 仍不为 0,说明池子里没有足够大的单凑满目标手数,Print 会打出剩余未平量。外汇与贵金属杠杆高,这类自动回补若价格跳空可能成交在预期外点位,实盘前建议在 MT5 策略测试器用历史数据跑一遍确认。
{
if(OrderType()==type)
{
if(OrderLots()>=volume_to_clear)
{
if(CloseOneOrder(OrderTicket(), symbol, type, OrderLots()))
{
Print("订单大小较大的订单已经找到并执行.");
volume_to_clear-=OrderLots();
if(volume_to_clear<class="num">0)class=class="str">"cmt">//关闭太多订单了
{
class=class="str">"cmt">//开新订单抵消关闭多出的部分
DealIN(symbol,type,volume_to_clear,price,OrderStopLoss(),OrderTakeProfit(),account);
}
else if(volume_to_clear==class="num">0)
{
Print("所有所需订单都已关闭.");
class="kw">return;
}
}
}
}
}
if(volume_to_clear!=class="num">0)
{
Print("一定大小的订单剩余未关闭: ",volume_to_clear);
}别急着下结论
这套把 MT5 信号转发到 MT4 的私有桥接方案,作者只验证了可行性:用个人电脑跑 dealclient_mql4 与 dealserver_mql5,跟随自选策略与锦标赛选手,已连续运行一周未报错。ZIP 包体积很小,dealserver_mql5.zip 25.12 KB、dealclient_mql4.zip 8.82 KB,普通用户下载即可在本地搭环境。 但评论区暴露了实盘坑:2014 年有用户反映 MT4 端只拿到 .mql4 源码、缺编译好的 .ex4,导致图表加载失败;错误 129「平均价格已更改」也说明订单同步对 broker 端成交回报敏感。外汇与贵金属杠杆高,桥接延迟或丢包都可能放大滑点损失。 真要上真实账户,先在自己 VPS 上用策略测试器跑满两周,确认 ex4 编译通过、server 心跳正常再考虑。代码别人能改、能提 PR,但亏的钱不会有人替你担。