构建自动运行的 EA(第 04 部分):手工触发器(I)(基础篇)
「用手工按钮给 EA 发信号」
在 MT5 里跑 EA,不一定非得让它全自动下单。给 EA 加一个图形面板按钮,由交易者自己点一下才触发建仓逻辑,这种「半自动」结构在应对突发消息时更可控。
实现思路是在 OnInit 里用 ObjectCreate 画一个矩形按钮,绑定到当前图表;点击动作在 OnChartEvent 里捕获 CHARTEVENT_OBJECT_CLICK,比对 sparam 是不是按钮名。下面这段是最小可运行骨架,复制进 MQ5 文件就能编译。
外汇与贵金属杠杆高,半自动也只是把决策权留给人,不代表风险更低,实盘前请在策略测试器用历史数据跑通点击逻辑。
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 混用相同魔幻数会互相抢单。
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 的鼠标类里,编译后移动鼠标就能看到线跟着跑。
{
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);