
Simulação de mercado: Position View (III)
Introdução
Olá pessoal, e sejam bem-vindos a mais um artigo da série sobre como construir um sistema de replay/simulação.
No artigo anterior Simulação de mercado: Position View (II), criamos algo bem simples, cujo objetivo seria mostrar no gráfico, onde se encontra as linhas de preços em uma posição aberta. Apesar de que, aquele indicador seja capaz de nos mostrar tal informação, ainda estamos longe de ter algo realmente prático. Já que ainda temos alguns pequenos problemas a serem resolvidos. Mas não é algo complicado. Apenas algo que você, caro leitor e entusiasta, precisa entender antes de que possamos avançar um pouco mais na implementação do código.
Para tornar as coisas um pouco mais simples de serem assimiladas. Vamos ver isto em um tópico, voltado apenas para tal tarefa. Então vamos a ele, sem mais delongas.
Entendendo a propriedade ZOrder
Não sei se você, caro leitor, já notou que temos nos objetos presentes no MQL5, um valor, que em geral é muito pouco usado. Ou definido em algum código. Eu mesmo, olhei diversos códigos diferentes e não encontrei ninguém que de fato define tal propriedade dos objetos. A propriedade na qual estou me referindo é a ZOrder. Se você vem acompanhando esta série, deve ter notado, que já há um bom tempo, esta propriedade está sendo definida na classe C_Terminal. Isto quando criamos os objetos via chamada CreateObjectGraphics. Que é um procedimento presente na referida classe.
E nestes últimos artigos, tenho mencionado o fato de que precisamos em alguns momentos definir um valor para esta propriedade. Mas por que? Já que muitos dos códigos, que adicionam objetos no gráfico, simplesmente não utilizam, ou melhor, não definem um valor para tal propriedade. Bem, não estou aqui, para dizer, o que cada programador, deve ou não fazer. Como ele deve ou não criar seus códigos. Estou aqui, a fim de mostrar, a você caro leitor, e interessado em realmente compreender como as coisas funcionam, por debaixo dos panos.
Sem entender o que irei explicar, ou sem compreender adequadamente, determinados conceitos no uso do MetaTrader 5. Ou mesmo da programação MQL5. Você, sem sombra de dúvidas acabará tendo uma péssima experiência no uso da plataforma. E um destes conceitos envolve justamente a propriedade ZOrder.
Para experimentar de fato as coisas, da maneira mais simples quanto for possível, para que você entenda do que estou falando. Vamos usar o seguinte código, visto abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property version "1.00" 04. #property indicator_chart_window 05. #property indicator_plots 0 06. //+------------------------------------------------------------------+ 07. input char user00 = 0; //ZOrder to Chart #01 08. input char user01 = 0; //ZOrder to Chart #02 09. //+------------------------------------------------------------------+ 10. void ChartOfTest(string sz1, int x, int y, int zOrder) 11. { 12. long id = ChartID(); 13. 14. ObjectCreate(id, sz1, OBJ_CHART, 0, 0, 0); 15. ObjectSetInteger(id, sz1, OBJPROP_XDISTANCE, x); 16. ObjectSetInteger(id, sz1, OBJPROP_YDISTANCE, y); 17. ObjectSetInteger(id, sz1, OBJPROP_ZORDER, zOrder); 18. ObjectSetInteger(id, sz1, OBJPROP_SELECTABLE, true); 19. } 20. //+------------------------------------------------------------------+ 21. int OnInit() 22. { 23. ChartOfTest("Chart #01", 160, 140, user00); 24. ChartOfTest("Chart #02", 200, 200, user01); 25. 26. return INIT_SUCCEEDED; 27. } 28. //+------------------------------------------------------------------+ 29. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 30. { 31. return rates_total; 32. } 33. //+------------------------------------------------------------------+ 34. void OnDeinit(const int reason) 35. { 36. ObjectsDeleteAll(ChartID(), "Chart #"); 37. } 38. //+------------------------------------------------------------------+
Código fonte do indicador
Este código, super simples, faz exatamente o que precisamos para entender como a propriedade ZOrder. Pode e afetará a sua experiência de uso no MetaTrader 5. Observe que nas linhas 23 e 24, estaremos chamando o procedimento, que é o responsável por criar dois objetos e os posicionar na tela do gráfico que estiver aberto no momento. Os objetos a serem criados, são OBJ_CHART, como você pode ver na linha 14. Para facilitar a sua vida, caro leitor. Nas linhas sete e oito, permitimos dois pontos de controle. Estes têm como finalidade, evitar a necessidade, de que você ajuste os valores de ZOrder dos objetos a todo o momento.
Por padrão, os valores são sempre zero. Por isto, deixamos desta maneira por enquanto. O resultado da execução pode ser visto na imagem logo abaixo.
Algo simples e notório. Como seria o esperado. Mas e se trocarmos o tempo gráfico? O que acontecerá com nesta janela? Onde temos um indicador que tem como objetivo, criar um papel de parede para o gráfico? Será que ficaremos impedidos de acessar os objetos OBJ_CHART? Bem, isto pode ser visto na animação logo abaixo.
Observem, que apesar do papel de parede, ou seja, um objeto do tipo OBJ_BITMAPLABEL, estar acima dos objetos OBJ_CHART. Dado o fato, de que o fundo dos objetos, OBJ_CHART, agora deixaram de ser pretos e passaram a ter o conteúdo da região onde estão sobre o OBJ_BITMAPLABEL. Isto normalmente poderia trazer problemas, mas devido ao fato de que na linha 17, estamos definindo um valor zero para o ZOrder. E o valor do ZOrder do objeto onde está o papel de parede ser menor, no caso -1. Como vimos nos artigos anteriores. A condição de clique, ou forma de selecionar os objetos OBJ_CHART, não foi afetado, como você pode ver na animação logo acima.
Porém, toda via e entretanto, você terá a nítida ilusão de que um objeto, que esteja em primeiro plano, terá prioridade. Como é visto na animação logo abaixo.
Como eu acabei de dizer. Isto é uma ilusão, já que o objeto que de fato está em primeiro plano é o OBJ_BITMAPLABEL, que contém a imagem que preenche todo o gráfico. Criando assim um papel de parede. Mas esta ilusão se torna ainda mais intrigante, quando você modifica, a prioridade dos objetos. Para fazer isto, usando o código visto acima. Veja a animação abaixo.
Note que mudei a prioridade do objeto que se encontra no fundo, ou seja o CHART #01. Assim, como ele tem uma prioridade mais alta, temos o resultado visto na animação. Se você não compreendeu, experimente isto em sua própria estação do MetaTrader 5. Você irá notar, que mesmo clicando em uma região pertencente ao CHART #02. Mas que esteja também pertencendo ao CHART #01. O CHART #01, será selecionado. Você somente conseguirá selecionar o CHART #02, se e somente se clicar em uma região fora do CHART #01, mas pertencente ao CHART #02. Caso ambos estejam exatamente na mesma região, o CHART #01, terá prioridade, mesmo estando encoberto pelo CHART #02.
Isto com toda a certeza ficará muito mais interessante, se você experimentar, colocar valores nos OBJ_CHART, menores ou iguais ao do objeto OBJ_BITMAPLABEL, que faz o papel de parede. Ou se colocar um outro objeto, via atalho que pode ser visto na imagem abaixo.
Experimente fazer isto. Adicionar mais um OBJ_CHART, mas desta vez pelo atalho mostrado acima, e brinque de mudar os valores do indicador conforme mostrado na animação acima. Você com toda a certeza, começará a entender de fato, a importância de se usar um valor adequado na propriedade ZOrder. Mas onde isto se aplica no que estamos desenvolvendo neste momento? Bem, para responder isto, de forma mais adequada, e assim separar os assuntos, vamos ver isto em um novo tópico.
Fazendo uso do conhecimento adquirido
Muito bem, saber e compreender, o que acabei de mostrar. É primordial para que você consiga entender uma coisa que será feita no momento da implementação. Tal coisa, depois deixará de fazer sentido, já que usaremos uma outra abordagem. Mas neste momento é importante. Ainda mais dependendo de como você vier a modificar ou ajustar o código para seu uso particular.
No artigo anterior, criamos três linhas. Uma para o preço de abertura da posição. Uma para o ponto de stop loss. E uma para o ponto de take profit. Muito bem, até neste ponto não existe nada de errado com o que foi feito. A linha horizontal do preço de abertura, nunca deverá se mover. Isto será resolvido em breve. Já as linhas horizontais de stop loss e take profit, podem ser movidas livremente. E é neste ponto que temos um problema. E o problema surge quando ambas linhas, a de stop loss e take profit, vierem a estar no mesmo preço.
Neste caso, qual linha deverá ter prioridade durante uma tentativa de selecionar uma das linhas? Isto a fim de que elas recebam um dado evento. Que pode ser mover a linha ou mesmo deletar a linha. Você poderia dizer que é a de stop loss, enquanto outro operador poderia dizer que seria a de take profit. Quem de fato, estaria com a razão nesta situação?
Perceberam o problema que temos em mãos? De certa maneira, a ordem em que as linhas estivessem sendo criadas. Irá influenciar tal decisão. Isto se ambas tiverem o mesmo valor de ZOrder. Então o simples fato de você mudar a ordem, que indica como será a criação. Já resolveria muitos dos problemas. Porém, vamos levar isto a uma situação ainda mais extrema. Não é raro, alguns operadores, ainda mais em contas do tipo HEDGING, terem mais de uma posição, em aberto. Neste caso você pode desejar colocar um ZOrder diferente entre a linha de stop loss e take profit. Mas mesmo que tenha feito isto, o que eliminaria o problema de uma linha de stop loss ficar no mesmo ponto de uma linha de take profit. Você teria outro problema, que seria quando duas linhas, como por exemplo, stop loss, viesse a ficar sobre uma outra linha de stop loss.
Notaram como não adianta simplesmente mudar o ZOrder de qualquer maneira? Mesmo que você use valores diferentes a fim de resolver um problema. Sempre existirá um outro problema para ser resolvido. No entanto, segundo alguns operadores profissionais, que estão me assessorando em algumas coisas, no que tange o desenvolvimento e implementação do replay/simulador. Não existe motivo, práticos, para que tenhamos mais de duas, três sendo no máximo quatro informações no servidor. Ou seja, se estivermos usando uma conta HEDGING, segundo eles não faz sentido, você ter mais de duas posições abertas ao mesmo tempo. Já que uma conta NETTING não temos esta questão. Irei neste primeiro momento, pensar apenas em contas do tipo HEDGING. Mas isto não quer dizer que o indicador não funcionará para contas NETTING.
Assim sendo, vamos voltar ao código visto no artigo anterior. Lá implementamos as linhas de stop loss e take profit, com o mesmo ZOrder. Para mudar isto, precisaremos fazer uma simples mudança no código. Tal mudança é tão singular, que não entrarei em detalhes sobre a mesma. Apenas veja como o código modificado irá se apresentar. Isto pode ser visto logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Indicator for tracking an open position on the server." 04. #property version "1.00" 05. #property indicator_chart_window 06. #property indicator_plots 0 07. //+------------------------------------------------------------------+ 08. #define def_SufixLinePrice "Price" 09. #define def_SufixLineTake "Take" 10. #define def_SufixLineStop "Stop" 11. //+------------------------------------------------------------------+ 12. #include <Market Replay\Auxiliar\C_Terminal.mqh> 13. //+------------------------------------------------------------------+ 14. input color user00 = clrRoyalBlue; //Color Line Price 15. input color user01 = clrForestGreen; //Color Line Take Profit 16. input color user02 = clrFireBrick; //Color Line Stop Loss 17. //+------------------------------------------------------------------+ 18. C_Terminal *Terminal; 19. struct st 20. { 21. long id; 22. string szPrefixName; 23. }glVariables; 24. //+------------------------------------------------------------------+ 25. void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n") 26. { 27. if (price <= 0) return; 28. (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == user00 ? ePriorityNull : (ePriorityOrders + (cor == user02)))); 29. ObjectSetDouble(glVariables.id, szObjName, OBJPROP_PRICE, price); 30. ObjectSetString(glVariables.id, szObjName, OBJPROP_TEXT, szDescription); 31. ObjectSetString(glVariables.id, szObjName, OBJPROP_TOOLTIP, szDescription); 32. ObjectSetInteger(glVariables.id, szObjName, OBJPROP_SELECTABLE, cor != user00); 33. } 34. //+------------------------------------------------------------------+ 35. int OnInit() 36. { 37. ZeroMemory(glVariables); 38. Terminal = new C_Terminal(); 39. if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED; 40. glVariables.id = (*Terminal).GetInfoTerminal().ID; 41. glVariables.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET)); 42. CreateLineInfos(glVariables.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), user00, "Position opening price."); 43. CreateLineInfos(glVariables.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), user01, "Take Profit point."); 44. CreateLineInfos(glVariables.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), user02, "Stop Loss point."); 45. 46. return INIT_SUCCEEDED; 47. } 48. //+------------------------------------------------------------------+ 49. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 50. { 51. return rates_total; 52. } 53. //+------------------------------------------------------------------+ 54. void OnDeinit(const int reason) 55. { 56. delete Terminal; 57. if (glVariables.id > 0) 58. ObjectsDeleteAll(glVariables.id, glVariables.szPrefixName); 59. } 60. //+------------------------------------------------------------------+
Indicador Position View
Note que agora, a linha de stop loss, terá prioridade sobre a linha de take profit. Isto por que ela tem um valor de ZOrder maior. Então caso tenhamos duas posições em aberto, e a linha de stop loss, esteja no mesmo preço que uma linha de take profit. Ao tentar qualquer manipulação nestas linhas. A linha de stop loss terá preferência. Note que a única mudança para que isto ocorresse foi feita na linha 28. Compare esta mesma linha com o do código anterior para entender a mudança.
Entretanto, apesar deste indicador está funcionando. Ele não é de fato adequado para ser usado em contas do tipo HEDGING. O motivo é que ele sempre irá buscar a primeira posição, ou última que se encontra aberta. E mesmo se você fizer algo que mude isto, de alguma forma, isto será apenas um placebo. Precisamos de fato implementar uma solução que tenha um melhor efeito, de uma forma mais geral. Além disto, você pode notar, que estamos dependendo das cores serem diferentes. Isto para que o indicador consiga saber, qual linha representa o que. E este tipo de coisa é adequada para testes. Mas para uma aplicação, mais geral, ela se torna completamente inadequada. Desta forma, temos muitas coisas a serem resolvidas e corrigidas aqui.
Nesta hora, muitos que estejam começando na área de programação. Costuma desistir, ou fazem tantas mudanças que acabam não conseguindo gerenciar o código que estão criando. Assim, caso você já saiba programar, e tem os conceitos corretos. Peço que tenha paciência, com os com menos experiência. Já que irei mostrar como aqueles com menos experiência, deveriam de fato começar a pensar. Tomando assim gosto pela arte de programar, e conseguindo criar suas próprias soluções.
Começando a corrigir as coisas
Quando você, caro leitor, vier a se deparar com um código, como visto acima. Onde você deve, de alguma maneira melhorar o mesmo. Não comece mudando ele a reveria. Comece separando as coisas em partes. Mas sempre mantendo o código funcionando. Não, e repito. NÃO mude nada, antes de realmente separar as coisas. Uma das maneiras mais simples e adequadas de separar as coisas é colocando, algumas partes do código em um arquivo de cabeçalho. Para logo em seguida começar a modificar o código. Mas como é muito mais simples, jogar todas as partes para dentro de uma classe. Será isto que faremos. Mas por que jogar o código para dentro de uma classe é mais simples que separar ele em arquivos de cabeçalho?
O motivo é simples. Se você coloca o código em uma classe, poderá colocar ele em um arquivo de cabeçalho futuramente. No entanto, apenas jogar ele para um arquivo de cabeçalho, não o torna mais simples de ser gerenciando. Isto por conta que pode vir a ocorrer conflitos de nomes no futuro. Não é raro, códigos com muitos arquivos, existir tais conflitos. Porém, quando jogamos as coisas para dentro de uma classe, a possibilidade de conflitos cai bastante. Não que eles sejam totalmente eliminados, como você verá conforme for progredindo na programação. No entanto, resolver tais conflitos será consideravelmente mais simples e rápido. Sabendo disto, vamos começar transportando as coisas para uma nova classe no sistema de replay / simulador.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Indicator for tracking an open position on the server." 04. #property version "1.00" 05. #property indicator_chart_window 06. #property indicator_plots 0 07. //+------------------------------------------------------------------+ 08. #define def_SufixLinePrice "Price" 09. #define def_SufixLineTake "Take" 10. #define def_SufixLineStop "Stop" 11. //+------------------------------------------------------------------+ 12. #include <Market Replay\Auxiliar\C_Terminal.mqh> 13. //+------------------------------------------------------------------+ 14. input color user00 = clrRoyalBlue; //Color Line Price 15. input color user01 = clrForestGreen; //Color Line Take Profit 16. input color user02 = clrFireBrick; //Color Line Stop Loss 17. //+------------------------------------------------------------------+ 18. class C_IndicatorPosition 19. { 20. private : 21. struct st00 22. { 23. long id; 24. string szPrefixName; 25. }m_Infos; 26. //+------------------------------------------------------------------+ 27. void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n") 28. { 29. if (price <= 0) return; 30. (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == user00 ? ePriorityNull : (ePriorityOrders + (cor == user02)))); 31. ObjectSetDouble(m_Infos.id, szObjName, OBJPROP_PRICE, price); 32. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TEXT, szDescription); 33. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TOOLTIP, szDescription); 34. ObjectSetInteger(m_Infos.id, szObjName, OBJPROP_SELECTABLE, cor != user00); 35. } 36. //+------------------------------------------------------------------+ 37. public : 38. //+------------------------------------------------------------------+ 39. C_IndicatorPosition(const long Id) 40. { 41. ZeroMemory(m_Infos); 42. m_Infos.id = Id; 43. m_Infos.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET)); 44. CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), user00, "Position opening price."); 45. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), user01, "Take Profit point."); 46. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), user02, "Stop Loss point."); 47. } 48. //+------------------------------------------------------------------+ 49. ~C_IndicatorPosition() 50. { 51. if (m_Infos.id > 0) 52. ObjectsDeleteAll(m_Infos.id, m_Infos.szPrefixName); 53. } 54. //+------------------------------------------------------------------+ 55. }; 56. //+------------------------------------------------------------------+ 57. C_Terminal *Terminal; 58. C_IndicatorPosition *Positions; 59. //+------------------------------------------------------------------+*/ 60. int OnInit() 61. { 62. Terminal = new C_Terminal(); 63. if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED; 64. Positions = new C_IndicatorPosition((*Terminal).GetInfoTerminal().ID); 65. 66. return INIT_SUCCEEDED; 67. } 68. //+------------------------------------------------------------------+ 69. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 70. { 71. return rates_total; 72. } 73. //+------------------------------------------------------------------+ 74. void OnDeinit(const int reason) 75. { 76. delete Terminal; 77. delete Positions; 78. } 79. //+------------------------------------------------------------------+
Código fonte do Position View
Neste código visto logo acima. Você pode imaginar que adicionamos complexidade no mesmo. Mas, na verdade tudo que fizemos foi colocar, o código que antes estava fora de uma classe, dentro de uma classe. Assim, ao contrário do que possa parecer. Não adicionamos complexidade. Mas sim melhoramos a forma como podemos implementar as coisas. Já que o mesmo código que existia antes. Agora tem uma melhor segurança e nos permite dividir melhor as tarefas. O motivo de eu dizer que existe uma melhor segurança, se deve ao fato de que a função CreateLineInfos, agora é privada. Ou seja, o chamador ou utilizador, não precisa saber como o indicador será de fato criado. Ele apenas pede para que o indicador seja criado. A forma de fazer isto pode mudar bastante com o tempo. Mas para o utilizador ela sempre será a mesma.
Porém, fazer o que acabei de mostrar, não resolve nenhum dos problemas que temos. Apenas facilita que possamos implementar o código de uma forma melhor. Observe que todo o código continua igual era antes. E seu funcionamento não mudou em nada. Esta é a primeira coisa que você de fato deve sempre garantir. Não mude o código, até que ele esteja de fato funcionando, como ele funcionava antes de você o colocar em uma classe. Depois disto, o que será feito, dependerá do que você precisa ou deseja que seja feito primeiro. Mas como a classe foi criada, podemos já colocar ela em um arquivo de cabeçalho. Assim não poluímos de forma desnecessária o código principal. Então criaremos um arquivo com o mesmo nome da classe a ser colocada nele. Isto é uma boa prática de programação. Porém não é obrigatório fazer assim. Com isto o novo código principal pode ser visto logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Indicator for tracking an open position on the server." 04. #property version "1.00" 05. #property indicator_chart_window 06. #property indicator_plots 0 07. //+------------------------------------------------------------------+ 08. #include <Market Replay\Auxiliar\C_Terminal.mqh> 09. #include <Market Replay\Order System\C_IndicatorPosition.mqh> 10. //+------------------------------------------------------------------+ 11. input color user00 = clrRoyalBlue; //Color Line Price 12. input color user01 = clrForestGreen; //Color Line Take Profit 13. input color user02 = clrFireBrick; //Color Line Stop Loss 14. //+------------------------------------------------------------------+ 15. C_Terminal *Terminal; 16. C_IndicatorPosition *Positions; 17. //+------------------------------------------------------------------+ 18. int OnInit() 19. { 20. Terminal = new C_Terminal(); 21. if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED; 22. Positions = new C_IndicatorPosition((*Terminal).GetInfoTerminal().ID); 23. 24. return INIT_SUCCEEDED; 25. } 26. //+------------------------------------------------------------------+ 27. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 28. { 29. return rates_total; 30. } 31. //+------------------------------------------------------------------+ 32. void OnDeinit(const int reason) 33. { 34. delete Terminal; 35. delete Positions; 36. } 37. //+------------------------------------------------------------------+
Código fonte de Position View
Mas onde foi parar o código da classe? Bem, ele agora se encontra no arquivo, cuja localização é indicada na linha nove do código acima. Observe que a classe levou junto com ela, algumas informações que existiam aqui no código principal. Isto por que, como foi dito anteriormente, o utilizador, não precisa saber como as coisas funcionam. Ele apenas precisa saber o que chamar e quais os parâmetros a serem passados para o código da classe. A forma como a classe trabalhará os dados não interessa ao chamador. Então veja o código do arquivo de cabeçalho C_IndicatorPosition, visto logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_SufixLinePrice "Price" 05. #define def_SufixLineTake "Take" 06. #define def_SufixLineStop "Stop" 07. //+------------------------------------------------------------------+ 08. class C_IndicatorPosition 09. { 10. private : 11. struct st00 12. { 13. long id; 14. string szPrefixName; 15. }m_Infos; 16. //+------------------------------------------------------------------+ 17. void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n") 18. { 19. if (price <= 0) return; 20. (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == user00 ? ePriorityNull : (ePriorityOrders + (cor == user02)))); 21. ObjectSetDouble(m_Infos.id, szObjName, OBJPROP_PRICE, price); 22. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TEXT, szDescription); 23. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TOOLTIP, szDescription); 24. ObjectSetInteger(m_Infos.id, szObjName, OBJPROP_SELECTABLE, cor != user00); 25. } 26. //+------------------------------------------------------------------+ 27. public : 28. //+------------------------------------------------------------------+ 29. C_IndicatorPosition(const long Id) 30. { 31. ZeroMemory(m_Infos); 32. m_Infos.id = Id; 33. m_Infos.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET)); 34. CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), user00, "Position opening price."); 35. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), user01, "Take Profit point."); 36. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), user02, "Stop Loss point."); 37. } 38. //+------------------------------------------------------------------+ 39. ~C_IndicatorPosition() 40. { 41. if (m_Infos.id > 0) 42. ObjectsDeleteAll(m_Infos.id, m_Infos.szPrefixName); 43. } 44. //+------------------------------------------------------------------+ 45. }; 46. //+------------------------------------------------------------------+ 47. #undef def_SufixLinePrice 48. #undef def_SufixLineTake 49. #undef def_SufixLineStop 50. //+------------------------------------------------------------------+
Código fonte de C_IndicatorPosition.mqh
Observe que o código continua igual. Apenas foram adicionadas três novas linhas no final do arquivo. Estas têm como objetivo remover as definições que só fazem sentido existirem aqui neste arquivo de cabeçalho. Ou seja, estamos usando o critério de menor privilegio. Ou para que você, caro leitor entenda. Ninguém precisa saber, o que não lhe diz respeito. Isto evita que informações venham a vazar sem que você note. Além é claro ser uma boa prática em programação, já que outros códigos podem vir a desejar utilizar os mesmos nomes que você definiu aqui. E se você não os remover aqui, terá que fazer isto em outro local. O que causa muitos aborrecimentos quando um código muito grande tiver que ser melhorado. Pois aparecem erros que não fazem o menor sentido. Por conta de conflitos feitos em definições.
Ok. Mas olhando agora apenas e somente o código, do arquivo de cabeçalho. Você nota que existem valores sendo declarados e utilizados ali, que são dependentes do código principal. Isto é um problema, já que se o código principal mudar, este arquivo de cabeçalho também deverá mudar. Ou seja, uma baita de uma trabalheira. Sendo assim, precisamos corrigir isto. Uma boa forma, é pedir que o código principal informe os valores. De uma maneira independente. Assim o novo arquivo de cabeçalho, é mostrado logo abaixo. Com as mudanças já implementadas.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_SufixLinePrice "Price" 05. #define def_SufixLineTake "Take" 06. #define def_SufixLineStop "Stop" 07. //+------------------------------------------------------------------+ 08. class C_IndicatorPosition 09. { 10. private : 11. struct st00 12. { 13. long id; 14. string szPrefixName; 15. color corPrice, corTake, corStop; 16. }m_Infos; 17. //+------------------------------------------------------------------+ 18. void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n") 19. { 20. if (price <= 0) return; 21. (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop)))); 22. ObjectSetDouble(m_Infos.id, szObjName, OBJPROP_PRICE, price); 23. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TEXT, szDescription); 24. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TOOLTIP, szDescription); 25. ObjectSetInteger(m_Infos.id, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice); 26. } 27. //+------------------------------------------------------------------+ 28. public : 29. //+------------------------------------------------------------------+ 30. C_IndicatorPosition(const long Id, color corPrice, color corTake, color corStop) 31. { 32. ZeroMemory(m_Infos); 33. m_Infos.id = Id; 34. m_Infos.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET)); 35. CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), m_Infos.corPrice = corPrice, "Position opening price."); 36. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), m_Infos.corTake = corTake, "Take Profit point."); 37. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), m_Infos.corStop = corStop, "Stop Loss point."); 38. } 39. //+------------------------------------------------------------------+ 40. ~C_IndicatorPosition() 41. { 42. if (m_Infos.id > 0) 43. ObjectsDeleteAll(m_Infos.id, m_Infos.szPrefixName); 44. } 45. //+------------------------------------------------------------------+ 46. }; 47. //+------------------------------------------------------------------+ 48. #undef def_SufixLinePrice 49. #undef def_SufixLineTake 50. #undef def_SufixLineStop 51. //+------------------------------------------------------------------+
C_IndicatorPosition.mqh
Note que agora o constructor, precisará receber três novos argumentos. Estes informam as cores de cada uma das linhas. Assim o código principal, deverá ser mudado no seguinte ponto mostrado no fragmento abaixo.
17. //+------------------------------------------------------------------+ 18. int OnInit() 19. { 20. Terminal = new C_Terminal(); 21. if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED; 22. Positions = new C_IndicatorPosition((*Terminal).GetInfoTerminal().ID, user00, user01, user02); 23. 24. return INIT_SUCCEEDED; 25. } 26. //+------------------------------------------------------------------+
Fragmento do Indicador
Simples assim. Agora já temos um código quase totalmente independente. Isto por que ainda temos a dependência de uma leitura por parte do código principal, a respeito da existência ou não de alguma posição. Isto é feito na linha 21 no fragmento acima. Então vamos corrigir isto. Pois queremos que o indicador venha a funcionar em contas do tipo HEDGING. Já que nelas poderemos ter mais de uma posição em aberto ao mesmo tempo e no mesmo ativo. Mas para resolver isto, iremos precisar mudar o código de uma maneira um pouco mais profunda. Porém, como dividimos as coisas, fazer tal mudança se torna bem mais simples e agradável. Assim sendo, o novo código do indicador pode ser observado logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Indicator for tracking an open position on the server." 04. #property version "1.00" 05. #property indicator_chart_window 06. #property indicator_plots 0 07. //+------------------------------------------------------------------+ 08. #define def_ShortName "Position View" 09. //+------------------------------------------------------------------+ 10. #include <Market Replay\Order System\C_IndicatorPosition.mqh> 11. //+------------------------------------------------------------------+ 12. input color user00 = clrRoyalBlue; //Color Line Price 13. input color user01 = clrForestGreen; //Color Line Take Profit 14. input color user02 = clrFireBrick; //Color Line Stop Loss 15. //+------------------------------------------------------------------+ 16. C_IndicatorPosition *Positions; 17. //+------------------------------------------------------------------+*/ 18. int OnInit() 19. { 20. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 21. Positions = new C_IndicatorPosition(user00, user01, user02); 22. if (!Positions.CheckCatch()) 23. { 24. ChartIndicatorDelete(ChartID(), 0, def_ShortName); 25. return INIT_FAILED; 26. } 27. 28. return INIT_SUCCEEDED; 29. } 30. //+------------------------------------------------------------------+ 31. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 32. { 33. return rates_total; 34. } 35. //+------------------------------------------------------------------+ 36. void OnDeinit(const int reason) 37. { 38. delete Positions; 39. } 40. //+------------------------------------------------------------------+
Código fonte de Position View
Note que temos algumas mudanças bem simples no código do procedimento OnInit. Basicamente, agora estamos trabalhando de tal forma, que se o indicador falhar, ele será removido do gráfico. Isto é conseguido, por meio da linha 20 e 24. A linha 24 é justamente a que removerá o indicador caso ele não possa permanecer no gráfico. Mas que tipo de condições impedem do indicador permanecer no gráfico? Bem, neste momento apenas uma condição. E para entender isto vamos ver o código que está sendo chamando nesta linha 22. Assim partimos para o novo código da classe que pode ser visto logo a seguir.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_SufixLinePrice "Price" 05. #define def_SufixLineTake "Take" 06. #define def_SufixLineStop "Stop" 07. //+------------------------------------------------------------------+ 08. #include "..\Auxiliar\C_Terminal.mqh" 09. //+------------------------------------------------------------------+ 10. class C_IndicatorPosition : private C_Terminal 11. { 12. private : 13. struct st00 14. { 15. ulong ticket; 16. string szPrefixName; 17. color corPrice, corTake, corStop; 18. }m_Infos; 19. //+------------------------------------------------------------------+ 20. void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n") 21. { 22. if (price <= 0) return; 23. CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop)))); 24. ObjectSetDouble(GetInfoTerminal().ID, szObjName, OBJPROP_PRICE, price); 25. ObjectSetString(GetInfoTerminal().ID, szObjName, OBJPROP_TEXT, szDescription); 26. ObjectSetString(GetInfoTerminal().ID, szObjName, OBJPROP_TOOLTIP, szDescription); 27. ObjectSetInteger(GetInfoTerminal().ID, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice); 28. } 29. //+------------------------------------------------------------------+ 30. public : 31. //+------------------------------------------------------------------+ 32. C_IndicatorPosition(color corPrice, color corTake, color corStop) 33. :C_Terminal() 34. { 35. ZeroMemory(m_Infos); 36. m_Infos.corPrice = corPrice; 37. m_Infos.corTake = corTake; 38. m_Infos.corStop = corStop; 39. } 40. //+------------------------------------------------------------------+ 41. ~C_IndicatorPosition() 42. { 43. if (m_Infos.ticket != 0) 44. ObjectsDeleteAll(GetInfoTerminal().ID, m_Infos.szPrefixName); 45. } 46. //+------------------------------------------------------------------+ 47. bool CheckCatch(void) 48. { 49. for (int count = PositionsTotal() - 1; count >= 0; count--, m_Infos.ticket = 0) 50. if ((m_Infos.ticket = PositionGetTicket(count)) > 0) 51. if (PositionGetString(POSITION_SYMBOL) == GetInfoTerminal().szSymbol) 52. { 53. m_Infos.szPrefixName = IntegerToString(m_Infos.ticket); 54. if (ObjectFind(GetInfoTerminal().ID, m_Infos.szPrefixName + def_SufixLinePrice) < 0) 55. break; 56. } 57. if (m_Infos.ticket == 0) return false; 58. IndicatorSetString(INDICATOR_SHORTNAME, IntegerToString(m_Infos.ticket)); 59. CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), m_Infos.corPrice, "Position opening price."); 60. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), m_Infos.corTake, "Take Profit point."); 61. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), m_Infos.corStop, "Stop Loss point."); 62. 63. return true; 64. } 65. //+------------------------------------------------------------------+ 66. }; 67. //+------------------------------------------------------------------+ 68. #undef def_SufixLinePrice 69. #undef def_SufixLineTake 70. #undef def_SufixLineStop 71. //+------------------------------------------------------------------+
C_IndicatorPosition.mqh
Perceba que o código está crescendo e ficando cada vez mais complexo. Porém estou indo aos poucos, para que você, caro leitor, consiga de fato acompanhar o que estou fazendo. Isto por que, entender a fundo este código, será muito importante no futuro. Então não tenha pressa. Continue acompanhando com calma e atenção as mudanças que estão ocorrendo. Pois mostrarei e explicá-las aos poucos. Para que o entendimento seja o melhor possível.
Observem, que desde a última vez onde vimos este código, ele não tinha nenhuma include. Agora tem. E esta é justamente a que torna possível que o código principal, precise de menos coisas sendo digitadas. Além é claro, ajudar em outros pontos na codificação. Na linha 10, incluímos via herança a classe C_Terminal a classe C_IndicatorPosition. Esta herança, permite que usemos, pelo menos por hora, os procedimentos públicos da classe C_Terminal, diretamente na classe C_IndicatorPosition. Seria como se esta classe C_IndicatorPosition, tivesse mais funções do que as existentes no MQL5. Mas isto é apenas uma ilusão. Pois as funções usadas, estão de fato na classe C_Terminal.
Porém note que você não poderá usar estas mesmas funções da classe C_Terminal, fora da classe C_IndicatorPosition. Isto por que a herança é privada. O que impede que os procedimentos da classe C_Terminal, sejam acessados externamente via classe C_IndicatorPosition.
Mas como, estamos herdando a classe C_Terminal. Precisamos inicializa-la antes de usar a classe C_IndicatorPosition. Isto é feito na linha 33. Agora observe que no constructor, removemos o antigo código presente ali. E deixamos apenas o código que inicializará as variáveis internas da classe. Ok. Agora partimos para a linha 47. Onde temos uma função que é responsável por testar se o indicador deverá ou não permanecer no gráfico. Isto durante o processo de inicialização.
No loop da linha 49 é onde fazemos a cobertura, a partir deste momento, das posições em conta HEDGING. Até então somente fazíamos a cobertura de contas do tipo NETTING. Observe que iremos varrer a lista de posições em aberto, a procura de uma posição ainda não capturada. E como fazemos isto? Bem, a cada interação do laço, faremos um teste na linha 50. Se este teste for positivo, faremos um novo teste. Isto na linha 51. Agora atenção. Caso este segundo teste na linha 51, seja positivo, iremos na linha 53, gerar um prefixo para o nome dos objetos a serem criados. Até neste ponto tudo igual era antes. Claro, com exceção de que agora poderemos olhar mais de uma posição aberta em um mesmo ativo. Que é justamente o que acontece em uma conta HEDGING.
Ótimo. Agora vem o grande detalhe. Como o indicador saberá se a posição já foi ou não analisada? Preste atenção a isto. Cada indicador de posição, presente no gráfico. Irá observar, ou pertencer a uma única posição. Pode haver diversas posições abertas. Porém, cada indicador pertencerá a somente uma posição. Um mesmo indicador, não, e repito, NÃO observará mais que uma posição. Assim para que o indicador, saiba, se a posição já está ou não sendo analisada por outro indicador de posição. Fazemos uma varredura nos objetos presentes no gráfico. Isto é feito na linha 54.
Caso o MetaTrader 5, informe que o objeto já se encontra no gráfico. Iremos procurar uma nova posição aberta no mesmo ativo. E o laço na linha 49 será executado novamente. Este tipo de coisa acontecerá até que uma das duas condições ocorram. A primeira, é que não tenhamos mais posições em aberto. A segunda é que a linha 55 seja executada.
De qualquer maneira, a linha 57, será executada em algum momento. Caso não tenhamos nenhum valor na variável ticket. Iremos retornar um valor falso. Indicando ao código principal que o indicador deverá ser removido do gráfico. Caso tenhamos algum valor na variável ticket. Iremos dar um novo nome ao indicador, para poder acessar ele depois e criaremos as linhas conforme foi visto até então. No final retornaremos um valor verdadeiro. Isto na linha 63. Desta maneira, é que conseguiremos fazer com que o indicador, consiga trabalhar da mesma forma tanto em contas HEDGING, quanto em contas NETTING. Sem nenhum tipo de testagem extra.
Considerações finais
Neste artigo, apresentei as mudanças necessárias, para que o indicador de posições abertas, seja de fato funcional em contas HEDGING e NETTING. É bem verdade, e você pode notar isto muito facilmente, de que este indicador, até o momento, não faz nada além de mostrar as linhas onde os preços referentes a uma dada posição estão. Além disto, este indicador, não consegue perceber que a posição já não existe. Para que ele venha de fato a notar tal condição, é preciso que você, como operador, mude o tempo gráfico.
Assim o indicador perceberá que uma posição foi fechada. Notando isto, ele deixará o gráfico, sendo desta maneira removido naturalmente, sem nenhuma intervenção extra. No próximo artigo iremos melhorar ainda mais este indicador, adicionando um pouco mais de funcionalidades a ele.
Arquivo | Descrição |
---|---|
Experts\Expert Advisor.mq5 | Demonstra a interação entre o Chart Trade e o Expert Advisor (É necessário o Mouse Study para interação) |
Indicators\Chart Trade.mq5 | Cria a janela para configuração da ordem a ser enviada (É necessário o Mouse Study para interação) |
Indicators\Market Replay.mq5 | Cria os controles para interação com o serviço de replay/simulador (É necessário o Mouse Study para interação) |
Indicators\Mouse Study.mq5 | Permite interação entre os controles gráficos e o usuário (Necessário tanto para operar o replay simulador, quanto no mercado real) |
Indicators\Order Indicator.mq5 | Responsável pela indicação de ordens de mercado, permitindo interação e controle das mesmas |
Indicators\Position View.mq5 | Responsável pela indicação de posições de mercado, permitindo interação e controle das mesmas |
Services\Market Replay.mq5 | Cria e mantém o serviço de replay e simulação de mercado ( Arquivo principal de todo o sistema ) |





- 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