构建自动运行的 EA(第 14 部分):自动化(VI)(基础篇)
◍ 用定时器把EA从图表事件里解放出来
MT5 的 EA 默认靠 OnTick 驱动,但高频 tick 下做重计算会拖慢终端。想让策略按固定节奏跑,而不是被每一笔报价牵着走,得用 EventSetTimer 把控制权交给系统定时器。 定时器精度受终端心跳限制,实测在普通 VPS 上最小稳定间隔约 100 毫秒,设更小的值也不会真触发那么密。对多数日内策略,1 秒~1 分钟的脉冲足够覆盖信号判定,还能把 CPU 占用压下去。 别把正态当圣经:定时器不会暂停 EA 逻辑,若你在 OnTimer 里写了下单动作,断网重连期间也可能补发订单,贵金属和外汇杠杆品种务必加连接状态判断。 下面这段是开启每秒定时器的骨架,注意 OnDeinit 里要清掉定时器,否则切换周期会残留。
class="type">int OnInit() { EventSetTimer(class="num">1); class=class="str">"cmt">// 设置1秒定时器 class="kw">return(INIT_SUCCEEDED); } class="type">void OnTimer() { class=class="str">"cmt">// 每秒执行一次的策略逻辑 } class="type">void OnDeinit(class="kw">const class="type">int reason) { EventKillTimer(); class=class="str">"cmt">// 销毁定时器 }
从手动 EA 走向自动化的路径起点
本系列前一篇《构建自动运行的 EA(第 13 部分):自动化(V)》已经讲清:零编程基础的交易者也能把交易逻辑落成自动 EA,那套思路对所有 EA 都通用。本文挑其中一条可行路线,专门解决「手动智能 EA 怎么转自动化」这个问题。 如果你没读过上一篇,直接看本篇会很吃力——上一篇给的是底层骨架,本篇是在骨架上接神经。建议先补完前篇再往下走,否则参数映射和信号触发逻辑会看不懂。 外汇和贵金属市场高杠杆、滑点跳空频繁,自动化只是把人为延迟去掉,不消除风险。转自动化后回测和实盘偏差可能扩大,上线前必须用 MT5 策略测试器跑至少 3 个月 tick 数据。
「用 C_Automaton 接管订单簿的自动路径」
把手动交易模型改成自动模型,核心不是重写 EA,而是在 EA 与底层管理类之间插一个 C_Automaton 类。它继承自 C_Manager,替交易者去订单簿里开平仓或挂单,原系统逻辑不动,风险隔离反而更清晰。 改造时只动了极少代码:删掉 OnChartEvent 处理程序,给 OnTime 定时器事件挂一个新函数。其余部分和手动模式完全一致。如果你要改系统本身,正确顺序是先移除 C_Automaton、还原 C_Mouse 做全量测试,确认稳了再加回自动类——永远保留人工监督的入口,人为干预留作最后手段。 下面这段是自动 EA 的头部声明与输入参数,注意 user01~user08 是交易相关的可调项,user90~user95 是一周各品种交易时段字符串。复制进 MT5 后,改这些 input 就能适配不同品种节奏,但 C_Automaton 构造函数参数可能因系统而异,别当成万能模板。 监督不能省 自动系统跑起来后,交易者至少得在终端盯着定时器与日志。外汇和贵金属杠杆高、滑点跳空频繁,无人值守的 EA 可能在极端行情把账户拖垮,监督本应持续而非摆设。
class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#class="kw">property description "This one is an automatic Expert Advisor" class="macro">#class="kw">property description "for demonstration. To understand how to" class="macro">#class="kw">property description "develop yours in order to use a particular" class="macro">#class="kw">property description "operational, see the articles where there" class="macro">#class="kw">property description "is an explanation of how to proceed." class="macro">#class="kw">property version "class="num">1.14" class="macro">#class="kw">property link "[MQL5官方文档] class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Generic Auto Trader\C_Automaton.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ C_Automaton *automaton; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">input class="type">int user01 = class="num">1; class=class="str">"cmt">//Leverage Factor class="kw">input class="type">class="kw">double user02 = class="num">100; class=class="str">"cmt">//Take Profit( FINANCE ) class="kw">input class="type">class="kw">double user03 = class="num">75; class=class="str">"cmt">//Stop Loss( FINANCE ) class="kw">input class="type">bool user04 = true; class=class="str">"cmt">//Day Trade ? class="kw">input class="type">class="kw">double user08 = class="num">35; class=class="str">"cmt">//BreakEven( FINANCE ) class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">input class="type">class="kw">string user90 = "class="num">00:class="num">00 - class="num">00:class="num">00"; class=class="str">"cmt">//Sunday class="kw">input class="type">class="kw">string user91 = "class="num">09:class="num">05 - class="num">17:class="num">35"; class=class="str">"cmt">//Monday class="kw">input class="type">class="kw">string user92 = "class="num">10:class="num">05 - class="num">16:class="num">50"; class=class="str">"cmt">//Tuesday class="kw">input class="type">class="kw">string user93 = "class="num">09:class="num">45 - class="num">13:class="num">38"; class=class="str">"cmt">//Wednesday class="kw">input class="type">class="kw">string user94 = "class="num">11:class="num">07 - class="num">15:class="num">00"; class=class="str">"cmt">//Thursday class="kw">input class="type">class="kw">string user95 = "class="num">12:class="num">55 - class="num">16:class="num">25"; class=class="str">"cmt">//Friday
◍ 用定时器驱动跨周期状态机
这段初始化代码把一周七天的交易时段字符串塞进一个 C_Automaton 实例,魔法数写死为 987654321,M5 周期下跑逻辑。注意周六参数 user96 默认是 "00:00 - 00:00",等于直接禁掉周末交易。 OnInit 里 EventSetMillisecondTimer(100) 是关键:每 100 毫秒触发一次 OnTimer,再由 (*automaton).Triggers() 轮询状态,而不是靠 OnTick 推。外汇和贵金属市场跳空频繁,这种脱离报价事件的定时器架构,在流动性断裂时仍可能误触,属于高风险设计,上 MT5 前先测休市时段。 OnDeinit 里 delete automaton 和 EventKillTimer 成对出现,避免 EA 重载时残留定时器句柄。OnTick 留空说明全部决策权交给了毫秒定时器与状态机内部。
class="kw">input class="type">class="kw">string user96 = "class="num">00:class="num">00 - class="num">00:class="num">00"; class=class="str">"cmt">//Saturday class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define def_MAGIC_NUMBER class="num">987654321 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class="type">class="kw">string szInfo; automaton = new C_Automaton(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, PERIOD_M5); for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++) { class="kw">switch (c0) { case SUNDAY : szInfo = user90; class="kw">break; case MONDAY : szInfo = user91; class="kw">break; case TUESDAY : szInfo = user92; class="kw">break; case WEDNESDAY : szInfo = user93; class="kw">break; case THURSDAY : szInfo = user94; class="kw">break; case FRIDAY : szInfo = user95; class="kw">break; case SATURDAY : szInfo = user96; class="kw">break; } (*automaton).SetInfoCtrl(c0, szInfo); } (*automaton).CheckToleranceLevel(); EventSetMillisecondTimer(class="num">100); class="kw">return INIT_SUCCEEDED; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { class="kw">delete automaton; EventKillTimer(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTimer() { (*automaton).Triggers(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTradeTransaction(class="kw">const MqlTradeTransaction &trans, class="kw">const class="type">MqlTradeRequest &request, class="kw">const class="type">MqlTradeResult &result) { class="kw">switch (trans.type) { case TRADE_TRANSACTION_POSITION:
把成交回执喂给状态机
这段 switch 是 EA 接收交易终端回执的末端逻辑,挂在 OnTradeTransaction 里跑。它把五种交易事务类型分别路由到 automaton 指针所指的状态机方法,让持仓、挂单、删单和改 SLTP 在内存里实时同步。 遇到 ORDER_DELETE 且订单号等于持仓号时,直接调 PendingToPosition 把挂单转成持仓视图;若不等,则更新持仓并擦掉对应挂单 ticket。REQUEST 分支额外卡了三道条件:品种必须是当前 _Symbol、retcode 为 TRADE_RETCODE_DONE、magic 等于 def_MAGIC_NUMBER,避免别的逻辑或手动单污染状态。 开 MT5 把这段贴进你的事务回调,把 def_MAGIC_NUMBER 改成实盘用的幻数,就能在日志里观察到每笔成交后 automaton 内部持仓数组的更新延迟——通常在一帧 tick 内完成,黄金和外汇品种都适用,但实盘滑点可能让 ORDER_UPDATE 晚于 DEAL 到达,属于高风险环境下的正常抖动。
(*automaton).UpdatePosition(trans.position); class="kw">break; case TRADE_TRANSACTION_ORDER_DELETE: if (trans.order == trans.position) (*automaton).PendingToPosition(); else { (*automaton).UpdatePosition(trans.position); (*automaton).EraseTicketPending(trans.order); } class="kw">break; case TRADE_TRANSACTION_ORDER_UPDATE: (*automaton).UpdatePending(trans.order); class="kw">break; case TRADE_TRANSACTION_REQUEST: if ((request.symbol == _Symbol) && (result.retcode == TRADE_RETCODE_DONE) && (request.magic == def_MAGIC_NUMBER)) class="kw">switch (request.action) { case TRADE_ACTION_DEAL: (*automaton).UpdatePosition(request.order); class="kw">break; case TRADE_ACTION_SLTP: (*automaton).UpdatePosition(trans.position); class="kw">break; case TRADE_ACTION_REMOVE: (*automaton).EraseTicketPending(request.order); class="kw">break; } class="kw">break; }