English Русский Español Português
preview
Entwicklung eines Replay-Systems (Teil 60): Abspielen des Dienstes (I)

Entwicklung eines Replay-Systems (Teil 60): Abspielen des Dienstes (I)

MetaTrader 5Beispiele | 14 März 2025, 09:51
18 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel „Entwicklung eines Wiedergabesystems (Teil 59): Eine neue Zukunft“ habe ich einige der Änderungen an den Kontrollanzeigen und Mausanzeigemodulen vorgestellt und erläutert. Obwohl diese Aktualisierungen uns mehrere künftige Verwendungsmöglichkeiten bieten, insbesondere für das Mausanzeigemodul, enthält es immer noch einen kleinen Fehler. Diese Schwachstelle betrifft uns jedoch derzeit nicht. Der Grund dafür ist, dass wir uns vorerst auf die Lösung einiger verbleibender Probleme im Zusammenhang mit der Kapselung innerhalb des Codes der Serviceklasse Replay/Simulator konzentrieren werden.

Obwohl wir bereits mehrere Anpassungen vorgenommen haben, wie in früheren Artikeln beschrieben, reichten diese nicht aus, um das Problem der Verkapselung vollständig zu lösen. Es gibt immer noch Elemente, die den Klassenbereich verlassen, und wir müssen dies sofort beheben. Andernfalls wird dies bald zu ernsthaften Problemen führen. Neben der Behebung des Kapselungsproblems, das dazu führt, dass interne Elemente, die verborgen bleiben sollten, zugänglich sind, müssen wir auch bestimmte Aspekte des Codes überarbeiten, um einen angemesseneren Zugriff auf einige Variablen und Informationen zu gewährleisten. Auf all dies wird in diesem Artikel eingegangen.


Die Dinge in Ordnung bringen

Sehr gut. Das erste Problem, das wir angehen werden, ist das Problem der Kapselung in der Klasse C_FilesTicks. Diese Klasse befindet sich in der Datei C_FilesTicks.mqh und gibt derzeit Informationen preis, auf die nicht direkt zugegriffen werden sollte, zumindest nicht so, wie es derzeit der Fall ist.

Um dieses Problem zu lösen, werden wir eine relativ einfache Lösung anwenden. Unten sehen Sie die aktualisierte und vollständige Version der Klasse C_FilesTicks.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_FileBars.mqh"
005. #include "C_Simulation.mqh"
006. //+------------------------------------------------------------------+
007. #define macroRemoveSec(A) (A - (A % 60))
008. //+------------------------------------------------------------------+
009. class C_FileTicks
010. {
011.    protected:
012.       enum ePlotType {PRICE_EXCHANGE, PRICE_FOREX};
013.       struct stInfoTicks
014.       {
015.          MqlTick     Info[];
016.          MqlRates    Rate[];
017.          int         nTicks,
018.                      nRate;
019.          bool        bTickReal;
020.          ePlotType   ModePlot;
021.       };m_Ticks;
022. //+------------------------------------------------------------------+
023. inline bool BuildBar1Min(const int iArg, MqlRates &rate, bool &bNew)
024.          {
025.             double dClose = 0;
026.             
027.             switch (m_Ticks.ModePlot)
028.             {
029.                case PRICE_EXCHANGE:
030.                   if (m_Ticks.Info[iArg].last == 0.0) return false;
031.                   dClose = m_Ticks.Info[iArg].last;
032.                   break;
033.                case PRICE_FOREX:
034.                   dClose = (m_Ticks.Info[iArg].bid > 0.0 ? m_Ticks.Info[iArg].bid : dClose);
035.                   if ((dClose == 0.0) || (m_Ticks.Info[iArg].bid == 0.0)) return false;
036.                   break;
037.             }
038.             if (bNew = (rate.time != macroRemoveSec(m_Ticks.Info[iArg].time)))
039.             {
040.                rate.time = macroRemoveSec(m_Ticks.Info[iArg].time);
041.                rate.real_volume = 0;
042.                rate.tick_volume = (m_Ticks.ModePlot == PRICE_FOREX ? 1 : 0);
043.                rate.open = rate.low = rate.high = rate.close = dClose;
044.             }else
045.             {
046.                rate.close = dClose;
047.                rate.high = (rate.close > rate.high ? rate.close : rate.high);
048.                rate.low = (rate.close < rate.low ? rate.close : rate.low);
049.                rate.real_volume += (long) m_Ticks.Info[iArg].volume_real;
050.                rate.tick_volume++;
051.             }
052.             
053.             return true;         
054.          }
055. //+------------------------------------------------------------------+
056.    private   :
057.       int         m_File;
058.       stInfoTicks m_Ticks;
059. //+------------------------------------------------------------------+
060. inline bool Open(const string szFileNameCSV)
061.          {
062.             string szInfo = "";
063.             
064.             if ((m_File = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
065.             {
066.                for (int c0 = 0; c0 < 7; c0++) szInfo += FileReadString(m_File);
067.                if (szInfo == "<DATE><TIME><BID><ASK><LAST><VOLUME><FLAGS>") return true;
068.                Print("File ", szFileNameCSV, ".csv not a traded tick file.");
069.             }else
070.                Print("Tick file ", szFileNameCSV,".csv not found...");
071.                
072.             return false;
073.          }
074. //+------------------------------------------------------------------+
075. inline bool ReadAllsTicks(const bool ToReplay)
076.          {
077.             string    szInfo;
078.                         
079.             Print("Loading replay ticks. Please wait...");
080.             ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
081.             m_Ticks.ModePlot = PRICE_FOREX;
082.             while ((!FileIsEnding(m_File)) && (m_Ticks.nTicks < (INT_MAX - 2)) && (!_StopFlag))
083.             {
084.                ArrayResize(m_Ticks.Info, m_Ticks.nTicks + 1, def_MaxSizeArray);
085.                szInfo = FileReadString(m_File) + " " + FileReadString(m_File);
086.                m_Ticks.Info[m_Ticks.nTicks].time = StringToTime(StringSubstr(szInfo, 0, 19));
087.                m_Ticks.Info[m_Ticks.nTicks].time_msc = (m_Ticks.Info[m_Ticks.nTicks].time * 1000) + (int)StringToInteger(StringSubstr(szInfo, 20, 3));
088.                m_Ticks.Info[m_Ticks.nTicks].bid = StringToDouble(FileReadString(m_File));
089.                m_Ticks.Info[m_Ticks.nTicks].ask = StringToDouble(FileReadString(m_File));
090.                m_Ticks.Info[m_Ticks.nTicks].last = StringToDouble(FileReadString(m_File));
091.                m_Ticks.Info[m_Ticks.nTicks].volume_real = StringToDouble(FileReadString(m_File));
092.                m_Ticks.Info[m_Ticks.nTicks].flags = (uchar)StringToInteger(FileReadString(m_File));
093.                m_Ticks.ModePlot = (m_Ticks.Info[m_Ticks.nTicks].volume_real > 0.0 ? PRICE_EXCHANGE : m_Ticks.ModePlot);
094.                m_Ticks.nTicks++;
095.             }
096.             FileClose(m_File);
097.             if (m_Ticks.nTicks == (INT_MAX - 2))
098.             {
099.                Print("Too much data in tick file.\nIt is not possible to continue...");
100.                return false;
101.             }
102.             return (!_StopFlag);
103.          }
104. //+------------------------------------------------------------------+
105.       int SetSymbolInfos(void)
106.          {
107.             int iRet;
108.             
109.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, iRet = (m_Ticks.ModePlot == PRICE_EXCHANGE ? 4 : 5));
110.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TRADE_CALC_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CALC_MODE_EXCH_STOCKS : SYMBOL_CALC_MODE_FOREX);
111.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_CHART_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CHART_MODE_LAST : SYMBOL_CHART_MODE_BID);
112.             
113.             return iRet;
114.          }
115. //+------------------------------------------------------------------+
116.    public   :
117. //+------------------------------------------------------------------+
118.       C_FileTicks()
119.          {
120.             ArrayResize(m_Ticks.Rate, def_BarsDiary);
121.             m_Ticks.nRate = -1;
122.             m_Ticks.Rate[0].time = 0;
123.          }
124. //+------------------------------------------------------------------+
125.       bool BarsToTicks(const string szFileNameCSV)
126.          {
127.             C_FileBars     *pFileBars;
128.             C_Simulation   *pSimulator = NULL;
129.             int            iMem = m_Ticks.nTicks,
130.                            iRet = -1;
131.             MqlRates       rate[1];
132.             MqlTick        local[];
133.             bool           bInit = false;
134.             
135.             pFileBars = new C_FileBars(szFileNameCSV);
136.             ArrayResize(local, def_MaxSizeArray);
137.             Print("Converting bars to ticks. Please wait...");
138.             while ((*pFileBars).ReadBar(rate) && (!_StopFlag))
139.             {
140.                if (!bInit)
141.                {
142.                   m_Ticks.ModePlot = (rate[0].real_volume > 0 ? PRICE_EXCHANGE : PRICE_FOREX);
143.                   pSimulator = new C_Simulation(SetSymbolInfos());
144.                   bInit = true;
145.                }
146.                ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 3 : def_BarsDiary), def_BarsDiary);
147.                m_Ticks.Rate[++m_Ticks.nRate] = rate[0];
148.                if (pSimulator == NULL) iRet = -1; else iRet = (*pSimulator).Simulation(rate[0], local);
149.                if (iRet < 0) break;
150.                for (int c0 = 0; c0 <= iRet; c0++)
151.                {
152.                   ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray);
153.                   m_Ticks.Info[m_Ticks.nTicks++] = local[c0];
154.                }
155.             }
156.             ArrayFree(local);
157.             delete pFileBars;
158.             delete pSimulator;
159.             m_Ticks.bTickReal = false;
160.             
161.             return ((!_StopFlag) && (iMem != m_Ticks.nTicks) && (iRet > 0));
162.          }
163. //+------------------------------------------------------------------+
164.       datetime LoadTicks(const string szFileNameCSV, const bool ToReplay = true)
165.          {
166.             int      MemNRates,
167.                      MemNTicks;
168.             datetime dtRet = TimeCurrent();
169.             MqlRates RatesLocal[],
170.                      rate;
171.             bool     bNew;
172.             
173.             MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
174.             MemNTicks = m_Ticks.nTicks;
175.             if (!Open(szFileNameCSV)) return 0;
176.             if (!ReadAllsTicks(ToReplay)) return 0;         
177.             rate.time = 0;
178.             for (int c0 = MemNTicks; c0 < m_Ticks.nTicks; c0++)
179.             {
180.                if (!BuildBar1Min(c0, rate, bNew)) continue;
181.                if (bNew) ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
182.                m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate;
183.             }
184.             if (!ToReplay)
185.             {
186.                ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
187.                ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
188.                CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
189.                dtRet = m_Ticks.Rate[m_Ticks.nRate].time;
190.                m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
191.                m_Ticks.nTicks = MemNTicks;
192.                ArrayFree(RatesLocal);
193.             }else SetSymbolInfos();
194.             m_Ticks.bTickReal = true;
195.                            
196.             return dtRet;
197.          };
198. //+------------------------------------------------------------------+
199. inline stInfoTicks GetInfoTicks(void) const
200.          {
201.             return m_Ticks;
202.          }
203. //+------------------------------------------------------------------+
204. };
205. //+------------------------------------------------------------------+
206. #undef def_MaxSizeArray
207. //+------------------------------------------------------------------+

Quellcode der Datei C_FilesTicks.mqh

Sie werden wahrscheinlich keinen großen Unterschied bemerken, außer natürlich etwas, das in Zeile 21 durchgestrichen ist.

Dieses durchgestrichene Element stellt genau das Datenleck dar, das wir angesprochen haben. Mit anderen Worten: Auch wenn alles sicher und voll funktionsfähig erschien, hätten wir ein ernsthaftes Sicherheitsproblem gehabt, wenn es darum gegangen wäre, mit angekommen oder simulierten Ticks umzugehen. Der Grund dafür ist, dass jede Klasse die Daten in dieser Struktur ändern könnte. Beachten Sie, dass eine solche Änderung außerhalb des Geltungsbereichs von Klassen nicht möglich ist, da die Struktur innerhalb einer geschützten Klausel liegt. In einem früheren Artikel habe ich erklärt, wie diese Klauseln die Zugriffsebenen für Klassenmitglieder festlegen.

Indem wir jedoch diese Variable entfernen und nur die Verwendung der Struktur selbst zulassen, erreichen wir ein viel akzeptableres Maß an Sicherheit und Kapselung. Doch mit dieser Änderung müssen wir eine weitere Entscheidung treffen. Die Entscheidung lautet wie folgt: entweder eine neue Variable deklarieren, diesmal als private, um die Struktur intern zu erhalten, und eine Funktion hinzufügen, um auf diese Variable zuzugreifen. Oder Sie deklarieren dieselbe Variable an anderer Stelle und übergeben sie als Argument an alle Funktionen, die auf ihren Inhalt zugreifen müssen.

Es mag seltsam klingen, aber jeder Fall hat seinen eigenen Kontext, seine eigenen Konsequenzen und Möglichkeiten. Angesichts der Art des Projekts habe ich mich für den ersten Ansatz entschieden, d. h. wir werden eine private Variable erstellen, die Sie in Zeile 58 sehen können. Abgesehen davon waren keine weiteren wesentlichen Änderungen am Kodex erforderlich. Indem wir diese Variable jedoch dem öffentlichen Zugriff entzogen haben (ja, wir haben sie an anderen Stellen verwendet, nicht nur zum Lesen, sondern auch zum Ändern ihrer Daten, wie wir gleich zeigen werden), müssen wir nun eine ordnungsgemäße Initialisierung der Struktur sicherstellen. Aus diesem Grund wird in Zeile 118 ein Konstruktor eingefügt. Dabei handelt es sich um eine recht einfache Aufgabe, die bereits an anderer Stelle erledigt wurde. Es gab das Leck, das durch den Kapselungsfehler verursacht wurde, den wir gerade in der Klasse C_FilesTicks behoben haben.

Außerdem müssen wir, wie bereits erwähnt, eine Möglichkeit zum Zugriff auf diese Variable schaffen, um ihre Daten zu lesen. Dies wird durch die in Zeile 199 gezeigte Funktion bewerkstelligt. Achten Sie bitte genau darauf: Wir können die Daten innerhalb der Struktur lesen, aber nicht schreiben. Dies ist auf die Art und Weise zurückzuführen, wie die Funktion deklariert ist - der zurückgegebene Wert muss als konstant behandelt werden. Die Anwendung dieses Ansatzes wurde bereits in einigen meiner früheren Artikel in dieser Reihe erläutert. Wenn Sie Zweifel haben, können Sie sich diese zur Klärung ansehen.

Nachdem wir nun eines der Probleme gelöst haben, müssen wir uns mit einem anderen befassen, das in gewisser Weise mit dem soeben behobenen Problem zusammenhängt. Das zweite Problem findet sich in der Klasse C_ConfigService, die sich in der Datei C_ConfigService.mqh befindet. Diese Header-Datei erforderte einige Anpassungen. Vor allem, weil das Datenleck von C_FilesTicks nicht mehr existiert. Außerdem werden einige in C_ConfigService deklarierte Elemente entfernt, da sie innerhalb dieser Klasse nicht mehr benötigt werden und stattdessen an anderer Stelle verwendet werden.

Der aktualisierte Code für die Datei C_ConfigService.mqh lautet also wie folgt:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "Support\C_FileBars.mqh"
005. #include "Support\C_FileTicks.mqh"
006. #include "Support\C_Array.mqh"
007. //+------------------------------------------------------------------+
008. class C_ConfigService : protected C_FileTicks
009. {
010.    protected:
011. //+------------------------------------------------------------------+
012.    private   :
013.       enum eWhatExec {eTickReplay, eBarToTick, eTickToBar, eBarPrev};
014.       enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE};
015.       struct st001
016.       {
017.          C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev;
018.          int      Line;
019.          bool     AccountHedging;
020.          char     ModelLoading;
021.          string   szPath;
022.       }m_GlPrivate;
023.       string    m_szPath;
024.       bool      m_AccountHedging;
025.       datetime m_dtPrevLoading;
026.       int      m_ReplayCount,
027.                m_ModelLoading;
028. //+------------------------------------------------------------------+
029. inline void FirstBarNULL(void)
030.          {
031.             MqlRates rate[1];
032.             int c0 = 0;
033.             
034.             for(; (GetInfoTicks().ModePlot == PRICE_EXCHANGE) && (GetInfoTicks().Info[c0].volume_real == 0); c0++);
035.             rate[0].close = (GetInfoTicks().ModePlot == PRICE_EXCHANGE ? GetInfoTicks().Info[c0].last : GetInfoTicks().Info[c0].bid);
036.             rate[0].open = rate[0].high = rate[0].low = rate[0].close;
037.             rate[0].tick_volume = 0;
038.             rate[0].real_volume = 0;
039.             rate[0].time = macroRemoveSec(GetInfoTicks().Info[c0].time) - 86400;
040.             CustomRatesUpdate(def_SymbolReplay, rate);
041.          }
042. //+------------------------------------------------------------------+
043. inline eTranscriptionDefine GetDefinition(const string &In, string &Out)
044.          {
045.             string szInfo;
046.             
047.             szInfo = In;
048.             Out = "";
049.             StringToUpper(szInfo);
050.             StringTrimLeft(szInfo);
051.             StringTrimRight(szInfo);
052.             if (StringSubstr(szInfo, 0, 1) == "#") return Transcription_INFO;
053.             if (StringSubstr(szInfo, 0, 1) != "[")
054.             {
055.                Out = szInfo;
056.                return Transcription_INFO;
057.             }
058.             for (int c0 = 0; c0 < StringLen(szInfo); c0++)
059.                if (StringGetCharacter(szInfo, c0) > ' ')
060.                   StringAdd(Out, StringSubstr(szInfo, c0, 1));               
061.             
062.             return Transcription_DEFINE;
063.          }
064. //+------------------------------------------------------------------+
065. inline bool Configs(const string szInfo)
066.          {
067.             const string szList[] = {
068.                      "PATH",
069.                      "POINTSPERTICK",
070.                      "VALUEPERPOINTS",
071.                      "VOLUMEMINIMAL",
072.                      "LOADMODEL",
073.                      "ACCOUNT"
074.                                     };
075.             string    szRet[];
076.             char      cWho;
077.             
078.             if (StringSplit(szInfo, '=', szRet) == 2)
079.             {
080.                StringTrimRight(szRet[0]);
081.                StringTrimLeft(szRet[1]);
082.                for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;
083.                switch (cWho)
084.                {
085.                   case 0:
086.                      m_GlPrivate.szPath = szRet[1];
087.                      return true;
088.                   case 1:
089.                      CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1]));
090.                      return true;
091.                   case 2:
092.                      CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1]));
093.                      return true;
094.                   case 3:
095.                      CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1]));
096.                      return true;
097.                   case 4:
098.                      m_GlPrivate.ModelLoading = StringInit(szRet[1]);
099.                      m_GlPrivate.ModelLoading = ((m_GlPrivate.ModelLoading < 1) && (m_GlPrivate.ModelLoading > 4) ? 1 : m_GlPrivate.ModelLoading);
100.                      return true;
101.                   case 5:
102.                      if (szRet[1] == "HEDGING") m_GlPrivate.AccountHedging = true;
103.                      else if (szRet[1] == "NETTING") m_GlPrivate.AccountHedging = false;
104.                      else
105.                      {
106.                         Print("Entered account type is not invalid.");                        
107.                         return false;
108.                      }
109.                      return true;         
110.                }
111.                Print("Variable >>", szRet[0], "<< not defined.");
112.             }else
113.                Print("Configuration definition >>", szInfo, "<< invalidates.");
114.                
115.             return false;
116.          }
117. //+------------------------------------------------------------------+
118. inline bool WhatDefine(const string szArg, char &cStage)
119.          {
120.             const string szList[] = {
121.                      "[BARS]",
122.                      "[TICKS]",
123.                      "[TICKS->BARS]",
124.                      "[BARS->TICKS]",
125.                      "[CONFIG]"
126.                                     };
127.                                     
128.             cStage = 1;
129.             for (char c0 = 0; c0 < ArraySize(szList); c0++, cStage++)
130.                if (szList[c0] == szArg) return true;
131.                
132.             return false;
133.          }
134. //+------------------------------------------------------------------+
135. inline bool CMD_Array(char &cError, eWhatExec e1)
136.          {
137.             bool       bBarsPrev = false;
138.             string     szInfo;
139.             C_FileBars *pFileBars;
140.             C_Array    *ptr = NULL;
141.             
142.             switch (e1)
143.             {
144.                case eTickReplay  : ptr = m_GlPrivate.pTicksToReplay; break;
145.                case eTickToBar   : ptr = m_GlPrivate.pTicksToBars;   break;
146.                case eBarToTick   : ptr = m_GlPrivate.pBarsToTicks;   break;
147.                case eBarPrev     : ptr = m_GlPrivate.pBarsToPrev;    break;
148.             }            
149.             if (ptr != NULL)
150.             {
151.                for (int c0 = 0; (c0 < INT_MAX) && (cError == 0); c0++)
152.                {
153.                   if ((szInfo = ptr.At(c0, m_GlPrivate.Line)) == "") break;
154.                   switch (e1)
155.                   {
156.                      case eTickReplay   :
157.                         if (LoadTicks(szInfo) == 0) cError = 4;
158.                         break;
159.                      case eTickToBar   :
160.                         if (LoadTicks(szInfo, false) == 0) cError = 5; else bBarsPrev = true;
161.                         break;
162.                      case eBarToTick   :
163.                         if (!BarsToTicks(szInfo)) cError = 6;
164.                         break;
165.                      case eBarPrev      :
166.                         pFileBars = new C_FileBars(szInfo);
167.                         if ((*pFileBars).LoadPreView() == 0) cError = 3; else bBarsPrev = true;
168.                         delete pFileBars;
169.                         break;
170.                   }
171.                }
172.                delete ptr;
173.             }
174.             
175.             return bBarsPrev;
176.          }
177. //+------------------------------------------------------------------+
178.    public   :
179. //+------------------------------------------------------------------+
180.       C_ConfigService()
181.          :C_FileTicks()
182.          {
183.             m_GlPrivate.AccountHedging = false;
184.             m_GlPrivate.ModelLoading = 1;
185.          }
186. //+------------------------------------------------------------------+
187. inline const bool TypeAccountIsHedging(void) const
188.          {
189.             return m_GlPrivate.AccountHedging;
190.          }
191. //+------------------------------------------------------------------+
192.       bool SetSymbolReplay(const string szFileConfig)
193.          {
194. #define macroFileName ((m_GlPrivate.szPath != NULL ? m_GlPrivate.szPath + "\\" : "") + szInfo)
195.             int         file;
196.             char        cError,
197.                         cStage;
198.             string      szInfo;
199.             bool        bBarsPrev;
200.                         
201.             if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
202.             {
203.                Print("Failed to open configuration file [", szFileConfig, "]. Service being terminated...");
204.                return false;
205.             }
206.             Print("Loading data for playback. Please wait....");
207.             ArrayResize(Ticks.Rate, def_BarsDiary);
208.             Ticks.nRate = -1;
209.             Ticks.Rate[0].time = 0;
210.             cError = cStage = 0;
211.             bBarsPrev = false;
212.             m_GlPrivate.Line = 1;
213.             m_GlPrivate.pTicksToReplay = m_GlPrivate.pTicksToBars = m_GlPrivate.pBarsToTicks = m_GlPrivate.pBarsToPrev = NULL;
214.             while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == 0))
215.             {
216.                switch (GetDefinition(FileReadString(file), szInfo))
217.                {
218.                   case Transcription_DEFINE:
219.                      cError = (WhatDefine(szInfo, cStage) ? 0 : 1);
220.                      break;
221.                   case Transcription_INFO:
222.                      if (szInfo != "") switch (cStage)
223.                      {
224.                         case 0:
225.                            cError = 2;
226.                            break;
227.                         case 1:
228.                            if (m_GlPrivate.pBarsToPrev == NULL) m_GlPrivate.pBarsToPrev = new C_Array();
229.                            (*m_GlPrivate.pBarsToPrev).Add(macroFileName, m_GlPrivate.Line);
230.                            break;
231.                         case 2:
232.                            if (m_GlPrivate.pTicksToReplay == NULL) m_GlPrivate.pTicksToReplay = new C_Array();
233.                            (*m_GlPrivate.pTicksToReplay).Add(macroFileName, m_GlPrivate.Line);
234.                            break;
235.                         case 3:
236.                            if (m_GlPrivate.pTicksToBars == NULL) m_GlPrivate.pTicksToBars = new C_Array();
237.                            (*m_GlPrivate.pTicksToBars).Add(macroFileName, m_GlPrivate.Line);
238.                            break;
239.                         case 4:
240.                            if (m_GlPrivate.pBarsToTicks == NULL) m_GlPrivate.pBarsToTicks = new C_Array();
241.                            (*m_GlPrivate.pBarsToTicks).Add(macroFileName, m_GlPrivate.Line);
242.                            break;
243.                         case 5:
244.                            if (!Configs(szInfo)) cError = 7;
245.                            break;
246.                      }
247.                      break;
248.                };
249.                m_GlPrivate.Line += (cError > 0 ? 0 : 1);
250.             }
251.             FileClose(file);
252.             CMD_Array(cError, (m_GlPrivate.ModelLoading <= 2 ? eTickReplay : eBarToTick));
253.             CMD_Array(cError, (m_GlPrivate.ModelLoading <= 2 ? eBarToTick : eTickReplay));
254.             bBarsPrev = (CMD_Array(cError, ((m_GlPrivate.ModelLoading & 1) == 1 ? eTickToBar : eBarPrev)) ? true : bBarsPrev);
255.             bBarsPrev = (CMD_Array(cError, ((m_GlPrivate.ModelLoading & 1) == 1 ? eBarPrev : eTickToBar)) ? true : bBarsPrev);
256.             switch(cError)
257.             {
258.                case 0:
259.                   if (GetInfoTicks().nTicks <= 0) //Atualizado ...
260.                   {
261.                      Print("There are no ticks to use. Service is being terminated...");
262.                      cError = -1;
263.                   }else if (!bBarsPrev) FirstBarNULL();
264.                   break;
265.                case 1   : Print("The command on the line ", m_GlPrivate.Line, " not recognized by the system...");    break;
266.                case 2   : Print("The system did not expect the contents of the line: ", m_GlPrivate.Line);            break;
267.                default  : Print("Error accessing the file indicated in the line: ", m_GlPrivate.Line);
268.             }
269.                      
270.             return (cError == 0 ? !_StopFlag : false);
271. #undef macroFileName
272.          }
273. //+------------------------------------------------------------------+
274. };
275. //+------------------------------------------------------------------+

Quellcode der Datei C_ConfigService.mqh

Es wurden mehrere Änderungen vorgenommen, von denen einige subtiler, andere offensichtlicher sind, wie zum Beispiel die durchgestrichenen Abschnitte. Allerdings sollten Sie jetzt feststellen, dass wir nicht mehr direkt auf die Tickdaten zugreifen. Die neue Art, dies zu handhaben, ist in Zeile 34 zu sehen. Beachten Sie, dass der Zugriff auf diese Daten nun wesentlich verfeinert und vor allem sicherer ist, da es nicht mehr möglich ist, die in der Skriptdatei konfigurierten oder in den Tick- oder Balkendateien enthaltenen Daten zu ändern. Wir können also definitiv sagen, dass wir jetzt ein viel sichereres und stabileres System haben, das wesentlich einfacher zu programmieren ist und weit weniger kritische Fehler zulässt.

Darüber hinaus möchte ich Sie, liebe Leserin, lieber Leser, auf einen weiteren Punkt aufmerksam machen. Achten Sie auf die Zeilen 23 bis 27: Die privaten globalen Variablen wurden entfernt. Einige von ihnen wurden in das Innere der Struktur verlegt, die in Zeile 15 beginnt. Aber es gibt einen Grund für diese Änderung in Bezug auf globale und private Variablen. Der Grund dafür ist, dass ich nicht möchte (weil wir es nicht brauchen), dass bestimmte Elemente Teil der allgemeinen Konfiguration für den Wiedergabe-/Simulationsdienst sind.

Aufgrund dieser Änderung zwischen den Zeilen 23 und 27 mussten mehrere Teile des Codes angepasst werden. Diese verdienen jedoch keine besondere Aufmerksamkeit, da es sich um einfache Änderungen handelt, die leicht zu verstehen sind. Achten Sie dennoch auf den Klassenkonstruktor, der in Zeile 180 zu finden ist. Beachten Sie, dass der Code dieses Konstruktors leicht geändert wurde. Dies ist jedoch nicht der Hauptpunkt, der diese Header-Datei diskussionswürdig macht. Der entscheidende Punkt liegt zwischen den Zeilen 207 und 209, die durchgestrichen sind.

Bevor das Kapselungsproblem in der Klasse C_FileTicks behoben wurde, wurde die Variable m_Ticks, die sich in einem geschützten Teil befand, in diesen Zeilen tatsächlich instanziiert oder besser gesagt, initialisiert.

Beachten Sie, wie riskant diese Situation war. Obwohl der Code recht stabil lief, war er nicht wirklich sicher, insbesondere wenn wir später größere Änderungen vornehmen wollten. Das war ein Fehler meinerseits, der sich bis heute gehalten hat. Da ich aber auf eine ganz bestimmte Art und Weise arbeiten muss, kann ich nicht zulassen, dass Informationen durchsickern und von irgendwo im Code geändert werden.

Mit diesen Korrekturen sind unsere Klassen, die für die Verwaltung von Tick- und Balkendaten sowie für die Konfiguration des Assets für die Wiedergabe/Simulation zuständig sind, nun vollständig abgesichert, ohne dass es zu Datenverlusten kommt. Mit anderen Worten, wir haben diesen Teil des Systems, der mit dem Laden von Daten, der Simulation und der Asset-Konfiguration zusammenhängt und der vom Replay/Simulator-Dienst genutzt wird, nun endgültig geschlossen. Von hier aus können wir uns darauf konzentrieren, dass der Dienst ordnungsgemäß funktioniert: Starten, Anhalten und auch, dass der Nutzer die Wiedergabe/Simulation von einem bestimmten Punkt aus starten kann.

Diese Funktionalitäten waren bereits vor all diesen Änderungen vorhanden. Von nun an werden wir sie jedoch auf eine viel sicherere, einfachere und stabilere Weise umsetzen. Wir werden auch für eine bessere Interaktion zwischen den Komponenten sorgen, an denen wir in naher Zukunft arbeiten werden.


Der Nutzer kann die Wiedergabe/Simulation abspielen und anhalten

In diesem Abschnitt zeige ich, wie der Dienst auf die Klicks des Nutzers auf die Schaltflächen „Play“ und „Pause“ reagieren kann. Dies ermöglicht sowohl Wiederholungs- als auch Simulationsfunktionen.

Zuvor möchte ich Ihnen, lieber Leser, aber noch zwei kleine Aktualisierungen zeigen, die am Kontrollanzeigenmodul vorgenommen wurden. Diese Updates sollen ein unangenehmes Problem verhindern, das manchmal beim Betrieb des Systems auftritt. Da es sich um ein zufälliges Problem handelt, kann es schwierig sein, es zu reproduzieren, da Sie das System unter Umständen mehrmals laufen lassen, ohne dass etwas Merkwürdiges passiert. Doch hin und wieder kommt es zu merkwürdigen Ereignissen. Um all den Frust zu vermeiden, der durch solche zufälligen Abstürze verursacht wird, habe ich einige Anpassungen am Code des Steuermoduls vorgenommen.

Daher aktualisieren wir in der Header-Datei C_Replay.mqh die Funktionen und Prozeduren, die in den folgenden Fragmenten dargestellt sind. Lassen Sie uns mit dem folgenden Punkt beginnen:

71. //+------------------------------------------------------------------+
72.       void SetPlay(bool state)
73.          {
74.             if (m_Section[ePlay].Btn == NULL)
75.                m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePlay), def_ColorFilter, "::" + def_ButtonPlay, "::" + def_ButtonPause);
76.             m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, ((m_Section[ePlay].state = state) ? 0 : 1));
77.             if (!state) CreateCtrlSlider();
78.          }
79. //+------------------------------------------------------------------+

Code aus der Datei C_Controls.mqh

Wir müssen dem ursprünglichen Code die Zeile 77 hinzufügen. Auf diese Weise weiß das Kontrollanzeigemodul immer, was zu tun ist, und zeigt den Schieberegler an, wenn die Wiedergabetaste sichtbar ist. Das heißt, wenn wir uns im Pausenmodus befinden, wird der Schieberegler im Chart angezeigt. Manchmal geschah dies nicht, aber mit dieser Aktualisierung wird der Balken immer angezeigt.

Es muss noch eine weitere Änderung vorgenommen werden. Dies sollte in dem unten dargestellten Codeteil geschehen:

170. //+------------------------------------------------------------------+
171.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
172.          {
173.             short x, y;
174.             static short iPinPosX = -1, six = -1, sps;
175.             uCast_Double info;
176.             
177.             switch (id)
178.             {
179.                case (CHARTEVENT_CUSTOM + evCtrlReplayInit):
180.                   info.dValue = dparam;
181.                   if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break;
182.                   x = (short) info._16b[0];
183.                   iPinPosX = m_Slider.Minimal = (x > def_MaxPosSlider ? def_MaxPosSlider : (x < iPinPosX ? iPinPosX : x));
184.                   SetPlay((short)(info._16b[1]) == SHORT_MAX);
185.                   break;
186.                case CHARTEVENT_OBJECT_DELETE:

Der Code in der Datei C_Controls.mqh

In diesem Abschnitt müssen Sie den ursprünglichen Code ändern und das einfügen, was sich zwischen den Zeilen 180 und 185 befindet. Der Zweck dieser Änderung ist es, das Kontrollanzeigenmodul widerstandsfähiger gegen ein besonders lästiges Problem zu machen, das manchmal auftritt. Dieses Problem betrifft ein nutzerdefiniertes Ereignis, das die Ausführung in Zeile 179 auslöst, was gelegentlich zu falschen Verhaltensweisen führt. Ein solches Verhalten ist das Eintreffen von fehlerhaften Werten im Index 1 des Arrays. Ursprünglich wurde in diesem Fall ein Fehler gemeldet, und das Steuermodul wurde aus der Chart entfernt, was zum Absturz des gesamten Wiedergabe-/Simulationsdienstes führte. Mit anderen Worten: Der Dienst würde aufgrund eines zufälligen Laufzeitfehlers, der ungültige Daten zur Folge hat, beendet.

Bei der neuen Implementierung geht das Kontrollmeldemodul beim Auftreten einer Störung sofort in den Pausenmodus über. Das ist viel besser als ein komplettes Versagen des Replay/Simulationssystems. Außerdem gibt es ein weiteres wichtiges Detail. Falls Sie es noch nicht bemerkt haben: In Zeile 181 gibt es eine kleine Prüfung. Dadurch wird sichergestellt, dass selbst wenn ungültige Werte auftauchen, diese nur verwendet werden, wenn die ersten beiden Bytes der acht im Doppelwert enthaltenen Bytes etwas Bestimmtes enthalten. Wenn diese Werte nicht vorhanden sind, wird das Ereignis vom Kontrollmeldemodul einfach ignoriert. Diese Art der Validierung gewährleistet die Integrität der Daten oder bestätigt zumindest, dass die Daten aus einer bestimmten Quelle stammen: dem Wiedergabe-/Simulationsdienst.

Sobald Sie diese beiden Änderungen vorgenommen haben, müssen Sie noch eine weitere im Quellcode des Indikators vornehmen. Diese letzte erforderliche Aktualisierung können Sie dem folgenden Fragment entnehmen.

41. //+------------------------------------------------------------------+
42. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
43. {
44.    (*control).DispatchMessage(id, lparam, dparam, sparam);
45.    if (_LastError >= ERR_USER_ERROR_FIRST + C_Terminal::ERR_Unknown)
46.    {
47.       Print("Internal failure in the messaging system...");
48.       ChartClose(user00);
49.    }
50.    (*control).SetBuffer(m_RatesTotal, m_Buff);
51. }
52. //+------------------------------------------------------------------+

Fragment des Quellcodes der Kontrollanzeige

Die durchgestrichenen Zeilen sollten aus dem Quellcode entfernt werden. Löschen Sie daher die kompilierte ausführbare Datei des Steuermoduls, damit der Dienst gezwungen ist, sie neu zu kompilieren, wenn Sie versuchen, den Dienst erneut zu kompilieren. Alternativ können Sie das Steuermodul auch manuell neu kompilieren, um sicherzustellen, dass die neueste aktualisierte Version ordnungsgemäß als Ressource in den Wiedergabe-/Simulationsdienst aufgenommen wird.

Nun können wir uns dem eigentlichen Code des Wiedergabe-/Simulationsdienstes zuwenden. Im Anschluss an die letzte Aktualisierung der Header-Datei C_Replay.mqh werden wir einige Änderungen an dem bisherigen Code vornehmen. Diese Änderungen wurden in einem früheren Artikel vorgenommen. Damals bestand der einzige funktionale Aspekt in der Anzeige der vom System geladenen Daten sowie in den Steuerungs- und Mausmodulen. Darüber hinaus war jedoch keine Interaktion möglich, d. h. wir konnten die Wiedergabe/Simulation nicht abspielen oder anhalten, um neue Daten dynamisch anzuzeigen.

Aber hier werden wir diese Realität ändern. Werfen wir also einen Blick auf die aktualisierte Header-Datei C_Replay.mqh. Die Datei wird im Folgenden vollständig wiedergegeben.

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

Der Quellcode der Datei C_Replay.mqh

Da dieser Code einige wichtige Änderungen erfahren hat, werde ich auch erklären, was gemacht wurde und was diese Änderungen bedeuten. Dies ist notwendig, um sicherzustellen, dass die Funktionsweise des Systems klar verstanden wird. Dies ist besonders wichtig, weil diese Version einige Probleme enthält, auf die ich am Ende dieses Artikels eingehen werde.

Zu Beginn habe ich beschlossen, die Informationen in Strukturen zu gliedern, da dies die Trennung einfacher und logischer macht. Beachten Sie die Zeile 20, in der ich das Handle für den Zugriff auf das Kontrollanzeigemodul deklariere. Dadurch wird eine große Anzahl von Aufrufen vermieden, nur um einen Wert zu holen, der während der gesamten Lebensdauer des Dienstes kontinuierlich verwendet wird. Es macht also keinen Sinn, auf diesen Wert auf andere Weise zuzugreifen, zumal wir viele Aufrufe an das Steuermodul tätigen werden. Jede Zeitersparnis zwischen diesen Anrufen ist von großem Nutzen. Sie werden bald verstehen, warum das wichtig ist.

Die Prozedur zwischen den Zeilen 33 und 54 hat keine größeren Änderungen erfahren, mit Ausnahme der Entfernung des Codes, der das Handle für den Zugriff auf den Puffer des Befehlsmoduls abruft und freigibt. Abgesehen davon wurde nur eine Änderung vorgenommen, die direkt mit dem in C_Replay.mqh aktualisierten Fragment zusammenhängt.

Beachten Sie die Zeilen 50 und 51, in denen ich dieselben Informationen zuweise, die die nutzerdefinierte Ereignisbehandlung in der Klasse C_Replay erwartet. Dadurch wird sichergestellt, dass die vom Dienst übermittelten Daten ordnungsgemäß vom Kontrollmodul empfangen werden.

Der nächste interessante Vorgang liegt zwischen den Zeilen 67 und 93. Diese Prozedur ist für die Erzeugung und Übermittlung von Balken- und Tickdaten an das nutzerdefinierte Symbol verantwortlich. Mit anderen Worten: Hier aktualisieren wir die Informationen, die MetaTrader 5 auf dem Chart anzeigen soll. Dieses Verfahren ist fast identisch mit dem, das in der letzten Version des Replay/Simulationsdienstes existierte. Damals verwendeten wir noch globale Terminalvariablen für den Nachrichtenaustausch zwischen Anwendungen. Aufgrund der Behebung des Datenlecks in der Klasse C_FileTicks mussten wir hier jedoch einige Punkte anpassen. Beachten Sie, wie auf Daten aus C_FileTicks zugegriffen wird. Ein Beispiel hierfür findet sich in Zeile 75. Das funktioniert zwar, aber es gibt einen wichtigen Punkt zu beachten: Jeder Anruf führt zu leichten Verzögerungen. Wir werden diesen Punkt später diskutieren.

Es war auch notwendig, ein weiteres Verfahren hinzuzufügen, das in älteren Versionen des Dienstes existierte. Es befindet sich zwischen den Zeilen 95 und 120 und dient der Initialisierung der Preis- oder Kurszeilenanzeige. Wenn Sie sich an den letzten Artikel erinnern, in dem C_Replay.mqh erschien, fehlte diese Prozedur. Wenn der Replay/Simulator MetaTrader 5 anweist, das nutzerdefinierte Symbolchart zu öffnen, sind daher keine Kurslinien sichtbar. Dieses Verfahren behebt dieses Problem. Anders als Sie vielleicht denken, wird dieser Vorgang jedoch nur einmal aufgerufen. Während der Abspielphase ist die Prozedur CreateBarInReplay in Zeile 67 dafür verantwortlich, dass diese Anzeige aktualisiert wird. Konkret wird diese Aufgabe in Zeile 89 innerhalb dieses Verfahrens durchgeführt.

Weiter geht es mit dem Klassenkonstruktor. Hier wurden nur die Initialisierungszeilen hinzugefügt. Diese Zeilen befinden sich zwischen den Zeilen 138 und 141. Ansonsten wurde nichts geändert. Im Destruktor der Klasse erscheint jedoch bei 146 eine neue Zeile, in der wir das Handle freigeben. Obwohl dies vielleicht nicht kritisch ist (da das Schließen des Charts den Dienst ohnehin beenden würde), ziehe ich es vor, sicherzustellen, dass MetaTrader 5 ausdrücklich darüber informiert wird, dass das Handle nicht mehr benötigt wird. Aus diesem Grund wurde diese Zeile hinzugefügt.

Da wir das Handle freigeben, müssen wir es von irgendwo erhalten haben. Dies geschieht in der Tat in dem Verfahren zwischen den Zeilen 171 und 188. Beachten Sie, dass wir in Zeile 178 nun die Prozedur zur Initialisierung der Preis-/Kurszeilen aufrufen, die vorher fehlte. In Zeile 180 wird dann das Handle erfasst, das für den Zugriff auf den Puffer des Kontrollindikators benötigt wird. Der Rest des Verfahrens bleibt unverändert.

Schließlich kommen wir zur letzten Funktion dieser Klasse, die sich in den Zeilen 190 bis 215 befindet. Dies ist vielleicht der problematischste Punkt von allen. Denn im Gegensatz zu anderen Verfahren oder Funktionen, die zu ruhigeren Zeiten durchgeführt werden können - ein Verfahren, das eine oder zwei Sekunden dauert, kann in einem vernünftigen Rahmen akzeptiert werden. Diese Funktion lässt eine solche Flexibilität nicht zu. Die Ausführung in Echtzeit ist hier von entscheidender Bedeutung, und jede Verzögerung oder Ungenauigkeit kann den Dienst erheblich beeinträchtigen.

In der Vergangenheit, als wir globale Terminalvariablen verwendet haben, bereitete diese Funktion erhebliche Probleme. Wir mussten drastische Maßnahmen ergreifen, um sie funktionsfähig zu machen. Nun, da wir einen anderen Ansatz verwenden, ist unser altes „Kopfzerbrechen“, bekannt als LoopEventOnTime, zurückgekehrt, um uns erneut heimzusuchen.

Doch bevor wir uns mit dieser Herausforderung befassen, sollten wir analysieren, was wir jetzt tun.

Zwischen den Zeilen 194 und 198 befindet sich eine Schleife. Dies ist unproblematisch, da der Zweck darin besteht, auf die Interaktion des Nutzers mit dem Steuermodul (über das Mausmodul) zu warten, um den Abspielmodus auszulösen. Wenn der Nutzer das Chart schließt oder den Dienst beendet, wird die Schleife dank der def_CheckLoopService-Prüfung beendet. Wir haben diese Definition in Zeile 9 angegeben, wo Sie sehen können, dass wir immer prüfen, ob das Chart geschlossen oder der Dienst beendet wurde. Im Allgemeinen bereitet uns diese Schleife keine Probleme.

Dasselbe kann jedoch nicht über die nächste Schleife zwischen den Zeilen 200 und 212 gesagt werden. Man könnte meinen, dass dies neu ist und dass wir deshalb Probleme haben. Sie basiert jedoch auf der alten Schleife, die wir für die Arbeit mit globalen Terminalvariablen verwendet haben. Allerdings ist diese Version im Vergleich zu der früheren Version vereinfacht. Es fehlen jedoch noch einige wichtige Elemente, um die frühere Funktionalität vollständig zu übernehmen.

Diese Schleife enthält eine interne Schleife, und wie ich bereits erklärt habe, ist diese interne Struktur entscheidend. In bestimmten Situationen würde das System ohne diese Funktion einfrieren und auf unbestimmte Zeit auf ein Häkchen warten, um den Dienst „freizugeben“. Eine genauere Erklärung finden Sie in den früheren Artikeln dieser Reihe über den Aufbau des Wiedergabe-/Simulatorsystems.

Auf den ersten Blick scheint dieser Code perfekt zu sein, bis auf die fehlende Logik zur Anpassung des Startpunkts der Wiedergabe/Simulation. Optimismus ist gut, aber bevor ich die Ergebnisse bewerte, möchte ich den Dienstcode zeigen. Obwohl sie seit dem letzten Artikel unverändert ist, ist sie wichtig, um zu verstehen, wo die wirklichen Probleme liegen.

01. //+------------------------------------------------------------------+
02. #property service
03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico"
04. #property copyright "Daniel Jose"
05. #property version   "1.60"
06. #property description "Replay-Simulator service for MetaTrade 5 platform."
07. #property description "This is dependent on the Market Replay indicator."
08. #property description "For more details on this version see the article."
09. #property link "https://www.mql5.com/pt/articles/12086"
10. //+------------------------------------------------------------------+
11. #include <Market Replay\Service Graphics\C_Replay.mqh>
12. //+------------------------------------------------------------------+
13. input string            user00 = "Mini Dolar.txt";     //Replay Configuration File.
14. input ENUM_TIMEFRAMES   user01 = PERIOD_M5;            //Initial Graphic Time.
15. input string            user02 = "Default";            //Template File Name
16. //+------------------------------------------------------------------+
17. C_Replay *pReplay;
18. //+------------------------------------------------------------------+
19. void OnStart()
20. {
21.    pReplay = new C_Replay();
22. 
23.    UsingReplay();   
24.    
25.    delete pReplay;
26. }
27. //+------------------------------------------------------------------+
28. void UsingReplay(void)
29. {
30.    if (!(*pReplay).SetSymbolReplay(user00)) return;
31.    if (!(*pReplay).OpenChartReplay(user01, user02)) return;
32.    if (!(*pReplay).InitBaseControl()) return;
33.    Print("Permission granted. Replay service can now be used...");
34.    while ((*pReplay).LoopEventOnTime());
35. }
36. //+------------------------------------------------------------------+

Der Quellcode des Dienstes

Wie Sie sehen können, hat sich am Code nichts Wesentliches geändert. 


Schlussfolgerung

Um sicherzustellen, dass Sie den Wiedergabe-/Simulationsdienst nicht selbst kompilieren und testen müssen, füge ich eine Videodemonstration bei. Bitte beobachten Sie dies genau, denn Sie werden einen drastischen Leistungsabfall des Dienstes feststellen. Warum ist das so? Der Grund liegt vor allem darin, dass es jetzt viel mehr Funktionsaufrufe und weniger Variablenverwendungen im Code gibt. Das ist jedoch nicht der einzige Faktor. Es gibt noch weitere Aspekte, die sich auf die Leistung auswirken. Da das Erklären, Analysieren und Testen dieser Faktoren Zeit und Geduld erfordert, werde ich diese Details für einen späteren Artikel aufheben.

Das Hauptaugenmerk wird jedoch auf der Schleife innerhalb der Funktion LoopEventOnTime liegen. Auf die eine oder andere Weise muss diese Schleife, die für die Erzeugung und den Start der Wiedergabe oder Simulation verantwortlich ist, optimiert werden, damit sie effizienter arbeitet. Wie bereits in diesem Artikel erwähnt, beeinträchtigen mehrere Aspekte die Leistung des Dienstes gerade dann, wenn eine hohe Leistung am wichtigsten ist. Während der Phase, in der globale Terminalvariablen verwendet wurden, war die Leistung akzeptabel. Was Sie im Video sehen werden, macht jedoch jeden praktischen Nutzen des neuen Modells inakzeptabel.

Dies deutet darauf hin, dass das derzeitige System nicht zuverlässig ist. Aber als Programmierer müssen wir oft Probleme lösen, die weit über einfache Berechnungslogik hinausgehen, da wir uns auch mit Leistungsfragen befassen müssen. Insgesamt hat das neue klassenbasierte System den Code wesentlich stabiler und sicherer gemacht. Unsere eigentliche Herausforderung besteht nun darin, das frühere Leistungsniveau wiederherzustellen (oder zumindest annähernd) und dabei ereignis- und pufferbasierte Kommunikation anstelle von globalen Variablen zu verwenden. Machen Sie sich also auf den nächsten Artikel gefasst, denn er wird sehr interessant sein.


Demo-Video


Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/12086

Beigefügte Dateien |
Anexo.zip (420.65 KB)
Die Übertragung der Trading-Signale in einem universalen Expert Advisor. Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
In diesem Artikel wurden die verschiedenen Möglichkeiten beschrieben, um die Trading-Signale von einem Signalmodul des universalen EAs zum Steuermodul der Positionen und Orders zu übertragen. Es wurden die seriellen und parallelen Interfaces betrachtet.
Neuronale Netze im Handel: Zustandsraummodelle Neuronale Netze im Handel: Zustandsraummodelle
Ein Großteil der bisher untersuchten Modelle basiert auf der Transformer-Architektur. Bei langen Sequenzen können sie jedoch ineffizient sein. In diesem Artikel werden wir uns mit einer alternativen Richtung der Zeitreihenprognose auf der Grundlage von Zustandsraummodellen vertraut machen.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
William-Gann-Methoden (Teil III): Funktioniert Astrologie? William-Gann-Methoden (Teil III): Funktioniert Astrologie?
Beeinflussen die Positionen von Planeten und Sternen die Finanzmärkte? Bewaffnen wir uns mit Statistiken und Big Data und begeben wir uns auf eine spannende Reise in die Welt, in der sich Sterne und Aktiencharts kreuzen.