如何在莫斯科交易所安全地使用您的 EA 进行交易·综合运用
(3/3)·从价格缺口到休眠模式,把技术风险关进笼子再让 EA 上场
点差采集与柱线初始化的底层写法
这段逻辑把点差拆成了开、高、低三档来记录,而不是只存一个瞬时值。GetSpread() 用 SYMBOL_ASK 减 SYMBOL_BID 再除以 Point(),最后 NormalizeDouble 到整数位,返回的是以「点」为单位的点差点数。 InitNewBar() 在新柱到来时把 spread_open 设为当前点差,spread_high 归零、spread_low 置为 DBL_MAX,相当于给后续逐 tick 刷新高低区间留好坑。avrg_current 和 count 清零,说明均值统计是每根柱独立计算的。 EA 侧通过 iCustom 挂了一个名为「Spread Record」的自定义指标,句柄存进 h_spread_record;MaxSpread 输入参数默认 9,意味着只处理点差不超过 9 点的行情——外汇与贵金属点差受流动性影响大,实盘里这个阈值可能要按品种重调。 开 MT5 把这段贴进 EA,改 MaxSpread 到对应品种常态点差上限,就能直接验证新柱初始化是否如预期清零。
class="type">class="kw">double GetSpread() { class="type">class="kw">double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK); class="type">class="kw">double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID); class="kw">return NormalizeDouble((ask-bid)/Point(), class="num">0); } class="type">void InitNewBar(class="type">int index) { spread_open[index] = GetSpread(); spread_high[index] = class="num">0.0; spread_low[index]=DBL_MAX; avrg_current=class="num">0.0; count=class="num">0; } class="macro">#define OPEN class="num">0 class="macro">#define HIGH class="num">1 class="macro">#define LOW class="num">2 class="macro">#define CLOSE class="num">3 class="macro">#define AVRG class="num">4 input class="type">int MaxSpread=class="num">9; class="type">int h_spread_record=INVALID_HANDLE; class="type">bool print_disable = class="kw">false; class="type">int OnInit() { h_spread_record=iCustom(Symbol(),Period(),"Spread Record"); class="kw">return(INIT_SUCCEEDED); }
◍ 用点差阈值卡掉无序行情
EA 在 OnTick 里第一道闸不是信号,而是点差过滤。IsTradeDisable(MaxSpread) 返回 true 就直接 return,后续交易逻辑根本不跑,相当于把流动性差的瞬间挡在门外。 核心判断在 IsTradeDisable:先用 CopyBuffer 取点差指标的最新收盘价,若取不到或句柄失效则返回 false(不拦交易);只有 close[0] 大于 MaxSpread 才置 print_disable 并禁交易。日志里 2015.05.27 16:57:08 这两行,允许和禁止相隔仅 20 毫秒,说明点差在秒级内穿越阈值,自动化拦截比手动快得多。 OnDeinit 里一句 IndicatorRelease(h_spread_record) 别漏,指标句柄不释放会在反复加载脚本时堆残留。外汇和贵金属点差跳变频繁,这类阈值过滤只能降低风险,不保证避开所有滑点。
class=class="str">"cmt">//| 程序逆初函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { IndicatorRelease(h_spread_record); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 程序分时价函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { if(IsTradeDisable(MaxSpread))class="kw">return; class=class="str">"cmt">// class=class="str">"cmt">// 交易逻辑... class=class="str">"cmt">// } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 如果禁止交易返回 true, 否则返回 class="kw">false | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool IsTradeDisable(class="type">int max_spread) { if(h_spread_record==INVALID_HANDLE) class="kw">return class="kw">false; class="type">class="kw">double close[]; if(CopyBuffer(h_spread_record, CLOSE, class="num">0, class="num">1, close) < class="num">1)class="kw">return class="kw">false; if(close[class="num">0]>MaxSpread) { if(!print_disable) printf("交易禁止"); print_disable=true; class="kw">return true; } if(print_disable) printf("交易允许"); print_disable=class="kw">false; class="kw">return class="kw">false; } class="num">2015.05.class="num">27 class="num">16:class="num">57:class="num">08.238 SpreadRecordControl(Si-class="num">6.15,H1) 交易允许 class="num">2015.05.class="num">27 class="num">16:class="num">57:class="num">08.218 SpreadRecordControl(Si-class="num">6.15,H1) 交易禁止 class="num">2015.05.class="num">27 class="num">16:class="num">56:class="num">49.411 SpreadRecordControl(Si-class="num">6.15,H1) 交易允许 class="num">2015.05.class="num">27 class="num">16:class="num">56:class="num">49.401 SpreadRecordControl(Si-class="num">6.15,H1) 交易禁止 class="num">2015.05.class="num">27 class="num">16:class="num">56:class="num">36.478 SpreadRecordControl(Si-class="num">6.15,H1) 交易允许 class="num">2015.05.class="num">27 class="num">16:class="num">56:class="num">36.452 SpreadRecordControl(Si-class="num">6.15,H1) 交易禁止
「用周期轮询躲开价格尖刺的止损坑」
市场报价看似连续,实则常出现离散跳空。以 Si-6.15 RUB/USD 期货在 5 月 28 日 10:03(莫斯科时间)的尖刺为例:多头仓位止损设在 52 740,理论亏损 300 点,但真实触发价落在 52 493,实际亏损 547 卢布/合约,比预设多吞了 247 点。若 EA 每分钟而非每笔分时价检查一次止损,这根尖刺会被直接忽略,仓位反而可能盈利收场。 外汇与贵金属自带高杠杆高风险,1:10 杠杆下若遇期货合约 5% 的价格限制,止损被执行时本金可能直接腰斩 50%。所以把交易逻辑「退敏」成固定周期轮询,不是模糊信号,而是避开低流动性下的假突破刺穿。 均线交叉 EA 是典型落地方案:快 MA 上穿慢 MA 买入、下穿卖出。但当前柱的 MA 随收盘价重绘会反复穿越,导致原地翻转。代码里只在「新柱线来临」后检查前一根已闭合柱线的均线状态,最后一根柱的重绘就不会干扰决策。 别把每笔分时价测试当圣经 MT5 策略测试器的「仅用开盘价」模式常被低估,其实它比「每笔分时价」更贴近实盘。后者用分时价发生器模拟 M1 内走势,无法还原价格缺口,容易养出测试里暴赚、实盘崩盘的「圣杯」。2015 年 5 月 25 日 19:00 SBRF-6.15 期货一分钟从 7473 拉到 7530,挂单在 7485 测试器里漂亮成交,真实分时图却显示缺口剧烈,实盘成交区在 7510–7520。 低流动性市场做回测,认准完整柱线或「1 分钟 OHLC」模式,别用每笔分时价激活停止单。大数法则下,理论入场价与实际的偏差只是噪声,差价常被其他正向因素抹平,结果取决于策略本身而非那几个点。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| MovingAverage.mq5 | 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">#class="kw">property version "class="num">1.00" class="macro">#include <Trade\Trade.mqh> input class="type">int FastMAPeriod = class="num">10; class=class="str">"cmt">// 快速 MA 周期 input class="type">int SlowMAPeriod = class="num">20; class=class="str">"cmt">// 慢速 MA 周期 input class="type">class="kw">double Volume = class="num">1.0; class=class="str">"cmt">// 交易量 class="type">int FastMA = INVALID_HANDLE; class=class="str">"cmt">// 快速 MA 指标的句柄。
双均线穿越的句柄与触发骨架
EA 启动时先把快、慢两条 SMA 的句柄拿到手,周期取当前图表 Period(),均线周期分别由 FastMAPeriod 与 SlowMAPeriod 控制,平滑方式 MODE_SMA、应用价 PRICE_CLOSE、偏移 1。若任一句柄返回 POINTER_INVALID,直接 INIT_FAILED 退出,避免在 OnTick 里对着空句柄 CopyBuffer 爆错。 OnDeinit 里用 IndicatorRelease 释放两个句柄,MT5 不会自动回收指标资源,漏掉这一句在反复加载脚本时可能堆积句柄。 OnTick 的逻辑很直白:先 NewBarDetect() 拦掉旧 Bar 的重复触发,再用 CrossOver / CrossUnder 判定穿越方向,然后 Trade.Buy / Trade.Sell 按 GetVolume() 手数下单。注意 CrossOver 里 CopyBuffer 从索引 1 取 2 根(即上一根与当前根),判定条件是 fast_ma[1]>slow_ma[1] 且 fast_ma[0]<slow_ma[0],这是「死叉」而非金叉——原文函数名与注释存在错位,实盘前需把命名和逻辑对齐。 外汇与贵金属杠杆高,均线穿越在震荡市会连续假突破,这段代码只是信号骨架,必须自加滑点、点差与净值校验后再上 MT5 回测。
class="type">int SlowMA = INVALID_HANDLE; class=class="str">"cmt">// 慢速 MA 指标的句柄。 class="type">class="kw">datetime TimeLastBar; CTrade Trade; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 程序初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { FastMA = iMA(Symbol(), Period(), FastMAPeriod, MODE_SMA, class="num">1, PRICE_CLOSE); SlowMA = iMA(Symbol(), Period(), SlowMAPeriod, MODE_SMA, class="num">1, PRICE_CLOSE); if(FastMA==POINTER_INVALID || SlowMA==POINTER_INVALID) { printf("指标句柄未能创建"); class="kw">return(INIT_FAILED); } class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 程序逆初函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { IndicatorRelease(FastMA); IndicatorRelease(SlowMA); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 程序分时价函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { if(!NewBarDetect())class="kw">return; if(CrossOver()) Trade.Buy(GetVolume()); else if(CrossUnder()) Trade.Sell(GetVolume()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 返回 true 如果快速 ma 穿越慢速 ma 向上。否则返回 class="kw">false。 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CrossOver() { class="type">class="kw">double fast_ma[]; class="type">class="kw">double slow_ma[]; if(CopyBuffer(FastMA, class="num">0, class="num">1, class="num">2, fast_ma) < class="num">1)class="kw">return class="kw">false; if(CopyBuffer(SlowMA, class="num">0, class="num">1, class="num">2, slow_ma) < class="num">1)class="kw">return class="kw">false; class="type">bool is_over=fast_ma[class="num">1]>slow_ma[class="num">1] && fast_ma[class="num">0]<slow_ma[class="num">0]; class="kw">return is_over; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 返回 true 如果快速 ma 穿越慢速 ma 向下。否则返回 class="kw">false。 | class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 死叉判定与仓位自适应的底层函数
这一段给出三个可直接抄进 EA 的底层函数,解决「怎么识别快线向下穿越慢线」「怎么按持仓状态翻倍手数」「怎么只在新 K 线跑逻辑」三个具体问题。外汇与贵金属波动剧烈,这类自动判定只是信号触发器,实际成交仍受滑点与点差影响,存在较高风险。 CrossUnder 取最近两根 K 线的快慢均线值:fast_ma[0] 在慢线上方、fast_ma[1] 在慢线下方,即判定为向下穿越。CopyBuffer 若取不够 2 个值直接返回 false,避免数组越界。 GetVolume 用 PositionSelect 检查当前品种是否已有持仓,有则 Volume*2.0,无则原量输出。这是典型的「加仓倍投」写法,回测中易放大回撤,实盘需谨慎。 NewBarDetect 靠 CopyTime 拿最新柱时间,与模块级变量 TimeLastBar 比对,不同才置新并返 true。OnTick 里首行就拦掉非新柱的 tick,把运算量压到每根 K 线一次而不是每次报价。
class="type">bool CrossUnder() { class="type">class="kw">double fast_ma[]; class="type">class="kw">double slow_ma[]; if(CopyBuffer(FastMA, class="num">0, class="num">1, class="num">2, fast_ma) < class="num">1)class="kw">return class="kw">false; if(CopyBuffer(SlowMA, class="num">0, class="num">1, class="num">2, slow_ma) < class="num">1)class="kw">return class="kw">false; class="type">bool is_under=fast_ma[class="num">0]>slow_ma[class="num">0] && fast_ma[class="num">1]<slow_ma[class="num">1]; class="kw">return is_under; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 返回交易量计数。 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double GetVolume() { if(PositionSelect(Symbol()))class="kw">return Volume*class="num">2.0; class="kw">return Volume; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 返回 true 如果检测到新柱线, 负责返回 class="kw">false。 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool NewBarDetect() { class="type">class="kw">datetime times[]; if(CopyTime(Symbol(),Period(),class="num">0,class="num">1,times)<class="num">1) class="kw">return class="kw">false; if(times[class="num">0] == TimeLastBar)class="kw">return class="kw">false; TimeLastBar = times[class="num">0]; class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { if(!NewBarDetect())class="kw">return; ... }
「别急着下结论」
把前面几节拆开看,核心其实就一句:价格图表是离散成交的压缩包,柱线没走完之前你看到的只是近似。低流动性品种里这种离散会被放大,限价单和停止限价单是绕开滑点最实在的手段,高位限价买、低位限价卖还能在终端离线时替你管住最大点差。 市场深度和实时点差是两个能摸流动性的窗口,点差一扩就该等,而不是硬吃。EA 用「休眠」模式只在新柱抵达时判一次逻辑,既抗尖刺又兼容完整柱线回测;而完整柱线模式因为吃的是离散且真实的历史数据,才是低流动性市场唯一靠谱的测试选项,速度也不慢。 这些结论都不是圣旨,外汇和贵金属杠杆高、跳空频繁,离散和流动性随时变脸。开 MT5 把 DeviationPanel.mq5 拖上图表,先盯一周自己常做品种的点差分布,再决定挂单类型,比背任何总结都管用。