Simulação de mercado: Position View (XVIII)
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 (XVII), mostrei como você pode fazer, para que o indicador de posição, consiga mostrar outros tipos de dados. Permitindo que o operador visualize a situação da posição em termos de variação percentual, financeira, número de ticks ou mesmo diferença entre valores. Algo que você meu caro leitor, pode facilmente expandir ou até mesmo tornar ainda melhor.
Mas apesar de já estarmos com o indicador de posição, em uma situação quase que totalmente adequada. Isto para que ele possa ser utilizado dentro do replay/simulador. A fim de que possamos experimentar o sistema. Testando nossa habilidade na leitura de mercado. Ainda não podemos de fato fazer isto. Devido a algumas pendências que se encontram dentro do indicador.
Além disto, ainda precisamos corrigir algumas pequenas falhas que o indicador ainda tem. Isto por que, caso o operador, sem perceber, venha a cometer certos deslizes durante a operação. Ele ficará completamente sem meios de usar o indicador de posição. Pois o mesmo não funcionará de maneira adequada. Ou na maior parte das vezes, forçará o operador a tomar certas atitudes, que não parecem de fato serem intuitivas.
Muito bem, então neste artigo, vamos melhorar um pouco mais as coisas. Isto para que venhamos a ficar, um passo mais próximo do nosso objetivo final. Assim vamos para o primeiro tópico deste artigo.
Indicando o volume
A primeira coisa que vamos fazer é indicar o volume que está em aberto. E por que fazer isto? Bem, o motivo para fazer tal coisa é permitir que o operador possa ter a devida noção do volume em aberto. E sabendo disto, ele pode planejar efetuar um conjunto diferente de estratégias a fim de potencializar seu lucro, ou reduzir o seu risco. Realizando assim algum tipo de operação em paralelo. Como tais coisas fogem completamente do escopo do artigo. Não entrarei em detalhes sobre tais modelos de estratégia. Aqui o nosso foco é a programação. E não a forma de operar no mercado.
Muito bem, para que possamos mostrar o volume em aberto. Precisamos basicamente fazer uma pequena adição ao código. Tal adição tem como objetivo, incluir um objeto OBJ_EDIT, ou mesmo um OBJ_LABEL ao indicador. Mas por que não usar o OBJ_TEXT neste caso? O motivo é a forma de posicionar tal objeto. Diferente dos objetos OBJ_EDIT e OBJ_LABEL, o objeto OBJ_TEXT é posicionado usando o preço e o tempo. Não que isto nos impeça de fazer uso do mesmo. Mas é consideravelmente muito mais simples usar os demais. Dado que usaremos coordenadas cartesianas do tipo X Y.
Ok. Então cabe a nós, como programadores, decidir qual seria mais adequado ser usado. Pessoalmente, não vejo muita diferença em usar um ou outro. Apenas vejo que o OBJ_EDIT, nos permite uma apresentação um pouco melhor. Isto devido ao fato de que, podemos controlar melhor as propriedades do objeto. Nos permitindo assim não deixar o texto simplesmente jogado no indicador. Então por este motivo, faremos uso do OBJ_EDIT.
Assim a primeira coisa a ser feita é vista no fragmento logo abaixo:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameHLine m_Info.szPrefixName + "#HLINE" 05. #define def_NameBtnClose m_Info.szPrefixName + "#CLOSE" 06. #define def_NameBtnMove m_Info.szPrefixName + "#MOVE" 07. #define def_NameInfoDirect m_Info.szPrefixName + "#DIRECT" 08. #define def_NameObjLabel m_Info.szPrefixName + "#PROFIT" 09. #define def_NameBackGround m_Info.szPrefixName + "#BACKGROUND" 10. #define def_NameVolume m_Info.szPrefixName + "#VOLUME" 11. //+------------------------------------------------------------------+ 12. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1)); 13. //+------------------------------------------------------------------+
Fragmento de C_ElementsTrade
Note que na linha dez, adicionamos uma nova definição. Esta diz qual será o nome do objeto que iremos criar em breve. Agora veja que no código original, temos uma chamada que nos permite criar o objeto OBJ_EDIT. Pois bem, vamos modificar esta chamada, para o que é visto logo abaixo.
117. //+------------------------------------------------------------------+ 118. inline void CreateObjectInfoText(const string szObj, const color _color) 119. { 120. CreateObjectGraphics(szObj, OBJ_EDIT, clrNONE, (EnumPriority)(ePriorityDefault)); 121. ObjectSetString(0, szObj, OBJPROP_FONT, def_FontName); 122. ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, def_FontSize); 123. ObjectSetInteger(0, szObj, OBJPROP_COLOR, clrBlack); 124. ObjectSetInteger(0, szObj, OBJPROP_BORDER_COLOR, _color); 125. ObjectSetInteger(0, szObj, OBJPROP_ALIGN, ALIGN_CENTER); 126. ObjectSetInteger(0, szObj, OBJPROP_READONLY, true); 127. } 128. //+------------------------------------------------------------------+
Fragmento de C_ElementsTrade
Perfeito. Agora temos que corrigir o código original. Então precisamos atualizar o código usando o fragmento logo a seguir.
171. //+------------------------------------------------------------------+ 172. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0) 173. { 174. m_Info.sizeText = 0; 175. ObjectsDeleteAll(0, m_Info.szPrefixName); 176. m_Info.volume = (vol > 0 ? vol : m_Info.volume); 177. m_Info.var = (var > 0 ? var : m_Info.var); 178. if (price > 0) 179. { 180. CreateLinePrice(); 181. CreateButtonClose(); 182. CreateObjectInfoText(def_NameObjLabel, m_Info._color); 183. } 184. CreateBoxInfo(m_Info.ev != evMsgClosePositionEA); 185. m_Info.open = open; 186. UpdateViewPort(m_Info.price = (price > 0 ? price : open)); 187. if (m_Info.ev != evMsgClosePositionEA) 188. ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price); 189. } 190. //+------------------------------------------------------------------+
Fragmento de C_ElementsTrade
Observe que na linha 182, foi onde precisamos atuar. Feita esta atualização, o antigo código foi modificado, mas continua funcionando de maneira totalmente idêntica. Porém, a mudança que fizemos na linha 118, e o fato de termos adicionado uma nova definição na linha dez, nos permite começa a criar o novo elemento que precisamos. E é agora que vem a parte divertida. Mas antes, vamos fazer mais uma pequena atualização. Isto para podemos fazer uma melhor reutilização do código já existente. Tal mudança pode ser vista no fragmento abaixo.
137. //+------------------------------------------------------------------+ 138. inline void AdjustDinamic(const string szObj, const string szTxt) 139. { 140. uint w, h; 141. 142. TextSetFont(def_FontName, def_FontSize * -10); 143. TextGetSize(szTxt, w, h); 144. m_Info.Text.Height = (uchar) h + 4; 145. m_Info.Text.Width = (uchar) w + 4; 146. m_Info.Text.Width = UpdateViewPort(0, m_Info.Text.Width, h = 32); 147. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, m_Info.Text.Width); 148. ObjectSetInteger(0, szObj, OBJPROP_YSIZE, m_Info.Text.Height); 149. ObjectSetInteger(0, def_NameBackGround, OBJPROP_XSIZE, m_Info.Text.Width + h + (m_Info.ev == evMsgClosePositionEA ? 8 : 0)); 150. ObjectSetInteger(0, def_NameBackGround, OBJPROP_YSIZE, m_Info.Text.Height + 5); 151. } 152. //+------------------------------------------------------------------+ . . . 195. //+------------------------------------------------------------------+ 196. void ViewValue(const double profit) 197. { 198. string szTxt; 199. 200. switch (m_Info.ViewMode) 201. { 202. case stInfos::eValue: 203. szTxt = StringFormat("%." + (string)m_Info.Text.digits + "f", MathAbs(profit)); 204. break; 205. case stInfos::eFinance: 206. szTxt = StringFormat("$ %." + (string)m_Info.Text.digits + "f", (MathAbs(profit) / m_Info.var) * m_Info.volume); 207. break; 208. case stInfos::eTicks: 209. szTxt = StringFormat("%d", (uint)MathRound(MathAbs(profit) / m_Info.tickSize)); 210. break; 211. case stInfos::ePercentage: 212. szTxt = StringFormat("%.2f%%", NormalizeDouble((MathAbs(profit) / (m_Info.open ? m_Info.open : m_Info.price)) * 100, 2)); 213. break; 214. } 215. ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, szTxt); 216. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral)); 217. if (StringLen(szTxt) != m_Info.sizeText) 218. { 219. AdjustDinamic(def_NameObjLabel, szTxt); 220. m_Info.sizeText = StringLen(szTxt); 221. } 222. } 223. //+------------------------------------------------------------------+
Fragmento de C_ElementsTrade
Note que modifiquei o número de parâmetros no procedimento da linha 138. Isto para que nas linhas 147 e 148, pudéssemos fazer uso deste mesmo código. Isto para permitir mostrar o volume. Agora já que modificamos este procedimento. Imediatamente precisamos atualizar o código. Isto é feito na linha 219. Onde o procedimento era de fato chamado. Com isto agora a reutilização do código, se torna bem maior. Precisando assim que venhamos a programar bem menos.
Muito bem, agora podemos partir para a criação e colocação do objeto OBJ_EDIT, a fim de mostrar o volume que está em aberto. Isto não é de fato uma tarefa complicada. Apenas é uma questão de ajustar e posicionar os objetos que estão sendo criados. Mas para de fato criarmos e já aplicamos o valor correto no OBJ_EDIT. Vamos modificar o procedimento UpdatePrice, como mostrado abaixo.
171. //+------------------------------------------------------------------+ 172. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0) 173. { 174. m_Info.sizeText = 0; 175. ObjectsDeleteAll(0, m_Info.szPrefixName); 176. m_Info.volume = (vol > 0 ? vol : m_Info.volume); 177. m_Info.var = (var > 0 ? var : m_Info.var); 178. if (price > 0) 179. { 180. CreateLinePrice(); 181. CreateButtonClose(); 182. CreateObjectInfoText(def_NameObjLabel, m_Info._color); 183. } 184. CreateBoxInfo(m_Info.ev != evMsgClosePositionEA); 185. m_Info.open = open; 186. m_Info.price = (price > 0 ? price : open); 187. if (m_Info.ev != evMsgClosePositionEA) 188. ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price); 189. else 190. { 191. CreateObjectInfoText(def_NameVolume, clrBlack); 192. ObjectSetInteger(0, def_NameVolume, OBJPROP_BGCOLOR, clrViolet); 193. ObjectSetString(0, def_NameVolume, OBJPROP_TEXT, DoubleToString(m_Info.volume, (MathRound(m_Info.volume) != m_Info.volume ? 2 : 0))); 194. AdjustDinamic(def_NameVolume, "88888" + (MathRound(m_Info.volume) != m_Info.volume ? ".88" : "")); 195. }; 196. UpdateViewPort(m_Info.price); 197. } 198. //+------------------------------------------------------------------+
Fragmento de C_ElementsTrade
Perfeito, com isto estamos criando o objeto. Mas vamos entender o que está acontecendo aqui. Note que na linha 187 estamos verificando se a classe está sendo usada pelo segmento de preço de abertura, ou por outro segmento. Com queremos colocar o volume, no segmento do preço de abertura, precisamos que este teste seja verdadeiro. Ou seja, ele será quando a linha 189 for alcançada. Neste ponto começa o trabalho de verdade. Observe que na linha 191, criamos o objeto OBJ_EDIT. Isto para receber o volume. Na linha 192 dizemos a cor de fundo do objeto OBJ_EDIT. Mas a parte realmente interessante, e que merece uma explicação, são as linhas 193 e 194. O que estas linhas, tem de tão interessante a ponto de merecer destaque? Bem, elas são linhas especiais. E o motivo é que elas permitem ao indicador de posição, se adequar ao tipo de informação que deverá ser impressa.
Muito provavelmente, você meu caro e estimado leitor, não esteja entendendo o que está acontecendo. Mas é simples. Quando tivermos de imprimir um valor em ponto flutuante, estilo 0.02, o código irá se adaptar a isto. Quando o valor não é flutuante, podendo ser representado em formato de inteiros, a parte decimal será ignorada. Seria algo como mostrar o valor 234. Não faz sentido colocar um ponto seguido de dois zeros neste caso. Mas por que fazer isto? Bem, o motivo é simples. Se você não está operando um ativo, ou mercado que permite usar frações de um ativo. Não faz sentido mostrar um valor fracionário. Ou seja, se o indicador estiver em um mercado, como a B3 ( Bolsa do Brasil ) onde não se pode negociar frações. O indicador apresentará no volume, apenas números inteiros.
Porém, caso você esteja em uma bolsa, ou mesmo em um mercado onde frações são permitidas. Como por exemplo na bolsa americana, ou no FOREX. O indicador irá apresentar o valor em termos de frações. E é justamente isto que este código faz. Mas ele o faz de uma maneira um pouco diferente. Já que ele não irá olhar de fato o ativo, ou mercado que está sendo utilizado. Ele apenas irá olhar o valor presente no volume. Assim, se o volume tiver um valor decimal, estilo: 10.25 o indicador irá apresentar este valor. Mas se no mesmo ativo, o volume for de por exemplo: 10.00 no indicador você verá apenas 10 sendo que a parte fracionária será ignorada. E é isto que este código maluco nas linhas 193 e 194 faz de fato.
Note o detalhe que UpdateViewPort, foi trocada de localização. Por isto agora ela está na linha 196. O motivo, é que quando havia a atualização do volume, este não acompanhava o segmento. Ficando deslocado da posição correta. Porém, isto era devido ao fato de que a atualização não estava acontecendo no devido momento. Mas pelo fato de que agora, UpdateViewPort é chamada depois, a atualização ocorre perfeitamente.
Muito bem. Agora que já temos o valor sendo colocado no objeto OBJ_EDIT. Precisamos apresentar este objeto no gráfico. E neste momento precisamos ir ao código, onde o posicionamento de todos os objetos é feito. Sim, vamos agora para o procedimento UpdateViewPort. Mas antes de fazermos isto, precisamos atualizar um pequeno detalhe. Este é no procedimento AdjustDinamic, como você pode ver logo a seguir.
137. //+------------------------------------------------------------------+ 138. inline void AdjustDinamic(const string szObj, const string szTxt) 139. { 140. uint w, h; 141. 142. TextSetFont(def_FontName, def_FontSize * -10); 143. TextGetSize(szTxt, w, h); 144. m_Info.Text.Height = (uchar) h + 4; 145. m_Info.Text.Width = (uchar) w + 4; 146. m_Info.Text.Width = UpdateViewPort(0, m_Info.Text.Width, h = 32); 147. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, m_Info.Text.Width); 148. ObjectSetInteger(0, szObj, OBJPROP_YSIZE, m_Info.Text.Height); 149. ObjectSetInteger(0, def_NameBackGround, OBJPROP_XSIZE, m_Info.Text.Width + h + (m_Info.ev == evMsgClosePositionEA ? m_Info.Text.Width + 8 : 0)); 150. ObjectSetInteger(0, def_NameBackGround, OBJPROP_YSIZE, m_Info.Text.Height + 5); 151. } 152. //+------------------------------------------------------------------+
Fragmento de C_ElementsTrade
Veja que a linha 149, precisou sofrer um pequeno ajuste no seu cálculo. Isto para que o fundo ficasse com as dimensões corretas. Uma vez feito este pequeno ajuste, podemos ir para a UpdateViewPort. Esta é vista no fragmento abaixo.
55. //+------------------------------------------------------------------+ 56. short UpdateViewPort(const double price, short size = 0, uint ui = 0) 57. { 58. static short _SizeControls; 59. static short _Width; 60. uint x, y; 61. 62. if (size > 0) 63. { 64. size += (short)(ui + 8); 65. _SizeControls = (_SizeControls > size ? _SizeControls : size); 66. size = (short)(_SizeControls - ui - 12); 67. _Width = (_Width > size ? _Width : size); 68. }else 69. { 70. ChartTimePriceToXY(0, 0, 0, price, x, y); 71. x = 125 + (m_Info.ev == evMsgClosePositionEA ? 0 : _Width + (m_Info.ev == evMsgCloseTakeProfit ? _SizeControls : (_SizeControls * 2))); 72. ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x); 73. ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0)); 74. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x); 75. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 76. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XDISTANCE, x + 10 + (m_Info.ev == evMsgClosePositionEA ? _Width + 2 : 0)); 77. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2)); 78. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + (_Width * 2) + 20); 79. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y); 80. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + _Width + 20); 81. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y); 82. ObjectSetInteger(0, def_NameBackGround, OBJPROP_XDISTANCE, x - 10); 83. ObjectSetInteger(0, def_NameBackGround, OBJPROP_YDISTANCE, y - ((m_Info.Text.Height + 5) / 2)); 84. ObjectSetInteger(0, def_NameVolume, OBJPROP_XDISTANCE, x + 10); 85. ObjectSetInteger(0, def_NameVolume, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2)); 86. } 87. return _Width; 88. } 89. //+------------------------------------------------------------------+
Fragmento de C_ElementsTrade
Agora sim, temos a correta implementação. Isto para que todos os segmentos sejam mostrados. O resultado pode ser visto na animação abaixo.

Veja que o indicador se adapta de forma com que não tenhamos algum tipo de colisão entre os objetos. Apesar de no momento isto servir bem para contas do tipo NETTING este tipo de implementação, não é muito adequada, para contas do tipo HEDGING. Já que teremos colisão entre objetos, que estejam representando posições distintas. Mas mesmo em contas do tipo NETTING, ainda temos um problema, que é o fato de que conforme o número de objetos vai aumentando, temos cada vez mais as coisas sendo deslocadas no eixo X. E em algum momento isto será um problema. Mas por hora, não vamos nos preocupar com este detalhe.
Como o fragmento acima, é bastante simples, sendo apenas um conjunto de ajustes de posições. Não vejo necessidade de entrar em detalhes sobre o que está acontecendo. É tudo uma questão de desenhar em um papel e você conseguirá facilmente entender como o posicionamento está ocorrendo. Com isto terminamos este tópico. Podendo assim passar para o próximo.
Evitando que os objetos desapareçam
Talvez a coisa que mais pegue quando o assunto é objetos no gráfico. É quando um objeto é removido, sendo que ele é importante de alguma forma para um perfeito uso de alguma aplicação no MetaTrader 5. Até o momento, não temos nos preocupado com esta questão. Visto que estávamos apenas focados em implementar as funcionalidades básicas do indicador de posição. Porém, neste momento, já temos um bom conjunto de coisas já sendo implementadas. E neste momento, se torna necessário, que passemos a considerar o fato, de que se o operador ou usuário vier a remover algum objeto crítico. O indicador acabará ficando em uma situação um tanto quanto incomoda. Isto no que diz respeito ao seu correto funcionamento. Já que todo o controle está sendo feito via objetos presentes no gráfico.
Sendo assim, precisamos garantir que tais objetos, se por ventura vierem a ser removidos de forma inadvertida. Sejam de fato recriados e recolocados no gráfico. Evitando desta maneira que o usuário ou operador, fique sem o devido acesso ao que se pode fazer via indicador de posição.
Efetivamente, esta tarefa de assegurar a presença de determinados objetos. Não é de fato algo complicado. Porém, é preciso que tenhamos o devido cuidado em recolocá-los em uma determinada ordem. Caso contrário eles poderão ficar ocultos por outros objetos. Que fazem parte do mesmo segmento da classe C_ElementsTrade. Talvez, e muito certamente o objeto mais crítico de todos é o botão de fechar. Pois sem ele não conseguimos acesso direto ao envio de requerimentos ao servidor de negociação. Visto que é ele o responsável por emitir um evento customizado ao Expert Advisor. Mas independentemente disto, faremos e garantiremos que todos os objetos sejam de forma adequada mantidas no gráfico. Evitando assim que o operador ou usuário venha a ter dificuldades no uso do indicador de posição.
Para cumprir este objetivo, precisaremos fazer uso de um evento. Este é disparado pelo MetaTrader 5, cada vez que um objeto é removido do gráfico. Como a classe C_Terminal, já indica ao MetaTrader 5, que estamos interessados em obter, o disparo do tal evento. Não precisaremos declarar isto no código. Pois ele já vem sendo incluído, via herança.
Muito bem, então vamos começar a adicionar o código necessário na classe C_ElementsTrade. Não é algo de fato complicado. Mas merece um pouco de atenção. Basicamente, existem dois pontos dentro do código, onde os objetos podem ser removidos. Devemos antes de começar, garantir que os objetos de fato venham a ser removidos nestes dois pontos. Obviamente ambos fazem uso de uma chamada ObjectsDeleteAll, ou uma outra chamada cujo objetivo é de fato remover objetos do gráfico. Como nosso código tem apenas dois pontos onde isto de fato acontece. Podemos facilmente limitar as coisas ao fragmento mostrado abaixo.
168. //+------------------------------------------------------------------+ 169. ~C_ElementsTrade() 170. { 171. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 172. ObjectsDeleteAll(0, m_Info.szPrefixName); 173. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true); 174. } 175. //+------------------------------------------------------------------+ 176. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0) 177. { 178. m_Info.sizeText = 0; 179. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 180. ObjectsDeleteAll(0, m_Info.szPrefixName); 181. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true); 182. m_Info.volume = (vol > 0 ? vol : m_Info.volume);
Fragmento de C_ElementsTrade
Este fragmento impede que o MetaTrader 5, venha a disparar o evento, quando os objetos tiverem que ser removidos do gráfico. Já que este código, vem sendo atualizado, a numeração das linhas está mudando. Mas se você atualizou tudo que foi visto no tópico anterior. Poderá seguir para os pontos indicados no fragmento acima. Observe que foram adicionadas quatro novas linhas, 171 e 179 desligam o pedido de evento. Já as linhas 173 e 181 religam o pedido de evento. De certa maneira, o melhor é que façamos as coisas de uma outra forma. Isto por que, evita que venhamos a cometer erros na programação.
Assim, apesar do fragmento acima, garantir o que precisamos fazer. O ideal é que coloquemos estas três linhas em algo um pouco melhor. Uma forma seria colocar em uma macro. Outra é criar um procedimento dentro da classe. Como a criação da macro exige que a remoção explicita da mesma no final no arquivo de cabeçalho. E não pretendo gerar confusão para você, meu caro leitor. Faremos a criação de um procedimento. Este pode ser visto no fragmento abaixo.
154. //+------------------------------------------------------------------+ 155. inline void RemoveAllsObjects(void) 156. { 157. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 158. ObjectsDeleteAll(0, m_Info.szPrefixName); 159. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true); 160. } 161. //+------------------------------------------------------------------+ 162. public : 163. //+------------------------------------------------------------------+ . . . 175. //+------------------------------------------------------------------+ 176. ~C_ElementsTrade() 177. { 178. RemoveAllsObjects(); 179. } 180. //+------------------------------------------------------------------+ 181. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0) 182. { 183. m_Info.sizeText = 0; 184. RemoveAllsObjects(); 185. m_Info.volume = (vol > 0 ? vol : m_Info.volume); 186. m_Info.var = (var > 0 ? var : m_Info.var); 187. if (price > 0) 188. {
Fragmento de C_ElementsTrade
Neste fragmento acima, estou mostrando para você, meu caro leitor, onde o procedimento foi criado. E como o fragmento anterior, foi modificado para fazer uso, deste novo procedimento na classe C_ElementsTrade. Note que agora temos algo realmente mais simples. Porém você pode estar imaginando que criar este procedimento da linha 155 é algo desnecessário. Visto que o fragmento anterior resolvia o nosso problema. Porém é muito comum nos esquecermos de fazer algo, quando o código vai crescendo em termos de complexidade. Assim para evitar dores de cabeça no futuro. É sempre preferível que você coloque códigos comuns dentro de uma função ou procedimento.
Assim se algo precisar ser mudando, você apenas precisará mudar uma única parte do código. Isto é bem melhor do que mudar diversos pontos espalhados por todo o código. Muito bem explicado o motivo pelo qual o procedimento da linha 155 surgiu, podemos finalmente ver o procedimento DispatchMessage. Este pode ser visto logo abaixo:
235. //+------------------------------------------------------------------+ 236. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 237. { 238. string sz0; 239. long _lparam = lparam; 240. double _dparam = dparam; 241. 242. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 243. switch (id) 244. { . . . 294. case CHARTEVENT_OBJECT_DELETE: 295. if (StringFind(sparam, m_Info.szPrefixName) < 0) break; 296. UpdatePrice(m_Info.open, m_Info.price); 297. break; 298. } 299. } 300. //+------------------------------------------------------------------+ 301.
Fragmento de C_ElementsTrade
Veja que estou destacando apenas o que realmente importa para nós. E você já deve estar pensando: Será que isto funciona? E a resposta é: Mais ou menos. Mas antes de explicar o porquê, da resposta não ser um sim ou um não. Vamos entender como este fragmento funciona. Dentro deste tópico, eu mencionei que o MetaTrader 5 irá disparar um evento, assim que um objeto vier a ser removido do gráfico. Pois bem, no momento em que isto ocorrer, a linha 294 irá capturar este evento vindo do MetaTrader 5. O nome do objeto removido estará no argumento sparam. Veja a documentação para mais detalhes.
Então o que precisamos fazer é: Verificar se o nome informado pelo MetaTrader 5 é o mesmo que um dos nomes criados pelo indicador de posição. Existem diversas formas de verificarmos isto. Mas aqui, estamos fazendo uso de uma das mais simples. Que é justamente o uso da linha 295, onde usamos uma chamada da biblioteca do MQL5. Isto para verificar se o prefixo do nome do objeto criado pelo indicador, bate com o informado pelo MetaTrader 5. Caso negativo, iremos simplesmente ignorar todo restante do procedimento de reconstrução. Caso positivo, iremos na linha 296 pedir para que a classe C_ElementsTrade volte a criar os objetos.
Agora perceba o seguinte: O procedimento UpdatePrice, irá recriar os objetos. Isto faz com que o código funcione como esperado. Porém, existe um pequeno problema. E este surge no seguinte caso: Quando uma posição não tem um dos segmentos, por exemplo, o take profit. Apenas o objeto de movimentação estará presente no gráfico. Bem, neste caso, se o usuário ou operador remover este objeto. Esta chamada na linha 296 irá recriar, todos os demais objetos. Não apenas o objeto de movimento. E por isto que podemos dizer que o código não funciona. Ou resumindo ele funciona mais ou menos. Para evitar esta questão de ele funcionar mais ou menos, precisamos atualizar o código. Mas não se preocupe, é algo simples de ser feito. A tal atualização a ser feita pode ser vista no fragmento logo abaixo.
180. //+------------------------------------------------------------------+ 181. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0) 182. { 183. m_Info.sizeText = 0; 184. RemoveAllsObjects(); 185. m_Info.volume = (vol > 0 ? vol : m_Info.volume); 186. m_Info.var = (var > 0 ? var : m_Info.var); 187. if (price > 0) 188. { 189. CreateLinePrice(); 190. CreateButtonClose(); 191. CreateObjectInfoText(def_NameObjLabel, m_Info._color); 192. } 193. CreateBoxInfo(m_Info.ev != evMsgClosePositionEA); 194. m_Info.open = open; 195. m_Info.price = (price > 0 ? price : -open); 196. if (m_Info.ev != evMsgClosePositionEA) 197. ViewValue(m_Info.bIsBuy ? MathAbs(m_Info.price) - m_Info.open : m_Info.open - MathAbs(m_Info.price)); 198. else 199. { 200. CreateObjectInfoText(def_NameVolume, clrBlack); 201. ObjectSetInteger(0, def_NameVolume, OBJPROP_BGCOLOR, clrViolet); 202. ObjectSetString(0, def_NameVolume, OBJPROP_TEXT, DoubleToString(m_Info.volume, (MathRound(m_Info.volume) != m_Info.volume ? 2 : 0))); 203. AdjustDinamic(def_NameVolume, "88888" + (MathRound(m_Info.volume) != m_Info.volume ? ".88" : "")); 204. }; 205. UpdateViewPort(MathAbs(m_Info.price)); 206. } 207. //+------------------------------------------------------------------+ . . . 235. //+------------------------------------------------------------------+ 236. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 237. { 238. string sz0; 239. long _lparam = lparam; 240. double _dparam = dparam; 241. 242. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 243. switch (id) 244. { 245. case (CHARTEVENT_KEYDOWN): 246. if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break; 247. _lparam = (long) m_Info.ticket; 248. _dparam = 0; 249. EventChartCustom(0, evUpdate_Position, _lparam, 0, ""); 250. case CHARTEVENT_CUSTOM + evMsgSetFocus: 251. if ((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev)) 252. UpdatePrice(m_Info.open, GetPositionsMouse().Position.Price); 253. macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev)); 254. EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, ""); 255. m_Info.bClick = false; 256. case CHARTEVENT_CHART_CHANGE: 257. UpdateViewPort(MathAbs(m_Info.price)); 258. if (m_Info.ev != evMsgClosePositionEA) 259. ViewValue(m_Info.bIsBuy ? MathAbs(m_Info.price) - m_Info.open : m_Info.open - MathAbs(m_Info.price)); 260. break; 261. case CHARTEVENT_CUSTOM + evMsgSwapViewModePosition:
Fragmento de C_ElementsTrade
Preste bastante atenção pois é algo muito sútil. Olhe a linha 195. Veja que ali estou dizendo que o valor de m_Info.price, será negativo. Isto caso o parâmetro price seja menor ou igual a zero. Este tipo de coisa somente acontecerá, caso o código principal diga que não temos um valor para o segmento. Para que você entenda melhor, meu caro leitor. Quando o código principal do indicador, que pode ser visto nos artigos anteriores. Tentar pegar o valor da linha, seja do take profit ou stop loss. E esta linha não existir na posição. Este parâmetro price será igual a zero. Caso contrário ele será diferente de zero.
E é aqui onde a mágica acontece. Caso o valor seja maior que zero, o fragmento anterior consegue repor os objetos do segmento. Mas caso ele seja igual a zero, dizemos que m_Info.price será negativo. Porém este valor negativo é exatamente o preço de abertura da posição. Assim, como não temos nenhuma linha para ser criada. O indicador irá representar apenas e tão somente o objeto que nos permite mover o preço. Isto para que possamos criar a linha que havia sido removida. Então caso o usuário, ou operador venha a tentar remover justamente este objeto. Quando o MetaTrader 5 disparar o evento CHARTEVENT_OBJECT_DELETE, a linha 296 chamará UpdatePrice. Neste momento, o objeto e apenas o objeto de movimentação será criado. Diferente do que acontecia antes.
Mas por que? Talvez você, meu caro leitor, ainda não tenha entendido por que isto acontecer. E tudo que foi preciso fazer foi atualizar o código de forma a tornar m_Info.price negativo. Para entender isto é preciso que você olhe a linha 296, e perceba que o segundo argumento passado para UpdatePrice é justamente m_Info.price. Mas neste ponto, volte sua atenção a linha 187. Veja que este valor é justamente o segundo argumento da chamada. Que pode ser visto na linha 181. Por conta que este valor é negativo, o teste na linha 187 irá retornar falso. Impedindo desta maneira que os demais objetos sejam recriados.
Porém fazer com que m_Info.price possa ser negativo, tem suas implicações. E tais implicações exigem que façamos novas atualizações no código. Desta maneira, note que nas linhas 197, 205, 257 e 259. Foi preciso adicionar uma chamada da biblioteca do MQL5, a fim de corrigir as coisas. Isto por que não podemos fazer os cálculos com valores negativos. MathAbs corrige isto, tornando o valor em seu valor absoluto. Desta forma, conseguimos com um mínimo de esforço corrigir o código. Tornando assim possível que o indicador de posição venha a impedir, caso aconteça. De o usuário ou operador remova objetos do gráfico. Objetos estes que foram criados pelo indicador de posição.
Antes de terminarmos vamos resolver uma pequena questão aqui. Quero que você, meu caro leitor, observe que temos uma faixa do código sendo duplicada no fragmento acima. Este código duplicado, pode ser notado com muita facilidade, devido ao recorte feito no código. E estou falando das linhas 257 a 259, que também aparecem entre 196 e 205. Apesar de não parecer estar duplicado. Este código de fato está duplicado. Então vamos resolver isto da mesma maneira que fizemos anteriormente. Vamos criar um procedimento para evitar esta duplicada. Assim o novo fragmento visto logo acima pode ser visto logo abaixo. Já atualizado.
161. //+------------------------------------------------------------------+ 162. inline void ChartChange(void) 163. { 164. UpdateViewPort(MathAbs(m_Info.price)); 165. if (m_Info.ev != evMsgClosePositionEA) 166. ViewValue(m_Info.bIsBuy ? MathAbs(m_Info.price) - m_Info.open : m_Info.open - MathAbs(m_Info.price)); 167. } 168. //+------------------------------------------------------------------+ 169. public : 170. //+------------------------------------------------------------------+ . . . 187. //+------------------------------------------------------------------+ 188. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0) 189. { 190. m_Info.sizeText = 0; 191. RemoveAllsObjects(); 192. m_Info.volume = (vol > 0 ? vol : m_Info.volume); 193. m_Info.var = (var > 0 ? var : m_Info.var); 194. if (price > 0) 195. { 196. CreateLinePrice(); 197. CreateButtonClose(); 198. CreateObjectInfoText(def_NameObjLabel, m_Info._color); 199. } 200. CreateBoxInfo(m_Info.ev != evMsgClosePositionEA); 201. m_Info.open = open; 202. m_Info.price = (price > 0 ? price : -open); 203. if (m_Info.ev == evMsgClosePositionEA) 204. { 205. CreateObjectInfoText(def_NameVolume, clrBlack); 206. ObjectSetInteger(0, def_NameVolume, OBJPROP_BGCOLOR, clrViolet); 207. ObjectSetString(0, def_NameVolume, OBJPROP_TEXT, DoubleToString(m_Info.volume, (MathRound(m_Info.volume) != m_Info.volume ? 2 : 0))); 208. AdjustDinamic(def_NameVolume, "88888" + (MathRound(m_Info.volume) != m_Info.volume ? ".88" : "")); 209. }; 210. ChartChange(); 211. } 212. //+------------------------------------------------------------------+ . . . 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:
Fragmento de C_ElementsTrade
Agora temos um novo procedimento na linha 162. Este procedimento é privativo da classe C_ElementsTrade. Agora um detalhe bastante importante. Apesar deste procedimento da linha 162 ter um com o mesmo nome na classe C_Terminal. Eles não causam confusão ao compilador. Isto por que ambos são privativos da classe em que estão sendo declarados. Mesmo que C_ElementsTrade receba por herança, procedimentos da classe C_Terminal. Ao tentar chamar, este código da linha 162, isto nas linhas 210 e 262. O compilador irá conseguir compreender que não se trata de um procedimento contido na classe C_Terminal. Isto por que aquele procedimento não pode ser acessado fora da classe C_Terminal. Mesmo que a classe C_ElementsTrade esteja herdando os procedimentos. Isto não irá acontecer de forma alguma. Então assim conseguimos eliminar completamente mais uma parte do código, que se encontrava duplicado no nosso sistema.
Considerações finais
Neste artigo, mostrei, ou melhor, tentei mostrar da forma o mais didática possível. Como você pode conseguir modificar e gerar um código que seja capaz de cumprir alguns objetivos. Isto modificando o mínimo possível um código já existente. Nada disto seria possível, sem que o sistema de fato estivesse sendo pensado para ser modular. Desenvolver sistemas desta maneira, apesar de parecer no começo algo complicado. De fato não é assim tão complexo. Mas exige que você, meu caro leitor e entusiasta, esteja sempre pensando, praticando e estudando. Isto para que consiga, de fato tirar todo o proveito do que uma determinada linguagem nos consegue promover.
No anexo deste artigo, irei deixar disponível esta versão do código já compilada. Assim como as demais aplicações necessárias para que o indicador de posição possa ser testado. Caso você queira vê-lo funcionando antes de tentar seguir os códigos que estão sendo mostrados nos artigos. Mas ainda não terminamos este indicador. Ele ainda contém alguns pontos que precisam ser melhor codificados para que possamos de fato usar o mesmo no Replay/Simulador. Mas tais pontos não são de fato um grande problema. Apenas meros inconvenientes que precisam ser ajeitados. Isto para que o Replay/Simulador, possa conseguir de fato fazer uso deste indicador. Estamos cada vez mais próximos de ter um sistema realmente funcional. Continuem acompanhando esta sequência de artigos. E nos vemos no próximo artigo. Onde continuaremos a nossa caminhada para conseguir produzir um Replay/Simulador realmente funcional.
| 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.
Otimização em estilo Battle Royale — Battle Royale Optimizer (BRO)
Do básico ao intermediário: Filas, Listas e Árvores (VII)
Redes neurais em trading: Hierarquia de habilidades para comportamento adaptativo de agentes (Conclusão)
Aprendizado de máquina em trading direcional de tendência com o exemplo do ouro
- 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