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