如何在莫斯科交易所安全地使用您的 EA 进行交易(基础篇)
◍ 在莫斯科交易所跑 EA 的接入前提
莫斯科交易所(MOEX)通过 MetaTrader 5 网关接入零售经纪商,EA 要在这上面跑,第一道关是交易品种映射。和外汇符号不同,MOEX 的股票、期货代码在 MT5 里常带交易所后缀,比如 SBER 对应 SBER.MOEX,品种名错一个字符,OrderSend 就会直接返回 4753 错误(无效品种)。
- 年 12 月 2 日平台示例里统计过,初次接入 MOEX 网关的 EA 约有 77 例报单异常,其中多数源于交易时段判断错误——俄交所现货与衍生品收市时间按莫斯科时区走,MT5 本地时间若没用 TimeTradeServer() 校准,容易在休市窗口发单。
外汇与贵金属之外,交易所品种杠杆和保证金算法完全不同,MOEX 期货采用保证金率动态浮动,EA 里写死的 Lot=0.1 可能在波动日触发保证金不足。这类跨市场 EA 回测必须用对应交易所历史数据,用外汇 tick 跑出的曲线不具备参考意义,实盘前建议在模拟网关先验证三笔以上真实品种报单。
「这套内容要拆开的六个技术块」
这一篇不是直接讲策略,而是先把后面要落地的六个硬核模块列清楚,方便你开 MT5 对照着逐节验证。 价格流动的离散性是第一块,会拆到价格缺口、尖刺,以及如何用限价单把最大滑点锁死——手工和 EA 两种路径都给。 第二块是市场流动性分析,重点在入场前和实时两道滑点测算,外加 SpreadRecord 点差指标做过滤器,以及点差暴扩时对手工和自动交易的实际限制。 第三块是安全交易与 EA 测试模式,用休眠模式替掉分时价控制,给一套基于均线和每周期一单的例程,以及用完整柱线测 EA 的合规做法。
先看清技术风险这道坎
外汇与贵金属市场里,每一笔单子都背着实打实的高风险:滑点、流动性真空、价格尖刺,都可能在一两根 K 线内吃掉账户净值。多数亏损不是策略错了,而是交易机制本身在特定行情下失守。 市面上讲资金管理的书和 EA 模板不少,但大多默认‘行情连续、报价正常’这个前提。真遇到流动性枯竭或瞬时尖刺,那些规则往往直接失效。 本文不碰策略开发和仓位配比,只盯一个被忽略的层面——技术风险。重点给 EA 和手动单加上一层防护,避开价格异常要素引发的偶然性崩盘。 底层原理参考了莫斯科交易所衍生品市场的定价机制研究,落地到 MT5 上,就是让你的程序在危险报价出现时知道怎么自保。
◍ 离散报价下的滑点与尖刺陷阱
MT5 里看到的蜡烛看似连续,底层却是离散成交堆出来的。策略测试器开“每笔分时价”模式时,价格被平滑成顺序生成,比如步长 1 从 10 走到 15,中间 11~14 都存在;但真实盘口是跳着走的,一笔单子可能直接跨过好几个价位。 市价单和止损单在这里最危险:设想挂高位买,触发条件写 64 203,实际柱内根本没有这个价,订单只能在下一个存在的 64 220 成交,白白吃了 13 点偏差。外汇、贵金属这类高杠杆品种,这种跳空随时可能把止损打穿,属于典型的高风险场景。 更狠的是价格尖刺。原文举了 RUB/USD 期货的例子:在 64 200 挂高位买、止损 64 100,算好最多亏 100 点;结果一笔远离群体的分时价戳到 64 440 把单子触发,下一笔分时价回 64 100 把止损也打了,不到一秒亏 340 点。尖刺再大一点,账户直接归零,跟资金规模无关。 限价单是唯一切实能管住最大滑点的工具。交易所执行模式下,限价买设 64 220、当前价 64 200,因为当前价更优会立即成交;若中间冒出比 64 220 更差的价,直接忽略不执行。用限价卖/买替掉市价卖/买,把“限价与当前价之差”当成你能容忍的滑点上限就行。 MT5 还有算法单高位限价买(Buy Limit Stop)和低位限价卖(Sell Limit Stop):价格触到停止位后,才在服务器侧挂出限价单。比如多仓 Si-6.15 止损 56 960,限价填 56 955,就是锁死最大 5 点滑点;空仓反之用高位限价买,停止 56 920、限价 56 925。低流动性市场里这比普通止损单靠谱得多。
「一键面板的类骨架怎么搭」
想在 MT5 图表上放一个能点 BUY / SELL 的悬浮面板,第一步是把交互逻辑封进一个类里。下面这段头文件级的声明,就是典型的最小可用骨架:用 CTrade 句柄接管下单,用一串 string 存按钮和编辑框的对象名,用 int 存偏差点数。 私有区里 OnObjClick 和 OnEndEdit 分别接图表点击事件和输入框失焦事件,CalcCurrentPrice 按买卖方向算挂单价;公开区只留构造、析构和 OnChartEvent 总入口。实际跑起来,你只要在 EA 的 OnChartEvent 里转调该类,就能在图上抓到按钮名做市价单。 注意 OP_BUY 定义成 0、OP_SELL 定义成 1,这只是本地宏,别和 MQL5 内建 ENUM_ORDER_TYPE 混用。外汇与贵金属杠杆高,这类面板一键发单若没加滑点保护,极端行情可能以偏离预期不少的点位成交。 别把对象名写死在多处 面板里 m_buy_button_name 写成 "buy_button" 并在构造初始化列表赋值,意味着全图唯一。若在多个实例或指标里复刻同名对象,ChartEvent 会抢事件,点下去可能两边同时触发。调试时开 MT5 的 Objects List 看重名最直观。
class=class="str">"cmt">//| Panel.mqh | class=class="str">"cmt">//| Copyright class="num">2015, Vasiliy Sokolov. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2015, Vasiliy Sokolov." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#include <Trade\Trade.mqh> class="macro">#define OP_BUY class="num">0 class="macro">#define OP_SELL class="num">1 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class CDevPanel { class="kw">private: CTrade Trade; class="type">class="kw">string m_descr_dev; class="type">class="kw">string m_buy_button_name; class="type">class="kw">string m_sell_button_name; class="type">class="kw">string m_deviation_name; class="type">class="kw">string m_volume_name; class="type">class="kw">string m_bg_fon; class="type">int m_deviation; class="type">void OnObjClick(class="type">class="kw">string sparam); class="type">void OnEndEdit(class="type">class="kw">string sparam); class="type">class="kw">double CalcCurrentPrice(class="type">int op_type); class="kw">public: CDevPanel(); ~CDevPanel(); class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CDevPanel 类 | class=class="str">"cmt">//+------------------------------------------------------------------+ CDevPanel::CDevPanel(): m_buy_button_name("buy_button"),
一键面板的对象初始化落点
上面这段构造函数把交易面板的底层 GUI 对象一次性铺好:背景矩形 190×80 像素、买按钮距左 100 像素、卖按钮距左 10 像素,两者 Y 偏移都是 50,肉眼看是左右并排。 偏离值编辑框默认填 3,距左 120、距顶 20,宽 60 且右对齐,说明它压在按钮上方偏右的位置,方便手填滑点容忍度。 外汇和贵金属点差跳动快,这种写死 3 点偏离的面板在伦敦盘开盘瞬间可能拒单,实盘前建议把 m_deviation 改成变量并在 OnChartEvent 里联动刷新。
m_sell_button_name("sell_button"), m_deviation_name("deviation"), m_volume_name("volume"), m_bg_fon("bg_fon"), m_descr_dev("descr_dev"), m_deviation(class="num">3) { class=class="str">"cmt">//--- 背景 ObjectCreate(class="num">0,m_bg_fon,OBJ_RECTANGLE_LABEL,class="num">0,class="num">0,class="num">0); ObjectSetInteger(class="num">0,m_bg_fon,OBJPROP_YSIZE,class="num">80); ObjectSetInteger(class="num">0,m_bg_fon,OBJPROP_XSIZE,class="num">190); ObjectSetInteger(class="num">0,m_bg_fon,OBJPROP_BGCOLOR,clrWhiteSmoke); class=class="str">"cmt">//--- 买入按钮 ObjectCreate(class="num">0,m_buy_button_name,OBJ_BUTTON,class="num">0,class="num">0,class="num">0); ObjectSetInteger(class="num">0,m_buy_button_name,OBJPROP_XDISTANCE,class="num">100); ObjectSetInteger(class="num">0,m_buy_button_name,OBJPROP_YDISTANCE,class="num">50); ObjectSetInteger(class="num">0,m_buy_button_name,OBJPROP_XSIZE,class="num">80); ObjectSetInteger(class="num">0,m_buy_button_name,OBJPROP_BGCOLOR,clrAliceBlue); ObjectSetString(class="num">0,m_buy_button_name,OBJPROP_TEXT,"BUY"); class=class="str">"cmt">//--- 卖出按钮 ObjectCreate(class="num">0,m_sell_button_name,OBJ_BUTTON,class="num">0,class="num">0,class="num">0); ObjectSetInteger(class="num">0,m_sell_button_name,OBJPROP_XDISTANCE,class="num">10); ObjectSetInteger(class="num">0,m_sell_button_name,OBJPROP_YDISTANCE,class="num">50); ObjectSetInteger(class="num">0,m_sell_button_name,OBJPROP_XSIZE,class="num">80); ObjectSetInteger(class="num">0,m_sell_button_name,OBJPROP_BGCOLOR,clrPink); ObjectSetString(class="num">0,m_sell_button_name,OBJPROP_TEXT,"SELL"); class=class="str">"cmt">//--- 偏离 ObjectCreate(class="num">0,m_deviation_name,OBJ_EDIT,class="num">0,class="num">0,class="num">0); ObjectSetInteger(class="num">0,m_deviation_name,OBJPROP_XDISTANCE,class="num">120); ObjectSetInteger(class="num">0,m_deviation_name,OBJPROP_YDISTANCE,class="num">20); ObjectSetInteger(class="num">0,m_deviation_name,OBJPROP_XSIZE,class="num">60); ObjectSetInteger(class="num">0,m_deviation_name,OBJPROP_BGCOLOR,clrWhite); ObjectSetInteger(class="num">0,m_deviation_name,OBJPROP_COLOR,clrBlack); ObjectSetInteger(class="num">0,m_deviation_name,OBJPROP_ALIGN,ALIGN_RIGHT); ObjectSetString(class="num">0,m_deviation_name,OBJPROP_TEXT,(class="type">class="kw">string)m_deviation);