Chart Trade 项目基础篇:用 MQL5 重写指标交互的底层逻辑(基础篇)
🛠️

Chart Trade 项目基础篇:用 MQL5 重写指标交互的底层逻辑(基础篇)

(1/3)·多数人只把 MetaTrader 5 当画图板,却没看过指标如何不靠对象实时刷新盘口信息

含代码示例实战向 第 1/3 篇
看演示视频时你大概疑惑:Chart Trade 窗口数据在变,代码里却没碰对象。这不是魔术,只是多数人没深挖 MQL5 的平台潜力。先看完视频再读代码,否则单纯读源码理解不了我们要做的事。

「给 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 是否就位。

MQL5 / C++
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 占用再上真仓。

MQL5 / C++
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 展示过——屏幕上仅一个对象,数值却实时变,正是这种「最小对象 + 编程注入」的模型。外汇与贵金属市场高杠杆、滑点频繁,这类自建面板虽能降干扰,但信号误读风险仍在,验证前先用模拟盘跑通逻辑。

MQL5 / C++
<span class="tag">&lt;chart&gt;</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">&lt;window&gt;</span>
height=class="num">100.000000
objects=class="num">18

<span class="tag">&lt;indicator&gt;</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">&lt;/indicator&gt;</span>

<span class="tag">&lt;<span class="keyword">object</span>&gt;</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">&lt;/<span class="keyword">object</span>&gt;</span>

<span class="tag">&lt;<span class="keyword">object</span>&gt;</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">&lt;/<span class="keyword">object</span>&gt;</span>

<span class="tag">&lt;<span class="keyword">object</span>&gt;</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">&lt;/<span class="keyword">object</span>&gt;</span>

<span class="tag">&lt;<span class="keyword">object</span>&gt;</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">&lt;/<span class="keyword">object</span>&gt;</span>

<span class="tag">&lt;<span class="keyword">object</span>&gt;</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">&lt;/<span class="keyword">object</span>&gt;</span>

<span class="tag">&lt;<span class="keyword">object</span>&gt;</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">&lt;/<span class="keyword">object</span>&gt;</span>

<span class="tag">&lt;<span class="keyword">object</span>&gt;</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 就能重排;外汇与贵金属波动剧烈,这类一键交易面板误触可能造成快速亏损,实盘前务必在模拟盘验证布局与按键命中区。

MQL5 / C++
<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>
交给小布盯盘看盘口
这些指标与鼠标互动的诊断逻辑,小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时盘口协作状态,你专注策略而非底层接线。

常见问题

本篇不再依赖对象刷新来更新信息,而是在指标层直接处理默认值与互动,结构更轻,视频演示的就是这种无对象更新方式。
概率偏低。文中逻辑靠视觉演示才直观,纯读源码容易卡在“为何没对象也在变”的疑问上,建议先看完视频 01。
不是必须。它们主要方便测试,实盘你可在第 20 行直接填默认值,跳过那几行互动点也能跑。
小布盯盘的品种页已内置类似盘口与指标协作的 AIGC 视图,虽不暴露 MQL5 源码,但交互诊断逻辑同源,可对照理解。
会。本篇只解释视频 01 展示的部分,未展示的扩展与对象目标拆解放在第 2、3 篇,完整讨论见《Chart Trade 项目实战篇·进阶应用》。