MT4与Matlab的DDE实时行情交互实战
用DDE把报价直送Matlab做策略研究
为什么要用 DDE 而不是 CSV 文件交换
在 MetaTrader 4 与 Matlab 之间交换数据,最早常见的做法是写 CSV 文件,由 MT4 导出、Matlab 定时读取。这种方法实现简单,但存在明显短板:文件落地到磁盘有 IO 延迟,读取周期难以对齐到每一次报价,且频繁读写占用磁盘资源。作者此前发布过 CSV 方案的文档,但实践中很多场景不可行或不被认可。
DDE(Dynamic Data Exchange,动态数据交换)是 Windows 早期提供的进程间通信机制,允许两个应用程序通过内存直接传递数据,无需文件中转。MT4 内置了 DDE 服务器,仅广播最新价格变动;Matlab 具备完整的 DDE 前后端支持。因此利用 DDE,我们可以把报价直接从 MT4 的 RAM 推送到 Matlab 的 RAM,实现近实时的行情接入,对柱内报价处理尤为可取。
启用 MT4 的 DDE 服务器与基础概念
在 MT4 客户端中,必须手动开启 DDE:点击“工具”->“选项”->“服务器”选项卡,勾选“启用 DDE 服务器”。未启用前,Matlab 端的 ddeinit 会失败。DDE 通信有两个角色:前端(客户端)是请求或接收数据的程序,本例为 Matlab;后端(服务器)是提供数据的程序,即 MT4。
DDE 标准定义了三种传输模式:客户端轮询请求、客户端请求且服务器通知后发送、服务器主动发送就绪数据。MT4 的 DDE 服务器仅工作在第三种模式——它不等请求、不等确认,只要有新报价就推给已连接的客户端。因此 Matlab 的任务只是:通知 MT4 自己在线、声明需要哪个品种、然后等待数据到达。
- service 名称固定为 'MT4'
- topic 可为 'BID'、'ASK'、'QUOTE' 等数据段
- 客户端用 Advisory link(咨询链接)接收主动推送
在 Matlab 中搭建 GUI 与图形对象
Matlab 提供 GUIDE 工具创建图形用户接口。在命令行输入 guide 启动向导,拖入以下控件:一个 Edit Text 输入框(用于填货币对,Tag 设 editPair)、一个 Axes 坐标轴(绘图用,Tag 设 axesChart,Box 设 on,FontSize 设 7,Units 设 pixels)、两个 Static Text(分别 Tag 为 textBid 与 textInfo,左对齐)。保存项目名建议为 DDEs,生成的 m 文件即为 DDEs.m。
Axes 使用 pixels 单位是为了后续按 1 像素对应 1 个报价的比例绘图;FontSize 调小避免标签遮挡。EditText 的 UserData 属性用来存“当前生效品种”,即使界面上被误改也能回退。GUI 外观满意后即可进入代码编辑阶段。
DDE 连接初始化与断开处理
在 DDEs_OpeningFcn 中建立通道:channel = ddeinit('MT4','QUOTE'),再用 ddeadv 建立咨询链接。ddeadv 的 callback 参数传字符串 'newTick(x)',意味着每次报价到达时 Matlab 会 eval 这行代码,调用独立文件 newTick.m 中的函数。格式参数 [1 1] 表示以文本方式传输。
关闭窗口时,在 figure1_CloseRequestFcn 中必须先 ddeunadv 取消链接,再 ddeterm 释放通道,否则 MT4 侧会保留无效连接。下面给出核心片段:
function DDEs_OpeningFcn(hObject, eventdata, handles, varargin)
setappdata(0,'hndls',handles);
channel = ddeinit('MT4','QUOTE');
pair = get(handles.editPair,'UserData');
rc = ddeadv(channel, pair,'newTick(x)','x',[1 1]);
if (rc==1) disp('Connected'); end
handles.chann = channel;
guidata(hObject, handles);
function figure1_CloseRequestFcn(hObject, eventdata, handles)
channel = handles.chann;
pair = get(handles.editPair,'UserData');
ddeunadv(channel,pair);
rc = ddeterm(channel);
if (rc==1) disp('Disconnected'); end
delete(hObject);
由于 callback 只能传字符串而非函数句柄,作者把报价处理函数独立成 newTick.m,反而带来热更新优势:不中断 DDE 即可修改 newTick.m 逻辑。初期 newTick 仅 disp 参数并播放提示音验证通路。
动态切换货币对与容错逻辑
用户在 editPair 输入新品种时,editPair_Callback 需先断开旧链接 ddeunadv,再对新品种 ddeadv。若断开或重连失败,则回退到 UserData 中存的老品种,保证连接不空窗。该机制避免输错名称导致客户端死锁。
function editPair_Callback(hObject, eventdata, handles)
oldPair = get(hObject,'UserData');
newPair = get(hObject,'String');
channel = handles.chann;
disconn = ddeunadv(channel,oldPair);
if (disconn==0)
set(hObject,'String',oldPair);
else
conn = ddeadv(channel, newPair,'newTick(x)','x',[1 1]);
if (conn==1)
set(hObject,'UserData',newPair);
else
ddeadv(channel, oldPair,'newTick(x)','x',[1 1]);
set(hObject,'String',oldPair);
end
end
解析报价并刷新 GUI 显示
MT4 推来的字符串形如 '日/月/年 时:分 买价 卖价'。newTick 中用 sscanf 按 '%i/%i/%i %i:%i %f %f' 拆解,提取服务器时间与 Bid/Ask。由于服务器时间只到分钟,不适合高频区分,故用 Matlab 的 now 取本地毫秒级时间。通过 getappdata(0,'hndls') 拿回 GUI 句柄,将 Bid 写进 textBid,本地时间写进 textInfo。
function newTick(simbols) timeLocal = now; handles = getappdata(0,'hndls'); parts = sscanf(simbols, '%i/%i/%i %i:%i %f %f'); timeServerVect = parts(1:5)'; timeServerVect = [timeServerVect 00]; timeServer = datenum(timeServerVect); Bid = parts(6); Ask = parts(7); set(handles.textBid,'String',['Bid:' num2str(Bid)]); set(handles.textInfo,'String', datestr(timeLocal));
累积报价数组与实时绘图
为绘出连续曲线,newTick 把每笔报价存到 root 的 appdata 'data' 结构,含 name 与 array 两字段。array 为 N×3 矩阵:列1本地时间、列2服务器时间、列3买价。若品种切换则清空重来。绘图时取 axesChart 宽度(像素),若点数少于像素则全画,否则仅画最近一段,用 stairs 画阶梯线并叠加当前 Bid 水平线。
ticks = getappdata(0,'data'); if ~strcmp(ticks.name,GUIpairName) ticks.name = GUIpairName; ticks.array = []; setappdata(0,'data',ticks); end ticks.array = [ticks.array; timeLocal timeServer Bid]; setappdata(0,'data',ticks); chartSize = get(handles.axesChart,'Position'); chartSize = chartSize(3); lenArray = size(ticks.array,1); set(handles.axesChart, 'NextPlot','replace'); if (chartSize >= lenArray) stairs(handles.axesChart,ticks.array(:,3)); else stairs(handles.axesChart,ticks.array(lenArray-chartSize+1:lenArray,3)); end set(handles.axesChart,'XLim',[1 chartSize]); plot(handles.axesChart,[1 chartSize], [Bid Bid],'m');
将行情数据导出为文本文件
在 GUI 加一个 Push Button(Tag pushSave),其回调让用户选路径,文件名自动含品种、日期、时间。数据矩阵由本地时间字符串、空格分隔列、Bid 字符串拼成,用 dlmwrite 存盘。这样用户可把实时收集的 tick 拿去离线分析或训练。
function pushSave_Callback(hObject, eventdata, handles) date = datestr(now,'yyyy-mm-dd'); time = datestr(now,'HH-MM-SS'); name = get(handles.editPair,'UserData'); template = [name '@' date '@' time]; [userName, userPath] = uiputfile([template '.txt']); if userName~=0 ticks = getappdata(0,'data'); timesStr = datestr(ticks.array(:,1)); bidStr = num2str(ticks.array(:,3)); delimStr(1:length(bidStr)) =' '; matrix=[timesStr delimStr' bidStr]; dlmwrite([userPath userName], matrix, ''); end
方案对比与工程落地建议
对比 CSV:DDE 延迟低、代码轻,但仅限 MT4 且需老版 Matlab;CSV 兼容新环境但慢。对比 MT5:MT5 无原生 DDE,需用 MQL5 写 DLL 或 socket 桥接。实际研究中,可用本文原型快速验证指标逻辑,再把算法移植到 EA。注意 MT4 DDE 不传成交量与多档深度,仅报价级数据。