构建自动运行的 EA(第 15 部分):自动化(VII)(基础篇)
📘

构建自动运行的 EA(第 15 部分):自动化(VII)(基础篇)

第 1/3 篇

从手动复盘到 MT5 自动巡检

把价格行为分析从人工盯盘转成 EA 自动跑,核心是先定义清楚你要机器替你干的“一件具体事”。以 MetaTrader 5 环境为例,最务实的切入点不是预测涨跌,而是让程序定时扫描品种、抓取特定 K 线形态并推送提醒。 外汇与贵金属市场高杠杆、高波动,自动化只解决“不漏看”,不解决“一定对”。任何扫描结果都只是概率信号,最终下单仍要人工确认风险敞口。 下面这段 MQL5 片段演示了在 OnTick 中判断最近一根柱是否收出看涨外包线(Outside Bar),逻辑可直接拷进 MT5 用策略测试器验证:

MQL5 / C++
class="type">bool IsBullishOutsideBar(class="type">int shift=class="num">0)
{
   class="type">class="kw">double open0=Open[shift];
   class="type">class="kw">double close0=Close[shift];
   class="type">class="kw">double high0=High[shift];
   class="type">class="kw">double low0=Low[shift];
   class="type">class="kw">double open1=Open[shift+class="num">1];
   class="type">class="kw">double close1=Close[shift+class="num">1];
   class="type">class="kw">double high1=High[shift+class="num">1];
   class="type">class="kw">double low1=Low[shift+class="num">1];
   class=class="str">"cmt">// 当前K线实体收阳
   if(close0<=open0) class="kw">return class="kw">false;
   class=class="str">"cmt">// 当前高点高于前高、低点低于前低(外包)
   if(high0<=high1 || low0>=low1) class="kw">return class="kw">false;
   class=class="str">"cmt">// 吞没前一根实体
   if(open0>=close1 && close0<=open1) class="kw">return true;
   class="kw">return class="kw">false;
}

◍ 用 C_Automaton 适配三种自动化模型

上篇里我们已经搭好了 C_Automaton 类的基础框架,但真正拿它去跑一套自动化系统,并没有类结构看起来那么直白。本文不重复讲系统逻辑,只盯住「适配层」——也就是怎么改 C_Automaton 让它托住不同的交易模型。 这一节会给出 3 种模型的接入示例,重点都在调整 C_Automaton 的依赖部分,而非重写整套策略。如果你手上有之前文章的 EA 骨架,直接对照本节的改法在 MT5 里编译验证即可。 外汇与贵金属自动化挂单属于高风险操作,模型回测通过不代表实盘概率等同,请先用策略测试器跑样本。

「用 9 周期 EMA 句柄驱动无止损 EA」

这套 EA 在构造函数里直接把止损、止盈、尾随停止、盈亏平衡和挂单全部关掉,只保留一个基于收盘价的 9 周期指数移动平均线句柄(iMA 参数里周期填 9、模式 MODE_EMA、价格 PRICE_CLOSE)。也就是说,出场和入场的边界完全交给价格行为本身,EA 不预设任何盈利或亏损上限,外汇和贵金属品种的高波动可能让浮亏快速放大,必须有人盯盘。 指标读取顺序很关键:先尝试读 EMA 缓冲区,读不到就返回空触发器,等下一个 OnTimer 再试;柱线计数器必须放在读取成功之后更新,否则会错过下一次事件。若把计数更新提前,信号可能在当根柱丢失,而离场信号丢失时账户倾向开始累积损失。 买卖判定只看缓冲区最新点(索引 0)和前一点:最新值高于前值,EA 立即买;低于前值,立即卖。EMA 对价格反应比 SMA 快,收盘价在均线上下方倾斜时能较早暴露方向。想把敏感度降下来,把 m_Infos.Shift 从 1 改成 2 或更大,相当于把均线偏移量推远,过滤掉微小扰动。 拉里·威廉姆斯式变体可以不直接市价入场,而是在触发信号那根柱的最高/最低点挂单,C_Manager 保证服务器只留一笔挂单,只需改 Triggers 函数。若挂单没在触发柱成交,下根柱自动重置——这套模型附件里没给,但思路能直接套到你自己的 EA 里验证。

MQL5 / C++
                C_Automaton(class="kw">const class="type">class="kw">ulong magic, class="type">class="kw">double FinanceStop, class="type">class="kw">double FinanceTake, class="type">uint Leverage,
                                    class="kw">const ENUM_TIMEFRAMES iPeriod,
                                    class="kw">const class="type">class="kw">double OverBought = class="num">70, class="kw">const class="type">class="kw">double OverSold = class="num">30, class="kw">const class="type">int iShift = class="num">1)
                :C_Manager(magic, class="num">0, class="num">0, Leverage, IsDayTrade, class="num">0, class="kw">false, class="num">10),
                 m_TF(iPeriod),
                 m_Handle(INVALID_HANDLE)
                {
                 m_Infos.Shift      = iShift;
                 m_Infos.OverBought = OverBought;
                 m_Infos.OverSold   = OverSold;
                 ArraySetAsSeries(m_Buff, true);
                 m_nBars  = iBars(NULL, m_TF);
                 m_Handle = iMA(NULL, m_TF, class="num">9, class="num">0, MODE_EMA, PRICE_CLOSE);
                }
class="kw">inline eTrigger CheckTrigger(class="type">void)
                {

触发逻辑里的缓冲越界与多空反转判定

这段 EA 核心函数用 iBars 拿到当前图表 K 线总数,先判断新柱数量是否超过记录值 m_nBars,同时指标句柄 m_Handle 必须有效,两个条件都满足才进判定分支。 进入后第一件事是 CopyBuffer 把指标缓冲拉到本地数组 m_Buff,请求长度是 m_Infos.Shift + 1。若实际拷贝到的数据少于这个数,直接 return TRIGGER_NONE,避免用空缓冲做比较导致误触发。 比较本身只看两个位置:m_Buff[0] 是最新值,m_Buff[m_Infos.Shift] 是偏移 Shift 根柱之前的值。最新值大于旧值倾向返回 TRIGGER_BUY,小于则倾向 TRIGGER_SELL,等于就不动作。 Triggers 函数里还有一层保护:CtrlTimeIsPassed 没过时间窗就先 ClosePosition 平仓;过了才去查 CheckTrigger。若上一次记忆是 TRIGGER_SELL 而本次出 TRIGGER_BUY,会先平掉反向仓再开新仓,这种反手逻辑在黄金 5 分钟这类高波动品种上可能频繁搓手续费,实盘前建议在 MT5 策略测试器里把 Shift 从默认 1 调到 3 看回测成交次数变化。

MQL5 / C++
class="type">int iRet;

if ((iRet = iBars(NULL, m_TF)) > m_nBars) && (m_Handle != INVALID_HANDLE)
   {
      if (CopyBuffer(m_Handle, class="num">0, class="num">0, m_Infos.Shift + class="num">1, m_Buff) < m_Infos.Shift + class="num">1) class="kw">return TRIGGER_NONE;
      m_nBars = iRet;
      if (m_Buff[class="num">0] > m_Buff[m_Infos.Shift]) class="kw">return TRIGGER_BUY;
      if (m_Buff[class="num">0] < m_Buff[m_Infos.Shift]) class="kw">return TRIGGER_SELL;
   };
   class="kw">return TRIGGER_NONE;
}
class="kw">inline class="kw">virtual class="type">void Triggers(class="type">void) final
   {
      if (!CtrlTimeIsPassed()) ClosePosition(); else class="kw">switch (CheckTrigger())
      {
         case TRIGGER_BUY:
            if (m_Memory == TRIGGER_SELL) ClosePosition();
            if (GetVolumeInPosition() == class="num">0)
            {

◍ 触发分支里的挂单与反手逻辑

这段 switch 分支处理的是价格穿越触发线后的动作。TRIGGER_BUY 分支先调 DestroyOrderPendent() 清掉旧挂单,再用 CreateOrder 以当前 0 号 K 线最高价 iHigh(NULL, m_TF, 0) 市价买入,并把 m_Memory 置为 TRIGGER_BUY。 TRIGGER_SELL 分支更谨慎:若 m_Memory 仍是 TRIGGER_BUY 会先 ClosePosition() 平多;只有 GetVolumeInPosition() 返回 0 确认无持仓,才 DestroyOrderPendent() 后以 iLow(NULL, m_TF, 0) 最低价卖空,最后写 m_Memory = TRIGGER_SELL。 实盘里这套逻辑意味着同一根 K 线内若先触买后触卖,会先平后反手,外汇与贵金属波动快时滑点可能吃掉预期空间,开 MT5 把 m_TF 换成 M5 跑一遍能直接看到挂单刷新频率。

MQL5 / C++
            DestroyOrderPendent();
            CreateOrder(ORDER_TYPE_BUY, iHigh(NULL, m_TF, class="num">0));
            }
            m_Memory = TRIGGER_BUY;
            class="kw">break;
            case TRIGGER_SELL:
            if (m_Memory == TRIGGER_BUY) ClosePosition();
            if (GetVolumeInPosition() == class="num">0)
            {
                DestroyOrderPendent();
                CreateOrder(ORDER_TYPE_SELL, iLow(NULL, m_TF, class="num">0));
            }
            m_Memory = TRIGGER_SELL;
            class="kw">break;
            };

「把 RSI 塞进自动化框架里」

把均线系统的思路平移到振荡器上,并不需要重写整套逻辑。EA_v2 的构造函数只改了几处参数,就从一个趋势跟踪器变成了基于 14 周期 RSI 的触发型机器人,外汇与贵金属品种上这类切换都属高风险操作,回测前先想清楚周期适配。 构造函数里默认超买 70、超卖 30,iShift 偏移设为 1,用来捕捉指标刚脱离极值区那一两根 K 线的方向。m_Handle 直接调 iRSI(NULL, m_TF, 14, PRICE_CLOSE),也就是说信号只看收盘价,避免在日内噪音里反复假触发。 CheckTrigger 之前先判断 RSI 是向上还是向下穿越阈值,这个走势偏移很关键,它倾向提示回调而非反转;只有指标退出超买或超卖区,才给出真实的买/卖触发。 和前一个例子不同,这里在开仓后接入了盈亏平衡与尾随停止的调用,盈利走势出现时系统会以更可观的方式平仓。改动极小,本质只是决定在哪一行插入 Trailing 调用,其余状态都由 C_Manager 托管。

MQL5 / C++
                C_Automaton(class="kw">const class="type">class="kw">ulong magic, class="type">class="kw">double FinanceStop, class="type">class="kw">double FinanceTake, class="type">uint Leverage,
                                class="type">bool IsDayTrade, class="type">class="kw">double Trailing, class="kw">const ENUM_TIMEFRAMES iPeriod,
                                class="kw">const class="type">class="kw">double OverBought = class="num">70, class="kw">const class="type">class="kw">double OverSold = class="num">30, class="kw">const class="type">int iShift = class="num">1)
                :C_Manager(magic, FinanceStop, class="num">0, Leverage, IsDayTrade, Trailing, true, class="num">10),
                 m_TF(iPeriod),
                 m_Handle(INVALID_HANDLE)
                {
                                m_Infos.Shift        = iShift;
                                m_Infos.OverBought = OverBought;
                                m_Infos.OverSold   = OverSold;
                                ArraySetAsSeries(m_Buff, true);
                                m_nBars = iBars(NULL, m_TF);
                                m_Handle = iRSI(NULL, m_TF, class="num">14, PRICE_CLOSE);
                }
class="kw">inline eTrigger CheckTrigger(class="type">void)
                {
                                class="type">int iRet;

常见问题

把巡检逻辑写进自动化框架,用定时器按周期跑策略检查,省去人工盯盘。外汇贵金属波动大,自动巡检只降人力成本,不降风险。
能跑,但无止损在高杠杆外汇贵金属里风险极高,价格反向跳空可能大幅回撤。建议先开模拟盘验证缓冲与反转判定再上实盘。
小布可以读取你的策略描述与日志,标出缓冲越界、多空反转漏判等可疑点。把重复诊断交给小布,你专注调参决策。
会,若不去重且缓冲判断不准,可能同根K线既挂单又反手。加持仓计数与时间间隔锁,确保一个信号只响应一次。
不用,按现有句柄模式新增RSI指标句柄,在触发分支里读值做阈值判断即可。保留原EMA与反转逻辑,只扩条件分支。