preview
Simulação de mercado: Position View (XX)

Simulação de mercado: Position View (XX)

MetaTrader 5Testador |
113 0
Daniel Jose
Daniel Jose

Introdução

Olá pessoal, e sejam bem-vindos a mais um artigo da série sobre como construir um sistema de replay/simulação.

No artigo anterior Simulação de mercado: Position View (XIX), adicionamos uma forma de poder analisar se estamos ou não ajustando corretamente o stop loss ou take profit. Isto para que estes dois pontos possam ser corretamente configurados, a fim de que o preço atual do ativo. Fique entre o stop loss e o take profit. Você deve ter notado que isto foi feito de uma maneira bastante simples e prática. Tenho tentado manter o foco dos artigos, neste quesito. Ou seja, que as mudanças sejam bastante simples, fazendo o melhor uso possível do código já implementado. E sempre tentando ser o mais didático possível. Isto para que você, meu caro leitor, consiga acompanhar o que está sendo desenvolvido. Mas principalmente, para que você, consiga modificar o código, a fim de conseguir implementar uma solução que lhe seja mais adequada.

Pois bem, aqui neste artigo, não será diferente. Tempos atrás, começamos a implementar o que será melhor explorado, e de fato implementado aqui. Talvez, o que será visto neste artigo, não lhe pareça assim tão necessário, ser implementado. Mas garanto a você, que será algo bastante interessante. Isto pelo ponto de vista, de como faremos a implementação. Já que vamos implementar uma pequena modificação, que tem como objetivo, mostrar ao operador ou usuário, onde estava anteriormente o ponto de stop loss ou take profit. Isto, antes de você começar a mover ele para uma nova posição.

Para que você compreenda melhor, o que será implementado. Quero que você entenda o seguinte: Depois de selecionarmos um dos segmentos, seja ele o take profit ou stop loss. Isto para mudar o ponto onde ele se encontra. Não fazemos a atualização do valor, ao mesmo tempo que o estamos movendo. É preciso que finalizemos de alguma maneira, seja clicando, seja pressionando ESC, a movimentação. Somente depois disto, é que o Expert Advisor, recebe um pedido do indicador de posição, para que seja enviado um requerimento ao servidor.

Este requerimento tem como objetivo, mudar o valor que estava originalmente sendo, o take profit ou stop loss. Porém durante esta movimentação, o valor antigo continua sendo o mesmo dentro do servidor. E é isto que iremos implementar neste artigo. Criaremos uma forma, de mostrar onde o preço ainda se encontra. Assim, caso a posição seja fechada antes de concluirmos a movimentação para um novo ponto. Saberemos o motivo. Não ficando uma coisa meio que estranha no uso do indicador de posição.

Para criar, ou melhor modificar o indicador de posição a fim de conseguir fazer isto. Será preciso que você entenda alguns detalhes. Então vamos para o primeiro tópico deste artigo. Para assim começar a implementar as mudanças necessárias.


Entendendo como criar uma sombra do preço

Conforme foi dito na introdução deste artigo. Aqui iremos criar um tipo de sombra. Esta tem como objetivo mostrar onde o preço se encontrava, ou ainda se encontra antes de que a movimentação, que estamos fazendo seja concluída. Criar esta sobra é algo bastante interessante pelo ponto de vista, da implementação. Já que na verdade não iremos fazer as coisas de uma maneira completamente maluca. Vamos simplesmente dizer ao MetaTrader 5, como ele deverá entender e lidar com os objetos do indicador de posição.

Muito bem, neste ponto do desenvolvimento, você deve estar pensando que adicionar qualquer nova funcionalidade ao indicador, irá nos forçar a criar muito código. Ou no mínimo, modificar o código já existente de uma forma bastante profunda. Mas não é bem isto, que será preciso ser feito, meu caro leitor. Como o código tem sido pensado para ser o mais modular possível. Precisaremos mudar muito pouco no código. Talvez uma ou outra nova rotina venha a ser necessário. Mas logo de início, devemos sempre tentar, usar o que já está implementado. Isto para que a quantidade de testes, a fim de garantir que tudo esteja funcionando, seja a menor possível. Este tipo de coisa, nos permite agilizar ao máximo o desenvolvimento de qualquer aplicação.

Assim, tendo esta ideia de que devemos modificar, sempre que possível, o mínimo de código. Vamos ver como podemos criar a tal sombra. Em primeiro lugar, precisamos definir uma questão importante. As cores. Isto por que, não queremos criar confusão ao olhar o gráfico. Então definimos as novas cores como mostrado abaixo. Assim como a questão dos novos nomes. Isto para que possamos separar os objetos que fazem parte da sombra dos objetos que não fazem parte. Para isto, vamos precisar adicionar algo bem simples ao código, como pode ser visto no fragmento abaixo.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_NameHLine      m_Info.szPrefixName + "#HLINE"
005. #define def_NameBtnClose   m_Info.szPrefixName + "#CLOSE"
006. #define def_NameBtnMove    m_Info.szPrefixName + "#MOVE"
007. #define def_NameInfoDirect m_Info.szPrefixName + "#DIRECT"
008. #define def_NameObjLabel   m_Info.szPrefixName + "#PROFIT"
009. #define def_NameBackGround m_Info.szPrefixName + "#BACKGROUND"
010. #define def_NameVolume     m_Info.szPrefixName + "#VOLUME"
011. //+------------------------------------------------------------------+
012. #define def_GhostColor     clrDarkGray
013. #define macro_GhostName(A) (m_Info.szPrefixName + "[GHOST]" + (A != "" ? StringSubstr(A, StringFind(A, "#")) : ""))
014. //+------------------------------------------------------------------+
015. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1));
016. //+------------------------------------------------------------------+
                                   .
                                   .
                                   .
332. //+------------------------------------------------------------------+
333. #undef def_GhostColor
334. #undef macro_GhostName
335. //+------------------------------------------------------------------+
336. #undef macro_LineInFocus
337. //+------------------------------------------------------------------+
338. #undef def_Btn_Close
339. #undef def_PathBtns
340. #undef def_FontName
341. #undef def_FontSize
342. //+------------------------------------------------------------------+
343. #undef def_NameVolume
344. #undef def_NameBackGround
345. #undef def_NameObjLabel
346. #undef def_NameInfoDirect
347. #undef def_NameBtnMove
348. #undef def_NameBtnClose
349. #undef def_NameHLine
350. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Note como a coisa começa a surgir. Na linha 12, definimos a cor a ser usada. Já na linha 13 definimos uma macro, cujo objetivo, é permitir que troquemos o nome dos objetos. Mas está macro não irá trabalha sozinha. Ela é apenas uma pequena parte de um código ainda maior. Mas vamos com calma. Primeiro comece prestando atenção, ao fato de que nas linhas 333 e 334, eu já coloco o código, a fim de remover as definições feitas nas linhas 12 e 13. Agora antes de prosseguirmos, vamos entender o que acontece na macro da linha 13. Para entender, observe os nomes dos objetos entre as linhas quatro e dez. Veja que todos começam com um valor predefinido. Mas todos tem algo em comum, que é justamente o carácter #. Este é o ponto que nos interessa.

Na macro, vamos continuar com o valor predefinido sendo utilizado. Isto para simplificar a remoção dos objetos criados pelo indicador de posição. Mas entre o nome original, que se encontra definido entre as linhas quatro e dez. Iremos adicionar algo. Este algo é[GHOST]. Mas manteremos o restante do nome original. Esta é justamente a tarefa feita pelas chamadas StringSubstr e StringFind. Elas têm o objetivo de remover os valores de m_info.szPrefixName e manter apenas o restante do nome original. Assim, por exemplo ao usarmos a macro para mudar o nome do objeto de fundo. O nome que anteriormente seria visto como por exemplo: 1685452684#BACKGROUND, agora seria visto como: 1685452684[GHOST]#BACKGROUND. Isto apenas usando a macro. Mas como eu disse, está macro, não irá trabalha sozinha. Precisamos de mais código para sustentar as coisas. Pois se trocarmos o nome dos objetos, sem garantir que eles serão acessíveis de alguma forma, corremos o risco de bagunçar completamente as coisas.

Para que você entenda como as coisas acontecerão. Vamos ver um código bem simples, onde mostro como a troca de nome funciona. Este código pode ser visto na íntegra logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. //+------------------------------------------------------------------+
07. #define prefixName "Obj #"
08. #define macro_Swap(A) (prefixName + "[GHOST]" + StringSubstr(A, StringFind(A, "#") + 1))
09. #define def_ObjectName(A) (prefixName + (string)(A))
10. //+------------------------------------------------------------------+
11. void ChartOfTest(string sz1, int x, int y, int zOrder)
12. {
13.     long id = ChartID();
14.     
15.     ObjectCreate(id, sz1, OBJ_CHART, 0, 0, 0);
16.     ObjectSetInteger(id, sz1, OBJPROP_XDISTANCE, x);
17.     ObjectSetInteger(id, sz1, OBJPROP_YDISTANCE, y);
18.     ObjectSetInteger(id, sz1, OBJPROP_ZORDER, zOrder);
19.     ObjectSetInteger(id, sz1, OBJPROP_SELECTABLE, true);
20. }
21. //+------------------------------------------------------------------+
22. int OnInit()
23. {
24.     ChartOfTest(def_ObjectName(1), 160, 140, 1);
25.     
26.     return INIT_SUCCEEDED;
27. }
28. //+------------------------------------------------------------------+
29. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
30. {
31.     return rates_total;
32. }
33. //+------------------------------------------------------------------+
34. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
35. {
36.     switch (id)
37.     {
38.         case CHARTEVENT_OBJECT_CLICK:
39.             if (StringSubstr(sparam, 0, StringFind(sparam, "#") + 1) == prefixName)
40.                 ObjectSetString(0, def_ObjectName(1), OBJPROP_NAME, macro_Swap(def_ObjectName(1)));
41.             break;
42.     }
43. }
44. //+------------------------------------------------------------------+
45. void OnDeinit(const int reason)
46. {
47.     ObjectsDeleteAll(ChartID(), prefixName);
48. }
49. //+------------------------------------------------------------------+

Código de Demonstração

Sei que talvez este código possa lhe parecer muito confuso, meu caro leitor. Mas não se engane. Ele é bastante simples. Tudo que estamos fazendo aqui, é justamente testar e entender, o que será feito no indicador de posição. Note que a ideia é criar um objeto, que no caso é um OBJ_CHART. Este é criado na linha 15. Com o nome definido na linha 24. Para dizer qual é este nome, usamos a definição da linha nove. Nesta definição temos um prefixo. Este é definido na linha sete. O uso do prefixo, nos permite remover o objeto OBJ_CHART, que este código criou de maneira bem simples. Isto na linha 47. Agora preste atenção. Quando você colocar esta aplicação em um gráfico. E pressionar a combinação CTRL + B, lhe será apresentada uma janela para que você veja todos objetos no gráfico. Esta pode ser vista na imagem logo abaixo:

Caso o OBJ_CHART, não apareça na lista, basta clicar no botão LIST ALL, como mostrada na imagem. Assim note o nome que o MetaTrader 5, está mostrando. Ok, agora feche esta janela que lista os objetos e clique no OBJ_CHART. Isto fará com que o MetaTrader 5, dispare um evento e este será capturado pela linha 38 do nosso código de teste. Ao fazer isto, na linha 39 iremos averiguar se o nome do objeto que recebeu o clique, tem como prefixo, a definição feita na linha sete. Caso isto seja verdadeiro, na linha 40, iremos trocar o nome do objeto OBJ_CHART por um outro nome. Mas qual nome?

Bem, este novo nome é descrito na macro. Veja que precisamos dizer a macro o nome original do objeto. Assim, a macro da linha oito, criaremos um novo nome para o objeto. Isto com base no nome original que o objeto já continha. Observe como o código da macro, se parece com o código que estamos colocando no arquivo de cabeçalho C_ElementsTrade.mqh. Muito bem, com um novo nome definido. Pressionamos novamente CTRL + B e podemos verificar que agora o nome do objeto é outro. Como mostrado na imagem abaixo.

Caso você nunca tenha feito isto antes. Experimente para ver os resultados. Em alguns casos pode ser algo bastante interessante de ser feito. E é justamente este mesmo mecanismo que iremos usar no indicador de posição. Isto para conseguir criar a tal sombra. Agora que já temos as bases e o conhecimento necessário. Podemos continuar, mas para separar as coisas vamos a um novo tópico.


Criando a sombra do preço

Agora que você, aprendeu que podemos trocar o nome dos objetos. Caso você ainda não soubesse que tal coisa era possível. É hora de fazer uso deste conhecimento. Isto para criar a tal sombra que desejamos. Bem, antes vamos entender como isto será feito. Se criamos novos objetos, eles estarão na parte superior da lista de objetos. E já vimos o que acontece, em artigos anteriores. Os novos objetos irão sobrepor os antigos. Ficando os antigos como sendo objetos de fundo. Muito bem, então para criar a tal sombra, a maneira mais simples é manter os objetos antigos no gráfico. Mudar o nome deles para um outro. E logo em seguida criar os novos objetos. Simples assim.

Mas o código atual, não nos permite visualizar os objetos, caso venhamos a mudar os seus nomes. Antes de começarmos a ver as modificações. Quero dizer, que neste primeiro momento, não nos interessa de fato, que o código não seja duplicado. O que de fato nos interessa é que ele venha a funcionar da forma como esperamos, que ele funcione. Ou seja, que possamos criar a sombra do preço, ao mesmo tempo em que fazemos as coisas, funcionarem como era antes. Assim precisamos de fato, em primeiro lugar resolver a questão de mostrar a sombra. Isto a fim de conseguir avançar na implementação. Para fazer isto, basta que modifiquemos o código como mostrado no fragmento abaixo.

029. //+------------------------------------------------------------------+
030. class C_ElementsTrade : private C_Mouse
031. {
032.     private    :
033. //+------------------------------------------------------------------+
034.         struct stInfos
035.         {
036.             struct st_01
037.             {
038.                 short   Width,
039.                         Height,
040.                         digits;
041.             }Text;
042.             ulong       ticket;
043.             string      szPrefixName,
044.                         szDescr,
045.                         szSymbol,
046.                         szMsgGhost;
047.             EnumEvents  ev;
048.             double      price,
049.                         open,
050.                         volume,
051.                         var,
052.                         tickSize,
053.                         tpsl,
054.                         limit,
055.                         priceGhost;
056.             bool        bClick,
057.                         bIsBuy;
058.             char        weight;
059.             color       _color;
060.             int         sizeText;
061.             enum e1 {eValue, eFinance, eTicks, ePercentage} ViewMode;
062.         }m_Info;
063. //+------------------------------------------------------------------+
064.         short UpdateViewPort(const double price, short size = 0, uint ui = 0)
065.         {
066.             static short _SizeControls;
067.             static short _Width;
068.             uint x, y;
069.             
070.             if (size > 0)
071.             {
072.                 size += (short)(ui + 8);
073.                 _SizeControls = (_SizeControls > size ? _SizeControls : size);
074.                 size = (short)(_SizeControls - ui - 12);
075.                 _Width = (_Width > size ? _Width : size);
076.             }else
077.             {
078.                 ChartTimePriceToXY(0, 0, 0, price, x, y);            
079.                 x = 125 + (m_Info.ev == evMsgClosePositionEA ? 0 : _Width + (m_Info.ev == evMsgCloseTakeProfit ? _SizeControls : (_SizeControls * 2)));
080.                 ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
081.                 ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));
082.                 ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
083.                 ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
084.                 ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XDISTANCE, x + 10 + (m_Info.ev == evMsgClosePositionEA ? _Width + 2 : 0));
085.                 ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2));
086.                 ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + (_Width * 2) + 20);
087.                 ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y);
088.                 ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + _Width + 20);
089.                 ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y);
090.                 ObjectSetInteger(0, def_NameBackGround, OBJPROP_XDISTANCE, x - 10);
091.                 ObjectSetInteger(0, def_NameBackGround, OBJPROP_YDISTANCE, y - ((m_Info.Text.Height + 5) / 2));
092.                 ObjectSetInteger(0, def_NameVolume, OBJPROP_XDISTANCE, x + 10);
093.                 ObjectSetInteger(0, def_NameVolume, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2));
094.                 if (m_Info.szMsgGhost != "")
095.                 {
096.                     ChartTimePriceToXY(0, 0, 0, m_Info.priceGhost, x, y);
097.                     x = 125 + (m_Info.ev == evMsgClosePositionEA ? 0 : _Width + (m_Info.ev == evMsgCloseTakeProfit ? _SizeControls : (_SizeControls * 2)));
098.                     ObjectSetInteger(0, macro_GhostName(def_NameHLine), OBJPROP_XDISTANCE, x);
099.                     ObjectSetInteger(0, macro_GhostName(def_NameHLine), OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));
100.                     ObjectSetInteger(0, macro_GhostName(def_NameObjLabel), OBJPROP_XDISTANCE, x + 10 + (m_Info.ev == evMsgClosePositionEA ? _Width + 2 : 0));
101.                     ObjectSetInteger(0, macro_GhostName(def_NameObjLabel), OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2));
102.                     ObjectSetInteger(0, macro_GhostName(def_NameBtnMove), OBJPROP_XDISTANCE, x + _Width + 20);
103.                     ObjectSetInteger(0, macro_GhostName(def_NameBtnMove), OBJPROP_YDISTANCE, y);
104.                     ObjectSetInteger(0, macro_GhostName(def_NameBackGround), OBJPROP_XDISTANCE, x - 10);
105.                     ObjectSetInteger(0, macro_GhostName(def_NameBackGround), OBJPROP_YDISTANCE, y - ((m_Info.Text.Height + 5) / 2));
106.                 }
107.             }
108.             return _Width;
109.         }
110. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Aqui nós já temos algumas poucas mudanças no código. Primeiro você pode ver na linha 46, declaramos uma nova variável. Assim como na linha 55. Ambas variáveis são inicializadas no constructor, como todas a demais. Porém devido a forma como o constructor inicializa a estrutura stInfos. Os valores de ambas serão padrão. Ou seja, a variável szMsgGhost, inicializa vazia. Já a variável priceGhost, com o valor zero. Então não precisamos no preocupar. Apenas preste atenção ao fato de que o teste na linha 94, garante que não iremos ficar tentando plotar algo que não se encontra no gráfico. Como inicialmente o valor de m_Info.szMsgGhost está vazio, o teste irá falhar. Talvez você esteja pensando: Mas por que não testar o valor de priceGhost. Que com certeza seria muito mais simples? Sim, de fato testar m_Info.priceGhost, seria mais simples. Porém, você verá que isto nos traria alguns inconvenientes. Por este motivo, estamos testando m_Info.szMsgGhost.

Observe que todo o código entre as linhas 96 e 105, se parece em muito com o código que posiciona os objetos que não são as sombras. Claro, apesar de parecer bastante, o mesmo contem pequenas peculiaridades, como você pode notar facilmente.

Muito bem, agora já podemos mostrar no gráfico, os objetos que serão a sombra dos originais, que indicam onde o preço se encontrava. Assim a próxima coisa a ser feita é vista no fragmento logo a seguir.

160. //+------------------------------------------------------------------+
161. inline void AdjustDinamic(const string szObj, const string szTxt)
162.         {
163.             uint w, h;
164. 
165.             TextSetFont(def_FontName, def_FontSize * -10);
166.             TextGetSize(szTxt, w, h);
167.             m_Info.Text.Height = (uchar) h + 4;
168.             m_Info.Text.Width = (uchar) w + 4;
169.             m_Info.Text.Width = UpdateViewPort(0, m_Info.Text.Width, h = 32);
170.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, m_Info.Text.Width);
171.             ObjectSetInteger(0, szObj, OBJPROP_YSIZE, m_Info.Text.Height);
172.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_XSIZE, m_Info.Text.Width + h + (m_Info.ev == evMsgClosePositionEA ? m_Info.Text.Width + 8 : 0));
173.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_YSIZE, m_Info.Text.Height + 5);
174.             if (m_Info.szMsgGhost != "")
175.             {
176.                 ObjectSetInteger(0, macro_GhostName(def_NameBackGround), OBJPROP_XSIZE, m_Info.Text.Width + h + (m_Info.ev == evMsgClosePositionEA ? m_Info.Text.Width + 8 : 0));
177.                 ObjectSetInteger(0, macro_GhostName(def_NameBackGround), OBJPROP_YSIZE, m_Info.Text.Height + 5);
178.                 ObjectSetString(0, macro_GhostName(def_NameObjLabel), OBJPROP_TEXT, m_Info.szMsgGhost);
179.             }
180.         }
181. //+------------------------------------------------------------------+
182. inline void ChartChange(void)
183.         {
184.             UpdateViewPort(MathAbs(m_Info.price));
185.             m_Info.limit = (m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price);
186.             if (m_Info.ev != evMsgClosePositionEA)
187.                 ViewValue(m_Info.limit);
188.         }
189. //+------------------------------------------------------------------+
190.         void CreateGhost(void)
191.         {
192.             ObjectSetInteger(0, def_NameHLine, OBJPROP_BGCOLOR, def_GhostColor);
193.             ObjectSetInteger(0, def_NameHLine, OBJPROP_COLOR, def_GhostColor);
194.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, def_GhostColor);
195.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BORDER_COLOR, def_GhostColor);
196.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_COLOR, def_GhostColor);
197.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_BGCOLOR, def_GhostColor);
198.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_COLOR, def_GhostColor);            
199.             ObjectSetString(0, def_NameHLine, OBJPROP_NAME, macro_GhostName(def_NameHLine));
200.             ObjectSetString(0, def_NameBtnMove, OBJPROP_NAME, macro_GhostName(def_NameBtnMove));
201.             ObjectSetString(0, def_NameObjLabel, OBJPROP_NAME, macro_GhostName(def_NameObjLabel));
202.             ObjectSetString(0, def_NameBackGround, OBJPROP_NAME, macro_GhostName(def_NameBackGround));
203.             m_Info.priceGhost = MathAbs(m_Info.price);
204.             m_Info.szMsgGhost = "";
205.         }
206. //+------------------------------------------------------------------+
207.     public    :
208. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Aqui temos um fragmento um pouco maior. Mas apesar de ele ser bastante simples, existem algumas coisas que merecem algum destaque aqui. A primeira coisa que você irá notar, é que não existe mais o código RemoveAllsObjects. Que foi implementado no artigo 130 desta sequência. Ou seja, o código está sempre sofrendo melhorias, ou mudanças. Tanto que, se você estiver seguindo, à risca, os fragmentos conforme eles vão sendo postados, notará que eles se encaixam perfeitamente. Gerando assim o código fonte, naquele ponto específico do desenvolvimento.

Mas neste fragmento, quero chamar a sua atenção, meu caro leitor, para os seguintes pontos. Primeiro, na linha 178, colocamos no OBJ_EDIT, que será a sombra. O mesmo valor que está sendo informado, quando o teste na linha 174 tiver sucesso. Mas observe que o valor será sempre constante. E é importante que você note isto, pois depois irei explicar um detalhe. Que neste primeiro momento limita as coisas. Mas nada de muito grave. Apenas fique atento a esta linha 178.

Bem, com tudo, precisamos focar na sombra. E esta é criada ao executarmos o procedimento da linha 190. Esta linha é quase mágica. Já que ele pega todos os objetos originais, e os torna sombras ou fantasmas. Observe como isto acontece da mesma maneira como foi feito no tópico anterior. Só que lá estávamos apenas modificando o nome. Já aqui, além do nome, também mudamos algumas das propriedades dos objetos. De qualquer maneira, no final teremos o que precisamos. Ou seja, a criação dos objetos sombra. Agora atenção, quase todos os objetos são afetados. Mas não estamos mexendo no objeto que aparece no gráfico como sendo o botão de fechar. Este irá ser tratado em outro local.

Agora um detalhe curioso, que você pode estar se perguntando. Neste procedimento da linha 190, onde criamos a sombra, não deveríamos, enviar algo para m_Info.szMsgGhost? Pois aqui estou vendo que a variável está recebendo um valor vazio. Assim como m_Info.priceGhost está recebendo um valor que é o módulo do preço. Mas por que? Se estamos criando as sombras, deveríamos colocar um valor em m_Info.szMsgGhost, a fim de que as sombras aparecem no gráfico.

Se você pensou assim, meu caro leitor. Devo parabenizá-lo, isto por que você de fato está compreendendo como o código deveria funcionar, ou qual deveria ser a ideia inicial a ser implementada. Porém, as coisas nem sempre são muito obvias. As vezes precisamos forçar algo para obter o resultado desejado. Para compreender o motivo pelo qual as variáveis estão recebendo estes valores nas linhas 203 e 204. Será preciso entender o que se passa em outros dois pontos do código. Para simplificar, dividirei os mesmos em dois fragmentos. O primeiro é visto logo abaixo.

208. //+------------------------------------------------------------------+
209.         C_ElementsTrade(const ulong ticket, string szSymbol, const EnumEvents ev, color _color, char digits, double ticksize, string szDescr = "\n", const bool IsBuy = true)
210.             :C_Mouse(0, "")
211.         {
212.             ZeroMemory(m_Info);
213.             m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
214.             m_Info._color = _color;
215.             m_Info.szDescr = szDescr;
216.             m_Info.bIsBuy = IsBuy;
217.             m_Info.Text.digits = digits;
218.             m_Info.tickSize = ticksize;
219.             m_Info.szSymbol = szSymbol;
220.         }
221. //+------------------------------------------------------------------+
222.         ~C_ElementsTrade()
223.         {
224.             ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
225.             ObjectsDeleteAll(0, m_Info.szPrefixName);
226.             ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
227.         }
228. //+------------------------------------------------------------------+
229. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0, const double special = -1)
230.         {
231.             m_Info.sizeText = 0;
232.             ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
233.             ObjectsDeleteAll(0, m_Info.szPrefixName + "#");
234.             ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
235.             m_Info.volume = (vol > 0 ? vol : m_Info.volume);
236.             m_Info.var = (var > 0 ? var : m_Info.var);
237.             m_Info.tpsl = (special >= 0 ? special : m_Info.tpsl);
238.             if (price > 0)
239.             {
240.                 CreateLinePrice();
241.                 CreateButtonClose();
242.                 CreateObjectInfoText(def_NameObjLabel, m_Info._color);
243.             }
244.             CreateBoxInfo(m_Info.ev != evMsgClosePositionEA);
245.             m_Info.open = open;
246.             m_Info.price = (price > 0 ? price : -open);
247.             if (m_Info.ev == evMsgClosePositionEA)
248.             {
249.                 CreateObjectInfoText(def_NameVolume, clrBlack);
250.                 ObjectSetInteger(0, def_NameVolume, OBJPROP_BGCOLOR, clrViolet);                
251.                 ObjectSetString(0, def_NameVolume, OBJPROP_TEXT, DoubleToString(m_Info.volume, (MathRound(m_Info.volume) != m_Info.volume ? 2 : 0)));
252.                 AdjustDinamic(def_NameVolume, "88888" + (MathRound(m_Info.volume) != m_Info.volume ? ".88" : ""));
253.             };
254.             ChartChange();
255.         }
256. //+------------------------------------------------------------------+
257.         void ViewValue(const double profit, const bool Enabled = true)
258.         {
259.             string szTxt;
260.             color  _cor;
261.             static double memSL, memTP;
262.             
263.             if (Enabled)
264.             {
265.                 switch (m_Info.ViewMode)
266.                 {
267.                     case stInfos::eValue:
268.                         szTxt = StringFormat("%." + (string)m_Info.Text.digits + "f", MathAbs(profit));
269.                         break;
270.                     case stInfos::eFinance:
271.                         szTxt = StringFormat("$ %." + (string)m_Info.Text.digits + "f", (MathAbs(profit) / m_Info.var) * m_Info.volume);
272.                         break;
273.                     case stInfos::eTicks:
274.                         szTxt = StringFormat("%d", (uint)MathRound(MathAbs(profit) / m_Info.tickSize));
275.                         break;
276.                     case stInfos::ePercentage:
277.                         szTxt = StringFormat("%.2f%%", NormalizeDouble((MathAbs(profit) / (m_Info.open ? m_Info.open : m_Info.price)) * 100, 2));
278.                         break;
279.                 }
280.                 m_Info.szMsgGhost = ((m_Info.szMsgGhost == "") && (m_Info.ev != evMsgClosePositionEA) ? szTxt : m_Info.szMsgGhost);
281.                 ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, szTxt);
282.                 if (StringLen(szTxt) != m_Info.sizeText)
283.                 {
284.                     AdjustDinamic(def_NameObjLabel, szTxt);
285.                     m_Info.sizeText = StringLen(szTxt);
286.                 }
287.             }
288.             _cor = (m_Info.limit >= 0 ? clrPaleGreen : clrCoral);
289.             switch (m_Info.ev)
290.             {
291.                 case evMsgCloseTakeProfit:
292.                     memTP = (Enabled ? memTP : profit);
293.                     _cor = (m_Info.limit < memTP ? clrYellow : _cor);
294.                     break;
295.                 case evMsgCloseStopLoss:
296.                     memSL = (Enabled ? memSL : profit);
297.                     _cor = (m_Info.limit > memSL ? clrYellow : _cor);
298.                     break;
299.                 case evMsgClosePositionEA:
300.                     _cor = (profit >= 0 ? clrPaleGreen : clrCoral);
301.                     break;
302.             }
303.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, _cor);
304.         }
305. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Observe que agora o destructor, que está presente na linha 222, removerá todos os objetos, como é esperado que venha a ser feito. Porém UpdatePrice, que está na linha 229, não fará isto. Até na versão anterior, tanto UpdatePrice, quanto o destructor, removia todos os objetos. Bem aqui vale uma explicação, que até o momento era implícita. Já que era imaginado que seria facilmente compreendido por todos. O fato de UpdatePrice, remover os objetos para depois os criar. Só tem sentido quando você pensa na possibilidade de que a linha de stop loss ou take profit, venham a ser removidas. Muito provavelmente você imagina que ao chamar UpdatePrice, estaremos de fato atualizando o preço, onde stop loss ou take profit estejam. Mas a realidade, é um pouco diferente.

A ideia é realmente mostrar onde estes pontos realmente estariam. Porém, caso eles não existam na posição, precisamos remover os mesmos do gráfico. Criar apenas um procedimento para fazer isto, pode à primeira vista ser uma boa ideia. Mas tomando como base, que sempre precisaríamos testar se existe ou não um stop loss ou take profit na posição. É bem mais viável simplesmente remover os objetos, e logo depois os criar e os posicionar conforme a necessidade. Por conta disto que UpdatePrice, remove os objetos do gráfico.

Porém, como aqui estamos tornando estes mesmos objetos, sombras de outros que já estão no gráfico. Temos que limitar um pouco esta remoção dos objetos. Lembra que mencionei, neste mesmo artigo, o fato de não criamos uma sombra para o botão de fechar? E que não seria necessário remover ele naquele ponto específico, pois ele seria removido em outro local? Pois bem, quando a linha 233 for executada, durante a criação da sombra. Aquele botão irá ser removido do gráfico.

Agora preste atenção. Quando temos uma linha de stop loss, ou take profit. O valor informado na linha 246 será o preço onde o stop loss ou take profit se encontra. Porém, quando um destes ou ambos valores não existirem. Permitindo assim que a posição exista, até que ela seja fechada manualmente. Ou fechada pelo servidor, se for Day Trade. O valor em m_Info.price será o preço de abertura. Porém negativo. Como precisamos saber onde o objeto de movimento deverá ser plotado. O valor negativo não servirá para nós. Precisamos assim converter ele usando a função MathAbs. Isto no momento em que estamos criando as sombras. Lá na linha 190.

Agora, vamos entender quando m_Info.szMsgGhost recebe um valor. Observe na linha 280. É ali que dizemos que a m_Info.szMsgGhost qual será o seu conteúdo. Mas espere um pouco: Esta função ViewValue é chamada em mais de um momento. Isto não irá disparar a linha 94? Não meu caro leitor. E o motivo é que estamos fazendo um duplo teste. Isto a fim de evitar que a linha 280, coloque um valor em m_Info.szMsgGhost, quando não for apropriado. Desta forma, a cópia do valor, que existe, sendo mostrada ao usuário, será mostrada no momento em que a sombra for criada. Mas existe um problema neste mecanismo, que será resolvido depois. Por hora não se preocupe com isto ainda. Mas para que tudo funcione de fato, ainda precisamos ver o segundo fragmento, do qual mencionei anteriormente. Este pode ser observado logo abaixo.

305. //+------------------------------------------------------------------+
306.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
307.         {
308.             string sz0;
309.             long _lparam = lparam;
310.             double _dparam = dparam;
311.             
312.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
313.             switch (id)
314.             {
315.                 case (CHARTEVENT_KEYDOWN):
316.                     if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break;
317.                     _lparam = (long) m_Info.ticket;
318.                     _dparam = 0;
319.                     EventChartCustom(0, evUpdate_Position, _lparam, 0, "");
320.                 case CHARTEVENT_CUSTOM + evMsgSetFocus:
321.                     if ((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev))
322.                     {
323.                         CreateGhost();
324.                         UpdatePrice(m_Info.open, GetPositionsMouse().Position.Price);
325.                     }else
326.                     {
327.                         ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
328.                         ObjectsDeleteAll(0, macro_GhostName(""));
329.                         ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
330.                     }
331.                     macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev));
332.                     EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, "");
333.                     m_Info.bClick = false;
334.                 case CHARTEVENT_CHART_CHANGE:
335.                     ChartChange();
336.                     break;

Fragmento de C_ElementsTrade

Aqui temos apenas a parte, realmente necessária a ser mostrada. Todo o restante do código permaneceu intacto. Porém esta parte é onde, de fato, tudo começa a acontecer. Observe o que está acontecendo na linha 321, onde efetuamos o teste, cuja finalidade é verificar se estamos lidando com o segmento correto. Antes apenas enviaremos um pedido para UpdatePrice. Porém agora, estamos fazendo as coisas um pouco diferente. Já que é aqui o local onde a sombra é de fato implementada. Então se este teste na linha 321, for positivo. Primeiro chamamos o procedimento que efetuará a modificação dos objetos atuais em objetos sombra. Logo depois, enviamos um pedido a UpdatePrice.

Desta forma, teremos a sombra em segundo plano, e os novos objetos que serão manipulados, em primeiro plano. Criando assim a ilusão de que estamos sombreando o antigo ponto onde o preço se encontrava. Porém você, ao usar o sistema, notará que as informações antigas serão mantidas. E este é o propósito do sombreamento da antiga posição.

Agora, caso o teste seja falso iremos simplesmente remover os objetos sombra, que estejam presentes no gráfico. Retornando o preço para a sua antiga posição, onde estava a sombra. A remoção é feita na linha 328. Já a recolocação é feita por conta do próprio sistema que já existia antes. Assim com poucas modificações no código, conseguimos o resultado que é visto na animação logo abaixo.

Você muito provavelmente, deve estar bastante curioso, com relação ao que de fato esta sombra representa. Mas é bem simples. Ela simplesmente mostra onde o take profit ou stop loss se encontra no servidor. Este tipo de situação, que pode ser vista claramente no gráfico, e na animação acima, nos permite evitar uma falsa ideia de que ao tentarmos mover a linha de preço, estamos movendo-a no servidor também. O que para usuários menos experientes ou que estejam vendo este sistema pela primeira vez. Venham a ter tal convicção. Porém vendo as coisas desta maneira, fica bem mais claro e simples de entender o que está acontecendo.

Porém como eu informei, este sistema contém uma pequena falha a ser resolvida. A falha é o fato de que quando você estiver com um segmento, seja o take profit, seja o stop loss. Selecionados para troca de posição do preço. Ao clicar no objeto OBJ_EDIT a fim de trocar a forma de visualizar os dados. Eles serão atualizados em todos objetos. Porém a sombra não recebe tal atualização. Tornando assim a interpretação dos dados um tanto quanto diferente. Alguns operadores ou usuários, podem vir a não gostar muito de utilizar o sistema devido a este pequeno inconveniente. Porém a solução, apesar de ser relativamente simples. Exige algumas mudanças na forma como o código se encontra. E como também quero remover as partes duplicadas. Isto não será visto neste artigo.


Considerações finais

Neste artigo, apresentei a você, meu caro leitor, algo que muitos imaginariam ser bastante trabalhoso de ser feito e conseguido. Criar uma sombra, a fim de mostrar ao operador ou usuário, onde o antigo preço se encontra. Apesar de o fato de dizer antigo, não é o correto, já que aquele ponto ainda estará presente na posição aberta. Sendo passivo de ser disparado, caso venha a acontecer algum negócio naquele ponto específico. Apesar do sistema, ainda precisar de alguns ajustes como mencionado durante o artigo. O mesmo se encontra funcional, a ponto de poder ser utilizado.

Porém como o objetivo destes artigos, sobre o replay/simulador, visam simplesmente serem didáticos. Você deve tomar os devidos cuidados caso venha a desejar usar qualquer uma destas aplicações em conta real. Isto por que elas não estão de fato, sendo otimizadas para tal coisa. Mas se você realmente deseja entender como tudo funciona. Sinta-se à vontade em usar e abusar no uso deste sistema em contas de demonstração.

Para aqueles que desejam experimentar o sistema, mas não sabem como construir os executáveis. No anexo estou deixando as aplicações já compiladas. É só colocá-las em meios as que você já usa, ou substituir as versões anteriores, e partir para a conta demonstração a fim de ver tudo acontecendo. Assim como foi mostrada na animação, vista no tópico anterior. Apesar de tudo, no próximo artigo, iremos corrigir a falha que existe na sombra. Onde ela não segue a troca no padrão de visualização, efetuada no indicador. E também sanar o fato de termos muito código sendo duplicado na classe C_ElementsTrade. Será algo interessante, além de que estamos a apenas um único passo de começar a usar estas quatro aplicações no nosso replay/simulador. Tudo que precisamos é corrigir alguns pouco detalhes e já poderemos voltar a trabalhar no serviço de replay/simulador. Então não perca o próximo artigo desta série sobre o replay/simulador.

Arquivo Descrição
Experts\Expert Advisor.mq5
Demonstra a interação entre o Chart Trade e o Expert Advisor (É necessário o Mouse Study para interação)
Indicators\Chart Trade.mq5 Cria a janela para configuração da ordem a ser enviada (É necessário o Mouse Study para interação)
Indicators\Market Replay.mq5 Cria os controles para interação com o serviço de replay/simulador (É necessário o Mouse Study para interação)
Indicators\Mouse Study.mq5 Permite interação entre os controles gráficos e o usuário (Necessário tanto para operar o replay simulador, quanto no mercado real)
Indicators\Order Indicator.mq5 Responsável pela indicação de ordens de mercado, permitindo interação e controle das mesmas
Indicators\Position View.mq5 Responsável pela indicação de posições de mercado, permitindo interação e controle das mesmas
Services\Market Replay.mq5 Cria e mantém o serviço de replay e simulação de mercado (Arquivo principal de todo o sistema)
Arquivos anexados |
Anexo.zip (779.24 KB)
Construindo um Modelo de Restrição de Tendência com Candlestick (Parte 10): Golden Cross e Death Cross Estratégicos (EA) Construindo um Modelo de Restrição de Tendência com Candlestick (Parte 10): Golden Cross e Death Cross Estratégicos (EA)
Você sabia que as estratégias Golden Cross e Death Cross, baseadas no cruzamento de médias móveis, são alguns dos indicadores mais confiáveis para identificar tendências de mercado de longo prazo? Um Golden Cross sinaliza uma tendência de alta quando uma média móvel mais curta cruza acima de uma média mais longa, enquanto o Death Cross indica uma tendência de baixa quando a média mais curta cruza abaixo. Apesar de sua simplicidade e eficácia, aplicar essas estratégias manualmente frequentemente leva a oportunidades perdidas ou negociações atrasadas.
Do básico ao intermediário: Sobrecarga de operadores (I) Do básico ao intermediário: Sobrecarga de operadores (I)
Neste artigo começaremos a ver como seria a implementação da chamada sobrecarga de operadores. Iremos começar vendo a motivação por detrás de tal implementação. Assim como também veremos que nem sempre as coisas são tão complicadas como parecem.
MQL5 Trading Toolkit (Parte 5): Expandindo a Biblioteca EX5 de Gerenciamento de Histórico com Funções de Posição MQL5 Trading Toolkit (Parte 5): Expandindo a Biblioteca EX5 de Gerenciamento de Histórico com Funções de Posição
Descubra como criar funções exportáveis em EX5 para consultar e salvar de forma eficiente dados históricos de posições. Neste guia passo a passo, ampliaremos a biblioteca EX5 de gerenciamento de histórico desenvolvendo módulos que recuperam propriedades-chave da posição fechada mais recentemente. Isso inclui lucro líquido, duração da negociação, stop loss em pips, take profit, valores de lucro e vários outros detalhes importantes.
Classes de tabela e cabeçalho baseadas no modelo de tabela em MQL5: Aplicação do conceito MVC Classes de tabela e cabeçalho baseadas no modelo de tabela em MQL5: Aplicação do conceito MVC
Esta é a segunda parte do artigo dedicada à implementação de um modelo de tabela em MQL5, utilizando o paradigma arquitetural MVC (Model-View-Controller). O artigo aborda o desenvolvimento das classes da tabela e de seu cabeçalho, com base no modelo de tabela criado anteriormente. As classes desenvolvidas servirão como base para a futura implementação dos componentes de visualização (View) e controle (Controller), que serão abordados nos próximos artigos.