
Desenvolvendo um sistema de Replay (Parte 70): Acertando o tempo (III)
Introdução
No artigo anterior Desenvolvendo um sistema de Replay (Parte 69): Acertando o tempo (II), mostrei como fazer para conseguir, mostrar o tempo restante da barra, mesmo quando o ativo está em um momento de baixa liquidez. Entenda esta baixa liquidez, como sendo o fato de não temos negócios sendo executados dentro de um dado momento. Tais coisas, tem diversos motivos. Mas não nos vem ao caso, explicar o motivo específico pelo qual isto acontece. Tudo que temos que fazer é conseguir lidar com tais situações de forma adequada.
Só que ainda nos resta um problema a ser resolvido. Este problema, é por assim dizer, bastante chato e complicado de ser resolvido. Não por conta da programação envolvida na solução. Mas por conta da forma de sabe, quando ele acontece e como devemos lidar com ele. O dito problema é conhecido como: LEILÃO.
Leilões no geral, são coisas que advêm de fatos bastante específicos. Eles não se dão de maneira arbitrária, ou aleatória. Na verdade, existem regras bem claras e bastante rígidas, sobre leilões. Mas para nós, aqui no desenvolvimento de um sistema de replay/simulação, o que realmente interessa é: Como podemos informar ao usuário que o ativo entrou em leilão? Este é o principal e único problema que temos que lidar. Mas a solução, como expliquei antes, já existe. E se encontra implementada no indicador de mouse. Se bem que teremos de fazer algumas mudanças no código, a fim de que tenhamos mais liberdade. Isto para que a informação, de que o ativo, no caso um ativo customizado, que está sendo usado em um replay ou simulação, se encontra em leilão.
Muito bem, esta é a parte fácil. A parte complicada é definir como o sistema de replay/simulador, deverá declarar que o ativo customizado entrou em processo de leilão. Esta é a parte difícil.
No passado, quando esta aplicação de replay/simulação ainda estava no começo de seu desenvolvimento. Foi feito o uso da seguinte regra: Se entre ticks negociados, houver um período igual ou superior a 60 segundos, devermos identificar isto como sendo um leilão. Ok, sei que não é a melhor solução de todas. Já que existem motivos bem específicos para que os leilões ocorram. Mas não quero complicar o sistema, a fim de incluir meios de que ele consiga analisar os movimentos dos ticks. Isto para que ele venha a perceber, que o ativo deveria entrar em processo de leilão. Fazer isto, iria de certa forma, extrapolar o nível de complexidade que acredito ser aceitável e viável de ser mantido e desenvolvido. Já que se isto viesse a ser feito, acabaríamos por construir, uma aplicação que conseguiria sentir o mercado a fim de saber que existe ou não algo estranho acontecendo. E esta não é a minha intenção. Você pode até usar este sistema de replay/simulador, a fim de estudar e conseguir fazer tal coisa. Mas não entrarei nos detalhes e explicações de como você poderia conseguir tal coisa.
Desta forma, vamos manter a ideia que foi desenvolvida lá no começo: Se o ativo, ficar um tempo igual ou superior a 60 segundos sem que um negócio tenha ocorrido. Deveremos fazer com que o indicador de mouse, informe ao usuário que o ativo entrou em leilão. Simples assim. Desta maneira podemos focar na parte do código. Então vamos começar a fazer a implementação.
Coisas engraçadas
Existem coisas na programação, que somente quem de fato desenvolve as coisas conseguiria entender do que estou falando. Não sei como e nem quero tentar entender. Definitivamente, depois de mais de duas décadas como programador profissional, não tento entender mais certas coisas. Simplesmente assumo, que as coisas não estão sendo feitas como deveriam. E depois apenas aceito que não posso controlar determinadas condições. Então, não tente entender o que se passou. Apenas mude os arquivos para o novo código que estarei mostrando abaixo. Não me pergunte o que aconteceu, ou como o código abaixo funciona. Pois se eu explicar você vai me taxar de louco. Assim sendo, os novos códigos que deverão ser utilizados se encontram na íntegra logo abaixo. Não haverá anexo. Apenas passe a usar estes novos códigos.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "Macros.mqh" 005. #include "..\Defines.mqh" 006. //+------------------------------------------------------------------+ 007. class C_Terminal 008. { 009. //+------------------------------------------------------------------+ 010. protected: 011. enum eErrUser {ERR_Unknown, ERR_FileAcess, ERR_PointerInvalid, ERR_NoMoreInstance}; 012. //+------------------------------------------------------------------+ 013. struct st_Terminal 014. { 015. ENUM_SYMBOL_CHART_MODE ChartMode; 016. ENUM_ACCOUNT_MARGIN_MODE TypeAccount; 017. long ID; 018. string szSymbol; 019. int Width, 020. Height, 021. nDigits, 022. SubWin, 023. HeightBar; 024. double PointPerTick, 025. ValuePerPoint, 026. VolumeMinimal, 027. AdjustToTrade; 028. }; 029. //+------------------------------------------------------------------+ 030. private : 031. st_Terminal m_Infos; 032. struct mem 033. { 034. long Show_Descr, 035. Show_Date; 036. bool AccountLock; 037. }m_Mem; 038. //+------------------------------------------------------------------+ 039. void CurrentSymbol(void) 040. { 041. MqlDateTime mdt1; 042. string sz0, sz1; 043. datetime dt = macroGetDate(TimeCurrent(mdt1)); 044. enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER; 045. 046. sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3); 047. for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS); 048. switch (eTS) 049. { 050. case DOL : 051. case WDO : sz1 = "FGHJKMNQUVXZ"; break; 052. case IND : 053. case WIN : sz1 = "GJMQVZ"; break; 054. default : return; 055. } 056. for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0)) 057. if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break; 058. } 059. //+------------------------------------------------------------------+ 060. inline void ChartChange(void) 061. { 062. int x, y, t; 063. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 064. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 065. ChartTimePriceToXY(m_Infos.ID, 0, 0, 0, x, t); 066. ChartTimePriceToXY(m_Infos.ID, 0, 0, m_Infos.PointPerTick * 100, x, y); 067. m_Infos.HeightBar = (int)((t - y) / 100); 068. } 069. //+------------------------------------------------------------------+ 070. public : 071. //+------------------------------------------------------------------+ 072. C_Terminal(const long id = 0, const uchar sub = 0) 073. { 074. m_Infos.ID = (id == 0 ? ChartID() : id); 075. m_Mem.AccountLock = false; 076. m_Infos.SubWin = (int) sub; 077. CurrentSymbol(); 078. m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR); 079. m_Mem.Show_Date = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE); 080. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false); 081. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, true); 082. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, true); 083. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false); 084. m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 085. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 086. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 087. m_Infos.PointPerTick = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); 088. m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); 089. m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP); 090. m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick; 091. m_Infos.ChartMode = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE); 092. if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)); 093. ChartChange(); 094. } 095. //+------------------------------------------------------------------+ 096. ~C_Terminal() 097. { 098. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, m_Mem.Show_Date); 099. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, m_Mem.Show_Descr); 100. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, false); 101. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, false); 102. } 103. //+------------------------------------------------------------------+ 104. inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg) 105. { 106. if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true; 107. m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING); 108. } 109. //+------------------------------------------------------------------+ 110. inline const st_Terminal GetInfoTerminal(void) const 111. { 112. return m_Infos; 113. } 114. //+------------------------------------------------------------------+ 115. const double AdjustPrice(const double arg) const 116. { 117. return NormalizeDouble(round(arg / m_Infos.PointPerTick) * m_Infos.PointPerTick, m_Infos.nDigits); 118. } 119. //+------------------------------------------------------------------+ 120. inline datetime AdjustTime(const datetime arg) 121. { 122. int nSeconds= PeriodSeconds(); 123. datetime dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0); 124. 125. return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt))); 126. } 127. //+------------------------------------------------------------------+ 128. inline double FinanceToPoints(const double Finance, const uint Leverage) 129. { 130. double volume = m_Infos.VolumeMinimal + (m_Infos.VolumeMinimal * (Leverage - 1)); 131. 132. return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade))); 133. }; 134. //+------------------------------------------------------------------+ 135. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 136. { 137. static string st_str = ""; 138. 139. switch (id) 140. { 141. case CHARTEVENT_CHART_CHANGE: 142. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 143. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 144. ChartChange(); 145. break; 146. case CHARTEVENT_OBJECT_CLICK: 147. if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false); 148. if (ObjectGetInteger(m_Infos.ID, sparam, OBJPROP_SELECTABLE) == true) 149. ObjectSetInteger(m_Infos.ID, st_str = sparam, OBJPROP_SELECTED, true); 150. break; 151. case CHARTEVENT_OBJECT_CREATE: 152. if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false); 153. st_str = sparam; 154. break; 155. } 156. } 157. //+------------------------------------------------------------------+ 158. inline void CreateObjectGraphics(const string szName, const ENUM_OBJECT obj, const color cor = clrNONE, const int zOrder = -1) const 159. { 160. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, false); 161. ObjectCreate(m_Infos.ID, szName, obj, m_Infos.SubWin, 0, 0); 162. ObjectSetString(m_Infos.ID, szName, OBJPROP_TOOLTIP, "\n"); 163. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_BACK, false); 164. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_COLOR, cor); 165. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTABLE, false); 166. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTED, false); 167. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_ZORDER, zOrder); 168. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true); 169. } 170. //+------------------------------------------------------------------+ 171. bool IndicatorCheckPass(const string szShortName) 172. { 173. string szTmp = szShortName + "_TMP"; 174. 175. IndicatorSetString(INDICATOR_SHORTNAME, szTmp); 176. m_Infos.SubWin = ((m_Infos.SubWin = ChartWindowFind(m_Infos.ID, szTmp)) < 0 ? 0 : m_Infos.SubWin); 177. if (ChartIndicatorGet(m_Infos.ID, m_Infos.SubWin, szShortName) != INVALID_HANDLE) 178. { 179. ChartIndicatorDelete(m_Infos.ID, 0, szTmp); 180. Print("Only one instance is allowed..."); 181. SetUserError(C_Terminal::ERR_NoMoreInstance); 182. 183. return false; 184. } 185. IndicatorSetString(INDICATOR_SHORTNAME, szShortName); 186. 187. return true; 188. } 189. //+------------------------------------------------------------------+ 190. };
Código fonte do arquivo C_Terminal.mqh
Este código acima deverá ser colocado no lugar no antigo arquivo C_Terminal.mqh. E o código que você verá abaixo, deverá ser colocado no lugar do antigo arquivo C_Mouse.mqh.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_Terminal.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_MousePrefixName "MouseBase" + (string)GetInfoTerminal().SubWin + "_" 007. #define macro_NameObjectStudy (def_MousePrefixName + "T" + (string)ObjectsTotal(0)) 008. //+------------------------------------------------------------------+ 009. class C_Mouse : public C_Terminal 010. { 011. public : 012. enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay}; 013. enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10}; 014. struct st_Mouse 015. { 016. struct st00 017. { 018. short X_Adjusted, 019. Y_Adjusted, 020. X_Graphics, 021. Y_Graphics; 022. double Price; 023. datetime dt; 024. }Position; 025. uchar ButtonStatus; 026. bool ExecStudy; 027. }; 028. //+------------------------------------------------------------------+ 029. protected: 030. //+------------------------------------------------------------------+ 031. void CreateObjToStudy(int x, int w, string szName, color backColor = clrNONE) const 032. { 033. if (!m_OK) return; 034. CreateObjectGraphics(szName, OBJ_BUTTON); 035. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_STATE, true); 036. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BORDER_COLOR, clrBlack); 037. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, clrBlack); 038. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BGCOLOR, backColor); 039. ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_FONT, "Lucida Console"); 040. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_FONTSIZE, 10); 041. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 042. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, x); 043. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1); 044. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XSIZE, w); 045. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YSIZE, 18); 046. } 047. //+------------------------------------------------------------------+ 048. private : 049. enum eStudy {eStudyNull, eStudyCreate, eStudyExecute}; 050. struct st01 051. { 052. st_Mouse Data; 053. color corLineH, 054. corTrendP, 055. corTrendN; 056. eStudy Study; 057. }m_Info; 058. struct st_Mem 059. { 060. bool CrossHair, 061. IsFull; 062. datetime dt; 063. string szShortName, 064. szLineH, 065. szLineV, 066. szLineT, 067. szBtnS; 068. }m_Mem; 069. bool m_OK; 070. //+------------------------------------------------------------------+ 071. void GetDimensionText(const string szArg, int &w, int &h) 072. { 073. TextSetFont("Lucida Console", -100, FW_NORMAL); 074. TextGetSize(szArg, w, h); 075. h += 5; 076. w += 5; 077. } 078. //+------------------------------------------------------------------+ 079. void CreateStudy(void) 080. { 081. if (m_Mem.IsFull) 082. { 083. CreateObjectGraphics(m_Mem.szLineV = macro_NameObjectStudy, OBJ_VLINE, m_Info.corLineH); 084. CreateObjectGraphics(m_Mem.szLineT = macro_NameObjectStudy, OBJ_TREND, m_Info.corLineH); 085. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_WIDTH, 2); 086. CreateObjToStudy(0, 0, m_Mem.szBtnS = macro_NameObjectStudy); 087. } 088. m_Info.Study = eStudyCreate; 089. } 090. //+------------------------------------------------------------------+ 091. void ExecuteStudy(const double memPrice) 092. { 093. double v1 = GetInfoMouse().Position.Price - memPrice; 094. int w, h; 095. 096. if (!CheckClick(eClickLeft)) 097. { 098. m_Info.Study = eStudyNull; 099. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 100. if (m_Mem.IsFull) ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T"); 101. }else if (m_Mem.IsFull) 102. { 103. string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ", 104. MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0)); 105. GetDimensionText(sz1, w, h); 106. ObjectSetString(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_TEXT, sz1); 107. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP)); 108. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XSIZE, w); 109. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YSIZE, h); 110. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XDISTANCE, GetInfoMouse().Position.X_Adjusted - w); 111. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h)); 112. ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price); 113. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP)); 114. } 115. m_Info.Data.ButtonStatus = eKeyNull; 116. } 117. //+------------------------------------------------------------------+ 118. inline void DecodeAlls(int xi, int yi) 119. { 120. int w = 0; 121. 122. xi = (xi > 0 ? xi : 0); 123. yi = (yi > 0 ? yi : 0); 124. ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X_Graphics = (short)xi, m_Info.Data.Position.Y_Graphics = (short)yi, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price); 125. m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt); 126. m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price); 127. ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, xi, yi); 128. yi -= (int)ChartGetInteger(GetInfoTerminal().ID, CHART_WINDOW_YDISTANCE, GetInfoTerminal().SubWin); 129. m_Info.Data.Position.X_Adjusted = (short) xi; 130. m_Info.Data.Position.Y_Adjusted = (short) yi; 131. } 132. //+------------------------------------------------------------------+ 133. public : 134. //+------------------------------------------------------------------+ 135. C_Mouse(const long id, const string szShortName) 136. :C_Terminal(id), 137. m_OK(false) 138. { 139. m_Mem.szShortName = szShortName; 140. } 141. //+------------------------------------------------------------------+ 142. C_Mouse(const long id, const string szShortName, color corH, color corP, color corN) 143. :C_Terminal(id) 144. { 145. if (!(m_OK = IndicatorCheckPass(m_Mem.szShortName = szShortName))) return; 146. m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL); 147. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true); 148. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false); 149. ZeroMemory(m_Info); 150. m_Info.corLineH = corH; 151. m_Info.corTrendP = corP; 152. m_Info.corTrendN = corN; 153. m_Info.Study = eStudyNull; 154. if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE)) 155. CreateObjectGraphics(m_Mem.szLineH = (def_MousePrefixName + (string)ObjectsTotal(0)), OBJ_HLINE, m_Info.corLineH); 156. ChartRedraw(GetInfoTerminal().ID); 157. } 158. //+------------------------------------------------------------------+ 159. ~C_Mouse() 160. { 161. if (!m_OK) return; 162. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 163. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, ChartWindowFind(GetInfoTerminal().ID, m_Mem.szShortName) != -1); 164. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair); 165. ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName); 166. } 167. //+------------------------------------------------------------------+ 168. inline bool CheckClick(const eBtnMouse value) 169. { 170. return (GetInfoMouse().ButtonStatus & value) == value; 171. } 172. //+------------------------------------------------------------------+ 173. inline const st_Mouse GetInfoMouse(void) 174. { 175. if (!m_OK) 176. { 177. double Buff[]; 178. uCast_Double loc; 179. int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName); 180. 181. ZeroMemory(m_Info.Data); 182. if (CopyBuffer(handle, 0, 0, 1, Buff) == 1) 183. { 184. loc.dValue = Buff[0]; 185. m_Info.Data.ButtonStatus = loc._8b[0]; 186. DecodeAlls((int)loc._16b[1], (int)loc._16b[2]); 187. } 188. IndicatorRelease(handle); 189. } 190. 191. return m_Info.Data; 192. } 193. //+------------------------------------------------------------------+ 194. inline void SetBuffer(const int rates_total, double &Buff[]) 195. { 196. uCast_Double info; 197. 198. info._8b[0] = (uchar)(m_Info.Study == C_Mouse::eStudyNull ? m_Info.Data.ButtonStatus : 0); 199. info._16b[1] = (ushort) m_Info.Data.Position.X_Graphics; 200. info._16b[2] = (ushort) m_Info.Data.Position.Y_Graphics; 201. Buff[rates_total - 1] = info.dValue; 202. } 203. //+------------------------------------------------------------------+ 204. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 205. { 206. int w = 0; 207. static double memPrice = 0; 208. 209. if (m_OK) 210. { 211. C_Terminal::DispatchMessage(id, lparam, dparam, sparam); 212. switch (id) 213. { 214. case (CHARTEVENT_CUSTOM + evHideMouse): 215. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, clrNONE); 216. break; 217. case (CHARTEVENT_CUSTOM + evShowMouse): 218. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, m_Info.corLineH); 219. break; 220. case CHARTEVENT_MOUSE_MOVE: 221. DecodeAlls((int)lparam, (int)dparam); 222. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineH, 0, 0, m_Info.Data.Position.Price); 223. if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineV, 0, m_Info.Data.Position.dt, 0); 224. m_Info.Data.ButtonStatus = (uchar) sparam; 225. if (CheckClick(eClickMiddle)) 226. if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy(); 227. if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate)) 228. { 229. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 230. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price); 231. m_Info.Study = eStudyExecute; 232. } 233. if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice); 234. m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute; 235. break; 236. case CHARTEVENT_OBJECT_DELETE: 237. if ((m_Mem.IsFull) && (sparam == m_Mem.szLineH)) 238. CreateObjectGraphics(m_Mem.szLineH, OBJ_HLINE, m_Info.corLineH); 239. break; 240. } 241. } 242. } 243. //+------------------------------------------------------------------+ 244. }; 245. //+------------------------------------------------------------------+ 246. #undef macro_NameObjectStudy 247. //+------------------------------------------------------------------+
Código fonte do arquivo C_Mouse.mqh
Feita estas mudanças vamos ver o que temos de fazer. Pois coisas estranhas as vezes acontecem. E você aspirante a programador, deve entender que as vezes, não será possível de fato entender o que está acontecendo. Isto por que coisas estranhas as vezes acontecem. Por conta disto estou mostrando, cada uma das modificações que estão acontecendo, para que você consiga ver que nem tudo está sobre o seu controle.
Um novo indicador de mouse, surge
Neste primeiro momento, temos que fazer uma mudança bastante inusitada por muitos. Precisamos fazer com que o indicador de mouse, consiga trabalhar com todos os eventos que existiria no mercado real, mas dentro do replay/simulação. Você pode estar pensando: Mas isto já não está acontecendo? Não. Infelizmente, até o presente momento, eu havia isolado o sistema de replay/simulação, do sistema onde estaríamos conectados ao servidor real. Isto fosse em uma conta demo ou conta real. Então haveria dois modos de operação do indicador de mouse. Um onde ele usaria os eventos de book e outro onde tais eventos seriam ignorados.
A grande questão é justamente esta: Os eventos de book. Só que estes eventos são um pouco mais complicados do que você pode estar achando. Não é somente usar a função CustomBookAdd, que faz parte da biblioteca de funções do MQL5, que tudo estaria resolvido. A coisa em si, é um pouco mais complicada para nós, aqui no replay/simulação. Mas não precisa ficar tenso ou com receio. Iremos de fato chegar neste ponto, onde explicarei como fazer uso da função CustomBookAdd. Mas vamos com calma. Uma coisa de cada vez.
Como você deve ter visto no tópico anterior. Tanto o código fonte do arquivo de cabeçalho C_Terminal.mqh, quanto o do arquivo C_Mouse.mqh foram modificados. Apesar de eu não entrar em detalhes sobre as mudanças que aconteceram, você tem que saber que de fato houve mudanças. Mas por elas serem relativamente simples de serem compreendidas, não vejo necessidade de entrar em detalhes sobre elas. Mas da mesma forma como aconteceu mudanças ali, temos também mudanças também no arquivo de cabeçalho C_Study.mqh. O código na íntegra deste arquivo pode ser visto logo abaixo.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\C_Mouse.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_" 007. //+------------------------------------------------------------------+ 008. class C_Study : public C_Mouse 009. { 010. private : 011. //+------------------------------------------------------------------+ 012. struct st00 013. { 014. eStatusMarket Status; 015. MqlRates Rate; 016. string szInfo, 017. szBtn1, 018. szBtn2, 019. szBtn3; 020. color corP, 021. corN; 022. int HeightText; 023. bool bvT, bvD, bvP; 024. }m_Info; 025. //+------------------------------------------------------------------+ 026. void Draw(void) 027. { 028. double v1; 029. 030. if (m_Info.bvT) 031. { 032. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18); 033. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_TEXT, m_Info.szInfo); 034. } 035. if (m_Info.bvD) 036. { 037. v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2); 038. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 039. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 040. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 041. } 042. if (m_Info.bvP) 043. { 044. v1 = NormalizeDouble((((GL_PriceClose - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2); 045. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 046. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 047. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 048. } 049. } 050. //+------------------------------------------------------------------+ 051. inline void CreateObjInfo(EnumEvents arg) 052. { 053. switch (arg) 054. { 055. case evShowBarTime: 056. C_Mouse::CreateObjToStudy(2, 110, m_Info.szBtn1 = (def_ExpansionPrefix + (string)ObjectsTotal(0)), clrPaleTurquoise); 057. m_Info.bvT = true; 058. break; 059. case evShowDailyVar: 060. C_Mouse::CreateObjToStudy(2, 53, m_Info.szBtn2 = (def_ExpansionPrefix + (string)ObjectsTotal(0))); 061. m_Info.bvD = true; 062. break; 063. case evShowPriceVar: 064. C_Mouse::CreateObjToStudy(58, 53, m_Info.szBtn3 = (def_ExpansionPrefix + (string)ObjectsTotal(0))); 065. m_Info.bvP = true; 066. break; 067. } 068. } 069. //+------------------------------------------------------------------+ 070. inline void RemoveObjInfo(EnumEvents arg) 071. { 072. string sz; 073. 074. switch (arg) 075. { 076. case evHideBarTime: 077. sz = m_Info.szBtn1; 078. m_Info.bvT = false; 079. break; 080. case evHideDailyVar: 081. sz = m_Info.szBtn2; 082. m_Info.bvD = false; 083. break; 084. case evHidePriceVar: 085. sz = m_Info.szBtn3; 086. m_Info.bvP = false; 087. break; 088. } 089. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 090. ObjectDelete(GetInfoTerminal().ID, sz); 091. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 092. } 093. //+------------------------------------------------------------------+ 094. public : 095. //+------------------------------------------------------------------+ 096. C_Study(long IdParam, string szShortName, color corH, color corP, color corN) 097. :C_Mouse(IdParam, szShortName, corH, corP, corN) 098. { 099. if (_LastError >= ERR_USER_ERROR_FIRST) return; 100. ZeroMemory(m_Info); 101. m_Info.corP = corP; 102. m_Info.corN = corN; 103. CreateObjInfo(evShowBarTime); 104. CreateObjInfo(evShowDailyVar); 105. CreateObjInfo(evShowPriceVar); 106. ResetLastError(); 107. } 108. //+------------------------------------------------------------------+ 109. void Update(const eStatusMarket arg) 110. { 111. int i0; 112. datetime dt; 113. 114. if (m_Info.Rate.close == 0) 115. m_Info.Rate.close = iClose(NULL, PERIOD_D1, ((_Symbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(NULL, PERIOD_D1, 0))) ? 0 : 1)); 116. switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status)) 117. { 118. case eCloseMarket : 119. m_Info.szInfo = "Closed Market"; 120. break; 121. case eInReplay : 122. case eInTrading : 123. i0 = PeriodSeconds(); 124. dt = (m_Info.Status == eInReplay ? (datetime) GL_TimeAdjust : TimeCurrent()); 125. m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time); 126. if (dt > 0) m_Info.szInfo = TimeToString((datetime)m_Info.Rate.time - dt, TIME_SECONDS); 127. break; 128. case eAuction : 129. m_Info.szInfo = "Auction"; 130. break; 131. default : 132. m_Info.szInfo = "ERROR"; 133. } 134. Draw(); 135. } 136. //+------------------------------------------------------------------+ 137. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 138. { 139. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 140. switch (id) 141. { 142. case CHARTEVENT_CUSTOM + evHideBarTime: 143. RemoveObjInfo(evHideBarTime); 144. break; 145. case CHARTEVENT_CUSTOM + evShowBarTime: 146. CreateObjInfo(evShowBarTime); 147. break; 148. case CHARTEVENT_CUSTOM + evHideDailyVar: 149. RemoveObjInfo(evHideDailyVar); 150. break; 151. case CHARTEVENT_CUSTOM + evShowDailyVar: 152. CreateObjInfo(evShowDailyVar); 153. break; 154. case CHARTEVENT_CUSTOM + evHidePriceVar: 155. RemoveObjInfo(evHidePriceVar); 156. break; 157. case CHARTEVENT_CUSTOM + evShowPriceVar: 158. CreateObjInfo(evShowPriceVar); 159. break; 160. case CHARTEVENT_MOUSE_MOVE: 161. Draw(); 162. break; 163. } 164. ChartRedraw(GetInfoTerminal().ID); 165. } 166. //+------------------------------------------------------------------+ 167. }; 168. //+------------------------------------------------------------------+ 169. #undef def_ExpansionPrefix 170. #undef def_MousePrefixName 171. //+------------------------------------------------------------------+
Código fonte do arquivo C_Study.mqh
Já que diferente dos outros arquivos de cabeçalho, vistos no tópico anterior, que não terão tanto peso, o que será implementado. Aqui temos uma situação um pouco diferente.
Observe que o constructor da classe C_Study, passou por um processo de limpeza de seu código. Tivemos na linha 99 a mudança do valor de teste, isto para evitar que erros não oriundos do código, faça o mesmo ser encerrado de forma prematura. Mas existe uma outra questão. Observe que agora não estamos mais inicializando o Rate.close aqui no constructor. Esta inicialização passou a ser feita em um outro ponto do código. Veja na linha 114, que estamos testando se o valor está zerado. Se este for o caso, na linha 115, fazemos a captura do valor de fechamento, assim como era feito antes.
Esta mudança se deve ao fato de que, quando o indicador estiver em um template e for colocado no gráfico por este. As vezes acontecia, de que o Rate.close ficava com um valor errado. Isto quando estávamos iniciando no constructor. De certa forma não consegui entender por que de isto passou a acontecer. Mas o fato é que depois das mudanças feitas no código. Começamos a ter de forma randômica uma inicialização errática do valor Rate.close. Para reduzir esta possibilidade e os efeitos, mudei o ponto de inicialização.
Agora tem um detalhe: O teste da linha 114, garantidamente será verdadeiro, quando estivermos executado a linha 100. Pois esta linha 100, fará com que todos os valores da estrutura m_Info sejam zerados. Mas mesmo que o código esteja na íntegra, estes foram os pontos que ao meu ver mereciam algum tipo de explicação. Todo o restante do código permaneceu praticamente inalterado. Muito bem. Agora, vamos ver as mudanças que aconteceram no código do indicador de mouse. Então o código do indicador, pode ser visto na integra logo abaixo:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "This is an indicator for graphical studies using the mouse." 04. #property description "This is an integral part of the Replay / Simulator system." 05. #property description "However it can be used in the real market." 06. #property version "1.70" 07. #property icon "/Images/Market Replay/Icons/Indicators.ico" 08. #property link "https://www.mql5.com/pt/articles/12326" 09. #property indicator_chart_window 10. #property indicator_plots 0 11. #property indicator_buffers 1 12. //+------------------------------------------------------------------+ 13. double GL_PriceClose; 14. datetime GL_TimeAdjust; 15. //+------------------------------------------------------------------+ 16. #include <Market Replay\Auxiliar\Study\C_Study.mqh> 17. //+------------------------------------------------------------------+ 18. C_Study *Study = NULL; 19. //+------------------------------------------------------------------+ 20. input color user02 = clrBlack; //Price Line 21. input color user03 = clrPaleGreen; //Positive Study 22. input color user04 = clrLightCoral; //Negative Study 23. //+------------------------------------------------------------------+ 24. C_Study::eStatusMarket m_Status; 25. int m_posBuff = 0; 26. double m_Buff[]; 27. //+------------------------------------------------------------------+ 28. int OnInit() 29. { 30. Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04); 31. if (_LastError >= ERR_USER_ERROR_FIRST) return INIT_FAILED; 32. MarketBookAdd((*Study).GetInfoTerminal().szSymbol); 33. OnBookEvent((*Study).GetInfoTerminal().szSymbol); 34. m_Status = C_Study::eCloseMarket; 35. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 36. ArrayInitialize(m_Buff, EMPTY_VALUE); 37. 38. return INIT_SUCCEEDED; 39. } 40. //+------------------------------------------------------------------+ 41. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], 42. const double& high[], const double& low[], const double& close[], const long& tick_volume[], 43. const long& volume[], const int& spread[]) 44. { 45. GL_PriceClose = close[rates_total - 1]; 46. if (_Symbol == def_SymbolReplay) 47. GL_TimeAdjust = spread[rates_total - 1] & (~def_MaskTimeService); 48. m_posBuff = rates_total; 49. (*Study).Update(m_Status); 50. 51. return rates_total; 52. } 53. //+------------------------------------------------------------------+ 54. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 55. { 56. (*Study).DispatchMessage(id, lparam, dparam, sparam); 57. (*Study).SetBuffer(m_posBuff, m_Buff); 58. 59. ChartRedraw((*Study).GetInfoTerminal().ID); 60. } 61. //+------------------------------------------------------------------+ 62. void OnBookEvent(const string &symbol) 63. { 64. MqlBookInfo book[]; 65. C_Study::eStatusMarket loc = m_Status; 66. 67. if (symbol != (*Study).GetInfoTerminal().szSymbol) return; 68. MarketBookGet((*Study).GetInfoTerminal().szSymbol, book); 69. m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : (symbol == def_SymbolReplay ? C_Study::eInReplay : C_Study::eInTrading)); 70. for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++) 71. if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction; 72. if (loc != m_Status) (*Study).Update(m_Status); 73. } 74. //+------------------------------------------------------------------+ 75. void OnDeinit(const int reason) 76. { 77. MarketBookRelease((*Study).GetInfoTerminal().szSymbol); 78. 79. delete Study; 80. } 81. //+------------------------------------------------------------------+
Código fonte do Indicador de Mouse
Ok. Veja que o código do indicador de mouse, está agora bem diferente. Na verdade, está muito diferente, do que havia antes. Estas mudanças, tem motivo de terem ocorrido. Mesmo que você não consiga, de fato compreender integralmente todas elas. Elas visam forçar o indicador de mouse, a trabalhar de uma forma mais genérica. Mas ao mesmo tempo igualitária em qualquer tipo de gráfico ou ativo. Vamos então entender as mudanças feitas aqui.
Primeiramente temos a função de inicialização OnInit. Se você observar, notará que ela está diferente do que estava na versão anterior. Aqui temos que presta atenção a alguns detalhes. O primeiro deles é o fato de que na linha 31 estamos verificando se a variável de erro, foi setada por alguma chamada que fizemos. Antes qualquer erro fazia com que a inicialização viesse a reportar INIT_FAILED. Mas então por que mudei isto? O motivo é que as vezes, o indicador durante a sua inicialização reportava algum erro, que não fazia nenhum sentido. Mas já que o constructor da classe não nos permite retornar nenhum valor. Eu estava fazendo uso da variável _LastError para este propósito. No entanto, isto se mostrou algo não tão viável. Assim decidi manter a mesma metodologia, mas filtrando os erros para algo que fosse informado ao usar a chamada SetUserError. Este tipo de erro não será muito frequente, mas terá importância para nós, a fim de verificarmos se os constructores fizeram o seu trabalho corretamente.
Uma vez feito isto, temos algo que antes não era feito. Dizemos ao MetaTrader 5, que desejamos receber eventos de book. Atenção: Antes os eventos de book, somente aconteciam, quando os dados do gráfico, pertencia a um ativo real. Ou seja, quando estávamos ligados de alguma forma ao servidor de negociação. Agora o que acontece, é que sempre e sempre, estaremos de fato, recebendo eventos do book. Mesmo que o ativo seja customizado. Isto é importante para nós. De fato, quero e pretendo usar o mecanismo já construído, a fim de informar ao usuário que o ativo se encontra em leilão. Isto torna todo o trabalho bem mais simples, já que a interface será a mesma, não precisando ser de alguma forma adaptada. Tudo que teremos e precisaremos fazer, será repassar os dados, de maneira correta para o indicador. Ele fará todo restante.
Mas veja que na linha 34, iniciamos sempre com o valor responsável por informar que o mercado está fechado. Logo depois nas linhas 35 e 36 iniciamos o buffer para os dados do indicador de mouse. Coisa simples e que já vinha sendo feita. Note que a diferença aqui foi justamente e somente na questão do MetaTrader 5, ser informado que desejamos receber sempre eventos de book. Independentemente do ativo que estiver no gráfico.
Vamos pular a função OnCalcule, por enquanto e deixar para explicar ela mais para o final. Isto por que, você já deve ter notado, que ela não está mais fazendo uso da chamada iSpread. Então vamos avançar um pouco e ir para a linha 75, onde se encontra a função OnDeInit. Já que iremos receber eventos do book, e isto para qualquer ativo. Precisamos informar ao MetaTrader 5, que em um dado momento, não queremos mais receber eventos de book. Isto é feito na linha 77. E como estamos removendo o indicador do gráfico, na linha 79, usamos o operador delete para devolver a memória de volta ao sistema.
Agora vamos ver, o procedimento de tratamento de mensagens do book. Isto será feito, na linha 62, onde temos a rotina OnBookEvent. Este procedimento, tende a ser o mais simples quanto for possível fazer. Não estamos interessados em analisar, o volume ou a quantidade de ordens presentes no book. O que realmente queremos fazer aqui, é saber se o ativo está em negociação, ou se ele está em leilão. Mas também reportar se o mercado se encontra fechado. Para saber este tipo de coisa, apenas precisamos observar o que está acontecendo no book.
Então a linha 67, filtra as chamadas com relação ao ativo onde o indicador se encontra. Isto por que o MetaTrader 5 não faz tal filtragem, ele simplesmente lança, qualquer evento de book, a todos os gráficos que o requisitaram. Por isto, não é bom ficar observando diversos books, de ativos que não nos interessa. Fazer isto, fará com que o Metatrader 5, dispare eventos que não nos interessa, mas que irão de alguma forma tomar nosso tempo. Uma vez filtrado, temos na linha 68, a chamada de biblioteca do MQL5, que faz a captura dos dados mais recentes do book. Isto para que o procedimento que estamos implementando, consiga fazer o seu trabalho.
Agora vem a parte realmente interessante e principalmente útil, deste procedimento OnBookEvent. Observe com atenção a linha 69. Se o array com dados do book estiver vazio, isto significa que o mercado está fechado. Agora caso exista alguma informação no array, teremos que fazer uma nova verificação. Mas por que desta nova verificação? O motivo é simples. Saber qual será a origem dos dados que iremos de fato analisar. Isto no arquivo de cabeçalho C_Study.mqh. Você pode verificar isto, observando o código fonte do arquivo C_Study.mqh mostrado um pouco acima, onde é possível ver o procedimento Update. Observe que na linha 124, iremos fazer uso de uma chamada TimeCurrent, caso estejamos em um ativo ligado ao servidor de negociação. Ou usaremos um valor que se encontra em uma variável global, caso estejamos em um ativo usado no replay/simulação. Este é o motivo para o teste na linha 69 do código fonte do indicador de mouse. Sem este teste, que nos indica qual a origem dos dados, poderíamos ficar a mercê de fazer uso de dados incorretos. E isto faria todo o trabalho desandar completamente. Já uma vez definido o status, primário do ativo. Precisamos saber o real estado dele. Isto é feito na linha 70, onde entramos em um laço, a fim de procurar a correspondência de um determinado tipo de informação dentro do book. A informação que estamos procurando, é bem simples e isto é feito na linha 71. Onde verificamos a presença ou não da constante BOOK_TYPE_BUY_MARKET ou da constante BOOK_TYPE_SELL_MARKET. A presença de qualquer uma destas duas constantes, indicará que o mercado se encontra aberto, porém o ativo está em leilão.
Notaram que com uma simples medida, lá no procedimento OnInit, onde começarmos a observar o book de qualquer ativo, onde o indicador se encontra, tornou possível informar o status atual do ativo. Este tipo de coisa é que de fato torna todo o trabalho de implementação algo bastante interessante de ser feito.
Agora podemos voltar a nossa atenção ao código na linha 41. Ou seja, o procedimento OnCalculate. A primeira pergunta que você deve estar se fazendo é: Por que este código foi modificado mais uma vez? Por um acaso entre o artigo anterior e este, ocorreu alguma nova atualização no MetaTrader 5? Bem, na verdade não. Lembre-se do seguinte meu caro leitor. Estes artigos foram escritos a muito tempo. Porém só agora o conteúdo veio a se tornar publico.
Mas o resultado de uma execução, pode ser vista no vídeo abaixo. Onde demonstro o que está de fato acontecendo. Antes de ver a explicação veja o vídeo. Isto com toda a certeza ajudará, a você entender a explicação que será dada em seguida.
Vídeo de demonstração.
Se você viu o vídeo, deve ter notado que estou fazendo as coisas de uma maneira bem específica. Não se preocupe por hora, com o código do serviço. Este será visto em outro momento. Por enquanto, vamos manter o foco aqui no indicador de mouse.
Primeiro, eu inicializei o serviço. Este ficou na espera, a fim de detectar que o indicador de mouse, viesse a ser colocado no gráfico. Assim que ele foi colocado, você pode observar que o mostrador, indicava uma contagem regressiva, cujo valores estavam sendo mostrados também na caixa de ferramentas do MetaTrader 5. Isto para podermos conferir se o indicador de mouse, estava de fato conseguindo acessar os valores postados pelo serviço.
Note o seguinte fato: No artigo anterior, em um dado momento, o serviço continuava a enviar dados para o indicador de mouse. Porém este não conseguia de fato nos informar, a temporização baseada nos valores que o serviço nos informava. Isto por que o mostrador simplesmente congelava. Agora note que ele congelava, quando usávamos o mesmo código, que estamos usando neste momento no procedimento OnCalculate. Sendo que para evitar que ele congelasse, foi feito o uso da chamada iSpread. Mas aqui não estamos usando esta chamada, e mesmo assim o mostrador no indicador não congelou. Por que motivo isto agora funcionou e não funcionou no artigo passado? Sinceramente não sei responder esta questão. Esta é uma daquelas questões, que somente quem realmente experimenta e testa as coisas, no seu limite irá de fato conseguir ter contato.
Estou aqui mostrando isto, a você, caro leitor. Para lhe mostrar que nem sempre, nos programadores temos de fato a resposta para tudo. Existem coisas das quais, muitas das vezes você tem contato, mas que não sabe por que e como se chegou naquele resultado. Ao meu ver, mostrar este tipo de coisa, a você, torna tanto o aprendizado, quanto a minha própria experiência, algo bem mais agradável. Pois você poderá aprender que nem sempre os objetivos são alcançados logo no começo. E para mim, aprendo como realmente cada uma das menores partes do MetaTrader 5 funciona. E até onde posso me meter nele, sem que isto cause uma pane geral na plataforma. Então para encerrar este artigo, vamos dar uma passada no código fonte do serviço que foi utilizado no vídeo.
Serviço de testagem
Normalmente, as pessoas têm, dificuldades em aprender a programar, pelo fato de não entenderem que:
A ORDEM DOS FATORES ALTERA O RESULTADO
Mas quando o assunto é programação, isto se aplica na grande maioria das vezes. Raramente podemos programar algo, sem nos preocupar com a ordem em que os eventos acontecem. Mas aqui no serviço de testagem, a citação acima é a mais pura verdade. Então vamos ver o código fonte do serviço. Este pode ser visto na íntegra logo abaixo.
01. //+------------------------------------------------------------------+ 02. #property service 03. #property copyright "Daniel Jose" 04. #property description "Data synchronization demo service." 05. //+------------------------------------------------------------------+ 06. #include <Market Replay\Defines.mqh> 07. #include <Market Replay\Auxiliar\Macros.mqh> 08. //+------------------------------------------------------------------+ 09. #define def_Loop ((!_StopFlag) && (ChartSymbol(id) != "")) 10. //+------------------------------------------------------------------+ 11. void OnStart() 12. { 13. long id; 14. int time; 15. MqlRates Rate[1]; 16. MqlBookInfo book[1]; 17. 18. Print("Starting Test Service..."); 19. SymbolSelect(def_SymbolReplay, false); 20. CustomSymbolDelete(def_SymbolReplay); 21. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay)); 22. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0.5); 23. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 5); 24. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TICKS_BOOKDEPTH, 1); 25. Rate[0].close = 105; 26. Rate[0].open = 100; 27. Rate[0].high = 110; 28. Rate[0].low = 95; 29. Rate[0].tick_volume = 5; 30. Rate[0].spread = 1; 31. Rate[0].real_volume = 10; 32. Rate[0].time = D'14.03.2023 08:30'; 33. CustomRatesUpdate(def_SymbolReplay, Rate, 1); 34. Rate[0].time = D'14.03.2023 09:00'; 35. CustomRatesUpdate(def_SymbolReplay, Rate, 1); 36. SymbolSelect(def_SymbolReplay, true); 37. id = ChartOpen(def_SymbolReplay, PERIOD_M30); 38. 39. Sleep(1000); 40. 41. Print("Waiting for Mouse Indicator..."); 42. while ((def_Loop) && (ChartIndicatorGet(id, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200); 43. 44. book[0].type = BOOK_TYPE_BUY; 45. book[0].price = Rate[0].close; 46. book[0].volume = 1; 47. CustomBookAdd(def_SymbolReplay, book, 1); 48. 49. Print(TimeToString(Rate[0].time, TIME_DATE | TIME_SECONDS)); 50. time = (int)macroGetTime(Rate[0].time); 51. while (def_Loop) 52. { 53. Rate[0].spread = (int)(def_MaskTimeService | time); 54. CustomRatesUpdate(def_SymbolReplay, Rate, 1); 55. Sleep(250); 56. time++; 57. Print(TimeToString(time, TIME_SECONDS), " >> (int):", time); 58. switch (time) 59. { 60. case 32430: 61. book[0].type = BOOK_TYPE_BUY_MARKET; 62. CustomBookAdd(def_SymbolReplay, book, 1); 63. break; 64. case 32460: 65. book[0].type = BOOK_TYPE_BUY; 66. CustomBookAdd(def_SymbolReplay, book, 1); 67. break; 68. } 69. } 70. ChartClose(id); 71. SymbolSelect(def_SymbolReplay, false); 72. CustomSymbolDelete(def_SymbolReplay); 73. Print("Finished Test Service..."); 74. } 75. //+------------------------------------------------------------------+
Código fonte do Serviço de teste
Observe que o código, é praticamente igual, ao que foi visto no artigo anterior, para fins de testar o indicador de mouse. Mas aqui temos algumas coisas que são novidades. E é justamente estas coisas que são novidades, é que causaram todo o processo que você pode ver no vídeo. Cada uma das novas linhas aqui tem um motivo e um significado. A maior parte dos que tentam fazer uso do book, em um ativo customizado, fracassam justamente por não entenderem o que deve ser feito. Isto por conta que eles se atentam a apenas no uso da chamada CustomBookAdd, que pode ser vista nas seguintes linhas: 47, 62 e 64. Mas não é assim que a banda toca. Tentar passar dados para o book em um ativo customizado, usando apenas a chamada de biblioteca CustomBookAdd, não irá de forma e maneira alguma funcionar. Isto se não tomarmos algumas medidas antes. E mesmo estas medidas, tem que ser pensadas, para que não venhamos a fazer bobagens.
A principal medida a ser tomada é vista na linha 24. Sem isto a função CustomBookAdd é completamente inútil para nós. Mas espere um pouco. Como assim? A função CustomBookAdd não serve justamente para enviar dados para o book? Assim como seria feito pelas funções de Tick e Rate? A resposta a todas estas questões é um sim e ao mesmo tempo não. Parece contraditório, mas a função CustomBookAdd somente terá algum efeito, se você definir um valor para SYMBOL_TICKS_BOOKDEPTH. O valor a ser utilizado em SYMBOL_TICKS_BOOKDEPTH, dependerá do que desejamos fazer. Aqui como o que desejamos é apenas orientar o indicador de mouse, a fim de que este possa informar ao usuário o status do ativo customizado. Precisaremos de apenas uma única posição. Por conta disto, o valor que estou utilizando na chamada da linha 24, a fim de definir o SYMBOL_TICKS_BOOKDEPTH é de um. Mas se você desejar criar um book artificial para colocar como uma forma fazer estudos, seja de simulação ou replay. Bastará mudar o valor de um para outro que lhe atenda.
De qualquer modo esta é a parte básica. Somente depois de definir SYMBOL_TICKS_BOOKDEPTH, poderemos realmente receber os dados que estarão na estrutura MqlBookInfo. Esta questão foi um pouco complicada, para que eu pudesse entender. Já que não encontrei nenhuma referência explicando como lançar a estrutura MqlBookInfo para um ativo customizado. Todas as referências apenas diziam para usar CustomBookAdd. No entanto, apenas e somente a documentação falava sobre SYMBOL_TICKS_BOOKDEPTH. Mas não fazia a ligação entre esta definição e o fato você poder acessar os dados da estrutura MqlBookInfo, que foram postados pela CustomBookAdd.
De qualquer modo, este serviço servirá apenas para testar o envio dos dados. Não se prestará a nenhuma outra coisa. Mas antes de fechar este artigo. Quero explicar de forma rápida o que acontece aqui. Entre as linhas 44 e 46, definimos uma posição do book. O mais importante aqui, para nós, é a linha 44. Na linha 47, informamos ao MetaTrader 5, para gerar um evento e colocar os dados de forma a ficarem disponíveis como sendo o book. Agora preste atenção ao seguinte fato: Na linha 50, faremos a captura do tempo. Na linha 56 iremos incrementar o tempo em um segundo. Pela definição da linha 34, o valor da variável time inicial será de 32400. Isto é dado em segundos. Não se esqueça deste fato. O tempo é dado em segundos. Então na linha 58, faremos um teste, este fará a verificação do ponto em que estamos. Quando avançarmos 30 segundos no tempo, mandaremos um novo valor para o book, isto na linha 62. O valor a ser enviado é o mostrado na linha 61. Tal valor irá de fato fazer com que a informação mostrada pelo indicador de mouse, seja a de que o ativo se encontra em leilão. Passados mais 30 segundos, o teste fará com que a linha 64 seja verdadeira, então o valor a ser enviado para o book será o da linha 65 via execução da linha 66. Isto irá, fazer com que o tempo volte a ser mostrado no indicador de mouse.
Um último detalhe: O tempo não corresponde a exatamente um segundo, já que na linha 55, estamos fazendo com que o delay, seja de um quarto de segundo.
Considerações finais
Neste artigo, apresentei a você a forma de repassar valores para o book em um ativo customizado. Apesar do fato, de que o serviço usado serve apenas e somente para testar este meio de passagem de valores. Ficou provado, que podemos fazer isto, de uma maneira bastante razoável. Apesar das dificuldades iniciais, podemos realmente fazer isto. Então sendo assim, agora você tem mais uma forma de transferir informações entre o serviço e um indicador ou Expert Advisor. Isto usando o book. Mas você deve sempre se lembrar, que tudo tem um custo. E usar o book de forma indiscriminada, terá um custo a ser pensado.
De qualquer modo, este serviço usado para os testes não reflete de fato o que faremos no próximo artigo. Onde iremos, fazer a aplicação de replay/simulador nos informar quando o ativo está em leilão, ou não. Este sistema é ou não é muito interessante? Então nos vemos no próximo artigo.
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.





- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso