Simulação de mercado: Position View (XIV)
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 (XIII), mostrei como você pode fazer para conseguir visualizar, muito facilmente o resultado de uma operação aberta. Vendo de uma maneira direta e objetiva, se uma posição estaria dando lucro ou prejuízo. Porém aquele mesmo sistema visto no artigo anterior, pode ser expandido, melhorado de diversas formas. Tornando o indicador de posição, algo realmente prático e bastante interessante de ser utilizado no dia a dia. E é justamente isto que faremos neste artigo de hoje. Vamos melhorar aquele mesmo sistema de forma que poderemos ter algo mais adaptativo a cada situação. Então lhes convido, a acompanhar o que faremos neste artigo. Pois será algo bastante interessante. E que pode lhe ajudar em alguns outros projetos que fazem uso do MQL5.
Implementando um alto dimensionamento
Talvez uma das coisas mais complicadas e que mais pesam quando se está criando objetos, via código. É dimensionar eles de maneira correta, e rápida. Muitas das vezes precisamos configurar uma dimensão de altura e largura. Compilar o código. Executar a aplicação no MetaTrader 5, para então verificarmos se as dimensões estão ou não adequadas.
Talvez esta tarefa, não seja assim tão complicada, quando estamos lidando com alguns tipos de objetos. Apesar de ser algo bastante cansativo e demorado de ser feito. Ainda mais quando não fazemos ideia da real dimensão que o objeto terá quando a implementação ficar mais avançada. Pois bem, um dos objetos que faremos bastante uso, é o objeto OBJ_EDIT. Isto para apresentar valores ao operador. Tal objeto, é mais adequado quando precisamos apresentar textos que precisam ficar contidos contendo um fundo predefinido. Você pode ver isto, no artigo anterior. Onde o valor de lucro ou prejuízo, é apresentado com um fundo, que nos permite saber de maneira bem rápida, se estamos positivos ou negativos.
Porém aqui temos um problema, que é justamente dimensionar de maneira adequada a altura e largura do objeto OBJ_EDIT. Isto por que, em alguns casos poderemos precisar de quatro carácteres para mostrar um valor. Mas em outros casos podemos necessitar de mais carácteres. A questão não é ajustar isto. E sim quando mudamos, ou melhor dizendo, quando você meu caro leitor, entusiasta e interessado em aprender a programar em MQL5. Deseja usar uma fonte diferente ou uma dimensão diferente na fonte. Porém usando o mesmo código que estou disponibilizando. Você pode acabar ficando decepcionado, ao verificar que o texto está sendo cortado. Ou não está sendo apresentado como você o esperava ver. E isto é frustrante. Ainda mais quando estamos aprendendo a programar, e não sabemos onde de fato mexer para corrigir as coisas.
Felizmente, existe uma solução bastante prática e interessante para se resolver tal problema. Apesar de ser pouco usada na grande maioria das vezes. Pode ser que ela venha a lhe servir quando você, meu caro leitor, estiver projetando algo para uso pessoal. E não quer ficar ali perdendo tempo, ajustando alturas e larguras do OBJ_EDIT, a fim de apresentar uma dada quantidade de carácteres.
Para fazer isto de forma correta, vamos para a classe C_ElementsTrade. E adicionaremos algumas coisas nesta classe. Para que você, meu caro leitor consiga realmente compreender o que estou fazendo, ou melhor programando. Farei as coisas de maneira bastante gradual. Assim será mais simples de você entender, como a coisa toda realmente estará sendo criada, para funcionar de maneira adequada. Ou para facilitar que você consiga modificar o que estou fazendo. Mas não somente isto, a ideia de fato, é que você meu caro leitor, consiga aperfeiçoar o que será visto. Assim, com base no que foi visto no artigo anterior. Vamos mudar um pouco a classe C_ElementsTrade. Conforme você pode ver 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. #define def_NameBtnMove m_Info.szPrefixName + "#MOVE" 007. #define def_NameInfoDirect m_Info.szPrefixName + "#DIRECT" 008. #define def_NameObjLabel m_Info.szPrefixName + "#PROFIT" 009. //+------------------------------------------------------------------+ 010. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1)); 011. //+------------------------------------------------------------------+ 012. #define def_PathBtns "Images\\Market Replay\\Orders\\" 013. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp" 014. #resource "\\" + def_Btn_Close; 015. //+------------------------------------------------------------------+ 016. #include "..\Auxiliar\C_Mouse.mqh" 017. //+------------------------------------------------------------------+ 018. #ifdef def_FontName 019. "Why are you trying to do this?" 020. #else 021. #define def_FontName "Lucida Console" 022. #define def_FontSize 10 023. #endif 024. //+------------------------------------------------------------------+ 025. class C_ElementsTrade : public C_Mouse 026. { 027. private : 028. //+------------------------------------------------------------------+ 029. struct st00 030. { 031. struct st_01 032. { 033. uchar Width, 034. Height, 035. digits; 036. }Text; 037. ulong ticket; 038. string szPrefixName, 039. szDescr; 040. EnumEvents ev; 041. double price, 042. open; 043. bool bClick, 044. bIsBuy; 045. char weight; 046. color _color; 047. }m_Info; 048. //+------------------------------------------------------------------+ 049. void UpdateViewPort(const double price) 050. { 051. int x, y; 052. 053. ChartTimePriceToXY(0, 0, 0, price, x, y); 054. x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 220 : 290)); 055. ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x); 056. ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0)); 057. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x); 058. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 059. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XDISTANCE, x + 10); 060. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2)); 061. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + m_Info.Text.Width + 20); 062. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y); 063. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + m_Info.Text.Width + 20); 064. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y); 065. } 066. //+------------------------------------------------------------------+ 067. inline void CreateLinePrice(void) 068. { 069. string szObj; 070. 071. CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault)); 072. ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color); 073. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH)); 074. ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT); 075. ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER); 076. ObjectSetString(0, szObj, OBJPROP_TOOLTIP, m_Info.szDescr); 077. macro_LineInFocus(false); 078. } 079. //+------------------------------------------------------------------+ 080. inline void CreateBoxInfo(const bool bMove) 081. { 082. string szObj; 083. const char c[] = {(char)(bMove ? 'u' : (m_Info.bIsBuy ? 236 : 238)), 0}; 084. 085. CreateObjectGraphics(szObj = (bMove ? def_NameBtnMove : def_NameInfoDirect), OBJ_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 086. ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings"); 087. ObjectSetString(0, szObj, OBJPROP_TEXT, CharArrayToString(c)); 088. ObjectSetInteger(0, szObj, OBJPROP_COLOR, (bMove ? m_Info._color : (m_Info.bIsBuy ? clrForestGreen : clrFireBrick))); 089. ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, (bMove ? 17 : 15)); 090. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 091. } 092. //+------------------------------------------------------------------+ 093. inline void CreateObjectInfoText(void) 094. { 095. string szObj; 096. 097. CreateObjectGraphics(szObj = def_NameObjLabel, OBJ_EDIT, clrNONE, (EnumPriority)(ePriorityDefault)); 098. ObjectSetString(0, szObj, OBJPROP_FONT, def_FontName); 099. ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, def_FontSize); 100. ObjectSetInteger(0, szObj, OBJPROP_COLOR, clrBlack); 101. ObjectSetInteger(0, szObj, OBJPROP_BORDER_COLOR, m_Info._color); 102. ObjectSetInteger(0, szObj, OBJPROP_ALIGN, ALIGN_CENTER); 103. ObjectSetInteger(0, szObj, OBJPROP_READONLY, true); 104. ObjectSetInteger(0, szObj, OBJPROP_YSIZE, m_Info.Text.Height); 105. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, m_Info.Text.Width); 106. } 107. //+------------------------------------------------------------------+ 108. inline void CreateButtonClose(void) 109. { 110. string szObj; 111. 112. CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 113. ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close); 114. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 115. } 116. //+------------------------------------------------------------------+ 117. public : 118. //+------------------------------------------------------------------+ 119. C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, char digits, string szDescr = "\n", const bool IsBuy = true) 120. :C_Mouse(0, "") 121. { 122. uint w, h; 123. 124. ZeroMemory(m_Info); 125. m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 126. m_Info._color = _color; 127. m_Info.szDescr = szDescr; 128. m_Info.bIsBuy = IsBuy; 129. m_Info.Text.digits = digits; 130. TextSetFont(def_FontName, def_FontSize * -10); 131. TextGetSize(StringFormat("%." + (string)digits + "f", 8888.88888), w, h); 132. m_Info.Text.Width = (uchar) w + 4; 133. m_Info.Text.Height = (uchar) h + 4; 134. } 135. //+------------------------------------------------------------------+ 136. ~C_ElementsTrade() 137. { 138. ObjectsDeleteAll(0, m_Info.szPrefixName); 139. } 140. //+------------------------------------------------------------------+ 141. inline void UpdatePrice(const double open, const double price) 142. { 143. if (price > 0) 144. { 145. CreateLinePrice(); 146. CreateButtonClose(); 147. }else 148. ObjectsDeleteAll(0, m_Info.szPrefixName); 149. CreateBoxInfo(m_Info.ev != evMsgClosePositionEA); 150. if (price > 0) 151. CreateObjectInfoText(); 152. m_Info.open = open; 153. UpdateViewPort(m_Info.price = (price > 0 ? price : open)); 154. if (m_Info.ev != evMsgClosePositionEA) 155. ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price); 156. } 157. //+------------------------------------------------------------------+ 158. void ViewValue(const double profit) 159. { 160. ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, StringFormat("%." + (string)m_Info.Text.digits + "f", (profit < 0 ? -(profit) : profit))); 161. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral)); 162. ChartRedraw(); 163. } 164. //+------------------------------------------------------------------+ 165. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 166. { 167. string sz0; 168. long _lparam = lparam; 169. double _dparam = dparam; 170. 171. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 172. switch (id) 173. { 174. case (CHARTEVENT_KEYDOWN): 175. if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break; 176. _lparam = (long) m_Info.ticket; 177. _dparam = 0; 178. case CHARTEVENT_CUSTOM + evMsgSetFocus: 179. macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev)); 180. EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, ""); 181. m_Info.bClick = false; 182. case CHARTEVENT_CHART_CHANGE: 183. UpdateViewPort(m_Info.price); 184. if (m_Info.ev != evMsgClosePositionEA) 185. ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price); 186. break; 187. case CHARTEVENT_OBJECT_CLICK: 188. sz0 = GetPositionsMouse().szObjNameClick; 189. if (m_Info.bClick) switch (m_Info.ev) 190. { 191. case evMsgClosePositionEA: 192. if (sz0 == def_NameBtnClose) 193. EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, ""); 194. break; 195. case evMsgCloseTakeProfit: 196. if (sz0 == def_NameBtnClose) 197. EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL)); 198. else if (sz0 == def_NameBtnMove) 199. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, ""); 200. break; 201. case evMsgCloseStopLoss: 202. if (sz0 == def_NameBtnClose) 203. EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL)); 204. else if (sz0 == def_NameBtnMove) 205. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, ""); 206. break; 207. } 208. m_Info.bClick = false; 209. break; 210. case CHARTEVENT_MOUSE_MOVE: 211. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 212. if (m_Info.weight > 1) 213. { 214. UpdateViewPort(_dparam = GetPositionsMouse().Position.Price); 215. if (m_Info.ev != evMsgClosePositionEA) 216. ViewValue(m_Info.bIsBuy ? _dparam - m_Info.open : m_Info.open - _dparam); 217. if (m_Info.bClick) 218. { 219. switch (m_Info.ev) 220. { 221. case evMsgCloseTakeProfit: 222. EventChartCustom(0, evMsgNewTakeProfit, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 223. break; 224. case evMsgCloseStopLoss: 225. EventChartCustom(0, evMsgNewStopLoss, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 226. break; 227. } 228. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 229. } 230. } 231. break; 232. } 233. } 234. //+------------------------------------------------------------------+ 235. }; 236. //+------------------------------------------------------------------+ 237. #undef macro_LineInFocus 238. //+------------------------------------------------------------------+ 239. #undef def_Btn_Close 240. #undef def_PathBtns 241. #undef def_FontName 242. #undef def_FontSize 243. //+------------------------------------------------------------------+ 244. #undef def_NameObjLabel 245. #undef def_NameInfoDirect 246. #undef def_NameBtnMove 247. #undef def_NameBtnClose 248. #undef def_NameHLine 249. //+------------------------------------------------------------------+
C_ElementsTrade.mqh
Ok. A primeira coisa que você pode notar é algo absolutamente bizarro e que não faz o menor sentido. Isto olhando a linha 18. Você, meu caro leitor, pode estar pensando: Mas que coisa, mais maluca e insana, é esta que está sendo feita aqui? Na verdade não é algo assim tão maluco e insano. Não para quem está acostumado a programar em C/C++. É o seguinte: Quando usamos, principalmente em C/C++, bibliotecas de funções ou coisas do tipo. É muito comum termos conflitos entre definições. Principalmente entre definições, criadas por programadores diferentes. Aqui no MQL5, não temos a diretiva de compilação #error, que está presente no C/C++. Esta diretiva visa, quando utilizada, impedir que você ignore algum alerta do compilador. Já que quando o compilador a encontra no código. Ele imediatamente finaliza a compilação, sendo gerada uma informação de erro. Esta informação segue a diretiva de compilação #error.
Como você, já deve saber, no MQL5, não temos esta possibilidade. Aliás, não é que ela não exista. É que não existe absolutamente nada a respeito disto. Mas aqui, estou mostrando isto, pois estou pretendendo mostrar como implementar uma coisa. Se isto vier a acontecer no futuro. Esta diretiva da linha 18 irá impedir que tenhamos um código em conflito com outro. Que ainda está sendo projetado. No entanto, vamos entender o que está acontecendo.
Caso a definição def_FontName, exista no exato momento que a linha 18 for executada. A linha 19 será lida pelo compilador. Como ele não conseguirá entender esta linha 19. A compilação será finalizada, e um erro será gerado. Informando justamente que esta linha 19 é uma falha. Porém, caso a definição def_FontName, não exista, e no momento ela não existe para você, meu caro leitor. O compilador pulará da linha 18 para a linha 20, iniciando a execução do que estiver presente daquele ponto, até a diretiva #endif. E esta se encontra na linha 23.
Muito bem, então se o compilador executar o que estiver entre as diretivas #else e #endif. Teremos a definição do nome da fonte, e o tamanho da mesma. Isto para facilitar que você a mude, sem precisar modificar diversos pontos no código. Feito isto, o próximo ponto que desejo um pouco de atenção é na linha 25. Observe que a herança que era privativa, agora passou a ser pública. Neste momento, isto não tem muita importância. Mas até o fim deste artigo fará muita diferença. Isto para o que será feito mais para o final. A próxima coisa, a ser mencionada, é vista na linha 31. Ali temos uma estrutura, cuja finalidade, será o de armazenar, algumas informações importantes sobre o texto a ser apresentado.
Agora pulando para a linha 49, ou seja, o procedimento UpdateViewPort, vemos os primeiros usos das novas variáveis declaradas. Como é algo bastante simples, não vou entrar em detalhes. Assim como também na linha 93, onde também fazemos uso das novas variáveis. Porém até neste momento, apenas usamos os valores presentes nas novas variáveis. Mas não inicializamos tais valores. Esta inicialização se dará no constructor da classe. Ou seja, na linha 119. Agora preste atenção ao que será explicado. Pois isto poderá ser diferente depois, caso você decida fazer outras coisas neste código.
Observe que na linha 122, declaramos duas novas variáveis locais. E até a linha 130, todo o código permanece como você já o conhecia. Mas o que estamos fazendo desta linha 130 até a linha 133? Bem, neste ponto, é que estamos dizendo ao compilador: Escuta, quero que você me diga, quantos pixel, um determinado texto tem de altura e de largura. Isto usando esta determinada fonte com este tamanho de fonte. Este tipo de coisa, é feita, entre as linhas 130 e 131. Quando o compilador, mas também pode ser feito em RUN-TIME, nos dizer: Este texto que você está querendo usar tem X pixel de largura por Y pixel de altura. Nós vamos usar estes valores e adicionar uma pequena folga em torno dele. Esta folga é de dois pixels de cada um dos lados, totalizando quatro. E é isto que é feito nas linhas 132 e 133. Existem outras poucas diferenças aqui nesta classe. Mas não merecem assim tanto destaque. Porém existe um ponto que quero destacar, pois ele foi adicionado, sendo importante que você saiba disto.
Observe na linha 184 e a linha 215. Por que elas apareceram aqui e agora? O motivo é que desejo, ou melhor dizendo, quero que o indicador de posição me diga, quantos pontos, tem entre o preço de abertura e a linha da classe. Esta linha pode ser, a de take profit ou stop loss. Agora preste bastante atenção. No artigo anterior, mostrei que podemos visualizar no gráfico, quantos pontos estamos tendo de lucro ou prejuízo. Isto diretamente visualizando o que está sendo impresso em um objeto OBJ_EDIT. Agora, você não pensou que eu seria tolo, ou louco de programar aquilo novamente, para apresentar a quantidade de pontos, de stop loss ou take profit. Ou pensou? Bem, se pensou, significa que você não entendeu de fato como a coisa toda funciona. E por que estou criando ela, desta maneira.
Da mesma forma que podemos visualizar se estamos tendo prejuízo ou lucro, olhando o OBJ_EDIT presente na linha de preço de abertura. Podemos fazer a mesma leitura em termos de take profit ou stop loss. Por isto estas linhas 185 e 216. Porém, como durante a atualização, pode haver um conflito de interesses. As linhas 184 e 215, filtram o usuário da classe. Caso seja a linha de preço de abertura, estes dois testes nas linhas 184 e 215, evitam que tal conflito ocorra.
Muito bem, mas se você compilar o indicador, não terá o funcionamento correto. É preciso mudar um pouco, ou melhor, atualizar o código do indicador. Então o código atualizado é visto logo abaixo, na íntegra.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. #property icon "/Images/Market Replay/Icons/Positions.ico" 004. #property description "Indicator for tracking an open position on the server." 005. #property description "This should preferably be used together with an Expert Advisor." 006. #property description "For more details see the same article." 007. #property version "1.126" 008. #property link "https://www.mql5.com/pt/articles/13361" 009. #property indicator_chart_window 010. #property indicator_plots 0 011. //+------------------------------------------------------------------+ 012. #define def_ShortName "Position View" 013. //+------------------------------------------------------------------+ 014. #include <Market Replay\Order System\C_ElementsTrade.mqh> 015. #include <Market Replay\Defines.mqh> 016. //+------------------------------------------------------------------+ 017. input ulong user00 = 0; //For Expert Advisor use 018. //+------------------------------------------------------------------+ 019. struct st00 020. { 021. ulong ticket; 022. string szShortName, 023. szSymbol; 024. double priceOpen; 025. char digits; 026. bool bIsBuy; 027. }m_Infos; 028. //+------------------------------------------------------------------+ 029. C_ElementsTrade *Open = NULL, *Stop = NULL, *Take = NULL; 030. //+------------------------------------------------------------------+ 031. bool CheckCatch(ulong ticket) 032. { 033. ZeroMemory(m_Infos); 034. m_Infos.szShortName = StringFormat("%I64u", m_Infos.ticket = ticket); 035. if (!PositionSelectByTicket(m_Infos.ticket)) return false; 036. if (ObjectFind(0, m_Infos.szShortName) >= 0) 037. { 038. m_Infos.ticket = 0; 039. return false; 040. } 041. m_Infos.szSymbol = PositionGetString(POSITION_SYMBOL); 042. m_Infos.digits = (char)SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 043. IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName); 044. EventChartCustom(0, evUpdate_Position, ticket, 0, ""); 045. 046. return true; 047. } 048. //+------------------------------------------------------------------+ 049. inline void ProfitNow(void) 050. { 051. double ask, bid; 052. 053. ask = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_ASK); 054. bid = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_BID); 055. if (Open != NULL) 056. (*Open).ViewValue((m_Infos.bIsBuy ? bid - m_Infos.priceOpen : m_Infos.priceOpen - ask)); 057. } 058. //+------------------------------------------------------------------+ 059. int OnInit() 060. { 061. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 062. if (!CheckCatch(user00)) 063. { 064. ChartIndicatorDelete(0, 0, def_ShortName); 065. return INIT_FAILED; 066. } 067. 068. return INIT_SUCCEEDED; 069. } 070. //+------------------------------------------------------------------+ 071. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 072. { 073. ProfitNow(); 074. 075. return rates_total; 076. } 077. //+------------------------------------------------------------------+ 078. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 079. { 080. if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam); 081. if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam); 082. if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam); 083. switch (id) 084. { 085. case CHARTEVENT_CUSTOM + evUpdate_Position: 086. if (lparam != m_Infos.ticket) return; 087. if (!PositionSelectByTicket(m_Infos.ticket)) 088. { 089. ChartIndicatorDelete(0, 0, m_Infos.szShortName); 090. return; 091. }; 092. if (Open == NULL) Open = new C_ElementsTrade(m_Infos.ticket, evMsgClosePositionEA, clrRoyalBlue, m_Infos.digits, StringFormat("%I64u : Position opening price.", m_Infos.ticket), m_Infos.bIsBuy = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)); 093. if (Take == NULL) Take = new C_ElementsTrade(m_Infos.ticket, evMsgCloseTakeProfit, clrForestGreen, m_Infos.digits, StringFormat("%I64u : Take Profit price.", m_Infos.ticket), m_Infos.bIsBuy); 094. if (Stop == NULL) Stop = new C_ElementsTrade(m_Infos.ticket, evMsgCloseStopLoss, clrFireBrick, m_Infos.digits, StringFormat("%I64u : Stop Loss price.", m_Infos.ticket), m_Infos.bIsBuy); 095. (*Open).UpdatePrice(0, m_Infos.priceOpen = PositionGetDouble(POSITION_PRICE_OPEN)); 096. (*Take).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_TP)); 097. (*Stop).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_SL)); 098. ProfitNow(); 099. break; 100. } 101. ChartRedraw(); 102. }; 103. //+------------------------------------------------------------------+ 104. void OnDeinit(const int reason) 105. { 106. delete Open; 107. delete Take; 108. delete Stop; 109. } 110. //+------------------------------------------------------------------+
Indicador de posição
O que foi modificado, ou adicionado no código, fica como dever de casa. Para que você busque estudar as mudanças que estão sendo feitas. Já que elas são bastante simples, sutis e fáceis de entender, não entrarei em detalhes aqui. Porém ao executar o código atualizado, você verá o seguinte resultado mostrado na animação abaixo:

Note que quando movemos a linha, seja de take profit, seja de stop loss. O indicador irá nos mostrar quantos pontos existem entre o preço de abertura e a linha que estamos ajustando. Um detalhe: Se você mover a linha de take profit de forma que o valor fique negativo. O indicador refletirá isto passando a ser vermelho. Como se fosse a linha de stop loss. Isto significa que se o take profit estiver vermelho, você ao encerrar a posição naquele ponto terá prejuízo. A mesma coisa se aplica ao stop loss. Quando ele entrar na região, onde ele vier a ficar verde, da mesma cor do take profit. Significará que quando a posição for encerrada naquele ponto. Você obviamente terá um lucro, na quantidade de pontos que está sendo indicada. É algo bastante simples e direto. Não tem segredo.
Porém existe um detalhe aqui. Você nota que tanto a linha de take profit, quanto de stop loss, estão presentes na posição. Mas o que acontece se você tentar criar ela, via interação com o indicador de posição? Será que poderemos visualizar a quantidade de pontos da mesma forma? Bem. Sem que o código sofra nenhuma nova modificação. O resultado será o que é visto na animação logo abaixo.

Note que somente depois, que efetuarmos o clique, indicando onde é o ponto, que será criado o take profit ou stop loss. É que de fato, teremos a informação de quantos pontos estaremos definindo para a linha. Isto funciona tanto para o take profit, quanto para o stop loss. De certa forma, isto não seria um problema. Pois você poderia arrastar a linha para bem longe, respeitando a direção informada pelo indicador de posição. E logo depois ajustar as coisas como é vista a primeira animação. Porém, apesar de não ser um problema. A coisa em si, parece muito estranha e mal feita. Podemos melhorar isto. E como você, meu caro leitor, pode estar imaginando. Isto talvez seja algo extremamente complicado. Algo que exigirá ser um PHD em programação ou conter uma super mente, com um grau de intelecto nível 5 milhões. Bem, mas será mesmo assim? Para responder esta pergunta, vamos a próximo tópico. Pois assim as coisas serão melhor separadas.
Adicionando uma interação mais intuitiva
O que vamos fazer agora, só é possível por que o MQL5, utiliza o mesmo princípio de funcionamento de uma programação baseada em eventos. Tal modelo de programação, é bastante usada na criação de DLL. Sei que no primeiro momento a coisa toda parecerá extremamente confusa e sem nenhuma lógica. Mas acredite, meu caro leitor, eu faço isto a bastante tempo. E é uma das coisas que posso dizer, ou melhor, afirmar. Dá bastante trabalho compreender e pegar o jeito. Você terá que programar muitas coisas que usam tal mecanismo, para de fato compreender totalmente o que vou tentar explicar aqui.
A ideia é modificar o mínimo possível o código, para que este consiga criar uma interação mais intuitiva. Ou seja, ao se tentar criar a linha de take profit, ou stop loss. O indicador de posição tenha o mesmo comportamento que teria caso a linha já existisse. Quero que você entenda isto antes de mais nada. Quando a linha de stop loss, ou take profit não existe. Apenas o objeto de interação, ou movimentação, estará presente. Este nos permite selecionar a linha que queremos criar e arrastar até um dado ponto e clicar neste ponto para que a linha seja criada. No entanto, como você pode ver no tópico anterior, tal interação de fato acontece perfeitamente. Porém não nos é indicado, qual o valor, ou melhor, qual é a distância, entre o ponto atual e o preço de abertura. Este é no nosso primeiro problema.
O segundo problema é: Quando a linha não existe, temos que criar a indicação a fim de mostrar a distância que existe entre o ponto atual e o preço de abertura. No entanto, ao fazermos isto, podemos decidir cancelar tal operação. Onde a linha de stop loss ou take profit já não será criada. Fazemos isto pressionando a tecla ESC. Sem olhar o que faremos a seguir. Você pode experimentar o indicador, e verá que isto de fato acontece. Porém, ao criamos a indicação de distância. Teremos o problema quando pressionarmos a tecla ESC, já que os valores voltarão para o original, no entanto o indicador permanecerá no gráfico. Dando assim uma falsa informação ao operador.
É justamente estes dois problemas que temos de resolver. Porém devemos fazer isto, mexendo o mínimo possível no código. Muitos com certeza, sairiam adicionando e recontando código a esmo. Muito provavelmente mexendo muito na classe C_ElementsTrade, tentando fazer com que as coisas funcionem. Pode confessar, você com certeza, faria isto. E não posso dizer que você estaria errado. Mas você verá que mexerei o mínimo possível. E para ser mais preciso e específico modificaremos apenas e somente o código do indicador de posição.
O novo código pode ser visto logo abaixo.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. #property icon "/Images/Market Replay/Icons/Positions.ico" 004. #property description "Indicator for tracking an open position on the server." 005. #property description "This should preferably be used together with an Expert Advisor." 006. #property description "For more details see the same article." 007. #property version "1.126" 008. #property link "https://www.mql5.com/pt/articles/13361" 009. #property indicator_chart_window 010. #property indicator_plots 0 011. //+------------------------------------------------------------------+ 012. #define def_ShortName "Position View" 013. //+------------------------------------------------------------------+ 014. #include <Market Replay\Order System\C_ElementsTrade.mqh> 015. #include <Market Replay\Defines.mqh> 016. //+------------------------------------------------------------------+ 017. input ulong user00 = 0; //For Expert Advisor use 018. //+------------------------------------------------------------------+ 019. struct st00 020. { 021. ulong ticket; 022. string szShortName, 023. szSymbol; 024. double priceOpen; 025. char digits; 026. bool bIsBuy; 027. }m_Infos; 028. //+------------------------------------------------------------------+ 029. C_ElementsTrade *Open = NULL, *Stop = NULL, *Take = NULL; 030. //+------------------------------------------------------------------+ 031. bool CheckCatch(ulong ticket) 032. { 033. ZeroMemory(m_Infos); 034. m_Infos.szShortName = StringFormat("%I64u", m_Infos.ticket = ticket); 035. if (!PositionSelectByTicket(m_Infos.ticket)) return false; 036. if (ObjectFind(0, m_Infos.szShortName) >= 0) 037. { 038. m_Infos.ticket = 0; 039. return false; 040. } 041. m_Infos.szSymbol = PositionGetString(POSITION_SYMBOL); 042. m_Infos.digits = (char)SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 043. IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName); 044. EventChartCustom(0, evUpdate_Position, ticket, 0, ""); 045. 046. return true; 047. } 048. //+------------------------------------------------------------------+ 049. inline void ProfitNow(void) 050. { 051. double ask, bid; 052. 053. ask = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_ASK); 054. bid = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_BID); 055. if (Open != NULL) 056. (*Open).ViewValue((m_Infos.bIsBuy ? bid - m_Infos.priceOpen : m_Infos.priceOpen - ask)); 057. } 058. //+------------------------------------------------------------------+ 059. int OnInit() 060. { 061. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 062. if (!CheckCatch(user00)) 063. { 064. ChartIndicatorDelete(0, 0, def_ShortName); 065. return INIT_FAILED; 066. } 067. 068. return INIT_SUCCEEDED; 069. } 070. //+------------------------------------------------------------------+ 071. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 072. { 073. ProfitNow(); 074. 075. return rates_total; 076. } 077. //+------------------------------------------------------------------+ 078. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 079. { 080. switch (id) 081. { 082. case CHARTEVENT_CUSTOM + evMsgSetFocus: 083. if (lparam != m_Infos.ticket) break; 084. switch ((EnumEvents)dparam) 085. { 086. case evMsgCloseTakeProfit: 087. (*Take).UpdatePrice(m_Infos.priceOpen, (*Take).GetPositionsMouse().Position.Price); 088. break; 089. case evMsgCloseStopLoss: 090. (*Stop).UpdatePrice(m_Infos.priceOpen, (*Stop).GetPositionsMouse().Position.Price); 091. break; 092. } 093. break; 094. case CHARTEVENT_CUSTOM + evUpdate_Position: 095. if (lparam != m_Infos.ticket) break; 096. if (!PositionSelectByTicket(m_Infos.ticket)) 097. { 098. ChartIndicatorDelete(0, 0, m_Infos.szShortName); 099. return; 100. }; 101. if (Open == NULL) Open = new C_ElementsTrade(m_Infos.ticket, evMsgClosePositionEA, clrRoyalBlue, m_Infos.digits, StringFormat("%I64u : Position opening price.", m_Infos.ticket), m_Infos.bIsBuy = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)); 102. if (Take == NULL) Take = new C_ElementsTrade(m_Infos.ticket, evMsgCloseTakeProfit, clrForestGreen, m_Infos.digits, StringFormat("%I64u : Take Profit price.", m_Infos.ticket), m_Infos.bIsBuy); 103. if (Stop == NULL) Stop = new C_ElementsTrade(m_Infos.ticket, evMsgCloseStopLoss, clrFireBrick, m_Infos.digits, StringFormat("%I64u : Stop Loss price.", m_Infos.ticket), m_Infos.bIsBuy); 104. (*Open).UpdatePrice(0, m_Infos.priceOpen = PositionGetDouble(POSITION_PRICE_OPEN)); 105. (*Take).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_TP)); 106. (*Stop).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_SL)); 107. ProfitNow(); 108. break; 109. } 110. if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam); 111. if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam); 112. if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam); 113. ChartRedraw(); 114. }; 115. //+------------------------------------------------------------------+ 116. void OnDeinit(const int reason) 117. { 118. delete Open; 119. delete Take; 120. delete Stop; 121. } 122. //+------------------------------------------------------------------+
Código do Indicador
E o resultado de quando ele é executado é visto primeiro na animação abaixo, onde usamos a tecla ESC para cancelar a criação da linha. Observe o que está acontecendo.

Assim como também, quando a criação é de fato efetuada. Isto pode ser visto na animação logo abaixo.

Observe que aparentemente não faz o mínimo sentido. Ainda mais olhando o código do indicador. Compare ele com a sua versão anterior, vista no tópico anterior neste mesmo artigo. Note que existem sim diferenças. Mas apesar de elas existirem. Por que este código, com tão poucas mudanças frente ao que seria esperado, consegue praticamente finalizar a criação do indicador de posição? Isto definitivamente parece algum tipo de mágica ou bruxaria. Coisa de que fez um pacto ou algo do tipo. Mas não, meu caro leitor. Isto é exatamente a aplicação do conhecimento que estou tentando passar nestes artigos. Não existe nada de mágica ou coisa do tipo. É pura e simples programação. E uma correta e mais aprofundada exploração do que o MQL5, nos permite fazer. Nada além disto.
Mas chega de papo, e vamos entender o que está acontecendo. E por que o fato de termos modificado, apenas o código do indicador, já foi o suficiente para conseguir o resultado que desejávamos. Muito bem, então preste bastante atenção. Em primeiro lugar, note que na linha 49, foi criado um novo procedimento. Este contém exatamente o código que havia dentro da função OnCalculate. Você pode ver isto, olhando o código no tópico anterior. Por consequência, na linha 73, efetuamos uma chamada para esta linha 49. Agora atenção: Na declaração a linha 49, estou definindo que a função é do tipo inline. Ou seja, eu quero que o compilador, coloque este mesmo código, que será criado, entre as linhas 49 e 57. Na posição que a chamada aparecer. Então este código entre as linhas 49 e 57, seria como uma macro.
Mas este procedimento, da linha 49, não está ali, por conta da função OnCalculate, e sim por conta da função OnChartEvent. Mas como assim? Calma, meu caro leitor, você já vai entender. Observe agora na linha 107, onde novamente a mesma chamada ao procedimento da linha 49 aparece mais uma vez. Agora esqueça todo restante do código, e vamos focar única e exclusivamente no procedimento OnChartEvent. Pois é aqui que a mágica acontece. E entender isto poderá fazer muita diferença na sua vida como programador. Ainda mais se você deseja, trabalhar com aplicações baseadas em eventos.
O que acontece aqui, depende do que o usuário, ou mesmo o MetaTrader 5 efetivamente venham a executar. Se você tentar ver uma ligação entre este código e todo restante, não entenderá absolutamente nada. Mas se você pensar em termos de eventos. Tudo começará a fazer sentido. Observe que mudamos a ordem das coisas aqui. No código original, que pode ser visto no início do artigo, as linhas 110 a 112, na verdade estão no começo do procedimento OnChartEvent. Aqui elas estão praticamente no final. E isto é importante, depois você entenderá o porquê.
Primeiro vamos entender o código em si. Se você olhar o código da classe C_ElementsTrade, verá que o evento tratado na linha 82, ou seja, evMsgSetFocus. Também é declarado na linha 178 da classe. Agora que a coisa começa a ficar interessante. Quando aqui no código principal, o evento da linha 82 for detectado, primeiro iremos filtrar se o bilhete é o mesmo do indicador de posição. Isto é feito na linha 83. Quando isto for verdadeiro, iremos executar a linha 84, onde iremos chavear entre diferentes casos. Cada um voltado a trabalhar com uma linha. Para que tanto as animações vista neste tópico, sejam diferentes das vistas nos tópicos e artigos anteriores, precisamos da linha 87. Quando estivermos criando, ou ajustando, a linha de take profit. E precisamos da linha 90, para fazer algo parecido com a linha de stop loss. Fazendo isto, todo o código está concluído e funcionará como visto nas animações deste tópico.
Mas ainda fica a pergunta: Como isto de fato faz com que as animações sejam possíveis? Não estou vendo nenhuma ligação entre o código do indicador e o código da classe C_ElementsTrade. A não ser, por estas breves chamadas que podem ser vistas aqui. Ainda não entendi.
De fato, meu caro e estimado leitor. A coisa não é tão simples assim. Você com toda a certeza estaria esperando que fosse preciso modificar alguma coisa na classe C_ElementsTrade. Isto para que as animações de fato fizessem sentido de acontecerem de fato. Porém tal mudança de fato ocorreu. Apesar de ser bastante sútil. Lembra, que no tópico anterior, menciono que a herança foi modificada? Pois bem, sem aquela mudança na herança, não seria possível fazer uso das chamadas vistas na linha 87 e 90. Onde buscamos o valor da posição de preço do mouse, para ajustar e apresentar os valores de distância em termos de pontos. Neste ponto, da implementação, você já deveria conseguir entender, que precisamos passar dois valores para aquela função. Um que seria o preço de abertura da posição, e um segundo valor que é onde está a linha de take profit ou stop loss. Algo muito parecido com o que pode ser visto nas linhas 105 e 106.
No entanto, nas linhas 105 e 106, o que estamos fazendo é uma atualização baseada no valor presente no servidor. Já nas linhas 87 e 90 estamos fazendo uma atualização virtual. Ou seja, as linhas de fato não existem, elas apenas existem na exata posição onde o mouse se encontra. Virgem Maria. Agora é que complicou geral. Então quando, selecionamos a linha e movemos o mouse, estas linhas 87 e 90. Dependendo se estamos mexendo do take profit, ou no stop loss, é que farão o movimento visto nas animações? NÃO.
Olhe o tópico anterior e você verá que este código daqui não existia. Porém, compare as animações e você verá que elas são diferentes. Mas por que? O motivo é que estas linhas 87 e 90, NÃO MOVEM AS LINHAS, elas apenas irão criar a linha correta. Seria como um passo extra entre as animações. De forma que a aplicação iria entender como se o operador tivesse de fato arrastando uma linha que já existe no servidor.
Considerações finais
Sei que, o que estou fazendo, parece muito mais confuso do que realmente é. Porém de fato não é fácil visualizar o que está acontecendo apenas observando o código. E sem entender de fato a lógica envolvida na troca de mensagens. Tudo isto fica muito mais complicado de ser entendido. No entanto, é preciso um pouco mais de espaço para explicar em detalhes o que está acontecendo aqui. Prometo que no próximo artigo, irei dar um tempo no desenvolvimento e implementação do sistema de replay/simulador. Para dar uma melhor explicação sobre o que está acontecendo, neste emaranhado de troca de mensagens e eventos entre as aplicações e o MetaTrader 5.
Assim com toda a certeza, acredito, que aqueles realmente interessados em aprender a fazer uso de tal modelo de programação. Conseguirão pelo menos saber por onde começar. Pois o que estou disposto a explicar, foi algo que demorei muito tempo para de fato compreender e entender como funciona. Mas uma vez que consegui entender, todos os meus programas tiveram uma mudança radical na forma como eram escritos e pensados.
E quero, honestamente, que você, meu caro leitor e amigo, consiga entender o que está acontecendo aqui. Pois muitos outros programadores, demoram várias horas, de codificação para conseguir produzir, algo que usando programação baseada em eventos e troca de mensagens. Você consegue produzir muito mais rápido e de forma muito mais objetiva e fácil de implementar.
E para você, que deve estar curioso, sobre se de fato este sistema de mensagens e eventos funciona. No anexo, foi deixar disponível os arquivos já compilados. Assim você não terá o trabalho, em compilar tudo, ou cometer algum erro durante a compilação. Desta forma de despeço por enquanto. E nos vemos no próximo artigo. Onde prometo, irei explicar este mecanismo de troca de mensagens e programação baseada em eventos. Então até mais.
| 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.
Desenvolvendo um EA multimoeda (Parte 25): Conectando uma nova estratégia (II)
Do básico ao intermediário: Classes (I)
Desenvolvimento do Conjunto de Ferramentas de Análise de Price Action – Parte (4): Analytics Forecaster EA
Migrando para o MQL5 Algo Forge (Parte 4): Trabalhando com versões e lançamentos
- 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