Взаимодействие между MetaTrader 4 и Matlab посредством DDE

Автор: lexy Вторник, Сентябрь 9th, 2014 Нет комментариев

Рубрика: Разное

Мной уже размещена здесь статья об обмене данными между MetaTrader 4 и Matlab посредством CSV-файлов (MT4 <-CSV->Matlab). Однако подход, описанный в статье, во многих случаях нерационален, а во многих — просто неприменим.

Поддерживаемый в MT4 механизм DDE (Dynamic data exchange) позволяет передавать данные из приложения в приложение непосредственно через RAM компьютера. Matlab обладает всей полнотой функций для реализации как клиентской, так и серверной части DDE, и нам хотелось бы воспользоваться этой возможностью.

DDE-сервер MT4 предоставляет только тиковые и только последние данные, но даже с такими ограничениями DDE-обмен предпочтительнее, например, при работе с котировками внутри баров.

Как и в статье “MT4 <-CSV->Matlab” я буду описывать последовательность создания инструмента для организации обмена.

Не забудьте разрешить передачу по DDE на вкладке “ Сервис -> Настройки -> Сервер” в вашем терминале MT4 и начнём.

 

Сначала о DDE

Итак, в организации обмена данными по DDE существуют две стороны, между которыми устанавливается связь – сервер и клиент. Клиент – это приложение, запрашивающее данные (в нашем случае — Matlab), сервер – приложение, обладающее этими данными (MT4).

Передача данных от сервера к клиенту в DDE возможна трёмя способами:
- по запросу клиента,
- по запросу клиента, после получения от сервера уведомления о готовности данных,
- по готовности данных.

DDE сервер MT4 функционирует только в одном (третьем) режиме и отправляет данные клиенту, как только они готовы, не ожидая запросов, подтверждений и прочей чепухи =) Поэтому задача Matlab состоит в том, чтобы уведомить MT4, что у него есть клиент, сообщить какие данные требуются и ждать, пока данные поступят.

При поступлении данных мы просто отобразим их на графике.

Создание GUI

В среде Matlab существует возможность создания графического пользовательского интерфейса (GUI). Создав GUI, мы объединим в нём средства управления, график цены и текстовую информацию, которую нам покажется необходимым вывести.

Подробнее построение GUI описано в разделе 3 статьи “MT4 <-CSV->Matlab”, поэтому здесь я лишь упомяну консольную команду “guide”, запускающую мастер создания GUI, и приведу список графических объектов, которые нам понадобятся.

Итак, нам необходимы:
- поле ввода “Edit Text” для ввода имени валютной пары;
- оси “Axes” для вывода графика;
- два поля вывода текста “Static Text” для вывода точного значения последней котировки и чего-нибудь ещё.

Вот как я разместил эти объекты на бланке GUI:

Установите свойства графических объектов следующим образом:

Для Axes:
Tag = axesChart (сюда будем выводить график);
Box = on (обводит поле графика полным прямоугольником, off – только слева и снизу);
FontSize = 7 (размер, установленный по умолчанию – просто огромный);
Units = pixels (понадобится при построении графика для установки масштаба 1:1).

Для EditText:
Tag = editPair (в это поле будем вводить название валютной пары).

Для StaticText под полем EditText:
Tag = textBid (сюда мы будем выводить точное значение последней котировки);
HorizontalAlignment = left (не принципиально, можете оставить ‘center’).

Для StaticText в самом низу бланка:
Tag = textInfo;
HorizontalAlignment = left.

Теперь можно нажать RUN.
Я назвал свой проект «DDEs», и если вы хотите, чтобы у вашей версии не было расхождения с моей версией, — назовите свой проект также.
Если внешний вид GUI вас устраивает, и m-файл готов к редактированию, — приступим к созданию DDE-клиента.

Инициализация соединения

В первую очередь необходимо организовать канал связи с сервером при запуске нашего GUI и озаботиться разрывом соединения при закрытии интерфейса.
В Matlab инициализация DDE- соединения осуществляется функцией: channel = ddeinit(‘service’,'topic’);
‘service’ здесь – имя DDE-сервера (‘MT4’)
‘topic’ – имя раздела данных. В нашем случае может принимать значения ‘BID’, ‘ASK’, ‘QUOTE’ и т.п.
Функция возвращает дескриптор проинициализированного канала, используемый для дальнейших обращений(conversation) к DDE-серверу.

Необходимо также задать способ обмена. В Matlab способ обмена, поддерживаемый MT4, называется “Advisory link” и инициализируется функцией: rc = ddeadv(channel,’item’,'callback’,'upmtx’,format);,
Где channel – дескриптор проинициализированного канала,
‘item’ – данные, которые нас интересуют, т.е. символьное имя валютной пары,
‘callback’ – строка для выполнения при приходе данных от сервера,
‘upmtx’ – символьное имя переменной, в которую занесутся данные от сервера,
format – массив из двух флагов, определяющий формат отправленных данных.
Функция ddeadv возвращает “1” в случае успеха и “0” в другом случае.

Обратите внимание, что в качестве параметра ‘callback’ указывается не дескриптор функции, а символьное выражение. Фактически будет выполнена функция “eval”, исполняющая строку, словно она набрана в консоли. С этой особенностью связано следующее затруднение:
по приходу новой котировки нам необходимо будет выполнить большую функцию приёма новой котировки. Причём хотелось бы передать в эту функцию структуру дескрипторов “handles”, с помощью которой мы намерены получать доступ к графическим объектам GUI. Но я не нашёл ни способов передать дескриптор структуры handles в исполняемую строку, ни просто вызвать функцию, расположенную в m-файле, описывающем GUI.
Всё это привело к тому, что я был вынужден вынести функцию принятия новой котировки в отдельный m-файл и вызывать уже её, как обычную функцию Matlab. Правда, неудобство оказалось достоинством, когда я обнаружил, что могу редактировать функцию-обработчик не прерывая работы DDE-клиента.

Итак, первым делом создадим отдельную функцию-обработчик, просто выводящую полученные ею данные в консоль.

function newTick(simbols)
% обработка нового тика
disp(simbols); % вывести аргумент в консоль
song = wavread(‘C:\WINDOWS\Media\Windows XP — пуск.wav’); % прочитать звук
wavplay(song,40000); % играть звук с частотой дискретизации 40kHz

Приведённый пример функции проигрывает ещё файл ’C:\WINDOWS\Media\Windows XP — пуск.wav’ при поступлении каждой новой котировки. Сохраним текст функции в рабочую папку MATLAB под именем «newTick.m».

Теперь редактируем m-файл, описывающий поведение нашего GUI. В функцию DDEs_OpeningFcn добавим инициализацию соединения, а в функцию figure1_CloseRequestFcn - деинициализацию.
(Для добавления функции CloseRequestFcn в m-файл, — в GUI-эдиторе необходимо выполнить: View -> View Callbacks -> CloseRequestFcn).

% — Executes just before DDEs is made visible.
function DDEs_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject handle to figure
% eventdata reserved — to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% varargin command line arguments to DDEs (see VARARGIN)

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; % сохранить ID канала в handles

% Choose default command line output for DDEs
handles.output = hObject;
% Update handles structure
guidata(hObject, handles);
% UIWAIT makes DDEs wait for user response (see UIRESUME)
% uiwait(handles.figure1);

% — Executes when user attempts to close figure1.
function figure1_CloseRequestFcn(hObject, eventdata, handles)
% hObject handle to figure1 (see GCBO)
% eventdata reserved — to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)

channel = handles.chann; % получить ID канала из handles
pair = get(handles.editPair,’UserData’); % прочитать имя инструмента
ddeunadv(channel,pair); % разорвать связь
rc = ddeterm(channel); % деинициализация
if (rc==1) % если всё ОК
disp(‘Disconnected’); % сообщить в консоль
end

% Hint: delete(hObject) closes the figure
delete(hObject);

% — Executes during object creation, after setting all properties.
function editPair_CreateFcn(hObject, eventdata, handles)
% hObject handle to editPair (see GCBO)
% eventdata reserved — to be defined in a future version of MATLAB
% handles empty — handles not created until after all CreateFcns called

set(hObject, ‘String’, ‘EURUSD’); % Записать имя инструмента в поле ввода
set(hObject, ‘UserData’, ‘EURUSD’); % И в UserData поля ввода — записать

% Hint: edit controls usually have a white background on Windows.
% See ISPC and COMPUTER.
if ispc && isequal(get(hObject,’BackgroundColor’), get(0,’defaultUicontrolBackgroundColor’))
set(hObject,’BackgroundColor’,'white’);
end

Здесь я привёл полные тексты изменяемых функций, вместе с текстом, заложенным в пустые заготовки функций разработчиками Matlab.

Последний блок реализует запись имени инструмента в соответствующее поле до запуска GUI. Запись дублируется в свойстве ‘UserData’. Копию в ‘UserData’ мы будем использовать всегда, а отображаемое в поле имя (‘String’) — только при попытке пользователя сменить инструмент. Если, пользователь ошибся при наборе и в ‘String’ записано неверное имя, — мы будем возращаться к имени, хранящемся ‘UserData’.

Следующий код реализует функцию смены имени инструмента пользователем:

function editPair_Callback(hObject, eventdata, handles)
% hObject handle to editPair (see GCBO)
% eventdata reserved — to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)

oldPair = get(hObject,’UserData’); % имя бывшего инструмента
newPair = get(hObject,’String’); % имя нового инструмента
channel = handles.chann; % получить ID канала

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

% Hints: get(hObject,’String’) returns contents of editPair as text
% str2double(get(hObject,’String’)) returns contents of editPair as a double

Приём тиков

Будем считать, что связь установлена и при при приходе нового тика вызывается функция «newTick(x)», отпечатывающая полученный от MT4 аргумент в консоль. Для начала отобразим последнюю принятую котировку в соответствующей строке нашего GUI.

Для этого нам необходимо иметь структуру дескрипторов графических объектов GUI — handles в распоряжении функции «newTick». Воспользуемся функцией setappdata(h,name,value), сохраняющей данные в область приложения. В качестве ID приложения укажем «0″. Это дескриптор объекта root Matlab, он неизменен и мы всегда можем его знать.

Добавим строку «setappdata(0,’hndls’,handles);» сразу после заголовка функции »DDEs_OpeningFcn»:

function DDEs_OpeningFcn(hObject, eventdata, handles, varargin)
setappdata(0,’hndls’,handles); %

Теперь в функции »newTick» мы сможем извлечь handles функцией value = getappdata(h,name), указав «0″ в качестве аргумента «h». Тогда из функции »newTick» мы сможем управлять объектами GUI.

Далее мы преобразуем строковый аргумент, переданный в функцию от DDE-сервера и выведем значение Bid в GUI. Кроме того, мы определим локальное время получения котировки и тоже отобразим его, но в информационной строке GUI. Локальное время необходимо потому, что DDE-сервер передаёт время с точностью до минут, что неприемлемо для работы с тиками. Функция now возвращает локальное время с точностью до долей миллисекунд, поэтому беспокоиться от том, что разные тики будут иметь одно и то же время — не будем.Время сервера тоже вычленим из строки, полученной от DDE-сервера, и преобразуем в формат времени Matlab.

Вот пример функции »newTick»:

function newTick(simbols)
% ОБРАБОТКА НОВОГО ТИКА

timeLocal = now; % Узнать точное локальное время
handles = getappdata(0,’hndls’); % Получить handles из root

% disp(simbols); % вывести аргумент в консоль (закоментировано)
song = wavread(‘C:\WINDOWS\Media\Windows XP — пуск.wav’); %прочитать звук
wavplay(song,40000); % играть звук с частотой дискретизации 40kHz

set(handles.textInfo,’String’, datestr(timeLocal)); % вывести локальное время в GUI

% — преобразование полученной от MT4 строки —
parts = sscanf(simbols, ‘%i/%i/%i %i:%i %f %f’ ); % разбор строки в соответствии
%с форматом: int/int/int int:int float float
timeServerVect = parts(1:5); % вычленить время
timeServerVect = timeServerVect’; % транспонировать (столбец в строку)
timeServerVect = [timeServerVect 00]; % добавить секунды
timeServer = datenum(timeServerVect); % перевести в формат времени Matlab
Bid = parts(6); % вычленить Bid
Ask = parts(7); % вычленить Ask
% — конец преобразования —

set(handles.textBid,’String’,['Bid: ' num2str(Bid)]); % Вывести Bid в GUI

Построение тикового графика

Вот продолжение функции «newTick», начатой чуть выше. Код снабжён подробными комментариями и, я думаю, вам не составит труда в нём разобраться.
Поясню только, что массив котировок Bid, также как и handles, хранится в пространстве объекта root, но под именем «data». Хранимые данные представляют собой структуру, состоящую из двух полей:
data.name — символьное имя валютной пары;
data.array — собственно массив котировок.

В функции «newTick» эти данные фигурируют под именем «ticks», и поля структуры имеют имена ticks.name и ticks.array соответственно.

ticks.array - представляет собой массив, состоящий из трёх столбцов:
- локальное время в формате времени Matlab (с точностью, обеспечиваемой форматом времени Matlab [микросекунды]);
- время сервера в формате времени Matlab (с точностью до минут);
- Bid.

Функция »newTick» очищает массив котировок, если имя рабочего инструмента в поле «editPair» изменилось и поступают котировки другого инструмента. Если НЕ изменилось — дописываются строки к существующему массиву.

Блок работы с графиком определяет параметры окна графика axesChart (размеры и позицию) и вычленяет из них ширину окна в пикселях. Это нужно для того, чтобы установить горизонтальный масштаб отображения — одна котировка на пиксель.
Если данных меньше, чем ширина окна в пикселях, — график отрисовывается целиком, если больше — отрисовываются только последние, умещающиеся на графике, данные.

% — работа с массивом котировок —
GUIpairName = get(handles.editPair, ‘UserData’); % имя инструмента
if (~isappdata(0,’data’)) % если данных не было
ticks.name = GUIpairName; % сформировать поле имени
ticks.array = []; % сформировать поле — пустой массив
setappdata(0,’data’,ticks); % записать данные в root
end
ticks = getappdata(0,’data’); % извлечь данные
if ~strcmp(ticks.name,GUIpairName) % если имя изменилось
ticks.name = GUIpairName; % сформировать поле имени
ticks.array = []; % сформировать поле — пустой массив
setappdata(0,’data’,ticks); % записать данные в root
end
ticks.array = [ticks.array; timeLocal timeServer Bid]; % добавить строку
% с новыми данными к существующему массиву данных
setappdata(0,’data’,ticks); % записать данные в root
% — конец работы с массивом —

% — работа с графиком —
chartSize = get(handles.axesChart,’Position’);% получить размеры окна графика
chartSize = chartSize(3); % вычленить ширину окна графика
lenArray = size(ticks.array); % получить размеры массива данных
lenArray = lenArray(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]); % установить масштаб — один отсчёт
% в одном пикселе ширины
set(handles.axesChart, ‘NextPlot’,'add’); % режим отрисовки — добавление
plot(handles.axesChart,[1 chartSize], [Bid Bid],’m');% нарисовать горизонталь Bid

Сохранение данных в файл

Последняя функция, которую я опишу — это сохранение тиковых данных в файл по запросу пользователя.
Сохранение будем выполнять при нажатии кнопки, поэтому добавьте объект «Push Button» на бланк GUI с помощью редактора.

Установите свойства объекта: Tag = pushSave, String = Save.

При нажатии кнопки «M-file Editor» заготовка функции pushSave_Callback автоматически будет добавлена в конец файла «DDEs.m».

Вот полный текст функции, выполняющей сохранение:

 

% — Executes on button press in pushSave.
function pushSave_Callback(hObject, eventdata, handles)
% hObject handle to pushSave (see GCBO)
% eventdata reserved — to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
date = datestr(now,’yyyy-mm-dd’); % узнать дату (string)
time = datestr(now,’HH-MM-SS’) % узнать время (string)
name = get(handles.editPair,’UserData’);% узнать имя инструмента(string)
template = [name '@' date '@' time]; % сформировать имя файла
[userName, userPath] = uiputfile([template '.txt']); % получить имя и путь от юзера
if userName~=0 % если не нажали отмена
ticks = getappdata(0,’data’); % полчить данные из root

timesStr = datestr(ticks.array(:,1)); % сформировать string-массив
% времени и даты
bidStr = num2str(ticks.array(:,3)); % сформировать string-массив BID
delimStr(1:length(bidStr)) =’ ‘ ; % сформировать «столбец» разделитель
% точнее строку, которая будет транспонироваться в столбец
matrix=[timesStr delimStr' bidStr]; % слить все Str в одну матрицу
dlmwrite([userPath userName], matrix, »);% записать матрицу в файл
end

 

Функция подготавливает имя файла, состоящее из локального времени, даты и символьного имени инструмента.
При осуществлении записи предварительно подготавливаются три матрицы символов:
- timesStr — локальные дата и время, соответствующие котировкам;
- delimStr — разделители;
- bidStr — столбец BID.
Затем всё объединяется в единую матрицу.

delimStr — представляет собой строку из пробелов, длина которой равна длине столбца BID. При объединении — строка delimStr транспонируется в столбец, и отделяет столбец котировок от времени.

 

 

 

Заключение

Я рассчитываю, что описанный здесь метод позволит вам использовать всё богатство математических функций Matlab при разработке и тестировании стратегий автоматической торговли.

 

 

Прикрепленные файлы:
 ddeadv.zip (1.4 Kb)
 work.rar (7.6 Kb)
 work.zip (7.7 Kb)
Источник: mql4.com

Оставить комментарий

Чтобы оставлять комментарии Вы должны быть авторизованы.

Похожие посты