English Русский 中文 Español Deutsch Português
preview
リプレイシステムの開発(第70回):正しい時間を知る(III)

リプレイシステムの開発(第70回):正しい時間を知る(III)

MetaTrader 5 |
155 0
Daniel Jose
Daniel Jose

はじめに

前回「リプレイシステムの開発(第69回):正しい時間を知る(II)」では、銘柄の流動性が低い場合でも、バーの残り時間を表示する方法について説明しました。動性が低いというのは、特定のタイミングで取引がおこなわれていない状態を指します。これにはさまざまな理由がありますが、それらの詳細については説明しません。私たちがやるべきことは、そのような状況を適切に処理する方法を見つけることです。

しかし、まだ解決すべき問題があります。この問題は、プログラミングそのものが難しいわけではなく、発生する条件やそれをどう扱うかが複雑で厄介なのです。この問題は、オークションとして知られています。

一般的に、オークションは非常に特定の状況下でのみ発生します。ランダムに起きるわけではなく、明確で厳密なルールに従って実行されます。しかし、私たちがリプレイ/シミュレーションシステムを開発する上で本当に重要なのは「資産がオークション状態に入ったことを、ユーザーにどのように知らせるか」です。これが唯一にして最大の取り組みべき課題です。幸いなことに、前述したように、解決策はすでに存在しており、マウスインジケーターに実装されています。ただし、これをより柔軟に使えるようにするために、コードにいくつかの変更を加える必要があります。これにより、リプレイやシミュレーションで使っているカスタム資産がオークション状態に入ったことをユーザーに知らせることが可能になります。

さて、ここまでは比較的簡単な話です。本当に難しいのは、リプレイ/シミュレーションシステムが「資産がオークション状態に入った」と判断するロジックをどう構築するかという点です。そして、これが非常に難しいのです。

過去にこのリプレイ/シミュレーションアプリケーションがまだ初期開発段階だったころ、 「取引ティックの間に60秒以上の間隔が空いた場合、それをオークションとみなす」ルールを使っていました。この方法が最適解ではないことは分かっています。実際には、オークションが発生する明確な理由があります。したがって、私はシステムにティックの動きを解析して、資産がオークション状態に入るべきタイミングを検出するような機能を追加して、システムを複雑化させたくはありません。そういった機能を加えるということは、システムの複雑さを、私が合理的かつ管理可能であると考えるレベルを超える方向へ押し上げることになります。これを実装した場合、それは本質的に「市場で何か異常が起きているかどうかを察知する能力を持つアプリケーション」を構築することになるでしょう。それは私の意図ではありません。もちろん、このリプレイ/シミュレーションシステムを使って、そのような機能を研究・開発することは可能ですし、自分自身でその実装に挑戦してみるのは良いことです。しかし、この記事ではその方法については詳しく触れません。

したがって、ここでは初期段階で考案されたシンプルなルール「資産が60秒以上取引されなかった場合、それをオークション状態とみなし、マウスインジケーターがその状態をユーザーに通知する」をそのまま使い続けます。それだけのことです。このアプローチを採用することで、コードの実装に集中することができます。それでは、さっそく実装に取りかかりましょう。


面白いこと

プログラミングには、実際にソフトウェア開発をしている人だけが本当に理解できるようなことがあります。それがどうして起きるのか、なぜそうなるのか、私には分かりませんし、もう理解しようとすらしていません。プロのプログラマーとして20年以上やってきて、私はある種の現象に対して、もう理屈を求めるのをやめました。物事が本来あるべきように動かないことはある、ただそれだけです。そういうときは、深く考えず、「そういうものだ」と受け入れて、次に進むようにしています。だから、何が起こったのかを理解しようとしないでください。このあと提供する新しいコードに差し替えてください。理由を聞かないでください。コードの仕組みについても聞かないでください。もし説明しようとしたら、おそらく私が気が狂ったと思われるでしょう。というわけで、以下に新しいコードをすべて記載しておきます。添付ファイルなどはありません。ただ、このコードをそのまま使い始めてください。

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "Macros.mqh"
005. #include "..\Defines.mqh"
006. //+------------------------------------------------------------------+
007. class C_Terminal
008. {
009. //+------------------------------------------------------------------+
010.    protected:
011.       enum eErrUser {ERR_Unknown, ERR_FileAcess, ERR_PointerInvalid, ERR_NoMoreInstance};
012. //+------------------------------------------------------------------+
013.       struct st_Terminal
014.       {
015.          ENUM_SYMBOL_CHART_MODE   ChartMode;
016.          ENUM_ACCOUNT_MARGIN_MODE TypeAccount;
017.          long           ID;
018.          string         szSymbol;
019.          int            Width,
020.                         Height,
021.                         nDigits,
022.                         SubWin,
023.                         HeightBar;
024.          double         PointPerTick,
025.                         ValuePerPoint,
026.                         VolumeMinimal,
027.                         AdjustToTrade;
028.       };
029. //+------------------------------------------------------------------+
030.    private   :
031.       st_Terminal m_Infos;
032.       struct mem
033.       {
034.          long  Show_Descr,
035.                Show_Date;
036.          bool  AccountLock;
037.       }m_Mem;
038. //+------------------------------------------------------------------+
039.       void CurrentSymbol(void)
040.          {
041.             MqlDateTime mdt1;
042.             string sz0, sz1;
043.             datetime dt = macroGetDate(TimeCurrent(mdt1));
044.             enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER;
045.       
046.             sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3);
047.             for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS);
048.             switch (eTS)
049.             {
050.                case DOL   :
051.                case WDO   : sz1 = "FGHJKMNQUVXZ"; break;
052.                case IND   :
053.                case WIN   : sz1 = "GJMQVZ";       break;
054.                default    : return;
055.             }
056.             for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0))
057.                if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break;
058.          }
059. //+------------------------------------------------------------------+
060. inline void ChartChange(void)
061.          {
062.             int x, y, t;
063.             m_Infos.Width  = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
064.             m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
065.             ChartTimePriceToXY(m_Infos.ID, 0, 0, 0, x, t);
066.             ChartTimePriceToXY(m_Infos.ID, 0, 0, m_Infos.PointPerTick * 100, x, y);
067.             m_Infos.HeightBar = (int)((t - y) / 100);
068.          }
069. //+------------------------------------------------------------------+
070.    public   :
071. //+------------------------------------------------------------------+      
072.       C_Terminal(const long id = 0, const uchar sub = 0)
073.          {
074.             m_Infos.ID = (id == 0 ? ChartID() : id);
075.             m_Mem.AccountLock = false;
076.             m_Infos.SubWin = (int) sub;
077.             CurrentSymbol();
078.             m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);
079.             m_Mem.Show_Date  = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE);
080.             ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false);
081.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, true);
082.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, true);
083.             ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false);
084.             m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
085.             m_Infos.Width   = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
086.             m_Infos.Height  = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
087.             m_Infos.PointPerTick  = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
088.             m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
089.             m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP);
090.             m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick;
091.             m_Infos.ChartMode   = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE);
092.             if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE));
093.             ChartChange();
094.          }
095. //+------------------------------------------------------------------+
096.       ~C_Terminal()
097.          {
098.             ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, m_Mem.Show_Date);
099.             ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, m_Mem.Show_Descr);
100.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, false);
101.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, false);
102.          }
103. //+------------------------------------------------------------------+
104. inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg)
105.          {
106.             if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true;
107.             m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING);
108.          }
109. //+------------------------------------------------------------------+
110. inline const st_Terminal GetInfoTerminal(void) const
111.          {
112.             return m_Infos;
113.          }
114. //+------------------------------------------------------------------+
115. const double AdjustPrice(const double arg) const
116.          {
117.             return NormalizeDouble(round(arg / m_Infos.PointPerTick) * m_Infos.PointPerTick, m_Infos.nDigits);
118.          }
119. //+------------------------------------------------------------------+
120. inline datetime AdjustTime(const datetime arg)
121.          {
122.             int nSeconds= PeriodSeconds();
123.             datetime   dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0);
124.             
125.             return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt)));
126.          }
127. //+------------------------------------------------------------------+
128. inline double FinanceToPoints(const double Finance, const uint Leverage)
129.          {
130.             double volume = m_Infos.VolumeMinimal + (m_Infos.VolumeMinimal * (Leverage - 1));
131.             
132.             return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade)));
133.          };
134. //+------------------------------------------------------------------+
135.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
136.          {
137.             static string st_str = "";
138.             
139.             switch (id)
140.             {
141.                case CHARTEVENT_CHART_CHANGE:
142.                   m_Infos.Width  = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
143.                   m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
144.                   ChartChange();
145.                   break;
146.                case CHARTEVENT_OBJECT_CLICK:
147.                   if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false);
148.                   if (ObjectGetInteger(m_Infos.ID, sparam, OBJPROP_SELECTABLE) == true)
149.                      ObjectSetInteger(m_Infos.ID, st_str = sparam, OBJPROP_SELECTED, true);
150.                   break;
151.                case CHARTEVENT_OBJECT_CREATE:
152.                   if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false);
153.                   st_str = sparam;
154.                   break;
155.             }
156.          }
157. //+------------------------------------------------------------------+
158. inline void CreateObjectGraphics(const string szName, const ENUM_OBJECT obj, const color cor = clrNONE, const int zOrder = -1) const
159.          {
160.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, false);
161.              ObjectCreate(m_Infos.ID, szName, obj, m_Infos.SubWin, 0, 0);
162.              ObjectSetString(m_Infos.ID, szName, OBJPROP_TOOLTIP, "\n");
163.              ObjectSetInteger(m_Infos.ID, szName, OBJPROP_BACK, false);
164.              ObjectSetInteger(m_Infos.ID, szName, OBJPROP_COLOR, cor);
165.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTABLE, false);
166.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTED, false);
167.              ObjectSetInteger(m_Infos.ID, szName, OBJPROP_ZORDER, zOrder);
168.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true);
169.          }
170. //+------------------------------------------------------------------+
171.       bool IndicatorCheckPass(const string szShortName)
172.          {
173.             string szTmp = szShortName + "_TMP";
174.             
175.             IndicatorSetString(INDICATOR_SHORTNAME, szTmp);
176.             m_Infos.SubWin = ((m_Infos.SubWin = ChartWindowFind(m_Infos.ID, szTmp)) < 0 ? 0 : m_Infos.SubWin);
177.             if (ChartIndicatorGet(m_Infos.ID, m_Infos.SubWin, szShortName) != INVALID_HANDLE)
178.             {
179.                ChartIndicatorDelete(m_Infos.ID, 0, szTmp);
180.                Print("Only one instance is allowed...");
181.                SetUserError(C_Terminal::ERR_NoMoreInstance);
182.                
183.                return false;
184.             }
185.             IndicatorSetString(INDICATOR_SHORTNAME, szShortName);
186.    
187.             return true;
188.          }
189. //+------------------------------------------------------------------+
190. };

C_Terminal.mqhのソースコード

上記のコードは古いC_Terminal.mqhファイルを置き換えます。以下のコードは古いC_Mouse.mqhファイルを置き換えます。

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_Terminal.mqh"
005. //+------------------------------------------------------------------+
006. #define def_MousePrefixName "MouseBase" + (string)GetInfoTerminal().SubWin + "_"
007. #define macro_NameObjectStudy (def_MousePrefixName + "T" + (string)ObjectsTotal(0))
008. //+------------------------------------------------------------------+
009. class C_Mouse : public C_Terminal
010. {
011.    public   :
012.       enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
013.       enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10};
014.       struct st_Mouse
015.       {
016.          struct st00
017.          {
018.             short    X_Adjusted,
019.                      Y_Adjusted,
020.                      X_Graphics,
021.                      Y_Graphics;
022.             double   Price;
023.             datetime dt;
024.          }Position;
025.          uchar       ButtonStatus;
026.          bool        ExecStudy;
027.       };
028. //+------------------------------------------------------------------+
029.    protected:
030. //+------------------------------------------------------------------+
031.       void CreateObjToStudy(int x, int w, string szName, color backColor = clrNONE) const
032.          {
033.             if (!m_OK) return;
034.             CreateObjectGraphics(szName, OBJ_BUTTON);
035.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_STATE, true);
036.              ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BORDER_COLOR, clrBlack);
037.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, clrBlack);
038.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BGCOLOR, backColor);
039.             ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_FONT, "Lucida Console");
040.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_FONTSIZE, 10);
041.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 
042.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, x);
043.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1);
044.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XSIZE, w); 
045.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YSIZE, 18);
046.          }
047. //+------------------------------------------------------------------+
048.    private   :
049.       enum eStudy {eStudyNull, eStudyCreate, eStudyExecute};
050.       struct st01
051.       {
052.          st_Mouse Data;
053.          color    corLineH,
054.                   corTrendP,
055.                   corTrendN;
056.          eStudy   Study;
057.       }m_Info;
058.       struct st_Mem
059.       {
060.          bool     CrossHair,
061.                   IsFull;
062.          datetime dt;
063.          string   szShortName,
064.                   szLineH,
065.                   szLineV,
066.                   szLineT,
067.                   szBtnS;
068.       }m_Mem;
069.       bool m_OK;
070. //+------------------------------------------------------------------+
071.       void GetDimensionText(const string szArg, int &w, int &h)
072.          {
073.             TextSetFont("Lucida Console", -100, FW_NORMAL);
074.             TextGetSize(szArg, w, h);
075.             h += 5;
076.             w += 5;
077.          }
078. //+------------------------------------------------------------------+
079.       void CreateStudy(void)
080.          {
081.             if (m_Mem.IsFull)
082.             {
083.                CreateObjectGraphics(m_Mem.szLineV = macro_NameObjectStudy, OBJ_VLINE, m_Info.corLineH);
084.                CreateObjectGraphics(m_Mem.szLineT = macro_NameObjectStudy, OBJ_TREND, m_Info.corLineH);
085.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_WIDTH, 2);
086.                CreateObjToStudy(0, 0, m_Mem.szBtnS = macro_NameObjectStudy);
087.             }
088.             m_Info.Study = eStudyCreate;
089.          }
090. //+------------------------------------------------------------------+
091.       void ExecuteStudy(const double memPrice)
092.          {
093.             double v1 = GetInfoMouse().Position.Price - memPrice;
094.             int w, h;
095.             
096.             if (!CheckClick(eClickLeft))
097.             {
098.                m_Info.Study = eStudyNull;
099.                ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);
100.                if (m_Mem.IsFull)   ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T");
101.             }else if (m_Mem.IsFull)
102.             {
103.                string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ",
104.                   MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0));
105.                GetDimensionText(sz1, w, h);
106.                ObjectSetString(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_TEXT, sz1);                                                
107.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP));
108.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XSIZE, w);
109.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YSIZE, h);
110.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XDISTANCE, GetInfoMouse().Position.X_Adjusted - w);
111.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h));            
112.                ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price);
113.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP));
114.             }
115.             m_Info.Data.ButtonStatus = eKeyNull;
116.          }
117. //+------------------------------------------------------------------+
118. inline void DecodeAlls(int xi, int yi)
119.          {
120.             int w = 0;
121. 
122.             xi = (xi > 0 ? xi : 0);
123.             yi = (yi > 0 ? yi : 0);
124.             ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X_Graphics = (short)xi, m_Info.Data.Position.Y_Graphics = (short)yi, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
125.             m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt);
126.             m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price);
127.             ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, xi, yi);
128.             yi -= (int)ChartGetInteger(GetInfoTerminal().ID, CHART_WINDOW_YDISTANCE, GetInfoTerminal().SubWin);
129.             m_Info.Data.Position.X_Adjusted = (short) xi;
130.             m_Info.Data.Position.Y_Adjusted = (short) yi;
131.          }
132. //+------------------------------------------------------------------+
133.    public   :
134. //+------------------------------------------------------------------+
135.       C_Mouse(const long id, const string szShortName)
136.          :C_Terminal(id),
137.          m_OK(false)
138.          {
139.             m_Mem.szShortName = szShortName;
140.          }
141. //+------------------------------------------------------------------+
142.       C_Mouse(const long id, const string szShortName, color corH, color corP, color corN)
143.          :C_Terminal(id)
144.          {
145.             if (!(m_OK = IndicatorCheckPass(m_Mem.szShortName = szShortName))) return;
146.             m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL);
147.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true);
148.              ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false);
149.             ZeroMemory(m_Info);
150.             m_Info.corLineH  = corH;
151.             m_Info.corTrendP = corP;
152.             m_Info.corTrendN = corN;
153.             m_Info.Study = eStudyNull;
154.             if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE))
155.                CreateObjectGraphics(m_Mem.szLineH = (def_MousePrefixName + (string)ObjectsTotal(0)), OBJ_HLINE, m_Info.corLineH);
156.             ChartRedraw(GetInfoTerminal().ID);
157.          }
158. //+------------------------------------------------------------------+
159.       ~C_Mouse()
160.          {
161.             if (!m_OK) return;
162.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
163.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, ChartWindowFind(GetInfoTerminal().ID, m_Mem.szShortName) != -1);
164.              ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair);
165.             ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName);
166.          }
167. //+------------------------------------------------------------------+
168. inline bool CheckClick(const eBtnMouse value) 
169.          {
170.             return (GetInfoMouse().ButtonStatus & value) == value;
171.          }
172. //+------------------------------------------------------------------+
173. inline const st_Mouse GetInfoMouse(void)
174.          {
175.             if (!m_OK)
176.             {
177.                double Buff[];
178.                uCast_Double loc;
179.                int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName);
180. 
181.                ZeroMemory(m_Info.Data);
182.                if (CopyBuffer(handle, 0, 0, 1, Buff) == 1)
183.                {
184.                   loc.dValue = Buff[0];
185.                   m_Info.Data.ButtonStatus = loc._8b[0];
186.                   DecodeAlls((int)loc._16b[1], (int)loc._16b[2]);
187.                }
188.                IndicatorRelease(handle);
189.             }
190. 
191.             return m_Info.Data;
192.          }
193. //+------------------------------------------------------------------+
194. inline void SetBuffer(const int rates_total, double &Buff[])
195.          {
196.             uCast_Double info;
197.             
198.             info._8b[0] = (uchar)(m_Info.Study == C_Mouse::eStudyNull ? m_Info.Data.ButtonStatus : 0);
199.             info._16b[1] = (ushort) m_Info.Data.Position.X_Graphics;
200.             info._16b[2] = (ushort) m_Info.Data.Position.Y_Graphics;
201.             Buff[rates_total - 1] = info.dValue;
202.          }
203. //+------------------------------------------------------------------+
204.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
205.          {
206.             int w = 0;
207.             static double memPrice = 0;
208.       
209.             if (m_OK)      
210.             {
211.                C_Terminal::DispatchMessage(id, lparam, dparam, sparam);
212.                switch (id)
213.                {
214.                   case (CHARTEVENT_CUSTOM + evHideMouse):
215.                      if (m_Mem.IsFull)   ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, clrNONE);
216.                      break;
217.                   case (CHARTEVENT_CUSTOM + evShowMouse):
218.                      if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, m_Info.corLineH);
219.                      break;
220.                   case CHARTEVENT_MOUSE_MOVE:
221.                      DecodeAlls((int)lparam, (int)dparam);
222.                      if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineH, 0, 0, m_Info.Data.Position.Price);
223.                      if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineV, 0, m_Info.Data.Position.dt, 0);
224.                      m_Info.Data.ButtonStatus = (uchar) sparam;
225.                      if (CheckClick(eClickMiddle))
226.                         if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
227.                      if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
228.                      {
229.                         ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
230.                         if (m_Mem.IsFull)   ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price);
231.                         m_Info.Study = eStudyExecute;
232.                      }
233.                      if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
234.                      m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute;
235.                      break;
236.                   case CHARTEVENT_OBJECT_DELETE:
237.                      if ((m_Mem.IsFull) && (sparam == m_Mem.szLineH))
238.                         CreateObjectGraphics(m_Mem.szLineH, OBJ_HLINE, m_Info.corLineH);
239.                      break;
240.                }
241.             }
242.          }
243. //+------------------------------------------------------------------+
244. };
245. //+------------------------------------------------------------------+
246. #undef macro_NameObjectStudy
247. //+------------------------------------------------------------------+

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

これらの変更を加えたところで、次に何をすべきかを見ていきましょう。なぜなら、奇妙に聞こえるかもしれませんが、時として予期せぬことが起こるからです。そしてあなたは、これからプログラマーを目指す者として、時には「何が起こっているのか分からない」という状況に直面することを理解する必要があります。なぜなら、本当に、説明のつかない奇妙なことが時々起こるからです。だからこそ、私は今行っているすべての変更を1つ1つあなたに見せているのです。それは、すべてを自分の力でコントロールできるわけではないという現実を、自分の目で確かめてほしいからです。


新しいマウスインジケーターの登場

この段階で、多くの開発者にとっては少し異例とも言える変更をおこなう必要があります。それは、リプレイ/シミュレーション環境の中で、実際の市場で発生するあらゆるイベントを、マウスインジケーターが処理できるようにすることです。ChartOpenを呼び出して取得したID値を使用しないのはなぜかと考えるかもしれませんすでにそうなっているのではないでしょうか。残念ながら、これまでは、リプレイ/シミュレーションシステムを、デモ口座やライブ口座などリアルサーバーに接続するシステムから意図的に切り離していました。そのため、マウスインジケーターには2つの動作モードが存在していたのです。1つはブックイベント(板情報の変化など)を使うモード、もう1つはそれらのイベントを無視するモードです。

ここでの最大の問題は、オーダーブックイベントに関するものです。ですが、これらのイベントは、あなたが想像するよりも少し複雑です。単にCustomBookAdd関数(これは MQL5 の標準ライブラリの一部です)を呼び出せば全てがうまくいくというほど簡単な話ではありません。リプレイ/シミュレーション環境で動いている私たちにとっては、事情がもう少しややこしいのです。とはいえ、心配はいりません。最終的には必ずCustomBookAdd関数の使い方について詳しく説明します。焦らず、一歩ずつ着実に進んでいきましょう。

前のセクションで気づいたかもしれませんが、C_Terminal.mqhヘッダファイルとC_Mouse.mqhファイルの両方のソースコードに変更が加えられました。これらの変更の詳細には踏み込みませんでしたが、変更があったということは理解しておいてください。とはいえ、それほど理解が難しい変更ではないため、細かく説明する必要はないだろうと考えています。そして、これらのファイルと同様に、C_Study.mqhヘッダファイルにも変更があります。その完全なコードは以下に掲載しています。

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\C_Mouse.mqh"
005. //+------------------------------------------------------------------+
006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_"
007. //+------------------------------------------------------------------+
008. class C_Study : public C_Mouse
009. {
010.    private   :
011. //+------------------------------------------------------------------+
012.       struct st00
013.       {
014.          eStatusMarket  Status;
015.          MqlRates       Rate;
016.          string         szInfo,
017.                         szBtn1,
018.                         szBtn2,
019.                         szBtn3;
020.          color          corP,
021.                         corN;
022.          int            HeightText;
023.          bool           bvT, bvD, bvP;
024.       }m_Info;
025. //+------------------------------------------------------------------+
026.       void Draw(void)
027.          {
028.             double v1;
029.             
030.             if (m_Info.bvT)
031.             {
032.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18);
033.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_TEXT, m_Info.szInfo);
034.             }
035.             if (m_Info.bvD)
036.             {
037.                v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
038.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
039.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
040.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
041.             }
042.             if (m_Info.bvP)
043.             {
044.                v1 = NormalizeDouble((((GL_PriceClose - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
045.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
046.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
047.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
048.             }
049.          }
050. //+------------------------------------------------------------------+
051. inline void CreateObjInfo(EnumEvents arg)
052.          {
053.             switch (arg)
054.             {
055.                case evShowBarTime:
056.                   C_Mouse::CreateObjToStudy(2, 110, m_Info.szBtn1 = (def_ExpansionPrefix + (string)ObjectsTotal(0)), clrPaleTurquoise);
057.                   m_Info.bvT = true;
058.                   break;
059.                case evShowDailyVar:
060.                   C_Mouse::CreateObjToStudy(2, 53, m_Info.szBtn2 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
061.                   m_Info.bvD = true;
062.                   break;
063.                case evShowPriceVar:
064.                   C_Mouse::CreateObjToStudy(58, 53, m_Info.szBtn3 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
065.                   m_Info.bvP = true;
066.                   break;
067.             }
068.          }
069. //+------------------------------------------------------------------+
070. inline void RemoveObjInfo(EnumEvents arg)
071.          {
072.             string sz;
073.             
074.             switch (arg)
075.             {
076.                case evHideBarTime:
077.                   sz = m_Info.szBtn1;
078.                   m_Info.bvT = false;
079.                   break;
080.                case evHideDailyVar:
081.                   sz = m_Info.szBtn2;
082.                   m_Info.bvD   = false;
083.                   break;
084.                case evHidePriceVar:
085.                   sz = m_Info.szBtn3;
086.                   m_Info.bvP = false;
087.                   break;
088.             }
089.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
090.             ObjectDelete(GetInfoTerminal().ID, sz);
091.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
092.          }
093. //+------------------------------------------------------------------+
094.    public   :
095. //+------------------------------------------------------------------+
096.       C_Study(long IdParam, string szShortName, color corH, color corP, color corN)
097.          :C_Mouse(IdParam, szShortName, corH, corP, corN)
098.          {
099.             if (_LastError >= ERR_USER_ERROR_FIRST) return;
100.             ZeroMemory(m_Info);
101.             m_Info.corP = corP;
102.             m_Info.corN = corN;
103.             CreateObjInfo(evShowBarTime);
104.             CreateObjInfo(evShowDailyVar);
105.             CreateObjInfo(evShowPriceVar);
106.             ResetLastError();
107.          }
108. //+------------------------------------------------------------------+
109.       void Update(const eStatusMarket arg)
110.          {
111.             int i0;
112.             datetime dt;
113.                
114.             if (m_Info.Rate.close == 0)
115.                m_Info.Rate.close = iClose(NULL, PERIOD_D1, ((_Symbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(NULL, PERIOD_D1, 0))) ? 0 : 1));
116.             switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
117.             {
118.                case eCloseMarket   :
119.                   m_Info.szInfo = "Closed Market";
120.                   break;
121.                case eInReplay      :
122.                case eInTrading   :
123.                   i0 = PeriodSeconds();
124.                   dt = (m_Info.Status == eInReplay ? (datetime) GL_TimeAdjust : TimeCurrent());
125.                   m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time);
126.                   if (dt > 0) m_Info.szInfo = TimeToString((datetime)m_Info.Rate.time - dt, TIME_SECONDS);
127.                   break;
128.                case eAuction      :
129.                   m_Info.szInfo = "Auction";
130.                   break;
131.                default            :
132.                   m_Info.szInfo = "ERROR";
133.             }
134.             Draw();
135.          }
136. //+------------------------------------------------------------------+
137. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
138.          {
139.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
140.             switch (id)
141.             {
142.                case CHARTEVENT_CUSTOM + evHideBarTime:
143.                   RemoveObjInfo(evHideBarTime);
144.                   break;
145.                case CHARTEVENT_CUSTOM + evShowBarTime:
146.                   CreateObjInfo(evShowBarTime);
147.                   break;
148.                case CHARTEVENT_CUSTOM + evHideDailyVar:
149.                   RemoveObjInfo(evHideDailyVar);
150.                   break;
151.                case CHARTEVENT_CUSTOM + evShowDailyVar:
152.                   CreateObjInfo(evShowDailyVar);
153.                   break;
154.                case CHARTEVENT_CUSTOM + evHidePriceVar:
155.                   RemoveObjInfo(evHidePriceVar);
156.                   break;
157.                case CHARTEVENT_CUSTOM + evShowPriceVar:
158.                   CreateObjInfo(evShowPriceVar);
159.                   break;
160.                case CHARTEVENT_MOUSE_MOVE:
161.                   Draw();
162.                   break;
163.             }
164.             ChartRedraw(GetInfoTerminal().ID);
165.          }
166. //+------------------------------------------------------------------+
167. };
168. //+------------------------------------------------------------------+
169. #undef def_ExpansionPrefix
170. #undef def_MousePrefixName
171. //+------------------------------------------------------------------+

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

前のセクションで取り上げた他のヘッダファイルとは異なり、ここで実装するコードはより重要性が高く、状況も少し異なります。

まず注目してほしいのは、C_Studyクラスのコンストラクタがコードのクリーンアップを受けたという点です。具体的には、99行目でテスト値が変更されており、コードに起因しないエラーによって実行が早期終了するのを防ぐようになっています。ただし、これとは別に注目すべき点があります。コンストラクタ内でRate.closeを初期化しなくなったことに気づいたでしょうか。この初期化処理は、コード内の別の場所でおこなわれるようになりました。114行目を見てください。ここで、Rate.closeの値が0であるかどうかをチェックしています。もしゼロであれば、115行目で終値を取得しています。これは以前と同様の処理です。

この変更は、インジケーターをテンプレートに保存し、それをチャートに追加した際に、コンストラクタでRate.closeを初期化してしまうと、不正な値が入ってしまうことがあったためです。正直に言うと、なぜそのような現象が起こるようになったのか、完全には理解できていません。しかし事実として、コードにいくつかの変更を加えたあとから、Rate.closeの初期化にランダムかつ予測不可能な不具合が発生するようになったのです。そのため、この問題の発生確率と影響範囲を抑える目的で、初期化のタイミングを移動しました。

ここで重要なポイントがあります。100行目では、m_Info構造体の全メンバーをゼロにリセットしています。そのため、114行目のチェックは常にtrueを返すことになります。とはいえ、ここではコード全体をすべて提示しているので、説明が必要だと感じたポイントについてだけ解説しました。それ以外のコード部分については、ほとんど変更されていません。それでは、次はマウスインジケーターのコードに加えた変更に進みましょう。完全なコードは以下に記載しています。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "This is an indicator for graphical studies using the mouse."
04. #property description "This is an integral part of the Replay / Simulator system."
05. #property description "However it can be used in the real market."
06. #property version "1.70"
07. #property icon "/Images/Market Replay/Icons/Indicators.ico"
08. #property link "https://www.mql5.com/ru/articles/12326"
09. #property indicator_chart_window
10. #property indicator_plots 0
11. #property indicator_buffers 1
12. //+------------------------------------------------------------------+
13. double GL_PriceClose;
14. datetime GL_TimeAdjust;
15. //+------------------------------------------------------------------+
16. #include <Market Replay\Auxiliar\Study\C_Study.mqh>
17. //+------------------------------------------------------------------+
18. C_Study *Study       = NULL;
19. //+------------------------------------------------------------------+
20. input color user02   = clrBlack;                         //Price Line
21. input color user03   = clrPaleGreen;                     //Positive Study
22. input color user04   = clrLightCoral;                    //Negative Study
23. //+------------------------------------------------------------------+
24. C_Study::eStatusMarket m_Status;
25. int m_posBuff = 0;
26. double m_Buff[];
27. //+------------------------------------------------------------------+
28. int OnInit()
29. {
30.    Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04);
31.    if (_LastError >= ERR_USER_ERROR_FIRST) return INIT_FAILED;
32.    MarketBookAdd((*Study).GetInfoTerminal().szSymbol);
33.    OnBookEvent((*Study).GetInfoTerminal().szSymbol);
34.    m_Status = C_Study::eCloseMarket;
35.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
36.    ArrayInitialize(m_Buff, EMPTY_VALUE);
37.    
38.    return INIT_SUCCEEDED;
39. }
40. //+------------------------------------------------------------------+
41. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], 
42.                 const double& high[], const double& low[], const double& close[], const long& tick_volume[], 
43.                 const long& volume[], const int& spread[]) 
44. {
45.    GL_PriceClose = close[rates_total - 1];
46.    if (_Symbol == def_SymbolReplay)
47.       GL_TimeAdjust = spread[rates_total - 1] & (~def_MaskTimeService);
48.    m_posBuff = rates_total;
49.    (*Study).Update(m_Status);   
50.    
51.    return rates_total;
52. }
53. //+------------------------------------------------------------------+
54. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
55. {
56.    (*Study).DispatchMessage(id, lparam, dparam, sparam);
57.    (*Study).SetBuffer(m_posBuff, m_Buff);
58.    
59.    ChartRedraw((*Study).GetInfoTerminal().ID);
60. }
61. //+------------------------------------------------------------------+
62. void OnBookEvent(const string &symbol)
63. {
64.    MqlBookInfo book[];
65.    C_Study::eStatusMarket loc = m_Status;
66.    
67.    if (symbol != (*Study).GetInfoTerminal().szSymbol) return;
68.    MarketBookGet((*Study).GetInfoTerminal().szSymbol, book);
69.    m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : (symbol == def_SymbolReplay ? C_Study::eInReplay : C_Study::eInTrading));
70.    for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++)
71.       if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction;
72.    if (loc != m_Status) (*Study).Update(m_Status);
73. }
74. //+------------------------------------------------------------------+
75. void OnDeinit(const int reason)
76. {
77.    MarketBookRelease((*Study).GetInfoTerminal().szSymbol);
78. 
79.    delete Study;
80. }
81. //+------------------------------------------------------------------+

マウスポインタのソースコード

ご覧のとおり、マウスインジケーターのコードは大きく変わりました。というより、もはや以前とはまったく別物です。これらの変更には理由があります。たとえすべてを理解できなくても、インジケーターをより汎用的にし、どんなチャートや銘柄でも一貫性を持たせることが目的です。それでは、変更点を順に見ていきましょう。

まず、OnInit初期化関数からです。よく見ると、以前のバージョンとは異なる点があることに気づくはずです。ここで注目してほしいポイントのひとつが、31行目で、これまでに発生したエラー変数をチックしている点です。以前は、エラーが発生するとINIT_FAILEDを返して初期化が失敗していました。では、なぜこれを変更したのでしょうか。理由は、初期化中に「意味不明なエラー」が報告されることがあったためです。しかし、クラスのコンストラクタでは戻り値を返すことができないため、以前は_LastErrorを代用として使っていました。ですが、これはあまり実用的ではありませんでした。そこで、現在はSetUserErrorを使って、より具体的なエラーだけをフィルタリングするように変更しました。こういったエラーは頻繁には発生しませんが、コンストラクタが正しく処理されたかどうかを確認するうえで重要です。

次に、これまでになかった新しい要素があります。それは、MetaTrader 5に対して「オーダーブックイベントを受け取りたい」と通知する処理です。重要な点として、以前はブックイベントはリアルな銘柄に対してのみ利用可能でした。つまり、取引サーバーに接続されている必要があったのです。しかし、今ではカスタム銘柄であっても、常にブックイベントを受け取ることができるようになりました。これは重要です。この仕組みにより、銘柄がオークションモードにあることを、ユーザーに通知することができます。インターフェイスはそのままで、特別な調整は不要です。必要なのは正しいデータをインジケーターに渡すことだけで、あとはインジケーターが処理します。

34行目では、市場が閉まっていることを示す初期値を設定しています。その後、35行目と36行目で、マウスインジケーターのデータバッファを初期化しています。これは以前にもおこなっていた簡単な処理ですが、今回の違いは、どんな銘柄であってもブックイベントを受信するよう明示的に指定している点です。

OnCalculateについては、今は飛ばしておき、後ほど戻ってくることにしましょう。お気づきかもしれませんが、iSpreadは呼び出されていません。代わりに、75行目に進んでみましょう。そこには OnDeInit 関数があります。今回は、どの銘柄に対してもオーダーブックイベントを受け取ることになるため、あるタイミングでイベントが不要なことをMetaTrader 5に伝える必要があります。その処理がおこなわれているのが、77行目です。そして、インジケーターをチャートから削除するため、79行目ではdelete演算子を使って、使用していたメモリをシステムに返しています。

さて、次にオーダーブックイベントのハンドラを見ていきましょう。それは62行目にあるOnBookEventルーチンです。この関数は、可能な限りシンプルに設計されています。私たちは、オーダーブック内の出来高や注文数の分析には興味がありません。関心があるのは、銘柄が取引モードにあるかどうか、オークションモードにあるかどうか、市場が閉じているかどうかです。これらを判定するためには、単にオーダーブックで何が起きているのかを見るだけで十分です。

67行目では、イベントの対象となっている銘柄が、このインジケーターにアタッチされているものかどうかをチェックしています。これは、MetaTrader 5 がオーダーブックイベントを銘柄ごとにフィルタせず、それをリクエストしたすべてのチャートに送信するためです。したがって、関係のない銘柄のブックを監視するのは得策ではありません。これにより不要なイベントが発生し、時間の無駄になります。フィルタされた後、68行目で MQL5 ライブラリの呼び出しによって最新のオーダーブックデータを取得します。これは、この処理が正しく機能するために必要です。

ここからが OnBookEvent の最も興味深く、実用的な部分です。69行目に注意してください。オーダーブックデータ配列が空であれば、市場は閉じているということです。データが存在する場合は、新たなチェックが必要です。なぜでしょうか。単純に、解析しているデータの出所を知るためです。これは、少し前に示したC_Study.mqhヘッダファイル、特にそのUpdateプロシージャで見ることができます。124行目では、銘柄が取引サーバーに接続されていればTimeCurrentを呼び出し、カスタム銘柄であればグローバル変数を使います。だからこそ、69行目のテストが必要なのです。これがないと、誤ったデータを使ってしまい、すべてが崩壊してしまいます。銘柄の一次状態を特定したら、その実際の状態を確認する必要があります。これは 70行目でおこなわれており、ブックをループして特定の種類の情報を探しています。この場合、BOOK_TYPE_BUY_MARKETまたはBOOK_TYPE_SELL_MARKETの存在を確認します。どちらかが見つかれば、市場は開いているが銘柄はオークションモードにあるということです。

OnInitの中での単純な処理、すなわちどんな銘柄でもオーダーブックイベントを有効にするという処理によって、銘柄の現在の状態を特定することが可能になります。このような実装の細部が、この作業を楽しいものにしてくれるのです。

さて、41行目のOnCalculateプロシージャに戻りましょう。おそらく、なぜこのコードがまた変更されたのか。前回の記事と今回の間にMetaTrader 5にアップデートがあったのかと疑問に思われるでしょう。実際には、そうではありません。これらの記事はかなり前に書かれたものであり、内容がようやく今になって公開されているというだけの話です。

この実行結果は、下記のビデオで確認できます。説明を読む前に、まずビデオを見てください。そうすれば、これからの説明がより理解しやすくなるでしょう。

デモビデオ

ビデオをご覧になった方は、私が非常に特殊な方法で作業を進めていることに気づいたと思います。サービスのコードについては、今は気にしなくて大丈夫です。それについては別の機会に取り上げます。今回は、マウスインジケーターに焦点を当てていきましょう。

まず、サービスを初期化しました。サービスは、チャートにマウスインジケーターが追加されるのを検出するまで待機状態にありました。インジケーターが配置された瞬間、表示画面にカウントダウンが表示され、その値がMetaTrader 5のツールボックスにも表示されたのが分かったと思います。これは、マウスインジケーターが本当にサービスから提供される値にアクセスできているかどうかを検証するためでした。

ここで次の点に注目してください。前回の記事では、ある時点でサービスがマウスインジケーターにデータを送り続けていました。しかし、インジケーターは受け取った値に基づいた時間的な更新を提供することができませんでした。なぜなら、表示が単にフリーズしてしまったからです。今使っているOnCalculate関数内のコードは当時と同じなのに、そのときは表示が止まってしまいました。フリーズを回避するために、当時はiSpread関数を使用するという手段を取りました。ところが今回は、その呼び出しを使っていないにもかかわらず、インジケーターの表示はフリーズしませんでした。では、なぜ今はうまく動作して、前回はダメだったのでしょうか。正直なところ、その答えは私にもわかりません。これは、本当に実験して限界に挑戦する者だけが体験できる種類の出来事なのです。

私がこれを読者の皆さんと共有したいのは、プログラマーとして、私たちはすべての答えを常に持っているわけではないということを伝えたいからです。なぜそうなったのか、どのようにしてその結果にたどり着いたのか分からないこともあります。私の考えでは、このような状況を共有することで、学びの過程も私自身の経験もずっと楽しくなるのです。なぜなら、すべての目標が最初から達成できるわけではないということを、あなたも学ぶからです。そして私にとっては、MetaTrader 5の細かい部分がどのように機能しているのか、どこまで限界を押し広げてもシステム全体がクラッシュしないのかを学ぶ機会となるのです。それでは、この記事の締めくくりとして、ビデオで使用したサービスのソースコードを見ていきましょう。


テストサービス

一般的に、多くの人がプログラミングの習得に苦労するのは、次のことを理解していないからです。 

イベントの順序は結果に影響を与える

しかし、プログラミングにおいては、これはほとんどの場合に当てはまります。イベントがどの順番で発生するかを気にせずにプログラムを書くことができるケースは、ごくまれです。そしてここ、このテスト用サービスにおいては、そのことがまさに完全に当てはまります。それでは、サービスのソースコードを見てみましょう。

01. //+------------------------------------------------------------------+
02. #property service
03. #property copyright "Daniel Jose"
04. #property description "Data synchronization demo service."
05. //+------------------------------------------------------------------+
06. #include <Market Replay\Defines.mqh>
07. #include <Market Replay\Auxiliar\Macros.mqh>
08. //+------------------------------------------------------------------+
09. #define def_Loop ((!_StopFlag) && (ChartSymbol(id) != ""))
10. //+------------------------------------------------------------------+
11. void OnStart()
12. {
13.    long id;
14.    int time;
15.    MqlRates Rate[1];
16.    MqlBookInfo book[1];
17.    
18.    Print("Starting Test Service...");
19.    SymbolSelect(def_SymbolReplay, false);
20.    CustomSymbolDelete(def_SymbolReplay);
21.    CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
22.    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0.5);
23.    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 5);
24.    CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TICKS_BOOKDEPTH, 1);
25.    Rate[0].close = 105;
26.    Rate[0].open = 100;
27.    Rate[0].high = 110;
28.    Rate[0].low = 95;
29.    Rate[0].tick_volume = 5;
30.    Rate[0].spread = 1;
31.    Rate[0].real_volume = 10;
32.    Rate[0].time = D'14.03.2023 08:30';
33.    CustomRatesUpdate(def_SymbolReplay, Rate, 1);
34.    Rate[0].time = D'14.03.2023 09:00';
35.    CustomRatesUpdate(def_SymbolReplay, Rate, 1);
36.    SymbolSelect(def_SymbolReplay, true);
37.    id = ChartOpen(def_SymbolReplay, PERIOD_M30);
38. 
39.    Sleep(1000);
40. 
41.    Print("Waiting for Mouse Indicator...");
42.    while ((def_Loop) && (ChartIndicatorGet(id, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200);
43. 
44.    book[0].type = BOOK_TYPE_BUY;
45.    book[0].price = Rate[0].close;
46.    book[0].volume = 1;
47.    CustomBookAdd(def_SymbolReplay, book, 1);
48. 
49.    Print(TimeToString(Rate[0].time, TIME_DATE | TIME_SECONDS));
50.    time = (int)macroGetTime(Rate[0].time);   
51.    while (def_Loop)
52.    {
53.       Rate[0].spread = (int)(def_MaskTimeService | time);
54.       CustomRatesUpdate(def_SymbolReplay, Rate, 1);
55.       Sleep(250);
56.       time++;
57.       Print(TimeToString(time, TIME_SECONDS), " >> (int):", time);      
58.       switch (time)
59.       {
60.          case 32430:
61.             book[0].type = BOOK_TYPE_BUY_MARKET;
62.             CustomBookAdd(def_SymbolReplay, book, 1);
63.             break;
64.          case 32460:
65.             book[0].type = BOOK_TYPE_BUY;
66.             CustomBookAdd(def_SymbolReplay, book, 1);
67.             break;
68.       }
69.    }
70.    ChartClose(id);
71.    SymbolSelect(def_SymbolReplay, false);
72.    CustomSymbolDelete(def_SymbolReplay);
73.    Print("Finished Test Service...");   
74. }
75. //+------------------------------------------------------------------+

テストサービスのソースコード

コードを見てわかるように、これは前回の記事でマウスインジケーターのテストに使ったものとほとんど同じです。しかし、ここにはいくつか新しい要素があります。これらが、ビデオで見た一連の処理を引き起こすトリガーとなっています。ここに追加されたすべての新しい行には、それぞれ理由と意味があります。多くの人がカスタム銘柄でオーダーブックを使おうとして失敗するのは、本当に必要な手順を理解していないからです。彼らの多くは、CustomBookAdd 呼び出し(47行目、62行目、64行目にあります)だけに注目します。しかし、それではうまくいきません。CustomBookAddライブラリ関数だけを使ってカスタム銘柄のブックにデータを送ろうとしても、事前の手順を踏まなければ、絶対に動作しません。そして、ミスを避けるためには、その手順を慎重に計画する必要があります。

最も重要な手順は 24行目にあります。この行がなければ、CustomBookAdd 関数は私たちにとってまったく意味を持ちません。しかし、CustomBookAddはオーダーブックにデータを送るための関数ではないのか。TickやRateの関数と同じではないかと思われるかもしれません。その答えは「はい」でもあり「いいえ」でもあります。矛盾しているように聞こえますが、SYMBOL_TICKS_BOOKDEPTHに値を定義しない限り、CustomBookAdd関数は何の効果も持ちません。この SYMBOL_TICKS_BOOKDEPTHに設定する値は、私たちがやりたいことによって変わります。今回の目的は、マウスインジケーターにカスタム銘柄の状態を通知させることだけなので、1段階のレベルだけで十分です。そのため、24行目では値を1に設定しました。ただし、シミュレーションやリプレイの研究のために人工的なブックを作成したい場合は、この値を適宜変更すればよいのです。

いずれにしても、これは基本中の基本です。SYMBOL_TICKS_BOOKDEPTHを定義してはじめて、MqlBookInfo構造体の中にデータを受け取ることが可能になります。この点は私にとっても理解するのが少し難しかった部分です。なぜなら、カスタム銘柄に対してMqlBookInfo構造体をどのように使えばよいかという情報は、どこにも書かれていなかったからです。ほとんどのリファレンスでは「CustomBookAdd を使えばいい」とだけ書かれていました。しかし、ドキュメントにあったのはSYMBOL_TICKS_BOOKDEPTHのことだけで、その設定が MqlBookInfoへのアクセスにどうつながるかについての説明はありませんでした。

とにかく、このサービスはデータ送信をテストするためだけに使われます。それ以外の用途はありません。ただ、この記事を締めくくる前に、何が起こっているのかを簡単に説明しておきましょう。44行目から46行目の間で、ブックのレベルを定義しています。この中で最も重要なのは 44行目 です。そして、47行目では MetaTrader 5に対して、イベントを発生させ、このデータをブックとして利用可能にするように指示を出しています。ここで注意すべき点は、50行目で現在の時刻を取得しており、56行目でその時刻に1秒を加算しているということです。34行目で定義された最初の時刻の値は 32400(秒)です。この単位が秒であることを忘れないでください。そして、58行目で現在の位置をチェックし、30秒後に62行目 にて新しい値をブックに送信します。その送信される値は61行目にあります。この値は、マウスインジケーターに、銘柄がオークションモードにあることと表示させるためのものです。さらに30秒後、チェック処理によって64行目 が実行されると、65行目で定義された値が66行目 を通じて送信されます。これにより、マウスインジケーター上に再び時刻の表示が戻ります。

最後にもうひとつだけ補足しておくと、「1秒」と言っても厳密には1秒ではありません。というのも、55行目で1/4秒(250ミリ秒)の遅延を入れているからです。


最後に

この記事では、カスタム銘柄のオーダーブックに値を渡す方法を紹介しました。今回使用したサービスの唯一の目的は、この値の渡し方をテストすることでしたが、それでも非常に理にかなった方法で実現できることを示すことができました。最初はいくつかの課題があったものの、最終的には可能であることが分かりました。したがって、これであなたはサービスとインジケーター(またはエキスパートアドバイザー)間で情報をやり取りするもう1つの方法を手に入れたことになります。それがオーダーブックを使った通信手段です。ただし、すべてにはコストが伴うということを忘れてはいけません。オーダーブックを無分別に使うことには、それなりのコストが伴うという点も、常に考慮する必要があります。

とはいえ、今回のテストサービスは、次回の記事で扱う内容とは異なります。次回は、リプレイ/シミュレーターを用いて、銘柄がオークションモードにあるかどうかを判定する仕組みを実装していきます。興味深いシステムだと思いませんか。それでは、次の記事でお会いしましょう。

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

添付されたファイル |
Anexo.zip (420.65 KB)
取引におけるニューラルネットワーク:パターンTransformerを用いた市場分析 取引におけるニューラルネットワーク:パターンTransformerを用いた市場分析
モデルを使用して市場の状況を分析する場合、主にローソク足に注目します。しかし、ローソク足パターンが将来の価格変動を予測するのに役立つことは長い間知られていました。この記事では、これら両方のアプローチを統合できる方法について説明します。
初級から中級まで:配列(II) 初級から中級まで:配列(II)
この記事では、動的配列と静的配列について見ていきます。この2つの使い方に違いはあるのでしょうか。それとも、どちらを使っても常に同じ結果になるのでしょうか。また、どのような場面でそれぞれを使うべきなのか、そして「定数配列」とは何かについても解説します。配列のすべての要素を初期化せずに使うリスクにも触れながら、これらの構造が何のために設計されたのかを一緒に考えていきます。
取引におけるニューラルネットワーク:対照パターンTransformer 取引におけるニューラルネットワーク:対照パターンTransformer
Contrastive Transformerは、個々のローソク足のレベルと、全体のパターンに基づいて市場を分析するよう設計されています。これにより、市場トレンドのモデリングの質が向上します。さらに、ローソク足とパターンの表現を整合させるために対照学習を用いることで、自己調整が促され、予測の精度が高まります。
取引におけるニューラルネットワーク:相対エンコーディング対応Transformer 取引におけるニューラルネットワーク:相対エンコーディング対応Transformer
自己教師あり学習は、ラベル付けされていない大量のデータを分析する効果的な手段となり得ます。この手法の効率性は、モデルが金融市場特有の特徴に適応することで実現され、従来手法の有効性も向上します。本記事では、入力間の相対的な依存関係や関係性を考慮した新しいAttention(注意)機構を紹介します。