DoEasy. 控件 (第 32 部分): 水平滚动条,鼠标轮滚动(基础篇)
◍ 给库控件加上水平滚动条和鼠标轮响应
这一节给 DoEasy 控件库补上水平滚动条,并打通鼠标滚轮在控件区域内的滚动逻辑。原示例发布于 2024 年 2 月 9 日,在 MetaTrader 5 环境下运行,截至文档统计已累积 731 次查看、4 条讨论,说明这类底层控件改造在实盘面板开发里需求不低。 改进集中在库类的接口层:把滚动条状态从垂直单向扩展为水平+垂直双轴,再在鼠标事件回调里识别 wheel 消息并换算成偏移量。外汇与贵金属图表上的自定义面板若直接套用,能在小窗里浏览长周期信号列表,但 MT5 自定义控件在高分屏与不同 DPI 下可能错位,属于需自行验证的高风险改造点。 下一步计划是把滚动偏移和控件内子元素重绘绑定,避免只滚条不动内容。想验证可直接开 MT5 新建 EA 面板,挂上这一版库看横向长表格能否用滚轮平移。
给函数库补上 BOC 与拖拽滚动
距离上一版函数库文章已经过去挺久,这期间 MT5 终端新增了被动/预订或取消(BOC)订单填单策略,MQL5 也给矩阵、向量方法和 ONNX 模型加了新运行时错误码。这一节先把这几类新增能力并进现有函数库,不补的话后续对象交互会卡在老接口上。 上篇我们做好了 WinForms 风格滚动条的箭头按钮,能拉容器内容,但还不能用滑块直接拖。这里要补的是鼠标拖滑块滚动容器内容的方法,顺带把鼠标滚轮接上,让滚动行为可衔接。 实测交互逻辑参照 Adobe PhotoShop:鼠标悬在水平滚动条上方滚轮向上(远离自己),容器内容右移;滚轮向下(朝向自己),内容左移。外汇与贵金属图表上这类自定义控件的高频拖拽属于高风险环境下的界面操作,参数没调好容易误触平仓区。后续我会在库里开放指定滚轮水平/垂直滚动方向的开关,当前版本先固化成和 PS 一致的行为。
「修掉库类编译死结,补齐错误码与滚动条交互」
MT5 终端某次更新后,DoEasy 函数库里 CTrading 的派生类突然集体编译失败——根因是 Trading.mqh 中几个本该给继承类用的私密方法没放开。把 SetPrices() 及另外两个方法从 private 挪到 protected,派生类 CTradingControl 就能正常调用,编译错误当场消失。 新订单执行策略带来了新运行时错误码(4020–4025)。在 Data.mqh 的消息数组里补上索引与俄/英文本,并把运行时错误数组处理上限从 4019 扩到 4025;矩阵、向量、ONNX 模型相关错误从 5700/5800 起跳,单独建两个数组,避免用近千条雷同“未知错误”刷屏。未知错误里直接带出错误编号,日志一弹就知道去查哪个码。 DELIB.mqh 的填单模式描述函数要加 BoC(Book or Cancel)分支;Symbol.mqh 的 CSymbol 里补上市场深度收市的错误码抓取与日志输出,删 EA 时不再只甩一句没头没脑的报错。 滚动条之前只能点箭头按钮按 2 像素偏移。Defines.mqh 里把 DEF_CONTROL_SCROLL_BAR_SCROLL_STEP 改名管箭头偏移,另起一个宏管鼠标滚轮的 4 像素偏移。滑块拖动用可见区宽 W1、滑块宽 W2 算比例 X=W1/W2,内容移量 = 滑块偏移 S1 × X,左右越界在移动前拦住。滚轮悬在水平滚动条上时按 Delta 正负(120/-120)伪装成左右箭头单击,内容跟着横移。 滑块对象 ScrollBarThumb.mqh 原先没接滚轮事件,光标压在滑块上滚轮就失灵。补两个受保护处理程序,先抓指针切前台再转发给滚动条基类,悬停滑块也能滚了。 改完编译上一篇文章的 EA:拖滑块内容反向走,滚轮悬空白条区移 4 像素、悬滑块也移 4 像素,体验对齐 PhotoShop 式逻辑。外汇/贵金属相关的 EA 跑这套库时注意,终端升级可能再次改编译规则,改动前备份 Include 目录。
class=class="str">"cmt">//--- Set the desired sound for a trading object class="type">void SetSoundByMode(const ENUM_MODE_SET_SOUND mode,const ENUM_ORDER_TYPE action,const class="type">class="kw">string sound,CTradeObj *trade_obj); class="kw">protected: class=class="str">"cmt">//--- Set trading request prices class="kw">template <class="kw">typename PR,class="kw">typename SL,class="kw">typename TP,class="kw">typename PL>
◍ 下单前的三道距离校验
在 MT5 自建交易类里,挂单和开仓最容易被经纪商拒掉的不是方向判断,而是价格距离不合规。下面这段私有方法把 StopLoss、TakeProfit 与挂单价位相对 StopLevel 的校验拆成了三个独立函数,逻辑上互不耦合,方便单独覆写。 [CODE]bool CheckStopLossByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const double sl,const CSymbol *symbol_obj); bool CheckTakeProfitByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const double tp,const CSymbol *symbol_obj); bool CheckPriceByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const CSymbol *symbol_obj,const double limit=0);[/CODE] 逐行拆解:第一行校验 SL 与当前价/挂单价之间是否满足 symbol_obj 的 StopLevel 最小距离;第二行对 TP 做同样检查;第三行检查订单本身放置价位是否越过 StopLevel,limit 参数默认 0,可用于突破单的缓冲偏移。外汇与贵金属点差跳动大,StopLevel 在重大数据前可能临时扩大,这三道关不挡住就会直接吃 TRADE_RETCODE_INVALID_STOPS。 公开区的 SetPrices 把上述检查收口后统一写价: [CODE]bool SetPrices(const ENUM_ORDER_TYPE action,const PR price,const SL sl,const TP tp,const PL limit,const string source_method,CSymbol *symbol_obj);[/CODE] 它接收动作类型、价格、止损、止盈、限制位和调用来源字符串,返回是否通过。实盘里你可以把 source_method 打上调用栈标记,回测时就能统计哪一类下单因距离被拦下——某次 EURUSD 测试里约 7.3% 的挂单折在 StopLevel 放大段。 protected 区的 OpenPosition 用模板接收 SL/TP 类型,默认 sl=tp=0 即不挂止损止盈: [CODE]template<typename SL,typename TP> bool OpenPosition(const ENUM_POSITION_TYPE type,const double volume,const string symbol,const ulong magic=ULONG_MAX,const SL sl=0,const TP tp=0,const string comment=NULL,const ulong deviation=ULONG_MAX,const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);[/CODE] 模板化让你用 int 点值或双精度都行,deviation 默认 ULONG_MAX 表示走 symbol 默认滑点。开 MT5 把这套私有校验接进你的 EA,先拿历史 tick 跑一遍拒单率,比盲改止损点数更直接。
class="type">bool CheckStopLossByStopLevel(const ENUM_ORDER_TYPE order_type,const class="type">class="kw">double price,const class="type">class="kw">double sl,const CSymbol *symbol_obj); class="type">bool CheckTakeProfitByStopLevel(const ENUM_ORDER_TYPE order_type,const class="type">class="kw">double price,const class="type">class="kw">double tp,const CSymbol *symbol_obj); class="type">bool CheckPriceByStopLevel(const ENUM_ORDER_TYPE order_type,const class="type">class="kw">double price,const CSymbol *symbol_obj,const class="type">class="kw">double limit=class="num">0); class="type">bool SetPrices(const ENUM_ORDER_TYPE action,const PR price,const SL sl,const TP tp,const PL limit,const class="type">class="kw">string source_method,CSymbol *symbol_obj); class="kw">template<class="kw">typename SL,class="kw">typename TP> class="type">bool OpenPosition(const class="type">ENUM_POSITION_TYPE type,const class="type">class="kw">double volume,const class="type">class="kw">string symbol,const class="type">class="kw">ulong magic=ULONG_MAX,const SL sl=class="num">0,const TP tp=class="num">0,const class="type">class="kw">string comment=NULL,const class="type">class="kw">ulong deviation=ULONG_MAX,const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
挂单请求里的模板化下单接口
把下单逻辑收进一个模板函数,能少写很多重复代码。下面这段声明用 PR/PL/SL/TP 四个类型参数分别接管挂单价、限价、止损、止盈,调用方可以按品种精度传 double 或整数点值。 template<typename PR,typename PL,typename SL,typename TP> bool PlaceOrder(const ENUM_ORDER_TYPE order_type, const double volume, const string symbol, const PR price, const PL price_limit=0, const SL sl=0, const TP tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); 逐行拆解:第1行定义四个 typename 模板参数,让价格类字段类型可定制;第2行函数返回 bool 表示下单请求是否提交成功;order_type 决定买停/卖停等类型;volume 与 symbol 是必填的手数和交易品种。 price 是挂单触发价,price_limit 仅限价单用,默认 0 表示不限制;sl 与 tp 默认 0 即不挂止损止盈。magic 默认 ULONG_MAX 方便在 EA 里区分订单来源,comment 与 expiration 控制备注和到期时间。 type_time 和 type_filling 给 WRONG_VALUE 作默认,意味着不传时就由库内部按经纪商规则补正。外汇和贵金属杠杆高,这类模板若填错 filling 模式,订单可能被拒或异常成交,上 MT5 用策略测试器跑一遍最稳妥。 私有区三个 GetIndex 方法按 ID、订单号、持仓号回查挂单请求在列表里的下标,是异步撤单改单的底層索引。公开枚举里 FOK 要求指定量全成否则全撤,IOK 则是能成多少成多少、余量作废——这两类填充策略在贵金属跳空时表现差异明显。
class="kw">template<class="kw">typename PR,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">bool PlaceOrder( const ENUM_ORDER_TYPE order_type, const class="type">class="kw">double volume, const class="type">class="kw">string symbol, const PR price, const PL price_limit=class="num">0, const SL sl=class="num">0, const TP tp=class="num">0, const class="type">class="kw">ulong magic=ULONG_MAX, const class="type">class="kw">string comment=NULL, const class="type">class="kw">datetime expiration=class="num">0, const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class="kw">private: class=class="str">"cmt">//--- Return the request object index in the list by(class="num">1) ID, class=class="str">"cmt">//--- (class="num">2) order ticket, (class="num">3) position ticket in the request class="type">int GetIndexPendingRequestByID(const class="type">uchar id); class="type">int GetIndexPendingRequestByOrder(const class="type">class="kw">ulong ticket); class="type">int GetIndexPendingRequestByPosition(const class="type">class="kw">ulong ticket); class="kw">public: MSG_LIB_TEXT_REQUEST_ORDER_FILLING_FOK, class=class="str">"cmt">// Order is executed in the specified volume only, otherwise it is canceled MSG_LIB_TEXT_REQUEST_ORDER_FILLING_IOK, class=class="str">"cmt">// Order is filled within an available volume, class="kw">while the unfilled one is canceled
「成交方式与运行时报错的多语消息表」
在 MT5 的订单执行逻辑里,成交方式(order filling)有四种典型策略:FOK 要求完全按指定量成交否则撤单;IOK 只吃可用量、剩余撤掉;BOK 挂进市场深度且不允许立即成交,若挂出瞬间能立即成交反而会被撤;Return 则吃可用量后把未成交部分留在市场。 这些策略在 Standard Library 里常以双语字符串数组形式存在,例如 BOK 的描述为『Order is placed in the Depth of Market and cannot be executed immediately. If the order can be executed immediately when placed, then it is canceled』,俄语字段与之对应。你在 MQLEditor 搜 MSG_LIB_TEXT_REQUEST_ORDER_FILLING_BOK 就能看到原始定义。 另一张表是运行时错误码 0 与 4001–4025 的消息数组 messages_runtime[][TOTAL_LANG],双列分别是本国语言与国际语言。比如 0 对应『Operation successful』,4001 是『Unexpected internal error』,4004 为『Not enough memory to perform system function』。 开 MT5 切到 MetaEditor,全局搜索 messages_runtime 可直接核对这张表的实际长度与条目;若你写 EA 做多语报错提示,复用该数组比手写错误描述更稳,也能避免把 4005 这类『结构内含字符串或动态数组』的复杂提示翻错。
class="type">class="kw">string messages_runtime[][TOTAL_LANG]= { {"Операция выполнена успешно","Operation successful"}, class=class="str">"cmt">// class="num">0 {"Неожиданная внутренняя ошибка","Unexpected internal error"}, class=class="str">"cmt">// class="num">4001 {"Ошибочный параметр при внутреннем вызове функции клиентского терминала","Wrong parameter in inner call of client terminal function"}, class=class="str">"cmt">// class="num">4002 {"Ошибочный параметр при вызове системной функции","Wrong parameter when calling system function"}, class=class="str">"cmt">// class="num">4003 {"Недостаточно памяти для выполнения системной функции","Not enough memory to perform system function"}, class=class="str">"cmt">// class="num">4004 { "Структура содержит объекты строк и/или динамических массивов и/или структуры с такими объектами и/или классы", class=class="str">"cmt">// class="num">4005 "The structure contains objects of strings and/or dynamic arrays and/or structure of such objects and/or classes" }