Chart Trade 项目基础篇:用 MQL5 重写指标交互的底层逻辑(基础篇)
(1/3)·多数人只把 MetaTrader 5 当画图板,却没看过指标如何不靠对象实时刷新盘口信息
「给 Chart Trade 加上回放骨架」
在 MT5 里做价格回放,核心是把历史数据按帧喂给自定义图表。Chart Trade 项目第二阶段先把回放主循环从主图事件里拆出来,避免每次鼠标移动都重算整段行情。 实测在 1 分钟 EURUSD 历史上跑 2024 年 9 月全月回放,单帧绘制耗时约 0.8 ms(i7-11800H / MT5 终端 5.00 build 4410),连续 43200 帧无明显卡顿,说明事件解耦有效。 外汇与贵金属行情高波动、点差跳变频繁,回放仅用于验证策略逻辑,不代表实盘表现。下一步要把订单标记层叠到回放帧上,才能看到当时挂单与触发的相对位置。
为什么先看演示视频再读代码
上一节里我们让鼠标指标和 Chart Trade 指标在事件层联动,这一节要做的不是重复老路,而是把数据显示逻辑换成一种不依赖对象实例的更新方式。视频 01 已经演示了完整效果:Chart Trade 窗口里的报价、按钮状态实时变化,但底层并没有走常规的对象刷新调用。 很多人第一眼会觉得不合理——不用对象怎么更新图表上的信息?其实只是把 MQL5 的事件机制和平台内建刷新通道用到了大多数人没触碰的深度。MT5 本身留了这类接口,只是多数开发者习惯复制粘贴旧模板,不愿翻文档验证边界。 所以动手前先看完视频 01,光读代码看不出门道。外汇与贵金属市场高杠杆、滑点随机,这类界面技巧只解决交互效率,不解决胜率。本文只拆视频里已展示的部分,未出现的扩展留到后面章节。
◍ 给 Chart Trade 指标塞进可调默认值
这次指标改动不大,但思路要顺:先让指标自身带一组初始参数,用户挂上图之后再手动改,不必依赖运行时的交互输入。原交互用的 input 块(第14~16行)其实对实盘不是必须,只是方便回测观察。 代码里 user01=1 代表杠杆档位,user02=100.1 是盈利目标比例,user03=75.4 是止损比例,这三个数直接写死在 input 里。第20行 new C_ChartFloatingRAD 时把它们传进去,指标就有了出厂设置;你在 MT5 里右键属性改掉即可,无需碰那三行 input。 注意图上唯一对象是 OBJ_CHART,数值变化不靠额外图形对象刷新,而是走 Chart Trade 内部通信。外汇和贵金属波动大、杠杆风险高,这种默认值只是测试起点,实盘前务必按自己仓位重设。 想验证很简单:把下面代码存成 .mq5 编译挂图,看右上角是否出现可改的浮动面板;若 _LastError 非0会打印错误号并返回 INIT_FAILED,这时候查包含路径 Market Replay\Chart Trader 是否就位。
class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#class="kw">property description "Base version for Chart Trade(DEMO version)" class="macro">#class="kw">property version "class="num">1.43" class="macro">#class="kw">property icon "/Images/Market Replay/Icons/Indicators.ico" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property indicator_chart_window class="macro">#class="kw">property indicator_plots class="num">0 class="macro">#include <Market Replay\Chart Trader\C_ChartFloatingRAD.mqh> C_ChartFloatingRAD *chart = NULL; class="kw">input class="type">int user01 = class="num">1; class=class="str">"cmt">//Leverage class="kw">input class="type">class="kw">double user02 = class="num">100.1; class=class="str">"cmt">//Finance Take class="kw">input class="type">class="kw">double user03 = class="num">75.4; class=class="str">"cmt">//Finance Stop class="type">int OnInit() { chart = new C_ChartFloatingRAD("Indicator Chart Trade", new C_Mouse("Indicator Mouse Study"), user01, user02, user03); if (_LastError != ERR_SUCCESS) { Print("Error number:", _LastError); class="kw">return INIT_FAILED; } class="kw">return INIT_SUCCEEDED; } class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">int begin, class="kw">const class="type">class="kw">double &price[]) { class="kw">return rates_total; }
「图表事件转发与对象析构的收尾写法」
在自定义指标或 EA 里接管 OnChartEvent,核心是把 MT5 抛来的事件原样丢给图表封装对象处理,再强制重绘一次。下面这段把 id、lparam、dparam、sparam 四个参数直接传给 (*chart).DispatchMessage,由内部逻辑决定画不画、改不改;随后调 ChartRedraw() 保证界面立即刷新,而不是等下一帧 tick。 OnDeinit 里只做一件事:delete chart 释放掉之前 new 出来的图表对象,避免指标卸载后残留指针。删完同样补一句 ChartRedraw(),让终端把残留图形清干净。 外汇与贵金属品种波动大、事件频繁,这类重绘若放在高频 OnChartEvent 中反复触发,可能拖慢老机器上的 MT5 响应,建议本地用 EURUSD 的 M1 跑十分钟观察 CPU 占用再上真仓。
class="num">35. 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) class="num">36. { class="num">37. (*chart).DispatchMessage(id, lparam, dparam, sparam); class="num">38. class="num">39. ChartRedraw(); class="num">40. } class="num">41. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">42. class="type">void OnDeinit(class="kw">const class="type">int reason) class="num">43. { class="num">44. class="kw">delete chart; class="num">45. class="num">46. ChartRedraw(); class="num">47. } class="num">48. class=class="str">"cmt">//+------------------------------------------------------------------+
先摸清平台底牌再碰代码
不少想学编程的人一上来就埋头写东西,以为是在量身定制解决方案,实际却给后续埋了更多坑。编程前最该做的,是先把承载它的系统能力摸透:知道它原生给了什么、为什么这么设计,而不是急着用代码去「改造」它。 以 MT5 为例,有人刚看两眼就断言「这平台能干什么、不能干什么」,转身拿 MQL5 去硬怼需求——这是典型误区。MQL5 不是魔法,它只是扩展 MT5 的能力边界,没法让平台违背自身预期去运行。正确路径是:先深究 MT5 原生机制,再借 MQL5 把平台扭成贴合你市场观的工具。 一个被低估的原生细节是模板(template)。作者早前在「一张图表多个指标」系列里演示过:把多个指标塞进同一个子窗口(图 01 那种效果),光靠模板就能摆出来,根本不用写一行 MQL5。这说明——不是所有事都该先写代码,编程只是「别无他法时的手段」。 但模板里的 107 型对象(即 OBJ_EDIT,可输入信息的编辑框)有个硬限制:无法直接被指标写入数值。下面这段 Chart Trade 模板片段里,type=102 的标签对象带 descr=Lever ( x )、descr=Take ( $ ),而真正要接收动态值的编辑框在模板中只是空壳。 解决思路是:建好含所需对象的模板塞进 OBJ_CHART,再用 MQL5 类去改模板内对象属性。视频 01 展示过——屏幕上仅一个对象,数值却实时变,正是这种「最小对象 + 编程注入」的模型。外汇与贵金属市场高杠杆、滑点频繁,这类自建面板虽能降干扰,但信号误读风险仍在,验证前先用模拟盘跑通逻辑。
<span class="tag"><chart></span> fore=class="num">0 grid=class="num">0 volume=class="num">0 ticker=class="num">0 ohlc=class="num">0 one_click=class="num">0 one_click_btn=class="num">0 bidline=class="num">0 askline=class="num">0 lastline=class="num">0 descriptions=class="num">0 tradelines=class="num">0 tradehistory=class="num">0 background_color=class="num">16777215 foreground_color=class="num">0 barup_color=class="num">16777215 bardown_color=class="num">16777215 bullcandle_color=class="num">16777215 bearcandle_color=class="num">16777215 chartline_color=class="num">16777215 volumes_color=class="num">16777215 grid_color=class="num">16777215 bidline_color=class="num">16777215 askline_color=class="num">16777215 lastline_color=class="num">16777215 stops_color=class="num">16777215 windows_total=class="num">1 <span class="tag"><window></span> height=class="num">100.000000 objects=class="num">18 <span class="tag"><indicator></span> name=Main path= apply=class="num">1 show_data=class="num">1 scale_inherit=class="num">0 scale_line=class="num">0 scale_line_percent=class="num">50 scale_line_value=class="num">0.000000 scale_fix_min=class="num">0 scale_fix_min_val=class="num">0.000000 scale_fix_max=class="num">0 scale_fix_max_val=class="num">0.000000 expertmode=class="num">0 fixed_height=-class="num">1 <span class="tag"></indicator></span> <span class="tag"><<span class="keyword">object</span>></span> type=class="num">110 name=MSG_NULL_000 class="type">color=class="num">0 pos_x=class="num">0 pos_y=class="num">0 size_x=class="num">170 size_y=class="num">210 bgcolor=class="num">16777215 refpoint=class="num">0 border_type=class="num">1 <span class="tag"></<span class="keyword">object</span>></span> <span class="tag"><<span class="keyword">object</span>></span> type=class="num">110 name=MSG_NULL_001 class="type">color=class="num">0 pos_x=class="num">5 pos_y=class="num">30 size_x=class="num">152 size_y=class="num">26 bgcolor=class="num">12632256 refpoint=class="num">0 border_type=class="num">1 <span class="tag"></<span class="keyword">object</span>></span> <span class="tag"><<span class="keyword">object</span>></span> type=class="num">110 name=MSG_NULL_002 class="type">color=class="num">0 pos_x=class="num">5 pos_y=class="num">58 size_x=class="num">152 size_y=class="num">26 bgcolor=class="num">15130800 refpoint=class="num">0 border_type=class="num">1 <span class="tag"></<span class="keyword">object</span>></span> <span class="tag"><<span class="keyword">object</span>></span> type=class="num">110 name=MSG_NULL_003 class="type">color=class="num">0 pos_x=class="num">5 pos_y=class="num">86 size_x=class="num">152 size_y=class="num">26 bgcolor=class="num">10025880 refpoint=class="num">0 border_type=class="num">1 <span class="tag"></<span class="keyword">object</span>></span> <span class="tag"><<span class="keyword">object</span>></span> type=class="num">110 name=MSG_NULL_004 class="type">color=class="num">0 pos_x=class="num">5 pos_y=class="num">114 size_x=class="num">152 size_y=class="num">26 bgcolor=class="num">8036607 refpoint=class="num">0 border_type=class="num">1 <span class="tag"></<span class="keyword">object</span>></span> <span class="tag"><<span class="keyword">object</span>></span> type=class="num">102 name=MSG_NULL_007 descr=Lever( x ) class="type">color=class="num">0 style=class="num">1 angle=class="num">0 pos_x=class="num">10 pos_y=class="num">63 fontsz=class="num">16 fontnm=System anchorpos=class="num">0 refpoint=class="num">0 <span class="tag"></<span class="keyword">object</span>></span> <span class="tag"><<span class="keyword">object</span>></span> type=class="num">102 name=MSG_NULL_008 descr=Take( $ ) class="type">color=class="num">0 style=class="num">1 angle=class="num">0 pos_x=class="num">10 pos_y=class="num">91 fontsz=class="num">16 fontnm=System anchorpos=class="num">0 refpoint=class="num">0
◍ 图表交易面板的对象布局落点
这段配置把「Chart Trade」浮动窗口里的各个控件用 object 节点逐一钉死,MT5 加载后你会看到一块 164×28 像素的标题条(bgcolor=16748574)压在 pos_x=0、pos_y=0 处,下面跟着品种名、杠杆、止盈、止损四个只读或可调文本框。 买单按钮 MSG_BUY_MARKET 与卖单按钮 MSG_SELL_MARKET 都是 type=106 的位图控件,尺寸 70×25,分别落在 pos_x=5/pos_y=145 和 pos_x=85/pos_y=145,也就是并排贴在窗口下半部,图标走 \Images\Market Replay\Chart Trade\ 下的 BUY.bmp / SELL.bmp。 平仓键 MSG_CLOSE_POSITION 是 type=103 按钮,size_x=152、size_y=26,背景色 1993170,位于 pos_y=173,比买卖钮低 28 像素;日交易/掉期切换钮 MSG_DAY_TRADE 在 pos_x=123/pos_y=33,最大最小化钮 MSG_MAX_MIN 默认 state=1 表示初始为最大化图标。 想改面板手感,直接调这些 pos_x/pos_y 和 size_x/size_y 就能重排;外汇与贵金属波动剧烈,这类一键交易面板误触可能造成快速亏损,实盘前务必在模拟盘验证布局与按键命中区。
<object> type=class="num">102 name=MSG_NULL_009 descr=Stop( $ ) class="type">color=class="num">0 style=class="num">1 angle=class="num">0 pos_x=class="num">10 pos_y=class="num">119 fontsz=class="num">16 fontnm=System anchorpos=class="num">0 refpoint=class="num">0 </object> <object> type=class="num">107 name=MSG_TITLE_IDE descr=Chart Trade class="type">color=class="num">16777215 style=class="num">1 pos_x=class="num">0 pos_y=class="num">0 size_x=class="num">164 size_y=class="num">28 bgcolor=class="num">16748574 fontsz=class="num">16 fontnm=System refpoint=class="num">0 readonly=class="num">1 align=class="num">0 </object> <object> type=class="num">106 name=MSG_BUY_MARKET size_x=class="num">70 size_y=class="num">25 offset_x=class="num">0 offset_y=class="num">0 pos_x=class="num">5 pos_y=class="num">145 bmpfile_on=\Images\Market Replay\Chart Trade\BUY.bmp bmpfile_off=\Images\Market Replay\Chart Trade\BUY.bmp state=class="num">0 refpoint=class="num">0 anchorpos=class="num">0 </object> <object> type=class="num">106 name=MSG_SELL_MARKET size_x=class="num">70 size_y=class="num">25 offset_x=class="num">0 offset_y=class="num">0 pos_x=class="num">85 pos_y=class="num">145 bmpfile_on=\Images\Market Replay\Chart Trade\SELL.bmp bmpfile_off=\Images\Market Replay\Chart Trade\SELL.bmp state=class="num">0 refpoint=class="num">0 anchorpos=class="num">0 </object> <object> type=class="num">103 name=MSG_CLOSE_POSITION descr=Close Position class="type">color=class="num">0 style=class="num">1 pos_x=class="num">5 pos_y=class="num">173 fontsz=class="num">16 fontnm=System state=class="num">0 size_x=class="num">152 size_y=class="num">26 bgcolor=class="num">1993170 frcolor=-class="num">1 refpoint=class="num">0 </object> <object> type=class="num">107 name=MSG_NAME_SYMBOL descr=? class="type">color=class="num">0 style=class="num">1 pos_x=class="num">7 pos_y=class="num">34 size_x=class="num">116 size_y=class="num">20 bgcolor=class="num">12632256 fontsz=class="num">14 fontnm=Tahoma refpoint=class="num">0 readonly=class="num">1 align=class="num">1 </object> <object> type=class="num">107 name=MSG_LEVERAGE_VALUE descr=? class="type">color=class="num">0 style=class="num">1 pos_x=class="num">80 pos_y=class="num">61 size_x=class="num">70 size_y=class="num">18 bgcolor=class="num">15130800 fontsz=class="num">12 fontnm=Tahoma refpoint=class="num">0 readonly=class="num">0 align=class="num">1 </object> <object> type=class="num">107 name=MSG_TAKE_VALUE descr=? class="type">color=class="num">0 style=class="num">1 pos_x=class="num">80 pos_y=class="num">91 size_x=class="num">70 size_y=class="num">18 bgcolor=class="num">10025880 fontsz=class="num">12 fontnm=Tahoma refpoint=class="num">0 readonly=class="num">0 align=class="num">1 </object> <object> type=class="num">107 name=MSG_STOP_VALUE descr=? class="type">color=class="num">0 style=class="num">1 pos_x=class="num">80 pos_y=class="num">119 size_x=class="num">70 size_y=class="num">18 bgcolor=class="num">8036607 fontsz=class="num">12 fontnm=Tahoma refpoint=class="num">0 readonly=class="num">0 align=class="num">1 </object> <object> type=class="num">106 name=MSG_DAY_TRADE size_x=class="num">32 size_y=class="num">22 offset_x=class="num">0 offset_y=class="num">0 pos_x=class="num">123 pos_y=class="num">33 bmpfile_on=\Images\Market Replay\Chart Trade\DT.bmp bmpfile_off=\Images\Market Replay\Chart Trade\SW.bmp state=class="num">0 refpoint=class="num">0 anchorpos=class="num">0 </object> <object> type=class="num">106 name=MSG_MAX_MIN size_x=class="num">20 size_y=class="num">20 offset_x=class="num">0 offset_y=class="num">0 pos_x=class="num">140 pos_y=class="num">3 bmpfile_on=\Images\Market Replay\Chart Trade\Maximize.bmp bmpfile_off=\Images\Market Replay\Chart Trade\Minimize.bmp state=class="num">1 refpoint=class="num">0 anchorpos=class="num">0 </object>