
リプレイシステムの開発(第50回):物事は複雑になる(II)
はじめに
前回の「リプレイシステムの開発(第49回):物事は複雑になる(I)」稿では、リプレイ/シミュレーターシステム内の状況がさらに複雑化し始めました。この複雑化は意図的ではありませんが、システムの安定性とセキュリティを向上させることを目的としています。これは、完全にモジュール化されたモデルでシステムを利用できる可能性に関連しています。
これらの変更は、最初はまったく不必要に思えるかもしれませんが、リプレイ/シミュレーターシステムにのみ必要な特定の部分を保護することで、開発中の機能の一部がユーザーによって悪用されるのを防ぎます。こうすることで、経験の浅いユーザーがリプレイ/シミュレーターサービスに必要なチャートの一部を誤って削除したり、不適切に構成したりすることを防止します。これは、サービスの実行中に発生する可能性があります。
前回の記事の最後に、コントロール指標が不安定になる問題があることを報告しました。これにより、実際には発生しないはずの事象が起こる可能性があります。これらの問題は深刻ではなく、プラットフォームをクラッシュさせることはありませんが、予期しないサービス障害を引き起こす可能性があります。そのため、前回の記事にはファイルを添付しませんでした。そうしなければ、現在の開発段階でシステムにアクセスすることになってしまいます。
この記事では、既存の問題を修正するか、少なくとも修正を試みます。1つは修正する予定です。
最初の問題を解決する
最初の問題は、リプレイ/シミュレーターサービスがテンプレートを使用するように実装されていたことです。テンプレートには、チャートを開いてサービスを開始するために必要なすべてのデータが含まれていると想定されていました。
このアプローチは機能的で魅力的に思えますが、ユーザーにとっては制限があります。簡単に言えば、サービスが特定のテンプレートを使用することで、ユーザーは独自の方法や希望するチャート構成を利用できなくなります。特定の資産を取引するたびに手動で設定するよりも、必要なものがすべて含まれたユーザー作成のテンプレートを使用してチャートを構成する方がはるかに便利です。
市場初心者にはあまり理解できないかもしれませんが、経験豊富なトレーダーは、特定の時点における資産の標準的な分析をおこなうためのテンプレートを事前に設定しています。トレーダーは、すべてを事前に準備するために、数か月または数年をかけてこれらのテンプレートを開発します。このようにして、トレーダーはテンプレートを保存し、必要に応じてチャートに適用するだけで済みます。
同様の点は「リプレイシステムの開発(第48回):いくつかの概念を理解する」稿でも示されており、リプレイ/シミュレーターシステムの変更に関するすべての作業はここから始まりました。その記事では、テンプレートではなくサービスまたは標準チャートを使用してチャートを設定する方法を示しました。これにより、使用するテンプレートに関係なく特定の要素がチャート上に表示されるようになります。この設定は、作業を標準化するために、MetaTrader 5で実行されるサービスによって常にサポートされています。
ただし、コントロール指標がサービスを管理できるように、必要なグラフィックオブジェクトをチャート上に配置するには、これらのオブジェクトを受け取るチャートIDなどの重要な情報が必要です。
ChartID()関数を使用するだけで、インジケーターにこれを認識させるのは簡単だと思われるかもしれません。無知は幸福であると言う人もいます。誤解しないでください。私自身も、特定の時間に何かが正しく動作しない理由を解明しようとするたびに頭を悩ませてきました。ですから、私は間違っていないと思います。
実際、ChartID() 関数を使用すると、チャートIDが確実に返されるので、そのチャートにオブジェクトを配置できます。覚えておいてください。オブジェクトがどのチャートに添付されるかをMetaTrader 5に通知するには、IDが必要です。
しかし、チャートがサービスを通じて開かれる場合、ChartID関数は機能しません。つまり、サービスがC_Replay.mqhクラスを使用し、183行目のコードを実行すると、別のIDが作成されます。このコードは前回の記事で紹介しました。同じ183行目で、ChartOpenを呼び出してリプレイ/シミュレーションが実行される銘柄のチャートを作成します。
ChartOpenサービスによって返される値とコントロール指標のChartID値を比較すると、異なることがわかります。これは、MetaTrader 5プラットフォームがどのIDを使用するかを把握していないことを意味します。ChartIDによって返されるIDを使用すると、オブジェクトが間違ったウィンドウ、または存在しないウィンドウに配置されますが、サービス内で生成されたIDを使用すると、ChartOpenがIDを作成するとすぐにオブジェクトを利用できるようになります。
ここで問題が発生します:チャートIDの問題を解決する最善の方法は何でしょうか。ChartOpenを呼び出して取得したID値を使用しないのはなぜかと考えるかもしれません。しかし、ここに問題があります。前回の記事では、ChartOpenで生成されたチャートIDをコントロール指標に渡す役割を担っていたグローバル端末変数を削除しました。
その後、チャートIDを取得するタスクがC_Terminalクラスコードに転送されました。これは、ChartID関数を使用して実行されます。本連載従ってコードを更新していれば、C_Terminalクラスコードは次のようになります。
59. //+------------------------------------------------------------------+ 60. C_Terminal(const long id = 0) 61. { 62. m_Infos.ID = (id == 0 ? ChartID() : id); 63. m_Mem.AccountLock = false; 64. CurrentSymbol(); 65. m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR); 66. m_Mem.Show_Date = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE); 67. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false); 68. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, true); 69. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true); 70. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false); 71. m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 72. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 73. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 74. m_Infos.PointPerTick = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); 75. m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); 76. m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP); 77. m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick; 78. m_Infos.ChartMode = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE); 79. if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)); 80. ResetLastError(); 81. } 82. //+------------------------------------------------------------------+ 83.
C_Terminal.mqhソースコードの抜粋
このコードでは、60行目にC_Terminalクラスのコンストラクタが含まれています。このコンストラクタはデフォルト値としてNULLを取得し、通常のコンストラクタのように機能します。実際の問題は62行目で発生します。コンストラクタに渡された値をチェックする際に、使用するチャートIDを決定します。この値がデフォルトの状態で使用される場合、C_TerminalクラスはChartIDによって返された値を使用してMetaTrader 5にチャートIDを提供するように要求します。しかし、この呼び出しがおこなわれると、返される値は不正確になります。これは、サービスがチャートを作成し、指標を開始し、その指標がID値を見つけるためにC_Terminalを呼び出すからです。
したがって、C_TerminalクラスのコンストラクタにID値を渡すことが可能です。この場合、ChartIDの呼び出しは無視され、コンストラクタに渡されたIDが指標によって使用されることになります。
もう一度強調しておきますが、この値を指標に渡すためにグローバル端末変数は使用しなくなります。一時的な解決策としては機能するかもしれませんが、真の解決策は異なります。指標呼び出しのパラメータを通じてID値を渡す方法を採用します。
ソリューションの実装
これからおこなうことに驚かれるかもしれませんが、それは前回の記事でソリューションの実装方法と適用前の仕組みについて説明していなかったためです。システムの動作を示すビデオ 01 をご覧ください。これは、IDがコントロール指標にパラメータとして渡される前のものです。
ビデオ 01
エラーメッセージが表示されることに気付くかもしれません。このメッセージは、指標がチャートIDを認識できないために表示されます。そのため、ChartID呼び出しはエラーを返し、指標コードがこれをチェックします(上記の指標コードの25おこな目を参照)。ただし、このコードを見ると、コードとビデオの間に違いがあることに気付くでしょう。確かに、いくつかの点が異なります。しかし、心配しないでください。すぐにビデオに示されているコードにアクセスできるようになるので、すべてがより信頼性が高くなります。ビデオで確認できるものと上記のコードとの違いは、当時、指標がチャートに存在するとリストされているのに、チャートに表示されない理由が理解できなかったことです。
なぜ正しく動作しないのかを突き止めるために、コードを変更する必要がありました。だから、「無知は幸福である」と言ったとしても気分を害さないでくださいとお願いしたのです。また、コードがなぜこのように動作するのか理解できませんでした。
ただし、すべてが完全に修正されたと主張するつもりはありません。それは私の不誠実な行為だからです。チャートから指標に ID を渡すと、指標が表示されます。ただし...その「ただし」について検討する前に、コードを変更してすべてが再び機能するようにする方法を見てみましょう。少なくとも、コントロール指標はチャート上に表示されます。
サービスのソースコードを変更する必要はありませんでしたが、C_Replay.mqh ヘッダー ファイルのコードを変更する必要がありました。変更されたファイルの全文を以下に示します。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_ConfigService.mqh" 005. //+------------------------------------------------------------------+ 006. class C_Replay : private C_ConfigService 007. { 008. private : 009. long m_IdReplay; 010. struct st01 011. { 012. MqlRates Rate[1]; 013. datetime memDT; 014. }m_MountBar; 015. struct st02 016. { 017. bool bInit; 018. double PointsPerTick; 019. MqlTick tick[1]; 020. }m_Infos; 021. //+------------------------------------------------------------------+ 022. void AdjustPositionToReplay(const bool bViewBuider) 023. { 024. u_Interprocess Info; 025. MqlRates Rate[def_BarsDiary]; 026. int iPos, nCount; 027. 028. Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); 029. if (Info.s_Infos.iPosShift == (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks)) return; 030. iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1))); 031. Rate[0].time = macroRemoveSec(m_Ticks.Info[iPos].time); 032. CreateBarInReplay(true); 033. if (bViewBuider) 034. { 035. Info.s_Infos.isWait = true; 036. GlobalVariableSet(def_GlobalVariableReplay, Info.df_Value); 037. }else 038. { 039. for(; Rate[0].time > (m_Ticks.Info[m_ReplayCount].time); m_ReplayCount++); 040. for (nCount = 0; m_Ticks.Rate[nCount].time < macroRemoveSec(m_Ticks.Info[iPos].time); nCount++); 041. nCount = CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, nCount); 042. } 043. for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag);) CreateBarInReplay(false); 044. CustomTicksAdd(def_SymbolReplay, m_Ticks.Info, m_ReplayCount); 045. Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); 046. Info.s_Infos.isWait = false; 047. GlobalVariableSet(def_GlobalVariableReplay, Info.df_Value); 048. } 049. //+------------------------------------------------------------------+ 050. inline void CreateBarInReplay(const bool bViewTicks) 051. { 052. #define def_Rate m_MountBar.Rate[0] 053. 054. bool bNew; 055. double dSpread; 056. int iRand = rand(); 057. 058. if (BuildBar1Min(m_ReplayCount, def_Rate, bNew)) 059. { 060. m_Infos.tick[0] = m_Ticks.Info[m_ReplayCount]; 061. if ((!m_Ticks.bTickReal) && (m_Ticks.ModePlot == PRICE_EXCHANGE)) 062. { 063. dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 ); 064. if (m_Infos.tick[0].last > m_Infos.tick[0].ask) 065. { 066. m_Infos.tick[0].ask = m_Infos.tick[0].last; 067. m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread; 068. }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid) 069. { 070. m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread; 071. m_Infos.tick[0].bid = m_Infos.tick[0].last; 072. } 073. } 074. if (bViewTicks) CustomTicksAdd(def_SymbolReplay, m_Infos.tick); 075. CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate); 076. } 077. m_ReplayCount++; 078. #undef def_Rate 079. } 080. //+------------------------------------------------------------------+ 081. void ViewInfos(void) 082. { 083. MqlRates Rate[1]; 084. 085. ChartSetInteger(m_IdReplay, CHART_SHOW_ASK_LINE, m_Ticks.ModePlot == PRICE_FOREX); 086. ChartSetInteger(m_IdReplay, CHART_SHOW_BID_LINE, m_Ticks.ModePlot == PRICE_FOREX); 087. ChartSetInteger(m_IdReplay, CHART_SHOW_LAST_LINE, m_Ticks.ModePlot == PRICE_EXCHANGE); 088. m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE); 089. m_MountBar.Rate[0].time = 0; 090. m_Infos.bInit = true; 091. CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, Rate); 092. if ((m_ReplayCount == 0) && (m_Ticks.ModePlot == PRICE_EXCHANGE)) 093. for (; m_Ticks.Info[m_ReplayCount].volume_real == 0; m_ReplayCount++); 094. if (Rate[0].close > 0) 095. { 096. if (m_Ticks.ModePlot == PRICE_EXCHANGE) m_Infos.tick[0].last = Rate[0].close; else 097. { 098. m_Infos.tick[0].bid = Rate[0].close; 099. m_Infos.tick[0].ask = Rate[0].close + (Rate[0].spread * m_Infos.PointsPerTick); 100. } 101. m_Infos.tick[0].time = Rate[0].time; 102. m_Infos.tick[0].time_msc = Rate[0].time * 1000; 103. }else 104. m_Infos.tick[0] = m_Ticks.Info[m_ReplayCount]; 105. CustomTicksAdd(def_SymbolReplay, m_Infos.tick); 106. ChartRedraw(m_IdReplay); 107. } 108. //+------------------------------------------------------------------+ 109. void CreateGlobalVariable(const string szName, const double value) 110. { 111. GlobalVariableDel(szName); 112. GlobalVariableTemp(szName); 113. GlobalVariableSet(szName, value); 114. } 115. //+------------------------------------------------------------------+ 116. void AddIndicatorControl(void) 117. { 118. int handle; 119. 120. handle = iCustom(ChartSymbol(m_IdReplay), ChartPeriod(m_IdReplay), "::" + def_IndicatorControl, m_IdReplay); 121. ChartIndicatorAdd(m_IdReplay, 0, handle); 122. IndicatorRelease(handle); 123. } 124. //+------------------------------------------------------------------+ 125. public : 126. //+------------------------------------------------------------------+ 127. C_Replay(const string szFileConfig) 128. { 129. m_ReplayCount = 0; 130. m_dtPrevLoading = 0; 131. m_Ticks.nTicks = 0; 132. m_Infos.bInit = false; 133. Print("************** Market Replay Service **************"); 134. srand(GetTickCount()); 135. GlobalVariableDel(def_GlobalVariableReplay); 136. SymbolSelect(def_SymbolReplay, false); 137. CustomSymbolDelete(def_SymbolReplay); 138. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol); 139. CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX); 140. CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX); 141. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0); 142. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0); 143. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0); 144. CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation"); 145. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8); 146. m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1); 147. SymbolSelect(def_SymbolReplay, true); 148. } 149. //+------------------------------------------------------------------+ 150. ~C_Replay() 151. { 152. ArrayFree(m_Ticks.Info); 153. ArrayFree(m_Ticks.Rate); 154. m_IdReplay = ChartFirst(); 155. do 156. { 157. if (ChartSymbol(m_IdReplay) == def_SymbolReplay) 158. ChartClose(m_IdReplay); 159. }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); 160. for (int c0 = 0; (c0 < 2) && (!SymbolSelect(def_SymbolReplay, false)); c0++); 161. CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX); 162. CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX); 163. CustomSymbolDelete(def_SymbolReplay); 164. GlobalVariableDel(def_GlobalVariableReplay); 165. GlobalVariableDel(def_GlobalVariableServerTime); 166. Print("Finished replay service..."); 167. } 168. //+------------------------------------------------------------------+ 169. bool ViewReplay(ENUM_TIMEFRAMES arg1) 170. { 171. #define macroError(A) { Print(A); return false; } 172. u_Interprocess info; 173. 174. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) 175. macroError("Asset configuration is not complete, it remains to declare the size of the ticket."); 176. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) 177. macroError("Asset configuration is not complete, need to declare the ticket value."); 178. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) 179. macroError("Asset configuration not complete, need to declare the minimum volume."); 180. if (m_IdReplay == -1) return false; 181. if ((m_IdReplay = ChartFirst()) > 0) do 182. { 183. if (ChartSymbol(m_IdReplay) == def_SymbolReplay) 184. { 185. ChartClose(m_IdReplay); 186. ChartRedraw(); 187. } 188. }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); 189. Print("Waiting for [Market Replay] indicator permission to start replay ..."); 190. info.ServerTime = ULONG_MAX; 191. CreateGlobalVariable(def_GlobalVariableServerTime, info.df_Value); 192. m_IdReplay = ChartOpen(def_SymbolReplay, arg1); 193. AddIndicatorControl(); 194. while ((!GlobalVariableGet(def_GlobalVariableReplay, info.df_Value)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750); 195. info.s_Infos.isHedging = TypeAccountIsHedging(); 196. info.s_Infos.isSync = true; 197. GlobalVariableSet(def_GlobalVariableReplay, info.df_Value); 198. 199. return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != "")); 200. #undef macroError 201. } 202. //+------------------------------------------------------------------+ 203. bool LoopEventOnTime(const bool bViewBuider) 204. { 205. u_Interprocess Info; 206. int iPos, iTest, iCount; 207. 208. if (!m_Infos.bInit) ViewInfos(); 209. iTest = 0; 210. while ((iTest == 0) && (!_StopFlag)) 211. { 212. iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1); 213. iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.df_Value) ? iTest : -1); 214. iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest); 215. if (iTest == 0) Sleep(100); 216. } 217. if ((iTest < 0) || (_StopFlag)) return false; 218. AdjustPositionToReplay(bViewBuider); 219. Info.ServerTime = m_Ticks.Info[m_ReplayCount].time; 220. GlobalVariableSet(def_GlobalVariableServerTime, Info.df_Value); 221. iPos = iCount = 0; 222. while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag)) 223. { 224. iPos += (int)(m_ReplayCount < (m_Ticks.nTicks - 1) ? m_Ticks.Info[m_ReplayCount + 1].time_msc - m_Ticks.Info[m_ReplayCount].time_msc : 0); 225. CreateBarInReplay(true); 226. while ((iPos > 200) && (!_StopFlag)) 227. { 228. if (ChartSymbol(m_IdReplay) == "") return false; 229. GlobalVariableGet(def_GlobalVariableReplay, Info.df_Value); 230. if (!Info.s_Infos.isPlay) return true; 231. Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks); 232. GlobalVariableSet(def_GlobalVariableReplay, Info.df_Value); 233. Sleep(195); 234. iPos -= 200; 235. iCount++; 236. if (iCount > 4) 237. { 238. iCount = 0; 239. GlobalVariableGet(def_GlobalVariableServerTime, Info.df_Value); 240. if ((m_Ticks.Info[m_ReplayCount].time - m_Ticks.Info[m_ReplayCount - 1].time) > 60) Info.ServerTime = ULONG_MAX; else 241. { 242. Info.ServerTime += 1; 243. Info.ServerTime = ((Info.ServerTime + 1) < m_Ticks.Info[m_ReplayCount].time ? Info.ServerTime : m_Ticks.Info[m_ReplayCount].time); 244. }; 245. GlobalVariableSet(def_GlobalVariableServerTime, Info.df_Value); 246. } 247. } 248. } 249. return (m_ReplayCount == m_Ticks.nTicks); 250. } 251. //+------------------------------------------------------------------+ 252. }; 253. //+------------------------------------------------------------------+ 254. #undef macroRemoveSec 255. #undef def_SymbolReplay 256. //+------------------------------------------------------------------+
C_Replay.mqhクラスのソースコード
このコードは必要なものをサポートするために変更されましたが、近い将来にはさらに多くの機能が追加される予定です。まずは、どのような追加がおこなわれたのかを見ていきましょう。コードの大部分は前回の記事と同じままであることがわかります。実装方法をしっかり理解していただくために、使用する関数の配置を明確にするために、完全なコードを含めました。
116行目には、チャートにコントロール指標を追加する新しい手順が追加されました。の手順は193行目で呼び出されます。つまり、チャートがサービスによって開かれ、MetaTrader 5 によって表示された直後です。それでは、116行目に戻りましょう。最初におこなうことは、120行目でサービスコードにある指標を参照するハンドルを作成することです。指標はリソースとしてサービス実行ファイルに埋め込まれていることを覚えておいてください。MetaTrader 5 に指標の場所を通知した後、いくつかの情報を提供する必要があります。これは、ChartOpen呼び出しによって作成されたチャートIDであるm_IdReplayを表します。
したがって、指標はどのチャートIDが正しいかを認識します。これに注意してください。リプレイ銘柄に関連付けられた別のチャートを開いても、指標は表示されず、サービスによって作成されたチャートにのみ表示されます。これは121行目で強制され、実行されます。次に、122行目で、作成されたハンドルは不要になったため解放します。
しかし、ここで紹介したのはソリューションの一部に過ぎません。残りの部分はコントロール指標のソースコードにあります。以下で確認できます。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico" 04. #property description "Control indicator for the Replay-Simulator service." 05. #property description "This one doesn't work without the service loaded." 06. #property version "1.50" 07. #property link "https://www.mql5.com/ja/articles/11871" 08. #property indicator_chart_window 09. #property indicator_plots 0 10. //+------------------------------------------------------------------+ 11. #include <Market Replay\Service Graphics\C_Controls.mqh> 12. //+------------------------------------------------------------------+ 13. C_Terminal *terminal = NULL; 14. C_Controls *control = NULL; 15. //+------------------------------------------------------------------+ 16. input long user00 = 0; //ID 17. //+------------------------------------------------------------------+ 18. int OnInit() 19. { 20. u_Interprocess Info; 21. 22. ResetLastError(); 23. if (CheckPointer(control = new C_Controls(terminal = new C_Terminal(user00))) == POINTER_INVALID) 24. SetUserError(C_Terminal::ERR_PointerInvalid); 25. if ((!(*terminal).IndicatorCheckPass("Market Replay Control")) || (_LastError != ERR_SUCCESS)) 26. { 27. Print("Control indicator failed on initialization."); 28. return INIT_FAILED; 29. } 30. if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.df_Value = 0; 31. EventChartCustom(user00, C_Controls::ev_WaitOff, 1, Info.df_Value, ""); 32. (*control).Init(Info.s_Infos.isPlay); 33. 34. return INIT_SUCCEEDED; 35. } 36. //+------------------------------------------------------------------+ 37. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 38. { 39. static bool bWait = false; 40. u_Interprocess Info; 41. 42. Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); 43. if (!bWait) 44. { 45. if (Info.s_Infos.isWait) 46. { 47. EventChartCustom(user00, C_Controls::ev_WaitOn, 1, 0, ""); 48. bWait = true; 49. } 50. }else if (!Info.s_Infos.isWait) 51. { 52. EventChartCustom(user00, C_Controls::ev_WaitOff, 1, Info.df_Value, ""); 53. bWait = false; 54. } 55. 56. return rates_total; 57. } 58. //+------------------------------------------------------------------+ 59. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 60. { 61. (*control).DispatchMessage(id, lparam, dparam, sparam); 62. } 63. //+------------------------------------------------------------------+ 64. void OnDeinit(const int reason) 65. { 66. switch (reason) 67. { 68. case REASON_REMOVE: 69. case REASON_CHARTCLOSE: 70. if (ChartSymbol(user00) != def_SymbolReplay) break; 71. GlobalVariableDel(def_GlobalVariableReplay); 72. ChartClose(user00); 73. break; 74. } 75. delete control; 76. delete terminal; 77. } 78. //+------------------------------------------------------------------+
コントロール指標のソースコード
16行目に入力があることに注目してください。これは、指標がパラメータを受け取ることを示しています。このため、ユーザーはこの指標に直接アクセスしたり、手動でチャートに配置したりすることができません。指標が16行目で受け取るパラメータは、オブジェクトが配置されるチャートのIDを示す重要な値です。この値は正しく入力する必要があります。デフォルトではNULLであるため、サービスが指標を配置しようとしてもチャートが提供されない場合、エラーが発生します。エラーメッセージは27行目に表示され、これは予想されたものとビデオ01で提示された内容との違いを説明しています。
次に、16行目に入力されたパラメータの値がどこで使用されているかに注目してください。この値は複数の場所で利用されていますが、特に重要なのは23行目で、チャートIDを検索する際に生成された値を使用しないようにC_Terminalクラスに指示している点です。C_Terminalクラスは、チャートを作成したサービスが報告する値を使用する必要があります。他の部分でも16行目に指定された値が活用されていますが、サービスファイルを再コンパイルし、MetaTrader 5で実行すると、ビデオ02に示される結果が得られます。
ビデオ02
ビデオ02を注意深くご覧ください。ご覧のとおり、システムと対話しようとすると、システムの動作が非常に奇妙になることがあります。問題は、なぜこのようなことが起こるのかということです。この動作を引き起こすような変更はおこなっていません。
これは、解決しなければならない2番目の問題です。ただし、この問題の解決は非常に複雑です。ここでの原因は、サービスやコントロール指標、MetaTrader 5プラットフォームにはありません。実際の原因は、マウス指標とチャート上のオブジェクトの相互作用、またはその欠如にあります。
この時点で、「一見完璧に動作しているコードを変更する前には存在しなかったバグをどうやって修正するのか?」と考えているかもしれません。それは、リプレイ/シミュレーターシステムが長い間使用していたテンプレートではなく、ユーザーがカスタムテンプレートを使用できるようにするという運命的な決定が下される前のことです。これがプログラミングの醍醐味です。新しい要素を導入する際には、予期しない問題が発生し、それを解決し、将来的な問題にも対処する必要があります。
しかし、この問題に取り組む前に、マウス指標がコントロール指標によって作成されたチャートオブジェクトと相互作用する問題を解決するための関数を実装し、ユーザーがカスタムテンプレートを使用できるようにしましょう。これにより、ユーザーはデモ口座またはリアル口座で特定の銘柄に適用することを目的とした事前設定されたテンプレートを利用できるようになります。ただし、ここでは、ユーザーがリプレイ/シミュレーターシステムで同じテンプレートを使用できるようにすることが目的です。
この作業は難しくありませんが、ユーザーはしばしば非論理的な方法で操作をおこなう傾向があるため、コントロール指標がチャート上に残っていることを確認する必要があります。ユーザーがリプレイ/シミュレーターシステムを意図しない方法で使用し続ける場合でも、コントロール指標を保護する必要があります。
このため、最初におこなうべきことは、ユーザーが特定のテンプレートに従ってチャートを開くようにサービスに指示できるようにすることです。これはいくつかの小さな変更で簡単に実現できます。コード全体を繰り返さないように、変更が必要な関数の部分のみを示します。まずはサービスコードを見てみましょう。これがその内容です。
01. //+------------------------------------------------------------------+ 02. #property service 03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico" 04. #property copyright "Daniel Jose" 05. #property version "1.50" 06. #property description "Replay-Simulator service for MT5 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/ja/articles/11871" 10. //+------------------------------------------------------------------+ 11. #define def_IndicatorControl "Indicators\\Replay\\Market Replay.ex5" 12. #resource "\\" + def_IndicatorControl 13. //+------------------------------------------------------------------+ 14. #include <Market Replay\Service Graphics\C_Replay.mqh> 15. //+------------------------------------------------------------------+ 16. input string user00 = "Forex - EURUSD.txt"; //Replay Configuration File. 17. input ENUM_TIMEFRAMES user01 = PERIOD_M5; //Initial Graphic Time. 18. input string user02 = "Default"; //Template File Name 19. //+------------------------------------------------------------------+ 20. void OnStart() 21. { 22. C_Replay *pReplay; 23. 24. pReplay = new C_Replay(user00); 25. if ((*pReplay).ViewReplay(user01, user02)) 26. { 27. Print("Permission granted. Replay service can now be used..."); 28. while ((*pReplay).LoopEventOnTime(false)); 29. } 30. delete pReplay; 31. } 32. //+------------------------------------------------------------------+
サービスのソースコード
新しい行18を追加しました。これにより、サービスがリプレイまたはシミュレーターとして使用するために、銘柄チャートを開く際に使用するテンプレートをユーザーに指定できるオプションが提供されます。この値は、必要な構成をおこなう行25でクラスに渡されます。何が起こっているかを理解するために、この手順の一部を見てみましょう。コード全体を繰り返すのは無駄なので、この部分のみを示します。
175. //+------------------------------------------------------------------+ 176. bool ViewReplay(ENUM_TIMEFRAMES arg1, const string szNameTemplate) 177. { 178. #define macroError(A) { Print(A); return false; } 179. u_Interprocess info; 180. 181. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) 182. macroError("Asset configuration is not complete, it remains to declare the size of the ticket."); 183. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) 184. macroError("Asset configuration is not complete, need to declare the ticket value."); 185. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) 186. macroError("Asset configuration not complete, need to declare the minimum volume."); 187. if (m_IdReplay == -1) return false; 188. if ((m_IdReplay = ChartFirst()) > 0) do 189. { 190. if (ChartSymbol(m_IdReplay) == def_SymbolReplay) 191. { 192. ChartClose(m_IdReplay); 193. ChartRedraw(); 194. } 195. }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); 196. Print("Waiting for [Market Replay] indicator permission to start replay ..."); 197. info.ServerTime = ULONG_MAX; 198. CreateGlobalVariable(def_GlobalVariableServerTime, info.df_Value); 199. m_IdReplay = ChartOpen(def_SymbolReplay, arg1); 200. if (!ChartApplyTemplate(m_IdReplay, szNameTemplate + ".tpl")) 201. Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl"); 202. AddIndicatorControl(); 203. while ((!GlobalVariableGet(def_GlobalVariableReplay, info.df_Value)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750); 204. info.s_Infos.isHedging = TypeAccountIsHedging(); 205. info.s_Infos.isSync = true; 206. GlobalVariableSet(def_GlobalVariableReplay, info.df_Value); 207. 208. return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != "")); 209. #undef macroError 210. } 211. //+------------------------------------------------------------------+
C_Replay.mqhソースコードの抜粋 (更新)
176行目には、どのテンプレートがロードされるかをクラスに通知する追加のパラメータが追加されています。次に、200行目で、ユーザーが提供したテンプレートを適用しようとします。試行が失敗した場合、201行目でユーザーに通知し、デフォルトのテンプレートに切り替えます。ここで、少し説明を中断しましょう。デフォルトでは、MetaTrader 5 は常に定義済みのテンプレートを使用するため、それをロードしてチャートに適用するための新しい呼び出しは必要ありません。
この時点で明確にしておきたい興味深い点があります。複数の銘柄を扱い、特定のテンプレートを使用して各銘柄を対象にした調査を実行したい場合は、モデリング構成ファイルに目的のテンプレートの名前を追加すると良いかもしれません。これは事前に構成され、サービスの開始について通知する必要はありません。ただし、これはそれを実行したい人へのアイデアに過ぎず、このシステムではそれを実行するつもりはありません。さらに、このテンプレートに関する話には別の側面があります。これはまさにプログラマーにとって頭痛の種となる側面です。その側面とはユーザーです。
ユーザーが私たちにとってなぜ問題になるのでしょうか。あまり意味がないかもしれませんが、これは大きな頭痛の種です。MetaTrader 5 にサービスを開始するように指示すれば、適切なタイミングでリプレイ/シミュレーターのチャートテンプレートを変更できるからです。これは、サービスを再起動して変更を適用しなくても発生します。おそらく、問題の本質を理解していないのかもしれません。この場合、テンプレート設定がチャート設定を上書きし、問題はコントロール指標がチャートから削除されることにあります。
ユーザーは、チャート上にコントロール指標を手動で配置することはできませんが、これをおこなう方法は存在します。しかし、ユーザーがMetaTrader 5とMQL5の操作方法を知らない場合、チャート上のコントロール指標を置き換えることができないという事実を考慮に入れる必要があります。このような状況は、まさにユーザーが責任を負うべき障害です。ただし、修正の責任はプログラマーにあります。
テンプレートが変更されると、MetaTrader 5はチャート上のプログラムに対し、テンプレートの変更があったことを通知し、プログラムがアクションを起こせるようにします。MetaTrader 5は次のようにこれを実行します。
void OnDeinit(const int reason) { switch (reason) { case REASON_TEMPLATE: Print("Template change ..."); break;
テンプレートが変更されると、MetaTrader 5でDeInitイベントが発生し、上記のプロシージャが呼び出されます。MetaTrader 5がDeInitイベントを呼び出す原因となった条件を確認し、テンプレートの変更である場合、関連するメッセージが端末に表示されます。
つまり、テンプレートが変更されたかどうかは判別できますが、この認識によってすぐに指標がリセットされるわけではありません。ここで、サービスを強制的に終了するか、サービスにチャート上のコントロール指標を再配置させるかを決定する必要があります。私の主観的な意見では、サービスを強制的に終了するべきです。その理由は簡単です。ユーザーがリプレイ/シミュレーターのチャートで使用されるテンプレートをカスタマイズできるのであれば、ユーザーが手動でテンプレートを変更できるようにするのは無意味です。したがって、私の考えでは、最善の方法はサービスを強制的に終了し、サービスの開始時にユーザーにテンプレートを提供するように依頼することです。そうでなければ、ユーザーがサービスにテンプレートを提供できる必要はありません。
したがって、指標コードに別の簡単な更新を加える必要があります。これは以下に示すとおりです。
64. //+------------------------------------------------------------------+ 65. void OnDeinit(const int reason) 66. { 67. switch (reason) 68. { 69. case REASON_TEMPLATE: 70. Print("Modified template. Replay/simulation system shutting down."); 71. case REASON_PARAMETERS: 72. case REASON_REMOVE: 73. case REASON_CHARTCLOSE: 74. if (ChartSymbol(user00) != def_SymbolReplay) break; 75. GlobalVariableDel(def_GlobalVariableReplay); 76. ChartClose(user00); 77. break; 78. } 79. delete control; 80. delete terminal; 81. } 82. //+------------------------------------------------------------------+
コントロール指標のソースコードの抜粋(更新)
69行目に、指標がチャートから削除された理由を確認するテストが追加されています。理由がテンプレートの変更である場合、70行目で端末にメッセージが出力され、ユーザーがチャートを閉じた場合や、指標がユーザーによって削除された場合と同様に、サービスは終了します。この決定は過激すぎるように思えるかもしれませんが、前述の通り、それ以外の選択肢は意味がありません。
1つのバグを修正しましたが、今度は別の問題が発生しています。ユーザーは、コントロール指標で使用され、サービスによって渡されるパラメータを簡単に変更できてしまいます。システムはまだ構成中であり、新しいパラメータが表示される可能性があります。このような状況に対処するのは非常に困難ですが、この問題を解決するために根本的なアプローチを採用します。このソリューションは、上記のフラグメントですでに実装されています。
71行目に注意してください。以前はこのような行はありませんでしたが、これはサービスがコントロール指標に渡したパラメータをユーザーが変更できないようにするために追加されました。これが発生すると、MetaTrader 5はパラメータの変更を引数としてDeInitイベントを生成します。MetaTrader 5が指標を再起動すると障害が発生するため、この時点では障害は報告されません。しかし、より賢明なユーザーが実際のチャートのIDを提供できるため、76行目でリプレイ/シミュレーターのチャートを閉じます。その結果、サービスがチャートが開いているかどうかを確認するとエラーが発生し、サービスを終了する必要があることを意味します。したがって、この問題も修正します。
結論
この記事では、コントロール指標がユーザーから使用できなくなったことに起因するいくつかの主要なバグを修正しました。コントロール指標は、CTRL+Iショートカットを通じて指標ウィンドウに引き続き表示されますが、チャートで使用できる指標のリストには含まれなくなりました。このタイプの変更は、コードの安定性と一貫性を高め、ユーザーが望まない操作や予期しない操作を行わないようにするために、コードに多くの修正が含まれています。
しかし、ユーザーが指標を操作したり、マウス指標を使用してコントロール指標を調整および操作したりすることが難しいという問題は依然として残っています。ただし、この問題は近日中に修正される予定であり、その結果、システムはさらに自由で完全にモジュール化される見込みです。
下にあるビデオ03では、この記事で説明した更新によってシステムがどのように動作するかを確認できます。ただし、コードはまだ不安定なため、この記事にはファイルを添付していません。
ビデオ03
次の記事では、ユーザーとリプレイ/シミュレーターサービス間のやり取りに関連する問題と課題について引き続き取り上げます。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/11871





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索