MT4与Matlab的DDE实时行情交互实战
🔗

MT4与Matlab的DDE实时行情交互实战

用DDE把报价直送Matlab做策略研究

平台工具 难度 · 进阶 2026-04-25 7 分钟阅读
#MT4#Matlab#DDE#实时行情

为什么要用 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 服务器只提供最新一笔变动数据,不提供历史回放。若你需要多年 tick 级历史做训练,仍应走 CSV 或专用导出 API;若做实时信号原型、绘图验证,DDE 足够。

启用 MT4 的 DDE 服务器与基础概念

在 MT4 客户端中,必须手动开启 DDE:点击“工具”->“选项”->“服务器”选项卡,勾选“启用 DDE 服务器”。未启用前,Matlab 端的 ddeinit 会失败。DDE 通信有两个角色:前端(客户端)是请求或接收数据的程序,本例为 Matlab;后端(服务器)是提供数据的程序,即 MT4。

DDE 标准定义了三种传输模式:客户端轮询请求、客户端请求且服务器通知后发送、服务器主动发送就绪数据。MT4 的 DDE 服务器仅工作在第三种模式——它不等请求、不等确认,只要有新报价就推给已连接的客户端。因此 Matlab 的任务只是:通知 MT4 自己在线、声明需要哪个品种、然后等待数据到达。

在 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 外观满意后即可进入代码编辑阶段。

⚠ 版本注意
较新 Matlab(如 2014a 之后)已移除 dde 系列函数,本文代码仅适用于保留 ddeinit/ddeadv 的旧版 Matlab。新版用户需借助 ActiveX 或第三方中间件替代。

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 不传成交量与多档深度,仅报价级数据。

✦ 延伸价值
借助 Matlab 丰富的统计与机器学习工具箱,你可以用实时 Bid 数组测试滤波、突变检测或自适应止损,再转为 MQL4/5 代码上架。

常见问题

打开 MT4 客户端,点“工具”->“选项”->“服务器”选项卡,勾选“启用 DDE 服务器”并确认。之后 Matlab 用 ddeinit('MT4','QUOTE') 才能连上,否则返回空通道。
2014a 之后 Matlab 移除了 DDE 工具箱。可选方案:1) 使用旧版 Matlab 虚拟机;2) 在 MT4 用 MQL4 写 DLL 通过命名管道/WebSocket 推送;3) 改用 CSV 或 MT5+Python 桥接。
仅最新一笔报价文本,格式为“日/月/年 时:分 买价 卖价”,例如“15/3/16 10:30 1.1234 1.1236”。不含成交量、历史 tick 或多品种批量,需自行在客户端解析。
在输入框回调里先 ddeunadv(channel,旧品种) 断开,再 ddeadv(channel,新品种,'newTick(x)','x',[1 1]) 重连;任一步失败就回退到 UserData 存的老品种,保证链接始终有效。