轻松快捷开发 MetaTrader 程序的函数库 (第 三十一部分) :延后交易请求 - 在特定条件下开仓(基础篇)
「把开仓请求挂起来等条件」
在 MT5 里直接市价开仓,常常错过你真正想要的入场环境。把交易请求做成「延后对象」,让程序先记着这笔单,等价格或指标满足条件再真正发往服务器,是更稳的做法。 本文示例基于 2020 年 4 月 13 日发布的思路,原文示例阅读量 3038,说明这类延迟执行结构在 EA 开发里需求不小。 核心流程分四步:先理清概念,再准备所需数据,然后按需创建延后请求对象,最后做测试验证。下一步才是接入具体交易逻辑。 外汇和贵金属杠杆高,延后请求只是控制触发时机,不消除滑点与熔断风险,条件命中也可能不盈利。
◍ 用延后请求把条件交易写进库里
做函数库时我提过一个思路:交易不用立刻发,而是先挂成“延后请求”,等条件满足再送交易服务器。之前二十几篇一直在填“服务器报错后重发”的那一半,现在反过来补齐另一半——由程序自己判定条件,条件到了才下单。 具体能玩出什么组合?举几个轻量例子:价格跌破某阈值且过了指定时间就买;账户浮利超过某值就部分平仓;止损单被扫后自动反手。条件源只有三类——品种属性、账户属性、账户事件,三者可任意拼。本期先落地品种和账户属性的跟踪,事件响应留到后面。 延后请求对象里要塞进一张二维数组当条件表:第一维是条件序号,第二维装该条件的全部字段——源类型(品种/账户/事件)、具体条件枚举、比较符(>/</==/!=/>=/<=)、引用值、当前值。管理类开个定时器扫这张表,全部命中就在同一类里把请求推出去。 本篇只做最小验证:测试 EA 里只跟两个条件——价格和时间。它们可单独生效,也可“价格且时间”联合触发,用来确认延后请求在条件交易里的通路。外汇与贵金属杠杆高,条件单也只降低手动延迟,不消除滑点与黑天鹅风险。
延后请求的数据源与消息索引搭建
做延后请求功能前,先在 Datas.mqh 里补一批消息索引和对应文本。账户、品种、事件这几类激活条件的来源完全不同,如果只靠已有的属性索引,很容易把不同对象的同名属性搞混,所以必须显式声明数据源再跟踪数值。 在 Defines.mqh 中写两个枚举:一个是延后请求激活来源(账户 / 品种 / 事件),另一个是各来源下的具体激活准则。枚举常量的值直接等于对应属性文本消息的常量值,日志输出时只需用常量索引就能带出归属,不用再额外判断它属于哪一类。 三个枚举可任意组合,编译出所需的激活条件集合。DELib.mqh 里补一个返回比较类型描述的函数;原库里 STOP_LOSS / TAKE_PROFIT 字符串常量改名为 SL / TP,涉及文件较多,全局替换时要注意别漏。 下面这段是 Datas.mqh 中新增的索引与文本片断,高亮行为本次延后请求相关的新增项,可对照抄进你的库里验证编译。
class=class="str">"cmt">//--- CEvent MSG_EVN_EVENT, class=class="str">"cmt">// Event MSG_EVN_TYPE, class=class="str">"cmt">// Event type class=class="str">"cmt">//--- CAccount MSG_ACC_ACCOUNT, class=class="str">"cmt">// Account MSG_ACC_PROP_LOGIN, class=class="str">"cmt">// Account number MSG_LIB_TEXT_REQUEST, class=class="str">"cmt">// Pending request # MSG_LIB_TEXT_REQUEST_ACTIVATED, class=class="str">"cmt">// Pending request activated: # MSG_LIB_TEXT_REQUEST_DATAS, class=class="str">"cmt">// Trading request parameters MSG_LIB_TEXT_PEND_REQUEST_DATAS, class=class="str">"cmt">// Pending trading request parameters MSG_LIB_TEXT_PEND_REQUEST_CREATED, class=class="str">"cmt">// Pending request created MSG_LIB_TEXT_PEND_REQUEST_DELETED, class=class="str">"cmt">// Removed due to expiration MSG_LIB_TEXT_PEND_REQUEST_EXECUTED, class=class="str">"cmt">// Removed due to execution MSG_LIB_TEXT_PEND_REQUEST_GETTING_FAILED, class=class="str">"cmt">// Failed to obtain a pending request object from the list MSG_LIB_TEXT_PEND_REQUEST_FAILED_ADD_PARAMS, class=class="str">"cmt">// Failed to add request activation parameters. Error: MSG_LIB_TEXT_PEND_REQUEST_PRICE_CREATE, class=class="str">"cmt">// Price at the moment of request generation MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_EXPIRATION, class=class="str">"cmt">// Actual order lifetime MSG_LIB_TEXT_PEND_REQUEST_NO_FREE_IDS, class=class="str">"cmt">// No free IDs to create a pending request MSG_LIB_TEXT_PEND_REQUEST_ACTIVATION_TERMS, class=class="str">"cmt">// Activation conditions MSG_LIB_TEXT_PEND_REQUEST_CRITERION, class=class="str">"cmt">// Criterion MSG_LIB_TEXT_PEND_REQUEST_ADD_CRITERIONS, class=class="str">"cmt">// Added pending request activation conditions }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- CEvent {"Событие","Event"}, {"Тип события","Event&class="macro">#x27;s type"}, class=class="str">"cmt">//--- CAccount {"Аккаунт","Account"},
「挂单请求的多语言标签与激活枚举」
在 MT5 的 EA 工程里,挂单请求(pending request)的诊断信息常以俄英双字符串数组形式硬编码,方便日志同时输出母语与英文。上面这段数组里,背景高亮的几行才是运行时真正容易踩坑的节点:'Pending request activated: #' 表示挂单已被触发,'Failed to get pending request object from list' 则说明内部链表索引失效,这类错误在账户切换或历史清理时概率明显上升。 激活来源被拆成三个枚举值:PEND_REQ_ACTIVATION_SOURCE_ACCOUNT 代表由账户权益、保证金等数据驱动;PEND_REQ_ACTIVATION_SOURCE_SYMBOL 由品种报价或点差变动驱动;PEND_REQ_ACTIVATION_SOURCE_EVENT 则来自成交、删单等交易事件。实盘中外汇与贵金属波动剧烈,若把激活源误配成 SYMBOL 而品种流动性断裂,挂单可能长时间不触发。 请求类型只有两种:PEND_REQ_TYPE_ERROR 是基于错误码生成的挂单(通常不该手动建),PEND_REQ_TYPE_REQUEST 才是正常由交易请求派生的挂单。写代码时建议先判断类型再处理,避免把系统纠错单当成用户策略单。
enum ENUM_PEND_REQ_TYPE { PEND_REQ_TYPE_ERROR=PENDING_REQUEST_ID_TYPE_ERR, class=class="str">"cmt">// Pending request created based on the class="kw">return code or error PEND_REQ_TYPE_REQUEST=PENDING_REQUEST_ID_TYPE_REQ, class=class="str">"cmt">// Pending request created by request }; enum ENUM_PEND_REQ_ACTIVATION_SOURCE { PEND_REQ_ACTIVATION_SOURCE_ACCOUNT, class=class="str">"cmt">// Pending request activated by account data PEND_REQ_ACTIVATION_SOURCE_SYMBOL, class=class="str">"cmt">// Pending request activated by symbol data PEND_REQ_ACTIVATION_SOURCE_EVENT, class=class="str">"cmt">// Pending request activated by trading event data };
◍ 挂单按账户状态触发的枚举映射
在 MQL5 的挂单管理封装里,可以用一组枚举把「挂单激活条件」绑定到账户属性上。下面这段枚举把空值、杠杆、挂单上限、交易许可等映射到对应的账户属性消息常量。 整数类条件覆盖账户开关与限额:未设置值映射到 MSG_LIB_PROP_NOT_SET;杠杆对应 MSG_ACC_PROP_LEVERAGE;最大活动挂单数对应 MSG_ACC_PROP_LIMIT_ORDERS;服务器侧账户交易权限与 EA 交易权限分别映射到 MSG_ACC_PROP_TRADE_ALLOWED 和 MSG_ACC_PROP_TRADE_EXPERT。 double 类条件则绑定资金与风险指标:余额、信用、利润、净值、已用保证金、自由保证金、保证金水平百分比、初始保证金、维持保证金、账户资产,各自映射到 MSG_ACC_PROP_BALANCE 到 MSG_ACC_PROP_ASSETS 这一系列常量。 开 MT5 在 Include 里搜 PEND_REQ_ACTIVATE_BY_ACCOUNT_ 前缀,能直接看到这组映射;写 EA 时若想让挂单在「保证金水平低于某值」才激活,就走 PEND_REQ_ACTIVATE_BY_ACCOUNT_MARGIN_LEVEL 这条枚举。外汇与贵金属杠杆波动大,这类触发逻辑仅作风控辅助,实际触发概率受经纪商规则影响。
PEND_REQ_ACTIVATE_BY_ACCOUNT_EMPTY = MSG_LIB_PROP_NOT_SET, class=class="str">"cmt">// Value not set PEND_REQ_ACTIVATE_BY_ACCOUNT_LEVERAGE = MSG_ACC_PROP_LEVERAGE, class=class="str">"cmt">// Activate by a provided leverage PEND_REQ_ACTIVATE_BY_ACCOUNT_LIMIT_ORDERS = MSG_ACC_PROP_LIMIT_ORDERS, class=class="str">"cmt">// Activate by a maximum allowed number of active pending orders PEND_REQ_ACTIVATE_BY_ACCOUNT_TRADE_ALLOWED = MSG_ACC_PROP_TRADE_ALLOWED, class=class="str">"cmt">// Activate by the permission to trade for the current account from the server side PEND_REQ_ACTIVATE_BY_ACCOUNT_TRADE_EXPERT = MSG_ACC_PROP_TRADE_EXPERT, class=class="str">"cmt">// Activate by the permission to trade for an EA from the server side class=class="str">"cmt">//--- class="type">class="kw">double PEND_REQ_ACTIVATE_BY_ACCOUNT_BALANCE = MSG_ACC_PROP_BALANCE, class=class="str">"cmt">// Activate by an account balance in the deposit currency PEND_REQ_ACTIVATE_BY_ACCOUNT_CREDIT = MSG_ACC_PROP_CREDIT, class=class="str">"cmt">// Activate by credit in a deposit currency PEND_REQ_ACTIVATE_BY_ACCOUNT_PROFIT = MSG_ACC_PROP_PROFIT, class=class="str">"cmt">// Activate by the current profit on the account in the deposit currency PEND_REQ_ACTIVATE_BY_ACCOUNT_EQUITY = MSG_ACC_PROP_EQUITY, class=class="str">"cmt">// Sort by an account equity in the deposit currency PEND_REQ_ACTIVATE_BY_ACCOUNT_MARGIN = MSG_ACC_PROP_MARGIN, class=class="str">"cmt">// Activate by an account reserved margin in the deposit currency PEND_REQ_ACTIVATE_BY_ACCOUNT_MARGIN_FREE = MSG_ACC_PROP_MARGIN_FREE, class=class="str">"cmt">// Activate by account free funds available for opening a position in the deposit currency PEND_REQ_ACTIVATE_BY_ACCOUNT_MARGIN_LEVEL = MSG_ACC_PROP_MARGIN_LEVEL, class=class="str">"cmt">// Activate by account margin level in % PEND_REQ_ACTIVATE_BY_ACCOUNT_MARGIN_INITIAL = MSG_ACC_PROP_MARGIN_INITIAL, class=class="str">"cmt">// Activate by funds reserved on an account to ensure a guarantee amount for all pending orders PEND_REQ_ACTIVATE_BY_ACCOUNT_MARGIN_MAINTENANCE = MSG_ACC_PROP_MARGIN_MAINTENANCE, class=class="str">"cmt">// Activate by funds reserved on an account to ensure a minimum amount for all open positions PEND_REQ_ACTIVATE_BY_ACCOUNT_ASSETS = MSG_ACC_PROP_ASSETS class=class="str">"cmt">// Activate by the current assets on the account
按品种属性触发挂单请求的条件枚举
延着账户负债与冻结佣金触发逻辑往下,品种维度的激活条件被收进 ENUM_PEND_REQ_ACTIVATE_BY_SYMBOL_PROP 枚举,把报价、成交量、挂单分布等实时属性直接映射成触发开关。 double 类里最常用的是 BID、ASK 和 LAST:分别对应可卖出最优价、可买入最优价与最近成交价,盯盘时若想在某品种报价刺穿关键位才激活请求,这三个值就是底层钩子。 long 类覆盖的是 session 与日度量级数据,例如 SESSION_DEALS 取当前会话成交笔数,SESSION_BUY_ORDERS / SESSION_SELL_ORDERS 取当下多空挂单数量,VOLUMEHIGH / VOLUMELOW 取当日最大与最小成交量;SPREAD 则以点数为单位暴露点差宽度。 这套枚举没有默认生效项,EMPTY 对应 MSG_LIB_PROP_NOT_SET 表示未设置。实盘接贵金属或外汇品种前,先在 MT5 用 SymbolInfoInteger/SymbolInfoDouble 把上面字段打印一遍,确认你的券商确实推送了对应属性,否则触发可能永远不响。
enum ENUM_PEND_REQ_ACTIVATE_BY_SYMBOL_PROP { PEND_REQ_ACTIVATE_BY_SYMBOL_EMPTY = MSG_LIB_PROP_NOT_SET, class=class="str">"cmt">// Value not set class=class="str">"cmt">//--- class="type">class="kw">double PEND_REQ_ACTIVATE_BY_SYMBOL_BID = MSG_LIB_PROP_BID, class=class="str">"cmt">// Activate by Bid - the best price at which a symbol can be sold PEND_REQ_ACTIVATE_BY_SYMBOL_ASK = MSG_LIB_PROP_ASK, class=class="str">"cmt">// Activate by Ask - best price, at which an instrument can be bought PEND_REQ_ACTIVATE_BY_SYMBOL_LAST = MSG_LIB_PROP_LAST, class=class="str">"cmt">// Activate by the last deal price class=class="str">"cmt">//--- class="type">long PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_DEALS = MSG_SYM_PROP_SESSION_DEALS, class=class="str">"cmt">// Activate by number of deals in the current session PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_BUY_ORDERS = MSG_SYM_PROP_SESSION_BUY_ORDERS, class=class="str">"cmt">// Activate by number of Buy orders at the moment PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_SELL_ORDERS = MSG_SYM_PROP_SESSION_SELL_ORDERS, class=class="str">"cmt">// Activate by number of Sell orders at the moment PEND_REQ_ACTIVATE_BY_SYMBOL_VOLUME = MSG_SYM_PROP_VOLUME, class=class="str">"cmt">// Activate by the last deal volume PEND_REQ_ACTIVATE_BY_SYMBOL_VOLUMEHIGH = MSG_SYM_PROP_VOLUMEHIGH, class=class="str">"cmt">// Activate by maximum Volume per day PEND_REQ_ACTIVATE_BY_SYMBOL_VOLUMELOW = MSG_SYM_PROP_VOLUMELOW, class=class="str">"cmt">// Activate by minimum Volume per day PEND_REQ_ACTIVATE_BY_SYMBOL_TIME = MSG_SYM_PROP_TIME, class=class="str">"cmt">// Activate by the last quote time PEND_REQ_ACTIVATE_BY_SYMBOL_SPREAD = MSG_SYM_PROP_SPREAD class=class="str">"cmt">// Activate by spread in points };