恥は/dev/nullへ by 初心者

プログラミング素人がのろのろと学んだことをつづっています♪

[MQL5]TradingView風味の操作でMT5に四角形を描画してみる

TradingView風味にできないかしら?

トレードにはMT5を使っていますが、チャート分析ではTradingViewを使うことが多いので、MT5のチャートにオブジェクト(四角形、トレンドライン、水平線など)を配置する時、 TradingViewと同じような操作感にできないものかと思いました。

たとえば、MT5で四角形を描画する場合は

(1) 四角形アイコンをクリック
(2) チャート上のどこかにマウスカーソルを移動
(3) マウスの左ボタンを押下したまま、マウスを移動
(4) マウスの左ボタンを放す

とします。

TradingViewで四角形を描画する場合は

(1) 四角形アイコンをクリック
(2) チャート上のどこかをクリック
(3) マウスを移動
(4) マウスの左ボタンをクリック

とします。

個人的には、TradingViewの方式の方が楽だと感じています。そこで、TradingView風の操作感で四角形を作成するボタンを作ってみました。



TradingView風味のコード(MT5用)

// このインディケーターが対応している時間軸は M5, M15, H1, H4, D1, W1, MN

#property copyright "Copyright 2022. PHILOJUAN"
#property link      "https://uhoho.hatenablog.jp/"
#property strict
#property indicator_chart_window
#property indicator_plots 0

input color color_1  = clrLimeGreen;     // 色(5分足)
input int   width_1  = 1;                // 太さ(5分足)
input color color_2  = clrLimeGreen;     // 色(15分足)
input int   width_2  = 1;                // 太さ(15分足)
input color color_3  = C'85, 85, 255';   // 色(1時間足)
input int   width_3  = 1;                // 太さ(1時間足)
input color color_4  = clrDarkOrange;    // 色(4時間足)
input int   width_4  = 1;                // 太さ(4時間足)
input color color_5  = clrMagenta;       // 色(日足)
input int   width_5  = 2;                // 太さ(日足)
input color color_6  = clrDeepSkyBlue;   // 色(週足)
input int   width_6  = 2;                // 太さ(週足)
input color color_7  = clrMediumOrchid;  // 色(月足)
input int   width_7  = 2;                // 太さ(月足)

input int buttonXdis1 = 450;      // ボタンの表示位置(X軸)
input int buttonYdis1 =   6;      // ボタンの表示位置(Y軸)
input color colorOfBorder = clrNONE;     // ボタンの縁の色

//--------------------------------------------------------------------
// グローバル変数 (パラメーター設定の対象にしないもの)
//--------------------------------------------------------------------
string buttonName  = "GBBPPQ_Rec_HHK";    // ボタン名
color  colorOfRecs = clrNONE;             // オブジェクトの色
int    widthOfLine = 1;                   // Rectangleのラインの太さ
string RecName     = "";                  // Rectangleの名前
int    numOfRec    = 0;                   // Rectangleの数
int    step        = 0;                   // 処理のステップ
string prefix      = "RZ98FDBWQQP_";      // オブジェクト名の接頭辞


//------------------------------------------------------------------
//  インディケーター挿入時 or timeframe変更直後
//------------------------------------------------------------------
int OnInit()
{
    string shortname = "Rec_button_indicator";
    IndicatorSetString(INDICATOR_SHORTNAME, shortname);

    if(width_1 < 1 || width_2 < 1 || width_3 < 1 || width_4 < 1 ||  width_5 < 1 ||  width_6 < 1 ||  width_7 < 1 || 
       width_1 > 5 || width_2 > 5 || width_3 > 5 || width_4 > 5 ||  width_5 > 5 ||  width_6 > 5 ||  width_7 > 5   ) {
        Print("Width parameter is wrong");
        ChartIndicatorDelete(0, 0,shortname);
        return INIT_FAILED;
    }

    if(buttonXdis1 < 1 || buttonYdis1 < 1) {
        Print("Position parameter is wrong");
        ChartIndicatorDelete(0, 0,shortname);
        return INIT_FAILED;
    }

    CreateButton(buttonName, "Rect", CORNER_LEFT_UPPER, buttonXdis1, buttonYdis1,
                 60, 15, 7, clrWhite, colorOfBorder, clrGray);

    SetColorAndWidth(TimeFrameToMinutes(Period()));
    return(INIT_SUCCEEDED);
}


//------------------------------------------------------------------
// Tick受信時                                (今回は処理なし)
//------------------------------------------------------------------
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
    return(rates_total);
}


//------------------------------------------------------------------
//  インディケーター除去時 or timeframe変更直前
//------------------------------------------------------------------
void OnDeinit(const int reason)
{
    ObjectDelete(0, buttonName);
    ChartRedraw();
}


//+------------------------------------------------------------------
//| ChartEvent function
//+------------------------------------------------------------------
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
    datetime time   =   0;
    double   price  = 0.0;
    int      window =   0;

    if(id == CHARTEVENT_OBJECT_CLICK) {
        if(sparam == buttonName && step == 0) step = 1;
    }

    if(id == CHARTEVENT_CLICK) {
        if(step == 1) step = 2;
        else if(step == 2) {
            if(!ChartXYToTimePrice(0, (int)lparam, (int)dparam, window, time, price))
                Print("ChartXYToTimePrice error. Erro code : ", GetLastError());
            
            RecName = prefix + IntegerToString(numOfRec);  // オブジェクト名作成
            // 既に同名オブジェクトがある場合への対応
            while(ObjectFind(0, RecName) >= 0) {
                numOfRec++;
                RecName = prefix + IntegerToString(numOfRec);
            }

            MakeRectangle(RecName, time, price, time, price, colorOfRecs, STYLE_SOLID, widthOfLine, false);
            ChartRedraw();
            ChartEventMouseMoveSet(true);  // CHARTEVENT_MOUSE_MOVEモードに入る
        }
    }

    if(id == CHARTEVENT_MOUSE_MOVE) {
        if(step == 2) {
            // マウスの移動に合わせて、Rectangleのアンカーを変更
            if(!ChartXYToTimePrice(0, (int)lparam, (int)dparam, window, time, price))
                Print("ChartXYToTimePrice error. Erro code : ", GetLastError());

            // ObjectMoveの第3引数はpoint index(アンカーポイント番号)
            // Rectangleの場合、アンカーポイントは2つなので、point index は 0 か 1
            ObjectMove(0, RecName, 1, time, price);
            ChartRedraw();

            // 左クリックをしたら CHARTEVENT_MOUSE_MOVE モードを抜ける
            // CHARTEVENT_MOUSE_MOVEでのsparamはマウスボタンの状態(1は押下した状態)
            if(sparam == "1") {     
                ChartEventMouseMoveSet(false);
                ObjectSetInteger(0, buttonName, OBJPROP_STATE, false);
                ChartRedraw();
                step = 0;
            }
        }
    }
}


//--------------------------------------------------------------------
// CHART_EVENT_MOUSE_MOVEモードのON / OFF を切り替える関数
//--------------------------------------------------------------------
bool ChartEventMouseMoveSet(const bool value, const long chart_ID = 0)
{
    ResetLastError();
    if(!ChartSetInteger(chart_ID, CHART_EVENT_MOUSE_MOVE, 0, value)) {
        Print(__FUNCTION__ + ", Error Code = ", GetLastError());
        return(false);
    }
    return(true);
}
// この関数はMetaEditorのヘルプからそのまま流用(記述箇所は以下のとおり)
// 「標準的な定数、列挙と構造体」→「チャート定数」→「チャート操作の例」


//--------------------------------------------------------------------
// Rectangleを描画する関数
//--------------------------------------------------------------------
void MakeRectangle(string          objName,  // オブジェクト名
                   datetime        rcTime1,  // 描画座標1(時間)
                   double          rcPrice1, // 描画座標1(価格)
                   datetime        rcTime2,  // 描画座標2(時間)
                   double          rcPrice2, // 描画座標2(価格)
                   color           rcColor,  // 色
                   ENUM_LINE_STYLE rcStyle,  // 線種
                   int             rcWidth,  // 太さ
                   bool            rcFill)   // 塗りつぶし
{
    if(rcStyle != STYLE_SOLID && rcWidth > 1) {
        Alert("[MakeRectangle Function] Width is over 1. Style must be solid.");
        return;
    }

    ObjectCreate(    0, objName, OBJ_RECTANGLE, 0, rcTime1, rcPrice1, rcTime2, rcPrice2);
    ObjectSetInteger(0, objName, OBJPROP_COLOR, rcColor);
    ObjectSetInteger(0, objName, OBJPROP_STYLE, rcStyle);
    ObjectSetInteger(0, objName, OBJPROP_WIDTH, rcWidth);
    ObjectSetInteger(0, objName, OBJPROP_BACK, false);     // false 前景
    ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, true);
    ObjectSetInteger(0, objName, OBJPROP_SELECTED, true);
    ObjectSetInteger(0, objName, OBJPROP_HIDDEN, false);
    ObjectSetInteger(0, objName, OBJPROP_ZORDER, 0);
    ObjectSetString( 0, objName, OBJPROP_TOOLTIP, "\n");
    ObjectSetInteger(0, objName, OBJPROP_FILL, rcFill);   // 塗りつぶし
}


//--------------------------------------------------------------------
// MQL5の時間軸をint型の「分」に変換する関数
//--------------------------------------------------------------------
// [備考] MQL4と違い、PERIOD_H1以上は分数に対応していない
int TimeFrameToMinutes(ENUM_TIMEFRAMES timeframe)
{
    int minutes = 0;

    if(timeframe == PERIOD_CURRENT) timeframe = _Period;

    if(timeframe < PERIOD_H1) {
        minutes = (int)StringToInteger(StringSubstr(EnumToString(timeframe), 8));
        // [備考]
        // MT5の仕様が変わらないなら、if(timeframe < PERIOD_H1) の場合は
        //     minutes = timeframe;
        // でもOK。
    }
    else if(timeframe < PERIOD_D1) {
        minutes = 60 * (int)StringToInteger(StringSubstr(EnumToString(timeframe), 8));
    }
    else {
        switch(timeframe) {
            case PERIOD_D1:
                minutes = 1440;
                break;
            case PERIOD_W1:
                minutes = 10080;
                break;
            case PERIOD_MN1:
                minutes = 43200;  // 43200分は 30日に相当(MQL4のMN1を踏襲)
                break;
            default:
                minutes = -1;   // ERROR
        }
    }
    return minutes;
}


//--------------------------------------------------------------------
// ボタンを作成する関数
//--------------------------------------------------------------------
void CreateButton(string name,        // オブジェクト名
                  string caption,     // ボタン内に表示するテキスト
                  int corner,         // 基点(ボタンを配置する時に基準とする場所)
                  int xshift,         // 基点からの距離(横方向)
                  int yshift,         // 基点からの距離(縦方向)
                  int xsize,          // ボタンのサイズ(横)
                  int ysize,          // ボタンのサイズ(縦)
                  int font_Size,      // ボタン内に表示するテキストのサイズ
                  color textColor,    // ボタン内に表示するテキストの色
                  color bo_color,     // ボタンの縁の色
                  color bg_Color)     // ボタンの色
{
    ObjectCreate(    0, name, OBJ_BUTTON, 0, 0, 0);
    ObjectSetInteger(0, name, OBJPROP_CORNER, corner);
    ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xshift);
    ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yshift);
    ObjectSetInteger(0, name, OBJPROP_XSIZE, xsize);
    ObjectSetInteger(0, name, OBJPROP_YSIZE, ysize);

    ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, bo_color);
    ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bg_Color);

    ObjectSetString (0, name, OBJPROP_TEXT, caption);
    ObjectSetString (0, name, OBJPROP_FONT, "Arial");
    ObjectSetInteger(0, name, OBJPROP_FONTSIZE, font_Size);
    ObjectSetInteger(0, name, OBJPROP_COLOR, textColor);

    ObjectSetInteger(0, name, OBJPROP_STATE, false);
    ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);

    ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
    ObjectSetInteger(0, name, OBJPROP_SELECTED, false);
    ObjectSetInteger(0, name, OBJPROP_ZORDER, 0);
}


//--------------------------------------------------------------------
// チャートの時間軸に応じた色と太さをセットする関数
//--------------------------------------------------------------------
// [注意] この関数内ではグローバル変数を使用している
void SetColorAndWidth(int timeframe)
{
    if(timeframe <= 5) {               // 5分足
        colorOfRecs = color_1;
        widthOfLine = width_1;
    }
    else if(timeframe <= 15) {         // 15分足
        colorOfRecs = color_2;
        widthOfLine = width_2;
    }
    else if(timeframe <= 60) {         // 1時間足
        colorOfRecs = color_3;
        widthOfLine = width_3;
    }
    else if(timeframe <= 240) {        // 4時間足
        colorOfRecs = color_4;
        widthOfLine = width_4;
    }
    else if(timeframe <= 1440) {       // 日足
        colorOfRecs = color_5;
        widthOfLine = width_5;
    }
    else if(timeframe <= 10080) {      // 週足
        colorOfRecs = color_6;
        widthOfLine = width_6;
    }
    else {                      // 月足
        colorOfRecs = color_7;
        widthOfLine = width_7;
    }
}


ついでに、MT5風味のコード(MT5用)

メリットは特にないのですが、MT5のツールバーに配置されている四角形作成ボタンを使った場合と同じ操作感をインディケーター(以下「インジ」)で再現してみようと思い、コードを書いてみました。

// このインディケーターが対応している時間軸は M5, M15, H1, H4, D1, W1, MN

#property copyright "Copyright 2022. PHILOJUAN"
#property link      "https://uhoho.hatenablog.jp/"
#property strict
#property indicator_chart_window
#property indicator_plots 0

input color color_1  = clrLimeGreen;     // 色(5分足)
input int   width_1  = 1;                // 太さ(5分足)
input color color_2  = clrLimeGreen;     // 色(15分足)
input int   width_2  = 1;                // 太さ(15分足)
input color color_3  = C'85, 85, 255';   // 色(1時間足)
input int   width_3  = 1;                // 太さ(1時間足)
input color color_4  = clrDarkOrange;    // 色(4時間足)
input int   width_4  = 1;                // 太さ(4時間足)
input color color_5  = clrMagenta;       // 色(日足)
input int   width_5  = 2;                // 太さ(日足)
input color color_6  = clrDeepSkyBlue;   // 色(週足)
input int   width_6  = 2;                // 太さ(週足)
input color color_7  = clrMediumOrchid;  // 色(月足)
input int   width_7  = 2;                // 太さ(月足)

input int buttonXdis1 = 450;             // ボタンの表示位置(X軸)
input int buttonYdis1 =   6;             // ボタンの表示位置(Y軸)
input color colorOfBorder = clrNONE;     // ボタンの縁の色

//--------------------------------------------------------------------
// グローバル変数 (パラメーター設定の対象にしないもの)
//--------------------------------------------------------------------
string buttonName  = "GBBPPQ_Rec_HHK";    // ボタン名
color  colorOfRecs = clrNONE;             // オブジェクトの色
int    widthOfLine = 1;                   // ラインの太さ
string RecName     = "";                  // Rectangleの名前
int    numOfRec    = 0;                   // Rectangleの数
int    step        = 0;                   // 処理のステップ
string prefix      = "RZ98FDBWQQP_";      // オブジェクト名の接頭辞


//------------------------------------------------------------------
//  インディケーター挿入時 or timeframe変更直後
//------------------------------------------------------------------
int OnInit()
{
    string shortname = "Rec_button_indicator";
    IndicatorSetString(INDICATOR_SHORTNAME, shortname);

    if(width_1 < 1 || width_2 < 1 || width_3 < 1 || width_4 < 1 ||  width_5 < 1 ||  width_6 < 1 ||  width_7 < 1 ||
       width_1 > 5 || width_2 > 5 || width_3 > 5 || width_4 > 5 ||  width_5 > 5 ||  width_6 > 5 ||  width_7 > 5   ) {
        Print("Width parameter is wrong");
        ChartIndicatorDelete(0, 0, shortname);
        return INIT_FAILED;
    }

    if(buttonXdis1 < 1 || buttonYdis1 < 1) {
        Print("Position parameter is wrong");
        ChartIndicatorDelete(0, 0, shortname);
        return INIT_FAILED;
    }

    CreateButton(buttonName, "Rect", CORNER_LEFT_UPPER, buttonXdis1, buttonYdis1,
                 60, 15, 7, clrWhite, colorOfBorder, clrGray);

    SetColorAndWidth(TimeFrameToMinutes(Period()));
    return(INIT_SUCCEEDED);
}


//------------------------------------------------------------------
// Tick受信時                                 (今回は処理なし)
//------------------------------------------------------------------
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
    return(rates_total);
}


//------------------------------------------------------------------
//  インディケーター除去時 or timeframe変更直前
//------------------------------------------------------------------
void OnDeinit(const int reason)
{
    ObjectDelete(0, buttonName);
    ChartRedraw();
}


//+------------------------------------------------------------------
//| ChartEvent function
//+------------------------------------------------------------------
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
    datetime time   =   0;
    double   price  = 0.0;
    int      window =   0;

    //--------------------------------------------
    // Rectボタンクリック時
    //--------------------------------------------
    if(id == CHARTEVENT_OBJECT_CLICK) {
        if(sparam == buttonName && step == 0) {
            ChartMouseScrollSet(false);       // 左ボタンによるチャートスクロールを無効にする
            ChartEventMouseMoveSet(true);     // CHARTEVENT_MOUSE_MOVEモードに入る

            bool moveIsON = false;
            ChartEventMouseMoveGet(moveIsON); // CHARTEVENT_MOUSE_MOVEの状態を取得
            if(moveIsON) step = 1;
        }
    }

    //--------------------------------------------
    // CHARTEVENT_MOUSE_MOVEモード中の処理
    //--------------------------------------------
    // [備考] このモード中、sparamの値は「マウスボタンの状態」
    if(id == CHARTEVENT_MOUSE_MOVE) {
        // Rectボタンクリック後、次のクリックでRectangleを作成
        if(step == 1 && sparam == "1") {
            if(!ChartXYToTimePrice(0, (int)lparam, (int)dparam, window, time, price))
                Print("ChartXYToTimePrice error. Erro code : ", GetLastError());

            RecName = prefix + IntegerToString(numOfRec);   // オブジェクト名作成
            // 既に同名オブジェクトがある場合への対応
            while(ObjectFind(0, RecName) >= 0) {
                numOfRec++;
                RecName = prefix + IntegerToString(numOfRec);
            }

            MakeRectangle(RecName, time, price, time, price, colorOfRecs, STYLE_SOLID, widthOfLine, false);
            ChartRedraw();
            step = 2;
        }

        // [if文を整理したくなったら、このメモを読む]
        //    以下の else if文 2つを step == 2でまとめ、1階層下に sparam == 1 と
        //    sparam == 0 のif文を作るとネストが深くなるので、このままにしておく

        // 左ボタンを押したままマウスを動かしたらRectangleのアンカーを変更
        else if(step == 2 && sparam == "1") {   
            if(!ChartXYToTimePrice(0, (int)lparam, (int)dparam, window, time, price))
                Print("ChartXYToTimePrice error. Erro code : ", GetLastError());

            // ObjectMoveの第3引数はpoint index(アンカーポイント番号)
            // Rectangleの場合、アンカーポイントは2つだけなので、point index は 0 と 1 だけ
            ObjectMove(0, RecName, 1, time, price);
            ChartRedraw();
        }

        // マウスの左ボタンを離したら全ての作業完了
        //  [備考] ボタンを離した判定をするのはCHARTEVENT_MOUSE_MOVEモード中なので、ここに記述
        else if(step == 2 && sparam == "0")  {
            ChartEventMouseMoveSet(false);
            ChartMouseScrollSet(true);
            ObjectSetInteger(0, buttonName, OBJPROP_STATE, false);
            ChartRedraw();
            step = 0;
        }
    }
}


//--------------------------------------------------------------------
// Rectangleを描画する関数
//--------------------------------------------------------------------
void MakeRectangle(string          objName,  // オブジェクト名
                   datetime        rcTime1,  // 描画座標1(時間)
                   double          rcPrice1, // 描画座標1(価格)
                   datetime        rcTime2,  // 描画座標2(時間)
                   double          rcPrice2, // 描画座標2(価格)
                   color           rcColor,  // 色
                   ENUM_LINE_STYLE rcStyle,  // 線種
                   int             rcWidth,  // 太さ
                   bool            rcFill)   // 塗りつぶし
{
    if(rcStyle != STYLE_SOLID && rcWidth > 1) {
        Alert("[MakeRectangle Function] Width is over 1. Style must be solid.");
        return;
    }

    ObjectCreate(    0, objName, OBJ_RECTANGLE, 0, rcTime1, rcPrice1, rcTime2, rcPrice2);
    ObjectSetInteger(0, objName, OBJPROP_COLOR, rcColor);
    ObjectSetInteger(0, objName, OBJPROP_STYLE, rcStyle);
    ObjectSetInteger(0, objName, OBJPROP_WIDTH, rcWidth);
    ObjectSetInteger(0, objName, OBJPROP_BACK, false);
    ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, true);
    ObjectSetInteger(0, objName, OBJPROP_SELECTED, true);
    ObjectSetInteger(0, objName, OBJPROP_HIDDEN, false);
    ObjectSetInteger(0, objName, OBJPROP_ZORDER, 0);
    ObjectSetString( 0, objName, OBJPROP_TOOLTIP, "\n");
    ObjectSetInteger(0, objName, OBJPROP_FILL, rcFill);
}


//--------------------------------------------------------------------
// MQL5の時間軸をint型の「分」に変換する関数
//--------------------------------------------------------------------
// [備考] MQL4と違い、PERIOD_H1以上は分数に対応していない
int TimeFrameToMinutes(ENUM_TIMEFRAMES timeframe)
{
    int minutes = 0;

    if(timeframe == PERIOD_CURRENT) timeframe = _Period;

    if(timeframe < PERIOD_H1) {
        minutes = (int)StringToInteger(StringSubstr(EnumToString(timeframe), 8));
        // [備考]
        // MT5の仕様が変わらないなら、if(timeframe < PERIOD_H1) の場合は
        //     minutes = timeframe;
        // でもOK。
    }
    else if(timeframe < PERIOD_D1) {
        minutes = 60 * (int)StringToInteger(StringSubstr(EnumToString(timeframe), 8));
    }
    else {
        switch(timeframe) {
            case PERIOD_D1:
                minutes = 1440;
                break;
            case PERIOD_W1:
                minutes = 10080;
                break;
            case PERIOD_MN1:
                minutes = 43200;  // 43200分は 30日に相当(MQL4のMN1を踏襲)
                break;
            default:
                minutes = -1;   // ERROR
        }
    }
    return minutes;
}


//--------------------------------------------------------------------
// ボタンを作成する関数
//--------------------------------------------------------------------
void CreateButton(string name,        // オブジェクト名
                  string caption,     // ボタン内に表示するテキスト
                  int corner,         // 基点(ボタンを配置する時に基準とする場所)
                  int xshift,         // 基点からの距離(横方向)
                  int yshift,         // 基点からの距離(縦方向)
                  int xsize,          // ボタンのサイズ(横)
                  int ysize,          // ボタンのサイズ(縦)
                  int font_Size,      // ボタン内に表示するテキストのサイズ
                  color textColor,    // ボタン内に表示するテキストの色
                  color bo_color,     // ボタンの縁の色
                  color bg_Color)     // ボタンの色
{
    ObjectCreate(    0, name, OBJ_BUTTON, 0, 0, 0);
    ObjectSetInteger(0, name, OBJPROP_CORNER, corner);
    ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xshift);
    ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yshift);
    ObjectSetInteger(0, name, OBJPROP_XSIZE, xsize);
    ObjectSetInteger(0, name, OBJPROP_YSIZE, ysize);

    ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, bo_color);
    ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bg_Color);

    ObjectSetString (0, name, OBJPROP_TEXT, caption);
    ObjectSetString (0, name, OBJPROP_FONT, "Arial");
    ObjectSetInteger(0, name, OBJPROP_FONTSIZE, font_Size);
    ObjectSetInteger(0, name, OBJPROP_COLOR, textColor);

    ObjectSetInteger(0, name, OBJPROP_STATE, false);
    ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);

    ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
    ObjectSetInteger(0, name, OBJPROP_SELECTED, false);
    ObjectSetInteger(0, name, OBJPROP_ZORDER, 0);
}


//--------------------------------------------------------------------
// チャートの時間軸に応じた色と太さをセットする関数
//--------------------------------------------------------------------
// [注意] この関数内ではグローバル変数を使用している
void SetColorAndWidth(int timeframe)
{
    if(timeframe <= 5) {               // 5分足
        colorOfRecs = color_1;
        widthOfLine = width_1;
    } else if(timeframe <= 15) {       // 15分足
        colorOfRecs = color_2;
        widthOfLine = width_2;
    } else if(timeframe <= 60) {       // 1時間足
        colorOfRecs = color_3;
        widthOfLine = width_3;
    } else if(timeframe <= 240) {      // 4時間足
        colorOfRecs = color_4;
        widthOfLine = width_4;
    } else if(timeframe <= 1440) {     // 日足
        colorOfRecs = color_5;
        widthOfLine = width_5;
    } else if(timeframe <= 10080) {    // 週足
        colorOfRecs = color_6;
        widthOfLine = width_6;
    } else {                           // 月足
        colorOfRecs = color_7;
        widthOfLine = width_7;
    }
}


//********************************************************************************
// 以下の3つの関数はMetaEditorのヘルプからそのまま引用(記述箇所は以下のとおり)
//   「標準的な定数、列挙と構造体」→「チャート定数」→「チャート操作の例」
//********************************************************************************

//--------------------------------------------------------------------
// CHART_EVENT_MOUSE_MOVEモードのON / OFF を切り替える関数
//--------------------------------------------------------------------
bool ChartEventMouseMoveSet(const bool value, const long chart_ID = 0)
{
    ResetLastError();
    if(!ChartSetInteger(chart_ID, CHART_EVENT_MOUSE_MOVE, 0, value)) {
        Print(__FUNCTION__ + ", Error Code = ", GetLastError());
        return(false);
    }
    return(true);
}


//--------------------------------------------------------------------
// マウスの左ボタンによるチャートスクロールを有効化/無効化する関数
//--------------------------------------------------------------------
bool ChartMouseScrollSet(const bool value, const long chart_ID = 0)
{
    ResetLastError();
    if(!ChartSetInteger(chart_ID, CHART_MOUSE_SCROLL, 0, value)) {
        Print(__FUNCTION__ + ", Error Code = ", GetLastError());
        return(false);
    }
    return(true);
}


//--------------------------------------------------------------------
// CHART_EVENT_MOUSE_MOVEモードがONかOFFか調べる関数
//--------------------------------------------------------------------
bool ChartEventMouseMoveGet(bool &result, const long chart_ID = 0)
{
    long value;
    ResetLastError();
    if(!ChartGetInteger(chart_ID, CHART_EVENT_MOUSE_MOVE, 0, value)) {
        Print(__FUNCTION__ + ", Error Code = ", GetLastError());
        return(false);
    }
    result = value;
    return(true);
}


MT5風味のコードで発生した問題

TradingView風味の操作を再現するよりも、MT5風味の操作を再現する方が少し時間がかかりました。というのも、インジをチャートに入れてから1回目の操作では正常に動くのですが、2回目の操作では正常に動作しなかったからです。

具体的に書きますと

(1) インジをチャートに挿入
(2) Rectボタン(四角形を作成するボタン)をクリック
(3) チャート上の任意の地点をクリック
(4) そのまま(マウスの左ボタンを押したまま)マウスを移動
(5) マウスの左ボタンを放す(四角形の描画が終了)

以上が1回目の操作です。
そして、2回目の操作に入ります。

(6) Rectボタン(四角形を作成するボタン)をクリック
(7) チャート上の任意の地点をクリック

(7)まで来た段階で「あれ?」となりました。四角形が描画されないのです。

調査したところ、2回目のRectボタンクリック時にマウスMOVEモードがONになっていませんでした(CHART_EVENT_MOUSE_MOVEがtrueになっていませんでした)。

しかし、この調査をするために以下の関数をOnChartEvent関数の末尾で実行したところ、2回目以降の操作でもマウスMOVEモードがONになっていることに気づきました。

//--------------------------------------------------------------------
// CHART_EVENT_MOUSE_MOVEがtrueかfalseか調べる関数
//--------------------------------------------------------------------
// この関数はMetaEditorのヘルプに記述されていたものをそのまま引用
bool ChartEventMouseMoveGet(bool &result, const long chart_ID = 0)
{
    long value;
    ResetLastError();
    if(!ChartGetInteger(chart_ID, CHART_EVENT_MOUSE_MOVE, 0, value)) {
        Print(__FUNCTION__ + ", Error Code = ", GetLastError());
        return(false);
    }
    result = value;
    return(true);
}


試しにこの関数を除去して動作実験をしたら、やはり2回目以降の操作でマウスMOVEモードがONになりませんでした。どうやら、ChartGetInteger関数でCHART_EVENT_MOUSE_MOVEの状態を確認すると、期待している動作をするようです。

まだ原因は解明できていませんが、マウスMOVEモードがONになっていないのに処理が進んでも意味は無いので、stepを1にする前にこの関数を実行することにしました。それが以下の箇所です。

bool moveIsON = false;
ChartEventMouseMoveGet(moveIsON);
if(moveIsON) step = 1;