English Русский 中文 Español Deutsch Português
preview
リプレイシステムの開発(第60回):サービスの再生(I)

リプレイシステムの開発(第60回):サービスの再生(I)

MetaTrader 5 | 26 3月 2025, 12:16
51 0
Daniel Jose
Daniel Jose

はじめに

前回の「リプレイシステムの開発(第59回):新たな未来」では、コントロールインジケーターとマウスインジケーターモジュールに加えられた変更のいくつかを紹介し、説明しました。これらのアップデートにより、特にマウスインジケーターモジュールについては、将来的に使用できる可能性がいくつか提供されますが、まだ小さな欠陥が残っています。ただし、この欠陥は現段階では影響しません。これは、現時点では、リプレイ/シミュレーターサービスクラスコード内のカプセル化に関連する残りの問題に対処することに重点を置いているためです。

以前の記事で説明したように、すでにいくつかの調整を行ってきましたが、カプセル化の問題を完全に解決するには不十分でした。クラス内から要素が漏れている状態がまだ残っているので、これをすぐに修正する必要があります。さもなければ、すぐに深刻な問題を引き起こすことになります。非表示のままにしておくべき内部要素にアクセス可能になるカプセル化の問題に対処することに加えて、一部の変数や情報へのより適切なアクセスを確保するために、コードの特定の側面をリファクタリングする必要もあります。これらすべてについてはこの記事で説明します。


物事を直し始める

さて、最初に取り組む問題は、C_FilesTicksクラスのカプセル化の問題です。このクラスはファイルC_FilesTicks.mqhにあり、現在のところ、少なくとも現在の方法では直接アクセスできないはずの情報が公開されています。

この漏れの問題を解決するために、比較的単純な解決策を適用します。以下に、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. //+------------------------------------------------------------------+

C_FilesTicks.mqhファイルのソースコード

もちろん、21行目に取り消し線が引かれている部分を除けば、大きな違いに気付かない可能性が高いでしょう。

その取り消し線が引かれた要素は、まさに私たちが対処していたデータ漏洩を表しています。つまり、すべてが安全で完全に機能しているように見えても、ロードまたはシミュレートされたティックを処理するときになると、深刻なセキュリティ問題に直面することになります。どのクラスでもその構造内のデータを変更できるからです。構造体がprotected句内にあるため、クラスのスコープ外ではこのような変更はおこなえないことに注意してください。前回の記事では、これらの句がクラスメンバーへのアクセスレベルをどのように決定するかについて説明しました。

ただし、その変数を削除し、構造自体の使用のみを許可することで、はるかに許容できるレベルのセキュリティとカプセル化を実現できます。しかし、この変更をおこなうことで、私たちは別の決断を下さなければなりません。決定は次のとおりです。今度はprivateとして新しい変数を宣言して構造を内部的に維持し、この変数にアクセスするための関数を追加します。または、同じ変数を他の場所で宣言し、その内容にアクセスする必要がある関数に引数として渡します。

奇妙に聞こえるかもしれませんが、すべてのケースには独自の背景、結果、そして可能性があります。プロジェクトの性質を考慮して、最初のアプローチに従うことにしました。つまり、58行目にあるprivate変数を作成します。これ以外に、コードに大きな変更を加える必要はありませんでした。ただし、この変数をpublicアクセスから削除すると(この変数は他の場所でも使用しており、読み取りだけでなく、データの修正もおこなっています。これについては後ほど説明します)、構造体の適切な初期化を確実におこなう必要があります。このため、118行目にコンストラクタが出現することがわかります。これは非常に単純で、以前は別の場所で実行されていたタスクを実行するものであることに注意してください。C_FilesTicksクラス内で修正したばかりのカプセル化の欠陥によって漏れが発生していました。

さらに、前述したように、この変数にアクセスしてそのデータを読み取る方法も提供する必要があります。これは、199行目に示されている関数によって実現されます。構造体内のデータは読み取ることはできるが書き込むことはできないことに、細心の注意を払ってください。これは関数の宣言方法によるもので、戻り値は定数として扱う必要があります。このアプローチの使用については、本連載の以前の記事のいくつかで説明しました。疑問がある場合は、遠慮なくそれらを振り返って明確にしてください。

問題の1つを解決したので、今度は、先ほど修正した問題と多少関連する別の問題に対処する必要があります。この2番目の問題は、C_ConfigService.mqhファイルにあるC_ConfigServiceクラスにあります。このヘッダーファイルにはいくつかの調整が必要でした。主な理由は、C_FilesTicksからのデータの漏れがなくなったためです。また、C_ConfigServiceで宣言された一部の要素は、このクラス内では不要になったため削除され、代わりに他の場所で使用されるようになります。

したがって、C_ConfigService.mqhファイルの更新されたコードは次のようになります。

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. //+------------------------------------------------------------------+

C_ConfigService.mqhファイルのソースコード

いくつかの変更が加えられましたが、その中には微妙なものもあれば、取り消し線で消された部分のようにより明白なものもありました。しかし、ティックデータに直接アクセスしなくなったことに気付くはずです。これを処理する新しい方法は34行目にあります。スクリプトファイルで構成されたデータやティックデータファイルまたはバーデータファイルに含まれるデータを変更することはできなくなったため、このデータに、より洗練された、そして最も重要なことに、より安全な方法でアクセスできるようになったことに注目してください。したがって、今でははるかに安全で安定したシステムがあり、プログラミングがかなり簡単になり、重大なエラーが発生する可能性がはるかに低くなったと断言できます。

それに加えて、読者の皆様にもう1つの点に注目していただきたいと思います。23行目から27行目に注目してください。privateグローバル変数が削除されています。それらのいくつかは、15行目から始まる構造内に移動されました。しかし、グローバル変数とprivate変数に関するこの変更には理由があります。特定の要素をリプレイ/シミュレーションサービスの一般的な構成の一部にしたくない(必要がないため)ということです。

23行目と27行目の間に見られるこの変更により、コードのいくつかの部分を調整する必要がありました。ただし、これらは単純な変更であり、理解しやすいため、特別な注意を払う必要はありません。それでも、180行目にあるクラスコンストラクタには細心の注意を払ってください。このコンストラクタのコードが若干変更されていることに注意してください。ただし、これがこのヘッダーファイルについて議論する価値がある主なポイントではありません。重要なポイントは、取り消し線が引かれている行207と行209の間にあります。

C_FileTicksクラスのカプセル化の問題を修正する前は、protected部分にあったm_Ticks変数が、これらの行内で実際にインスタンス化、またはより正確に言えば初期化されていました。

この状況がいかに危険であったかに注目してください。コードは非常に安定して実行されていましたが、特に後で大きな変更を導入する予定があった場合、真に安全ではありませんでした。これは私のミスであり、現在まで続いています。しかし、非常に特殊な方法で動作させる必要があるため、コードのどこからでも情報が漏洩したり変更されたりすることは許されません。

これらの修正により、ティックデータ、バーデータを維持し、リプレイ/シミュレーションで使用するアセットを構成するクラスは、データ漏洩がなく、完全に保護されるようになりました。つまり、リプレイ/シミュレーターサービスで使用されるデータの読み込み、シミュレーション、アセット構成に関連するシステムのこの部分は、これで完全に閉じられました。ここからは、サービスが適切に動作するようにすること、つまり開始、一時停止、さらにユーザーが特定の時点からリプレイ/シミュレーションを開始するように設定できるようにすることに集中できます。

これらの機能は、これらすべての変更がおこなわれる前からすでに存在していました。しかし、今後はより安全、シンプル、安定した方法で実装していきます。また、近い将来に取り組むコンポーネント間の相互作用も改善していきます。


ユーザーがリプレイ/シミュレーションを再生および一時停止できるようにする

このセクションでは、最終的に、ユーザーが再生ボタンと一時停止ボタンをクリックしたときにサービスが応答するようにする方法を説明します。これにより、リプレイ機能とシミュレーション機能の両方が有効になります。

しかしその前に、読者の皆様に、コントロールインジケーターモジュールに加えられた2つの小さな更新についてお見せしたいと思います。これらのアップデートは、システムの実行中に時々発生する不快な問題を防ぐことを目的としています。これはランダムな問題であるため、システムを複数回実行しても異常が起こらない可能性があり、再現が難しい場合があります。しかし、時々、奇妙なことが起こります。このようなランダムなクラッシュによって引き起こされるフラストレーションを回避するために、コントロールモジュールのコードにいくつかの調整を加えました。

したがって、ヘッダーファイルC_Replay.mqhで、以下のフラグメントに示す関数とプロシージャを更新します。まず次の点から始めましょう。

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. //+------------------------------------------------------------------+

C_Controls.mqhファイルからのコード

元のコードに77行目を追加する必要があります。こうすることで、コントロールインジケーターモジュールは常に何をすべきかを認識し、再生ボタンが表示されている場合はスライダーバーを表示します。つまり、一時停止モードのときは、チャートにスライダーバーが表示されます。これが発生しないこともありましたが、今回のアップデートによりバーは常に表示されるようになります。

さらにもう1つ変更が必要です。これは、以下に示すコード部分でおこなわれる必要があります。

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:

C_Controls.mqhファイルからのコード

このセクションでは、元のコードを変更し、180行目と185行目の間にある内容を挿入する必要があります。この変更の目的は、時々発生する特に厄介な問題に対して、コントロールインジケーターモジュールの耐性を高めることです。この問題には、179行目で実行をトリガーするカスタムイベントが関係しており、これが誤った動作につながることがあります。そのような動作の1つは、配列のインデックス1に誤った値が到着することです。元々、これが発生すると、エラーが報告され、コントロールモジュールがチャートから削除され、リプレイ/シミュレーションサービス全体がクラッシュしていました。つまり、無効なデータが表示されるランダムな実行時障害により、サービスが終了することになります。

新しい実装では、障害が発生すると、コントロールインジケーターモジュールはすぐに一時停止モードになります。これは、リプレイ/シミュレーションシステムの完全な障害よりもはるかに優れています。さらに、もう1つ重要な詳細があります。気づいていないかもしれませんが、181行目に小さなチェックがあります。これにより、無効な値が表示されても、double値に存在する8バイトのうち最初の2バイトに特定の値が含まれている場合にのみ、その値が使用されます。これらの値が存在しない場合は、イベントはコントロールインジケーターモジュールによって単に無視されます。このタイプの検証により、データの整合性が確保され、少なくともデータが特定のソース(リプレイ/シミュレーションサービス)から取得されたことが確認されます。

これら2つの変更が完了したら、インジケーターのソースコードにもう1つ変更を加える必要があります。この最終的な必要な更新は、次のフラグメントで確認できます。

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. //+------------------------------------------------------------------+

コントロールインジケーターのソースコードの一部

取り消し線はソースコードから削除する必要があります。したがって、コントロールモジュールのコンパイル済み実行可能ファイルを削除して、サービスを再度コンパイルしようとしたときにサービスが強制的に再コンパイルされるようにします。または、必要に応じて、コントロールモジュールを手動で再コンパイルし、最新の更新バージョンがリプレイ/シミュレーションサービスのリソースとして適切に含まれていることを確認します。

ここで、リプレイ/シミュレーションサービスコード自体に進むことができます。C_Replay.mqhヘッダーファイルにおこなわれた最後の更新に従って、これまで存在していたコードにいくつかの変更を加えていきます。これらの変更は以前の記事で実装されました。当時の唯一の機能面は、コントロールモジュールとマウスモジュールの他に、システムによってロードされたデータの表示でした。ただし、これ以上の操作は不可能であり、リプレイ/シミュレーションを再生または一時停止して新しいデータを動的に表示することはできませんでした。

しかし、ここで私たちはその現実を変えていきます。それでは、更新されたC_Replay.mqhヘッダーファイルを見てみましょう。以下にファイル全体が掲載されています。

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. //+------------------------------------------------------------------+

C_Replay.mqhファイルのソースコード

このコードにはいくつか重要な変更が加えられているため、何がおこなわれ、これらの変更が何を表しているかについても説明します。これは、システムの操作が明確に理解されていることを確認するために必要です。このバージョンには、この記事の最後で説明するいくつかの問題が含まれているため、これは特に重要です。

まず、情報を構造に整理することにしました。こうすることで、情報を分離するのがより簡単かつ論理的になります。20行目に注意してください。ここで、コントロールインジケーターモジュールにアクセスするためのハンドルを宣言しています。これにより、サービスの有効期間を通じて継続的に使用される値を取得するためだけの大量の呼び出しを回避できます。したがって、特にコントロールモジュールへの呼び出しを多数実行することになるため、他の方法でこの値にアクセスしても意味がありません。これらの通話間の時間の節約は非常に有益です。これがなぜ重要なのかはすぐに分かるでしょう。

33行目から54行目までの手順は、コマンドモジュールのバッファにアクセスするためのハンドルを取得および解放するコードが削除されたことを除いて、大きな変更はありません。それ以外では、C_Replay.mqhで更新されたフラグメントに直接関連する1つの変更のみがおこなわれました。

50行目と51行目では、C_Replayクラス内のカスタムイベントハンドラーで期待される同じ情報を割り当てています。これにより、サービスによって渡されたデータがコントロールモジュールによって適切に受信されることが保証されます。

次に注目すべき手順は、67行目と93行目の間にあります。この手順は、バーとティックのデータを生成し、カスタム銘柄に送信する役割を担います。つまり、MetaTrader5がチャートに表示する情報を更新する場所です。この手順は、リプレイ/シミュレーションサービスの以前のバージョンに存在していたものとほぼ同じです。当時は、アプリケーション間でメッセージを交換するために、まだターミナルのグローバル変数を使用していました。ただし、C_FileTicksクラスのデータ漏えいの問題を修正したため、ここでいくつかの点を調整する必要がありました。C_FileTicksのデータがどのようにアクセスされるかに注意してください。この例は75行目にあります。これで機能しますが、考慮すべき重要な点が1つあります。それは、各呼び出しによってわずかな遅延が発生するということです。この点については後ほど説明します。

また、サービスの古いバージョンに存在していた別の手順を追加する必要もありました。これは95行目と120行目の間にあり、価格または見積行の表示を初期化することを目的としています。C_Replay.mqhが登場した前回の記事では、この手順は存在しませんでした。このため、リプレイ/シミュレーターがMetaTrader5にカスタム銘柄チャートを開くように指示したときに、価格ラインが表示されませんでした。この手順により、その問題は解決されます。ただし、予想と異なり、この手順は1回だけ呼び出されます。「再生」フェーズでは、67行目にあるCreateBarInReplayプロシージャがこの表示を最新の状態に保つ役割を担います。具体的には、このタスクはそのプロシージャ内の89行目で実行されます。

先に進むと、クラスコンストラクタに到達します。ここでは、初期化行のみが追加されました。これらの行は138行目と141行目の間にあります。他には何も変更されていません。ただし、クラスデストラクタでは、ハンドルを解放する146に新しい行が表示されます。おそらく重要ではないかもしれませんが(チャートを閉じるといずれにせよサービスが終了するため)、ハンドルが不要になったことをMetaTrader5に明示的に通知することをお勧めします。このため、この行が追加されました。

ハンドルを解放するということは、どこかで取得したに違いありません。実際、これは171行目と188行目の間の手順で発生します。178行目には、以前は存在しなかった価格/見積り行を初期化する手順が呼び出されていることに注意してください。次に、180行目で、コントロールインジケーターのバッファにアクセスするために必要なハンドルを取得します。残りは変更されません。

最後に、190行目から215行目にあるこのクラスの最後の関数に到達します。これが一番厄介なことかもしれません。これは、静かな時間に実行できる他のプロシージャや関数とは異なり(1秒または2秒かかる手順は妥当な範囲内で許容される場合があります)、この関数ではそのような柔軟性が考慮されないためです。ここではリアルタイム実行が重要であり、遅延や不正確さはサービスに大きな影響を与える可能性があります。

以前、ターミナルのグローバル変数を使用していたとき、この同じ機能が正しく動作しないという大きな問題が発生していました。それを機能させるためには、抜本的な対策を講じる必要がありました。今では別のアプローチを使用しているため、LoopEventOnTimeとして知られる古い「頭痛の種」に再び悩まされることになります。

しかし、この課題に取り組む前に、今何をしているのかを分析してみましょう。

194行目から198行目にかけてループがあります。これは、ユーザーがコントロールモジュール(マウスモジュール経由)と対話して再生モードをトリガーするのを待つことが目的であるため、問題にはなりません。ユーザーがチャートを閉じるかサービスを停止すると、def_CheckLoopServiceチェックによりループは終了します。この定義は9行目で宣言されており、チャートが閉じられたか、サービスが終了したかを常にチェックしていることがわかります。一般に、このループによって問題が発生することはありません。

ただし、次の200行目と212行目の間のループについては同じことが言えません。これは新しいことなので、問題が発生していると思われるかもしれません。しかし、これはターミナルのグローバル変数を操作するために使用していた古いループに基づいています。ただし、これは以前のバージョンに比べて簡素化されています。しかし、以前の機能を完全に再現するには、まだいくつかの重要な要素が欠けています。

このループには内部ループが含まれており、前に説明したように、この内部構造は非常に重要です。特定の状況では、これがないとシステムがフリーズし、サービスを「解放」するためのティックを無期限に待機することになります。より詳しい説明については、リプレイ/シミュレーターシステムの構築に関する本連載の初期の記事を参照してください。

一見すると、このコードは、リプレイ/シミュレーションの開始点を調整するためのロジックが欠けていることを除けば完璧に見えるかもしれません。楽観的なのは良いことですが、結果を評価する前に、サービスコードを示したいと思います。前回の記事から変更はありませんが、実際の問題がどこにあるのかを理解するために不可欠です。

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. //+------------------------------------------------------------------+

サービスのソースコード

ご覧のとおり、コードには特に大きな変更はありません。 


結論

リプレイ/シミュレーションサービスを自分でコンパイルしてテストする必要がないように、ビデオデモを添付します。サービスのパフォーマンスが大幅に低下していることに気付くと思うので、注意深く見てください。なぜこのようなエラーが発生するのでしょうか。コード内で関数呼び出しが大幅に増加し、変数の使用が減少したことが主な理由です。しかし、それが唯一の要因ではありません。パフォーマンスに影響を与える他の側面もあります。これらの要素の説明、分析、テストには時間と忍耐が必要なので、詳細については今後の記事で取り上げることにします。

ただし、主な焦点は、LoopEventOnTime関数内のループにあります。いずれにせよ、リプレイまたはシミュレーションの生成と起動を担当するこのループを最適化して、より効率的に動作させる必要があります。この記事の前半で述べたように、高いパフォーマンスが最も重要となる瞬間に、いくつかの側面がサービスパフォーマンスを低下させています。グローバル端末変数が使用されていたフェーズでは、パフォーマンスは許容範囲内でした。しかし、ビデオで見る限り、この新しいモデルの実用性は受け入れられないものです。

これは、現在のシステムが信頼できないことを示唆しています。しかし、プログラマーとしては、パフォーマンスの問題にも対処する必要があるため、単純な計算ロジックをはるかに超えた問題を解決しなければならないことがよくあります。全体的に、新しいクラスベースのシステムにより、コードの安定性とセキュリティが大幅に向上しました。私たちの現在の本当の課題は、グローバル変数の代わりにイベントとバッファベースの通信を使用しながら、以前のパフォーマンスレベル(または少なくともそれに近いレベル)を回復することです。次の記事は非常に興味深いものになるので、ぜひお読みください。


デモビデオ


MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/12086

添付されたファイル |
Anexo.zip (420.65 KB)
初級から中級へ:値渡しまたは参照渡し 初級から中級へ:値渡しまたは参照渡し
この記事では、値渡しと参照渡しの違いを実際の例を通じて理解します。これは単純で一般的な概念であり、特に問題を引き起こすようには思えませんが、多くの経験豊富なプログラマーでさえ、この小さな違いのためにコードの作成中に思わぬ失敗をすることがあります。値渡しまたは参照渡しをいつ、どのように、なぜ使用するかを知ることは、プログラマーとしての私たちの生活に大きな違いをもたらします。ここで提示されるコンテンツは、教育目的のみを目的としています。いかなる状況においても、提示された概念を学習し習得する以外の目的でアプリケーションを閲覧することは避けてください。
取引におけるニューラルネットワーク:状態空間モデル 取引におけるニューラルネットワーク:状態空間モデル
これまでにレビューしたモデルの多くは、Transformerアーキテクチャに基づいています。ただし、長いシーケンスを処理する場合には非効率的になる可能性があります。この記事では、状態空間モデルに基づく時系列予測の別の方向性について説明します。
取引におけるニューラルネットワーク:複雑な軌道予測法(Traj-LLM) 取引におけるニューラルネットワーク:複雑な軌道予測法(Traj-LLM)
この記事では、自動運転車の動作の分野における問題を解決するために開発された興味深い軌道予測方法を紹介します。この手法の著者は、さまざまな建築ソリューションの最良の要素を組み合わせました。
人工藻類アルゴリズム(AAA) 人工藻類アルゴリズム(AAA)
本稿では、微細藻類に特徴的な生物学的プロセスに基づく人工藻類アルゴリズム(AAA)について考察します。このアルゴリズムには、螺旋運動、進化過程、適応過程が含まれており、最適化問題を解くことができます。この記事では、AAAが機能する原理と、数学的モデリングにおけるその可能性について詳しく分析し、自然とアルゴリズムによる解とのつながりを強調しています。