轻松快捷开发 MetaTrader 程序的函数库 (第 三十部分) :延后交易请求 - 管理请求对象(基础篇)
◍ 延后交易请求的对象管理起点
在 MT5 里做EA时,频繁发交易请求容易被服务器限流或造成上下文混乱。把请求先存成对象、延后再发,是降低重试成本和逻辑耦合的常见做法。 原文示例发布于 2020-03-24,截至统计页显示阅读 3644、评论 8,说明这套延后请求思路在俄语区交易者里有明确需求。 本小节只搭骨架:定义“暂停对象”来承载待发请求,再做一点轻量代码优化,后续才用类统一管理。开 MT5 建个空 EA,先想清楚你的请求对象要存哪些字段(品种、手数、挂单类型),比直接调 OrderSend 更稳。
延后请求管理的类结构思路
延后交易请求这套机制,从函数库第二十六部分起就在逐步搭框架。上一篇文章已经写出了延后请求对象类,这一节要补的是「管理这些对象」的类。 原本想写个完全独立的 manager 类,但实测发现它和主交易类 CTrading 耦合太深,直接让管理类继承自主交易类反而更省事。延后对象的完整调度放在类计时器里,所以基类计时器设成虚拟,派生类再各自实现具体逻辑。 为了避免 Sleep() 把整个程序执行流卡死,这里另起一个小型暂停类来分配等待时间。好处是脱离即时报价依赖,周末也能跑需要等待的回测代码——外汇和贵金属品种周末无报价,这点对验证延时逻辑很关键。 我之前漏了一点:魔幻数字里塞了魔术 ID 和组值,多个类都要读,却忘了所有对象本应从函数库基准对象继承。现在把写读魔幻数字、日志级别控制这些重复方法统一挪到基准对象,新类直接继承,比到处重写合理得多。 这一节还会给延后请求对象加新属性,用来比对「订单当前状态」和「请求对象状态」,请求发到服务器后及时从活动列表剔除。下面是 Datas.mqh 里新增的消息索引与文本片段,先铺好日志输出地基。
MSG_LIB_TEXT_PEND_REQUEST_STATUS_REMOVE, class=class="str">"cmt">// Pending request to class="kw">delete a pending order MSG_LIB_TEXT_PEND_REQUEST_STATUS_MODIFY, class=class="str">"cmt">// Pending request to modify pending order parameters MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_VOLUME, class=class="str">"cmt">// Actual volume MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_PRICE, class=class="str">"cmt">// Actual order price MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_STOPLIMIT, class=class="str">"cmt">// Actual StopLimit order price MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_SL, class=class="str">"cmt">// Actual StopLoss order price MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_TP, class=class="str">"cmt">// Actual TakeProfit order price MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_TYPE_FILLING, class=class="str">"cmt">// Actual order filling type MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_TYPE_TIME, class=class="str">"cmt">// Actual order expiration type MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_EXPIRATION, class=class="str">"cmt">// Actual order lifetime }; class=class="str">"cmt">//+------------------------------------------------------------------+ {"Отложенный запрос на удаление отложенного ордера","Pending request to remove pending order"}, {"Отложенный запрос на модификацию параметров отложенного ордера","Pending request to modify pending order parameters"}, {"Фактический объем","Actual volume"},
「成交回执里的真实字段」
在 MT5 的订单回执结构里,系统返回的不是你挂单时填的理想值,而是一组带「Actual」前缀的真实成交属性。上面这段数组定义把俄文标签映射成英文,核心就是记录订单实际落地时的价格与执行参数。 实际挂单价格(Actual order placement price)、StopLimit 触发挂单价格、SL 与 TP 的实际挂价,这四项是复盘滑点最直接的依据。比如你限价单填 1.0850,回执若显示 1.0853,就说明被向上滑了 3 个点,外汇与贵金属品种在高波动时段这种偏差概率明显抬高。 后面三项——成交填充类型、过期类型、订单存活时长——决定了订单是 IOC 瞬间填充还是返回式挂单,以及从发单到成交或撤消经历了多少毫秒。把这些字段打进日志,你才能在 MT5 里用历史回放核对自己的执行逻辑到底哪里漏了。
{“Фактическая цена установки ордера”,”Actual order placement price”},
{“Фактическая цена установки StopLimit-ордера”,”Actual StopLimit order placement price”},
{“Фактическая цена установки StopLoss-ордера”,”Actual StopLoss order placement price”},
{“Фактическая цена установки TakeProfit-ордера”,”Actual TakeProfit order placement price”},
{“Фактический тип заливки ордера”,”Actual order filling type”},
{“Фактический тип экспирации ордера”,”Actual of order expiration type”},
{“Фактическое время жизни ордера”,”Actual of order lifetime”},
};◍ 暂停对象
在函数库服务类和函数的文件夹 \MQL5\Include\DoEasy\ Services\ 之下,在 Pause.mqh 文件中创建新的 CPause 类,并立即添加我们需要的所有内容: 包含服务函数文件 DELib.mqh ,显示以毫秒为单位的时间 — 在日志中显示暂停开始时间的辅助类方法需要它。 在类的私密部分中,声明三个计算暂停所需的类成员变量: m_start 变量是必需的,用于在创建暂停对象时指定参考数字,并在以前创建的暂停对象中写入新的倒计时开始参考数字。 m_time_begin 变量存储指定的暂停倒计时开始时间,它仅用于显示信息消息。 m_wait_msc 变量包含暂停的毫秒数 — 在指定的毫秒数流逝后,暂停被视为结束。 我相信,类方法不需要解释。 首先,设置新倒计时开始时间的 SetTimeBegin() 方法将已流逝的时间写入 m_time_begin 变量。 然后,它将一个新的倒计时开始参考数字设置到 m_start 变量。 由 GetTickCount() 函数返回的结果在该类中可作为任何倒计时的参考数字。 Passed() 方法返回暂停流逝的毫秒数,返回的数值为当前参考时间测量值与设置在 m_start 中的起始暂停倒计时值之间的毫秒差。 其余的类方法一目了然,勿需解释。 稍后我们将需要该类。 为了令暂停类可以从函数库、以及基于函数库的程序中的任何地方进行访问,请将 CPause 类文件包括在 DELib.mqh 函数库服务函数的文件中 : [CODE] <span class="comment">//+------------------------------------------------------------------+</span>
| <span class="comment">// | Pause.mqh | </span> |
|---|---|---|
| // | Copyright 2019, MetaQuotes Software Corp. |
<span class="comment">//| &nb
用 CPause 类掐表测 MT5 暂停耗时
在 MT5 里做延迟或节流逻辑时,靠手写时间戳容易漏掉毫秒级误差。CPause 这个类把「从构造到当前经过的毫秒数」封装成 Passed(),构造函数里直接用 ::GetTickCount() 记下起点 m_start,调用一次就能拿到Tick差。 IsCompleted() 的判断很直白:当 Passed() 大于预设的 m_wait_msc 就返回 true,相当于一个非阻塞的「睡够没」开关,适合放在 OnTick 里轮询而不卡线程。 描述函数把毫秒转成可读串:PassedDescription() 用 TimeToString(Passed()/1000, TIME_SECONDS) 只显示到秒;WaitingSECDescription() 同理把 m_wait_msc 除 1000 转秒。注意 TimeBeginDescription() 调用了 TimeMSCtoString,这是个自定义毫秒时间格式化函数,标准库没有,复制代码时要自己补。 外汇与贵金属波动剧烈,这类计时若用于控制下单频率,仍可能因滑点产生超预期成交,属高风险操作,参数 m_wait_msc 建议先在策略测试器跑通再上实盘。
class="type">ulong Passed(class="type">void) const { class="kw">return ::GetTickCount()-this.m_start; } class="type">bool IsCompleted(class="type">void) const { class="kw">return this.Passed()>this.m_wait_msc; } class="type">ulong TimeBegin(class="type">void) const { class="kw">return this.m_time_begin; } class="type">ulong TimeWait(class="type">void) const { class="kw">return this.m_wait_msc; } class=class="str">"cmt">//--- Return the description(class="num">1) of the time passed till the countdown starts in milliseconds, class=class="str">"cmt">//--- (class="num">2) pause countdown start time, (class="num">3) pause in milliseconds class="type">class="kw">string PassedDescription(class="type">void) const { class="kw">return ::TimeToString(this.Passed()/class="num">1000,TIME_SECONDS); } class="type">class="kw">string TimeBeginDescription(class="type">void) const { class="kw">return ::TimeMSCtoString(this.m_time_begin); } class="type">class="kw">string WaitingMSCDescription(class="type">void) const { class="kw">return (class="type">class="kw">string)this.m_wait_msc; } class="type">class="kw">string WaitingSECDescription(class="type">void) const { class="kw">return ::TimeToString(this.m_wait_msc/class="num">1000,TIME_SECONDS); } class=class="str">"cmt">//--- Constructor CPause(class="type">void) : m_start(::GetTickCount()){;} };
「把暂停逻辑塞进EA的包含文件」
这段尾部代码展示了如何在 MT5(兼容 MQL4 严格模式)的 EA 工程里,通过 include 把通用模块拆出去。 #property strict 是 MQL4 迁移到 MQL5 编译器时的必要开关,少了它旧式写法会直接报编错。 #include "..\Defines.mqh" 和 #include "Message.mqh"、"TimerCounter.mqh" 都是相对路径引用,说明工程用了分层目录管理;而被高亮的那行 #include "Pause.mqh" 才是本节重点——把「暂停交易」封装成独立文件,主程序只管调接口。 你在 MT5 里新建 EA 时,若也想隔离风控开关,直接照这个结构建 Pause.mqh 并 include 进来,编译后就能在策略里随时挂起下单。外汇与贵金属杠杆高,暂停机制能在极端波动时帮你掐断自动单,但无法消除持仓已有风险。
class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property strict class=class="str">"cmt">// Necessary for mql4 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Include files | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "..\Defines.mqh" class="macro">#include "Message.mqh" class="macro">#include "TimerCounter.mqh" class="macro">#include "Pause.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+