Simulação de mercado: Position View (X)
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 (IX), começamos a explorar a questão de como deveria ser implementada a movimentação das linhas de take profit e stop loss. Bem, como eu quero explicar a todos, e deixar bem claro, como e por que as mudanças estão acontecendo no código. Neste artigo, iremos de fato tentar resolver um problema, que foi apresentado no artigo anterior. O problema é a questão do ZOrder. Se você não sabe a importância que a propriedade ZOrder, tem nos objetos. Não se preocupe, apenas procure estudar os artigos anteriores desta mesma série. Já que ali, explico a importância de se saber definir adequadamente o valor da propriedade ZOrder.
Mas independentemente disto, aqui mora uma questão: Como podemos fazer para usar o ZOrder ao nosso favor? Bem, a resposta é simples. Não podemos. Parece ser algo absurdo e completamente sem sentido. Mas por melhor que você, meu caro leitor, possa conseguir programar. Você não conseguirá superar o que já se encontra programado no MetaTrader 5. Isto a fim de conseguir lidar com o ZOrder.
Porém, precisamos de fato, de algum meio para conseguir lidar com os objetos gráficos que serão criados. A proposta mostrada no artigo anterior, se encaixa perfeitamente bem, em alguns cenários. No entanto, aqui, precisamos de algo um pouco mais elaborado. Isto devido a natureza do problema com que estamos lidando. Assim sendo, não tentaremos de maneira alguma substituir os mecanismos que estão presentes no MetaTrader 5. Isto para conseguir lidar com o ZOrder, além é claro, verificar qual objeto está em primeiro plano ou encoberto por outro objeto. Vamos fazer algo completamente diferente. Aqui vou mostrar quais as modificações que precisam ser feitas no código a fim de conseguir, tirar de alguma forma, proveito do que o MetaTrader 5, já faz para nós. Ou seja, dizer qual objeto deve ou não ser manipulado por ter recebido um clique.
Mas para que você de fato, consiga compreender o que estaremos fazendo. É necessário primeiro ver um código bastante simples. Porém extremamente útil para podermos saber como lidar com o problema, que resolveremos.
O mais simples dos códigos
Para de fato entender as coisas, é preciso usar algo que você de fato consiga compreender. Muitos costuma pensar que para resolver um problema, temos que criar uma solução mirabolante, ou extremamente complexa. Mas não é bem assim que devemos agir na prática. Na prática devemos criar algo, que seja o mais simples quanto for possível ser feito. Usar a aplicação e entender o que ela está nos informando. Para demonstrar isto, vamos usar o código que é visto logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property indicator_chart_window 04. #property indicator_plots 0 05. //+------------------------------------------------------------------+ 06. #define debug(A) Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 07. //+------------------------------------------------------------------+ 08. int OnInit() 09. { 10. string sz1 = "Object #01"; 11. 12. ObjectCreate(0, sz1, OBJ_BUTTON, 0, 0, 0); 13. ObjectSetInteger(0, sz1, OBJPROP_XDISTANCE, 100); 14. ObjectSetInteger(0, sz1, OBJPROP_YDISTANCE, 100); 15. 16. sz1 = "Object #02"; 17. ObjectCreate(0, sz1, OBJ_BUTTON, 0, 0, 0); 18. ObjectSetInteger(0, sz1, OBJPROP_XDISTANCE, 200); 19. ObjectSetInteger(0, sz1, OBJPROP_YDISTANCE, 200); 20. 21. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 22. 23. return INIT_SUCCEEDED; 24. } 25. //+------------------------------------------------------------------+ 26. int OnCalculate(const int rates_total, 27. const int prev_calculated, 28. const int begin, 29. 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. debug(sparam); 40. break; 41. case CHARTEVENT_MOUSE_MOVE: 42. if (sparam != "0") 43. debug(sparam); 44. break; 45. } 46. }; 47. //+------------------------------------------------------------------+ 48. void OnDeinit(const int reason) 49. { 50. ObjectsDeleteAll(0, "Object", -1, OBJ_BUTTON); 51. } 52. //+------------------------------------------------------------------+
Indicador para testes
Este código é extremamente simples. Tudo que ele faz é criar dois objetos com um ZOrder padrão, ou seja, zero. E imprimir uma mensagem quando um evento acontece. Simples assim. Pode parecer algo tolo e até bobo. Mas ele tem o poder de nos mostrar algo extremamente importante e útil.
Quando você o executa no gráfico, irá ter o resultado que pode ser visto na animação logo abaixo.

Nesta animação o ponto de interesse é as mensagens que estão aparecendo. Esqueça todo restante e foque nas mensagens. Note que existe um padrão. Agora e se adicionarmos um objeto, que se sobreponha aos botões. O que aconteceria? Bem, para responder esta pergunta, basta olhar a animação a seguir.

Novamente, preste atenção as mensagens que estão sendo postadas. Agora compare as mesmas e me diga: Qual é o padrão aqui? Se você não conseguiu notar isto. Procure olhar as mensagens e o código. E pense: Qual é o padrão que existe aqui.
Se você não entendeu, ou não conseguiu perceber. O padrão é o seguinte: Quando clicamos em um objeto no gráfico. Primeiro o MetaTrader 5 dispara um evento CHARTEVENT_MOUSE_MOVE e depois um evento CHARTEVENT_OBJECT_CLICK. Saber disto é importante. Mas por que? Por que sabendo disto, podemos usar este mesmo padrão ao nosso favor. A fim de resolver o problema quando temos muitos objetos no gráfico. Então ao contrário do que muitos poderiam estar esperando. Não tentaremos de maneira alguma substituir o mecanismo existente no MetaTrader 5. Usaremos ele ao nosso favor. E para fazer isto, este padrão que você pode testar em sua plataforma, será manipulado por nossas aplicações. Isto para que quando estivermos executando um estudo usando o indicador de mouse. Possamos ignorar cliques que vierem a ser dados em objetos que tem eventos em nossas aplicações.
Você pode estar pensando: Mas como faremos isto? Já estamos bastante avançados no desenvolvimento do código. Teremos que voltar lá no começo e abandonar completamente tudo que já foi criado? Não meu caro leitor. Com venho dizendo, o propósito desta série de artigos, não é mostrar como desenvolver realmente um replay/simulador. E sim mostrar como um programador, deve de fato lidar com os problemas que vão surgindo. Muita gente imagina que criar uma aplicação é algo direto e sem problemas. Mas quem de fato, entra neste mundo, sabe que as coisas são um tanto quanto tortuosas. E muitas vezes precisamos lidar com problemas mais ou menos complexos.
Este problema do ZOrder, pode em muitos casos ser algo bastante complicado. Você meu caro leitor, poderia simplesmente imaginar que em nenhum ponto do desenvolvimento eu, como programador, tive problemas ou dificuldades. Isto por que se eu simplesmente corrigisse o código e o posta-se já sem falhas. Você poderia ter a ilusão de que tudo correu às mil maravilhas e que se você, meu estimado leitor e entusiasta, estivesse tendo dificuldades em programar algo. Seria devido ao fato de você ser um completo débil mental. Mas não é bem assim. Todos passamos por dificuldades. A forma como você as supera é que lhe faz aprender e a se tornar um profissional de melhor ou pior qualidade.
Então vamos pensar o seguinte, e quero que você, meu caro leitor, tente acompanhar o raciocínio. Como podemos usar esta informação que este simples programinha nos deu, a nosso favor. De modo que precisemos modificar o mínimo possível, o código já projetado e implementado? Bem, está de fato é uma questão que muitos acabariam por desistir em tentar solucionar. Já que parece ser uma tarefa extremamente complica. Mas como eu disse: Quero mostrar a você, meu caro leitor. Que se você parar e pensar por uns instantes, acabara compreendendo o que precisa ser feito.
Atualizando o código
Bem, antes de continuar a leitura deste artigo. Espero que você de fato tenha tentado pensar em uma solução para o problema. Pois por incrível que pareça ela é mais simples do que muitos poderiam imaginar.
A primeira coisa que faremos será remover os níveis do ZOrder. Na verdade, não removeremos, mas iremos os redefinir, de maneira que tenhamos um auxílio completo do MetaTrader 5. Isto para que o próprio MetaTrader 5, possa nos dizer facilmente, quem recebeu de fato um evento de clique. Com isto, vamos modificar o arquivo que pode ser visto logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. #define def_MaskTimeService 0xFED00000 16. #define def_IndicatorTimeFrame (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96)))) 17. #define def_IndexTimeFrame 4 18. //+------------------------------------------------------------------+ 19. union uCast_Double 20. { 21. double dValue; 22. long _long; // 1 Information 23. datetime _datetime; // 1 Information 24. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 25. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 26. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 27. }; 28. //+------------------------------------------------------------------+ 29. enum EnumEvents { 30. evTicTac, //Event of tic-tac 31. evHideMouse, //Hide mouse price line 32. evShowMouse, //Show mouse price line 33. evHideBarTime, //Hide bar time 34. evShowBarTime, //Show bar time 35. evHideDailyVar, //Hide daily variation 36. evShowDailyVar, //Show daily variation 37. evHidePriceVar, //Hide instantaneous variation 38. evShowPriceVar, //Show instantaneous variation 39. evCtrlReplayInit, //Initialize replay control 40. evChartTradeBuy, //Market buy event 41. evChartTradeSell, //Market sales event 42. evChartTradeCloseAll, //Event to close positions 43. evChartTrade_At_EA, //Event to communication 44. evEA_At_ChartTrade, //Event to communication 45. evChatWriteSocket, //Event to Mini Chat 46. evChatReadSocket, //Event To Mini Chat 47. evUpdate_Position, //Event to communication 48. evMsgClosePositionEA, //Event to communication 49. evMsgCloseTakeProfit, //Event to communication 50. evMsgCloseStopLoss, //Event to communication 51. evMsgNewTakeProfit, //Event to communication 52. evMsgNewStopLoss //Event to communication 53. }; 54. //+------------------------------------------------------------------+ 55. enum EnumPriority { //Priority list on objects 56. ePriorityNull = -1, 57. ePriorityDefault = 0 58. }; 59. //+------------------------------------------------------------------+
Defines.mqh
Note que agora temos menos tipos de enumeração em EnumPriority. Com isto, agora temos o tipo padrão, ou default, que é o zero. E temos um outro que é o ePriorityNull. Este deverá ser usado em todos os objetos que não deverão de maneira alguma receber alguma interação. Pois bem, feito esta mudança, teremos de fazer uma nova compilação de todo o nosso código. Isto para garantir que ele permaneça completamente estável. Ao tentarmos compilar o indicador Chart Trade, é gerado alguns erros vindos da classe C_ChartFloatingRAD. Eles podem ser visualizados na imagem abaixo.

Neste ponto, muitos já começam a se descabelar. Pensando: Cara o que fizemos? As coisas já não irão mais funcionar. Mas será mesmo? Bem, indo ao ponto onde o primeiro erro está sendo reportado. Ou seja, na linha 169 da classe C_ChartFloatingRAD. Veremos o motivo do erro. Então para corrigir ele, bastará mudar o código original, pelo código visto no fragmento abaixo.
164. //+------------------------------------------------------------------+ 165. template <typename T > 166. void CreateObjectEditable(eObjectsIDE arg, T value) 167. { 168. DeleteObjectEdit(); 169. CreateObjectGraphics(m_Info.szObj_Editable, OBJ_EDIT, clrBlack, ePriorityDefault); 170. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_XDISTANCE, m_Info.Regions[arg].x + m_Info.x + 3); 171. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_YDISTANCE, m_Info.Regions[arg].y + m_Info.y + 3); 172. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_XSIZE, m_Info.Regions[arg].w); 173. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_YSIZE, m_Info.Regions[arg].h); 174. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_BGCOLOR, m_Info.Regions[arg].bgcolor); 175. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_ALIGN, ALIGN_CENTER); 176. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_FONTSIZE, m_Info.Regions[arg].FontSize - 1); 177. ObjectSetString(m_Init.id, m_Info.szObj_Editable, OBJPROP_FONT, m_Info.Regions[arg].FontName); 178. ObjectSetString(m_Init.id, m_Info.szObj_Editable, OBJPROP_TEXT, (typename(T) == "double" ? DoubleToString(value, 2) : (string) value)); 179. ChartRedraw(); 180. } 181. //+------------------------------------------------------------------+
Fragmento de C_ChartFloatingRAD.mqh
Efetuada a mudança, tentamos novamente compilar o indicador Chart Trade. E com isto, o resultado é visto logo abaixo.

Ou seja, sucesso. Podemos passar para o próximo. No caso do indicador de Mouse, não haverá de fato nenhuma mudança. Já que ele não sofreu quais quer alterações em seu ZOrder. Então o próximo passo a ser de fato executado. Seria compilar o indicador de posição. Mas como o código da classe C_ElementsTrade é onde existirá problemas no ZOrder e o mesmo código passará por mudanças extras. Não vou mostrar ainda o código modificado. Vamos para o passo a fim de utilizar aquela informação que obtivermos no tópico anterior. Ou seja, vamos implementar a assistência que o MetaTrader 5, irá nos fornecer. Isto a fim de que ao clicarmos em algo. Possamos saber exatamente no que clicamos. Porém, quando estivermos fazendo um estudo, que o clique seja ignorado.
Bem, para fazer isto, será preciso uma mudança profunda e radical. Algo extremamente complexo e difícil até de imaginar. Algo que apenas um grande guru conseguiria de fato imaginar e fazer. Então veja o qual difícil e complicada é a solução. Ela pode ser vista no código abaixo.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "Macros.mqh" 005. #include "..\Defines.mqh" 006. //+------------------------------------------------------------------+ 007. class C_Terminal 008. { 009. //+------------------------------------------------------------------+ 010. public : 011. //+------------------------------------------------------------------+ 012. struct st_Mouse 013. { 014. struct st00 015. { 016. short X_Adjusted, 017. Y_Adjusted, 018. X_Graphics, 019. Y_Graphics; 020. double Price; 021. datetime dt; 022. }Position; 023. uchar ButtonStatus; 024. bool ExecStudy; 025. string szObjNameClick; 026. }; 027. //+------------------------------------------------------------------+ 028. protected: 029. enum eErrUser {ERR_Unknown, ERR_FileAcess, ERR_PointerInvalid, ERR_NoMoreInstance}; 030. //+------------------------------------------------------------------+ 031. struct st_Terminal 032. { 033. ENUM_SYMBOL_CHART_MODE ChartMode; 034. ENUM_ACCOUNT_MARGIN_MODE TypeAccount; 035. long ID; 036. string szSymbol; 037. int Width, 038. Height, 039. nDigits, 040. SubWin, 041. HeightBar; 042. double PointPerTick, 043. ValuePerPoint, 044. VolumeMinimal, 045. AdjustToTrade; 046. }; 047. //+------------------------------------------------------------------+ 048. void CurrentSymbol(bool bUsingFull) 049. { 050. MqlDateTime mdt1; 051. string sz0, sz1; 052. datetime dt = macroGetDate(TimeCurrent(mdt1)); 053. enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER; 054. 055. sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3); 056. for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS); 057. switch (eTS) 058. { 059. case DOL : 060. case WDO : sz1 = "FGHJKMNQUVXZ"; break; 061. case IND : 062. case WIN : sz1 = "GJMQVZ"; break; 063. default : return; 064. } 065. sz0 = EnumToString((eTypeSymbol)(((eTS & 1) == 1) ? (bUsingFull ? eTS : eTS - 1) : (bUsingFull ? eTS + 1: eTS))); 066. for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0)) 067. if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break; 068. } 069. //+------------------------------------------------------------------+ 070. inline void DecodeMousePosition(int xi, int yi) 071. { 072. int w = 0; 073. 074. xi = (xi > 0 ? xi : 0); 075. yi = (yi > 0 ? yi : 0); 076. ChartXYToTimePrice(m_Infos.ID, m_Mouse.Position.X_Graphics = (short)xi, m_Mouse.Position.Y_Graphics = (short)yi, w, m_Mouse.Position.dt, m_Mouse.Position.Price); 077. m_Mouse.Position.dt = AdjustTime(m_Mouse.Position.dt); 078. m_Mouse.Position.Price = AdjustPrice(m_Mouse.Position.Price); 079. ChartTimePriceToXY(m_Infos.ID, w, m_Mouse.Position.dt, m_Mouse.Position.Price, xi, yi); 080. yi -= (int)ChartGetInteger(m_Infos.ID, CHART_WINDOW_YDISTANCE, m_Infos.SubWin); 081. m_Mouse.Position.X_Adjusted = (short) xi; 082. m_Mouse.Position.Y_Adjusted = (short) yi; 083. } 084. //+------------------------------------------------------------------+ 085. private : 086. st_Terminal m_Infos; 087. st_Mouse m_Mouse; 088. struct mem 089. { 090. long Show_Descr, 091. Show_Date; 092. bool AccountLock; 093. }m_Mem; 094. //+------------------------------------------------------------------+ 095. inline void ChartChange(void) 096. { 097. int x, y, t; 098. 099. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 100. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 101. ChartTimePriceToXY(m_Infos.ID, 0, 0, 0, x, t); 102. ChartTimePriceToXY(m_Infos.ID, 0, 0, m_Infos.PointPerTick * 100, x, y); 103. m_Infos.HeightBar = (int)(t - y) / 100; 104. } 105. //+------------------------------------------------------------------+ 106. public : 107. //+------------------------------------------------------------------+ 108. C_Terminal(const long id = 0, const uchar sub = 0, const bool bFull = false) 109. { 110. m_Infos.ID = (id == 0 ? ChartID() : id); 111. m_Mem.AccountLock = false; 112. m_Infos.SubWin = (int) sub; 113. CurrentSymbol(bFull); 114. ZeroMemory(m_Mouse); 115. m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR); 116. m_Mem.Show_Date = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE); 117. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false); 118. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, true); 119. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, true); 120. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false); 121. m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 122. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 123. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 124. m_Infos.PointPerTick = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); 125. m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); 126. m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP); 127. m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick; 128. m_Infos.ChartMode = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE); 129. if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)); 130. ChartChange(); 131. } 132. //+------------------------------------------------------------------+ 133. ~C_Terminal() 134. { 135. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, m_Mem.Show_Date); 136. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, m_Mem.Show_Descr); 137. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, false); 138. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, false); 139. } 140. //+------------------------------------------------------------------+ 141. inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg) 142. { 143. if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true; 144. m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING); 145. } 146. //+------------------------------------------------------------------+ 147. inline const st_Terminal GetInfoTerminal(void) const 148. { 149. return m_Infos; 150. } 151. //+------------------------------------------------------------------+ 152. inline const st_Mouse GetPositionsMouse(void) const 153. { 154. return m_Mouse; 155. } 156. //+------------------------------------------------------------------+ 157. const double AdjustPrice(const double arg) const 158. { 159. return NormalizeDouble(round(arg / m_Infos.PointPerTick) * m_Infos.PointPerTick, m_Infos.nDigits); 160. } 161. //+------------------------------------------------------------------+ 162. inline datetime AdjustTime(const datetime arg) 163. { 164. int nSeconds= PeriodSeconds(); 165. datetime dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0); 166. 167. return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt))); 168. } 169. //+------------------------------------------------------------------+ 170. inline double FinanceToPoints(const double Finance, const uint Leverage) 171. { 172. double volume = m_Infos.VolumeMinimal + (m_Infos.VolumeMinimal * (Leverage - 1)); 173. 174. return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade))); 175. }; 176. //+------------------------------------------------------------------+ 177. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 178. { 179. static string st_str = ""; 180. 181. switch (id) 182. { 183. case CHARTEVENT_CHART_CHANGE: 184. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 185. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 186. ChartChange(); 187. break; 188. case CHARTEVENT_MOUSE_MOVE: 189. DecodeMousePosition((int)lparam, (int)dparam); 190. break; 191. case CHARTEVENT_OBJECT_CLICK: 192. m_Mouse.szObjNameClick = sparam; 193. if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false); 194. if (ObjectGetInteger(m_Infos.ID, sparam, OBJPROP_SELECTABLE) == true) 195. ObjectSetInteger(m_Infos.ID, st_str = sparam, OBJPROP_SELECTED, true); 196. break; 197. case CHARTEVENT_OBJECT_CREATE: 198. if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false); 199. st_str = sparam; 200. break; 201. } 202. } 203. //+------------------------------------------------------------------+ 204. inline void CreateObjectGraphics(const string szName, const ENUM_OBJECT obj, const color cor = clrNONE, const EnumPriority zOrder = ePriorityNull) const 205. { 206. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, false); 207. ObjectCreate(m_Infos.ID, szName, obj, m_Infos.SubWin, 0, 0); 208. ObjectSetString(m_Infos.ID, szName, OBJPROP_TOOLTIP, "\n"); 209. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_BACK, false); 210. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_COLOR, cor); 211. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTABLE, false); 212. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTED, false); 213. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_ZORDER, zOrder); 214. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true); 215. } 216. //+------------------------------------------------------------------+ 217. bool IndicatorCheckPass(const string szShortName) 218. { 219. string szTmp = szShortName + "_TMP"; 220. 221. IndicatorSetString(INDICATOR_SHORTNAME, szTmp); 222. m_Infos.SubWin = ((m_Infos.SubWin = ChartWindowFind(m_Infos.ID, szShortName)) < 0 ? 0 : m_Infos.SubWin); 223. if (ChartIndicatorGet(m_Infos.ID, m_Infos.SubWin, szShortName) != INVALID_HANDLE) 224. { 225. ChartIndicatorDelete(m_Infos.ID, 0, szTmp); 226. Print("Only one instance is allowed..."); 227. SetUserError(C_Terminal::ERR_NoMoreInstance); 228. 229. return false; 230. } 231. IndicatorSetString(INDICATOR_SHORTNAME, szShortName); 232. 233. return true; 234. } 235. //+------------------------------------------------------------------+ 236. };
C_Terminal.mqh
Bem, a solução está implementada neste código acima. Você consegue perceber onde? Se você não está acompanhando esta sequência. Ou não está estudando este material que estou disponibilizando. Com toda a certeza não conseguirá ver a solução. E de propósito, coloquei o código inteiro da classe C_Terminal, para que você, meu caro leitor, ficasse completamente perdido e sem de fato entender onde a solução foi implementada.
Brincadeiras à parte. De fato, a solução é fazer com que o MetaTrader 5 trabalhe para nós. Mas para isto, é preciso que usemos de algum tipo de artifício. Este artifício poderia ser implementado aqui, na classe C_Terminal, ou no local onde o código implementado aqui, de fato seria usado. Mas você pode estar pensando: Cara, a implementação não deveria ocorrer na classe C_Mouse, já que ela é a responsável por reportar eventos de clique a nossas aplicações? Sim de fato. Mas se isto for tentado, teremos um pequeno problema. Observe novamente as animações. Note que o evento CHARTEVENT_MOUSE_MOVE vem antes do evento CHARTEVENT_OBJECT_CLICK. E quando analisamos se houve um clique em um objeto, isto durante um estudo. Teríamos uma certa perda no sincronismo dos eventos, ou melhor dizendo, quando você clica-se em um objeto. A classe C_Mouse ainda estaria apontando para o objeto que teria recebido o clique anterior. Talvez isto pareça estranho. Mas no momento em que formos ver a classe C_ElementsTrade isto ficará mais claro.
Então novamente, a solução foi adicionar uma nova variável. Esta está na linha 25 e é utilizada apenas na linha 192. Então como eu disse, poderíamos colocar a solução, no mesmo código que a usaria. Mas como quero deixar tudo muito bem organizado. A coloco neste local. Ou seja, na classe C_Terminal. Com isto podemos agora ver o código do indicador de posição.
Atualizando o indicador de posição
Uma vez que as mudanças tenham sido feitas nos demais códigos, mostrados acima. Podemos ver o que foi preciso mudar no indicador de posição. Para começo de conversa. Foi decidido que as cores usadas nas linhas, não mais poderão ser alteradas pelo operador, ou usuário. Elas serão padronizadas dentro do indicador. Assim o código do arquivo principal, mudou e pode ser visto logo abaixo.
//+------------------------------------------------------------------+ 01. #property copyright "Daniel Jose" 02. #property icon "/Images/Market Replay/Icons/Positions.ico" 03. #property description "Indicator for tracking an open position on the server." 04. #property description "This should preferably be used together with an Expert Advisor." 05. #property description "For more details see the same article." 06. #property version "1.122" 07. #property link "https://www.mql5.com/pt/articles/13274" 08. #property indicator_chart_window 09. #property indicator_plots 0 10. //+------------------------------------------------------------------+ 11. #define def_ShortName "Position View" 12. //+------------------------------------------------------------------+ 13. #include <Market Replay\Order System\C_IndicatorPosition.mqh> 14. #include <Market Replay\Defines.mqh> 15. //+------------------------------------------------------------------+ 16. input ulong user00 = 0; //For Expert Advisor use 17. //+------------------------------------------------------------------+ 18. C_IndicatorPosition *Positions = NULL; 19. //+------------------------------------------------------------------+ 20. int OnInit() 21. { 22. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 23. Positions = new C_IndicatorPosition(); 24. if (!Positions.CheckCatch(user00)) 25. { 26. ChartIndicatorDelete(0, 0, def_ShortName); 27. return INIT_FAILED; 28. } 29. 30. return INIT_SUCCEEDED; 31. } 32. //+------------------------------------------------------------------+ 33. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 34. { 35. return rates_total; 36. } 37. //+------------------------------------------------------------------+ 38. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 39. { 40. (*Positions).DispatchMessage(id, lparam, dparam, sparam); 41. }; 42. //+------------------------------------------------------------------+ 43. void OnDeinit(const int reason) 44. { 45. delete Positions; 46. } 47. //+------------------------------------------------------------------+
Indicador de posição
Por conta disto, o código da classe C_IndicatorPosition mudou levemente. Então no novo código pode ser visto integralmente logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "C_ElementsTrade.mqh" 05. //+------------------------------------------------------------------+ 06. class C_IndicatorPosition 07. { 08. private : 09. struct st00 10. { 11. ulong ticket; 12. string szShortName; 13. }m_Infos; 14. C_ElementsTrade *Open, *Stop, *Take; 15. //+------------------------------------------------------------------+ 16. public : 17. //+------------------------------------------------------------------+ 18. C_IndicatorPosition() 19. { 20. ZeroMemory(m_Infos); 21. Open = Take = Stop = NULL; 22. } 23. //+------------------------------------------------------------------+ 24. ~C_IndicatorPosition() 25. { 26. delete Open; 27. delete Take; 28. delete Stop; 29. } 30. //+------------------------------------------------------------------+ 31. bool CheckCatch(ulong ticket) 32. { 33. m_Infos.szShortName = StringFormat("%I64u", m_Infos.ticket = ticket); 34. if (!PositionSelectByTicket(m_Infos.ticket)) return false; 35. if (ObjectFind(0, m_Infos.szShortName) >= 0) 36. { 37. m_Infos.ticket = 0; 38. return false; 39. } 40. IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName); 41. EventChartCustom(0, evUpdate_Position, ticket, 0, ""); 42. 43. return true; 44. } 45. //+------------------------------------------------------------------+ 46. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 47. { 48. double value; 49. 50. if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam); 51. if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam); 52. if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam); 53. switch (id) 54. { 55. case CHARTEVENT_CUSTOM + evUpdate_Position: 56. if (lparam != m_Infos.ticket) return; 57. if (!PositionSelectByTicket(m_Infos.ticket)) 58. { 59. ChartIndicatorDelete(0, 0, m_Infos.szShortName); 60. return; 61. }; 62. if (Open == NULL) Open = new C_ElementsTrade(m_Infos.ticket, evMsgClosePositionEA, clrRoyalBlue, ePriorityNull, StringFormat("%I64u : Position opening price.", m_Infos.ticket)); 63. if (Take == NULL) Take = new C_ElementsTrade(m_Infos.ticket, evMsgCloseTakeProfit, clrForestGreen, ePriorityDefault, StringFormat("%I64u : Take Profit price.", m_Infos.ticket)); 64. if (Stop == NULL) Stop = new C_ElementsTrade(m_Infos.ticket, evMsgCloseStopLoss, clrFireBrick, ePriorityDefault, StringFormat("%I64u : Stop Loss price.", m_Infos.ticket)); 65. (*Open).UpdatePrice(PositionGetDouble(POSITION_PRICE_OPEN)); 66. if ((value = PositionGetDouble(POSITION_TP)) > 0) (*Take).UpdatePrice(value); else 67. { 68. delete Take; 69. Take = NULL; 70. } 71. if ((value = PositionGetDouble(POSITION_SL)) > 0) (*Stop).UpdatePrice(value); else 72. { 73. delete Stop; 74. Stop = NULL; 75. } 76. break; 77. } 78. ChartRedraw(); 79. } 80. //+------------------------------------------------------------------+ 81. }; 82. //+------------------------------------------------------------------+
C_IndicatorPosition.mqh
Mas com tudo, agora chegou a hora de ver finalmente o novo código da classe C_ElementsTrade. E este pode ser visto logo a seguir. Note que o código é bastante diferente do que foi visto no artigo anterior. Porém ele está com os mesmos elementos e funciona da mesma maneira.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #define def_NameHLine m_Info.szPrefixName + "#HLINE" 005. #define def_NameBtnClose m_Info.szPrefixName + "#CLOSE" 006. //+------------------------------------------------------------------+ 007. #define def_PathBtns "Images\\Market Replay\\Orders\\" 008. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp" 009. #resource "\\" + def_Btn_Close; 010. //+------------------------------------------------------------------+ 011. #include "..\Auxiliar\C_Mouse.mqh" 012. //+------------------------------------------------------------------+ 013. class C_ElementsTrade : private C_Mouse 014. { 015. private : 016. //+------------------------------------------------------------------+ 017. struct st00 018. { 019. ulong ticket; 020. string szPrefixName; 021. EnumEvents ev; 022. double price; 023. bool bClick; 024. }m_Info; 025. //+------------------------------------------------------------------+ 026. void UpdateViewPort(void) 027. { 028. int x, y; 029. 030. ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y); 031. ObjectSetDouble(0, def_NameHLine, OBJPROP_PRICE, m_Info.price); 032. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, 130); 033. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 034. } 035. //+------------------------------------------------------------------+ 036. public : 037. //+------------------------------------------------------------------+ 038. C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, EnumPriority ePrio, string szDescr = "\n") 039. :C_Mouse(0, "") 040. { 041. string szObj; 042. 043. ZeroMemory(m_Info); 044. m_Info.szPrefixName = StringFormat("%I64u@%d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 045. CreateObjectGraphics(szObj = def_NameHLine, OBJ_HLINE, _color, ePrio); 046. ObjectSetInteger(0, szObj, OBJPROP_WIDTH, 2); 047. ObjectSetString(0, szObj, OBJPROP_TEXT, szDescr); 048. ObjectSetString(0, szObj, OBJPROP_TOOLTIP, szDescr); 049. ObjectSetInteger(0, szObj, OBJPROP_SELECTABLE, ePrio != ePriorityNull); 050. CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 051. ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close); 052. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 053. } 054. //+------------------------------------------------------------------+ 055. ~C_ElementsTrade() 056. { 057. if (m_Info.szPrefixName != "") 058. ObjectsDeleteAll(0, m_Info.szPrefixName); 059. } 060. //+------------------------------------------------------------------+ 061. inline void UpdatePrice(const double price) 062. { 063. m_Info.price = price; 064. UpdateViewPort(); 065. } 066. //+------------------------------------------------------------------+ 067. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 068. { 069. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 070. switch (id) 071. { 072. case CHARTEVENT_OBJECT_CLICK: 073. if ((m_Info.bClick) && (GetPositionsMouse().szObjNameClick == def_NameBtnClose)) switch (m_Info.ev) 074. { 075. case evMsgClosePositionEA: 076. EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, ""); 077. break; 078. case evMsgCloseTakeProfit: 079. EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL)); 080. break; 081. case evMsgCloseStopLoss: 082. EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL)); 083. break; 084. } 085. m_Info.bClick = false; 086. break; 087. case CHARTEVENT_MOUSE_MOVE: 088. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 089. break; 090. case CHARTEVENT_CHART_CHANGE: 091. UpdateViewPort(); 092. break; 093. } 094. } 095. //+------------------------------------------------------------------+ 096. }; 097. //+------------------------------------------------------------------+ 098. #undef def_Btn_Close 099. #undef def_PathBtns 100. //+------------------------------------------------------------------+ 101. #undef def_NameBtnClose 102. #undef def_NameHLine 103. //+------------------------------------------------------------------+
C_ElementsTrade.mqh
Diversos dos elementos que haviam sido colocados, foram removidos. Como você pode notar claramente, comparando ambos os códigos. Porém note que na linha 23, surgiu uma nova variável. Esta é usada lá no procedimento DispatchMessage. Fora isto nada mais ficou diferente. Mas vamos ver como o procedimento DispatchMessage trabalhará agora. Isto por que deste momento em diante, ele passará a contar com o auxílio do MetaTrader 5.
Observe na linha 67, onde o procedimento DispatchMessagem se inicia de que não tivemos mudanças drásticas. Apenas adaptamos o código que existia anteriormente. Note que no evento CHARTEVENT_MOUSE_MOVE na linha 87, tudo que fazemos é verificar junto ao indicador de Mouse, se um clique é ou não valido. Fazemos isto de uma maneira bastante simples e direta. Assim aquela variável vista na linha 23 receberá um valor, de verdadeiro, quando temos um clique valido. Ou falso, caso o indicador de mouse estivesse efetuando algum estudo. Esta é a parte em que nossa aplicação tomará conta. Já a parte que o MetaTrader 5, irá nos auxiliar, pode ser vista no evento CHARTEVENT_OBJECT_CLICK, presente na linha 72.
Perceba uma coisa neste código no evento CHARTEVENT_OBJECT_CLICK. Ele é exatamente o mesmo que era visto no artigo anterior. Só que ele estava no evento CHARTEVENT_MOUSE_MOVE. Mas será que não teremos problemas, aqui? No bem da verdade não. Já que antes do evento de clique teremos um evento de movimento. E quando o clique acontece, teremos verificado se ele é ou não valido. E somente teremos uma execução como era visto no artigo anterior. Se e somente se, tivermos um clique valido, e o nome do objeto corresponder ao nome que estamos tratando neste momento. Ou seja, o objeto que representa o botão de fechar. Todo resto já foi densamente explicado, não necessitando de explicações extras.
Muito bem, mas antes de finalizar este artigo, vamos fazer uma pequena mudança no sistema. Isto por que não faz muito sentido ter uma linha horizontal que vai de canto a canto do gráfico e tem em algum ponto dela um botão de fechar. Podemos melhorar isto de forma a ter uma linha que termine no botão de fechar, o que de certa forma faz um pouco mais de sentido. Porém aqui vamos preparar o terreno para uma outra modificação que será feita no futuro. No entanto, para deixar as coisas um pouco mais próximas do que precisamos, vamos necessitar mudar a classe. Como não tenho certeza plena, e absoluta de como as coisas serão feitas depois. Não fiquem muito apegados ao que irão ver. Mas procurem entender como as coisas funcionam. Pois quem sabe isto possa vir a lhe ser útil em algum momento futuro.
Preparando para os próximos passos
O próximo passo a ser implementado, pode parecer um pouco bobo e até desnecessário. Porém ele é importante, visto o que precisaremos fazer logo depois. Muito bem, qual é a ideia neste momento? A ideia é promover um meio de verificarmos visualmente e de forma bastante clara, qual linha estamos manipulando. E por que isto é importante? O motivo é que podemos fazer uso de um sistema clique uma vez para agarrar e clique novamente para soltar. E sem saber qual linha estamos manipulando. Fica muito confuso fazer as coisas da maneira correta. Assim precisamos de um meio, que seja simples e eficiente para promover tal indicação.
Muitos iriam pensar em fazer o seguinte: Já que temos uma posição aberta, podemos simplesmente criar um sistema de chaveamento interno. E mais uma vez concordo com você, meu caro leitor. Implementar um sistema de chaveamento interno é a coisa mais prudente a ser feita. Porém vamos generalizar um pouco mais as coisas. Pense no seguinte: Se você estiver em uma conta do tipo HEDGING, um sistema de chaveamento não será suficientemente adequado. Já que poderemos ter dois ou mais indicadores de posição no mesmo gráfico. O que torna bastante complicado implementar tal chaveamento. Isto pensando na possibilidade de você clicar na linha de uma das posições e depois clicar na linha de uma outra posição. Ou seja, teremos um impasse se isto ocorrer.
Mas além disto, ainda temos um outro problema, que apesar de no momento, não nos afetar, irá nos dar grande problemas no futuro. Isto se não implementarmos as coisas de uma maneira mais genérica. Estou falando das ordens pendentes. Estas irão usar um sistema muito similar ao que viermos a desenvolver aqui e agora. Então um chaveamento interno a fim de fazer as devidas mudanças não será o suficiente. Porém e felizmente temos uma solução igualmente simples, e que cobrirá completamente todos os cenários possíveis. Esta envolve o envio de mensagens entre os indicadores. Mas você pode imaginar que isto é algo extremamente complicado e difícil de ser implementado. Mas será mesmo? Pois bem, antes de vermos as mudanças necessárias, vamos ver o que mudou no arquivo de cabeçalho Defines.mqh. Este pode ser visto logo a seguir.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. #define def_MaskTimeService 0xFED00000 16. #define def_IndicatorTimeFrame (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96)))) 17. #define def_IndexTimeFrame 4 18. //+------------------------------------------------------------------+ 19. union uCast_Double 20. { 21. double dValue; 22. long _long; // 1 Information 23. datetime _datetime; // 1 Information 24. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 25. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 26. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 27. }; 28. //+------------------------------------------------------------------+ 29. enum EnumEvents { 30. evTicTac, //Event of tic-tac 31. evHideMouse, //Hide mouse price line 32. evShowMouse, //Show mouse price line 33. evHideBarTime, //Hide bar time 34. evShowBarTime, //Show bar time 35. evHideDailyVar, //Hide daily variation 36. evShowDailyVar, //Show daily variation 37. evHidePriceVar, //Hide instantaneous variation 38. evShowPriceVar, //Show instantaneous variation 39. evCtrlReplayInit, //Initialize replay control 40. evChartTradeBuy, //Market buy event 41. evChartTradeSell, //Market sales event 42. evChartTradeCloseAll, //Event to close positions 43. evChartTrade_At_EA, //Event to communication 44. evEA_At_ChartTrade, //Event to communication 45. evChatWriteSocket, //Event to Mini Chat 46. evChatReadSocket, //Event To Mini Chat 47. evUpdate_Position, //Event to communication 48. evMsgClosePositionEA, //Event to communication 49. evMsgCloseTakeProfit, //Event to communication 50. evMsgCloseStopLoss, //Event to communication 51. evMsgNewTakeProfit, //Event to communication 52. evMsgNewStopLoss, //Event to communication 53. evMsgSetFocus //Event to communication 54. }; 55. //+------------------------------------------------------------------+ 56. enum EnumPriority { //Priority list on objects 57. ePriorityNull = -1, 58. ePriorityDefault = 0 59. }; 60. //+------------------------------------------------------------------+
Defines.mqh
Observe que na linha 53, temos um novo evento sendo definido. Este é o evento que usaremos para permitir efetuar a tarefa que desejamos implementar. Feito isto podemos passar para o código da classe C_ElementsTrade. E assim ver as modificações que foram necessárias para se conseguir gerar o resultado esperado. O código da classe pode ser visto na íntegra logo 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. //+------------------------------------------------------------------+ 007. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 7 : 3)); 008. //+------------------------------------------------------------------+ 009. #define def_PathBtns "Images\\Market Replay\\Orders\\" 010. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp" 011. #resource "\\" + def_Btn_Close; 012. //+------------------------------------------------------------------+ 013. #include "..\Auxiliar\C_Mouse.mqh" 014. //+------------------------------------------------------------------+ 015. class C_ElementsTrade : private C_Mouse 016. { 017. private : 018. //+------------------------------------------------------------------+ 019. struct st00 020. { 021. ulong ticket; 022. string szPrefixName; 023. EnumEvents ev; 024. double price; 025. bool bClick; 026. char weight; 027. }m_Info; 028. //+------------------------------------------------------------------+ 029. void UpdateViewPort(void) 030. { 031. int x, y; 032. 033. ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y); 034. x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 200 : 250)); 035. ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x); 036. ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight / 2)); 037. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x); 038. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 039. } 040. //+------------------------------------------------------------------+ 041. public : 042. //+------------------------------------------------------------------+ 043. C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, EnumPriority ePrio, string szDescr = "\n") 044. :C_Mouse(0, "") 045. { 046. string szObj; 047. 048. ZeroMemory(m_Info); 049. m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 050. CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, _color, (EnumPriority)(ePriorityDefault)); 051. macro_LineInFocus(false); 052. ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, _color); 053. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH)); 054. ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT); 055. ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER); 056. ObjectSetString(0, szObj, OBJPROP_TOOLTIP, szDescr); 057. ObjectSetInteger(0, szObj, OBJPROP_SELECTABLE, ePrio != ePriorityNull); 058. CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 059. ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close); 060. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 061. } 062. //+------------------------------------------------------------------+ 063. ~C_ElementsTrade() 064. { 065. ObjectsDeleteAll(0, m_Info.szPrefixName); 066. } 067. //+------------------------------------------------------------------+ 068. inline void UpdatePrice(const double price) 069. { 070. m_Info.price = price; 071. UpdateViewPort(); 072. } 073. //+------------------------------------------------------------------+ 074. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 075. { 076. string sz0; 077. 078. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 079. switch (id) 080. { 081. case CHARTEVENT_CUSTOM + evMsgSetFocus: 082. macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev)); 083. break; 084. case CHARTEVENT_OBJECT_CLICK: 085. sz0 = GetPositionsMouse().szObjNameClick; 086. if (m_Info.bClick) switch (m_Info.ev) 087. { 088. case evMsgClosePositionEA: 089. if (sz0 == def_NameBtnClose) 090. EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, ""); 091. else if (sz0 == def_NameHLine) 092. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 093. break; 094. case evMsgCloseTakeProfit: 095. if (sz0 == def_NameBtnClose) 096. EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL)); 097. else if (sz0 == def_NameHLine) 098. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, ""); 099. break; 100. case evMsgCloseStopLoss: 101. if (sz0 == def_NameBtnClose) 102. EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL)); 103. else if (sz0 == def_NameHLine) 104. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, ""); 105. break; 106. } 107. m_Info.bClick = false; 108. break; 109. case CHARTEVENT_MOUSE_MOVE: 110. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 111. break; 112. case CHARTEVENT_CHART_CHANGE: 113. UpdateViewPort(); 114. break; 115. } 116. } 117. //+------------------------------------------------------------------+ 118. }; 119. //+------------------------------------------------------------------+ 120. #undef macro_LineInFocus 121. //+------------------------------------------------------------------+ 122. #undef def_Btn_Close 123. #undef def_PathBtns 124. //+------------------------------------------------------------------+ 125. #undef def_NameBtnClose 126. #undef def_NameHLine 127. //+------------------------------------------------------------------+
C_ElementsTrade.mqh
E o resultado esperado é visto na animação logo a seguir.

Estou mostrando este resultado, antes de explicar o código, para que você, meu caro leitor, preste atenção ao que será explicado. Pois este mecanismo poderá vir a sofrer algumas mudanças depois. Porém, se em algum momento, você precisar fazer algo similar. Saiba que é possível fazer diversas coisas. Porém é necessário que você entenda por que elas funcionam. Dito isto, acredito que você já esteja curioso para entender como a animação acima foi possível de acontecer, sem que fosse criado um mecanismo de chaveamento convencional. Bem, então vamos entender como e por que isto acontece.
Primeiramente, note que na linha sete, defini uma macro a fim de destacar de forma bastante evidente a linha que receber o foco. Na linha 26, defini uma nova variável que irá armazenar o valor do peso que a linha definida nesta classe irá ter. Na linha 36, estaremos posicionando a nossa linha criada aqui, em uma posição que ela fique centralizada. Para que isto ocorra da forma correta, a espessura da linha deverá ser definida em termos de um valor ímpar. Se você definir um valor par, estilo dois ou quatro, a linha ficará levemente deslocada da posição ideal. Observe uma outra coisa. Na linha 34, definimos onde cada um dos botões irá ficar. Isto para evitar que eles fiquem muito próximos ou venham a se sobrepor. Isto dificultaria o correto acesso a eles. Estes valores não são definitivos. Mas já servem ao seu propósito.
Até aqui tudo bem. Agora note uma mudança no constructor da classe. Antes era usado um objeto HLINE para indicar a linha de preço. Porém agora estaremos usando um objeto OBJ_RECTANGLE_LABEL. Esta mudança nos permite colocar um limite máximo no tamanho das linhas horizontais. Assim elas não irão de canto a canto no gráfico. E o motivo de estarmos usando um OBJ_RECTANGLE_LABEL e não um OBJ_TREND é justamente pelo fato de que as linhas de tendência necessitam de uma ancoragem no tempo. Algo que se torna complicado em alguns momentos de ser devidamente ajustado. Porém um OBJ_RECTANGLE_LABEL faz uso de coordenadas cartesianas. O que é bastante prático para o nosso propósito.
Praticamente todo o código se manteve. Apesar desta diferença no objeto que será a nossa linha horizontal. No entanto, a verdadeira mudança realmente aconteceu no procedimento da linha 74. Ou seja, em DispatchMessage. Neste ponto, as coisas podem parecer um pouco confusas neste primeiro momento. Mas não existe motivo para pânico. Apenas preste atenção e tudo será bem compreendido.
Observe que na linha 81 temos o tratamento da mensagem que criamos no arquivo Defines.mqh. Porém esta mensagem somente existe aqui, pelo fato de que estamos generalizando ao máximo as coisas. Note que o tratamento da mensagem é muito simples. Se uma classe C_ElementsTrade, receber esta mensagem evMsgSetFocus. Ela verificará se o valor lparam é o mesmo do bilhete observado pela classe. Assim como dparam tem que ser um dos valores de evento. Caso isto não aconteça, a linha será reconfigurada para uma espessura mais fina. Caso tenhamos uma condição satisfatória, a linha será configurada para uma espessura mais grossa. Simples assim.
Mas espere um pouco. Como a classe saberá quando a linha será mais grossa ou fina? Isto não precisaria ser definido em algum lugar? Sim, meu caro leitor. Quem de fato efetua a tarefa de informar isto são os eventos presentes nas linhas 92, 98 e 104. Cada um deles somente é disparado quando tivermos um clique válido no indicador de mouse. Observe que a condição que permite tais eventos serem disparados é justamente o nome do objeto clicado. Hum. Mas por que fazer assim? Não seria mais rápido, simples e prático colocar a macro no lugar destes disparos de eventos? Novamente, você está correto meu caro leitor.
Porém, isto nos traria justamente o problema que acontece quando temos mais de um indicador de posição no gráfico. Ou quando tivermos os indicadores de ordens pendentes. Neste caso, um indicador não conseguiria informar a outro que ele perdeu o foco, e que não mais receberá atenção. Mas usando justamente este mecanismo de passagem de mensagens, permitimos que todos indicadores trabalhem em perfeita harmonia. Sabendo exatamente o que deve ou não ser feito. Apesar deste mecanismo não ser assim tal eficiente em termos de execução, ele se mostra bastante prático e seguro. Além de ser consideravelmente mais simples que um mecanismo mais eficiente em termos de execução.
Considerações finais
Você pode verificar pessoalmente como tal mecanismo funciona. Isto usando uma conta demo. Sugiro você usar a conta demo do MetaQuotes, isto para que possa ver o mecanismo funcionando quando houver mais de uma posição aberta. Acredite, vai ser algo bastante interessante de ser visualizado e estudado.
Caso você venha a fazer tal coisa, você certamente notará que existe uma certa dificuldade em selecionar as linhas. Mesmo elas tendo uma espessura superior a 1 pixel. Selecionar as mesmas pode ser um pouco complicado em alguns momentos. Para facilitar ainda mais as coisas, no próximo artigo, melhoraremos um pouco mais as coisas. Além é claro, começaremos fazer as coisas diretamente no gráfico, sem necessitar do sistema de terminal, a fim de criar as linhas de stop loss e take profit.
No anexo você terá os executáveis para poder experimentar o que foi visto até aqui. Isto para quem não souber como compilar todo o sistema.
| 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.
Automatização de estratégias de trading com MQL5 (Parte 1): Sistema Profitunity (Trading Chaos de Bill Williams)
Do básico ao intermediário: Filas, Listas e Árvores (II)
Ciência de dados e aprendizado de máquina (Parte 32): Como manter a relevância de modelos de IA com treinamento on-line
Observador Connexus (Parte 8): Adicionando Request Observer (Observador de requisições)
- 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