Simulação de mercado: Position View (XIX)
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 (XVIII), mostrei como lidar com algumas situações. Como por exemplo, evitar que o usuário ou operador, viesse a remover os objetos gráficos, que haveriam sido colocados pelo indicador de posição. Fizemos isto de uma maneira supersimples e bastante fácil e sem muita complicação. Já que muitas das vezes repor os objetos na ordem correta, pode ser bastante custoso e nos forçar a verdadeiros malabarismos em termos de programação. Assim como também, adicionamos uma indicação de volume. Que é algo muito usado por alguns operadores a fim de conseguir saber se haveria ou não, a possibilidade de efetuar, algum tipo de estratégia na posição.
Seja para melhorar o lucro aumentando de forma consciente uma posição já lucrativa. Ou para reduzir o risco, já que se você estiver com um volume consideravelmente grande. Pode acabar por ter uma perda muito grande em um movimento em que o mercado vai contra a sua posição. Muitas das vezes isto faz com que o risco, encerre sua operação, para logo depois você ver o mercado indo na direção que você imaginava. Este tipo de coisa além de ser muito ruim, detona completamente o psicológico do operador. Tirando completamente a sua confiança.
Tudo isto tem sido feito, de maneira bastante simples e com pouquíssimas mudanças de fato no código já existente. Acredito, que você, meu caro leitor, esteja de fato conseguindo acompanhar e perceber, que não é muito complicado fazer as coisas. Apenas precisamos pensar com calma antes de começar a de fato tentar mudar o código. Pois dependendo do caso, uma ou outra leve mudança, na forma com as coisas estão implementadas. Já será mais que o suficiente para que consigamos resolver algum problema especifico.
Mas agora precisamos resolver uma questão, um tanto quanto, mais complicada. Que eu tenho postergado em resolver, já a algum tempo. Mas não se preocupem, em imaginar que o trabalho a ser feito aqui, é algo complicadíssimo. Na verdade você irá de fato notar, que apesar de ser relativamente complicado, irei tratar a coisa de uma maneira bastante simples e fácil de entender. Então vamos ao trabalho.
Liberando a classe C_ElementsTrade
Uma das coisas que mais tem me incomodado, é o fato da classe C_ElementsTrade, ter em seu código, coisas que permitem acessar as posições. Não entenda isto como uma falha, pois de fato não é. Apenas torna algumas partes do que precisaremos fazer no futuro, algo um tanto quanto sujeitas a erros. Todo o trabalho que tem sido feito, para implementar o indicador de posição. Tem sido feito, pensando em usar ele no replay/simulador. Porém, uma vez que ele esteja sendo usado no replay/simulador. Não teremos de forma alguma, acesso a uma posição real. Sendo assim, qualquer chamada da biblioteca MQL5, cujo objetivo é acessar dados da posição. Não terão qualquer efeito no código. Sendo mais um transtorno do que qualquer outra coisa. Visto que isto criará dependências na classe C_ElementsTrade.
Tais dependências não nos causa problemas, caso estejam no código principal. Ou seja, no código onde o indicador está sendo criado. Mas ter estas mesmas dependências em um arquivo de cabeçalho. Isto sim é um problema e algo que precisamos resolver. Antes mesmo de começar a implementar as modificações necessárias para usar o indicador de posição, junto ao serviço de replay/simulador.
Bem, felizmente, até este ponto do desenvolvimento. Foi tomado um grande cuidado na implementação. Isto para reduzir ao máximo as dependências, ou ligações com chamadas cujo objetivo é buscar dados de uma posição. Tais pontos se resumem basicamente a um único procedimento. Ou seja, o DispatchMessage. Vamos ver onde temos tais dependências sendo criadas. Para isto observe o fragmento abaixo. Onde temos o código do procedimento DispatchMessage presente na classe C_ElementsTrade.
240. //+------------------------------------------------------------------+ 241. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 242. { 243. string sz0; 244. long _lparam = lparam; 245. double _dparam = dparam; 246. 247. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 248. switch (id) 249. { 250. case (CHARTEVENT_KEYDOWN): 251. if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break; 252. _lparam = (long) m_Info.ticket; 253. _dparam = 0; 254. EventChartCustom(0, evUpdate_Position, _lparam, 0, ""); 255. case CHARTEVENT_CUSTOM + evMsgSetFocus: 256. if ((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev)) 257. UpdatePrice(m_Info.open, GetPositionsMouse().Position.Price); 258. macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev)); 259. EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, ""); 260. m_Info.bClick = false; 261. case CHARTEVENT_CHART_CHANGE: 262. ChartChange(); 263. break; 264. case CHARTEVENT_CUSTOM + evMsgSwapViewModePosition: 265. m_Info.ViewMode = (stInfos::e1)((((stInfos::e1)_dparam) + 1) & 0x03); 266. for (int c0 = PositionsTotal() - 1; c0 >= 0; c0--) 267. EventChartCustom(0, evUpdate_Position, PositionGetTicket(c0), 0, NULL); 268. break; 269. case CHARTEVENT_OBJECT_CLICK: 270. sz0 = GetPositionsMouse().szObjNameClick; 271. if (m_Info.bClick) 272. { 273. if (sz0 == def_NameBtnMove) 274. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, m_Info.ev, ""); 275. if (sz0 == def_NameBtnClose) 276. EventChartCustom(0, (ushort) m_Info.ev, m_Info.ticket, PositionGetDouble(m_Info.ev == evMsgCloseTakeProfit ? POSITION_SL : POSITION_TP), PositionGetString(POSITION_SYMBOL)); 277. if (sz0 == def_NameObjLabel) 278. EventChartCustom(0, evMsgSwapViewModePosition, m_Info.ticket, (double)m_Info.ViewMode, NULL); 279. } 280. m_Info.bClick = false; 281. break; 282. case CHARTEVENT_MOUSE_MOVE: 283. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 284. if (m_Info.weight > 1) 285. { 286. UpdateViewPort(_dparam = GetPositionsMouse().Position.Price); 287. if (m_Info.ev != evMsgClosePositionEA) 288. ViewValue(m_Info.bIsBuy ? _dparam - m_Info.open : m_Info.open - _dparam); 289. if (m_Info.bClick) 290. { 291. if ((m_Info.ev == evMsgCloseTakeProfit) || (m_Info.ev == evMsgCloseStopLoss)) 292. EventChartCustom(0, (ushort)(m_Info.ev == evMsgCloseTakeProfit ? evMsgNewTakeProfit : evMsgNewStopLoss), m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 293. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 294. } 295. } 296. break; 297. case CHARTEVENT_OBJECT_DELETE: 298. if (StringFind(sparam, m_Info.szPrefixName) < 0) break; 299. UpdatePrice(m_Info.open, m_Info.price); 300. break; 301. } 302. } 303. //+------------------------------------------------------------------+
Fragmento de C_ElementsTrade
Observe que basicamente temos chamadas a PositionsTotal, PositionGetTicket, PositionGetDouble e PositionGetString presentes neste fragmento. Bem, logo de cara podemos dizer que PositionGetString, que é uma função que no nosso caso, devolve o nome do ativo. Pode ser removida de forma muito fácil. Basta que durante a chamada do constructor da classe, passemos o nome do ativo, como parâmetro e o armazenemos em uma variável local privativa dentro da classe. Já as outras três chamadas exigirão um trabalho, ou melhor dizendo, uma abordagem, um pouco diferente. Então vamos começar resolvendo o problema do nome do ativo.
Para fazer isto, termos que mudar o código da classe, de forma que agora ele será como mostrado no fragmento abaixo.
026. //+------------------------------------------------------------------+ 027. class C_ElementsTrade : private C_Mouse 028. { 029. private : 030. //+------------------------------------------------------------------+ 031. struct stInfos 032. { 033. struct st_01 034. { 035. short Width, 036. Height, 037. digits; 038. }Text; 039. ulong ticket; 040. string szPrefixName, 041. szDescr, 042. szSymbol; 043. EnumEvents ev; 044. double price, 045. open, 046. volume, 047. var, 048. tickSize; 049. bool bClick, 050. bIsBuy; 051. char weight; 052. color _color; 053. int sizeText; 054. enum e1 {eValue, eFinance, eTicks, ePercentage} ViewMode; 055. }m_Info; 056. //+------------------------------------------------------------------+ . . . 169. //+------------------------------------------------------------------+ 170. public : 171. //+------------------------------------------------------------------+ 172. C_ElementsTrade(const ulong ticket, string szSymbol, const EnumEvents ev, color _color, char digits, double ticksize, string szDescr = "\n", const bool IsBuy = true) 173. :C_Mouse(0, "") 174. { 175. ZeroMemory(m_Info); 176. m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 177. m_Info._color = _color; 178. m_Info.szDescr = szDescr; 179. m_Info.bIsBuy = IsBuy; 180. m_Info.Text.digits = digits; 181. m_Info.tickSize = ticksize; 182. m_Info.szSymbol = szSymbol; 183. } 184. //+------------------------------------------------------------------+ . . . 242. //+------------------------------------------------------------------+ 243. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 244. { . . . 266. case CHARTEVENT_CUSTOM + evMsgSwapViewModePosition: 267. m_Info.ViewMode = (stInfos::e1)((((stInfos::e1)_dparam) + 1) & 0x03); 268. for (int c0 = PositionsTotal() - 1; c0 >= 0; c0--) 269. EventChartCustom(0, evUpdate_Position, PositionGetTicket(c0), 0, NULL); 270. break; 271. case CHARTEVENT_OBJECT_CLICK: 272. sz0 = GetPositionsMouse().szObjNameClick; 273. if (m_Info.bClick) 274. { 275. if (sz0 == def_NameBtnMove) 276. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, m_Info.ev, ""); 277. if (sz0 == def_NameBtnClose) 278. EventChartCustom(0, (ushort) m_Info.ev, m_Info.ticket, PositionGetDouble(m_Info.ev == evMsgCloseTakeProfit ? POSITION_SL : POSITION_TP), m_Info.szSymbol); 279. if (sz0 == def_NameObjLabel) 280. EventChartCustom(0, evMsgSwapViewModePosition, m_Info.ticket, (double)m_Info.ViewMode, NULL); 281. } 282. m_Info.bClick = false; 283. break; 284. case CHARTEVENT_MOUSE_MOVE: 285. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 286. if (m_Info.weight > 1) 287. { 288. UpdateViewPort(_dparam = GetPositionsMouse().Position.Price); 289. if (m_Info.ev != evMsgClosePositionEA) 290. ViewValue(m_Info.bIsBuy ? _dparam - m_Info.open : m_Info.open - _dparam); 291. if (m_Info.bClick) 292. { 293. if ((m_Info.ev == evMsgCloseTakeProfit) || (m_Info.ev == evMsgCloseStopLoss)) 294. EventChartCustom(0, (ushort)(m_Info.ev == evMsgCloseTakeProfit ? evMsgNewTakeProfit : evMsgNewStopLoss), m_Info.ticket, GetPositionsMouse().Position.Price, m_Info.szSymbol); 295. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 296. } 297. } 298. break;
Fragmento de C_ElementsTrade
Neste fragmento acima, deixei apenas e tão somente, as partes realmente necessárias, para que você, meu caro leitor, veja onde foram feitas as mudanças. Note que começamos pela linha 42, onde definimos uma nova variável para receber o nome do ativo. Assim como foi informado anteriormente, no constructor iremos receber o nome do ativo. Isto pode ser visto na linha 172. Observe que na linha 182, armazenamos o valor passado pelo chamador. Agora já em DispatchMessage, fazemos a substituição das chamadas PositionGetString, pelo valor da variável que definimos na linha 42. Com isto resolvemos o primeiro dos problemas. Por enquanto, não se preocupe com o código principal do indicador. Quando já tivermos resolvido todas as pendências e dependências na classe C_ElementsTrade. Iremos ver o como ficará o novo código principal.
Muito bem, agora vamos resolver um outro problema. O das chamadas PositionsTotal e PositionGetTicket. Estas podem ser vistas nas linhas 268 e 269 do fragmento. Se você vem acompanhando esta sequência. Deve saber por que motivo de tais chamadas estão no código. Mas apesar de elas terem um motivo. Não faz sentido as manter de fato no código. Isto por que, o evento customizado será de fato capturado por todas as aplicações que estiverem presentes no gráfico. Assim, podemos ter o mesmo resultado, usando uma forma um pouco diferente dentro deste mesmo código. Desta maneira, o novo código do fragmento pode ser visto logo abaixo.
266. case CHARTEVENT_CUSTOM + evMsgSwapViewModePosition: 267. m_Info.ViewMode = (stInfos::e1)((((stInfos::e1)_dparam) + 1) & 0x03); 268. EventChartCustom(0, evUpdate_Position, m_Info.ticket, 0, NULL); 269. break;
Fragmento de C_ElementsTrade
É curioso parar e pensar no que acabamos de fazer. E por que isto não foi feito anteriormente. Muita gente, imagina que quando programamos ou estamos implementando algo inteiramente novo. Somos capazes de logo de início criar um código 100% perfeito. Mas isto não é bem verdade. O que de fato ocorrer, é que quando voltamos e analisamos o código. Percebemos que certas coisas não fazem muito sentido. E que podem ser melhoradas. E é justamente isto que estamos fazendo neste fragmento acima.
Quando disparamos o evento customizado, a fim de mudar a forma de visualizar o resultado de uma posição. Todas e absolutamente todas as aplicações presentes no gráfico, irão conseguir detectar o evento. Porém isto não é de fato claro de ser notado durante a codificação. Apenas quando experimentamos o código, conseguimos notar isto. E neste ponto, deixamos o código solto. E mesmo que estejamos em uma conta HEDGING, todos os indicadores de posição obedecerão e verão o mesmo comando. Ou seja, troque a forma de mostrar o resultado. Mas como apenas trocar a forma não resulta na atualização. Precisamos enviar um novo evento. Este tem como objetivo, pedir ao indicador para atualizar os dados. E como cada indicador tem conhecimento de qual o bilhete que ele representa. O uso da linha 268, substitui completamente o código que é visto no fragmento anterior. Onde precisamos utilizar chamadas PositionsTotal e PositionGetTicket a fim de cobrir todas as posições. Mas mesmo assim, este código, ainda pode ser melhorado. Mas por hora, vamos deixar as coisas assim.
Mesmo assim ainda temos uma outra questão, a chamada PositionGetDouble. Esta parece ser algo bem mais difícil de ser resolvida. Mas será mesmo? Bem vamos ver, onde e por que desta chamada ser usada. Assim você entenderá como a substituiremos. A linha onde está chamada pode ser vista é mostrada logo abaixo.
270. case CHARTEVENT_OBJECT_CLICK: 271. sz0 = GetPositionsMouse().szObjNameClick; 272. if (m_Info.bClick) 273. { 274. if (sz0 == def_NameBtnMove) 275. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, m_Info.ev, ""); 276. if (sz0 == def_NameBtnClose) 277. EventChartCustom(0, (ushort) m_Info.ev, m_Info.ticket, PositionGetDouble(m_Info.ev == evMsgCloseTakeProfit ? POSITION_SL : POSITION_TP), m_Info.szSymbol); 278. if (sz0 == def_NameObjLabel) 279. EventChartCustom(0, evMsgSwapViewModePosition, m_Info.ticket, (double)m_Info.ViewMode, NULL); 280. } 281. m_Info.bClick = false; 282. break;
Fragmento de C_ElementsTrade
Observe a linha 277. Veja que usamos a chamada PositionGetDouble, justamente para conseguir os valores de take profit ou stop loss da posição. Isto para que possamos fechar, ou remover o ponto limite que recebeu o clique. Ou seja, quando clicamos para fechar o take profit, precisamos saber qual o valor do stop loss. E quando fechamos o stop loss, precisamos saber o valor do take profit. Assim temos um tipo de referência cruzada entre estes dois valores. Resolver este tipo de coisa é algo interessante. Já que cada segmento, estará ligado a um valor corrente. Assim, talvez o que irei fazer neste momento, pode lhe parecer um tanto quanto confuso. Mas você, meu caro leitor, em breve conseguirá entender o princípio da operação. Muito bem, então para remover esta chamada a PositionGetDouble, iremos modificar a classe C_ElementsTrade, como mostrado no fragmento abaixo.
026. //+------------------------------------------------------------------+ 027. class C_ElementsTrade : private C_Mouse 028. { 029. private : 030. //+------------------------------------------------------------------+ 031. struct stInfos 032. { 033. struct st_01 034. { 035. short Width, 036. Height, 037. digits; 038. }Text; 039. ulong ticket; 040. string szPrefixName, 041. szDescr, 042. szSymbol; 043. EnumEvents ev; 044. double price, 045. open, 046. volume, 047. var, 048. tickSize, 049. tpsl; 050. bool bClick, 051. bIsBuy; 052. char weight; 053. color _color; 054. int sizeText; 055. enum e1 {eValue, eFinance, eTicks, ePercentage} ViewMode; 056. }m_Info; 057. //+------------------------------------------------------------------+ . . . 190. //+------------------------------------------------------------------+ 191. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0, const double special = -1) 192. { 193. m_Info.sizeText = 0; 194. RemoveAllsObjects(); 195. m_Info.volume = (vol > 0 ? vol : m_Info.volume); 196. m_Info.var = (var > 0 ? var : m_Info.var); 197. m_Info.tpsl = (special >= 0 ? special : m_Info.tpsl); . . . 244. //+------------------------------------------------------------------+ 245. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 246. { . . . 272. case CHARTEVENT_OBJECT_CLICK: 273. sz0 = GetPositionsMouse().szObjNameClick; 274. if (m_Info.bClick) 275. { 276. if (sz0 == def_NameBtnMove) 277. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, m_Info.ev, ""); 278. if (sz0 == def_NameBtnClose) 279. EventChartCustom(0, (ushort) m_Info.ev, m_Info.ticket, m_Info.tpsl, m_Info.szSymbol); 280. if (sz0 == def_NameObjLabel) 281. EventChartCustom(0, evMsgSwapViewModePosition, m_Info.ticket, (double)m_Info.ViewMode, NULL); 282. } 283. m_Info.bClick = false; 284. break;
Fragmento de C_ElementsTrade
Observe que na linha 49 declaramos uma nova variável. Aqui é onde começa a nossa solução. Feito isto, na linha 191, modificamos a declaração do procedimento UpdatePrice. Isto para que possamos indicar qual o valor que a variável declarada na linha 49 receberá. Agora preste atenção. Na linha 197, verificamos se o valor é igual ou maior que zero. Mas por que disto? O motivo é que, caso o take profit ou stop loss não esteja definido, o valor informado será igual a zero. Assim conseguimos estabelecer corretamente o valor adequado. Agora já dentro do procedimento DispatchMessage, vemos a mudança na linha 279.
Neste ponto, você já deve estar começando a duvidar de minha sanidade mental. Já que estamos fazendo tudo que era feito antes, mas sem fazer uso de nenhuma chamada que nos cause dependência. Lembrando que tais dependências que estou mencionando, são de fato, a existência de posições no servidor de negociação. Desta maneira, a classe C_ElementsTrade, passa a ser viável e passiva de ser utilizada junto ao serviço de replay/simulador.
Mas tudo que foi visto aqui, nos obriga a modificar o código principal. Assim o código principal pode ser visto abaixo. Na verdade apenas a parte realmente necessária. Já que nem todo ele precisou ser modificado.
084. //+------------------------------------------------------------------+ 085. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 086. { 087. double volume; 088. 089. if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam); 090. if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam); 091. if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam); 092. switch (id) 093. { 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( 102. m_Infos.ticket, 103. m_Infos.szSymbol, 104. evMsgClosePositionEA, 105. clrRoyalBlue, 106. m_Infos.digits, 107. m_Infos.tickSize, 108. StringFormat("%I64u : Position opening price.", m_Infos.ticket), 109. m_Infos.bIsBuy = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) 110. ); 111. if (Take == NULL) Take = new C_ElementsTrade( 112. m_Infos.ticket, 113. m_Infos.szSymbol, 114. evMsgCloseTakeProfit, 115. clrForestGreen, 116. m_Infos.digits, 117. m_Infos.tickSize, 118. StringFormat("%I64u : Take Profit price.", m_Infos.ticket), 119. m_Infos.bIsBuy 120. ); 121. if (Stop == NULL) Stop = new C_ElementsTrade( 122. m_Infos.ticket, 123. m_Infos.szSymbol, 124. evMsgCloseStopLoss, 125. clrFireBrick, 126. m_Infos.digits, 127. m_Infos.tickSize, 128. StringFormat("%I64u : Stop Loss price.", m_Infos.ticket), 129. m_Infos.bIsBuy 130. ); 131. volume = PositionGetDouble(POSITION_VOLUME); 132. (*Open).UpdatePrice(0, m_Infos.priceOpen = PositionGetDouble(POSITION_PRICE_OPEN), volume, m_Infos.var); 133. (*Take).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_TP), volume, m_Infos.var, PositionGetDouble(POSITION_SL)); 134. (*Stop).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_SL), volume, m_Infos.var, PositionGetDouble(POSITION_TP)); 135. ProfitNow(); 136. break; 137. } 138. ChartRedraw(); 139. }; 140. //+------------------------------------------------------------------+
Fragmento do código do indicador de posição
Observe atentamente este procedimento OnChartEvent. Que de fato está presente no código principal. Não existem muitas mudanças, sendo feitas aqui. Mas você pode notar que foi preciso adicionar o nome do ativo, em todos os constructores definidos aqui. Mas não é isto que de fato, quero que você meu caro leitor, preste atenção. O que desejo chamar a sua atenção é para as linhas 133 e 134. Observe que ali, o quinto parâmetro não é exatamente o que muitos poderiam vir a estar de fato imaginando. Já que estamos fazendo uma referência cruzada. Olhando estas duas linhas e analisando o fragmento anterior, você conseguirá entender, por que das coisas serem feitas, da forma como estão sendo feitas.
Note que apesar de remover da classe C_ElementsTrade, as chamadas a fim de buscas dados da posição. As mesmas chamadas agora estão presentes no código principal. Porém diferente do que era feito antes. Agora temos um melhor controle, sobre como tais chamadas serão efetivamente usadas. Assim conseguiremos modelar este mesmo indicador para que ele possa ser utilizado no serviço de replay/simulador.
Muito bem, neste ponto já temos o indicador funcionando da mesma maneira, que ele estava antes das modificações. Porém existe uma outra questão, que até este ponto passou desapercebido. Não sei por que motivo demorei tanto para notar tal falha. Talvez estivesse passando batido, por durante os testes estava simplesmente usando o conjunto. Não me importando em tentar colocar o indicador de forma manual. Porém, vamos corrigir esta falha agora. Observe o fragmento abaixo.
30. //+------------------------------------------------------------------+ 31. C_ElementsTrade *Open = NULL, *Stop = NULL, *Take = NULL; 32. //+------------------------------------------------------------------+ 33. bool CheckCatch(ulong ticket) 34. { 35. double vv; 36. 37. ZeroMemory(m_Infos); 38. m_Infos.szShortName = StringFormat("%I64u", m_Infos.ticket = ticket); 39. if (!PositionSelectByTicket(m_Infos.ticket)) return false; 40. if (ChartWindowFind(0, m_Infos.szShortName) >= 0) 41. { 42. m_Infos.ticket = 0; 43. return false; 44. } 45. m_Infos.szSymbol = PositionGetString(POSITION_SYMBOL); 46. m_Infos.digits = (char)SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 47. m_Infos.tickSize = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); 48. vv = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); 49. m_Infos.var = m_Infos.tickSize / vv; 50. IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName); 51. EventChartCustom(0, evUpdate_Position, ticket, 0, ""); 52. 53. return true; 54. } 55. //+------------------------------------------------------------------+
Fragmento do código do indicador de posição
E sim, ainda estamos no código principal do indicador. Observe a linha 40. Até no código presente no artigo anterior, a chamada ali era um ObjectFind. Não me perguntem, pois eu não saberia explicar o motivo de ter usado esta chamada em específico. A chamada correta, é justamente esta que você está vendo agora. E qual o objetivo desta linha 40? O objetivo é evitar que o operador ou usuário tente colocar dois indicadores no gráfico. Isto quando apenas um seria suficiente. Ao tentar fazer isto, esta linha 40, cuja chamada é um ChartWindowFind, irá verificar se já existe um indicador, com o mesmo nome curto presente no gráfico. Caso isto esteja acontecendo, o indicador que o usuário estiver tentando adicionar, será removido imediatamente. Mantendo assim sempre apenas um único indicador no gráfico, ligado a cada posição aberta. É importante você notar, que se estiver em uma conta HEDGING poderá haver mais de um indicador de posição. Mas cada um estará ligado a uma posição. Nunca a mesma posição.
Muito bem, com isto praticamente concluímos o indicador de posição. Mas antes de começar a adaptar o código, a fim de poder usar ele no Replay/Simulador. Quero fazer duas outras mudanças. Talvez você as considere desnecessárias. Então caso não veja utilidade nelas, sinta-se à vontade em ignorar as mesmas. De qualquer maneira, as aplicações já compiladas que estarão sendo disponibilizadas no anexo, já conterão tais modificações. Então para separar as coisas vamos a um novo tópico.
Indicando que o take profit e stop loss estão ok
Talvez, você meu caro leitor, não saiba, ou nunca tenha testemunhando. Mas o MetaTrader 5, contém um código de cores que é mostrado na caixa de mensagens, na aba negociação. Este código de cores normalmente mostram a situação da operação. É bastante comum você ver os códigos de cores quando tudo está perfeito. Mas existe um código que quando algo está errado irá aparecer. Como é algo bastante raro, na grande maioria dos casos. É difícil de mostrar o mesmo. Mas quando o preço, pula o stop loss ou take profit. A posição irá receber uma cor amarela. Indicando assim que existe algo errado. E que ela merece uma atenção especial. Pessoalmente eu vi isto apenas uma única vez. Isto em mais de quatro anos operando. Tal acontecimento, se deu por conta de uma volatilidade exagerada no contrato do dólar. Fazendo com que o preço pulasse a ordem de saída. O que me chamou bastante a atenção, foi justamente o fato de que isto não é tão comentado. Talvez pelo motivo de que é algo relativamente raro de ocorrer, a ponto de você visualizar a posição ficando em amarelo.
Pois bem, a minha ideia, neste momento é justamente implementar isto no indicador de posição. Assim você, quando o estiver utilizando, conseguirá notar que algo de errado aconteceu, podendo desta forma tomar as devidas providências. Fazer isto não é algo complicado. Apenas exige que mudemos levemente o código já implementado. Diferente do que acontece na aba negociação da caixa de mensagem do MetaTrader 5. Aqui vamos fazer isto de uma maneira um pouco diferente. Visando principalmente alertar ao operador ou usuário, que o stop loss ou take profit não estão em um ponto coerente. Ou seja, eles estão localizados de forma errada não criando assim um canal. Apenas falando, isto pareça ser algo complicado de entender. Mas não se preocupe, você logo conseguirá entender o objetivo desejado. Para fazer o que precisamos, será necessário adicionar algumas coisas na classe C_ElementsTrade. No fragmento abaixo podemos ver onde as mudanças aconteceram.
026. //+------------------------------------------------------------------+ 027. class C_ElementsTrade : private C_Mouse 028. { 029. private : 030. //+------------------------------------------------------------------+ 031. struct stInfos 032. { 033. struct st_01 034. { 035. short Width, 036. Height, 037. digits; 038. }Text; 039. ulong ticket; 040. string szPrefixName, 041. szDescr, 042. szSymbol; 043. EnumEvents ev; 044. double price, 045. open, 046. volume, 047. var, 048. tickSize, 049. tpsl, 050. limit; 051. bool bClick, 052. bIsBuy; 053. char weight; 054. color _color; 055. int sizeText; 056. enum e1 {eValue, eFinance, eTicks, ePercentage} ViewMode; 057. }m_Info; 058. //+------------------------------------------------------------------+ . . . 164. //+------------------------------------------------------------------+ 165. inline void ChartChange(void) 166. { 167. UpdateViewPort(MathAbs(m_Info.price)); 168. m_Info.limit = (m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price); 169. if (m_Info.ev != evMsgClosePositionEA) 170. ViewValue(m_Info.limit); 171. } 172. //+------------------------------------------------------------------+ . . . 218. //+------------------------------------------------------------------+ 219. void ViewValue(const double profit, const bool Enabled = true) 220. { 221. string szTxt; 222. color _cor; 223. static double memSL, memTP; 224. 225. if (Enabled) 226. { 227. switch (m_Info.ViewMode) 228. { 229. case stInfos::eValue: 230. szTxt = StringFormat("%." + (string)m_Info.Text.digits + "f", MathAbs(profit)); 231. break; 232. case stInfos::eFinance: 233. szTxt = StringFormat("$ %." + (string)m_Info.Text.digits + "f", (MathAbs(profit) / m_Info.var) * m_Info.volume); 234. break; 235. case stInfos::eTicks: 236. szTxt = StringFormat("%d", (uint)MathRound(MathAbs(profit) / m_Info.tickSize)); 237. break; 238. case stInfos::ePercentage: 239. szTxt = StringFormat("%.2f%%", NormalizeDouble((MathAbs(profit) / (m_Info.open ? m_Info.open : m_Info.price)) * 100, 2)); 240. break; 241. } 242. ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, szTxt); 243. if (StringLen(szTxt) != m_Info.sizeText) 244. { 245. AdjustDinamic(def_NameObjLabel, szTxt); 246. m_Info.sizeText = StringLen(szTxt); 247. } 248. } 249. _cor = (m_Info.limit >= 0 ? clrPaleGreen : clrCoral); 250. switch (m_Info.ev) 251. { 252. case evMsgCloseTakeProfit: 253. memTP = (Enabled ? memTP : profit); 254. _cor = (m_Info.limit < memTP ? clrYellow : _cor); 255. break; 256. case evMsgCloseStopLoss: 257. memSL = (Enabled ? memSL : profit); 258. _cor = (m_Info.limit > memSL ? clrYellow : _cor); 259. break; 260. case evMsgClosePositionEA: 261. _cor = (profit >= 0 ? clrPaleGreen : clrCoral); 262. break; 263. } 264. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, _cor); 265. } 266. //+------------------------------------------------------------------+ 267. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 268. { 269. string sz0; 270. long _lparam = lparam; 271. double _dparam = dparam; 272. 273. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 274. switch (id) 275. { . . . 307. case CHARTEVENT_MOUSE_MOVE: 308. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 309. if (m_Info.weight > 1) 310. { 311. UpdateViewPort(_dparam = GetPositionsMouse().Position.Price); 312. ViewValue(m_Info.limit = (m_Info.bIsBuy ? _dparam - m_Info.open : m_Info.open - _dparam)); 313. if (m_Info.bClick) 314. { 315. if ((m_Info.ev == evMsgCloseTakeProfit) || (m_Info.ev == evMsgCloseStopLoss)) 316. EventChartCustom(0, (ushort)(m_Info.ev == evMsgCloseTakeProfit ? evMsgNewTakeProfit : evMsgNewStopLoss), m_Info.ticket, GetPositionsMouse().Position.Price, m_Info.szSymbol); 317. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 318. } 319. } 320. break;
Fragmento de C_ElementsTrade
Sei que pode parecer bastante confuso. Mas tudo começa, com o fato de adicionarmos uma nova variável na linha 50. Esta mesma variável recebe um valor em dois locais. Um destes locais é a linha 168. Note que é algo bastante simples de ser compreendido. O valor dependerá se estamos em uma mão de compra ou de venda. Mas também dependerá do segmento que esteja fazendo uso da classe C_ElementsTrade. Ou seja, teremos um valor para o stop loss e outro para o take profit. Por enquanto vamos ignorar o procedimento ViewValue, presente na linha 219. E vamos ver o segundo local onde a variável declarada na linha 50, receberá um valor. Este segundo ponto é na linha 312. Preste atenção a isto. Na linha 168, temos o valor conforme a posição indicada no servidor. Já nesta linha 312, o valor dependerá do movimento do mouse. Isto por que estaremos de fato fazendo a modificação da posição, seja do take profit, seja do stop loss.
Agora antes de passarmos para o procedimento ViewValue, presente na linha 219. Será preciso ver o código principal do indicador. Na verdade, irei mostrar apenas o fragmento que sofreu modificação. Este pode ser visto logo abaixo.
55. //+------------------------------------------------------------------+ 56. inline void ProfitNow(void) 57. { 58. double ask, bid, value; 59. 60. ask = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_ASK); 61. bid = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_BID); 62. if (Open != NULL) 63. { 64. (*Open).ViewValue(value = (m_Infos.bIsBuy ? bid - m_Infos.priceOpen : m_Infos.priceOpen - ask)); 65. (*Take).ViewValue(value, false); 66. (*Stop).ViewValue(value, false); 67. } 68. } 69. //+------------------------------------------------------------------+
Fragmento do código do indicador de posição
Observe que nas linhas 65 e 66, efetuaremos a chamada a linha 219 do arquivo de cabeçalho que contém a classe C_ElementsTrade. Mas note o seguinte fato, o valor que estará sendo passado é justamente o mesmo passado para o segmento, responsável pela linha de preço de abertura. Então a cada atualização do valor indicado como resultado da operação, também atualizaremos, quando necessário a cor do take profit e do stop loss. Observem também o fato, de que em ambos casos, o segundo argumento é declarado como sendo falso. Agora podemos ir para a linha 219 do fragmento anterior. Pois agora o que irei explicar fará sentido. E para deixar você de fato mais atento a explicação. O resultado pode ser visto na animação abaixo.

Começamos vendo a linha 223. Nela temos duas variáveis estáticas. O princípio aqui é quase o mesmo usado no procedimento UpdateViewPort. Mas como este procedimento ViewValue, será utilizado por dois segmentos, o take profit e o stop loss. Precisamos de duas variáveis estáticas. Poderíamos usar uma variável, declarada junto com a que criamos na linha 50. Porém, dar nome as variáveis muitas das vezes é uma tarefa complicada. Até que dar nome não é complicado. O problema é não cometer o erro de usar ela em um local equivocado. Mas isto é um mero detalhe. Uma vez declaradas estas variáveis estáticas. Você pode ver na linha 225 que estamos testando o segundo argumento do procedimento. Lembre-se: Ele será falso apenas nos dois pontos vistos no código principal. Em qualquer outro momento ele será verdadeiro. Assim se ele é verdadeiro, efetuamos o mesmo trabalho que era feito anteriormente. Caso ele seja falso ignoramos qualquer atitude. Mas a verdadeira tarefa, inicia de fato na linha 249. Neste ponto é que começamos a fazer o trabalho de identificar se o preço está dentro do canal. Este canal é formado pelas linhas de stop loss e take profit. Quando o preço estiver fora deste canal, a mágica começa a acontecer.
Pois bem, vamos entender como isto se dá. Na linha 249, verificamos junto com o valor de limite, se vamos ter a cor de fundo verde ou vermelha. Isto independente do segmento que esteja executando o código. Já na linha 250, iremos fazer um chaveamento entre os segmentos. Isto para que as cores sejam corretamente definidas. Ok, você, meu caro leitor, agora deve estar se perguntando: Mas usar a linha 249 para definir as cores, se iremos as definir novamente, fazendo um chaveamento? Isto não faz sentido. Na verdade, o motivo são as linhas 254 e 258. Sem a presença da linha 249, seria necessário replicar o mesmo código da linha 249, nas linhas 254 e 258, onde a variável _cor é indicada. Muito bem, mas então vamos entender esta loucura que está sendo feita entre as linhas 252 e 259. Já que a linha 260 é bem simples de ser compreendida.
Pois bem, aqui vou explicar evMsgCloseTakeProfit, mas a explicação serve para evMsgCloseStopLoss. Na linha 253 e na linha 257 temos a mesma coisa sendo feita. Ou seja, testamos se estamos recebendo o valor das linhas 65 e 66 do código principal ou não. Quando estivermos recebendo os valores do código principal, iremos armazenar ele em uma variável estática adequada. Caso contrário, replicamos o valor, para que possamos fazer uso do operador ternário. Agora também fazendo uso do operador ternário. Nas linhas 254 e 258, verificamos se o preço do ativo, está ou não dentro do canal. Um detalhe, este preço, faz uso do spread. Assim pode ser que mesmo olhando e imaginando que está tudo certo. Pode ser que não esteja. Então caso o preço esteja fora do canal adequado, iremos fazer com que o fundo se torne amarelo. Se ele, o preço, estiver no local adequado, o fundo poderá ficar verde ou vermelho. Dependendo do caso. Isto pode ser visto na animação acima. Quando tentamos colocar o stop loss em uma posição errada. Logo que a linha do stop loss entre em uma posição errada, ou equivocada teremos a indicação sendo feita em amarelo.
Quando a linha fica em uma posição adequada, a indicação muda de cor, saindo de amarelo para verde ou vermelha. Dependendo se haverá perda ou lucro naquele valor de preço específico. Por fim para que as coisas sejam atualizadas temos a execução da linha 264. Que irá de fato colocar a cor ajustada no fundo do objeto OBJ_EDIT, que nos informa o resultado naquele ponto.
Considerações finais
Sei que tudo isto pode parecer bastante confuso e complicado. Mas se você experimentar este sistema em uma conta DEMO, que é de fato a minha sugestão. Irá notar algo interessante. Sempre que o fundo ficar em amarelo, e você tentar fazer com que a linha de preço seja colocada ali, receberá como resultado do requerimento de operação um erro sendo reportado pelo servidor de negociação. Normalmente este erro será o 4756, como você pode perceber na imagem abaixo.

Apesar deste erro não ser de fato um erro vindo do servidor, e estando em meio aos erros de tempo de execução. O que é bastante curioso. Ele diz que o envio da solicitação de negociação falhou. Sendo que sua tag é ERR_TRADE_SEND_FAILED. Assim sendo, sempre que o fundo do objeto OBJ_EDIT ficar amarelo, esteja ciente de que este erro será visto na caixa de mensagem. Isto caso você, como usuário ou operador tente colocar o preço naquela posição.
Bem, de qualquer maneira, a implementação foi feita. Espero que você, meu caro leitor, tenha de fato compreendido o motivo e como ela funciona. Apesar de normalmente ser algo raro ela ficar em amarelo, em situações normais de negociação. Pode acontecer este fato. Se isto ocorrer, você deverá tomar algum tipo de atitude a fim de solucionar esta questão. Esta implementação não visa, lhe apavorar ou lhe causar pânico. Ela tem como único objetivo tornar bem mais prático e seguro o uso do indicador de posição.
No anexo, vou deixar disponível as aplicações necessárias para que você experimente localmente o sistema. Isto caso você não saiba como usar as informações contidas nos artigos, para de fato conseguir criar os códigos fontes. No demais, me despeço por hora, e nos vemos no próximo artigo, desta sequência sobre como criar um sistema de replay/simulador. 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.
Simplificando a negociação com base em notícias (Parte 6): Executando trades (III)
Do básico ao intermediário: Filas, Listas e Árvores (VIII)
Construa EAs auto-otimizáveis em MQL5 (Parte 3): Acompanhamento dinâmico de tendência e retorno à média
Construa Expert Advisors Auto-Otimizáveis em MQL5 (Parte 2): Estratégia de Scalping USDJPY
- 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