
开发回放系统(第 70 部分):取得正确的时间(三)
概述
在上一篇文章 “开发回放系统(第 69 部分):取得正确的时间(二) ” 中,我们已经了解了即使在交易品种流动性较低时如何显示柱形的剩余时间。低流动性是指在特定时刻没有执行交易。这可能是由于各种原因造成的。然而,我们没有必要解释发生这种情况的具体原因。我们所要做的就是找到一种适当处理这种情况的方法。
然而,我们仍然面临着一个需要解决的问题。可以说,这个问题相当烦人和复杂,不是因为解决方案中涉及的编程,而是因为我们如何确定它何时发生以及我们应该如何处理它。这个问题被称为竞价。
一般来说,竞价都是在非常特殊的情况下产生的。它们不会任意或随机发生。事实上,竞价有非常明确和严格的规则。但对于我们来说,在回放/模拟系统的开发中,真正重要的是:我们如何通知用户该资产已进入竞价?这是我们需要解决的主要也是唯一的问题。幸运的是,正如我之前提到的,解决方案已经存在 —— 它在鼠标指标中实现。但是,我们需要对代码做一些修改,以获得更大的灵活性。这将使我们能够表明在回放或模拟中使用的自定义资产已进入竞价。
很好,这是容易的部分,困难的部分是确定回放/模拟器系统应如何声明自定义资产已进入竞价状态。这确实很困难。
过去,当这个回放/模拟应用程序仍处于开发的早期阶段时,我们使用了以下规则:如果交易报价之间的间隔为 60 秒或更长时间,我们应该将其解释为竞价。我知道这不是最好的解决方案,因为竞价发生的原因非常具体。但我不想通过添加分析分时报价运动来检测资产何时应该进入竞价状态的功能而使系统复杂化。这样做实际上会使系统复杂性达到我认为不合理和可管理的程度。如果我们实现了这一点,我们基本上是在构建一个能够 “感知” 市场的应用程序,以检测何时发生了异常情况。这不是我的本意。当然,您可以使用这个回放/模拟器系统自己研究和开发这样的功能,但我不会详细介绍如何实现这一点。
因此,我们将保持早期开发的原始想法:如果资产 60 秒或更长时间没有发生交易,我们将使用鼠标指标通知用户该资产已进入竞价状态。就这么简单。这种方法让我们专注于代码。那么,让我们从实现开始吧。
有趣的事情
编程中有一些东西只有真正开发软件的人才能真正理解。我不知道它们是如何或为什么发生的,我甚至不再试图理解它们。作为一名专业程序员二十多年后,我已经不再试图理解某些事情。我只是接受有时候事情并没有按预期进行,然后我继续前进,接受我无法控制某些情况。所以,不要试图弄清楚发生了什么。只需使用我将在下面提供的新代码更新文件。不要问我发生了什么,或者代码是如何工作的。如果我尝试解释,你可能会认为我疯了。话虽如此,下面完整地提供了要使用的新代码。沒有附件。只需开始使用此新代码。
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_Terminal.mqh 源代码
上面显示的代码应该替换旧的 C_Terminal.mqh 文件。下面的代码应该替换旧的 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_Mouse.mqh 文件的源代码
做出这些修改之后,让我们看看接下来需要做什么。因为,尽管听起来很奇怪,但意想不到的事情有时会发生。作为一名有抱负的程序员,你需要明白,有时你根本无法弄清楚发生了什么。这是因为奇怪的事情确实不时发生。这就是为什么我向你展示正在进行的每一项更改,这样你就可以亲眼看到并非一切都在你的控制范围内。
新的鼠标指标出现
在这个阶段,我们需要对许多开发人员做出一个相当不寻常的改变。我们需要启用鼠标指标来处理真实市场中存在的所有事件,但在回放/模拟的背景下。您可能会想:“但它不是已经这样做了?”不,不幸的是,到目前为止,我已经将回放/模拟系统与连接到真实服务器的系统隔离开来,无论是在模拟账户还是真实账户上。因此,鼠标指标有两种操作模式:一种是使用订单簿事件,另一种是忽略这些事件。
这里最大的问题围绕着订单簿事件。然而,这些事件比你想象的要复杂一些。它不仅仅是调用 CustomBookAdd 函数(MQL5 标准库的一部分)并期望一切正常工作的问题。对于我们在回放/模拟环境中工作的情况来说,情况有点复杂。但没有必要担心。我们一定会达到解释如何使用 CustomBookAdd 函数的程度。我们不慌不忙地行动吧,让我们一步一步来吧。
正如您可能在上一节中注意到的那样,C_Terminal.mqh 头文件和 C_Mouse.mqh 文件的源代码都已被修改。尽管我没有详细介绍这些更改,但您应该知道已经做出了改变。然而,由于它们相对容易理解,我认为没有必要详细介绍它们。正如这些文件有所更改一样,C_Study.mqh 头文件也发生了更改。该文件的完整代码如下所示。
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_Study.mqh 文件源代码
与上一节讨论的其他头文件不同,这里将实现的代码具有更大的权重,情况也有所不同。
请注意,C_Study 类的构造函数已经过代码清理过程。在第 99 行中,我们更改了测试值,以防止非源于代码的错误过早终止执行。然而,还有一点值得一提。请注意,我们不再在构造函数中初始化 Rate.close。现在,此初始化在代码的不同位置执行。看第 114 行,我们检查值是否为 0。如果是这种情况,我们会在第 115 行获取收盘价,就像我们之前所做的那样。
之所以做出此更改,是因为当指标被放置在模板中然后添加到图表中时,如果我们在构造函数中初始化 Rate.close,它有时会得到不正确的值。老实说,我无法完全理解为什么会发生这种情况。但事实是,在对代码进行更改后,我们开始遇到 Rate.close 的随机不稳定初始化。为了降低此问题的可能性和影响,我移动了初始化点。
现在,有一个重要的细节:当我们执行第 100 行时,第 114 行中的检查将始终返回 true,因为第 100 行将 m_Info 结构中的所有值重置为 0。然而,尽管整个代码都是完整的,但我认为这些是值得解释的。其余代码几乎保持不变。好吧,现在让我们继续讨论鼠标指标代码所做的更改。完整代码如下所示。
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/ru/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. //+------------------------------------------------------------------+
鼠标指标源代码
如您所见,鼠标指标代码现在已经非常不同了。事实上,它与以前完全不同了。这些变化是有原因的。即使你不能完全理解它们,我们的想法是让鼠标指标更通用,同时仍然保持任何类型的图表或工具的一致性。让我们来看看这些变化。
首先,我们有 OnInit 初始化函数。如果你仔细看,你会发现它与之前的版本不同。在这里,我们需要注意一些细节。首先,在第 31 行中,我们检查我们进行的任何调用是否设置了错误变量。以前,任何错误都会导致初始化报告 INIT_FAILED。那么,我为什么要改变这个?原因是,有时指标在初始化过程中报告了一个毫无意义的错误。但是由于类构造函数不允许我们返回值,我为此使用了 _LastError 变量。然而,事实证明这并不十分实用。因此,我决定保持相同的方法,但使用 SetUserError 将错误过滤到更具体的内容。这种类型的错误并不经常发生,但对我们来说,确认构造函数是否正确完成了工作非常重要。
完成此操作后,我们引入了以前没有的东西:我们告诉 MetaTrader 5 我们想接收订单簿事件。重要提示:在过去,只有当图表数据属于真实工具时,即连接到交易服务器时,才能使用订单簿事件。现在,我们总是持续不断地收到订单簿事件,即使它是一种定制工具。这很重要。我打算使用相同的机制来通知用户该工具何时处于竞价模式。这使得工作变得更简单,因为界面保持不变,不需要特殊的调整。我们所需要做的就是将正确的数据传递给指标,指标会处理其余的数据。
请注意,在第 34 行中,我们从表示市场关闭的值开始。然后,在第 35 和 36 行,我们初始化鼠标指标的数据缓冲区。这是一个以前做过的简单过程。不同之处在于,现在我们明确告诉 MetaTrader 5,无论使用哪种工具,都要始终监听订单簿事件。
让我们暂时跳过 OnCalculate,稍后再返回。您可能已经注意到它不再调用 iSpread。相反,让我们前进到第 75 行,在那里我们找到了 OnDeInit 函数。因为我们将接收任何工具的订单簿事件,所以我们需要告诉 MetaTrader 5,在某个时候,我们不再想接收它们。这是在第 77 行完成的。由于我们要从图表中删除指标,因此在第 79 行,我们使用删除运算符将内存释放回系统。
现在,让我们检查一下订单簿事件处理程序。它位于 OnBookEvent 例程的第 62 行。这被设计得尽可能简单。我们对分析订单簿中的订单量或数量不感兴趣。我们关心的是了解该工具是否处于交易模式还是竞价模式,以及市场是否关闭。为了确定这一点,我们只需要观察订单簿中发生的情况。
在第 67 行,我们根据指标所附加的工具来过滤调用。这是因为 MetaTrader 5 不会根据工具过滤订单簿事件;它只是将它们发送到每个请求它们的图表。因此,监控多个本我们不关心的工具的订单簿并不是一个好主意。这会产生不必要的事件并浪费时间。过滤后,第 68 行通过 MQL5 库调用获取最新的订单簿数据。这是必要的,以便该程序能够完成其工作。
OnBookEvent 最有趣和最有用的部分来了。请密切关注第 69 行。如果订单簿数据数组为空,则表示市场已关闭。如果有数据,我们需要进行新的检查。为什么呢?很简单:了解我们正在分析的数据的来源。您可以在前面显示的 C_Study.mqh 头文件中看到这一点。特别是它的 Update 过程。在第 124 行,如果该工具连接到交易服务器,我们调用 TimeCurrent,或者如果它是回放/模拟模式下的自定义工具,我们使用全局变量。这就是为什么第 69 行的测试是必要的。没有它,我们就有可能使用不正确的数据,这将完全扰乱一切。一旦确定了交易品种的主要状态,我们就需要检查它的实际状态。这是在第 70 行完成的,我们循环遍历整个订单簿来寻找特定类型的信息。在这种情况下,我们检查 BOOK_TYPE_BUY_MARKET 或 BOOK_TYPE_SELL_MARKET 是否存在。如果我们发现其中任何一个,则意味着市场开放,但该工具处于竞价模式。
请注意,OnInit 中的一个简单步骤是如何为任何工具启用订单簿事件,从而可以确定工具的当前状态。正是这种实现细节让这项工作变得如此有趣。
现在让我们把注意力转回到第 41 行,即 OnCalculate 过程。您可能想知道:这段代码为什么又被修改了呢?MetaTrader 5 在上一篇文章和这篇文章之间是否收到了更新?好吧,事实上,没有。亲爱的读者,请记住,这些文章是很久以前写的,但内容现在才被发布。
执行的结果可以在下面的视频中看到,我在视频中演示了实际发生的事情。在阅读解释之前,请先观看视频。它肯定会帮助你理解接下来的内容。
演示视频
如果你看了这段视频,你可能会注意到我正在以一种非常具体的方式做事。现在不要担心服务的代码,稍后会介绍。现在,让我们把注意力集中在鼠标指标上。
首先,我初始化了服务。它处于等待状态,以检测何时将鼠标指标添加到图表中。放置后,您可以观察到显示器显示倒计时,值也出现在 MetaTrader 5 工具箱中。这样我们就可以验证鼠标指标是否真的能够访问服务提供的值。
现在,请注意以下几点:在上一篇文章中,服务一度继续向鼠标指标发送数据。但是,该指标无法根据从服务接收的值向我们提供基于时间的更新。这是因为显示完全冻结了。现在观察一下,当我们在 OnCalculate 函数中使用与现在相同的代码时,它冻结了。为了防止当时的冻结,我们采用了 iSpread 调用。然而,在这里,我们没有使用该调用,但指标显示并没有冻结。为什么现在可以了,但在上一篇文章中却不行呢?老实说,我不知道这个问题的答案。这是只有那些真正尝试和突破极限的人才能亲身体验到的事情之一。
亲爱的读者,我与你分享这一点,是为了告诉你,作为程序员,我们并不总是对所有事情都有答案。我们遇到了一些事情,但我们不知道为什么或如何得出这个结果。在我看来,与你分享这种情况会让学习过程和我自己的经历变得更加愉快。因为你会发现,并非所有目标都能从一开始就实现。对我来说,我了解了 MetaTrader 5 的每一个最微小的部分是如何工作的,以及我可以在不导致完全系统崩溃的情况下推动它走多远。因此,为了结束本文,让我们来看看视频中使用的服务的源代码。
测试服务
通常,人们很难学习编程,因为他们不理解:
事件的顺序影响结果
但说到编程,在大多数情况下都是如此。我们很少能在编程时不担心事件发生的顺序。但在这里,在测试服务中,上述陈述是绝对正确的。那么让我们看一下该服务的源代码。
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. //+------------------------------------------------------------------+
测试服务源代码
请注意,该代码实际上与上一篇文章中看到的代码相同,旨在测试鼠标指标。然而,这里有一些新元素。这些元素触发了您在视频中看到的整个过程。这里的每一行新代码都有原因和意义。大多数尝试使用本书来编写自定义交易品种的人都失败了,因为他们不明白真正需要做什么。这是因为他们只关注 CustomBookAdd 调用,可以在第 47、62 和 64 行看到。但事情不是这样的。除非我们采取一些预先的步骤,否则仅仅尝试使用 CustomBookAdd 库调用将数据传递到自定义交易品种上的订单簿是绝对行不通的。而且,为了避免错误,必须仔细规划这些步骤。
要采取的主要步骤是在第 24 行。没有它,CustomBookAdd 函数对我们来说完全无用。但是 CustomBookAdd 不是专门用于向订单簿发送数据的吗?就像 Tick 和 Rate 的功能一样?所有这些问题的答案都是肯定的和否定的。这听起来很矛盾,但只有首先为 SYMBOL_TICKS_BOOKDEPTH 定义一个值,CustomBookAdd 函数才会有任何效果。用于 SYMBOL_TICKS_BOOKDEPTH 的值取决于我们打算做什么。在这里,由于我们的目标只是引导鼠标指标,以便它可以告知用户自定义交易品种的状态,因此我们只需要一个级别。出于这个原因,我在第 24 行的调用中用来定义 SYMBOL_TICKS_BOOKDEPTH 的值是 1。但是,如果您想创建一本用于模拟或回放研究的人工订单簿,您只需将值从 1 更改为适合您需要的任何值。
无论如何,这是最基本的部分。只有在定义 SYMBOL_TICKS_BOOKDEPTH 之后,我们才能真正接收 MqlBookInfo 结构中的数据。这方面对我来说有点难以理解,因为我找不到任何参考资料来解释如何为自定义交易品种填充 MqlBookInfo 结构。所有参考文献都只是简单地说使用 CustomBookAdd。但是,只有文档提到了 SYMBOL_TICKS_BOOKDEPTH。它没有将此定义与访问 CustomBookAdd 发布的 MqlBookInfo 结构中的数据的能力联系起来。
无论如何,此服务将仅用于测试数据传输。它不会有任何其他用途。但在结束本文之前,我想快速解释一下这里发生了什么。在第 44 行和第 46 行之间,我们定义了一个订单簿级别。对于我们来说这里最重要的部分是第 44 行。在第 47 行,我们通知 MetaTrader 5 生成一个事件,并将数据作为订单簿提供。现在,请密切注意这一点:在第 50 行,我们获得了 time。在第 56 行,我们将 time 增加一秒。根据第 34 行的定义,time 变量的初始值为 32400。这是以秒为单位的,不要忘记这一点。因此,在第 58 行,我们执行检查以确定我们当前的位置。30 秒后,我们在第 62 行向 book 发送一个新值。要发送的值显示在第 61 行。该值将导致鼠标指标显示该交易品种处于竞价模式。再过 30 秒,检查将导致第 64 行执行,这意味着要发送到 book 的值将来自第 65 行,并通过执行第 66 行。这将使时间显示返回到鼠标指标上。
最后一个细节:时间并不正好对应于一秒,因为在第 55 行我们引入了四分之一秒的延迟。
最后的探讨
在本文中,我向您展示了如何将值传递给自定义交易品种上的订单簿。虽然我们使用的服务的唯一目的是测试这种传递值的方式,但我们已经证明我们可以以完全合理的方式做到这一点。尽管面临最初的挑战,但这确实是可能的。因此,您现在可以使用订单簿在服务和指标或 EA 交易之间传输信息的另一种方式。然而,你应该永远记住,一切都是有代价的。不加区分地使用订单簿也会带来必须考虑的代价。
无论如何,这个测试服务并不能反映我们在下一篇文章中要做的事情。我们将应用回放/模拟器来告知我们该交易品种是否处于竞价模式。这个系统是不是很有趣?那么,我们下篇文章再见。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/12326
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。


