用于在以仓位为中心的 MetaTrader 5 环境中跟踪订单的虚拟订单管理程序·进阶篇
📘

用于在以仓位为中心的 MetaTrader 5 环境中跟踪订单的虚拟订单管理程序·进阶篇

第 2/3 篇

用分形均线交叉搭一个虚拟订单EA

先从发行包里的EA模板起手,写一个最简单的均线交叉策略,但把普通MA换成分形自适应移动平均(FrAMA)。FrAMA会根据波动伸缩周期,在横向盘整时拉长均线,倾向减少无意义的穿插信号——这正是传统双均线最容易被洗的地方。 需要明确:这个EA只是演示虚拟订单管理器(VOM)怎么接进去的样例,不建议直接上实盘。回测里篮子能盈利,但总交易次数偏少,统计意义不足,外汇和贵金属本身又是高杠杆高风险品种,照搬参数大概率扛不住滑点和点差。 EA文件放在 experts\Virtual Order Manager\VOM EAs 目录下,可以直接丢进MT5策略测试器跑。下面这段代码就是核心骨架,重点看 OnInit 里怎么挂VOM、怎么取FrAMA句柄,以及 OnTick 里如何做交叉判定。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                        FraMA Cross EA VOM.mq5 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Paul Hampton-Smith"
class="macro">#class="kw">property link      "http:class=class="str">"cmt">//paulsfxrandomwalk.blogspot.com"
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">// 这是仅需的包含文件,它指向上一级文件夹
class="macro">#include "..\VirtualOrderManager.mqh"
class="kw">input class="type">class="kw">double   Lots=class="num">0.1;
class="kw">input class="type">int      Fast_MA_Period=class="num">2;
class="kw">input class="type">int      Slow_MA_Period=class="num">58;
class=class="str">"cmt">/*
因为经纪商价格为3位或者5位小数,止损和获利值应该乘以10.  
看起来似乎所有支持MetaTrader 5的经纪商都是提供3/5位小数价格的,
但是如果不是这样,还有一项重要工作即是增加
小数位数的侦测. */
class="kw">input class="type">int      Stop_Loss=class="num">5000;
class="kw">input class="type">int      Take_Profit=class="num">0;
class=class="str">"cmt">/*
我们也可以改变记录的级别.  LOG_VERBOSE 是最详细的
记录级别.  当EA交易被完全调试好以后,它可以改为
LOG_MAJOR.  记录文件位于 files\EAlogs 文件夹下并且会
自动删除30天以前的记录文件. */
class="kw">input ENUM_LOG_LEVEL Log_Level=LOG_VERBOSE;
class=class="str">"cmt">// 以下全局变量将保存移动平均(MA)的句柄和数值 
class="type">class="kw">double g_FastFrAMA[];
class="type">class="kw">double g_SlowFrAMA[];
class="type">int g_hFastFrAMA;
class="type">int g_hSlowFrAMA;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA初始化函数                                                    | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
  LogFile.LogLevel(Log_Level);
class=class="str">"cmt">// 使用 CVirtualOrderManager 的所有EA交易都需要包含下面这一行  
  VOM.Initialise();
  Comment(VOM.m_OpenOrders.SummaryList());
  g_hFastFrAMA = iFrAMA(_Symbol,_Period,Fast_MA_Period,class="num">0,PRICE_CLOSE);
  g_hSlowFrAMA = iFrAMA(_Symbol,_Period,Slow_MA_Period,class="num">0,PRICE_CLOSE);
  ArraySetAsSeries(g_FastFrAMA,true);
  ArraySetAsSeries(g_SlowFrAMA,true);
  class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA订单函数                                                      | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">// 使用 CVirtualOrderManager 的所有EA交易都需要包含下面这一行  
  VOM.OnTick();
  Comment(VOM.m_OpenOrders.SummaryList());
class=class="str">"cmt">// 现在我们取得 FrAMA 数值的最近两个拷贝, 
class=class="str">"cmt">// 它们位于 g_FastFrAMA 和  g_SlowFrAMA 数组中.  
  if(CopyBuffer(g_hFastFrAMA,class="num">0,Shift,class="num">2,g_FastFrAMA)!=class="num">2) ||
     CopyBuffer(g_hSlowFrAMA,class="num">0,Shift,class="num">2,g_SlowFrAMA)!=class="num">2)
     {
     Print("载入的历史数据不足");
     class="kw">return;
     }
class=class="str">"cmt">// 现在我们侦测快速 FrAMA 和 慢速 FrAMA 的交叉,
class=class="str">"cmt">// 关闭任何反向订单,新开一个买入订单

「快慢 FrAMA 金叉死叉的开平仓触发」

用两条不同周期的 FrAMA(自适应移动平均)做交叉过滤,是这套虚拟订单逻辑的核心进出场条件。快线从下穿上穿慢线,且上一根 K 线还是快线低于等于慢线,才认定金叉成立。 代码里先平掉反向虚拟单:金叉时清掉所有 SELL 虚拟单,死叉时清掉所有 BUY 虚拟单,避免双向持仓互锁。 开仓前还有两道闸门——同一根 K 线内已开单数小于 1,且当前总开放订单数为 0。这意味着信号即使重复触发,每根 bar 最多只进场一次,回测里能明显压住过度交易。外汇与贵金属杠杆高,这种同 bar 限制对滑点和重绘风险有缓冲作用,但盈亏仍取决于参数与品种波动,不保证胜率。 下面这段是交叉判定与下单的原样逻辑,可直接丢进 MT5 的 EA 主循环里验证:

MQL5 / C++
  if(g_FastFrAMA[class="num">0]>g_SlowFrAMA[class="num">0] && g_FastFrAMA[class="num">1]<=g_SlowFrAMA[class="num">1])
    {
      VOM.CloseAllOrders(_Symbol,VIRTUAL_ORDER_TYPE_SELL);
      if(VOM.OpenedOrdersInSameBar()<class="num">1 && VOM.OpenOrders()==class="num">0)
        {
         VOM.Buy(_Symbol,Lots,Stop_Loss,Take_Profit);
        }
    }
class=class="str">"cmt">// 相反则卖出
  if(g_FastFrAMA[class="num">0]<g_SlowFrAMA[class="num">0] && g_FastFrAMA[class="num">1]>=g_SlowFrAMA[class="num">1])
    {
      VOM.CloseAllOrders(_Symbol,VIRTUAL_ORDER_TYPE_BUY);
      if(VOM.OpenedOrdersInSameBar()<class="num">1 && VOM.OpenOrders()==class="num">0)
        {
         VOM.Sell(_Symbol,Lots,Stop_Loss,Take_Profit);
        }
    }
}

◍ 拆开 VOM 的骨架

VOM 把主要配置收敛到一个全局对象 Config 里,由 CConfig 管理,成员变量直接 public 暴露,不写 get/set。这样 EA 侧读写参数没有函数调用开销,但代价是配置项容易被任意模块误改,多人协作时要自己加约束。 全局变量走 MQL5 的 GlobalVariableGet() 系列接口,VOM 用它干两件要紧事:用 CGlobalVariable 记录并自增最新虚拟订单单证编号;维护一张所有虚拟止损的清单,支撑灾难防护用的服务器侧止损。重启 MT5 后这两类数据不丢,是虚拟订单能续命的基础。 订单与历史落地靠 CVirtualOrderArrays,在 Files\VOM 下给每个挂 VOM 的 EA 写一对永久磁盘文件。CVirtualOrder 实例生于 VOM.m_OpenOrders 数组,平仓或删除后转入 VOM.m_OrderHistory 数组——这意味着你能在终端 Files 目录直接翻到这两个文件,验证重启重建是否靠谱。 日志封装在 CLog,四个详细级别可选,且自动清旧文件控磁盘。调试时把级别调高,能在 MT5 日志里看到虚拟订单流转的全链路,比盲猜 EA 逻辑快得多。

用服务器止损兜住虚拟单的断线风险

虚拟止损只在客户端挂着,经纪商看不到你的退出位,这常被用来规避猎杀止损。但代价是连接一断,止损就失效,单子裸奔在市价里。 VOM 的做法是在服务器上挂一层灾难防护止损(DPSL),距离最紧的虚拟止损留出可配置的一段缓冲,比如下例统一设为 50 点。只有当经纪商-客户端连接中断一阵(断网或宕机)时,DPSL 才真正接管。 建 0.1 手买 #1 于 2.00000,虚损 1.99000,服务器同步 0.1 手买、服损 1.98500,即 DPSL 低于虚损 50 点。再建 0.1 手买 #2 虚损 1.99500,合并 0.2 手买后服损收到 1.99000,跟着更紧的虚损走。平 #2 后服损回退到 1.98500。 随后开 0.1 手卖 #3 虚损 2.00500,与 #1 在服务器上对冲抵消,仓位清空、服损撤销。平掉 #1 后剩卖 #3 未平,DPSL 翻到 2.01000,高于虚损 50 点。外汇与贵金属杠杆高,断线本身就可能放大亏损,DPSL 只是把‘连接依赖’这层风险从客户端挪到服务器,不等于免死金牌。

常见问题

快线FrAMA上穿慢线FrAMA视为金叉开多,下穿死叉平多开空;建议先在实盘品种用1小时图回看近3个月信号密度再上实仓。
本地虚拟单断线确实可能失联,需用券商服务器的真实止损单同步挂出,用服务器止损兜住断线期间的滑点风险。
小布可接入品种页实时标注快慢FrAMA金叉死叉并推送断线预警,你只需点开对应页面看AI标记即可。
VOM用内存数组存仓位方向手数开价,真实订单走交易池;调试时打印数组长度比对成交回执是最快验证法。
会,黄金美盘流动性裂口常致FrAMA毛刺;加最小跨度过滤或仅限亚盘低波动段运行,可降约四成无效交叉。