Simulação de mercado: Position View (XX)
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 (XIX), adicionamos uma forma de poder analisar se estamos ou não ajustando corretamente o stop loss ou take profit. Isto para que estes dois pontos possam ser corretamente configurados, a fim de que o preço atual do ativo. Fique entre o stop loss e o take profit. Você deve ter notado que isto foi feito de uma maneira bastante simples e prática. Tenho tentado manter o foco dos artigos, neste quesito. Ou seja, que as mudanças sejam bastante simples, fazendo o melhor uso possível do código já implementado. E sempre tentando ser o mais didático possível. Isto para que você, meu caro leitor, consiga acompanhar o que está sendo desenvolvido. Mas principalmente, para que você, consiga modificar o código, a fim de conseguir implementar uma solução que lhe seja mais adequada.
Pois bem, aqui neste artigo, não será diferente. Tempos atrás, começamos a implementar o que será melhor explorado, e de fato implementado aqui. Talvez, o que será visto neste artigo, não lhe pareça assim tão necessário, ser implementado. Mas garanto a você, que será algo bastante interessante. Isto pelo ponto de vista, de como faremos a implementação. Já que vamos implementar uma pequena modificação, que tem como objetivo, mostrar ao operador ou usuário, onde estava anteriormente o ponto de stop loss ou take profit. Isto, antes de você começar a mover ele para uma nova posição.
Para que você compreenda melhor, o que será implementado. Quero que você entenda o seguinte: Depois de selecionarmos um dos segmentos, seja ele o take profit ou stop loss. Isto para mudar o ponto onde ele se encontra. Não fazemos a atualização do valor, ao mesmo tempo que o estamos movendo. É preciso que finalizemos de alguma maneira, seja clicando, seja pressionando ESC, a movimentação. Somente depois disto, é que o Expert Advisor, recebe um pedido do indicador de posição, para que seja enviado um requerimento ao servidor.
Este requerimento tem como objetivo, mudar o valor que estava originalmente sendo, o take profit ou stop loss. Porém durante esta movimentação, o valor antigo continua sendo o mesmo dentro do servidor. E é isto que iremos implementar neste artigo. Criaremos uma forma, de mostrar onde o preço ainda se encontra. Assim, caso a posição seja fechada antes de concluirmos a movimentação para um novo ponto. Saberemos o motivo. Não ficando uma coisa meio que estranha no uso do indicador de posição.
Para criar, ou melhor modificar o indicador de posição a fim de conseguir fazer isto. Será preciso que você entenda alguns detalhes. Então vamos para o primeiro tópico deste artigo. Para assim começar a implementar as mudanças necessárias.
Entendendo como criar uma sombra do preço
Conforme foi dito na introdução deste artigo. Aqui iremos criar um tipo de sombra. Esta tem como objetivo mostrar onde o preço se encontrava, ou ainda se encontra antes de que a movimentação, que estamos fazendo seja concluída. Criar esta sobra é algo bastante interessante pelo ponto de vista, da implementação. Já que na verdade não iremos fazer as coisas de uma maneira completamente maluca. Vamos simplesmente dizer ao MetaTrader 5, como ele deverá entender e lidar com os objetos do indicador de posição.
Muito bem, neste ponto do desenvolvimento, você deve estar pensando que adicionar qualquer nova funcionalidade ao indicador, irá nos forçar a criar muito código. Ou no mínimo, modificar o código já existente de uma forma bastante profunda. Mas não é bem isto, que será preciso ser feito, meu caro leitor. Como o código tem sido pensado para ser o mais modular possível. Precisaremos mudar muito pouco no código. Talvez uma ou outra nova rotina venha a ser necessário. Mas logo de início, devemos sempre tentar, usar o que já está implementado. Isto para que a quantidade de testes, a fim de garantir que tudo esteja funcionando, seja a menor possível. Este tipo de coisa, nos permite agilizar ao máximo o desenvolvimento de qualquer aplicação.
Assim, tendo esta ideia de que devemos modificar, sempre que possível, o mínimo de código. Vamos ver como podemos criar a tal sombra. Em primeiro lugar, precisamos definir uma questão importante. As cores. Isto por que, não queremos criar confusão ao olhar o gráfico. Então definimos as novas cores como mostrado abaixo. Assim como a questão dos novos nomes. Isto para que possamos separar os objetos que fazem parte da sombra dos objetos que não fazem parte. Para isto, vamos precisar adicionar algo bem simples ao código, como pode ser visto no fragmento abaixo.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #define def_NameHLine m_Info.szPrefixName + "#HLINE" 005. #define def_NameBtnClose m_Info.szPrefixName + "#CLOSE" 006. #define def_NameBtnMove m_Info.szPrefixName + "#MOVE" 007. #define def_NameInfoDirect m_Info.szPrefixName + "#DIRECT" 008. #define def_NameObjLabel m_Info.szPrefixName + "#PROFIT" 009. #define def_NameBackGround m_Info.szPrefixName + "#BACKGROUND" 010. #define def_NameVolume m_Info.szPrefixName + "#VOLUME" 011. //+------------------------------------------------------------------+ 012. #define def_GhostColor clrDarkGray 013. #define macro_GhostName(A) (m_Info.szPrefixName + "[GHOST]" + (A != "" ? StringSubstr(A, StringFind(A, "#")) : "")) 014. //+------------------------------------------------------------------+ 015. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1)); 016. //+------------------------------------------------------------------+ . . . 332. //+------------------------------------------------------------------+ 333. #undef def_GhostColor 334. #undef macro_GhostName 335. //+------------------------------------------------------------------+ 336. #undef macro_LineInFocus 337. //+------------------------------------------------------------------+ 338. #undef def_Btn_Close 339. #undef def_PathBtns 340. #undef def_FontName 341. #undef def_FontSize 342. //+------------------------------------------------------------------+ 343. #undef def_NameVolume 344. #undef def_NameBackGround 345. #undef def_NameObjLabel 346. #undef def_NameInfoDirect 347. #undef def_NameBtnMove 348. #undef def_NameBtnClose 349. #undef def_NameHLine 350. //+------------------------------------------------------------------+
Fragmento de C_ElementsTrade
Note como a coisa começa a surgir. Na linha 12, definimos a cor a ser usada. Já na linha 13 definimos uma macro, cujo objetivo, é permitir que troquemos o nome dos objetos. Mas está macro não irá trabalha sozinha. Ela é apenas uma pequena parte de um código ainda maior. Mas vamos com calma. Primeiro comece prestando atenção, ao fato de que nas linhas 333 e 334, eu já coloco o código, a fim de remover as definições feitas nas linhas 12 e 13. Agora antes de prosseguirmos, vamos entender o que acontece na macro da linha 13. Para entender, observe os nomes dos objetos entre as linhas quatro e dez. Veja que todos começam com um valor predefinido. Mas todos tem algo em comum, que é justamente o carácter #. Este é o ponto que nos interessa.
Na macro, vamos continuar com o valor predefinido sendo utilizado. Isto para simplificar a remoção dos objetos criados pelo indicador de posição. Mas entre o nome original, que se encontra definido entre as linhas quatro e dez. Iremos adicionar algo. Este algo é[GHOST]. Mas manteremos o restante do nome original. Esta é justamente a tarefa feita pelas chamadas StringSubstr e StringFind. Elas têm o objetivo de remover os valores de m_info.szPrefixName e manter apenas o restante do nome original. Assim, por exemplo ao usarmos a macro para mudar o nome do objeto de fundo. O nome que anteriormente seria visto como por exemplo: 1685452684#BACKGROUND, agora seria visto como: 1685452684[GHOST]#BACKGROUND. Isto apenas usando a macro. Mas como eu disse, está macro, não irá trabalha sozinha. Precisamos de mais código para sustentar as coisas. Pois se trocarmos o nome dos objetos, sem garantir que eles serão acessíveis de alguma forma, corremos o risco de bagunçar completamente as coisas.
Para que você entenda como as coisas acontecerão. Vamos ver um código bem simples, onde mostro como a troca de nome funciona. Este código pode ser visto na íntegra logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property version "1.00" 04. #property indicator_chart_window 05. #property indicator_plots 0 06. //+------------------------------------------------------------------+ 07. #define prefixName "Obj #" 08. #define macro_Swap(A) (prefixName + "[GHOST]" + StringSubstr(A, StringFind(A, "#") + 1)) 09. #define def_ObjectName(A) (prefixName + (string)(A)) 10. //+------------------------------------------------------------------+ 11. void ChartOfTest(string sz1, int x, int y, int zOrder) 12. { 13. long id = ChartID(); 14. 15. ObjectCreate(id, sz1, OBJ_CHART, 0, 0, 0); 16. ObjectSetInteger(id, sz1, OBJPROP_XDISTANCE, x); 17. ObjectSetInteger(id, sz1, OBJPROP_YDISTANCE, y); 18. ObjectSetInteger(id, sz1, OBJPROP_ZORDER, zOrder); 19. ObjectSetInteger(id, sz1, OBJPROP_SELECTABLE, true); 20. } 21. //+------------------------------------------------------------------+ 22. int OnInit() 23. { 24. ChartOfTest(def_ObjectName(1), 160, 140, 1); 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 OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 35. { 36. switch (id) 37. { 38. case CHARTEVENT_OBJECT_CLICK: 39. if (StringSubstr(sparam, 0, StringFind(sparam, "#") + 1) == prefixName) 40. ObjectSetString(0, def_ObjectName(1), OBJPROP_NAME, macro_Swap(def_ObjectName(1))); 41. break; 42. } 43. } 44. //+------------------------------------------------------------------+ 45. void OnDeinit(const int reason) 46. { 47. ObjectsDeleteAll(ChartID(), prefixName); 48. } 49. //+------------------------------------------------------------------+
Código de Demonstração
Sei que talvez este código possa lhe parecer muito confuso, meu caro leitor. Mas não se engane. Ele é bastante simples. Tudo que estamos fazendo aqui, é justamente testar e entender, o que será feito no indicador de posição. Note que a ideia é criar um objeto, que no caso é um OBJ_CHART. Este é criado na linha 15. Com o nome definido na linha 24. Para dizer qual é este nome, usamos a definição da linha nove. Nesta definição temos um prefixo. Este é definido na linha sete. O uso do prefixo, nos permite remover o objeto OBJ_CHART, que este código criou de maneira bem simples. Isto na linha 47. Agora preste atenção. Quando você colocar esta aplicação em um gráfico. E pressionar a combinação CTRL + B, lhe será apresentada uma janela para que você veja todos objetos no gráfico. Esta pode ser vista na imagem logo abaixo:

Caso o OBJ_CHART, não apareça na lista, basta clicar no botão LIST ALL, como mostrada na imagem. Assim note o nome que o MetaTrader 5, está mostrando. Ok, agora feche esta janela que lista os objetos e clique no OBJ_CHART. Isto fará com que o MetaTrader 5, dispare um evento e este será capturado pela linha 38 do nosso código de teste. Ao fazer isto, na linha 39 iremos averiguar se o nome do objeto que recebeu o clique, tem como prefixo, a definição feita na linha sete. Caso isto seja verdadeiro, na linha 40, iremos trocar o nome do objeto OBJ_CHART por um outro nome. Mas qual nome?
Bem, este novo nome é descrito na macro. Veja que precisamos dizer a macro o nome original do objeto. Assim, a macro da linha oito, criaremos um novo nome para o objeto. Isto com base no nome original que o objeto já continha. Observe como o código da macro, se parece com o código que estamos colocando no arquivo de cabeçalho C_ElementsTrade.mqh. Muito bem, com um novo nome definido. Pressionamos novamente CTRL + B e podemos verificar que agora o nome do objeto é outro. Como mostrado na imagem abaixo.

Caso você nunca tenha feito isto antes. Experimente para ver os resultados. Em alguns casos pode ser algo bastante interessante de ser feito. E é justamente este mesmo mecanismo que iremos usar no indicador de posição. Isto para conseguir criar a tal sombra. Agora que já temos as bases e o conhecimento necessário. Podemos continuar, mas para separar as coisas vamos a um novo tópico.
Criando a sombra do preço
Agora que você, aprendeu que podemos trocar o nome dos objetos. Caso você ainda não soubesse que tal coisa era possível. É hora de fazer uso deste conhecimento. Isto para criar a tal sombra que desejamos. Bem, antes vamos entender como isto será feito. Se criamos novos objetos, eles estarão na parte superior da lista de objetos. E já vimos o que acontece, em artigos anteriores. Os novos objetos irão sobrepor os antigos. Ficando os antigos como sendo objetos de fundo. Muito bem, então para criar a tal sombra, a maneira mais simples é manter os objetos antigos no gráfico. Mudar o nome deles para um outro. E logo em seguida criar os novos objetos. Simples assim.
Mas o código atual, não nos permite visualizar os objetos, caso venhamos a mudar os seus nomes. Antes de começarmos a ver as modificações. Quero dizer, que neste primeiro momento, não nos interessa de fato, que o código não seja duplicado. O que de fato nos interessa é que ele venha a funcionar da forma como esperamos, que ele funcione. Ou seja, que possamos criar a sombra do preço, ao mesmo tempo em que fazemos as coisas, funcionarem como era antes. Assim precisamos de fato, em primeiro lugar resolver a questão de mostrar a sombra. Isto a fim de conseguir avançar na implementação. Para fazer isto, basta que modifiquemos o código como mostrado no fragmento abaixo.
029. //+------------------------------------------------------------------+ 030. class C_ElementsTrade : private C_Mouse 031. { 032. private : 033. //+------------------------------------------------------------------+ 034. struct stInfos 035. { 036. struct st_01 037. { 038. short Width, 039. Height, 040. digits; 041. }Text; 042. ulong ticket; 043. string szPrefixName, 044. szDescr, 045. szSymbol, 046. szMsgGhost; 047. EnumEvents ev; 048. double price, 049. open, 050. volume, 051. var, 052. tickSize, 053. tpsl, 054. limit, 055. priceGhost; 056. bool bClick, 057. bIsBuy; 058. char weight; 059. color _color; 060. int sizeText; 061. enum e1 {eValue, eFinance, eTicks, ePercentage} ViewMode; 062. }m_Info; 063. //+------------------------------------------------------------------+ 064. short UpdateViewPort(const double price, short size = 0, uint ui = 0) 065. { 066. static short _SizeControls; 067. static short _Width; 068. uint x, y; 069. 070. if (size > 0) 071. { 072. size += (short)(ui + 8); 073. _SizeControls = (_SizeControls > size ? _SizeControls : size); 074. size = (short)(_SizeControls - ui - 12); 075. _Width = (_Width > size ? _Width : size); 076. }else 077. { 078. ChartTimePriceToXY(0, 0, 0, price, x, y); 079. x = 125 + (m_Info.ev == evMsgClosePositionEA ? 0 : _Width + (m_Info.ev == evMsgCloseTakeProfit ? _SizeControls : (_SizeControls * 2))); 080. ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x); 081. ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0)); 082. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x); 083. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 084. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XDISTANCE, x + 10 + (m_Info.ev == evMsgClosePositionEA ? _Width + 2 : 0)); 085. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2)); 086. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + (_Width * 2) + 20); 087. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y); 088. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + _Width + 20); 089. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y); 090. ObjectSetInteger(0, def_NameBackGround, OBJPROP_XDISTANCE, x - 10); 091. ObjectSetInteger(0, def_NameBackGround, OBJPROP_YDISTANCE, y - ((m_Info.Text.Height + 5) / 2)); 092. ObjectSetInteger(0, def_NameVolume, OBJPROP_XDISTANCE, x + 10); 093. ObjectSetInteger(0, def_NameVolume, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2)); 094. if (m_Info.szMsgGhost != "") 095. { 096. ChartTimePriceToXY(0, 0, 0, m_Info.priceGhost, x, y); 097. x = 125 + (m_Info.ev == evMsgClosePositionEA ? 0 : _Width + (m_Info.ev == evMsgCloseTakeProfit ? _SizeControls : (_SizeControls * 2))); 098. ObjectSetInteger(0, macro_GhostName(def_NameHLine), OBJPROP_XDISTANCE, x); 099. ObjectSetInteger(0, macro_GhostName(def_NameHLine), OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0)); 100. ObjectSetInteger(0, macro_GhostName(def_NameObjLabel), OBJPROP_XDISTANCE, x + 10 + (m_Info.ev == evMsgClosePositionEA ? _Width + 2 : 0)); 101. ObjectSetInteger(0, macro_GhostName(def_NameObjLabel), OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2)); 102. ObjectSetInteger(0, macro_GhostName(def_NameBtnMove), OBJPROP_XDISTANCE, x + _Width + 20); 103. ObjectSetInteger(0, macro_GhostName(def_NameBtnMove), OBJPROP_YDISTANCE, y); 104. ObjectSetInteger(0, macro_GhostName(def_NameBackGround), OBJPROP_XDISTANCE, x - 10); 105. ObjectSetInteger(0, macro_GhostName(def_NameBackGround), OBJPROP_YDISTANCE, y - ((m_Info.Text.Height + 5) / 2)); 106. } 107. } 108. return _Width; 109. } 110. //+------------------------------------------------------------------+
Fragmento de C_ElementsTrade
Aqui nós já temos algumas poucas mudanças no código. Primeiro você pode ver na linha 46, declaramos uma nova variável. Assim como na linha 55. Ambas variáveis são inicializadas no constructor, como todas a demais. Porém devido a forma como o constructor inicializa a estrutura stInfos. Os valores de ambas serão padrão. Ou seja, a variável szMsgGhost, inicializa vazia. Já a variável priceGhost, com o valor zero. Então não precisamos no preocupar. Apenas preste atenção ao fato de que o teste na linha 94, garante que não iremos ficar tentando plotar algo que não se encontra no gráfico. Como inicialmente o valor de m_Info.szMsgGhost está vazio, o teste irá falhar. Talvez você esteja pensando: Mas por que não testar o valor de priceGhost. Que com certeza seria muito mais simples? Sim, de fato testar m_Info.priceGhost, seria mais simples. Porém, você verá que isto nos traria alguns inconvenientes. Por este motivo, estamos testando m_Info.szMsgGhost.
Observe que todo o código entre as linhas 96 e 105, se parece em muito com o código que posiciona os objetos que não são as sombras. Claro, apesar de parecer bastante, o mesmo contem pequenas peculiaridades, como você pode notar facilmente.
Muito bem, agora já podemos mostrar no gráfico, os objetos que serão a sombra dos originais, que indicam onde o preço se encontrava. Assim a próxima coisa a ser feita é vista no fragmento logo a seguir.
160. //+------------------------------------------------------------------+ 161. inline void AdjustDinamic(const string szObj, const string szTxt) 162. { 163. uint w, h; 164. 165. TextSetFont(def_FontName, def_FontSize * -10); 166. TextGetSize(szTxt, w, h); 167. m_Info.Text.Height = (uchar) h + 4; 168. m_Info.Text.Width = (uchar) w + 4; 169. m_Info.Text.Width = UpdateViewPort(0, m_Info.Text.Width, h = 32); 170. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, m_Info.Text.Width); 171. ObjectSetInteger(0, szObj, OBJPROP_YSIZE, m_Info.Text.Height); 172. ObjectSetInteger(0, def_NameBackGround, OBJPROP_XSIZE, m_Info.Text.Width + h + (m_Info.ev == evMsgClosePositionEA ? m_Info.Text.Width + 8 : 0)); 173. ObjectSetInteger(0, def_NameBackGround, OBJPROP_YSIZE, m_Info.Text.Height + 5); 174. if (m_Info.szMsgGhost != "") 175. { 176. ObjectSetInteger(0, macro_GhostName(def_NameBackGround), OBJPROP_XSIZE, m_Info.Text.Width + h + (m_Info.ev == evMsgClosePositionEA ? m_Info.Text.Width + 8 : 0)); 177. ObjectSetInteger(0, macro_GhostName(def_NameBackGround), OBJPROP_YSIZE, m_Info.Text.Height + 5); 178. ObjectSetString(0, macro_GhostName(def_NameObjLabel), OBJPROP_TEXT, m_Info.szMsgGhost); 179. } 180. } 181. //+------------------------------------------------------------------+ 182. inline void ChartChange(void) 183. { 184. UpdateViewPort(MathAbs(m_Info.price)); 185. m_Info.limit = (m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price); 186. if (m_Info.ev != evMsgClosePositionEA) 187. ViewValue(m_Info.limit); 188. } 189. //+------------------------------------------------------------------+ 190. void CreateGhost(void) 191. { 192. ObjectSetInteger(0, def_NameHLine, OBJPROP_BGCOLOR, def_GhostColor); 193. ObjectSetInteger(0, def_NameHLine, OBJPROP_COLOR, def_GhostColor); 194. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, def_GhostColor); 195. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BORDER_COLOR, def_GhostColor); 196. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_COLOR, def_GhostColor); 197. ObjectSetInteger(0, def_NameBackGround, OBJPROP_BGCOLOR, def_GhostColor); 198. ObjectSetInteger(0, def_NameBackGround, OBJPROP_COLOR, def_GhostColor); 199. ObjectSetString(0, def_NameHLine, OBJPROP_NAME, macro_GhostName(def_NameHLine)); 200. ObjectSetString(0, def_NameBtnMove, OBJPROP_NAME, macro_GhostName(def_NameBtnMove)); 201. ObjectSetString(0, def_NameObjLabel, OBJPROP_NAME, macro_GhostName(def_NameObjLabel)); 202. ObjectSetString(0, def_NameBackGround, OBJPROP_NAME, macro_GhostName(def_NameBackGround)); 203. m_Info.priceGhost = MathAbs(m_Info.price); 204. m_Info.szMsgGhost = ""; 205. } 206. //+------------------------------------------------------------------+ 207. public : 208. //+------------------------------------------------------------------+
Fragmento de C_ElementsTrade
Aqui temos um fragmento um pouco maior. Mas apesar de ele ser bastante simples, existem algumas coisas que merecem algum destaque aqui. A primeira coisa que você irá notar, é que não existe mais o código RemoveAllsObjects. Que foi implementado no artigo 130 desta sequência. Ou seja, o código está sempre sofrendo melhorias, ou mudanças. Tanto que, se você estiver seguindo, à risca, os fragmentos conforme eles vão sendo postados, notará que eles se encaixam perfeitamente. Gerando assim o código fonte, naquele ponto específico do desenvolvimento.
Mas neste fragmento, quero chamar a sua atenção, meu caro leitor, para os seguintes pontos. Primeiro, na linha 178, colocamos no OBJ_EDIT, que será a sombra. O mesmo valor que está sendo informado, quando o teste na linha 174 tiver sucesso. Mas observe que o valor será sempre constante. E é importante que você note isto, pois depois irei explicar um detalhe. Que neste primeiro momento limita as coisas. Mas nada de muito grave. Apenas fique atento a esta linha 178.
Bem, com tudo, precisamos focar na sombra. E esta é criada ao executarmos o procedimento da linha 190. Esta linha é quase mágica. Já que ele pega todos os objetos originais, e os torna sombras ou fantasmas. Observe como isto acontece da mesma maneira como foi feito no tópico anterior. Só que lá estávamos apenas modificando o nome. Já aqui, além do nome, também mudamos algumas das propriedades dos objetos. De qualquer maneira, no final teremos o que precisamos. Ou seja, a criação dos objetos sombra. Agora atenção, quase todos os objetos são afetados. Mas não estamos mexendo no objeto que aparece no gráfico como sendo o botão de fechar. Este irá ser tratado em outro local.
Agora um detalhe curioso, que você pode estar se perguntando. Neste procedimento da linha 190, onde criamos a sombra, não deveríamos, enviar algo para m_Info.szMsgGhost? Pois aqui estou vendo que a variável está recebendo um valor vazio. Assim como m_Info.priceGhost está recebendo um valor que é o módulo do preço. Mas por que? Se estamos criando as sombras, deveríamos colocar um valor em m_Info.szMsgGhost, a fim de que as sombras aparecem no gráfico.
Se você pensou assim, meu caro leitor. Devo parabenizá-lo, isto por que você de fato está compreendendo como o código deveria funcionar, ou qual deveria ser a ideia inicial a ser implementada. Porém, as coisas nem sempre são muito obvias. As vezes precisamos forçar algo para obter o resultado desejado. Para compreender o motivo pelo qual as variáveis estão recebendo estes valores nas linhas 203 e 204. Será preciso entender o que se passa em outros dois pontos do código. Para simplificar, dividirei os mesmos em dois fragmentos. O primeiro é visto logo abaixo.
208. //+------------------------------------------------------------------+ 209. C_ElementsTrade(const ulong ticket, string szSymbol, const EnumEvents ev, color _color, char digits, double ticksize, string szDescr = "\n", const bool IsBuy = true) 210. :C_Mouse(0, "") 211. { 212. ZeroMemory(m_Info); 213. m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 214. m_Info._color = _color; 215. m_Info.szDescr = szDescr; 216. m_Info.bIsBuy = IsBuy; 217. m_Info.Text.digits = digits; 218. m_Info.tickSize = ticksize; 219. m_Info.szSymbol = szSymbol; 220. } 221. //+------------------------------------------------------------------+ 222. ~C_ElementsTrade() 223. { 224. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 225. ObjectsDeleteAll(0, m_Info.szPrefixName); 226. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true); 227. } 228. //+------------------------------------------------------------------+ 229. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0, const double special = -1) 230. { 231. m_Info.sizeText = 0; 232. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 233. ObjectsDeleteAll(0, m_Info.szPrefixName + "#"); 234. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true); 235. m_Info.volume = (vol > 0 ? vol : m_Info.volume); 236. m_Info.var = (var > 0 ? var : m_Info.var); 237. m_Info.tpsl = (special >= 0 ? special : m_Info.tpsl); 238. if (price > 0) 239. { 240. CreateLinePrice(); 241. CreateButtonClose(); 242. CreateObjectInfoText(def_NameObjLabel, m_Info._color); 243. } 244. CreateBoxInfo(m_Info.ev != evMsgClosePositionEA); 245. m_Info.open = open; 246. m_Info.price = (price > 0 ? price : -open); 247. if (m_Info.ev == evMsgClosePositionEA) 248. { 249. CreateObjectInfoText(def_NameVolume, clrBlack); 250. ObjectSetInteger(0, def_NameVolume, OBJPROP_BGCOLOR, clrViolet); 251. ObjectSetString(0, def_NameVolume, OBJPROP_TEXT, DoubleToString(m_Info.volume, (MathRound(m_Info.volume) != m_Info.volume ? 2 : 0))); 252. AdjustDinamic(def_NameVolume, "88888" + (MathRound(m_Info.volume) != m_Info.volume ? ".88" : "")); 253. }; 254. ChartChange(); 255. } 256. //+------------------------------------------------------------------+ 257. void ViewValue(const double profit, const bool Enabled = true) 258. { 259. string szTxt; 260. color _cor; 261. static double memSL, memTP; 262. 263. if (Enabled) 264. { 265. switch (m_Info.ViewMode) 266. { 267. case stInfos::eValue: 268. szTxt = StringFormat("%." + (string)m_Info.Text.digits + "f", MathAbs(profit)); 269. break; 270. case stInfos::eFinance: 271. szTxt = StringFormat("$ %." + (string)m_Info.Text.digits + "f", (MathAbs(profit) / m_Info.var) * m_Info.volume); 272. break; 273. case stInfos::eTicks: 274. szTxt = StringFormat("%d", (uint)MathRound(MathAbs(profit) / m_Info.tickSize)); 275. break; 276. case stInfos::ePercentage: 277. szTxt = StringFormat("%.2f%%", NormalizeDouble((MathAbs(profit) / (m_Info.open ? m_Info.open : m_Info.price)) * 100, 2)); 278. break; 279. } 280. m_Info.szMsgGhost = ((m_Info.szMsgGhost == "") && (m_Info.ev != evMsgClosePositionEA) ? szTxt : m_Info.szMsgGhost); 281. ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, szTxt); 282. if (StringLen(szTxt) != m_Info.sizeText) 283. { 284. AdjustDinamic(def_NameObjLabel, szTxt); 285. m_Info.sizeText = StringLen(szTxt); 286. } 287. } 288. _cor = (m_Info.limit >= 0 ? clrPaleGreen : clrCoral); 289. switch (m_Info.ev) 290. { 291. case evMsgCloseTakeProfit: 292. memTP = (Enabled ? memTP : profit); 293. _cor = (m_Info.limit < memTP ? clrYellow : _cor); 294. break; 295. case evMsgCloseStopLoss: 296. memSL = (Enabled ? memSL : profit); 297. _cor = (m_Info.limit > memSL ? clrYellow : _cor); 298. break; 299. case evMsgClosePositionEA: 300. _cor = (profit >= 0 ? clrPaleGreen : clrCoral); 301. break; 302. } 303. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, _cor); 304. } 305. //+------------------------------------------------------------------+
Fragmento de C_ElementsTrade
Observe que agora o destructor, que está presente na linha 222, removerá todos os objetos, como é esperado que venha a ser feito. Porém UpdatePrice, que está na linha 229, não fará isto. Até na versão anterior, tanto UpdatePrice, quanto o destructor, removia todos os objetos. Bem aqui vale uma explicação, que até o momento era implícita. Já que era imaginado que seria facilmente compreendido por todos. O fato de UpdatePrice, remover os objetos para depois os criar. Só tem sentido quando você pensa na possibilidade de que a linha de stop loss ou take profit, venham a ser removidas. Muito provavelmente você imagina que ao chamar UpdatePrice, estaremos de fato atualizando o preço, onde stop loss ou take profit estejam. Mas a realidade, é um pouco diferente.
A ideia é realmente mostrar onde estes pontos realmente estariam. Porém, caso eles não existam na posição, precisamos remover os mesmos do gráfico. Criar apenas um procedimento para fazer isto, pode à primeira vista ser uma boa ideia. Mas tomando como base, que sempre precisaríamos testar se existe ou não um stop loss ou take profit na posição. É bem mais viável simplesmente remover os objetos, e logo depois os criar e os posicionar conforme a necessidade. Por conta disto que UpdatePrice, remove os objetos do gráfico.
Porém, como aqui estamos tornando estes mesmos objetos, sombras de outros que já estão no gráfico. Temos que limitar um pouco esta remoção dos objetos. Lembra que mencionei, neste mesmo artigo, o fato de não criamos uma sombra para o botão de fechar? E que não seria necessário remover ele naquele ponto específico, pois ele seria removido em outro local? Pois bem, quando a linha 233 for executada, durante a criação da sombra. Aquele botão irá ser removido do gráfico.
Agora preste atenção. Quando temos uma linha de stop loss, ou take profit. O valor informado na linha 246 será o preço onde o stop loss ou take profit se encontra. Porém, quando um destes ou ambos valores não existirem. Permitindo assim que a posição exista, até que ela seja fechada manualmente. Ou fechada pelo servidor, se for Day Trade. O valor em m_Info.price será o preço de abertura. Porém negativo. Como precisamos saber onde o objeto de movimento deverá ser plotado. O valor negativo não servirá para nós. Precisamos assim converter ele usando a função MathAbs. Isto no momento em que estamos criando as sombras. Lá na linha 190.
Agora, vamos entender quando m_Info.szMsgGhost recebe um valor. Observe na linha 280. É ali que dizemos que a m_Info.szMsgGhost qual será o seu conteúdo. Mas espere um pouco: Esta função ViewValue é chamada em mais de um momento. Isto não irá disparar a linha 94? Não meu caro leitor. E o motivo é que estamos fazendo um duplo teste. Isto a fim de evitar que a linha 280, coloque um valor em m_Info.szMsgGhost, quando não for apropriado. Desta forma, a cópia do valor, que existe, sendo mostrada ao usuário, será mostrada no momento em que a sombra for criada. Mas existe um problema neste mecanismo, que será resolvido depois. Por hora não se preocupe com isto ainda. Mas para que tudo funcione de fato, ainda precisamos ver o segundo fragmento, do qual mencionei anteriormente. Este pode ser observado logo abaixo.
305. //+------------------------------------------------------------------+ 306. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 307. { 308. string sz0; 309. long _lparam = lparam; 310. double _dparam = dparam; 311. 312. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 313. switch (id) 314. { 315. case (CHARTEVENT_KEYDOWN): 316. if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break; 317. _lparam = (long) m_Info.ticket; 318. _dparam = 0; 319. EventChartCustom(0, evUpdate_Position, _lparam, 0, ""); 320. case CHARTEVENT_CUSTOM + evMsgSetFocus: 321. if ((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev)) 322. { 323. CreateGhost(); 324. UpdatePrice(m_Info.open, GetPositionsMouse().Position.Price); 325. }else 326. { 327. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 328. ObjectsDeleteAll(0, macro_GhostName("")); 329. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true); 330. } 331. macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev)); 332. EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, ""); 333. m_Info.bClick = false; 334. case CHARTEVENT_CHART_CHANGE: 335. ChartChange(); 336. break;
Fragmento de C_ElementsTrade
Aqui temos apenas a parte, realmente necessária a ser mostrada. Todo o restante do código permaneceu intacto. Porém esta parte é onde, de fato, tudo começa a acontecer. Observe o que está acontecendo na linha 321, onde efetuamos o teste, cuja finalidade é verificar se estamos lidando com o segmento correto. Antes apenas enviaremos um pedido para UpdatePrice. Porém agora, estamos fazendo as coisas um pouco diferente. Já que é aqui o local onde a sombra é de fato implementada. Então se este teste na linha 321, for positivo. Primeiro chamamos o procedimento que efetuará a modificação dos objetos atuais em objetos sombra. Logo depois, enviamos um pedido a UpdatePrice.
Desta forma, teremos a sombra em segundo plano, e os novos objetos que serão manipulados, em primeiro plano. Criando assim a ilusão de que estamos sombreando o antigo ponto onde o preço se encontrava. Porém você, ao usar o sistema, notará que as informações antigas serão mantidas. E este é o propósito do sombreamento da antiga posição.
Agora, caso o teste seja falso iremos simplesmente remover os objetos sombra, que estejam presentes no gráfico. Retornando o preço para a sua antiga posição, onde estava a sombra. A remoção é feita na linha 328. Já a recolocação é feita por conta do próprio sistema que já existia antes. Assim com poucas modificações no código, conseguimos o resultado que é visto na animação logo abaixo.

Você muito provavelmente, deve estar bastante curioso, com relação ao que de fato esta sombra representa. Mas é bem simples. Ela simplesmente mostra onde o take profit ou stop loss se encontra no servidor. Este tipo de situação, que pode ser vista claramente no gráfico, e na animação acima, nos permite evitar uma falsa ideia de que ao tentarmos mover a linha de preço, estamos movendo-a no servidor também. O que para usuários menos experientes ou que estejam vendo este sistema pela primeira vez. Venham a ter tal convicção. Porém vendo as coisas desta maneira, fica bem mais claro e simples de entender o que está acontecendo.
Porém como eu informei, este sistema contém uma pequena falha a ser resolvida. A falha é o fato de que quando você estiver com um segmento, seja o take profit, seja o stop loss. Selecionados para troca de posição do preço. Ao clicar no objeto OBJ_EDIT a fim de trocar a forma de visualizar os dados. Eles serão atualizados em todos objetos. Porém a sombra não recebe tal atualização. Tornando assim a interpretação dos dados um tanto quanto diferente. Alguns operadores ou usuários, podem vir a não gostar muito de utilizar o sistema devido a este pequeno inconveniente. Porém a solução, apesar de ser relativamente simples. Exige algumas mudanças na forma como o código se encontra. E como também quero remover as partes duplicadas. Isto não será visto neste artigo.
Considerações finais
Neste artigo, apresentei a você, meu caro leitor, algo que muitos imaginariam ser bastante trabalhoso de ser feito e conseguido. Criar uma sombra, a fim de mostrar ao operador ou usuário, onde o antigo preço se encontra. Apesar de o fato de dizer antigo, não é o correto, já que aquele ponto ainda estará presente na posição aberta. Sendo passivo de ser disparado, caso venha a acontecer algum negócio naquele ponto específico. Apesar do sistema, ainda precisar de alguns ajustes como mencionado durante o artigo. O mesmo se encontra funcional, a ponto de poder ser utilizado.
Porém como o objetivo destes artigos, sobre o replay/simulador, visam simplesmente serem didáticos. Você deve tomar os devidos cuidados caso venha a desejar usar qualquer uma destas aplicações em conta real. Isto por que elas não estão de fato, sendo otimizadas para tal coisa. Mas se você realmente deseja entender como tudo funciona. Sinta-se à vontade em usar e abusar no uso deste sistema em contas de demonstração.
Para aqueles que desejam experimentar o sistema, mas não sabem como construir os executáveis. No anexo estou deixando as aplicações já compiladas. É só colocá-las em meios as que você já usa, ou substituir as versões anteriores, e partir para a conta demonstração a fim de ver tudo acontecendo. Assim como foi mostrada na animação, vista no tópico anterior. Apesar de tudo, no próximo artigo, iremos corrigir a falha que existe na sombra. Onde ela não segue a troca no padrão de visualização, efetuada no indicador. E também sanar o fato de termos muito código sendo duplicado na classe C_ElementsTrade. Será algo interessante, além de que estamos a apenas um único passo de começar a usar estas quatro aplicações no nosso replay/simulador. Tudo que precisamos é corrigir alguns pouco detalhes e já poderemos voltar a trabalhar no serviço de replay/simulador. Então não perca o próximo artigo desta série sobre o replay/simulador.
| 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) |
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Construindo um Modelo de Restrição de Tendência com Candlestick (Parte 10): Golden Cross e Death Cross Estratégicos (EA)
Do básico ao intermediário: Sobrecarga de operadores (I)
MQL5 Trading Toolkit (Parte 5): Expandindo a Biblioteca EX5 de Gerenciamento de Histórico com Funções de Posição
Classes de tabela e cabeçalho baseadas no modelo de tabela em MQL5: Aplicação do conceito MVC
- 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