EA com interface gráfica: Fornecendo funcionalidade (Parte II)
Anatoli Kazharski | 1 agosto, 2018
Sumário
- Introdução
- Adquirindo dados de símbolos e de indicadores
- Adquirindo dados de posições abertas
- Inicializando tabelas com dados
- Atualizando as tabelas em tempo real
- Manipulando os eventos de controle
- Métodos para realizar operações de negociação
- Fim do artigo
Introdução
No artigo anterior, mostrei como criar rapidamente a GUI do EA. Aqui continuaremos a trabalhar com esse material e vincularemos a interface gráfica à funcionalidade do EA.
Adquirindo dados de símbolos e de indicadores
Primeiro, você precisa garantir que os dados de símbolos e de indicadores sejam recebidos. Você coletará numa tabela os símbolos forex, com base nos valores do filtro no campo inserido Symbols filter. Isso é feito através do método CProgram::GetSymbols().
No início do método, na barra de progresso indique que o processo de adquisição de símbolos está agora em progresso. Inicialmente, não se sabe quantos símbolos haverá. Portanto, defina em 50% a faixa do indicador. Em seguida, libere a matriz de símbolos. Ao trabalhar com o aplicativo, você pode precisar criar outra lista de símbolos, daí que é necessário fazer isso toda vez que chamar o método CProgram::GetSymbols().
O filtro no campo inserido Symbols filter será usado somente se sua caixa de verificação estiver ativada e se no campo inserido houver rótulos inseridos por meio de uma vírgula. Se estas condições forem atendidas, esses rótulos serão recebidos na matriz como elementos separados, a fim de usá-los para procurar os símbolos desejados. Por precaução, limpe, tirando os símbolos especiais das extremidades de cada elemento.
O próximo passo é o ciclo de coleta de símbolos forex. Ele percorrerá a lista completa dos símbolos disponíveis no servidor. No início de cada iteração, obtenha o nome do símbolo e o remova da janela Observação do mercado. Assim, as listas na interface gráfica do programa e nesta janela irão coincidir. Em seguida, verifique se o símbolo recebido pertence à categoria de símbolos forex. Se você precisar de todos os símbolos, apenas comente ou exclua essa condição. Neste artigo, você trabalhará apenas com símbolos forex.
Se o filtro de nome estiver ativado, verifique se no ciclo existe no nome do símbolo obtido nesta iteração uma correspondência com os rótulos do campo inserido Symbols filter. Se não houver correspondência, adicione o símbolo à matriz.
Se nenhum símbolo for encontrado, à matriz será adicionado somente o símbolo atual do gráfico principal. Depois disso, todos os símbolos adicionados à matriz ficam visíveis na janela Observação do mercado.
//+-------------------------------------------------------------------+ //| Classe para criar o aplicativo | //+-------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Símbolos para negociação string m_symbols[]; //--- private: //--- Obtendo símbolos void GetSymbols(void); }; //+-------------------------------------------------------------------+ //| Obtendo os símbolos | //+-------------------------------------------------------------------+ void CProgram::GetSymbols(void) { m_progress_bar.LabelText("Get symbols..."); m_progress_bar.Update(1,2); ::Sleep(5); //--- Libere a matriz de símbolos ::ArrayFree(m_symbols); //--- Matriz de elementos de linhas string elements[]; //--- Filtrando os nomes dos símbolos if(m_symb_filter.IsPressed()) { string text=m_symb_filter.GetValue(); if(text!="") { ushort sep=::StringGetCharacter(",",0); ::StringSplit(text,sep,elements); //--- int elements_total=::ArraySize(elements); for(int e=0; e<elements_total; e++) { //--- Limpeza de extremos ::StringTrimLeft(elements[e]); ::StringTrimRight(elements[e]); } } } //--- Colete uma matriz de símbolos forex int symbols_total=::SymbolsTotal(false); for(int i=0; i<symbols_total; i++) { //--- Obtenha o nome do símbolo string symbol_name=::SymbolName(i,false); //--- Oculte-o na janela ‘Observação do mercado’ ::SymbolSelect(symbol_name,false); //--- Se não houver um símbolo forex, vá para o próximo if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_CALC_MODE)!=SYMBOL_CALC_MODE_FOREX) continue; //--- Filtrando os nomes dos símbolos if(m_symb_filter.IsPressed()) { bool check=false; int elements_total=::ArraySize(elements); for(int e=0; e<elements_total; e++) { //--- Procurando correspondências no nome do símbolo if(::StringFind(symbol_name,elements[e])>-1) { check=true; break; } } //--- Ir para o próximo, caso o filtro não deixe passar if(!check) continue; } //--- Salve o símbolo numa matriz int array_size=::ArraySize(m_symbols); ::ArrayResize(m_symbols,array_size+1); m_symbols[array_size]=symbol_name; } //--- Se a matriz estiver vazia, defina o símbolo atual como padrão int array_size=::ArraySize(m_symbols); if(array_size<1) { ::ArrayResize(m_symbols,array_size+1); m_symbols[array_size]=::Symbol(); } //--- Exiba na janela Observação do mercado int selected_symbols_total=::ArraySize(m_symbols); for(int i=0; i<selected_symbols_total; i++) ::SymbolSelect(m_symbols[i],true); }
Agora considere obter os identificadores de indicador para todos os símbolos selecionados usando o método CProgram::GetHandles(). Primeiro, configure a matriz de identificadores com o mesmo tamanho da matriz de símbolos. Os identificadores serão obtidos com o timeframe, como indicado na caixa de combinação Timeframes. Como você pode obter um valor de string a partir da caixa de combinação, ele deve ser convertido no tipo correspondente (ENUM_TIMEFRAMES). No ciclo, preencha a matriz de identificadores. Neste caso, é um indicador Stochastic com valores padrão. Em cada iteração, atualize a barra de progresso. No final do método, lembre-se do primeiro índice do identificador do gráfico que será exibido.
class CProgram : public CWndEvents { private: //--- Identificador do indicador int m_handles[]; //--- Índice do identificador do gráfico atual int m_current_handle_index; //--- private: //--- Obtendo os identificadores void GetHandles(void); }; //+-------------------------------------------------------------------+ //| Obtendo os identificadores do indicador para todos os símbolos | //+-------------------------------------------------------------------+ void CProgram::GetHandles(void) { //--- Defina o tamanho da matriz dos identificadores int symbols_total=::ArraySize(m_symbols); ::ArrayResize(m_handles,symbols_total); //--- Obtendo o valor da lista suspensa da caixa de combinação string tf=m_timeframes.GetListViewPointer().SelectedItemText(); //--- Percorrendo a lista de símbolos for(int i=0; i<symbols_total; i++) { //--- Obtendo o identificador do indicador m_handles[i]=::iStochastic(m_symbols[i],StringToTimeframe(tf),5,3,3,MODE_SMA,STO_LOWHIGH); //--- Barra de progresso m_progress_bar.LabelText("Get handles: "+string(symbols_total)+"/"+string(i)+" ["+m_symbols[i]+"] "+((m_handles[i]!=WRONG_VALUE)? "ok" : "wrong")+"..."); m_progress_bar.Update(i,symbols_total); ::Sleep(5); } //--- Lembre-se do primeiro índice de identificador do gráfico m_current_handle_index=0; }
Obtenha os valores dos indicadores usando o método CProgram::GetIndicatorValues(). Descreva seu algoritmo. Primeiro, defina o tamanho da matriz - para os valores do indicador - igual ao tamanho da matriz de identificadores. No ciclo principal, percorra a matriz de identificadores e, em cada iteração, faça cinco tentativas de obter dados do indicador. Por precaução, verifique o identificador e, se não foi recebido antes, tente obtê-lo novamente. No final do ciclo principal, atualize a barra de progresso para ver em qual estágio está atualmente o programa.
class CProgram : public CWndEvents { private: //--- Valores do indicador double m_values[]; //--- private: //--- Obtendo os valores dos indicadores em todos os símbolos void GetIndicatorValues(void); }; //+-------------------------------------------------------------------+ //| Obtendo os valores dos indicadores em todos os símbolos | //+-------------------------------------------------------------------+ void CProgram::GetIndicatorValues(void) { //--- Defina o tamanho int handles_total=::ArraySize(m_handles); ::ArrayResize(m_values,handles_total); //--- Obtendo o valor da lista suspensa da caixa de combinação string tf=m_timeframes.GetListViewPointer().SelectedItemText(); //--- Obtendo os valores dos indicadores para todos os símbolos na lista for(int i=0; i<handles_total; i++) { //--- Tente 5 vezes obter dados int attempts=0; int received=0; while(attempts<5) { //--- Se o identificador for inválido, tente obtê-lo novamente if(m_handles[i]==WRONG_VALUE) { //--- Obtenha o identificador do indicador m_handles[i]=::iStochastic(m_symbols[i],StringToTimeframe(tf),5,3,3,MODE_SMA,STO_LOWHIGH); continue; } //--- Tente obter os valores do indicador double values[1]; received=::CopyBuffer(m_handles[i],1,0,1,values); if(received>0) { //--- Salve o valor m_values[i]=values[0]; break; } //--- Aumente o contador attempts++; ::Sleep(100); } //--- Barra de progresso m_progress_bar.LabelText("Get values: "+string(handles_total)+"/"+string(i)+" ["+m_symbols[i]+"] "+((received>0)? "ok" : "wrong")+"..."); m_progress_bar.Update(i,handles_total); ::Sleep(5); } }
Formada a lista de símbolos e obtidos os dados dos indicadores, é necessário adicionar os valores dessas matrizes à tabela na guia Trade. Isso é feito através do método CProgram::RebuildingTables(). O número de símbolos pode ser alterado. Portanto, toda vez que esse método é chamado, a tabela é completamente reconstruída.
Primeiro, todas as linhas são excluídas, exceto uma — a de backup. Em seguida, as linhas são adicionadas à tabela de acordo com o número de símbolos. Logo, você os itera num ciclo e adiciona os valores previamente recebidos em matrizes separadas. Além dos próprios valores, você ainda precisa destacar o texto para ver quais sinais já foram formados de acordo com os valores do indicador. Destaque com azul valores inferiores ao mínimo do indicador Stochastic como os sinais de compra, e com vermelho - os superiores ao máximo como sinais de venda. Em cada iteração, é atualizada a barra de progresso à medida que o programa progride. No final do método, você precisa atualizar a tabela e as barras de rolagem.
//+-------------------------------------------------------------------+ //| Reorganizando a tabela de símbolos | //+-------------------------------------------------------------------+ void CProgram::RebuildingTables(void) { //--- Excluindo todas as linhas m_table_symb.DeleteAllRows(); //--- Defina o número de linhas de acordo com o número de símbolos int symbols_total=::ArraySize(m_symbols); for(int i=0; i<symbols_total-1; i++) m_table_symb.AddRow(i); //--- Defina os valores para a primeira coluna uint rows_total=m_table_symb.RowsTotal(); for(uint r=0; r<(uint)rows_total; r++) { //--- Defina os valores m_table_symb.SetValue(0,r,m_symbols[r]); m_table_symb.SetValue(1,r,::DoubleToString(m_values[r],2)); //--- Definas as cores color clr=(m_values[r]>(double)m_up_level.GetValue())? clrRed :(m_values[r]<(double)m_down_level.GetValue())? C'85,170,255' : clrBlack; m_table_symb.TextColor(0,r,clr); m_table_symb.TextColor(1,r,clr); //--- Atualize a barra de progresso m_progress_bar.LabelText("Initialize tables: "+string(rows_total)+"/"+string(r)+"..."); m_progress_bar.Update(r,rows_total); ::Sleep(5); } //--- Atualizar tabela m_table_symb.Update(true); m_table_symb.GetScrollVPointer().Update(true); m_table_symb.GetScrollHPointer().Update(true); }
Todos os métodos descritos acima são chamados no método CProgram::RequestData(). Para ele é transferido um único argumento cujo valor permite que você verifique o identificador do controle — botão Request. Após esta verificação, oculte temporariamente a tabela e torne a barra de progresso visível. Em seguida, todos os métodos acima são sucessivamente chamados para obter os dados e inseri-los na tabela. Em seguida, oculte a barra de progresso, defina o intervalo de tempo a partir da caixa de combinação para o gráfico e torne as últimas alterações visíveis.
//+-------------------------------------------------------------------+ //| Solicitando dados | //+-------------------------------------------------------------------+ bool CProgram::RequestData(const long id) { //--- Verificação do identificador do elemento if(id!=m_request.Id()) return(false); //--- Ocultando a tabela m_table_symb.Hide(); //--- Mostrando o progresso m_progress_bar.Show(); m_chart.Redraw(); //--- Inicialização do gráfico e da tabela GetSymbols(); GetHandles(); GetIndicatorValues(); RebuildingTables(); //--- Ocultado o progresso m_progress_bar.Hide(); //--- Obtenha o valor a partir da caixa de combinação string tf=m_timeframes.GetListViewPointer().SelectedItemText(); //--- Obtenha o ponteiro do gráfico usando o índice m_sub_chart1.GetSubChartPointer(0).Period(StringToTimeframe(tf)); m_sub_chart1.ResetCharts(); //--- Mostrando a tabela m_table_symb.Show(); m_chart.Redraw(); return(true); }
Adquirindo dados de posições abertas
Quando o EA é carregado no gráfico, você precisa determinar imediatamente se há posições abertas para exibir essas informações, na tabela, na guia Positions. Você pode ver a lista de todas as posições, na janela Caixa de Ferramentas, na guia Negociação. Para fechar apenas uma posição do símbolo, você precisa clicar na cruz, na célula da tabela, na coluna Profit. Se houver várias posições no símbolo (numa conta de cobertura) e você precisar fechar tudo, serão necessários vários passos. Na tabela de posições da interface gráfica, faça que numa linha para cada símbolo haja uma informação cumulativa sobre o resultado atual, sobre a carga do depósito e sobre o preço médio. Além disso, adicione a capacidade de fechar simultaneamente todas as posições no símbolo especificado.
Primeiro, considere o método CProgram::GetPositionsSymbols(), para obter a lista de símbolos das posições abertas. Para ele é transferida uma matriz dinâmica vazia em que serão obtidos os símbolos. Em seguida, no ciclo, passe por todas as posições abertas. Em cada iteração, obtenha o nome do símbolo da posição e o adicione à variável de string por meio do separador ",". Antes de adicionar o nome do símbolo, primeiro verifique se já está nesta linha.
Concluído o ciclo e formada a string de símbolos, na matriz carregada obtenha os elementos dessa string e retorne o número de símbolos recebidos.
//+-------------------------------------------------------------------+ //| Obtendo numa matriz os símbolos das posições abertas | //+-------------------------------------------------------------------+ int CProgram::GetPositionsSymbols(string &symbols_name[]) { string symbols=""; //--- Percorra pela primeira vez o ciclo e obtenha os símbolos das posições abertas int positions_total=::PositionsTotal(); for(int i=0; i<positions_total; i++) { //--- Selecione a posição e obtenha seu símbolo string position_symbol=::PositionGetSymbol(i); //--- Se houver um nome de símbolo if(position_symbol=="") continue; //--- Se não existir essa linha, adicionamo-la if(::StringFind(symbols,position_symbol,0)==WRONG_VALUE) ::StringAdd(symbols,(symbols=="")? position_symbol : ","+position_symbol); } //--- Obtemos os elementos da linha pelo separador ushort u_sep=::StringGetCharacter(",",0); int symbols_total=::StringSplit(symbols,u_sep,symbols_name); //--- Retornamos o número de símbolos return(symbols_total); }
Agora que temos uma matriz de símbolos, pode-se obter dados para cada posição agregada, simplesmente especificando o nome do símbolo. Considere os métodos para obter indicadores em todas as colunas de dados na tabela de posições.
Para obter o número de posições no símbolo especificado, use o método CProgram::PositionsTotal(). No ciclo, ele percorre todas as posições e considera apenas aquelas que coincidem com o símbolo especificado no argumento do método.
//+-------------------------------------------------------------------+ //| Número de trades com as propriedades especificadas | //+-------------------------------------------------------------------+ int CProgram::PositionsTotal(const string symbol) { //--- Contador de posições int pos_counter=0; //--- Verifique se existe uma posição com as propriedades especificadas int positions_total=::PositionsTotal(); for(int i=positions_total-1; i>=0; i--) { //--- Se você não consegue selecionar a posição, vá para a próxima if(symbol!=::PositionGetSymbol(i)) continue; //--- Aumentar o contador pos_counter++; } //--- Retorne o número de posições return(pos_counter); }
Você pode obter o volume de posições usando o método CProgram::PositionsVolumeTotal(). Além do símbolo para o qual você precisa obter o volume total de posições, para método também pode ser transferido seu tipo. Mas o tipo de posições é um argumento opcional neste método. Por padrão, é especificado o valor WRONG_VALUE. Se o tipo não for especificado, não será usada essa verificação e o método retornará o volume total para todas as posições.
//+-------------------------------------------------------------------+ //| Volume total de posições com as propriedades especificadas | //+-------------------------------------------------------------------+ double CProgram::PositionsVolumeTotal(const string symbol,const ENUM_POSITION_TYPE type=WRONG_VALUE) { //--- Contador de volume double volume_counter=0; //--- Verifique se existe uma posição com as propriedades especificadas int positions_total=::PositionsTotal(); for(int i=positions_total-1; i>=0; i--) { //--- Se você não consegue selecionar a posição, vá para a próxima if(symbol!=::PositionGetSymbol(i)) continue; //--- Se você precisa verificar o tipo if(type!=WRONG_VALUE) { //--- Se o tipo não corresponder, vá para a próxima posição if(type!=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE)) continue; } //--- Soma do volume volume_counter+=::PositionGetDouble(POSITION_VOLUME); } //--- Retorne o volume return(volume_counter); }
O método CProgram::PositionsFloatingProfitTotal() permite obter o lucro flutuante total das posições para o símbolo especificado. O cálculo leva em consideração o swap acumulado das posições. Aqui, você também pode especificar como argumento opcional o tipo de posições para as quais deseja receber lucro flutuante. Assim, o método se torna universal.
//+-------------------------------------------------------------------+ //| Lucro flutuante total de posições com propriedades especificadas | //+-------------------------------------------------------------------+ double CProgram::PositionsFloatingProfitTotal(const string symbol,const ENUM_POSITION_TYPE type=WRONG_VALUE) { //--- Contador de lucro atual double profit_counter=0.0; //--- Verifique se existe uma posição com as propriedades especificadas int positions_total=::PositionsTotal(); for(int i=positions_total-1; i>=0; i--) { //--- Se você não consegue selecionar a posição, vá para a próxima if(symbol!=::PositionGetSymbol(i)) continue; //--- Se você precisa verificar o tipo if(type!=WRONG_VALUE) { //--- Se o tipo não corresponder, vá para a próxima posição if(type!=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE)) continue; } //--- Soma o lucro atual + swap acumulado profit_counter+=::PositionGetDouble(POSITION_PROFIT)+::PositionGetDouble(POSITION_SWAP); } //--- Retornando o resultado return(profit_counter); }
O preço médio é calculado pelo método CProgram::PositionAveragePrice(). No ciclo, para cada posição do símbolo, obtenha o preço e o volume. Em seguida, some o produto desses valores e separadamente — o volume de posições. Concluído o ciclo, para obter o preço médio das posições do símbolo especificado, é necessário dividir a soma do produto de preços e de volumes pela soma dos volumes. É esse valor que retorna o método exibido.
//+-------------------------------------------------------------------+ //| Preço médio da posição | //+-------------------------------------------------------------------+ double CProgram::PositionAveragePrice(const string symbol) { //--- Para cálculo do preço médio double sum_mult =0.0; double sum_volumes =0.0; //--- Verifique se existe uma posição com as propriedades especificadas int positions_total=::PositionsTotal(); for(int i=positions_total-1; i>=0; i--) { //--- Se você não consegue selecionar a posição, vá para a próxima if(symbol!=::PositionGetSymbol(i)) continue; //--- Obtenha o preço e o volume da posição double pos_price =::PositionGetDouble(POSITION_PRICE_OPEN); double pos_volume =::PositionGetDouble(POSITION_VOLUME); //--- Some os valores intermediários sum_mult+=(pos_price*pos_volume); sum_volumes+=pos_volume; } //--- Evitando a divisão por zero if(sum_volumes<=0) return(0.0); //--- Retornando o preço médio return(::NormalizeDouble(sum_mult/sum_volumes,(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS))); }
Considere o valor da carga do depósito. Para obtê-lo, você precisa chamar o método genérico CProgram::DepositLoad(). Dependendo dos argumentos transmitidos, você pode obter valores em termos percentuais ou na moeda da conta. Além disso, você pode obter a carga geral do depósito para todas as posições abertas ou apenas para o símbolo especificado.
O método tem quatro argumentos, três dos quais são opcionais. Se o primeiro argumento for false, o método retornará o valor na moeda de depósito. Se o valor for true, será retornado o valor em termos percentuais, em relação aos fundos disponíveis.
Se você deseja obter a carga atual sobre o depósito do símbolo especificado, então, quando a moeda da conta é diferente da moeda base do símbolo, no cálculo é preciso ter o preço da posição. Se houver várias posições abertas no símbolo, deve ser transferido o preço médio.
//+-------------------------------------------------------------------+ //| Carga do depósito | //+-------------------------------------------------------------------+ double CProgram::DepositLoad(const bool percent_mode,const double price=0.0,const string symbol="",const double volume=0.0) { //--- Calcule o valor atual da carga de depósito double margin=0.0; //--- Carga total na conta if(symbol=="" || volume==0.0) margin=::AccountInfoDouble(ACCOUNT_MARGIN); //--- Carga no símbolo especificado else { //--- Obtenha os dados para calcular a margem double leverage =((double)::AccountInfoInteger(ACCOUNT_LEVERAGE)==0)? 1 : (double)::AccountInfoInteger(ACCOUNT_LEVERAGE); double contract_size =::SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE); string account_currency =::AccountInfoString(ACCOUNT_CURRENCY); string base_currency =::SymbolInfoString(symbol,SYMBOL_CURRENCY_BASE); //--- Se a moeda da conta de negociação for a mesma que a moeda base do símbolo if(account_currency==base_currency) margin=(volume*contract_size)/leverage; else margin=(volume*contract_size)/leverage*price; } //--- Obtenha os fundos atuais double equity=(::AccountInfoDouble(ACCOUNT_EQUITY)==0)? 1 : ::AccountInfoDouble(ACCOUNT_EQUITY); //--- Retornando a carga do depósito return((!percent_mode)? margin : (margin/equity)*100); }
Todos os métodos para obter valores são adicionados à tabela chamando o método CProgram::SetValuesToPositionsTable(). Para o método você precisa transferir a matriz dos símbolos desejados. Primeiro, verifique se a matriz transmitida não é menor do que o número de linhas na tabela. Em seguida, no ciclo, percorra todas as linhas da tabela, recebendo os indicadores sequencialmente e enchendo as células da tabela com eles. Além dos valores propriamente ditos, também defina a cor do texto: verde para positivo, vermelho para negativo, cinza para zero. Repare que a carga do depósito será exibida para cada símbolo separadamente através de uma barra («/») em termos monetários e percentuais.
//+-------------------------------------------------------------------+ //| Definindo os valores na tabela de posições | //+-------------------------------------------------------------------+ void CProgram::SetValuesToPositionsTable(string &symbols_name[]) { //--- Verificar se está fora do intervalo uint symbols_total =::ArraySize(symbols_name); uint rows_total =m_table_positions.RowsTotal(); if(symbols_total<rows_total) return; //--- Obtenha na tabela os valores for(uint r=0; r<rows_total; r++) { int positions_total =PositionsTotal(symbols_name[r]); double pos_volume =PositionsVolumeTotal(symbols_name[r]); double buy_volume =PositionsVolumeTotal(symbols_name[r],POSITION_TYPE_BUY); double sell_volume =PositionsVolumeTotal(symbols_name[r],POSITION_TYPE_SELL); double pos_profit =PositionsFloatingProfitTotal(symbols_name[r]); double buy_profit =PositionsFloatingProfitTotal(symbols_name[r],POSITION_TYPE_BUY); double sell_profit =PositionsFloatingProfitTotal(symbols_name[r],POSITION_TYPE_SELL); double average_price =PositionAveragePrice(symbols_name[r]); string deposit_load =::DoubleToString(DepositLoad(false,average_price,symbols_name[r],pos_volume),2)+"/"+ ::DoubleToString(DepositLoad(true,average_price,symbols_name[r],pos_volume),2)+"%"; //--- Defina os valores m_table_positions.SetValue(0,r,symbols_name[r]); m_table_positions.SetValue(1,r,(string)positions_total); m_table_positions.SetValue(2,r,::DoubleToString(pos_volume,2)); m_table_positions.SetValue(3,r,::DoubleToString(buy_volume,2)); m_table_positions.SetValue(4,r,::DoubleToString(sell_volume,2)); m_table_positions.SetValue(5,r,::DoubleToString(pos_profit,2)); m_table_positions.SetValue(6,r,::DoubleToString(buy_profit,2)); m_table_positions.SetValue(7,r,::DoubleToString(sell_profit,2)); m_table_positions.SetValue(8,r,deposit_load); m_table_positions.SetValue(9,r,::DoubleToString(average_price,(int)::SymbolInfoInteger(symbols_name[r],SYMBOL_DIGITS))); //--- Defina a cor m_table_positions.TextColor(3,r,(buy_volume>0)? clrBlack : clrLightGray); m_table_positions.TextColor(4,r,(sell_volume>0)? clrBlack : clrLightGray); m_table_positions.TextColor(5,r,(pos_profit!=0)? (pos_profit>0)? clrGreen : clrRed : clrLightGray); m_table_positions.TextColor(6,r,(buy_profit!=0)? (buy_profit>0)? clrGreen : clrRed : clrLightGray); m_table_positions.TextColor(7,r,(sell_profit!=0)?(sell_profit>0)? clrGreen : clrRed : clrLightGray); } }
Para atualizar a tabela de posições após as alterações feitas, faça um método separado, já que ele terá que ser chamado em vários lugares no programa.
//+-------------------------------------------------------------------+ //| Atualizando a tabela de posições | //+-------------------------------------------------------------------+ void CProgram::UpdatePositionsTable(void) { //--- Atualizar tabela m_table_positions.Update(true); m_table_positions.GetScrollVPointer().Update(true); m_table_positions.GetScrollHPointer().Update(true); }
Inicialize a tabela de posições no método CProgram::InitializePositionsTable(). Nele são chamados todos os métodos discutidos nesta seção. Primeiro, obtenha os símbolos de posições abertas na matriz. Em seguida, prepare a tabela, para fazer isso, remova todas as linhas e adicione novas pelo número de símbolos recebidos na matriz. Se houver posições abertas, você deve primeiro estabelecer as células da primeira coluna como botões. Para fazer isso, defina o tipo apropriado (CELL_BUTTON) e adicione a imagem. Depois disso, nas células são definidos os valores, enquanto a tabela é atualizada.
//+-------------------------------------------------------------------+ //| Inicializando a tabela de posições | //+-------------------------------------------------------------------+ #resource "\\Images\\EasyAndFastGUI\\Controls\\close_black.bmp" //--- void CProgram::InitializePositionsTable(void) { //--- Obtenha os símbolos das posições abertas string symbols_name[]; int symbols_total=GetPositionsSymbols(symbols_name); //--- Excluindo todas as linhas m_table_positions.DeleteAllRows(); //--- Defina o número de linhas de acordo com o número de símbolos for(int i=0; i<symbols_total-1; i++) m_table_positions.AddRow(i); //--- Se houver posições if(symbols_total>0) { //--- Matriz de imagens para os botões string button_images[1]={"Images\\EasyAndFastGUI\\Controls\\close_black.bmp"}; //--- Defina os valores na terceira coluna for(uint r=0; r<(uint)symbols_total; r++) { //--- Defina o tipo e as imagens m_table_positions.CellType(0,r,CELL_BUTTON); m_table_positions.SetImages(0,r,button_images); } //--- Defina os valores na tabela SetValuesToPositionsTable(symbols_name); } //--- Atualizar tabela UpdatePositionsTable(); }
Inicializando tabelas com dados
Após a criação da interface gráfica do usuário, é necessário inicializar a tabela de símbolos e a tabela de posições. Você pode saber quando é concluída a formação da GUI, usando o evento personalizado ON_END_CREATE_GUI no processador de eventos. Para inicialização da tabela de símbolos, você deve chamar o método CProgram::RequestData(), que já foi considerado anteriormente. Para que o método funcione com sucesso, você precisa passar para ele o identificador do elemento do botão Request.
//+-------------------------------------------------------------------+ //| Manipulador de eventos | //+-------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Evento de criação da GUI if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI) { //--- Pedido de dados RequestData(m_request.Id()); //--- Inicialização da tabela de posições InitializePositionsTable(); return; } }
Como resultado, após carregado o programa no gráfico, a tabela de símbolos fica assim:
Fig. 1. Tabela de símbolos inicializada.
Se antes de carregado o programa na conta já havia posições abertas, a tabela de posições fica assim:
Fig. 2. Tabela de posições inicializada.
Atualizando as tabelas em tempo real
Como o preço se move o tempo todo, durante o pregão os dados nas tabelas devem ser constantemente recalculados. Você vai atualizar a tabela em determinados intervalos no temporizador do programa. Para garantir que os elementos sejam atualizados em intervalos diferentes, você pode usar objetos do tipo CTimeCounter. Esta classe está na compilação da biblioteca EasyAndFast. Para usá-la no projeto, basta conectar o arquivo com seu conteúdo:
//+-------------------------------------------------------------------+ //| Program.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| http://www.mql5.com | //+-------------------------------------------------------------------+ ... #include <EasyAndFastGUI\TimeCounter.mqh> ...
Nosso EA precisa de três contadores de tempo para atualizar os dados na barra de status e nas tabelas.
- Na barra de status, segundo o temporizador, são atualizados dois elementos, nomeadamente o segundo e o terceiro. O segundo mostra a carga total do depósito para toda a conta, enquanto no terceiro — a hora atual do servidor de negociação. Defina o intervalo de tempo para este contador como 500 ms.
- Na tabela de símbolos, você vai atualizar os valores atuais dos indicadores para todos os símbolos. Como pode haver muitos símbolos, você não deve fazer isso com muita frequência, portanto, defina o intervalo como 5 000 ms.
- Na tabela de posições existem indicadores que também dependem dos preços atuais. Portanto, você precisa atualizar periodicamente para ter dados reais. Para este contador, defina o intervalo como 1 000 ms.
Para configurar os contadores, basta declarar objetos do tipo CTimeCounter e definir seus parâmetros no construtor (veja a listagem abaixo). O primeiro parâmetro é a frequência do temporizador, enquanto o segundo — o intervalo de tempo após o qual o método CTimeCounter::CheckTimeCounter() retorna o valor true. Depois disso, o contador é reiniciado e iniciado novamente.
//+-------------------------------------------------------------------+ //| Classe para criar o aplicativo | //+-------------------------------------------------------------------+ class CProgram : public CWndEvents { private: ... //--- Contadores de tempo CTimeCounter m_counter1; CTimeCounter m_counter2; CTimeCounter m_counter3; ... }; //+-------------------------------------------------------------------+ //| Constructor | //+-------------------------------------------------------------------+ CProgram::CProgram(void) { //--- Defina os parâmetros para os contadores de tempo m_counter1.SetParameters(16,500); m_counter2.SetParameters(16,5000); m_counter3.SetParameters(16,1000); ... }
Para atualizar a barra de status no bloco do primeiro contador no temporizador do programa, adicione o código mostrado abaixo. Para exibir as alterações, não esqueça atualizar cada elemento separadamente.
//+-------------------------------------------------------------------+ //| Temporizador | //+-------------------------------------------------------------------+ void CProgram::OnTimerEvent(void) { ... //--- Atualizando os itens na barra de status if(m_counter1.CheckTimeCounter()) { //--- Defina os valores m_status_bar.SetValue(1,"Deposit load: "+::DoubleToString(DepositLoad(false),2)+"/"+::DoubleToString(DepositLoad(true),2)+"%"); m_status_bar.SetValue(2,::TimeToString(::TimeTradeServer(),TIME_DATE|TIME_SECONDS)); //--- Atualize os itens m_status_bar.GetItemPointer(1).Update(true); m_status_bar.GetItemPointer(2).Update(true); } ... }
Para acelerar a atualização da tabela em que é necessário substituir apenas os valores dos indicadores, use o método separado — CProgram::UpdateSymbolsTable(). Antes de chamá-lo, você deve primeiro atualizar a matriz de valores do indicador. Em seguida, é chamado o método CProgram::UpdateSymbolsTable(). Aqui, em cada iteração, é verificado se a matriz está fora do intervalo. Se a verificação for passada, atualize as células da segunda coluna da tabela e corrija a cor do texto. O processo de aquisição de dados e inicialização de tabelas é mostrado na barra de progresso.
//+-------------------------------------------------------------------+ //| Atualizando a tabela de símbolos | //+-------------------------------------------------------------------+ void CProgram::UpdateSymbolsTable(void) { uint values_total=::ArraySize(m_values); //--- Defina os valores na tabela de símbolos uint rows_total=m_table_symb.RowsTotal(); for(uint r=0; r<(uint)rows_total; r++) { //--- Pare o ciclo, se a matriz estiver fora do intervalo if(r>values_total-1 || values_total<1) break; //--- Defina os valores m_table_symb.SetValue(1,r,::DoubleToString(m_values[r],2)); //--- Definas as cores color clr=(m_values[r]>(double)m_up_level.GetValue())? clrRed :(m_values[r]<(double)m_down_level.GetValue())? C'85,170,255' : clrBlack; m_table_symb.TextColor(0,r,clr,true); m_table_symb.TextColor(1,r,clr,true); //--- Atualize a barra de progresso m_progress_bar.LabelText("Initialize tables: "+string(rows_total)+"/"+string(r)+"..."); m_progress_bar.Update(r,rows_total); ::Sleep(5); } //--- Atualizar tabela m_table_symb.Update(); }
O bloco do segundo contador de tempo para atualizar a tabela de símbolos é mostrado abaixo. Assim, a cada 5 segundos o programa receberá os valores atuais dos indicadores para todos os símbolos e atualizará sua tabela.
void CProgram::OnTimerEvent(void) { ... //--- Atualizando a tabela de símbolos if(m_counter2.CheckTimeCounter()) { //--- Mostrando o progresso m_progress_bar.Show(); m_chart.Redraw(); //--- Atualizando os valores na tabela GetIndicatorValues(); UpdateSymbolsTable(); //--- Ocultado o progresso m_progress_bar.Hide(); m_chart.Redraw(); } ... }
Para atualizar a tabela de posições no cronômetro, primeiro obtenha a matriz de símbolos das posições abertas. Em seguida, atualize a tabela com os dados reais. Classifique-a pela mesma coluna e direção, como antes da atualização. Aplique à tabela para exibir as alterações feitas.
void CProgram::OnTimerEvent(void) { ... //--- Atualizando a tabela de posições if(m_counter3.CheckTimeCounter()) { //--- Obtenha os símbolos das posições abertas string symbols_name[]; int symbols_total=GetPositionsSymbols(symbols_name); //--- Atualizando os valores na tabela SetValuesToPositionsTable(symbols_name); //--- Classifique se isso já foi feito pelo usuário antes da atualização m_table_positions.SortData((uint)m_table_positions.IsSortedColumnIndex(),m_table_positions.IsSortDirection()); //--- Atualizar tabela UpdatePositionsTable(); } }
Manipulando os eventos de controle
Nesta seção, vamos considerar os métodos de processamento de eventos que são gerados ao interagir com a interface gráfica do usuário de nosso aplicativo. Já consideramos o método CProgram::RequestData() para obter símbolos e dados de indicadores. Se a inicialização não for a primeira, o método é chamado ao pressionar o botão Request a qualquer momento durante a execução do programa. Quando o botão é pressionado, é gerado o evento personalizado com identificador ON_CLICK_BUTTON.
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... //--- Evento de pressionamento de botões if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { //--- Pedido de dados if(RequestData(lparam)) return; //--- return; } ... }
No gif abaixo, vemos que na tabela é gerada uma lista com símbolos forex contendo USD. Em seguida, é rapidamente gerada uma lista de símbolos contendo EUR. Para fazer isso, no campo inserido Filtro Symbols filter basta digitar o texto «EUR» e clicar no botão Request. Se você quiser ver todos os símbolos disponíveis no servidor com as moedas USD e EUR, insira estas moedas usando vírgulas: «USD,EUR».
Fig. 3. Gerando uma lista de símbolos forex.
A geração da lista de símbolos forex e a obtenção dos identificadores dos indicadores são realizadas de acordo com o período indicado na caixa de combinação Timeframes. Se você selecionar outro timeframe na lista suspensa, precisará obter novos identificadores e atualizar os valores na tabela. Para fazer isso, é necessário o método CProgram::ChangePeriod(). Se o identificador da caixa de combinação tiver chegado, primeiro atualize o timeframe no objeto gráfico. Em seguida, obtenha os identificadores e os dados do indicador para todos os símbolos na tabela, após isso, ela é atualizada para exibir as alterações feitas.
//+-------------------------------------------------------------------+ //| Alterando o timeframe | //+-------------------------------------------------------------------+ bool CProgram::ChangePeriod(const long id) { //--- Verificação do identificador do elemento if(id!=m_timeframes.Id()) return(false); //--- Obtenha o valor a partir da caixa de combinação string tf=m_timeframes.GetListViewPointer().SelectedItemText(); //--- Obtenha o ponteiro do gráfico usando o índice m_sub_chart1.GetSubChartPointer(0).Period(StringToTimeframe(tf)); m_sub_chart1.ResetCharts(); //--- Mostrando o progresso m_progress_bar.Show(); m_chart.Redraw(); //--- Obtenha os identificadores e os dados dos indicadores GetHandles(); GetIndicatorValues(); //--- Atualize a tabela UpdateSymbolsTable(); //--- Ocultado o progresso m_progress_bar.Hide(); m_chart.Redraw(); return(true); }
Ao selecionar o item na lista suspensa, são gerados os eventos personalizados com o identificador ON_CLICK_COMBOBOX_ITEM:
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... //--- Evento de seleção de item na caixa de combinação if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM) { //--- Alteração do timeframe if(ChangePeriod(lparam)) return; //--- return; } ... }
Veja a alteração do timeframe e a obtenção de novos dados:
Fig. 4 Alterando o timeframe.
Agora considere como alterar rapidamente o símbolo no objeto gráfico. Na tabela de símbolos, a primeira coluna já possui os nomes dos símbolos. Portanto, você pode alternar entre eles, simplesmente selecionando as linhas na tabela. Ao clicar numa linha, é chamado o método CProgram::ChangeSymbol(). Aqui, primeiro, é verificado o identificador da tabela de símbolos. Então, é preciso verificar se a linha na tabela está selecionada, uma vez que a seleção das linhas é removida por um segundo clique. Após essa verificação, você precisará salvar o índice da linha selecionada, como o índice do identificador que poderá usar depois para definir o indicador no gráfico (veja mais adiante no artigo).
Após recebido o símbolo pelo índice da linha selecionada na primeira coluna da tabela, defina-o no objeto gráfico. Como informação adicional, exiba a descrição completa do símbolo no primeiro item da barra de status. Ao desmarcar uma linha da tabela, o texto é definido com o valor padrão.
//+-------------------------------------------------------------------+ //| Alterando o símbolo | //+-------------------------------------------------------------------+ bool CProgram::ChangeSymbol(const long id) { //--- Verificação do identificador do elemento if(id!=m_table_symb.Id()) return(false); //--- Sair se a linha estiver selecionada if(m_table_symb.SelectedItem()==WRONG_VALUE) { //--- Exiba a descrição completa do símbolo na barra de status m_status_bar.SetValue(0,"For Help, press F1"); m_status_bar.GetItemPointer(0).Update(true); return(false); } //--- Salve o índice do identificador m_current_handle_index=m_table_symb.SelectedItem(); //--- Obtenha o símbolo string symbol=m_table_symb.GetValue(0,m_current_handle_index); //--- Atualizar gráfico m_sub_chart1.GetSubChartPointer(0).Symbol(symbol); m_sub_chart1.ResetCharts(); //--- Exiba a descrição completa do símbolo na barra de status m_status_bar.SetValue(0,::SymbolInfoString(symbol,SYMBOL_DESCRIPTION)); m_status_bar.GetItemPointer(0).Update(true); m_chart.Redraw(); return(true); }
Quando você seleciona uma linha na tabela, é gerado o evento personalizado com identificador ON_CLICK_LIST_ITEM. Você pode alternar os símbolos, usando as teclas «Up», «Down», «Home» e «End». Neste caso, é gerado o evento CHARTEVENT_KEYDOWN. Como o método para seu processamento foi considerado no artigo anterior, não vamos nos debruçar sobre ele aqui.
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... //--- Evento de seleção do item na lista/tabela if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM) { //--- Alteração do símbolo if(ChangeSymbol(lparam)) return; //--- return; } //--- Pressionamento de tecla if(id==CHARTEVENT_KEYDOWN) { //--- Seleção de resultados usando as teclas if(SelectingResultsUsingKeys(lparam)) return; //--- return; } ... }
Como resultado do processamento destes eventos, você verá algo assim:
Fig. 5. Alternando símbolos.
Às vezes você precisa ver no gráfico o indicador pelo qual recebemos sinais. A caixa de verificação Show indicator é projetada para ativar a exibição do indicador. O método CProgram::ShowIndicator() responde pela interação com ela. Aqui, você também precisa verificar o ID do elemento e a saída para além dos limites do intervalo da matriz dos identificadores. Para adicionar ou remover o indicador de um objeto gráfico, é necessário o identificador para este gráfico. Em seguida, se a caixa de verificação estiver ativada, o indicador deverá ser adicionado ao gráfico. Como o indicador será sempre um, defina o número da subjanela como 1. Para casos mais complexos, você precisa determinar o número de indicadores no gráfico.
//+-------------------------------------------------------------------+ //| Visibilidade do indicador | //+-------------------------------------------------------------------+ bool CProgram::ShowIndicator(const long id) { //--- Verificação do identificador do elemento if(id!=m_show_indicator.Id()) return(false); //--- Verificando se a matriz está fora do intervalo int handles_total=::ArraySize(m_handles); if(m_current_handle_index<0 || m_current_handle_index>handles_total-1) return(true); //--- Obtenha o identificador do gráfico long sub_chart_id=m_sub_chart1.GetSubChartPointer(0).GetInteger(OBJPROP_CHART_ID); //--- Número da subjanela do indicador int subwindow =1; //--- Obtenha o ponteiro do gráfico usando o índice if(m_show_indicator.IsPressed()) { //--- Adicione o indicador ao gráfico ::ChartIndicatorAdd(sub_chart_id,subwindow,m_handles[m_current_handle_index]); } else { //--- Remova o indicador do gráfico ::ChartIndicatorDelete(sub_chart_id,subwindow,ChartIndicatorName(sub_chart_id,subwindow,0)); } //--- Atualizar gráfico m_chart.Redraw(); return(true); }
Ao interagir com a caixa de verificação, é gerado o evento personalizado ON_CLICK_CHECKBOX:
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... //--- Evento de pressionamento no elemento "Caixa de verificação" if(id==CHARTEVENT_CUSTOM+ON_CLICK_CHECKBOX) { //--- Se pressionada a caixa de verificação "Show indicator" if(ShowIndicator(lparam)) return; //--- return; } }
Veja como funciona durante o trabalho:
Fig. 6. Exibição do indicador.
Mais dois controles estão relacionados com o indicador na interface gráfica do usuário do EA. Eles são os campos inseridos numéricos dos níveis do indicador Stochastic: Up level e Down level. Por padrão, eles são definidos como 80 e 20. Se os valores dos indicadores em cada símbolo excederem esses limites para cima e para baixo, o texto nas células da tabela de símbolos mudará de preto para azul para o nível superior e de preto para vermelho para o inferior. Se você alterar os valores nestes campos inseridos, na seguinte atualização (a cada cinco segundos) será alterada a exibição de cores.
Veja como isso funciona quando você altera valores de 80/20 para 90/10 e vice-versa:
Fig. 7. Alterando os níveis de sinal do indicador.
Vários controles são projetados para trabalhar com as propriedades do gráfico, isto é:
- caixas de verificação Date scale e Price scale para controlar a visibilidade das escalas gráficas;
- campo inserido Chart scale para controlar a escala do gráfico
- e botão Chart shift para incluir um recuo no lado direito do gráfico.
Os métodos de processamento a partir das caixas de verificação Date scale e Price scale são muito semelhantes. Neles, dependendo do estado da caixa de verificação, é ativada ou desativada a propriedade correspondente do gráfico. O método CStandardChart::ResetCharts() desloca o gráfico para o final.
//+-------------------------------------------------------------------+ //| Visibilidade da escala de tempo | //+-------------------------------------------------------------------+ bool CProgram::DateScale(const long id) { //--- Verificação do identificador do elemento if(id!=m_date_scale.Id()) return(false); //--- Obtenha o ponteiro do gráfico usando o índice m_sub_chart1.GetSubChartPointer(0).DateScale(m_date_scale.IsPressed()); m_sub_chart1.ResetCharts(); //--- Atualizar gráfico m_chart.Redraw(); return(true); } //+-------------------------------------------------------------------+ //| Visibilidade da escala de tempo | //+-------------------------------------------------------------------+ bool CProgram::PriceScale(const long id) { //--- Verificação do identificador do elemento if(id!=m_price_scale.Id()) return(false); //--- Obtenha o ponteiro do gráfico usando o índice m_sub_chart1.GetSubChartPointer(0).PriceScale(m_price_scale.IsPressed()); m_sub_chart1.ResetCharts(); //--- Atualizar gráfico m_chart.Redraw(); return(true); }
Para gerenciar a escala do gráfico, é usado o método CProgram::ChartScale(). Aqui, se o valor no campo inserido for alterado, ele será atribuído ao gráfico.
//+-------------------------------------------------------------------+ //| Escala do gráfico | //+-------------------------------------------------------------------+ bool CProgram::ChartScale(const long id) { //--- Verificação do identificador do elemento if(id!=m_chart_scale.Id()) return(false); //--- Defina a escala if((int)m_chart_scale.GetValue()!=m_sub_chart1.GetSubChartPointer(0).Scale()) m_sub_chart1.GetSubChartPointer(0).Scale((int)m_chart_scale.GetValue()); //--- Atualização m_chart.Redraw(); return(true); }
A alteração do valor no campo inserido Chart scale é processado na chegada de eventos personalizados com identificadores ON_CLICK_BUTTON e ON_END_EDIT.
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... //--- Evento de pressionamento de botões if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { //--- Escala do gráfico if(ChartScale(lparam)) return; //--- return; } //--- Eventos do final da mudança do valor no campo inserido if(id==CHARTEVENT_CUSTOM+ON_END_EDIT) { //--- Escala do gráfico if(ChartScale(lparam)) return; //--- return; } }
Abaixo é mostrado o código do método CProgram::ChartShift() para ativar no gráfico o recuo à direita. Nele, após verificar o ID do elemento, primeiro obtenha o identificador do gráfico, em seguida, usando-o como chave de acesso, você pode definir o recuo (CHART_SHIFT).
//+-------------------------------------------------------------------+ //| Deslocando o gráfico | //+-------------------------------------------------------------------+ bool CProgram::ChartShift(const long id) { //--- Verificação do identificador do elemento if(id!=m_chart_shift.Id()) return(false); //--- Obtenha o identificador do gráfico long sub_chart_id=m_sub_chart1.GetSubChartPointer(0).GetInteger(OBJPROP_CHART_ID); //--- Defina o recuo no lado direito do gráfico ::ChartSetInteger(sub_chart_id,CHART_SHIFT,true); m_sub_chart1.ResetCharts(); return(true); }
Veja como fica:
Fig. 8. Gerenciando as propriedades do gráfico.
Métodos para realizar operações de negociação
Vou exemplificar como você pode fácil e rapidamente vincular métodos de negociação à interface gráfica do EA. Além de visualizar dados, nosso EA realizará operações de negociação. É mais fácil trabalhar, quando tudo está num só lugar, alternando rapidamente gráficos. Como exemplo para operações de negociação, usaremos os recursos da biblioteca padrão. No entanto, você pode conectar outras bibliotecas de negociação.
Conecte ao projeto o arquivo Trade.mqh com classe CTrade e declare a instância desta classe:
//--- Classe para operações de negociação #include <Trade\Trade.mqh> //+-------------------------------------------------------------------+ //| Classe para criar o aplicativo | //+-------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Operações de negociação CTrade m_trade; };
Defina o modo assíncrono para conclusão do trade, a fim de que o programa não espere o resultado de cada operação de negociação. Além disso, defina a derrapagem máxima permitida. Ou seja, os trades serão concluídos após qualquer desvio em relação ao preço especificado na operação de negociação.
//+-------------------------------------------------------------------+ //| Constructor | //+-------------------------------------------------------------------+ CProgram::CProgram(void) { ... m_trade.SetAsyncMode(true); m_trade.SetDeviationInPoints(INT_MAX); }
O pressionamento dos botões Buy e Sell será processado usando os botões CProgram::OnBuy() e CProgram::OnSell(). Recebemos o volume do trade a partir do campo inserido Lot. No objeto gráfico, tome o símbolo em que você está operando. Este é o mínimo necessário para concluir a operação de negociação. Na classe CTrade, estão os métodos CTrade::Buy() e CTrade::Sell(), que ao serem chamadas permitem passar estes dois argumentos.
//+-------------------------------------------------------------------+ //| Compra | //+-------------------------------------------------------------------+ bool CProgram::OnBuy(const long id) { //--- Verificação do identificador do elemento if(id!=m_buy.Id()) return(false); //--- Volume e símbolo para abertura da posição double lot =::NormalizeDouble((double)m_lot.GetValue(),2); string symbol =m_sub_chart1.GetSubChartPointer(0).Symbol(); //--- Abrir posição m_trade.Buy(lot,symbol); return(true); } //+-------------------------------------------------------------------+ //| Venda | //+-------------------------------------------------------------------+ bool CProgram::OnSell(const long id) { //--- Verificação do identificador do elemento if(id!=m_sell.Id()) return(false); //--- Volume e símbolo para abertura da posição double lot =::NormalizeDouble((double)m_lot.GetValue(),2); string symbol =m_sub_chart1.GetSubChartPointer(0).Symbol(); //--- Abrir posição m_trade.Sell(lot,symbol); return(true); }
Para fechamento de todas as posições de uma vez ou apenas no símbolo especificado, você precisa escrever um método separado — na classe CTrade não existe tal coisa. Se o símbolo for transferido para o método (parâmetro opcional), somente as posições neste símbolo serão fechadas. Se o símbolo não for especificado, todas as posições serão fechadas.
//+-------------------------------------------------------------------+ //| Fechando todas as posições | //+-------------------------------------------------------------------+ bool CProgram::CloseAllPosition(const string symbol="") { //--- Verifique se existe uma posição com as propriedades especificadas int total=::PositionsTotal(); for(int i=total-1; i>=0; i--) { //--- Selecione a posição string pos_symbol=::PositionGetSymbol(i); //--- Se especificado o fechamento de acordo com o símbolo if(symbol!="") if(symbol!=pos_symbol) continue; //--- Obtenha a boleta ulong position_ticket=::PositionGetInteger(POSITION_TICKET); //--- Redefina o último erro ::ResetLastError(); //--- Se a posição não estiver fechada, exiba uma mensagem sobre isso if(!m_trade.PositionClose(position_ticket)) ::Print(__FUNCTION__,": > An error occurred when closing a position: ",::GetLastError()); } //--- return(true); }
O fechamento de todas as posições está associado ao botão Close all positions. Seu pressionamento será processado no método CProgram::OnCloseAllPositions(). Para evitar pressionamentos acidentais no botão, será aberta a caixa de diálogo para confirmação da operação.
//+-------------------------------------------------------------------+ //| Fechando todas as posições | //+-------------------------------------------------------------------+ bool CProgram::OnCloseAllPositions(const long id) { //--- Verificação do identificador do elemento if(id!=m_close_all.Id()) return(false); //--- Caixa de diálogo int mb_id=::MessageBox("Are you sure you want to close \nall positions?","Close positions",MB_YESNO|MB_ICONWARNING); //--- Feche as posições if(mb_id==IDYES) CloseAllPosition(); //--- return(true); }
Veja como fica isso:
Fig. 9. Fechando todas as posições.
Você pode fechar posições do símbolo especificado na guia Positions. Às células da primeira coluna da tabela de posições foram adicionados botões na forma de cruzes. Eles podem fechar imediatamente todas as posições do símbolo cujos dados são mostrados nesta linha. Ao pressionar os botões nas células, é gerado o evento personalizado com identificador ON_CLICK_BUTTON. No entanto, no elemento do tipo CTable existem barras de rolagem cujos botões geram os mesmos eventos, e o identificador do elemento também coincide. Portanto, você precisa rastrear o parâmetro de string (sparam) do evento, a fim de não processar acidentalmente um clique nos outros botões do elementos. O parâmetro de string especifica o tipo do elemento no qual houve pressionamento. As barras de rolagem têm o valor «scroll». Se um evento com esse valor chegar, o programa sai do método. Depois disso, você precisa verificar se ainda há posições abertas.
Se todas as verificações forem concluídas, você precisará extrair o índice de string a partir da descrição do parâmetro de string que define o símbolo na primeira coluna da tabela. Aqui também, para excluir cliques acidentais nos botões, primeiro é exibida uma caixa de diálogo para confirmar a ação. O clique no botão Sim fecha posições apenas no símbolo especificado.
//+-------------------------------------------------------------------+ //| Fechando todas as posições do símbolo definido | //+-------------------------------------------------------------------+ bool CProgram::OnCloseSymbolPositions(const long id,const string desc) { //--- Verificação do identificador do elemento if(id!=m_table_positions.Id()) return(false); //--- Sair, se pressionado o botão da barra de rolagem if(::StringFind(desc,"scroll",0)!=WRONG_VALUE) return(false); //--- Sair se não houver posições if(::PositionsTotal()<1) return(true); //--- Extraia os dados da linha string str_elements[]; ushort sep=::StringGetCharacter("_",0); ::StringSplit(desc,sep,str_elements); //--- Obtenha o índice e o símbolo int row_index =(int)str_elements[1]; string symbol =m_table_positions.GetValue(0,row_index); //--- Caixa de diálogo int mb_id=::MessageBox("Are you sure you want to close \nall positions on symbol "+symbol+"?","Close positions",MB_YESNO|MB_ICONWARNING); //--- Feche todas as posições para o símbolo especificado if(mb_id==IDYES) CloseAllPosition(symbol); //--- return(true); }
Veja como fica:
Fig. 10. Fechando todas as posições do símbolo especificado.
Todas as operações de negociação descritas acima são processadas após a chegada do evento ON_CLICK_BUTTON:
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... //--- Evento de pressionamento de botões if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { ... //--- Compra if(OnBuy(lparam)) return; //--- Venda if(OnSell(lparam)) return; //--- Fechar todas as posições if(OnCloseAllPositions(lparam)) return; //--- Fechar todas as posições do símbolo especificado if(OnCloseSymbolPositions(lparam,sparam)) return; //--- return; } ... }
Cada operação de negociação deve ser refletida na tabela de posições. Para fazer isso, você precisa acompanhar os eventos de negociação e o histórico das trades da conta de negociação. Se o número de trades foi alterado, a tabela deverá ser gerada novamente. Para verificar se o histórico foi alterado, é usado o método CProgram::IsLastDealTicket(). Cada vez após a verificação, você precisa salvar o tempo e a boleta do último trade. Salve o tempo, para que você não peça todo o histórico de trades toda vez. Use a boleta para verificar se o número de trades no histórico foi alterado. Como o trade inicia vários eventos de negociação, esse método retornará true apenas uma vez.
class CProgram : public CWndEvents { private: //--- Hora e boleta do último trae verificado datetime m_last_deal_time; ulong m_last_deal_ticket; //--- private: //--- Verificação de um novo trade no histórico bool IsLastDealTicket(void); }; //+-------------------------------------------------------------------+ //| Constructor | //+-------------------------------------------------------------------+ CProgram::CProgram(void) : m_last_deal_time(NULL), m_last_deal_ticket(WRONG_VALUE) { ... } //+-------------------------------------------------------------------+ //| Verificando um novo trade no histórico | //+-------------------------------------------------------------------+ bool CProgram::IsLastDealTicket(void) { //--- Sair se o histórico não for recebido if(!::HistorySelect(m_last_deal_time,UINT_MAX)) return(false); //--- Obtemos o número de trades na lista int total_deals=::HistoryDealsTotal(); /--- Percorremos todos os trades na lista recebida, desde a última transação até a primeira for(int i=total_deals-1; i>=0; i--) { //--- Obtemos o boleto do trade ulong deal_ticket=::HistoryDealGetTicket(i); //--- Se as boletas forem iguais, sairemos if(deal_ticket==m_last_deal_ticket) return(false); //--- Se as boletas não forem iguais, informamos sobre isso else { datetime deal_time=(datetime)::HistoryDealGetInteger(deal_ticket,DEAL_TIME); //--- Lembramos a hora a boleta do último trade m_last_deal_time =deal_time; m_last_deal_ticket =deal_ticket; return(true); } } //--- Boletas de outro símbolo return(false); }
Chame uma vez o método CProgram::IsLastDealTicket() no manipulador de eventos. Se o histórico mudou, a tabela de posições será gerada de novo:
//+-------------------------------------------------------------------+ //| Evento de operação de negociação | //+-------------------------------------------------------------------+ void CProgram::OnTradeEvent(void) { //--- Se houver um novo trade if(IsLastDealTicket()) { //--- Inicialização da tabela de posições InitializePositionsTable(); } }
Veja como fica:
Fig. 11. Gerando a tabela ao fechar as posições pelo símbolo.
Fim do artigo
No artigo, mostrei como, sem muito esforço, você pode criar interfaces gráficas de qualquer complexidade para seus programas. Você pode continuar desenvolvendo este programa e usá-lo para seus próprios propósitos. A ideia pode ser melhorada, adicionando seus próprios indicadores e resultados de cálculos.
Para quem não quer estudar com cuidado o código e compilar o programa, no mercado já foi publicado o aplicativo pronto Trading Exposure.
O artigo contém arquivos para testes e um estudo mais detalhado do código apresentado nele.
Nome do arquivo | Comentário |
---|---|
MQL5\Experts\TradePanel\TradePanel.mq5 | EA - para negociação manual - com interface gráfica |
MQL5\Experts\TradePanel\Program.mqh | Arquivo com a classe do programa |
MQL5\Experts\TradePanel\CreateGUI.mqh | Arquivo com a implementação dos métodos para criar uma interface gráfica a partir da classe de programa no arquivo Program.mqh |
MQL5\Include\EasyAndFastGUI\Controls\Table.mqh | Classe atualizada CTable |
MQL5\Include\EasyAndFastGUI\Keys.mqh | Classe atualizada CKeys |