Interação entre o MetaTrader 4 e o Matlab através de arquivos CSV
Introdução
O ambiente de energia computacional do Matlab é conhecido por ser consideravelmente superior à de qualquer linguagem de programação incluindo MQL4. A ampla variedade de funções matemáticas fornecidas pelo Matlab permite realizar cálculos computacionais complexos negligenciando totalmente a base teórica das operações realizadas.
No entanto, a interação em tempo real entre um terminal de trading e o Matlab representa uma tarefa não trivial. Neste artigo, sugiro uma forma de organizar a troca de dados entre o MetaTrader 4 e o Matlab através de arquivos CSV.
1. Interfuncionamento
Suponha que, na entrada de cada nova barra, o MetaTrader 4 deve enviar dados sobre as últimas 100 barras para o Matlab e ser respondido com seus resultados de processamento.
Para resolver este problema, vamos precisar criar um indicador no MetaTrader 4 que iria escrever dados em um arquivo de texto e ler os resultados do processamento de outro arquivo de texto criado pelo Matlab.
O MetaTrader 4 deve formar seu próprio arquivo de dados na entrada de cada nova barra. O MetaTrader 4 também deve tentar ler os resultados em cada crédito. A fim de não ler o resultado antes do Matlab atualiza-lo, teremos excluído o arquivo que contém o resultado antes de formar o nosso arquivo de saída. Neste caso, a tentativa de leitura terá sucesso somente após o Matlab terminar seu cálculo e formar um novo arquivo.
O Matlab deve analisar os atributos do arquivo criados no MetaTrader 4 a cada segundo e iniciar o processamento quando seu tempo de criação mudar. Após o processamento tiver terminado, o arquivo excluído pelo MetaTrader 4 antes do início da gravação de dados é recriado. O MetaTrader 4 o exclui com êxito, carrega novos dados e aguarda resposta.
2. Formação de um arquivo de dados de saída
Há muitos artigos dedicados a salvar dados como arquivos, por isso, neste artigo, não vou focar neste ponto. Apenas vou deixar claro que escrevemos dados em 7 colunas: “DATA”, “TEMPO”, “ALTA”, “BAIXA”, “FECHAMENTO”, “ABERTURA”, “VOLUME”. O caractere de separação é “;”. A prioridade da barra é das mais antigas para as posteriores, isto é, a linha que contém as características da barra de zero deve ser registrada por último. O arquivo será fornecido com uma linha contendo os nomes das colunas. O nome do arquivo será composto do nome do símbolo e do período de tempo.
#property indicator_chart_window extern int length = 100; // The amount of bars sent to be processed double ExtMap[]; // Chart buffer string nameData; int init() { nameData = Symbol()+".txt"; // name of the data file to be sent return(0); } int start() { static int old_bars = 0; // remember the amount of bars already known if (old_bars != Bars) // if a new bar is received { write_data(); // write the data file } old_bars = Bars; // remember how many bars are known return(0); } //+------------------------------------------------------------------+ void write_data() { int handle; handle = FileOpen(nameData, FILE_CSV|FILE_WRITE,';'); if(handle < 1) { Comment("Creation of "+nameData+" failed. Error #", GetLastError()); return(0); } FileWrite(handle, ServerAddress(), Symbol(), Period()); // heading FileWrite(handle, "DATE","TIME","HIGH","LOW","CLOSE","OPEN","VOLUME"); // heading int i; for (i=length-1; i>=0; i--) { FileWrite(handle, TimeToStr(Time[i], TIME_DATE), TimeToStr(Time[i], TIME_SECONDS), High[i], Low[i], Close[i], Open[i], Volume[i]); } FileClose(handle); Comment("File "+nameData+" has been created. "+TimeToStr(TimeCurrent(), TIME_SECONDS) ); return(0); }
Não vamos precisar de todos estes dados, é claro, mas é sempre melhor ter um arquivo significativo do que apenas um conjunto de colunas com valores desconhecidos.
3. Criação da Interface Gráfica do Usuário (GUI)
O arquivo está pronto. Vamos iniciar o Matlab.
Deveríamos desenvolver uma aplicação que iria ler dados de texto do arquivo, processar e gravar os resultados em outro arquivo. Teremos que criar uma GUI para especificar o nome do arquivo, visualizar gráficos e iniciar o processamento. Vamos começar agora.
Para criar a GUI, vamos iniciar o "Guia de Início Rápido", digitando "guia" no console ou pressionando no painel principal do Matlab. Na caixa de diálogo exibida, selecione "Criar Nova GUI" -> "GUI em branco (padrão)". Agora podemos ver a interface para a criação de uma GUI com um formulário vazio. Neste formulário, vamos colocar os seguintes objetos: "Editar Texto", "Pressionar Botão", "Texto estático", "Eixos", "Pressionar Botão". O resultado é mais ou menos assim:
Agora devemos chamar o construtor de propriedade visual para cada objeto e definir as propriedades da seguinte forma:
Texto estático: Alinhamento Horizontal - esquerda, Tag - textinfo, String - Info.
Editar texto: Alinhamento Horizontal - esquerda, Tag - editarCaminho, String - Selecionar caminho.
Pressionar botão: Tag - pressionarNavegação, String - Navegação.
Eixos: Caixa - ligada, NomedaFonte - MS Sans Serif, Tamanho da fonte - 8, Tag - eixosdoGráfico.
Pressionar botão: Tag - pressionarIniciar, String - Iniciar.
Ao alterar a propriedade da Tag, selecionamos um nome exclusivo para cada objeto. Ao alterar outros, modificamos a aparência da Tag.
Quando tudo estiver pronto, vamos iniciar a interface pressionando "Executar", confirme o salvamento da interface e do arquivo-M, dê um nome (por exemplo, "De Para"), e pressione "Salvar". Depois disso, GUI será lançada e aparece como aparece durante seu trabalho. O Matlab gera o arquivo-M para ser a base do nosso programa futuro e o abre no editor incorporado.
Se a aparência não lhe adapta por algum motivo, feche a GUI e corrija o arranjo do objeto usando o editor. Meu distributivo, por exemplo, não exibiu MS Sans Serif corretamente. Então eu tive que mudar para "Sans Serif".
4. Construção da Interface do Usuário
O comportamento da interface pode ser programado no Editor do arquivo-M utilizando a linguagem Matlab. O programa esqueleto gerado pelo Matlab representa uma lista de funções a serem chamadas pelo usuário ao trabalhar com os objetos de interface. As funções estão vazias, então a GUI ainda não faz nada. É nossa tarefa preencher as funções com os conteúdos necessários.
4.1 Programação do Botão de Navegação
Primeiramente, precisamos de acesso a um arquivo gerado pelo MetaTrader 4, por isso vamos começar chamando a função pressionando "Navegação".
O nome da função chamada pressionando o botão consiste no nome do botão (definido pela propriedade "Tag") e pós-fixado "_Callback". Vamos encontrar a função "pushBrowse_Callback" no arquivo de texto ou apenar pressionar "Mostrar funções" na barra de ferramentas e selecionar "pushBrowse_Callback" na lista.
A sintaxe da linguagem de programação Matlab difere das regras convencionais de codificação nas linguagens C e similares. Particularmente, não há necessidade de marcar o corpo da função com chaves ou especificar o tipo de dados a serem passados para a função, índices da matriz (vetor) iniciam com um, e o caractere de comentário é "%". Assim, todo o texto verde acima não é um programa, mas um comentário feito pelos desenvolvedores do Matlab para sermos capazes de compreender o caso.
Teremos de criar um diálogo para introduzir o nome completo do arquivo. Para isso, vamos usar a função "uigetfile":
% --- Executes on button press in pushBrowse. function pushBrowse_Callback(hObject, eventdata, handles) [fileName, filePath] = uigetfile('*.txt'); % receive and the path from the user if fileName==0 % if it is canceled fileName=''; % create an empty name filePath=''; % create an empty path end fullname = [filePath fileName] % form a new name set(handles.editPath,'String', fullname); % write in the editPath
"puxadores" aqui é uma estrutura que armazena descritores de todos os objetos na nossa GUI incluindo o do formulário em que o colocamos. A estrutura é passada de uma função para outra e permite o acesso aos objetos.
"hObject" é uma descrição do objeto que chamou a função.
"Set" ajuda a definir o valor do objeto por um determinado valor e tem a seguinte sintaxe: set(object_descriptor, object_property_name, property_value).
Você pode encontrar o valor das propriedades do objeto usando a seguinte função: property_value = get(object_descriptor, object_descriptor_name).
Mas não se esqueça que o nome é um valor do tipo string, por isso deve ser entre aspas simples.
Há uma última coisa que temos que saber sobre objetos e suas propriedades. O formulário, onde colocamos os elementos da GUI, é em si um objeto colocado no objeto "raiz" (é seu descendente). Ele também tem um conjunto de propriedades que podem ser modificadas. As propriedades podem ser visualizadas usando a ferramenta chamada "Editor de objeto" para ser chamada a partir da barra de ferramentas principal do editor de interface. Objeto "raiz", como o termo sugere, é a raiz da hierarquia dos objetos gráficos e não tem ascendência.
Agora vamos verificar o que temos como resultado. Vamos agora começar a nossa GUI pressionando Executar na barra de ferramentas principal do Editor de arquivo-M. Tente clicar no Navegador e selecionar nosso arquivo. Está funcionando? Em seguida, feche a GUI que está em operação e prossiga.
4.2 Programação do Botão Iniciar, Desenho Gráfico
Agora vamos atribuir o botão Iniciar com a chamada da função que iria ler os dados do arquivo e mostrá-los em um gráfico.
Primeiramente, vamos criar a função. Vamos precisar da estrutura dos descritores dos objetos "puxadores'' como entradas. Tendo acesso aos objetos, seremos capazes de lê-los e definir suas propriedades.
% data reading, chart drawing, processing, storage function process(handles) fullname = get(handles.editPath, 'String'); % read the name from editPath data = dlmread(fullname, ';', 2, 2); % read the matrix from file info = ['Last update: ' datestr(now)]; % form an informative message set(handles.textInfo, 'String',info); % write info into the status bar high = data(:,1); % it is now high where the first column of the data matrix is low = data(:,2); % d low -- the second close = data(:,3); % --/-- open = data(:,4); % len = length(open); % the amount of elements in open axes(handles.axesChart); % make the axes active hold off; % clean axes off before adding a new chart candle(high, low, close, open); % draw candlesticks (in the current axes) set(handles.axesChart,'XLim',[1 len]); % set limits for charting
Algumas explicações:
“dlmread” lê os dados do arquivo de texto com separadores e possui a seguinte sintaxe: dlmread(full_file_name, separator, skip_strings, skip_columns);
“comprimento(qqq)” – o lado maior da matriz qqq;
”agora” - hora e data atual;
“datestr(now)” - transforma data e hora em um texto;
Você também deve saber que o Matlab fornece uma informação útil enorme de ajuda com a teoria e com os exemplos.
Vamos colocar a nossa função no final do programa (será mais fácil de encontrá-la) e adicionar sua chamada na “pushStart_Callback”:
% --- Executes on button press in pushStart. function pushStart_Callback(hObject, eventdata, handles) process(handles);
Lance isto usando "Executar", selecione um arquivo, pressione "Iniciar" e aprecie o resultado.
4.3 Salvando o Caminho em um Arquivo
Tudo está bem agora, mas é um pouco irritante clicar permanentemente com o mouse para selecionar um arquivo depois de ter pressionado "Navegação". Vamos tentar salvar o caminho uma vez selecionado.
Vamos começar lendo. O nome de um arquivo que armazena o caminho será composto pelo nome da GUI e sufixo “_saveparam” e terá extensão ".mat".
A função “FromTo_OpeningFcn” é executada diretamente após a criação do formulário da GUI. Nós vamos adicionar lá a tentativa de ler o caminho do arquivo. Se a tentativa falhar, será utilizado o valor padrão.
% --- Executes just before FromTo is made visible. function FromTo_OpeningFcn(hObject, eventdata, handles, varargin) guiName = get(handles.figure1, 'Name'); % get the name of our GUI name = [guiName '_saveparam.mat'] % define the file name h=fopen(name); % try to open the file if h==-1 % if the file does not open path='D:\'; % the default value else load(name); % read the file fclose(h); % close the file end set(handles.editPath,'String', path); % write the name into object "editPath"
Outras strings da função “FromTo_OpeningFcn” serão mantidas inalteradas.
Vamos modificar a função “pushBrowse_Callback” como se segue:
% --- Executes on button press in pushBrowse. function pushBrowse_Callback(hObject, eventdata, handles) path = get(handles.editPath,'String'); % read the path from object editPath [partPath, partName, partExt] = fileparts(path); % divide the path into parts template = [partPath '\*.txt']; % create a template of parts [userName, userPath] = uigetfile(template); % get the user name and the path from the user if userName~=0 % if "Cancel" is not pressed path = [userPath userName]; % reassemble the path end set(handles.editPath,'String', path); % write the path into object "editPath" guiName = get(handles.figure1, 'Name'); % get to know the name of our GUI save([guiName '_saveparam.mat'], 'path'); % save the path
4.4 Processamento de dados
Como um processo exemplar, vamos interpolar a coluna "ABERTURA" por uma função polinomial de quarta ordem.
Vamos adicionar o seguinte código no final da nossa função, "process":
fitPoly2 = fit((1:len)',open,'poly4'); % get the polynomial formula fresult = fitPoly2(1:len); % calculate Y values for X=(from 1 to len) hold on; % a new chart has been added to the old one stairs(fresult,'r'); % plot stepwise by color - 'r'- red
Vamos tentar lançar e pressionar "Iniciar".
Se você tem aproximadamente o mesmo resultado como mostrado acima, é hora de começar a salvar os dados como um arquivo.
4.5 Salvar dados como um arquivo
Salvar dados não é mais complicado que lê-los. A única "sutileza" é que o vetor "fResult" deve ser contado para baixo, isto é, a partir do mais recente para o primeiro. Isto é feito para simplificar a leitura do arquivo no MetaTrader 4, a partir da barra zero até o final do arquivo.
Vamos complementar a função "process" com o seguinte código:
[pathstr,name,ext,versn] = fileparts(fullname); % divide the full name % of the file into parts newName = [pathstr '\' name '_result' ext]; % re-compose the new file name fresult = flipud(fresult); % turn vector fresult dlmwrite(newName, fresult); % write in t% function called by the timer function checktime(obj, event, handles) set(handles.textInfo,'String',datestr(now));he file
Agora, por favor, certifique-se de que o arquivo que contém o resultado foi criado, localizado no mesmo lugar onde há o arquivo inicial e possui o mesmo nome complementado pelo sufixo "_result".
4.6 Controle do temporizador
Esta é a parte mais difícil do trabalho. Teremos que criar um temporizador que verifique o arquivo de formação do tempo do MetaTrader 4 a cada segundo. Se o tempo mudar, a função "process" deve ser lançada. O temporizador para-inicia será executado utilizando "Iniciar". Quando a GUI abrir, eliminaremos todos os temporizadores criados anteriormente.
Vamos criar um temporizador colocando o seguinte código na função “FromTo_OpeningFcn”:
timers = timerfind; % find timers if ~isempty(timers) % if timers are available delete(timers); % delete all timers end handles.t = timer('TimerFcn',{@checktime, handles},'ExecutionMode','fixedRate','Period',1.0,'UserData', 'NONE');
O código acima deve ser inserido imediatamente após a nossa inserção anterior nesta função, isto é, antes das strings “handles.output = hObject;” e “guidata(hObject, handles);”.
Ao executar este código, o Matlab, imediatamente após a criação da GUI, irá verificar se há disponibilidade de temporizadores, eliminando os já existentes e criando um novo. O temporizador irá chamar a função “checktime” cada segundo e passará a lista de descritores "alças" na função. Além das "alças", o temporizador passará seu próprio descritor para a função, bem como a estrutura que contém a duração da chamada e da razão. Não podemos influenciar isto, mas temos que considerar quando a codificação da função for chamada pelo temporizador.
Você pode localizar a própria função quando quiser. Deixe que ela própria escreva na barra de status do Matlab o momento em que foi chamada:
Na sua criação, o temporizador é parado, agora devemos lançá-lo. Vamos encontrar a função “pushStart_Callback”. Vamos comentar chamando de 'process(handles)' colocado nele e escrever a gestão do temporizador para ele:
% --- Executes on button press in pushStart. function pushStart_Callback(hObject, eventdata, handles) % process(handles); statusT = get(handles.t, 'Running'); % Get to know the timer status if strcmp(statusT,'on') % If it is enabled - stop(handles.t); % disable it set(hObject,'ForegroundColor','b'); % change color of pushStart marking set(hObject,'String',['Start' datestr(now)]); % change the button top marking end if strcmp(statusT,'off') % If it is disabled - start(handles.t); % enable it set(hObject,'ForegroundColor','r');% change color of pushStart marking set(hObject,'String',['Stop' datestr(now)]); % change the button top marking end
Agora vamos verificar como tudo funciona. Vamos tentar ativar e desativar o temporizador utilizando "Iniciar". Se o temporizador estiver ativado, o relógio acima do caminho do campo de entrada deve funcionar.
Seria mais correto eliminar o temporizador utilizando o botão "X" no fechamento da GUI. Se você quiser fazer isso, adicione-o
stop(handles.t) ; % stop the timer delete(handles.t); % delete the timer
no início da função “figure1_CloseRequestFcn”. Esta função será chamada no fechamento da GUI. Você pode acessá-la a partir do editor da GUI:
Mas, por favor, levar em consideração que, agora, se você pressionar "Executar" do editor sem ter fechado a GUI operacional, o temporizador antigo não será excluído ao criar o novo. E da próxima vez vai haver mais um criado, etc. Você pode lidar com temporizadores "desestabilizados" usando o comando "delete(timerfind)" do console Matlab.
Agora, se tudo estiver funcionando bem, criaremos uma função para verificar o tempo da última modificação do arquivo do MetaTrader 4:
% function to be called by the timer function checktime(obj, event, handles) filename = get(handles.editPath, 'String'); % get to know the file name fileInfo = dir(filename); % get info about the file oldTime = get(obj, 'UserData'); % recall the time if ~strcmp(fileInfo.date, oldTime) % if the time has changed process(handles); end set(obj,'UserData',fileInfo.date); % save the time set(handles.pushStart,'String',['Stop ' datestr(now)]); % override the time
A função "dir(full_file_name)" retorna uma estrutura que contém as informações do arquivo (nome, data, bytes, isDir). As informações sobre o tempo de criação do arquivo anterior serão armazenadas na propriedade "Userdata" do objeto do temporizador. Seu descritor é passado para a função "checktime" nomeada como obj.
Agora, ao mudar um arquivo criado por MetaTrader 4, nosso programa irá substituir o resultado. Você pode verificar isso, modificando o arquivo manualmente (por exemplo, deletando as últimas strings) e rastreando as alterações no gráfico ou arquivo resultante. Claro que, o botão "Iniciar" deve ser pressionado para isso.
Se uma janela extra contendo a cópia do gráfico for criada durante o funcionamento do programa, adicione a seguinte string no início da função "process":
set(handles.figure1,'HandleVisibility','on');
5. Desenhando os resultados no MetaTrader 4
Agora vamos retornar ao MetaTrader 4. Temos que complementar nosso indicador com uma função que lê o resultado do arquivo e o desenha em um gráfico. O comportamento do programa será descrito da seguinte forma:
1. Se uma nova barra for recebida: Exclua o arquivo do resultado anterior, apague o gráfico e salve o arquivo de dados.
2. Se o resultado do arquivo for legível: Leia o arquivo, desenhe um gráfico e exclua o arquivo do resultado.
Não vou descrever aqui como o código abaixo funciona já que a leitura de dados do arquivo e a elaboração de indicadores podem ser encontrados em outros artigos. Aqui, o arquivo do resultado é eliminado imediatamente depois de ter sido colocado no gráfico. Sendo assim, não se preocupe se você ver várias mensagens de erro de leitura.
Erros de leitura ocorrem em dois casos:
1. Imediatamente após uma nova barra ter rendimentos, uma vez que o arquivo do resultado não foi criado ainda.
2. Imediatamente após o resultado ser lido e o gráfico ser desenhado, já que o arquivo foi excluído a fim de não voltar a ler os mesmos dados.
Assim, o programa mantém seu status de "erro de leitura" praticamente o tempo todo. :)
#property indicator_chart_window #property indicator_buffers 1 #property indicator_width1 2 #property indicator_color1 Tomato extern int length = 100; // The amount of bars to be sent for processing double ExtMap[]; // Chart buffer string nameData; string nameResult; int init() { nameData = Symbol()+".txt"; // the name of the data file to be sent nameResult = Symbol()+"_result.txt";// the name of the received file containing results SetIndexStyle(0, DRAW_LINE); SetIndexBuffer(0, ExtMap); return(0); } int deinit() { Comment(""); return(0); } int start() { static int attempt = 0; // the amount of attempts to read the result static int old_bars = 0; // remember the amount of the known bars if (old_bars != Bars) // if a new bar has income { FileDelete(nameResult); // delete the result file ArrayInitialize( ExtMap, EMPTY_VALUE); // empty the chart write_data(); // save the data file old_bars = Bars; return(0); // nothing should be done this time } // int handle_read = FileOpen(nameResult, FILE_CSV|FILE_READ, ';'); // try to open the result file attempt++; // count the attempt to open if(handle_read >= 0) // if the file has opened for reading { Comment(nameResult+". Opened with attempt #"+ attempt); // opening report read_n_draw(handle_read); // read the result and draw a chart FileClose(handle_read); // close the file FileDelete(nameResult); // delete the result file attempt=0; // zeroize the amount of attempts to read } else // if we cannot open the result file { Comment( "Failed reading "+nameResult+ ". Amount of attempts: "+attempt+ ". Error #"+GetLastError()); //Report about failed reading } old_bars = Bars; // remember how many bars are known return(0); } //+------------------------------------------------------------------+ void read_n_draw(int handle_read) { int i=0; while ( !FileIsEnding(handle_read)) { ExtMap[i] = FileReadNumber(handle_read); i++; } ExtMap[i-1] = EMPTY_VALUE; } void write_data() { int handle; handle = FileOpen(nameData, FILE_CSV|FILE_WRITE,';'); if(handle < 1) { Comment("Failed creating "+nameData+". Error #", GetLastError()); return(0); } FileWrite(handle, ServerAddress(), Symbol(), Period()); // header FileWrite(handle, "DATE","TIME","HIGH","LOW","CLOSE","OPEN","VOLUME"); // header int i; for (i=length-1; i>=0; i--) { FileWrite(handle, TimeToStr(Time[i], TIME_DATE), TimeToStr(Time[i], TIME_SECONDS), High[i], Low[i], Close[i], Open[i], Volume[i]); } FileClose(handle); Comment("File "+nameData+" has been created. "+TimeToStr(TimeCurrent(), TIME_SECONDS) ); return(0); }
Abaixo está o meu resultado final. Espero não ter cometido nenhum erro e espero que você seja capaz de reproduzi-lo.
Conclusão
Neste artigo, descrevemos uma forma de organizar uma interação entre o MetaTrader 4 e o Matlab através de arquivos CSV. Este método não é único nem otimizado. O valor desta abordagem é que ela auxilia na troca de matrizes de dados sem habilidades especiais de operação com quaisquer ferramentas de programação além do MetaTrader 4 e Matlab.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/1489
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso