构建自动运行的 EA(第 04 部分):手工触发器(I)(基础篇)
📘

构建自动运行的 EA(第 04 部分):手工触发器(I)(基础篇)

第 1/2 篇

「用手工按钮给 EA 发信号」

在 MT5 里跑 EA,不一定非得让它全自动下单。给 EA 加一个图形面板按钮,由交易者自己点一下才触发建仓逻辑,这种「半自动」结构在应对突发消息时更可控。 实现思路是在 OnInit 里用 ObjectCreate 画一个矩形按钮,绑定到当前图表;点击动作在 OnChartEvent 里捕获 CHARTEVENT_OBJECT_CLICK,比对 sparam 是不是按钮名。下面这段是最小可运行骨架,复制进 MQ5 文件就能编译。 外汇与贵金属杠杆高,半自动也只是把决策权留给人,不代表风险更低,实盘前请在策略测试器用历史数据跑通点击逻辑。

MQL5 / C++
class="type">int btn;
class="type">int OnInit()
{
   btn = ObjectCreate(class="num">0, "TradeBtn", OBJ_BUTTON, class="num">0, class="num">0, class="num">0);
   ObjectSetInteger(class="num">0, "TradeBtn", OBJPROP_XDISTANCE, class="num">20);
   ObjectSetInteger(class="num">0, "TradeBtn", OBJPROP_YDISTANCE, class="num">20);
   ObjectSetString(class="num">0, "TradeBtn", OBJPROP_TEXT, "手动开仓");
   class="kw">return(INIT_SUCCEEDED);
}
class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam)
{
   if(id == CHARTEVENT_OBJECT_CLICK && sparam == "TradeBtn")
   {
      class=class="str">"cmt">// 这里放你的下单代码
   }
}

触发器系统的真实定位

上一节把订单系统讲完了,这一节直接进触发器。触发器是 EA 里最容易让人踩坑的模块,没有哪种实现能做到 100% 无错——有的方案出错概率高,有的低,但都会在你想不到的时候失效。 它的用途很直白:触发盈亏平衡、拉动尾随止损、决定开仓或平仓。事件一旦挂上触发器就全自动跑,交易者不用动手,除非 EA 陷入死循环,那时必须人工立刻停用。 这里有个硬提醒:别把触发器当保险丝用。它失效时你可能错过行情,也可能在外汇/贵金属这种高波动品种上直接吃一笔巨亏,所以对触发逻辑始终要保持怀疑。 为了把思路落地,我们会写一个手动 EA,但和之前示例不同——这次要加挂单和市价单开仓能力。教学 EA 只在模拟账户跑,实盘有卡死或发疯的风险,碰都别碰。

◍ 用键盘加鼠标在MT5图表上甩出市价单

想在MT5图表上做市价单,纯鼠标点面板太慢,键盘组合才是顺手的做法。下面这套 C_Mouse 类不是为了实盘,而是把事件触发链路拆给你看:SHIFT 表买入、CTRL 表卖出,再叠箭头键发市价单,CTRL+上箭头市价买入,CTRL+下箭头市价卖出。外汇与贵金属杠杆高,误触键盘可能瞬间进场,先在策略测试器里跑通再谈真金白银。 头文件里先给鼠标指示线起名,并用位掩码把按键状态抽出来。def_BtnLeftClick 取 0x01 位,def_SHIFT_Press 取 0x04 位,def_CTRL_Press 取 0x08 位——这三个宏让 EA 里判键不用写一堆魔法数字。def_MouseName 在头尾用 #define / #undef 包住,避免名字泄漏到别的编译单元。 类构造里只两行关键:ChartSetInteger 开 CHART_EVENT_MOUSE 收鼠标事件,再开 CHART_EVENT_OBJECT_DELETE 盯删除。析构时反向关掉通知并删掉那条水平线。读类内状态一律走返回 const 引用的函数,不露裸指针——这是防自己后面手滑改了私有变量的底线。 OnChartEvent 里把 MT5 给的事件原样转交 C_Mouse 的私有处理方法:删线就重建,鼠标移动就把价格线钉到光标价位并强制重绘,不然你会觉得平台卡。EA 侧捕获 Shift/Ctrl 状态,左键单击且记忆价为 0 就发挂单;市价单则看 CTRL+上下箭头,参数取自交互区。每个 EA 必须独立魔幻数,同品种多 EA 混用相同魔幻数会互相抢单。

MQL5 / C++
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define def_MouseName "MOUSE_H"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define def_BtnLeftClick(A)     ((A & 0x01) == 0x01)
class="macro">#define def_SHIFT_Press(A)      ((A & 0x04) == 0x04)
class="macro">#define def_CTRL_Press(A)       ((A & 0x08) == 0x08)
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#undef def_MouseName
class=class="str">"cmt">//+------------------------------------------------------------------+
class C_Mouse
{
    class="kw">private :
        class="kw">struct st00

「用水平线把鼠标位置钉在图上」

想在 MT5 图表上实时跟住鼠标画一条参考线,核心是先声明一个保存对象状态的结构体,再在构造函数里把当前图表 ID、线条颜色和 tick 步长一次性灌进去。下面这段结构体和两个方法,就是鼠标水平线功能的骨架。 结构体 m_Infos 里 PointPerTick 取自 SYMBOL_TRADE_TICK_SIZE,对外汇和贵金属意味着价格只能按经纪商规定的 tick 粒度吸附,XAUUSD 常见为 0.01,EURUSD 多为 0.00001,乱设小数位会被平台拒绝绘制。 CreateLineH 用 OBJ_HLINE 建一条横贯图表的线,OBJPROP_BACK 设 false 让它浮在 K 线之上,OBJPROP_TOOLTIP 传 "\n" 则是故意清空悬停提示,避免遮挡价格行为观察。AdjustPrice 里 MathRound(value/PointPerTick)*PointPerTick 把任意鼠标 Y 坐标对齐到合法报价,否则 ObjectMove 会静默失败。 外汇与贵金属杠杆高、滑点跳空频繁,这类辅助线只作主观位参考,不能替代止损纪律;复制下面代码到 EA 的鼠标类里,编译后移动鼠标就能看到线跟着跑。

MQL5 / C++
{
   class="type">long   Id;
   class="type">color  Cor;
   class="type">class="kw">double PointPerTick,
          Price;
   class="type">uint   BtnStatus;
}m_Infos;
class=class="str">"cmt">//+------------------------------------------------------------------+
   class="type">void CreateLineH(class="type">void)
      {
         ObjectCreate(m_Infos.Id, def_MouseName, OBJ_HLINE, class="num">0, class="num">0, class="num">0);
         ObjectSetString(m_Infos.Id, def_MouseName, OBJPROP_TOOLTIP, "\n");
         ObjectSetInteger(m_Infos.Id, def_MouseName, OBJPROP_BACK, false);
         ObjectSetInteger(m_Infos.Id, def_MouseName, OBJPROP_COLOR, m_Infos.Cor);
      }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">inline class="type">class="kw">double AdjustPrice(class="kw">const class="type">class="kw">double value)
      {
         class="kw">return MathRound(value / m_Infos.PointPerTick) * m_Infos.PointPerTick;
      }
class=class="str">"cmt">//+------------------------------------------------------------------+
   C_Mouse(class="kw">const class="type">color cor)
      {
         m_Infos.Id = ChartID();
         m_Infos.Cor = cor;
         m_Infos.PointPerTick = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);

常见问题

在EA里画一个OBJ_BUTTON对象并绑定点击事件,触发后调用OrderSend发市价单即可,不用每次手动填单。
不是。它只是让人来决定何时给EA发信号,单子执行和风控仍由EA跑,定位是“人给指令、机器干活”。
小布会在品种页直接标出你画的按钮与水平线状态,并提示触发器是否已激活,省得你盯图确认。
EA读取水平线对象的OBJPROP_PRICE1属性拿到价格,再把它当作挂单或参考位,鼠标放哪儿线就钉哪儿。
把常用方向做成图表按钮,用快捷键呼出,点击即按按钮坐标发单,比纯鼠标找位置稳得多。