一からの取引エキスパートアドバイザーの開発(第8部):概念的な飛躍

Daniel Jose | 12 7月, 2022

はじめに

時々、いくつかのプロジェクトを開発するとき、便利で、新しい理想的で新しい可能な機能を見つけて、作成しているシステムに大きな改善を提供することができることがあります。ただし、新しい機能を実装する最も簡単な方法は何であるかという疑問が生じます。

問題は、すでに開発されたものをすべて忘れて、ゼロから始めなければならない場合があるということです。やる気がなくなってしまいます。20年以上のC++プログラミングの後、私は時とともに特定の考え方を発展させました。最小限の労力で物事を計画し、変更を加えるのに役立ついくつかの概念を開発するのですが、状況が変わって当初見積ったよりもはるかに複雑になる場合があります。

これまでは、現在の機能を失うことなく新しいコードを追加できるようにEAを構築してきました。つまり、クラスを作成して追加するだけでした。ここで、1歩後退してから、2歩前進する必要があります。この後退により、新しい機能を導入できるようになります。この機能は、テンプレートに基づくいくつかの情報を持つウィンドウクラスで、ここでの最初の部分になります。現在のすべての機能を維持しながら、コードを根本的に変更します。第2部ではIDEを扱います。


計画

私たちのエキスパートアドバイザー(EA)は現在、オブジェクトクラスで構成されています。これは下の図で見ることができます。

システムは現在正常に機能しており、非常に安定していますが、次に示すようにEAを再構築する必要があります。C_TemplateChartとC_SubWindowの位置が変更されて、追加のクラスがあることにお気付きかもしれません。


そのように編成し直す目的は何でしょうか。問題は、フローティングウィンドウの実装方法がアセットデータを含むウィンドウに適していないということです。よって、このような変更が必要になります。ただし、この変更は構造の面で見た目が良いだけでなく、コードを大幅に変更する必要があるため、以前のコードと大きく異なります。

では、さっそく仕事に取りかかりましょう。


実際の実装

1.EAの内部コードの変更

最初の大きな変更は、EA初期化ファイルからです。以下のコードをご覧ください。

input group "Window Indicators"
input string                    user01 = "";                    //Subwindow indicators
input group "WallPaper"
input string                    user10 = "Wallpaper_01";        //Used BitMap
input char                      user11 = 60;                    //Transparency (from 0 to 100)
input C_WallPaper::eTypeImage   user12 = C_WallPaper::IMAGEM;   //Chart background type
input group "Chart Trader"
input int                       user20   = 1;                   //Leverage factor
input int                       user21   = 100;                 //Take Profit (financial)
input int                       user22   = 75;                  //Stop Loss (financial)
input color                     user23   = clrBlue;             //Price line color
input color                     user24   = clrForestGreen;      //Take Profit line color
input color                     user25   = clrFireBrick;        //Stop line color
input bool                      user26   = true;                //Day Trade?
input group "Volume At Price"
input color                     user30  = clrBlack;             //Bar color
input char                      user31  = 20;                   //Transparency (from 0 to 100 )
//+------------------------------------------------------------------+
C_TemplateChart Chart;
C_WallPaper     WallPaper;
C_VolumeAtPrice VolumeAtPrice;
//+------------------------------------------------------------------+
int OnInit()
{
        static string   memSzUser01 = "";
        
        Terminal.Init();
        WallPaper.Init(user10, user12, user11);
        if (memSzUser01 != user01)
        {
                Chart.ClearTemplateChart();
                Chart.AddThese(memSzUser01 = user01);
        }
        Chart.InitilizeChartTrade(user20, user21, user22, user23, user24, user25, user26);
        VolumeAtPrice.Init(user24, user25, user30, user31);
        
   OnTrade();
        EventSetTimer(1);
   
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+


読み込まれるテンプレートを示す変数が1つしかありません。強調表示された部分を除いて、コードの残りの部分はそのままです。ここで何をするのか、またはこの強調表示されたコードがEA初期化部分に配置された理由が完全には明確でないかもしれません。EAをチャートに読み込むと、いくつかのものが作成され、通常の使用ではそれらを変更できます。以前は、強調表示されたコードを追加しても意味がありませんでした。これは、すべてが連携して機能することを目的としていて、変更がEAの動作や外観に影響を与えなかったためです。しかし、フローティングインジケータを追加すると、時間枠を変更するたびに、EAが再起動してウィンドウが元の状態に戻るという、厄介なことが起こります。破壊しなければ無駄なものがチャートに蓄積され、誤って破壊すれば元の場所に再構築されるので大変不便です。ユーザーが必要なテンプレートを変更しなかった場合、強調表示されたコードはフローティングウィンドウが誤って破棄されるのを防ぎますが、テンプレートに変更がある場合、EAは正常に再起動します。非常にシンプルですが、非常に効率的です。

次に注意すべきことは、内部メッセージシステムに関連しています。以前は追加の変数がありましたが、削除したため、コードは次のようになっています。

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, NanoEA.CheckPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
}


システムはMQL5メッセージ交換システムをより効率的に使用するようになりましたが、メッセージの送信はOnChartEvent関数自体と非常によく似ています。オブジェクトクラスにストレスを与えることなくパラメータを渡すことができるため、各クラスはMetaTrader5システムによって生成されたイベントメッセージを最も適切な方法で処理できます。これにより、各オブジェクトクラスをさらに分離するため、EAは、ユーザーのタイプごとに、より少ない労力で、より多様な形式をとることができます。


2.サブウィンドウサポートコードの変更

これまで、サブウィンドウのコードは非常に単純でしたが、何らかの理由で、EAが作成されたサブウィンドウを削除できないという問題がありました。EAが再度開かれると、新しいサブウィンドウが作成されるためにシステムの制御が失われました。驚いたことに、これを修正するのは非常に簡単でした。まず、以下に示すサポートファイルのフラグメントをご覧ください。

int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "SubWinSupport");
        
        return INIT_SUCCEEDED;
}

強調表示された行では、サポートファイルのエイリアスを作成します。EAはこのエイリアスEAを確認し、サブウィンドウシステムが読み込まれているかどうかを確認します。EAはエイリアスのみをチェックするため、これはファイル名に関係なくおこなわれます。同じコードタイプは、EAでその他のものをサポートするために後で使用されます。ここではあまり詳しく説明しませんが、後に別の記事で、強調表示されたコードを利用する方法を説明します。

完了したので、サブウィンドウを読み込んで作成するコードを見てみましょう。以下に示します。

void Init(void)
{
        int i0;
        if ((i0 = ChartWindowFind(Terminal.Get_ID(), def_Indicador)) == -1)
                ChartIndicatorAdd(Terminal.Get_ID(), i0 = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WINDOWS_TOTAL), iCustom(NULL, 0, "::" + def_Resource));
        m_IdSubWinEA = i0;
}


ご覧のとおり、はるかに単純ですが、このコードはpublicではなく、別のpublicコードを介してアクセスされます。

inline int GetIdSubWinEA(void)
{
        if (m_IdSubWinEA < 0) Init();
        return m_IdSubWinEA;
}


しかし、なぜこのように実装されているのでしょうか。EAがサブウィンドウでインジケータを使用せず、システムがこれを認識すると、チャートからサブウィンドウを削除し、必要な場合にのみ作成します。ただし、この決定はEAコードではなく、C_TemplateChartクラスによっておこなわれます。


3.新しいC_TemplateChartクラス

次のアニメーションをご覧ください。

分析している場所を示す垂直線があることにご注意ください。このような線は互いに独立しています。以前は利用できなかったため、チャートによってはインジケータの一部の点を分析することが困難でした。これは、C_TemplateChartクラスに含まれる改善の1つです。大幅な変更があったので、クラス内のコードを見て理解しましょう。

変数が宣言されている次のコードを見てみましょう。

class C_TemplateChart : public C_SubWindow
{
#define def_MaxTemplates                8
#define def_NameTemplateRAD     "IDE"
//+------------------------------------------------------------------+
        private :
//+------------------------------------------------------------------+
        enum eParameter {TEMPLATE = 0, PERIOD, SCALE, WIDTH, HEIGHT};
//+------------------------------------------------------------------+
                struct st
                {
                        string          szObjName,
                                        szSymbol,
                                        szTemplate,
                                        szVLine;
                        int             width,
                                        scale;
                        ENUM_TIMEFRAMES timeframe;
                        long            handle;
                }m_Info[def_MaxTemplates];
                int     m_Counter,
                        m_CPre,
                        m_Aggregate;
                struct st00
                {
                        int     counter;
                        string  Param[HEIGHT + 1];
                }m_Params;


最初に注意することは、C_TemplateChartクラスがC_SubWindowクラスを拡張することです。コードのこの部分は特別なようには見えませんが、強調表示された部分に注意してください。これは、ユーザーが要求したインジケターを適切に作成および表示できる内部データ分析システムを指します。これで、ユーザーが物事をどのように示すかを説明するシステムが標準化され始めました。難しく見えても、時間の経過とともに明らかになります。新しい形式を説明するには次のフラグメントを分析する必要があります。これには、ユーザーの要求を分析する責任があります。

int GetCommand(int iArg, const string szArg)
{
        for (int c0 = TEMPLATE; c0 <= HEIGHT; c0++) m_Params.Param[c0] = "";
        m_Params.counter = 0;
        for (int c1 = iArg, c2 = 0; szArg[iArg] != 0x00; iArg++) switch (szArg[iArg])
        {
                case ')':
                case ';':
                        m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2);
                        for (; (szArg[iArg] != 0x00) && (szArg[iArg] != ';'); iArg++);
                        return iArg + 1;
                case ' ':
                        c2 += (c1 == iArg ? 0 : 1);
                        c1 = (c1 == iArg ? iArg + 1 : c1);
                        break;
                case '(':
                case ',':
                        c2 = (m_Params.counter == SCALE ? (c2 >= 1 ? 1 : c2) : c2);
                        m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2);
                        c2 = 0;
                        c1 = iArg + 1;
                        break;
                default:
                        c2++;
                        break;
        }
        return -1;
}


まず、以前に構造に含まれていたすべてのデータを消去します。次に、パラメータがある場合は、1つづつ分析と受信を開始します。これらは必須ではありませんが、ユーザーへの表示方法を示します。このシステムは自己拡張型です。したがって、さらに情報を追加する場合は、eParameter列挙で指定する必要があります。現在、システムには5つのパラメータがあり、eParameters 列挙で示されているのと同じ順序で指定する必要があります。変数宣言の部分ではこの列挙型が強調表示されています。正しい順序のパラメータとその説明を以下に示します。

パラメータ 結果
1.TEMPLATE/ASSET 表示するテンプレート/アセットを指定
2.PERIOD 指定すると、以前に使用されたのと同じように、インジケータが指定された固定期間に設定される
3.SCALE 指定すると、インジケータが固定スケールにバインドされる
4.幅 指定すると、ウィンドウ内のインジケータの幅を設定
5.HEIGHT この新しいパラメータについては、本稿で後で説明します。これは、フローティングウィンドウの使用を示しています。

現時点でパラメータ5の恩恵を受けない唯一の構造はIDEですが、これは修正される予定で、次の記事ではIDEの恩恵を受ける方法を示します。この記事では、その他のシステムに焦点を当てます。

ここで、何らかの理由で、ユーザーがインジケータの垂直線の色を制御したいとします。パラメータ分析コードに変更を加える必要はありません。次の変更を加えるだけです。

class C_TemplateChart : public C_SubWindow
{
#define def_MaxTemplates        8
#define def_NameTemplateRAD     "IDE"
//+------------------------------------------------------------------+
        private :
//+------------------------------------------------------------------+
        enum eParameter {TEMPLATE = 0, COLOR_VLINE, PERIOD, SCALE, WIDTH, HEIGHT};
//+------------------------------------------------------------------+

// ... Internal code ....

                struct st00
                {
                        int     counter;
                        string  Param[HEIGHT + 1];
                }m_Params;


システムは、1回の呼び出しで6つのパラメータが存在する可能性があることを自動的に認識します。ここで、別の問題が発生しています。上記のGetCommandコードは正常に機能してはいますが、バグがあります。自分で使用するシステムを作成している場合にはこのバグに気付かないことがよくありますが、システムを他の人が使用できるようにすると、エラーが明らかになります。経験の浅いプログラマーは問題を解決する方法がわからないかもしれません。これが、オブジェクト指向プログラミングが非常に高く評価されている理由です。最も関心のあるプログラムで使用するのに最も適切なモデルを作成できるということです。OOPの前提条件の1つは、データとクラス変数が正しく初期化されていることです。保証するには、すべてをテストすることが重要です。GetCommandコードは正しいように見えますが、バグが含まれています。最大パラメータ制限がチェックされていません。元のモデルが5つのパラメータのみを受け入れる場合、ユーザーが6つのパラメータを設定するとどうなるでしょうか。これは避けるべきことです。すべてが機能すると仮定せず、すべてが機能することを保証する必要があります。以下に示すようにコードを修正する必要があります(修正は強調表示されています)。

int GetCommand(int iArg, const string szArg)
{
        for (int c0 = TEMPLATE; c0 <= HEIGHT; c0++) m_Params.Param[c0] = "";
        m_Params.counter = 0;
        for (int c1 = iArg, c2 = 0; szArg[iArg] != 0x00; iArg++) switch (szArg[iArg])
        {
                case ')':
                case ';':
                        m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2);
                        for (; (szArg[iArg] != 0x00) && (szArg[iArg] != ';'); iArg++);
                        return iArg + 1;
                case ' ':
                        c2 += (c1 == iArg ? 0 : 1);
                        c1 = (c1 == iArg ? iArg + 1 : c1);
                        break;
                case '(':
                case ',':
                        if (m_Params.counter == HEIGHT) return StringLen(szArg) + 1;
                        c2 = (m_Params.counter == SCALE ? (c2 >= 1 ? 1 : c2) : c2);
                        m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2);
                        c2 = 0;
                        c1 = iArg + 1;
                        break;
                default:
                        c2++;
                        break;
        }
        return -1;
}

1行追加するだけで、システムが予期しない結果を生成するのを防ぐことができます。予期される最後のパラメータがHEIGHTであるがそれが最後ではない場合、これは論理的に何かが間違っていることを意味するため、その後に宣言されたすべてを無視して、問題を回避します。

システムがパターンとパラメータを認識する方法がわからない場合の構文は次のとおりです。

Parameter_00 (Parameter_01, Parameter_02, Parameter_03, Parameter_04)

ここで、Parameter_00は使用するテンプレートを指定し、残りはコンマ( , )で区切られ、eParameter列挙で定義された値を示します。Parameter_03のみを変更する場合は、残りを空のままにしておくことができます(下の図参照)。この図は、システムがユーザーの希望どおりに機能することを示しています。

       

関数呼び出しと非常によく似た標準化された表示がありますが、難しく見えるかもしれません。実際に起こったことは次のとおりです。RSIテンプレートを指定してから、期間もスケールも指定しません。システムがメインチャートに従う必要があることを理解できるように、これらの値は空白のままにします。ただし、幅と高さを指定するため、システムはこれがフローティングウィンドウに表示される必要があることを理解するので、RSIインジケーターがフローティングウィンドウに表示されます。ADXテンプレートでは、サブウィンドウで定義された幅で表示されるように、幅のみを指定します。Stochインジケータは、残りのサブウィンドウ全体を取得し、ADXとスペースを共有します。ただし、何かを変更したい場合は難しくはありません。ADXの高さを指定するとどうなるかを確認してください。

ADXの表示方法がすぐに変更されます。つまり、サブウィンドウ全体をStochに残したまま、ADXはフローティングウィンドウに配置されます。すべてのフローティングウィンドウは、互いから完全に独立しています。ただし、これで全部ではありませんl。次の図をご覧ください。

サブウィンドウは不要になったため、削除されています。しかし、どの関数がこれらすべてに責任があるのでしょうか。これは以下に示されています。変更をいくつか加えるだけで、多くの興味深いことができます。

void AddTemplate(void)
{
        ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;
        string sz0 = m_Params.Param[PERIOD];
        int w, h, i;
        bool bIsSymbol;

        if (sz0 == "1M") timeframe = PERIOD_M1; else
        if (sz0 == "2M") timeframe = PERIOD_M2; else
        if (sz0 == "3M") timeframe = PERIOD_M3; else
        if (sz0 == "4M") timeframe = PERIOD_M4; else
        if (sz0 == "5M") timeframe = PERIOD_M5; else
        if (sz0 == "6M") timeframe = PERIOD_M6; else
        if (sz0 == "10M") timeframe = PERIOD_M10; else
        if (sz0 == "12M") timeframe = PERIOD_M12; else
        if (sz0 == "15M") timeframe = PERIOD_M15; else
        if (sz0 == "20M") timeframe = PERIOD_M20; else
        if (sz0 == "30M") timeframe = PERIOD_M30; else
        if (sz0 == "1H") timeframe = PERIOD_H1; else
        if (sz0 == "2H") timeframe = PERIOD_H2; else
        if (sz0 == "3H") timeframe = PERIOD_H3; else
        if (sz0 == "4H") timeframe = PERIOD_H4; else
        if (sz0 == "6H") timeframe = PERIOD_H6; else
        if (sz0 == "8H") timeframe = PERIOD_H8; else
        if (sz0 == "12H") timeframe = PERIOD_H12; else
        if (sz0 == "1D") timeframe = PERIOD_D1; else
        if (sz0 == "1S") timeframe = PERIOD_W1; else
        if (sz0 == "1MES") timeframe = PERIOD_MN1;
        if ((m_Counter >= def_MaxTemplates) || (m_Params.Param[TEMPLATE] == "")) return;
        bIsSymbol = SymbolSelect(m_Params.Param[TEMPLATE], true);
        w = (m_Params.Param[WIDTH] != "" ? (int)StringToInteger(m_Params.Param[WIDTH]) : 0);
        h = (m_Params.Param[HEIGHT] != "" ? (int)StringToInteger(m_Params.Param[HEIGHT]) : 0);
        i = (m_Params.Param[SCALE] != "" ? (int)StringToInteger(m_Params.Param[SCALE]) : -1);
        i = (i > 5 || i < 0 ? -1 : i);
        if ((w > 0) && (h > 0)) AddIndicator(m_Params.Param[TEMPLATE], 0, -1, w, h, timeframe, i); else
        {
                SetBase(m_Params.Param[TEMPLATE], (bIsSymbol ? m_Params.Param[TEMPLATE] : _Symbol), timeframe, i, w);
                if (!ChartApplyTemplate(m_Info[m_Counter - 1].handle, m_Params.Param[TEMPLATE] + ".tpl")) if (bIsSymbol) ChartApplyTemplate(m_Info[m_Counter - 1].handle, "Default.tpl");
                if (m_Params.Param[TEMPLATE] == def_NameTemplateRAD)
                {
                        C_Chart_IDE::Create(GetIdSubWinEA());
                        m_Info[m_Counter - 1].szVLine = "";
                }else
                {
                        m_Info[m_Counter - 1].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand();
                        ObjectCreate(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJ_VLINE, 0, 0, 0);
                        ObjectSetInteger(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJPROP_COLOR, clrBlack);
                }
                ChartRedraw(m_Info[m_Counter - 1].handle);
        }
}

強調表示された部分は、データがどのように画面に表示されるかの選択を示しています。コードは、以前の既存コードとあまり変わりません。ただし、これらのテストで、システムがユーザーの希望どおりに動作することを保証します。これまでのところ、新しいモデルでコードを再構築する必要はありませんが、サブウィンドウのサイズ変更を担当する関数を見ると状況が変わります。

void Resize(void)
{
#define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, A, B)
        int x0, x1, y;
        if (!ExistSubWin()) return;
        x0 = 0;
        y = (int)(ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS, GetIdSubWinEA()));
        x1 = (int)((Terminal.GetWidth() - m_Aggregate) / (m_Counter > 0 ? (m_CPre == m_Counter ? m_Counter : (m_Counter - m_CPre)) : 1));
        for (char c0 = 0; c0 < m_Counter; x0 += (m_Info[c0].width > 0 ? m_Info[c0].width : x1), c0++)
        {
                macro_SetInteger(OBJPROP_XDISTANCE, x0);
                macro_SetInteger(OBJPROP_XSIZE, (m_Info[c0].width > 0 ? m_Info[c0].width : x1));
                macro_SetInteger(OBJPROP_YSIZE, y);
                if (m_Info[c0].szTemplate == "IDE") C_Chart_IDE::Resize(x0);
        }
        ChartRedraw();
#undef macro_SetInteger
}

強調表示された行で、システムが古いベースで構築されるのを防ぎます。この行は、EAによって開かれ、維持されているサブウィンドウがあるかどうかをチェックします。ない場合は関数は他に何もせずに戻ります。ただし、そのようなサブウィンドウが存在する場合は、その中のすべてのものを必要に応じてサイズ変更する必要があります。このテストのためだけに、システムは完全に刷新されました。

以下は、後1つ変更された関数です。

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        int mx, my;
        datetime dt;
        double p;

        C_Chart_IDE::DispatchMessage(id, lparam, dparam, sparam);
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        mx = (int)lparam;
                        my = (int)dparam;
                        ChartXYToTimePrice(Terminal.Get_ID(), mx, my, my, dt, p);
                        for (int c0 = 0; c0 < m_Counter; c0++)  if (m_Info[c0].szVLine != "")
                        {
                                ObjectMove(m_Info[c0].handle, m_Info[c0].szVLine, 0, dt, 0);
                                ChartRedraw(m_Info[c0].handle);
                        }
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        Resize();
                        for (int c0 = 0; c0 < m_Counter; c0++)
                        {
                                ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, OBJPROP_PERIOD, m_Info[c0].timeframe);
                                ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, OBJPROP_CHART_SCALE,(m_Info[c0].scale < 0 ? ChartGetInteger(Terminal.Get_ID(),CHART_SCALE):m_Info[c0].scale));
                        }
                        break;
        }
}


強調表示された部分には本当に特別な注意が必要です。何をするのでしょうか。このコードは、適切な場所と適切なテンプレートで垂直線を表示します。残りのコードでは、チャートの変更に応じてテンプレートを維持および調整するだけです。

OnChartEventシステム内のEAではなく、オブジェクトクラスでこれをおこなうことには多くの利点があります。主なものは、MetaTrader5がEAに送信するイベントを各クラスが処理できることです。すべてを単一の関数に集中させるのではなく、各クラスにその役割を任せます。EAでクラスを使用したくない場合は、残りのコードに副作用を与えることなく、クラスを削除するだけです。

プログラミングは美しいですね。私はプログラミングが大好きです...

この記事の次のポイントに進む前に、パラメータ1と2で使用できる値について簡単にコメントします。パラメータ1には、1M、2M、3M、4M、5M、6M、10M、12M、15M、20M、30M、1H、2H、3H、4H、6H、8H、12H、1D、1S、1MONTHの値を割り当てることができます。これらの値はランダムではなく、ENUM_TIMEFRAME列挙から取られています。通常のチャートを使用している場合とまったく同じように繰り返されます。パラメータ2は、0~5の値を取ることができます。ここで、0は最も遠いスケールで、5は最も近いスケールです。詳細については、CHART_SCALEを参照してください。


3.4 フローティングウィンドウのサポート

ここで、フローティングウィンドウがどのように作成および維持されるかを理解しましょう。これを理解しないと、システムを実際に利用することができません。これを担当するオブジェクトクラスには、C_ChartFloatingという名前が付けられました。標準MQL5ライブラリのControlクラスをなぜ使用しないのかと思われるかもしれませんが、理由は簡単です。コントロールクラスを使用すると、マシンに存在するオペレーティングシステムと非常によく似た機能を備えたウィンドウを作成および維持できますが、ここでの目的には大げさすりです。もっと単純なものが必要です。ここでやろうとしていることにコントロールクラスを使うのは、ロケット弾を使ってハエを殺すようなものです。よって、C_ChartFloatingクラスを使用します。これには、フローティングウィンドウをサポートし、制御するために必要な最小限の要素が含まれます。

4つのグラフィカルオブジェクトを作成するだけなので、クラス自体を説明する必要はあまりありません。しかし、内部関数の中には、特別な注意が必要なものが2つあります。ウィンドウを作成する関数から始めましょう。

bool AddIndicator(string sz0, int x = 0, int y = -1, int w = 300, int h = 200, ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT, int Scale = -1)
{
        m_LimitX = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS);
        m_LimitY = (int)ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS);
        if (m_MaxCounter >= def_MaxFloating) return false;
        y = (y < 0 ? m_MaxCounter * def_SizeBarCaption : y);
        CreateBarTitle();
        CreateCaption(sz0);
        CreateBtnMaxMin();
        CreateRegion(TimeFrame, Scale);
        m_Win[m_MaxCounter].handle = ObjectGetInteger(Terminal.Get_ID(), m_Win[m_MaxCounter].szRegionChart, OBJPROP_CHART_ID);
        ChartApplyTemplate(m_Win[m_MaxCounter].handle, sz0 + ".tpl");   
        m_Win[m_MaxCounter].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand();
        ObjectCreate(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJ_VLINE, 0, 0, 0);
        ObjectSetInteger(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJPROP_COLOR, clrBlack);
        m_Win[m_MaxCounter].PosX = -1;
        m_Win[m_MaxCounter].PosY = -1;
        m_Win[m_MaxCounter].PosX_Minimized = m_Win[m_MaxCounter].PosX_Maximized = x;
        m_Win[m_MaxCounter].PosY_Minimized = m_Win[m_MaxCounter].PosY_Maximized = y;
        SetDimension(w, h, true, m_MaxCounter);
        SetPosition(x, y, m_MaxCounter);
        ChartRedraw(m_Win[m_MaxCounter].handle);
        m_MaxCounter++;
        return true;
}

このコードでは、CHARTオブジェクトにテンプレートを適用できるように、必要なすべてのサポートを作成します。これは強調表示されたコード部分に実装されています。この関数を呼び出すために実際に必要なパラメータはテンプレート名だけであることに注意してください。他のすべての値は事前に初期化されていますが、必要な値を指定することを妨げるものはありません。新しいウィンドウが作成されるたびに、次のウィンドウはわずかにオフセットされ、デフォルトで他のウィンドウと重ならないようになります。次の図に示します。

y = (y < 0 ? m_MaxCounter * def_SizeBarCaption : y);


このクラスの次の興味深い関数ではメッセージを処理します。そのコードは次のとおりです。

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        int mx, my;
        datetime dt;
        double p;
        static int six = -1, siy = -1, sic = -1;
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        mx = (int)lparam;
                        my = (int)dparam;
                        if ((((int)sparam) & 1) == 1)
                        {
                                if (sic == -1)  for (int c0 = m_MaxCounter - 1; (sic < 0) && (c0 >= 0); c0--)
                                        sic = (((mx > m_Win[c0].PosX) && (mx < (m_Win[c0].PosX + m_Win[c0].Width)) && (my > m_Win[c0].PosY) && (my < (m_Win[c0].PosY + def_SizeBarCaption))) ? c0 : -1);
                                if (sic >= 0)
                                {
                                        if (six < 0) ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false);
                                        six = (six < 0 ? mx - m_Win[sic].PosX : six);
                                        siy = (siy < 0 ? my - m_Win[sic].PosY : siy);
                                        SetPosition(mx - six, my - siy, sic);
                                }
                        }else
                        {
                                if (six > 0) ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
                                six = siy = sic = -1;
                        }
                        ChartXYToTimePrice(Terminal.Get_ID(), mx, my, my, dt, p);
                        for (int c0 = 0; c0 < m_MaxCounter; c0++)
                                ObjectMove(m_Win[c0].handle, m_Win[c0].szVLine, 0, dt, 0);
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        for (int c0 = 0; c0 < m_MaxCounter; c0++) if (sparam == m_Win[c0].szBtnMaxMin)
                        {
                                SwapMaxMin((bool)ObjectGetInteger(Terminal.Get_ID(), m_Win[c0].szBtnMaxMin, OBJPROP_STATE), c0);
                                break;
                        }
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        for(int c0 = 0; c0 < m_MaxCounter; c0++)
                        {
                               ObjectSetInteger(Terminal.Get_ID(), m_Win[c0].szRegionChart, OBJPROP_PERIOD, m_Win[c0].TimeFrame);
                               ObjectSetInteger(Terminal.Get_ID(), m_Win[c0].szRegionChart, OBJPROP_CHART_SCALE,(m_Win[c0].Scale < 0 ? ChartGetInteger(Terminal.Get_ID(),CHART_SCALE):m_Win[c0].Scale));
                        }
                        m_LimitX = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS);
                        m_LimitY = (int)ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS);
                        break;
        }
        for (int c0 = 0; c0 < m_MaxCounter; c0++)
                ChartRedraw(m_Win[c0].handle);
}


この関数では、C_ChartFloatingクラスがサポートするすべてのイベント処理を実装します。ウィンドウがいくつあっても、処理はすべて同じです。EAのOnChartEvent関数内でこれをおこなった場合、関数は非常に複雑になり、安定性が低くなります。そして、ここでオブジェクトクラスで機能を実装することにより、コードの整合性を保証します。フローティングウィンドウを使用する必要がない場合は、クラスとアクセス可能なポイントからファイルを削除するだけです。この実装によってコードがはるかに高速かつ簡単になります。

また、上記のコードには興味深い部分があります。強調表示されていますが、その内部コードは次のとおりです。

void SwapMaxMin(const bool IsMax, const int c0)
{
        m_Win[c0].IsMaximized = IsMax;
        SetDimension((m_Win[c0].IsMaximized ? m_Win[c0].MaxWidth : 100), (m_Win[c0].IsMaximized ? m_Win[c0].MaxHeight : 0), false, c0);
        SetPosition((m_Win[c0].IsMaximized ? m_Win[c0].PosX_Maximized : m_Win[c0].PosX_Minimized), (m_Win[c0].IsMaximized ? m_Win[c0].PosY_Maximized : m_Win[c0].PosY_Minimized), c0);
}


上記のコードは何をするのでしょうか。難しすぎますか。理解するために、以下のアニメーションを詳しく見てみましょう。


作成されたフローティングウィンドウには、プログラマーまたはクラス自体の位置づけシステムによって指定される初期アンカーポイントがあります。このアンカーポイントは、最大化されたウィンドウと最小化されたウィンドウの両方で同じです。これらの値は固定されていないため、ユーザーが簡単に変更できます。

空白のチャートの特定の場所が必要な場合、最大化されたウィンドウをすばやく読みやすい場所に移動し、同じウィンドウを最小化して別の場所(たとえば画面の隅)に移動できます。システムはそれを記憶し、ウィンドウを最大化すると、最小化する前の最後のアンカーポイントにジャンプします。ウィンドウが最小化されているという逆の状況にも同じことが当てはまります。


結論

今のところこれですべてです。次の記事では、この機能をIDEサポートクラスに拡張します。