preview
Desenvolvendo um sistema de Replay (Parte 55): Módulo de controle

Desenvolvendo um sistema de Replay (Parte 55): Módulo de controle

MetaTrader 5Exemplos | 4 julho 2024, 16:01
211 1
Daniel Jose
Daniel Jose

Introdução

No artigo anterior Desenvolvendo um sistema de Replay (Parte 54): O nascimento do primeiro módulo, fizemos a montagem do primeiro módulo real de todo o nosso novo sistema de replay / simulador. Além de poder vim a ser utilizado no sistema que está sendo desenvolvido, poderemos também fazer uso dos módulos de forma pessoal e personalizada, a fim de evitar necessitar fazer uma grande programação, a fim de conseguir gerar tal sistema. Seja ele qual for, já que uma vez que o módulo tenha sido construído, podemos facilmente ajustar o mesmo, sem necessidade de uma nova compilação. 

Para fazer isto, será preciso apenas enviar uma mensagem para dentro do módulo, a fim de que ele possa mudar sua aparência ou forma de funcionar, algo que pode ser facilmente feito por meio de um script simples.

Muito bem, então com base no que foi visto nos últimos artigos, passamos a ter a possibilidade de produzir um sistema que tanto pode ser utilizado em uma conta real ou conta de demonstração. Mas também, poderemos fazer muito mais coisas, e entre elas criar um sistema de replay / simulador que venha a se comportar de maneira bastante similar ao que seria visto em uma conta real ou conta de demonstração.

Mas a principal vantagem, deste novo modelo que passaremos a fazer uso, é o fato de que você poderá utilizar as mesmas ferramentas e aplicações, tanto no sistema de replay / simulador, quanto no dia a dia, usando a plataforma MetaTrader 5, para estudar na conta de demonstração, ou mesmo executando operações em uma conta real.

Agora que o nosso indicador de mouse, já se encontra concluído. Podemos passar a construir, ou melhor dizendo, adequar o nosso indicador de controle, a fim de que ele passe a trabalhar de forma modular. Vale uma breve explicação aqui, a respeito do que acabei de mencionar.

Até pouco tempo atrás o sistema de replay / simulador, estava utilizando variáveis globais de terminal. Isto para promover uma certa comunicação entre os programas, que precisávamos ter para poder interagir, controlar e acessar o serviço de replay / simulação.

A partir do momento que passamos a fazer uso de um sistema modular, onde a troca de mensagens é feita via eventos customizados, não iremos mais precisar fazer uso das variáveis globais de terminal. Por conta disto, agora poderemos remover todas a variáveis globais de terminal que antes eram usadas. Mas ao fazer isto iremos precisar, adaptar as coisas, a fim de fazer com que a informação, continue fluindo entre os programas.

Fazer tal coisa, ou seja, modelar o sistema de transferência de informação, é uma tarefa da qual devemos fazer com bastante calma. Já que não existe a possibilidade de que, a informação poderá ser lida a posterior, ou melhor dizendo. Se o programa, ou aplicação, não estiver presente no gráfico, no momento que a informação chegar via evento customizado, ela estará perdida. Sendo assim necessário mecanismos extras a fim de fazer com que a mesma informação seja reenviada, até que tenhamos a garantia de que ela realmente foi capturada pela aplicação, ou programa desejado.

Então com base neste critério, foi decidido, que o sistema de replay / simulador, irá contar com, obrigatoriamente, 3 programas principais. Isto a fim de que ele de fato possa funcionar minimamente. Destes programas, apenas dois realmente estarão visíveis ao usuário. O programa responsável pelo serviço em si, e o indicador de mouse. Já o indicador de controle, irá ser tratado como um recurso do programa de serviço. Assim ele não poderá no final se utilizado, sem que o serviço de fato esteja sendo prestado.

Pois bem, dado esta pequena explicação, vamos ver como o indicador de controle, foi modificado a fim de que ele possa começar a fazer o seu trabalho. E este é justamente controlar o serviço de replay / simulador.


Modificando o indicador de controle

As mudanças que precisaram ser feitas no indicador de controle, nem foram tantas assim, já que durante a fase anterior, onde já começávamos a remover as variáveis globais de terminal, muitas mudanças já haviam sido feitas. No entanto, existem algumas coisas que sem o correto entendido do como a troca de mensagens irá acontecer, faz com que você não consiga de fato entender como o sistema consegue fazer o que ele estará fazendo.

Então vamos tentar entender as coisas desde o início, isto para que você não fique completamente a ver navios nos próximos artigos que ainda serão apresentados.

Quando o indicador é colocado no gráfico, podemos permitir ao usuário configurar algumas coisas. Tais coisas, fazem parte justamente das variáveis de entrada do indicador. Mas estas mesmas variáveis, na verdade, e em certos momentos, são mais um empecilho, do que aliadas. Não me entenda mal. Não estou aqui querendo promover uma completa radicalização das coisas. Mas quando damos acesso ao usuário a uma variável, a fim de permitir que o indicador ( no caso ), seja configurado previamente, estamos abrindo uma porta para que problemas venham a acontecer.

Mas desconsiderando o fato de que o usuário, venha a modificar ou mexer onde não deveria, estas mesmas variáveis nos ajudam bastante. Tanto que fizemos uso delas, a fim de conseguir informar a ID do gráfico ao indicador. Não que de fato um indicador venha a necessitar de tal informação. Mas você deve lembrar de que no momento que o indicador é colocado no gráfico, o mesmo pode vim a ter uma ID diferente da esperada pelos objetos. Isto foi visto em um artigo anterior, nesta mesma sequência.

Mas apesar de podemos usar o sistema de mensagens a fim de transmitir a ID para o indicador. E sim poderíamos fazer isto, pois quem irá abrir de fato o gráfico será o serviço, e este sabe qual é a ID do gráfico. Este tipo de coisa apenas iria complicar, de maneira desnecessária o código. Tanto o código do serviço, quando o código do indicador. Por este motivo, irei manter as coisas funcionando, como havíamos feito até aqui. No entanto precisaremos fazer algumas pequenas mudanças no código do indicador de controle. Visto que não mais iremos usar nenhuma variável global de terminal, a fim de repassar os dados para ele.

Bem, logo abaixo, você pode ver o código do arquivo C_Control.mqh, na integra. E já que grande parte do código, já foi explicado no decorrer dos artigos anteriores, vamos focar apenas nas partes novas, e que tem alguma redundância para serem de fato mencionadas e explicadas.

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

Código fonte do arquivo C_Control.mqh

Você pode notar, algumas coisas aparentemente estranhas no código. A primeira delas está na linha 150, onde indicamos um valor para o deslocamento mínimo dá o controle deslizante. Fazemos isto via uma constante definida dentro do MQL5, INT_MIN. Este valor é um valor negativo, sendo que ele é o valor mais negativo que poderemos ter em uma variável inteira. Mas por que eu fiz isto ?!?! Entender o motivo agora é um pouco complicado, então peço para que você tenha calma, pois para de fato entender por que desta linha 150, será preciso entender outras coisas também.

A próxima coisa que merece algum destaque, é visto na linha 160, onde temos uma rotina, a fim de poder escrever no buffer do indicador de controle. Vejam, que basicamente iremos escrever apenas dois valores, isto pelo menos neste primeiro momento, não sei se teremos necessidade de escrever mais valores, mas estes dois valores estarão compactados em um valor do tipo double, assim iremos usar apenas uma única posição dentro do buffer.

Para fazer esta compactação iremos usar uma união que é declarada na linha 162. Então na linha 164, colocamos o valor que será ajustado pelo usuário quando este manipular o controle deslizante. </b$> Atenção a este detalhe: Estaremos armazenando a posição do controle que foi modificada pelo usuário. Já na linha 165 estaremos informando o status do indicador de controle, ou seja, se ele estará em modo play ou modo pause.

Muita atenção aqui. Observem que quando estamos indicando que estaremos em play, ou seja, que o usuário pressionou o botão de play, teremos um valor sendo armazenado, já quando estivermos em modo pausado, estaremos usando um outro valor. Os valores que estaremos usando são os extremos do limite possível para o tipo inteiro. Desta maneira, evitamos a ambiguidade de usar o valor zero, ao mesmo tempo garantimos a integridade da informação, sendo mais simples de testar depois.

Este tipo de coisa é muito importante, se você não pensar com calma, durante esta fase de construção do indicador de controle, irá ter problemas depois. Você deve se lembrar, de uma coisa: A informação não irá diretamente do indicador de controle para dentro do serviço, ela irá ter que passar por um canal, e este canal, que iremos usar é o buffer. Mas irei voltar neste ponto em breve, já que será preciso explicar alguns detalhes sobre isto.

No final das contas, na linha 166, iremos armazenar o valor compactado, em uma posição especifica do buffer do indicador. Já expliquei em outro artigo desta mesma sequência o motivo de armazenar os dados nesta posição, então em caso de dúvidas, leia os artigos mais antigos desta sequência, a fim de sanar qualquer dúvida referente a isto.

A próxima coisa dentro deste código, que de fato merece ser explicada, está presente no tratador de mensagens. Este tratador, se inicia na linha 169. Neste temos dois pontos que merecem destaque. Então vamos começar com o que envolve menos coisas, e ainda assim está ligado ao que foi explicado a pouco. Desta forma vamos a linha 209.

Nesta linha temos algo curioso. Armazenamos o valor que foi ajustado pelo usuário ao mexer no controle deslizante, na variável m_Slider.Minimal. O motivo de fazer isto, é justamente para simplificar as coisas, assim podemos centralizar tudo em pontos chave do código. Já que se esta linha 209, não existisse, teríamos que fazer em algum ponto do código, algum tipo de teste, a fim de transferir para o buffer a posição ajustada pelo usuário. Ou pior, teríamos que conseguir de alguma maneira lançar o valor ajustado pelo usuário para o serviço, e isto sem fazer uso das variáveis globais de terminal. Já que antes quem fazia isto era justamente uma variável, mas agora iremos fazer uso de um buffer. Assim para não ficar testado e reajustando as coisas, colocamos o valor em um local que com certeza, será facilmente acessível. Detalhe: Este valor somente será armazenado neste ponto, quando o usuário pressionar o botão de play.

Dada esta explicação, podemos voltar um pouco no código, e veremos a linha 177. Nesta temos um evento customizado, e este tem como definição, inicializar o indicador de controle, isto a fim de que ele possa ter o controle deslizante adequadamente ajustado.

Este evento customizado, irá ser disparado de tempos em tempos, mas prestem bastante atenção aqui. Observem que o valor que irá conter os dados, estará presente dentro de um valor double, e de forma compactada. Então precisamos traduzir estas informações, ao mesmo tempo que devemos garantir a segurança e confiabilidade das mesmas. A informação recebida, de certa maneira, segue o mesmo princípio da informação que estaremos armazenando no buffer. Mas com um detalhe: Aqui estaremos testando a integridade da mesma. 

Observe que na linha 180 existe um pequeno teste. Este irá verificar, se o valor que indica se o sistema está em play ou pause, está com um valor zero. Se isto acontecer, significa que algo de errado está sendo informado ao indicador de controle, e que um erro aconteceu. Por conta disto que temos a chamada SetUserError. Normalmente ela não será executada, mas se for, teremos que providenciar as medidas cabíveis. Mas esta será vista depois, já no código do indicador, que será visto logo mais.

Então se tudo estiver de acordo, iremos fazer duas outras coisas. A primeira, é uma chamada na linha 182, ao procedimento responsável por mostrar o botão de play ou pause. Já a segunda é um teste, este é visto na linha 183. Caso o valor seja o mínimo, isto indica que estaremos no modo pausado, então precisamos recriar a barra de controle deslizante a fim de que o usuário possa ajustar as coisas.

Basicamente é isto. No momento que o indicador é lançado no gráfico, ele de fato não irá funcionar, isto até que um evento customizado o inicialize. Mas a forma com isto será feita, será visto depois. Mas a interação entre o indicador de mouse e o indicador de controle, é que irá gerar todo o ciclo de mensagens, isto a fim de o serviço de replay / simulador seja de fato controlado.

Agora vamos ver o código do indicador de controle. Este pode ser visto logo abaixo, na integra. Então preste bastante atenção a explicação deste código, já que aqui iremos formar a base do que será visto em futuros módulos deste mesmo sistema.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico"
04. #property description "Control indicator for the Replay-Simulator service."
05. #property description "This one doesn't work without the service loaded."
06. #property version   "1.55"
07. #property link "https://www.mql5.com/pt/articles/11988"
08. #property indicator_chart_window
09. #property indicator_plots 0
10. #property indicator_buffers 1
11. //+------------------------------------------------------------------+
12. #include <Market Replay\Service Graphics\C_Controls.mqh>
13. //+------------------------------------------------------------------+
14. C_Controls *control = NULL;
15. //+------------------------------------------------------------------+
16. input long user00 = 0;      //ID
17. //+------------------------------------------------------------------+
18. double m_Buff[];
19. int    m_RatesTotal;
20. //+------------------------------------------------------------------+
21. int OnInit()
22. {
23.    ResetLastError();   
24.    if (CheckPointer(control = new C_Controls(user00, "Market Replay Control", new C_Mouse(user00, "Indicator Mouse Study"))) == POINTER_INVALID)
25.       SetUserError(C_Terminal::ERR_PointerInvalid);
26.    if (_LastError != ERR_SUCCESS)
27.    {
28.       Print("Control indicator failed on initialization.");
29.       return INIT_FAILED;
30.    }
31.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
32.    ArrayInitialize(m_Buff, EMPTY_VALUE);
33.    
34.    return INIT_SUCCEEDED;
35. }
36. //+------------------------------------------------------------------+
37. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
38. {
39.    return m_RatesTotal = rates_total;
40. }
41. //+------------------------------------------------------------------+
42. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
43. {
44.    (*control).DispatchMessage(id, lparam, dparam, sparam);
45.    if (_LastError >= ERR_USER_ERROR_FIRST + C_Terminal::ERR_Unknown)
46.    {
47.       Print("Internal failure in the messaging system...");
48.       ChartClose(user00);
49.    }
50.    (*control).SetBuff(m_RatesTotal, m_Buff);
51. }
52. //+------------------------------------------------------------------+
53. void OnDeinit(const int reason)
54. {
55.    switch (reason)
56.    {
57.       case REASON_TEMPLATE:
58.          Print("Modified template. Replay/simulation system shutting down.");
59.       case REASON_INITFAILED:
60.       case REASON_PARAMETERS:
61.       case REASON_REMOVE:
62.       case REASON_CHARTCLOSE:
63.          ChartClose(user00);
64.          break;
65.    }
66.    delete control;
67. }
68. //+------------------------------------------------------------------+

Código fonte do indicador de controle

Note que na linha 10, agora dizemos o MQL5, que iremos precisar de um buffer, mas note que na linha 9, dizemos que não iremos plotar nenhuma informação no gráfico, ou seja, o buffer será interno do indicador, não sendo visível para o usuário, mas poderá ser acessado por qualquer código que saiba como fazer o acesso a ele.

Já que iremos fazer uso de um buffer, precisamos declarar ele. Primeiro fazemos isto na linha 18, para depois na linha 31, declaramos o buffer a fim de que ele possa ser acessível fora do indicador. Já na linha 32, garantimos que o buffer, irá estar completamente vazio. Agora preste muita atenção, pois isto é importante.

Quando o usuário interage com a plataforma MetaTrader 5, a fim de trocar o tempo gráfico. O MetaTrader 5, remove toda e qualquer coisa que esteja no gráfico, e logo em seguida repõem as coisas. Ao fazer isto, todo e qualquer código será reinicializado. No caso dos indicadores teremos uma nova chamada ao evento OnInit, e este executa todo o processo de reinicializar o indicador novamente. Nosso indicador de controle, não faz praticamente nada, ou melhor dizendo, ele não executa nenhum tipo de cálculo especifico. Tendo como única utilidade, fornecer meios de que o usuário venha a interagir e controlar o serviço que é usado para lançar as barras no gráfico em um ativo customizado.

Pois bem, quando o usuário modifica o tempo gráfico, todo e qualquer valor dentro do indicador será perdido. Precisamos garantir que isto de fato aconteça, no entanto isto tem que ser feito de maneira controlada. O serviço que está sendo controlado pelo indicador, não faz ideia do que de fato está acontecendo no gráfico, assim como o indicador não faz a menor ideia do que o serviço esteja aprontando. A forma de fazermos com que ambos, tanto o serviço, quanto o indicador saibam o que cada um está de fato fazendo é via mensagens trocadas entre eles.

Antes esta troca de mensagens era feita via variável global de terminal. Mas agora estamos criando uma forma diferente, e precisamos garantir que independentemente do que o usuário venha a fazer no gráfico, tanto o indicador quanto o serviço estejam alinhados e cientes do que esteja acontecendo. Então para que o serviço saiba, o que está acontecendo com o indicador, fazemos uso do buffer de indicador. Ali colocamos todas as informações relacionadas ao que esteja acontecendo no indicador.

Já a forma do indicador saber o que o serviço está fazendo, é via parâmetros de entrada, ou seja, a input que é declarada na linha 16, e eventos customizados, que são tratados dentro da chamada OnChartEvent do indicador.

Enviar dados ao indicador via parâmetros de entrada, depois que este já esteja presente no gráfico, é algo inviável. Não estou dizendo que não podemos fazer assim, mas estou dizendo que é algo inviável, não é de fato algo que iremos fazer. Então, uma vez que o serviço tenha colocado o indicador no gráfico, ele irá perder a possibilidade de se comunicar com o indicador via parâmetros de entrada. Assim passamos a depender dos eventos customizados.

Mas agora vem o detalhe. Quando o usuário troca o tempo gráfico, o indicador irá deixar de saber, qual era o status que estava sendo usado, e qual era a posição de deslocamento que já havia sido alcançada. No entanto, o serviço sabe destas informações. Mas como você poderia fazer o serviço, informar estes dados ao indicador, a fim de que ele se mantivesse integro ?!?! O serviço, não sabe quando o MetaTrader 5, repões os indicadores no gráfico, mas o serviço consegue ver o buffer do indicador. E é aqui que mora o pulo do gato.

Quando o indicador acabou de ser colocado no gráfico, seu buffer estará inicialmente em zero. No momento que o constructor da classe C_Controls for executado, teremos um valor sendo inicializado de forma estranha, isto na linha 150 ( volte ao código da classe para entender ). Mas este valor não será colocado no buffer até que uma chamada a OnChartEvent aconteça, quando de fato na linha 50 do código do indicador, teremos o buffer sendo modificado.

Então quando o serviço ler o buffer, depois que o MetaTrader 5, repôs o indicador de controle no gráfico. O serviço irá ver valores zerados, ou um valor estranho. Neste momento ele irá disparar um evento customizado para o MetaTrader 5. Desta forma o serviço irá informar ao indicador os valores atualizados para o este seja corretamente plotado na tela. Assim teremos novamente a informação dos botões e controle deslizantes sendo corretamente plotados.

Se fossemos tentar de alguma outra maneira fazer isto, teríamos que bolar alguma forma de repor estas mesmas informações que foram perdidas. Isto poderia nos levar a criar diversas soluções diferentes, e todas teriam como sendo o mesmo resultado, a inicialização do indicador. No entanto, algumas destas soluções seriam passiveis de serem manipuladas pelo usuário, tornando assim a comunicação entre o serviço que está sendo controlado, e o indicador que está controlando o serviço, algo muito mais complicado de ser gerenciado. Mas fazendo assim, jogamos toda a complicação para a troca de mensagens entre o serviço e o indicador. Ou seja, criamos uma camada extra de segurança, ao mesmo tempo que controlamos a forma de garantir que as informações sejam lidas apenas por quem realmente precisa ler as mesmas e fazer uso de seu conteúdo.

O que acabei de explicar aqui, parece muito confuso e bastante exótico, isto para grande maioria. Mas principalmente para quem está começando na programação, já que esta ideia de troca de mensagens, inicialização controlada é algo que tenho certeza, muitos provavelmente nunca viram falar. Então para dar uma demonstração que tão ver a coisa funcionando na prática ?!?! Para isto iremos usar o indicador de controle e o indicador de mouse. Mas precisaremos criar algo, apenas para demonstrar e entender a ideia, antes de ver o sistema real funcionando.

Para fazer isto, iremos usar um código bem mais simples, porém eficiente o suficiente para que a ideia por trás de tudo que foi mostrado aqui fique mais clara. Então veja o código a ser usado logo abaixo.

01. //+------------------------------------------------------------------+
02. #property service
03. #property copyright "Daniel Jose"
04. #property description "Data synchronization demo service."
05. #property version   "1.00"
06. //+------------------------------------------------------------------+
07. #include <Market Replay\Defines.mqh>
08. //+------------------------------------------------------------------+
09. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
10. #resource "\\" + def_IndicatorControl
11. //+------------------------------------------------------------------+
12. input string user00 = "BOVA11";    //Symbol
13. //+------------------------------------------------------------------+
14. #define def_Loop ((!_StopFlag) && (ChartSymbol(id) != ""))
15. //+------------------------------------------------------------------+
16. void OnStart()
17. {
18.    uCast_Double info;
19.    long id;
20.    int handle, iPos, iMode;
21.    double Buff[];
22.    
23.    SymbolSelect(user00, true);
24.    id = ChartOpen(user00, PERIOD_H1);            
25.    if ((handle = iCustom(ChartSymbol(id), ChartPeriod(id), "::" + def_IndicatorControl, id)) != INVALID_HANDLE)
26.       ChartIndicatorAdd(id, 0, handle);
27.    IndicatorRelease(handle);
28.    if ((handle = iCustom(ChartSymbol(id), ChartPeriod(id), "\\Indicators\\Mouse Study.ex5", id)) != INVALID_HANDLE)
29.       ChartIndicatorAdd(id, 0, handle);
30.    IndicatorRelease(handle);   
31.    Print("Service maintaining sync state...");
32.    iPos = 0;
33.    iMode = INT_MIN;
34.    while (def_Loop)
35.    {
36.       while (def_Loop && ((handle = ChartIndicatorGet(id, 0, "Market Replay Control")) == INVALID_HANDLE)) Sleep(50);
37.       info.dValue = 0;
38.       if (CopyBuffer(handle, 0, 0, 1, Buff) == 1) info.dValue = Buff[0];
39.       IndicatorRelease(handle);
40.       if (info._int[0] == INT_MIN)
41.       {
42.          info._int[0] = iPos;
43.          info._int[1] = iMode;
44.          EventChartCustom(id, evCtrlReplayInit, 0, info.dValue, "");
45.       }else if (info._int[1] != 0)
46.       {
47.          iPos = info._int[0];
48.          iMode = info._int[1];
49.       }
50.       Sleep(250);
51.    }
52.    ChartClose(id);
53.    Print("Finished service...");   
54. }
55. //+------------------------------------------------------------------+

Código fonte do Serviço de Demonstração

Note que na linha 10, tornamos o indicador de controle um recurso do serviço. Desta forma não precisamos dele na lista de indicadores, mesmo por que ele não tem utilidade para nada que não seja o serviço que estará sendo implementado. Na linha 12 temos como informar um ativo, a fim de poder testar o sistema. Um detalhe, utilize um ativo válido, já que não estaremos testando isto depois. Já na linha 14, temos uma definição que irá servir para testarmos algumas condições, a fim de poder encerrar o serviço de maneira adequada.

Na linha 23, colocamos o ativo na janela de observação de mercado, caso ele não esteja ali. Já na linha 24 iremos abrir uma janela gráfica que irá conter o ativo que você como usuário indicou. Assim que isto for feito, teremos a ID do gráfico, e poderemos usar está ID a fim de conseguir colocar os indicadores no gráfico.

Então na linha 25, iremos colocar o indicador de controle no gráfico que acabou de ser aberto. E na linha 29 iremos adicionar o indicador de mouse. Atenção ao fato de que o indicador de mouse estará sendo listado, e o indicador de controle não estará. Mas precisamos de ambos a fim de que as coisas possam ser devidamente testadas.

Na linha 31, informamos no terminal que o serviço estará ativo, e que irá ficar acompanhando o que estiver acontecendo no gráfico.

Até este momento, o indicador de mouse, já poderá ser visto no gráfico. No entanto o indicador de controle não estará visível, apesar de já se encontrar sendo listado entre os indicadores presentes no gráfico. Não se esqueça da explicação que dei sobre como o indicador de controle está sendo inicializado. Então neste momento, o buffer do mesmo terá valores estanhos, não tendo nenhum tipo de representatividade para nós. E por conta disto, não podemos interagir com ele. Mas se o indicador de controle foi corretamente inicializado, e o buffer do mesmo já tiver sido escrito, teremos um valor bastante especifico no mesmo. Então na linha 34 entramos em um laço que irá se manter, enquanto as condições definidas na linha 14 se mantiverem.

Agora um detalhe: Na linha 36, verificamos se o indicador de controle de fato já se encontra no gráfico. Mas por que fazer este teste e aguardar até que ele termine ?!? O motivo é que o código pode rodar muito mais rápido do que as coisas de fato acontecem. Então precisamos de alguma maneira permitir que o MetaTrader 5 estabilize as coisas, por isto fazemos este laço na linha 36.

Uma vez que tudo esteja em ordem, tentamos ler o buffer do indicador de controle. Mais uma vez quero lembrar a você, que o indicador ainda não estará visível no gráfico.

Se a leitura for feita, teremos algum valor diferente de zero sendo colocado na variável info.dValue. Neste momento poderemos testar as coisas a fim de verificar como o indicador de controle se encontra. Então na linha 40, verificamos se ele já foi inicializado. Como é a primeira vez, teremos como resposta que o indicador não foi inicializado. Então nas linhas 42 e 43, montamos o valor a ser transferido para o indicador, e enviamos um pedido ao MetaTrader 5, para que um evento customizado seja gerado no gráfico, este evento é mostrado na linha 44, onde iremos transferir uma mensagem para o indicador de controle a fim de inicializarmos o mesmo.

Já em qualquer outro momento, iremos verificar se o status é de play ou pause. Se for qualquer um destes dois, a linha 45 irá permitir que façamos o armazenamento dos valores ajustados pelo usuário no indicador. Assim quando o usuário mudar o tempo gráfico, teremos novamente o teste da linha 40 dando um valor verdadeiro, e os valores memorizados, aqui no serviço, serão novamente informados ao indicador, tornado seu uso algo plausível, mesmo que o tempo gráfico seja modificado, o indicador sempre saberá como se inicializar corretamente.

No final, fechamos o gráfico usando a linha 52, e informamos que o serviço foi encerrado. Isto na linha 53.

No vídeo abaixo, você pode ver o sistema funcionando, caso não queira experimentar o mesmo. Já que irei deixar no anexo os executáveis a fim de que você possa ver como tudo funciona na prática.



Vídeo de demonstração.


Conclusão

Neste artigo começamos a dar corpo a coisa que realmente será vista nos próximos artigos. Sei que o conteúdo é bastante denso, então estude com calma o que estou mostrando, pois a coisa só irá complicar cada vez mais de agora em diante.

Arquivos anexados |
Anexo.zip (420.65 KB)
Últimos Comentários | Ir para discussão (1)
1226820
1226820 | 14 jul 2024 em 13:45
Uma verdadeira aula.
Modelos de regressão da biblioteca Scikit-learn e sua exportação para ONNX Modelos de regressão da biblioteca Scikit-learn e sua exportação para ONNX
Neste artigo, exploraremos a aplicação de modelos de regressão do pacote Scikit-learn, tentaremos convertê-los para o formato ONNX e usaremos os modelos resultantes em programas MQL5. Além disso, compararemos a precisão dos modelos originais com suas versões ONNX para ambas as precisões float e double. Além disso, examinaremos a representação ONNX dos modelos de regressão, com o objetivo de fornecer uma melhor compreensão de sua estrutura interna e princípios operacionais.
Algoritmos de otimização populacionais: objetos de busca multissociais artificiais (artificial Multi-Social search Objects, MSO) Algoritmos de otimização populacionais: objetos de busca multissociais artificiais (artificial Multi-Social search Objects, MSO)
Continuação do artigo anterior como desenvolvimento da ideia de grupos sociais. No novo artigo, explora-se a evolução dos grupos sociais utilizando algoritmos de movimentação e memória. Os resultados ajudarão a entender a evolução dos sistemas sociais e aplicá-los na otimização e busca de soluções.
Redes neurais de maneira fácil (Parte 75): aumentando a produtividade dos modelos de previsão de trajetórias Redes neurais de maneira fácil (Parte 75): aumentando a produtividade dos modelos de previsão de trajetórias
Os modelos que estamos criando estão se tornando cada vez maiores e mais complexos. Com isso, aumentam os custos não apenas para o treinamento, mas também para a operação. Além disso, muitas vezes nos deparamos com situações em que o tempo de tomada de decisão é crítico. E, por isso, voltamos nossa atenção para métodos de otimização de desempenho dos modelos sem perder qualidade.
Algoritmos de otimização populacionais: evolução de grupos sociais (Evolution of Social Groups, ESG) Algoritmos de otimização populacionais: evolução de grupos sociais (Evolution of Social Groups, ESG)
Neste artigo, consideraremos o princípio de construção de algoritmos multipopulacionais e, como exemplo desse tipo de algoritmos, analisaremos a Evolução de Grupos Sociais (ESG), um novo algoritmo autoral. Analisaremos os conceitos principais, os mecanismos de interação entre populações e as vantagens desse algoritmo, bem como examinaremos seu desempenho em tarefas de otimização.