preview
Desenvolvendo um sistema de Replay (Parte 61): Dando play no serviço (II)

Desenvolvendo um sistema de Replay (Parte 61): Dando play no serviço (II)

MetaTrader 5Exemplos | 20 agosto 2024, 11:55
16 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior Desenvolvendo um sistema de Replay (Parte 60): Dando play no serviço (I), fizemos algumas mudanças a fim de que o serviço de replay/simulador, pudesse começar lançar novos dados no gráfico. Apesar de temos feito, o mínimo possível, para que o sistema conseguisse de fato, fazer o lançamento dos dados, ficou claro e evidente de que algo estranho aconteceu. Aparentemente o sistema sofreu, mesmo sem ter sofrido grandes mudanças, um grande baque. Tal coisa, dá a impressão, de que o sistema se tornou inviável, já que de uma hora para outra, ele ficou muito lento. Mas será mesmo? E se sim, como podemos resolver esta questão? Lembrando que desejamos manter as coisas, sempre seguindo as premissas da programação orientada em objetos.

Apesar de que, aconteceu de fato uma queda na performance. Podemos resolver grande parte deste detalhe, apenas compreendendo e ajustando adequadamente alguns pontos do código. Talvez neste artigo, eu comece a mostrar, para quem não sabe, como fazer uso de algumas ferramentas presentes no Meta Editor. Estas facilitam muito, quando o assunto é ajustar e melhorar um determinado código, que estamos trabalhando ou desenvolvendo. De certa forma, eu já deveria ter mostrado isto, a alguns artigos atrás. Mas não via assim tanta necessidade como agora, onde precisamos mesmo compreender como o código está funcionando, e por que ele sofreu uma queda tão acentuada em sua performance. Isto sem que fosse feita, uma mudança significativa, na forma como ele deveria trabalhar.


Executando as melhorias, mais evidentes e diretas

Muitas vezes, o mal entendimento, ou falta de alguma explicação mais profunda e avançada sobre como o MetaTrader 5, assim como o MQL5 funcionam, dificultam bastante alguns tipos de implementação. Felizmente, aqui na comunidade, podemos concentrar as coisas, de forma que algum conhecimento seja de fato repassado. Mesmo que ele não venha a servir, para algo assim tão imediato, no que estamos querendo implementar. De qualquer forma, conhecimento adequado e de qualidade é sempre muito bem-vindo.

Bem, uma destas coisas é justamente o que tentarei explicar. Já que muito do que tento expor, é mais facilmente compreendido, quando você de fato faz uso do MQL5, a fim de conseguir fazer, bem mais no MetaTrader 5, do que muitos normalmente fazem, ou tentam fazer.

Talvez uma das coisas mais mal compreendida, por parte de um grande número de pessoas que programam no MQL5, são os objetos gráficos. Muita gente acredita que tais objetos, somente podem ser acessados, manipulados e ajustados, por algo que esteja de fato presente no gráfico. Seja um indicador, script ou mesmo um Expert Advisor. O que não é nem de longe, um fato verdadeiro.

Até o presente momento, temos trabalhado de maneira, a não criar algum tipo de dependência, entre o que está na janela do gráfico do ativo customizado, e o que está sendo executado no MetaTrader 5. No entanto, além das maneiras que estamos usando para transferir informações, entre as aplicações que estão sendo executadas no MetaTrader 5, temos a possibilidade de fazer algo um pouco mais elaborado, porém arriscado. Não me entendam mal, mas quando fazemos as coisas, de forma a gerar alguma dependência, entre o que está sendo executado, e o que esperávamos, estar sendo executado, coisas estranhas podem começar a acontecer.

Apesar de muitas das vezes funcionar. Podemos estar entrando em um caminho tortuoso, onde poderemos acabar, dando de cara em uma parede, e assim perdendo um grande tempo que poderia ser melhor utilizado para outras coisas. E o motivo, é que muitas das vezes, tais mudanças inviabilizarão qualquer nova decisão, a fim de melhorar ou implementar algo. Para entender, o que estou querendo propor, é preciso que você tenha um conhecimento adequado, a fim de entender como a coisa em si funcionará.

O primeiro ponto importante, é que, o módulo do indicador de controle, somente estará no gráfico, se e somente se, o serviço de replay/simulação estiver em execução. Você não deve tentar colocar o módulo de controle no gráfico, de forma manual, pois isto quebrará tudo que faremos de agora em diante.

Segundo ponto: Os objetos gráficos criados pelo módulo de controle, deverão sempre seguir uma nomenclatura fixa e bastante rígida. Caso contrário, teremos sérios problemas futuros.

Além destes dois pontos. Iremos também implementar algo, que facilitará muito a legibilidade do código. Já que não faz sentido, usarmos símbolos ou grifos, que não fazem nenhum significado para nós. Mas estas mudanças na legibilidade, servirão mais para facilitar o entendimento de alguns ajustes, do que necessariamente tornar o código mais rápido. Mas isto será melhor entendido quando os códigos fontes forem expostos.

As primeiras mudanças que faremos, será justamente no código do arquivo de cabeçalho C_Controls.mqh. Mas antes de tentar entender, o por que precisaremos fazer, algumas mudanças neste arquivo. Vamos ver como o mesmo foi modificado. O novo código pode ser visto logo abaixo.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Auxiliar\C_DrawImage.mqh"
005. #include "..\Defines.mqh"
006. //+------------------------------------------------------------------+
007. #define def_PathBMP           "Images\\Market Replay\\Control\\"
008. #define def_ButtonPlay        def_PathBMP + "Play.bmp"
009. #define def_ButtonPause       def_PathBMP + "Pause.bmp"
010. #define def_ButtonLeft        def_PathBMP + "Left.bmp"
011. #define def_ButtonLeftBlock   def_PathBMP + "Left_Block.bmp"
012. #define def_ButtonRight       def_PathBMP + "Right.bmp"
013. #define def_ButtonRightBlock  def_PathBMP + "Right_Block.bmp"
014. #define def_ButtonPin         def_PathBMP + "Pin.bmp"
015. #resource "\\" + def_ButtonPlay
016. #resource "\\" + def_ButtonPause
017. #resource "\\" + def_ButtonLeft
018. #resource "\\" + def_ButtonLeftBlock
019. #resource "\\" + def_ButtonRight
020. #resource "\\" + def_ButtonRightBlock
021. #resource "\\" + def_ButtonPin
022. //+------------------------------------------------------------------+
023. #define def_ObjectCtrlName(A)   "MarketReplayCTRL_" + (typename(A) == "enum eObjectControl" ? EnumToString((C_Controls::eObjectControl)(A)) : (string)(A))
024. #define def_PosXObjects         120
025. //+------------------------------------------------------------------+
026. #define def_SizeButtons         32
027. #define def_ColorFilter         0xFF00FF
028. //+------------------------------------------------------------------+
029. #include "..\Auxiliar\C_Terminal.mqh"
030. #include "..\Auxiliar\C_Mouse.mqh"
031. //+------------------------------------------------------------------+
032. class C_Controls : private C_Terminal
033. {
034.    protected:
035.    private   :
036. //+------------------------------------------------------------------+
037.       enum eMatrixControl {eCtrlPosition, eCtrlStatus};
038.       enum eObjectControl {ePause, ePlay, eLeft, eRight, ePin, eNull, eTriState = (def_MaxPosSlider + 1)};
039. //+------------------------------------------------------------------+
040.       struct st_00
041.       {
042.          string  szBarSlider,
043.                  szBarSliderBlock;
044.          ushort  Minimal;
045.       }m_Slider;
046.       struct st_01
047.       {
048.          C_DrawImage *Btn;
049.          bool        state;
050.          short       x, y, w, h;
051.       }m_Section[eObjectControl::eNull];
052.       C_Mouse   *m_MousePtr;
053. //+------------------------------------------------------------------+
054. inline void CreteBarSlider(short x, short size)
055.          {
056.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_ObjectCtrlName("B1"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
057.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x);
058.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11);
059.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size);
060.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);
061.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);
062.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);
063.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);
064.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);
065.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_ObjectCtrlName("B2"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
066.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x);
067.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6);
068.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19);
069.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown);
070.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED);
071.          }
072. //+------------------------------------------------------------------+
073.       void SetPlay(bool state)
074.          {
075.             if (m_Section[ePlay].Btn == NULL)
076.                m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(ePlay), def_ColorFilter, "::" + def_ButtonPause, "::" + def_ButtonPlay);
077.             m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, ((m_Section[ePlay].state = state) ? 1 : 0));
078.             if (!state) CreateCtrlSlider();
079.          }
080. //+------------------------------------------------------------------+
081.       void CreateCtrlSlider(void)
082.          {
083.             CreteBarSlider(77, 436);
084.             m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock);
085.             m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock);
086.             m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(ePin), def_ColorFilter, "::" + def_ButtonPin);
087.             PositionPinSlider(m_Slider.Minimal);
088.          }
089. //+------------------------------------------------------------------+
090. inline void RemoveCtrlSlider(void)
091.          {         
092.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
093.             for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
094.             {
095.                delete m_Section[c0].Btn;
096.                m_Section[c0].Btn = NULL;
097.             }
098.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("B"));
099.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
100.          }
101. //+------------------------------------------------------------------+
102. inline void PositionPinSlider(ushort p)
103.          {
104.             int iL, iR;
105.             
106.             m_Section[ePin].x = (short)(p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
107.             iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1);
108.             iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1);
109.             m_Section[ePin].x += def_PosXObjects;
110.             m_Section[ePin].x += 95 - (def_SizeButtons / 2);
111.             for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
112.                m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0)));
113.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2);
114.          }
115. //+------------------------------------------------------------------+
116. inline eObjectControl CheckPositionMouseClick(short &x, short &y)
117.          {
118.             C_Mouse::st_Mouse InfoMouse;
119.             
120.             InfoMouse = (*m_MousePtr).GetInfoMouse();
121.             x = (short) InfoMouse.Position.X_Graphics;
122.             y = (short) InfoMouse.Position.Y_Graphics;
123.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
124.             {   
125.                if ((m_Section[c0].Btn != NULL) && (m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y))
126.                   return c0;
127.             }
128.             
129.             return eNull;
130.          }
131. //+------------------------------------------------------------------+
132.    public   :
133. //+------------------------------------------------------------------+
134.       C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr)
135.          :C_Terminal(Arg0),
136.           m_MousePtr(MousePtr)
137.          {
138.             if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown);
139.             if (_LastError != ERR_SUCCESS) return;
140.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
141.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
142.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
143.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
144.             {
145.                m_Section[c0].h = m_Section[c0].w = def_SizeButtons;
146.                m_Section[c0].y = 25;
147.                m_Section[c0].Btn = NULL;
148.             }
149.             m_Section[ePlay].x = def_PosXObjects;
150.             m_Section[eLeft].x = m_Section[ePlay].x + 47;
151.             m_Section[eRight].x = m_Section[ePlay].x + 511;
152.             m_Slider.Minimal = eTriState;
153.          }
154. //+------------------------------------------------------------------+
155.       ~C_Controls()
156.          {
157.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn;
158.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
159.             delete m_MousePtr;
160.          }
161. //+------------------------------------------------------------------+
162.       void SetBuffer(const int rates_total, double &Buff[])
163.          {
164.             uCast_Double info;
165.             
166.             info._16b[eCtrlPosition] = m_Slider.Minimal;
167.             info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause));//SHORT_MAX : SHORT_MIN);
168.             if (rates_total > 0)
169.                Buff[rates_total - 1] = info.dValue;
170.          }
171. //+------------------------------------------------------------------+
172.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
173.          {
174.             short x, y;
175.             static ushort iPinPosX = 0;
176.             static short six = -1, sps;
177.             uCast_Double info;
178.             
179.             switch (id)
180.             {
181.                case (CHARTEVENT_CUSTOM + evCtrlReplayInit):
182.                   info.dValue = dparam;
183.                   if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break;
184.                   x = (short) info._16b[eCtrlPosition];
185.                   iPinPosX = m_Slider.Minimal = (info._16b[eCtrlPosition] > def_MaxPosSlider ? def_MaxPosSlider : (info._16b[eCtrlPosition] < iPinPosX ? iPinPosX : info._16b[eCtrlPosition]));
186.                   SetPlay((eObjectControl)(info._16b[eCtrlStatus]) == ePlay);
187.                   break;
188.                case CHARTEVENT_OBJECT_DELETE:
189.                   if (StringSubstr(sparam, 0, StringLen(def_ObjectCtrlName(""))) == def_ObjectCtrlName(""))
190.                   {
191.                      if (sparam == def_ObjectCtrlName(ePlay))
192.                      {
193.                         delete m_Section[ePlay].Btn;
194.                         m_Section[ePlay].Btn = NULL;
195.                         SetPlay(m_Section[ePlay].state);
196.                      }else
197.                      {
198.                         RemoveCtrlSlider();
199.                         CreateCtrlSlider();
200.                      }
201.                   }
202.                   break;
203.                case CHARTEVENT_MOUSE_MOVE:
204.                   if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft))   switch (CheckPositionMouseClick(x, y))
205.                   {
206.                      case ePlay:
207.                         SetPlay(!m_Section[ePlay].state);
208.                         if (m_Section[ePlay].state)
209.                         {
210.                            RemoveCtrlSlider();
211.                            m_Slider.Minimal = iPinPosX;
212.                         }else CreateCtrlSlider();
213.                         break;
214.                      case eLeft:
215.                         PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal));
216.                         break;
217.                      case eRight:
218.                         PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider));
219.                         break;
220.                      case ePin:
221.                         if (six == -1)
222.                         {
223.                            six = x;
224.                            sps = (short)iPinPosX;
225.                            ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
226.                         }
227.                         iPinPosX = sps + x - six;
228.                         PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX)));
229.                         break;
230.                   }else if (six > 0)
231.                   {
232.                      six = -1;
233.                      ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);                     
234.                   }
235.                   break;
236.             }
237.             ChartRedraw(GetInfoTerminal().ID);
238.          }
239. //+------------------------------------------------------------------+
240. };
241. //+------------------------------------------------------------------+
242. #undef def_PosXObjects
243. #undef def_ButtonPlay
244. #undef def_ButtonPause
245. #undef def_ButtonLeft
246. #undef def_ButtonRight
247. #undef def_ButtonPin
248. #undef def_PathBMP
249. //+------------------------------------------------------------------+

Código fonte do arquivo C_Controls.mqh

A forma de fazer os objetos de controle, seguirem um formato bastante rígido, e ao mesmo tempo ser fácil de fazer a declaração, será usando a definição que pode ser vista na linha 23. Esta linha parece ser extremamente complicada, mas não se deixe levar, pela aparência extravagante que esta definição tem. Na dúvida, teste ela isoladamente para entender por que de ela funcionar.

Agora preste atenção a uma coisa, e que é importante. Veja que na linha 37 e 38 temos duas enumerações. A que se encontra na linha 37 não existia, mas foi criada no intuito de facilitar o acesso aos dados no buffer. Veja como isto é feito, observando o procedimento SetBuffer, que se encontra presente na linha 162. O mesmo tipo de coisa, foi feito no procedimento de tratamento de mensagens, mas neste caso a coisa é ligeiramente diferente. Veja isto entre as linhas 182 e 186. Mas atenção a linha 184, ela foi removida do código original.

Mas voltando a questão das enumerações, repare que a enumeração presente na linha 38, foi modificada perto do que era antes. O motivo disto é promover algumas mudanças a fim de tornar o código mais legível. Tanto que a variável presente na linha 44, que antes era uma do tipo sinalizada, agora é do tipo sem sinal. Este tipo de coisa, permitirá que possamos fazer algumas pequenas mudanças como a vista na linha 152, ou mesmo algo parecido com o que pode ser observado na linha 186.

Tudo isto, tende a deixar o código um pouco mais legível, já que a ideia aqui será fazer algo um pouco diferente do que existia antes.

Muito bem, então vamos ver o que de fato será feito. Isto de certa forma poderá economizar alguns ciclos de máquina depois. Mas primeiro vamos entender um detalhe aqui. Na linha 77, pedimos para trocar a imagem que é representada no objeto gráfico. Tal objeto é um botão, que nos diz se estamos em modo play ou modo pause. No entanto, o serviço fica sempre observando o buffer do indicador de controle, mas podemos fazer algo um pouco diferente, pelo menos no que diz respeito, a saber se estamos em modo play ou pause. E isto envolve justamente o objeto que é manipulado pela linha 77.


Acessando rapidamente o status do botão

Como foi dito no tópico anterior, aquelas simples mudanças não fazem o serviço se beneficiar, a ponto de que realmente venhamos a ter uma melhora na performance do mesmo. Porém, toda via e, entretanto, quando olhamos, onde o serviço mais precisa de ter algum tipo de ganho em termos de performance, iremos ver que a coisa muda de figura.

No artigo anterior, mostrei onde isto de fato é necessário. Para refrescar sua memória, o ponto é justamente no LoopEventOnTime. Esta função de tempos em tempos irá chamar uma outra, a fim de verificar o status do botão do indicador de controle, a fim de saber se estamos em modo pause, ou em modo play.

Tal verificação, a princípio se dá ao observar o que está presente no buffer do indicador de controle. Porém existe uma maneira um pouco mais elegante, se bem que nos gera alguns outros problemas, que é justamente olhar o objeto de controle. Lembre-se que o objeto de controle é um OBJ_BITMAP_LABEL, e tal tipo de objeto, tem dois estados, que podemos verificar olhando uma variável presente no objeto.

Ao fazer isto, olhar o conteúdo de uma dada variável, presente no objeto OBJ_BITMAP_LABEL, que está no gráfico. Podemos fazer com que o serviço consiga de fato ignorar a leitura do buffer, a fim de saber se devemos dar play ou pausar o lançamento de dados para o gráfico.

Porém, se você olhar o código do arquivo de cabeçalho C_DrawImage.mqh, não encontrará nenhum tipo de modificação da variável que precisamos no objeto OBJ_BITMAP_LABEL. Isto antes mesmo de tentar fazer algo no serviço. Porém a observar o código do arquivo C_Controls.mqh, você nota que na linha 77, fazemos um pedido para que o objeto seja atualizado. Então podemos partir deste ponto, e fazer as devidas mudanças a fim que o serviço possa se beneficiar com elas. Isto em tese, já que a economia será de alguns ciclos de máquina a cada chamada.

Já que as mudanças não serão muitas, não recolocarei todo o código do arquivo de cabeçalho aqui. Então abra o arquivo de cabeçalho C_DrawImage.mqh, e o modifique conforme mostrado no fragmento abaixo.

174. //+------------------------------------------------------------------+
175.       void Paint(const int x, const int y, const int w, const int h, const uchar cView, const int what)
176.          {
177.             
178.             if ((m_szRecName == NULL) || (what < 0) || (what >= def_MaxImages)) return;
179.             ReSizeImage(w, h, cView, what);
180.             ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_XDISTANCE, x);
181.             ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_YDISTANCE, y);
182.             if (ResourceCreate(m_szRecName, m_Pixels, w, h, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE))
183.             {
184.                ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, m_szRecName);
185.                ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, what, m_szRecName);
186.                ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_STATE, what == 1);
187.                ChartRedraw(GetInfoTerminal().ID);
188.             }
189.          }
190. //+------------------------------------------------------------------+

Fragmento do código fonte C_DrawImage.mqh

Observem uma coisa, a linha 184, foi substituída pela linha 185. A diferença entre ambas linhas é a existência do parâmetro que informa o índex da imagem. Mas o que de fato nos interessa é a linha 186. Esta linha irá indicar e mudar o estado da variável do objeto OBJ_BITMAP_LABEL. Então agora a variável OBJPROP_STATE, do objeto, já irá refletir qual o status do objeto. Lembrando novamente, temos apenas a possibilidade de dois estados. Play ou Pause.

Com isto podemos voltar a nossa atenção ao código do arquivo de cabeçalho C_Replay.mqh, a fim de permitir que o serviço acesse diretamente o objeto, para saber se estamos no modo play, ou no modo pause.

Para que de fato, consigamos fazer com que o serviço, saiba o que está acontecendo, será preciso primeiro adicionarmos algo novo. Então a primeira mudança é vista logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "C_ConfigService.mqh"
05. #include "C_Controls.mqh"
06. //+------------------------------------------------------------------+
07. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
08. #resource "\\" + def_IndicatorControl
09. //+------------------------------------------------------------------+
10. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_Infos.IdReplay) != ""))
11. //+------------------------------------------------------------------+
12. #define def_ShortNameIndControl "Market Replay Control"
13. //+------------------------------------------------------------------+
14. class C_Replay : public C_ConfigService
15. {
16.    private   :
17.       struct st00
18.       {
19.          C_Controls::eObjectControl Mode;
20.          uCast_Double               Memory;
21.          ushort                     Position;
22.          int                        Handle;
23.       }m_IndControl;

Fragmento do código fonte C_Replay.mqh

Observe que aqui adicionamos, primeiramente na linha cinco, uma referência ao arquivo de cabeçalho do indicador de controle. Não implementaremos nada que faça de fato uso da classe de controle, mas precisamos ter acesso as definições que foram colocadas lá. E a principal é a definição que nos permite saber o nome dos objetos criadas pela classe. Mas não se preocupe, chegaremos lá.

Ocorreram outras mudanças, neste mesmo fragmento. Você pode notar que na linha 19, agora a variável já é de outro tipo, isto facilitará a nossa vida na questão da legibilidade. Mas também foi adicionada, na linha 20, uma nova variável. Esta será usada como memória a fim de armazenar algumas coisas de dentro o buffer do indicador de controle. No entanto, isto não será usado da mesma forma como você possa estar imaginado. Em breve você entenderá isto. Assim que isto tenha sido feito, precisamos imediatamente fazer uma mudança no constructor da classe C_Replay. Então a mudança pode ser vista a seguir.

131. //+------------------------------------------------------------------+
132.       C_Replay()
133.          :C_ConfigService()
134.          {
135.             Print("************** Market Replay Service **************");
136.             srand(GetTickCount());
137.             SymbolSelect(def_SymbolReplay, false);
138.             CustomSymbolDelete(def_SymbolReplay);
139.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
140.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
141.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
142.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
143.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
144.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
145.             SymbolSelect(def_SymbolReplay, true);
146.             m_Infos.CountReplay = 0;
147.             m_IndControl.Handle = INVALID_HANDLE;
148.             m_IndControl.Mode = C_Controls::ePause;
149.             m_IndControl.Position = 0;
150.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState;
151.          }
152. //+------------------------------------------------------------------+

Fragmento do código fonte C_Replay.mqh

Observe que como estamos inicializando os valores da estrutura m_IndControl. É muito importante que você entenda como isto está sendo feito. E principalmente, entenda exatamente, o fato de estar sendo usado estes valores e não outros. Mas o motivo disto, não deve estar tão claro aqui. Mas logo se tornará. Pois a ideia aqui será justamente acessar o objeto que se encontra no gráfico, e que foi criado, e está sendo mantido pelo modulo do indicador de controle.

Agora para de que fato, possamos tirar algum proveito, e poder acessar o objeto OBJ_BITMAP_LABEL, diretamente no gráfico, e isto via serviço. Precisaremos modificar levemente o código UpdateIndicatorControl, que já existe no código da classe C_Replay. Tal modificação, a ser de fato feita pode ser vista no fragmento logo abaixo.

34. //+------------------------------------------------------------------+
35. inline void UpdateIndicatorControl(void)
36.          {
37.             static bool bTest = false;
38.             double Buff[];
39.                                  
40.             if (m_IndControl.Handle == INVALID_HANDLE) return;
41.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
42.             {
43.                if (bTest)
44.                   m_IndControl.Mode = (ObjectGetInteger(m_Infos.IdReplay, def_ObjectCtrlName((C_Controls::eObjectControl)C_Controls::ePlay), OBJPROP_STATE) == 1  ? C_Controls::ePause : C_Controls::ePlay);
45.                else
46.                {
47.                   if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
48.                      m_IndControl.Memory.dValue = Buff[0];
49.                   if ((C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus] != C_Controls::eTriState)
50.                      if (bTest = ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay))
51.                         m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
52.                }
53.             }else
54.             {
55.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
56.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
57.                m_IndControl.Memory._8b[7] = 'D';
58.                m_IndControl.Memory._8b[6] = 'M';
59.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
60.                bTest = false;
61.             }
62.          }
63. //+------------------------------------------------------------------+

Fragmento do código fonte C_Replay.mqh

Você deve estar notando, que este código, está muito diferente do que foi visto anteriormente. E o principal motivo, é promover meios que sejam o mais seguro, quando for possível oferecer, para que possamos acessar qualquer coisa relacionada ao módulo do indicador de controle.

Para que de fato você, consiga compreender o funcionamento deste fragmento. Primeiro é preciso que você se lembre de alguns fatos.

O primeiro fato é que: Os valores são iniciados no constructor. Segundo fato: Quem é o primeiro a chamar esta rotina é a função que inicializa o módulo do indicador de controle. Terceiro fato: De tempos em tempos, o procedimento de loop verificará o estado do botão, que se encontra no indicador de controle.

Estes três fatos funcionam nesta sequência. Mas entre eles, o terceiro, é o mais problemático. Já que ele é o que de fato lança novos ticks no gráfico, e fica o tempo todo observando o indicador de controle. Mas, e talvez, o fato de podermos ler diretamente no objeto presente no gráfico, o que está acontecendo, e apenas acessar o buffer, assim como enviar eventos para o indicador de controle, somente em casos especiais. Talvez faça com que este procedimento UpdateIndicatorControl, não faça o serviço de replay / simulação ter uma perda de performance durante a sua fase crítica.

Mas vamos entender, como este fragmento de fato funciona. Primeiramente na linha 40 verificamos se temos um manipulador válido. Se isto estiver correto continuaremos. A próxima coisa é verificar se o valor da memória é igual ao da posição, isto é feito na linha 41. Se estiver ok, testaremos na linha 43 se a variável estática é verdadeira. Se este for o caso, usaremos a função de acesso aos objetos, a fim de saber qual o valor que se encontra no OBJ_BITMAP_LABEL. Preste muita atenção ao como isto está sendo feito, pois pode ser que isto lhe pareça muito estranho, já que estamos nos referindo a algo que se encontra no arquivo de cabeçalho C_Controls.mqh. Mas de qualquer forma, o fato é que o acesso está de fato sendo feito.

Agora, se a variável estática for falsa, isto indica que não existe problema em fazermos uma leitura, digamos assim, um pouco mais lenta dos dados. Neste caso usaremos o buffer do indicador de controle. Atenção: Não estou de fato, dizendo que a leitura do buffer é mais lenta, mas compare a quantidade de coisas que estamos fazendo, frente ao de apenas ler algo que faz parte das propriedades do objeto, que se encontra presente no gráfico.

De qualquer maneira, uma vez que o buffer foi lido, iremos na linha 49 verificar se não estamos em TriState. Se isto for satisfeito, executaremos um conjunto de operações na linha 50, isto antes de verificarmos se estamos no modo play, o que tornará a variável estática verdadeira ou falsa. Este conjunto de operações, que na verdade são atribuições, estão todas aninhadas de maneira que o comando parece ser muito mais complicado, do que realmente é. Mas já que para o compilador isto não importa, e os valores estão sendo atribuídos da maneira como seria esperado, podemos fazer assim. Então se a linha 50 for verdadeira, armazenaremos o que estava no buffer, na variável de posição interna. Isto na linha 51.

Este tipo de coisa na verdade, irá de fato acontecer em apenas uma única situação: Quando o usuário interage com o controle deslizante, e muda a posição onde o replay/simulador deverá ser iniciado. Ou seja, quando estamos no modo play, este código não será de fato verifica, mas quando saímos do modo pause e vamos entrar no modo play, este código será executado. O que para nós será importante depois.

Mas, se o teste feito na linha 41 for falso, executaremos o que se encontra entre as linhas 55 e 60. Isto fará com que um evento customizado, venha a ser disparado, de forma a podemos atualizar o módulo do indicador de controle. E é assim que as coisas funcionarão de agora para frente.

Talvez isto, de ler o objeto presente no gráfico, não torne o serviço de fato mais performático. Mas já abre algumas possibilidades futuras, para quem deseja fazer alguns tipos de manipulação de objetos presentes no gráfico. Permitindo assim, construir coisas bem mais elaboradas e que não venham a sobrecarregar a plataforma MetaTrader 5, onde não precisaremos ter um gráfico cheio de indicadores e coisas do tipo, apenas para manipular objetos presentes no gráfico.


Ganhando performance de maneira real

Apesar de tudo que foi falado até aqui, não representar e não trazer um ganho real na performance do serviço de replay/simulador. Pelo menos não notei algo assim tão significativo. Tais mudanças nos ajuda e tornar algumas partes do código, mais eficientes. Mas o maior ganho, de fato, é na questão da legibilidade. Isto por conta das definições que foram feitas no arquivo C_Controls.mqh, e que foram utilizadas no arquivo de cabeçalho C_Replay.mqh. Mesmo que você não tenha de fato visto todo o código, ainda. Você muito provavelmente, já deve imaginar, onde modificar as coisas para melhorar a legibilidade da classe C_Replay.

Mas agora, vamos de fato ver algo, que faz com que o código ganhe performance. Isto para tentar fazer, com que ele volte a executar, e a criar uma barra de 1 minuto dentro deste tempo.

Pode parecer bizarro, e algo totalmente sem sentido, o que mostrarei. Mas acredite, e pode testar. Existe de fato um ganho substancial na performance, com uma simples mudança. Para ver isto, vamos observar todo o código, agora na íntegra do arquivo de cabeçalho C_Replay.mqh. Este pode ser visto logo a seguir. A numeração ficará um pouco diferente do que foi visto nos fragmentos acima. Mas não se apegue a isto. Vamos focar no código que está na íntegra.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_ConfigService.mqh"
005. #include "C_Controls.mqh"
006. //+------------------------------------------------------------------+
007. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
008. #resource "\\" + def_IndicatorControl
009. //+------------------------------------------------------------------+
010. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_Infos.IdReplay) != ""))
011. //+------------------------------------------------------------------+
012. #define def_ShortNameIndControl "Market Replay Control"
013. //+------------------------------------------------------------------+
014. class C_Replay : public C_ConfigService
015. {
016.    private   :
017.       struct st00
018.       {
019.          C_Controls::eObjectControl Mode;
020.            uCast_Double             Memory;
021.          ushort                     Position;
022.          int                        Handle;
023.       }m_IndControl;
024.       struct st01
025.       {
026.          long     IdReplay;
027.          int      CountReplay;
028.          double   PointsPerTick;
029.          MqlTick  tick[1];
030.          MqlRates Rate[1];
031.       }m_Infos;
032.       stInfoTicks m_MemoryData;
033. //+------------------------------------------------------------------+
034. inline bool MsgError(string sz0) { Print(sz0); return false; }
035. //+------------------------------------------------------------------+
036. inline void UpdateIndicatorControl(void)
037.          {
038.             static bool bTest = false;
039.             double Buff[];
040.                                  
041.             if (m_IndControl.Handle == INVALID_HANDLE) return;
042.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
043.             {
044.                if (bTest)
045.                   m_IndControl.Mode = (ObjectGetInteger(m_Infos.IdReplay, def_ObjectCtrlName((C_Controls::eObjectControl)C_Controls::ePlay), OBJPROP_STATE) == 1  ? C_Controls::ePause : C_Controls::ePlay);
046.                else
047.                {
048.                   if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
049.                      m_IndControl.Memory.dValue = Buff[0];
050.                   if ((C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus] != C_Controls::eTriState)
051.                      if (bTest = ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay))
052.                         m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
053.                }
054.             }else
055.             {
056.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
057.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
058.                m_IndControl.Memory._8b[7] = 'D';
059.                m_IndControl.Memory._8b[6] = 'M';
060.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
061.                bTest = false;
062.             }
063.          }
064. //+------------------------------------------------------------------+
065.       void SweepAndCloseChart(void)
066.          {
067.             long id;
068.             
069.             if ((id = ChartFirst()) > 0) do
070.             {
071.                if (ChartSymbol(id) == def_SymbolReplay)
072.                   ChartClose(id);
073.             }while ((id = ChartNext(id)) > 0);
074.          }
075. //+------------------------------------------------------------------+
076. inline void CreateBarInReplay(bool bViewTick)
077.          {
078.             bool    bNew;
079.             double dSpread;
080.             int    iRand = rand();
081. 
082.             if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew))
083.             {
084.                m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay];
085.                if (m_MemoryData.ModePlot == PRICE_EXCHANGE)
086.                {                  
087.                   dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
088.                   if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
089.                   {
090.                      m_Infos.tick[0].ask = m_Infos.tick[0].last;
091.                      m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
092.                   }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
093.                   {
094.                      m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
095.                      m_Infos.tick[0].bid = m_Infos.tick[0].last;
096.                   }
097.                }
098.                if (bViewTick) CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
099.                CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
100.             }
101.             m_Infos.CountReplay++;
102.          }
103. //+------------------------------------------------------------------+
104.       void AdjustViewDetails(void)
105.          {
106.             MqlRates rate[1];
107. 
108.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_ASK_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
109.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_BID_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
110.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_LAST_LINE, GetInfoTicks().ModePlot == PRICE_EXCHANGE);
111.             m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
112.             CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, rate);
113.             if ((m_Infos.CountReplay == 0) && (GetInfoTicks().ModePlot == PRICE_EXCHANGE))
114.                for (; GetInfoTicks().Info[m_Infos.CountReplay].volume_real == 0; m_Infos.CountReplay++);
115.             if (rate[0].close > 0)
116.             {
117.                if (GetInfoTicks().ModePlot == PRICE_EXCHANGE)
118.                   m_Infos.tick[0].last = rate[0].close;
119.                else
120.                {
121.                   m_Infos.tick[0].bid = rate[0].close;
122.                   m_Infos.tick[0].ask = rate[0].close + (rate[0].spread * m_Infos.PointsPerTick);
123.                }               
124.                m_Infos.tick[0].time = rate[0].time;
125.                m_Infos.tick[0].time_msc = rate[0].time * 1000;
126.             }else
127.                m_Infos.tick[0] = GetInfoTicks().Info[m_Infos.CountReplay];
128.             CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
129.          }
130. //+------------------------------------------------------------------+
131.    public   :
132. //+------------------------------------------------------------------+
133.       C_Replay()
134.          :C_ConfigService()
135.          {
136.             Print("************** Market Replay Service **************");
137.             srand(GetTickCount());
138.             SymbolSelect(def_SymbolReplay, false);
139.             CustomSymbolDelete(def_SymbolReplay);
140.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
141.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
142.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
143.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
144.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
145.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
146.             SymbolSelect(def_SymbolReplay, true);
147.             m_Infos.CountReplay = 0;
148.             m_IndControl.Handle = INVALID_HANDLE;
149.             m_IndControl.Mode = C_Controls::ePause;
150.             m_IndControl.Position = 0;
151.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState;
152.          }
153. //+------------------------------------------------------------------+
154.       ~C_Replay()
155.          {
156.             IndicatorRelease(m_IndControl.Handle);
157.             SweepAndCloseChart();
158.             SymbolSelect(def_SymbolReplay, false);
159.             CustomSymbolDelete(def_SymbolReplay);
160.             Print("Finished replay service...");
161.          }
162. //+------------------------------------------------------------------+
163.       bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate)
164.          {
165.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
166.                return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket.");
167.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
168.                return MsgError("Asset configuration is not complete, need to declare the ticket value.");
169.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
170.                return MsgError("Asset configuration not complete, need to declare the minimum volume.");
171.             SweepAndCloseChart();
172.             m_Infos.IdReplay = ChartOpen(def_SymbolReplay, arg1);
173.             if (!ChartApplyTemplate(m_Infos.IdReplay, szNameTemplate + ".tpl"))
174.                Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl");
175.             else
176.                Print("Apply template: ", szNameTemplate, ".tpl");
177. 
178.             return true;
179.          }
180. //+------------------------------------------------------------------+
181.       bool InitBaseControl(const ushort wait = 1000)
182.          {
183.             Print("Waiting for Mouse Indicator...");
184.             Sleep(wait);
185.             while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200);
186.             if (def_CheckLoopService)
187.             {
188.                AdjustViewDetails();
189.                Print("Waiting for Control Indicator...");
190.                if ((m_IndControl.Handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false;
191.                ChartIndicatorAdd(m_Infos.IdReplay, 0, m_IndControl.Handle);
192.                UpdateIndicatorControl();
193.             }
194.             
195.             return def_CheckLoopService;
196.          }
197. //+------------------------------------------------------------------+
198.       bool LoopEventOnTime(void)
199.          {         
200.             int iPos;
201. 
202.             while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay))
203.             {
204.                UpdateIndicatorControl();
205.                Sleep(200);
206.             }
207.             m_MemoryData = GetInfoTicks();
208.             iPos = 0;
209.             while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService))
210.             {
211.                if (m_IndControl.Mode == C_Controls::ePause) return true;
212.                iPos += (int)(m_Infos.CountReplay < (m_MemoryData.nTicks - 1) ? m_MemoryData.Info[m_Infos.CountReplay + 1].time_msc - m_MemoryData.Info[m_Infos.CountReplay].time_msc : 0);
213.                CreateBarInReplay(true);
214.                while ((iPos > 200) && (def_CheckLoopService))
215.                {
216.                   Sleep(195);
217.                   iPos -= 200;
218.                   m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxPosSlider) / m_MemoryData.nTicks);
219.                   UpdateIndicatorControl();
220.                }
221.             }
222. 
223.             return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService));
224.          }
225. //+------------------------------------------------------------------+
226. };
227. //+------------------------------------------------------------------+
228. #undef macroRemoveSec
229. #undef def_SymbolReplay
230. #undef def_CheckLoopService
231. //+------------------------------------------------------------------+

Código fonte do arquivo C_Replay.mqh

Observe com muita, mas muita atenção o que explicarei. Pois se você não entender os detalhes envolvidos. Irá ficar completamente em órbita no que faremos a fim de conseguir melhorar a performance do serviço de replay/simulador.

Na linha 32 definimos uma nova variável. Esta variável é privativa da classe C_Replay. Porém, a estrutura usada é definida no arquivo de cabeçalho C_FileTicks.mqh. Para mais detalhes sobre isto, veja o artigo anterior. Muito bem, esta variável é utilizada em alguns pontos, considerados mais críticos dentro da classe C_Replay. Tais pontos foram escolhidos, por serem mais requisitados durante a execução do serviço. Um destes pontos é o procedimento CreateBarInReplay, que é justamente usado para tornar os ticks em barras no gráfico. E um outro ponto é justamente na função LoopEventOnTime, onde na linha 207, inicializamos esta mesma variável.

Agora atenção: Quando na linha 207, usamos a função GetInfoTicks, a fim de capturar os valores presentes, lá na classe C_FileTicks, e que também fazem parte de uma variável privativa. Não estamos de maneira alguma, criando algum tipo de ponteiro, a fim de poder manipular os dados presentes na variável privativa, presente na classe C_FileTicks. Na verdade, quando a linha 207 é executada, o que fazemos é duplicar as informações presentes na variável privativa da classe C_FileTicks, em uma nova variável que será privativa aqui da classe C_Replay.

Sei que isto, não parece ser nem de longe adequado, ou que irá nos ajudar a tornar a execução do serviço mais eficiente. Porém, apesar de termos um custo maior, por usar mais memória do sistema, temos de fato um ganho na performance. Visto que o acesso, não seria mais através de chamadas de procedimento, via função. Mas sim um acesso a variáveis, que é consideravelmente mais rápido. No entanto, se o MQL5, nos permitisse fazer uso de ponteiro, poderíamos colocar os dados em uma região comum, onde a classe C_FileTicks teria acesso de leitura e escrita, mas toda e qualquer outra classe, ou parte do código teria acesso apenas a leitura. Isto manteria o código consumindo menos memória, ao mesmo tempo, tendo uma performance bastante adequada. Além é claro, garantir todo o encapsulamento das variáveis nas classes.

Tal situação onde faríamos uso de ponteiro, é mais bem implementada quando fazemos uso de funções para isto. Este tipo de coisa foi implementado na versão que pode ser vista no artigo anterior. Porém ao colocar o serviço de replay / simulador funcionando, principalmente no modo simulação, onde as barras são transformadas em ticks para depois voltar a ser novamente barras. Notei uma queda substancial na performance do serviço. Isto devido ao fato de que, talvez o compilador MQL5, no momento que escrevo este artigo, não esteja conseguindo ver, aquela construção de função, como sendo um ponteiro, a ser usado de maneira a permitir que tenhamos acesso restrito a uma dada estrutura. Onde a classe base, no caso C_FileTicks, poderia ter acesso de leitura e escrita, mas qualquer código fora da classe, teria acesso apenas a leitura. Talvez no momento que você esteja lendo este artigo, os criadores e responsáveis por manter e desenvolvedor o compilador do MQL5, já tenham resolvido esta questão. Nos permitindo assim ter a mesma performance, que será conseguida usando este esquema daqui. Uma observação: Como este artigo foi escrito, a um bom tempo. Você deve experimentar, o código antes de implementar o que será visto aqui. Isto por que, pode não ser necessário fazer o que será mostrado.

Mas não culpo os criadores e desenvolvedores do compilador MQL5. Já que não conseguir ver em nenhum outro local, este tipo de construção que estou propondo. Porém, isto me forçou a mudar o conceito, e a forma de agir, sendo preciso uma decisão.

  • Voltar a quebrar o encapsulamento, que foi feito no artigo anterior;
  • Implementar a coisa duplicando os dados. 

Preferi duplicar os dados, já que memória é barata. Mas quebrar o encapsulamento, iria dificulta o desenvolvimento e aperfeiçoamento do código. Com isto, tivemos um ganho em torno de quatro a cinco vezes, na velocidade de execução. Mas mantendo o mesmo nível de segurança dos dados. Durante os testes, antes de implementar a duplicação dos dados, conseguíamos colocar perto de 3 segundos de ticks, isto usando o Dólar. Estes dados podem ser obtidos nos artigos da primeira fase. Agora conseguimos colocar quase 15 segundos de dados. Lembrando que isto são quando é usado a simulação. No caso do replay, a barra de 1 minuto passou a ser construída dentro do prazo aceitável, ou seja, próximo de 60 segundos.

Agora um detalhe importante. Você possivelmente pode estar pensando que a função Sleep, presente na linha 216, pode estar deixando o sistema lento, e se a removêssemos, teríamos mais dados sendo lançados no gráfico. Porém, quando fazemos uso de ticks reais, a presença da função Sleep, na linha 216, se torna necessária. Caso ela não estivesse ali, teríamos um lançamento de dados bem mais rápido do que o normal. Isto faria com que o replay, e não a simulação, da barra de 1 minuto, ocorresse antes mesmo que os 60 segundos viessem a passar.


Conclusão

Agora temos o seguinte problema: Quando estamos fazendo uma simulação, pode acontecer de que tenhamos, menos lançamentos de ticks do que seria o correto, isto no tempo de 60 segundos. No entanto, quando fazemos o replay, os lançamentos não tem um atraso assim tão substancial. Sendo assim, no caso do replay, o sistema está sendo executado, nos mesmos moldes que era executado na fase anterior, onde fazíamos uso das variáveis globais de terminal. Já no caso da simulação, a coisa terá que sofrer alguma mudança.

Arquivos anexados |
Anexo.zip (420.65 KB)
Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
Filtragem de Sazonalidade e Período de Tempo para Modelos de Deep Learning ONNX com Python para EA Filtragem de Sazonalidade e Período de Tempo para Modelos de Deep Learning ONNX com Python para EA
Podemos nos beneficiar da sazonalidade ao criar modelos de Deep Learning com Python? A filtragem de dados para os modelos ONNX ajuda a obter melhores resultados? Qual período de tempo devemos usar? Cobriremos tudo isso neste artigo.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Redes neurais de maneira fácil (Parte 82): modelos de equações diferenciais ordinárias (NeuralODE) Redes neurais de maneira fácil (Parte 82): modelos de equações diferenciais ordinárias (NeuralODE)
Neste artigo, gostaria de apresentar outro tipo de modelos voltados para o estudo da dinâmica do estado do ambiente.