今回の内容

今回は動画の用にフィボナッチをクリックするとボタンが生成され、生成されたボタンからフィボナッチのレベルを追加、削除するといった内容になっています。

全体コード

全体のコードです。コード内にも簡単な解説を挟んでいる部分はありますが、まずは自分で勉強したいという方や、インジケーターそのものの機能を改良したいという方は下記のコードをコピーしてお使い下さい。

#property copyright "Copyright 2022, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict
#property indicator_chart_window
//外部プロパティー設定
extern double F_LV0 = 0.382;
extern string F_Des0 = "38.2";
extern double F_LV1 = 0.5;
extern string F_Des1 = "50.0";
extern double F_LV2 = 0.618;
extern string F_Des2 = "61.8";
extern double F_LV3 = 1.5;
extern string F_Des3 = "150.0";
extern double F_LV4 = 1.618;
extern string F_Des4 = "161.8";
extern double F_LV5 = 2;
extern string F_Des5 = "200.0";

int F_COUNT = 6;
double F_Lv[6];
string F_Des[6];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(){//--- indicator buffers mapping
 ChartSetInteger(0,CHART_EVENT_OBJECT_DELETE,true);//オブジェクトDeleteの読み込み
 
 F_Lv[0]=F_LV0;F_Des[0]=F_Des0;
 F_Lv[1]=F_LV1;F_Des[1]=F_Des1;
 F_Lv[2]=F_LV2;F_Des[2]=F_Des2;
 F_Lv[3]=F_LV3;F_Des[3]=F_Des3;
 F_Lv[4]=F_LV4;F_Des[4]=F_Des4;
 F_Lv[5]=F_LV5;F_Des[5]=F_Des5;
 //---
 return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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 value of prev_calculated for next call
 return(rates_total);
}
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long &lparam,
                  const double &dparam,const string &sparam)
{
 if(ObjectGetInteger(0,sparam,OBJPROP_TYPE)==OBJ_FIBO){
  int tmp;
  string arr[2];                                        //配列の用意
  ArrayResize(arr,2);                                   //配列の領域を確保する
  StringSplit(sparam,StringGetCharacter(" ",0),arr);    //文字列を区切る (Fibo 11111→Fibo と11111を空白で分ける)
  tmp=(int)arr[1];                                      //区切った中での配列を使う Fibo 11111の場合(Fibo :0 11111:1)  
  
  double   FIBO_P1= ObjectGetDouble(0,sparam,OBJPROP_PRICE1,0);           //価格の読み込み
  datetime FIBO_T1=(datetime)ObjectGetInteger(0,sparam,OBJPROP_TIME1,0);  //時間の読み込み
  double   FIBO_P2= ObjectGetDouble(0,sparam,OBJPROP_PRICE2,1);           //価格の読み込み
  datetime FIBO_T2=(datetime)ObjectGetInteger(0,sparam,OBJPROP_TIME2,1);  //時間の読み込み
  int X,Y;
  
  ChartTimePriceToXY(0,0,FIBO_T2,FIBO_P2,X,Y);
 
  if(ObjectGetInteger(0,sparam,OBJPROP_SELECTED)==true){
   BUTTON("FIBOLvOpen_"+tmp,"Rset",X,Y+10);
  }
  else{ObjectDelete("FIBOLvOpen_"+tmp);   
   for(int i=1;i<=F_COUNT;i++){
    ObjectDelete("FIBOLv"+(string)i+"_"+tmp);
   }  
  }
 }
 
 if(id==CHARTEVENT_OBJECT_DELETE){ 
  if(StringSubstr(sparam,0,5)=="Fibo "){
   int tmp;
   string arr[2];                                        //配列の用意
   ArrayResize(arr,2);                                   //配列の領域を確保する
   StringSplit(sparam,StringGetCharacter(" ",0),arr);    //文字列を区切る (Fibo 11111→Fibo と11111を空白で分ける)
   tmp=(int)arr[1];                                      //区切った中での配列を使う Fibo 11111の場合(Fibo:0 11111:1)  
   
   ObjectDelete("FIBOLvOpen_"+tmp);
   for(int i=1;i<=F_COUNT;i++){
    ObjectDelete("FIBOLv"+(string)i+"_"+tmp);
   }  
  }
 }
 
 if(ObjectGetInteger(0,sparam,OBJPROP_TYPE)==OBJ_BUTTON){
  int tmp;
  string arr[2];                                        //配列の用意
  ArrayResize(arr,2);                                   //配列の領域を確保する
  StringSplit(sparam,StringGetCharacter("_",0),arr);    //文字列を区切る (FIBOLvOpen_11111→Fibo と11111を空白で分ける)
  tmp=(int)arr[1];                                      //区切った中での配列を使う FIBOLvOpen_11111の場合(FIBOLvOpen_:0 11111:1)  
    
  if(sparam=="FIBOLvOpen_"+tmp){
   if(ObjectGetInteger(0,sparam,OBJPROP_STATE)){
    int XX = ObjectGetInteger(0,sparam,OBJPROP_XDISTANCE);  //時間の読み込み
    int YY = ObjectGetInteger(0,sparam,OBJPROP_YDISTANCE);  //時間の読み込み
    
    for(int i=1;i<=F_COUNT;i++){
     BUTTON("FIBOLv"+(string)i+"_"+tmp,F_Des[i-1],XX,YY+(15*i));
    }
    ObjectSetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS,2);//元のレベルラインを一つにする
    ObjectSet("Fibo "+tmp,OBJPROP_FIRSTLEVEL+0,0.0);ObjectSetFiboDescription("Fibo "+tmp,0,"0"); 
    ObjectSet("Fibo "+tmp,OBJPROP_FIRSTLEVEL+1,1.0);ObjectSetFiboDescription("Fibo "+tmp,1,"100"); 
   }
   else{
    for(int i=1;i<=F_COUNT;i++){
     ObjectDelete("FIBOLv"+(string)i+"_"+tmp);
    }  
   }
  }
  for(int i=1;i<=F_COUNT;i++){
   if(sparam=="FIBOLv"+(string)i+"_"+tmp){
    int COUNT =ObjectGetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS);
    if(ObjectGetInteger(0,sparam,OBJPROP_STATE)){
     ObjectSetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS,COUNT+1);//元のレベルラインを一つにする
     ObjectSet("Fibo "+tmp,OBJPROP_FIRSTLEVEL+COUNT,F_Lv[i-1]);ObjectSetFiboDescription("Fibo "+tmp,COUNT,F_Des[i-1]); 
    }
    else {
     ObjectSetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS,COUNT-1);//元のレベルラインを一つにする
     int B_COUNT=0;
     for(int j=1;j<=F_COUNT;j++){
      if(i==j)continue;
      if(ObjectGetInteger(0,"FIBOLv"+(string)j+"_"+tmp,OBJPROP_STATE)){
       B_COUNT++;
       ObjectSetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS,2+B_COUNT);//元のレベルラインを一つにする
       ObjectSet("Fibo "+tmp,OBJPROP_FIRSTLEVEL+1+B_COUNT,F_Lv[j-1]);ObjectSetFiboDescription("Fibo "+tmp,1+B_COUNT,F_Des[j-1]); 
      }
     }
    }
   }
  }
 }
}

void BUTTON(string Button,string Text_button,int X,int Y){
 ObjectCreate    (0,Button,OBJ_BUTTON,0,0,0);
 ObjectSetString (0,Button,OBJPROP_TEXT,Text_button);         // ボタンのテキスト
 ObjectSetInteger(0,Button,OBJPROP_COLOR,clrYellow);          // テキスト色設定
 ObjectSetInteger(0,Button,OBJPROP_BGCOLOR,clrNONE);          // ボタン色
 ObjectSetInteger(0,Button,OBJPROP_BORDER_COLOR,C'40,40,40'); // ボタン枠色
 ObjectSetInteger(0,Button,OBJPROP_XDISTANCE,X);              // X座標
 ObjectSetInteger(0,Button,OBJPROP_YDISTANCE,Y);              // Y座標
 ObjectSetInteger(0,Button,OBJPROP_XSIZE,30);                 // ボタンサイズ幅
 ObjectSetInteger(0,Button,OBJPROP_YSIZE,15);                 // ボタンサイズ高さ
 ObjectSetString (0,Button,OBJPROP_FONT,"Microsoft YaHei");   // フォント
 ObjectSetInteger(0,Button,OBJPROP_FONTSIZE,7);               // フォントサイズ
 ObjectSetInteger(0,Button,OBJPROP_SELECTABLE,false);         // オブジェクトの選択可否設定
 ObjectSetInteger(0,Button,OBJPROP_SELECTED,false);           // オブジェクトの選択状態
 ObjectSetInteger(0,Button,OBJPROP_HIDDEN,true);              // オブジェクトリスト表示設定
 ObjectSetInteger(0,Button,OBJPROP_ZORDER,0);                 // オブジェクトのチャートクリックイベント優先順位
 ObjectSetInteger(0,Button,OBJPROP_STATE,false);              // ボタン押下状態
}

ここまでがコードになります。下項より各コードの解説等を機能別に説明していこうと思います。

コード解説1:外部プロパティーと初期設定

まずはグローバル関数とOnInit関数内の解説ですが、本記事のコードではこの項目においての重要度はあまり高くありませんので、MQLをある程度理解している方に関してはスキップしても問題ありません。

//外部プロパティー
extern double F_LV0 = 0.382;
extern string F_Des0 = "38.2";
extern double F_LV1 = 0.5;
extern string F_Des1 = "50.0";
extern double F_LV2 = 0.618;
extern string F_Des2 = "61.8";
extern double F_LV3 = 1.5;
extern string F_Des3 = "150.0";
extern double F_LV4 = 1.618;
extern string F_Des4 = "161.8";
extern double F_LV5 = 2;
extern string F_Des5 = "200.0";

int F_COUNT = 6;
double F_Lv[6];
string F_Des[6];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(){//--- indicator buffers mapping
 ChartSetInteger(0,CHART_EVENT_OBJECT_DELETE,true);//オブジェクトDeleteの読み込み
 
 F_Lv[0]=F_LV0;F_Des[0]=F_Des0;
 F_Lv[1]=F_LV1;F_Des[1]=F_Des1;
 F_Lv[2]=F_LV2;F_Des[2]=F_Des2;
 F_Lv[3]=F_LV3;F_Des[3]=F_Des3;
 F_Lv[4]=F_LV4;F_Des[4]=F_Des4;
 F_Lv[5]=F_LV5;F_Des[5]=F_Des5;
 //---
 return(INIT_SUCCEEDED);
}

外部プロパティー

外部プロパティーで設定した項目では、上記画像の用にインジケーターのパラメーターの入力欄に出力する項目です。

//外部プロパティー設定
extern double F_LV0 = 0.382;
extern string F_Des0 = "38.2";
//省
//略
extern double F_LV5 = 2;
extern string F_Des5 = "200.0";

実際に出力している部分のコードはこの部分で、本コードでは変数(double,string)の前にexternを用いていますが、inputによる出力も可能です。

出力する内容に関しては作成者の自由ですが、今回であればフィボナッチレベルの値を自由に変更できるようにしたい為、レベルラインの値とレベルの説明を出力できるようにしています。

グローバル変数と初期化関数内での処理

正直少しくどい書き方をしているのが今回の内容、ここだけの解説だとなんでこんなことしているんですか?って疑問を抱くと思いますが、

その理由としては、静的配列を用意してグローバル関数の定数の値を配列で扱いたい。という理由です。

MQLを始めたばかりの人は頭の中に「???」とか「何言ってるんですか?」ってなると思いますが、後々詳しく説明しますので、今は深く考えずにそーゆうものぐらいの気持ちでいてください。

ではグローバル変数に関してですが

int F_COUNT = 6;
double F_Lv[6];
string F_Des[6];

この項目です、今回は6を使用していますが、ここの数字は特別な理由はありません。強いていうのであれば、外部プロパティーで設定できる値が6セットなので6を使用しています。

6セットに合わせて、下項で使用するF_COUNT静的配列のF_LV[],F_Des[]の数字を合わせています。

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(){//--- indicator buffers mapping
 ChartSetInteger(0,CHART_EVENT_OBJECT_DELETE,true);//オブジェクトDeleteの読み込み
 
 F_Lv[0]=F_LV0;F_Des[0]=F_Des0;
 //省
 //略
 F_Lv[5]=F_LV5;F_Des[5]=F_Des5;
 //---
 return(INIT_SUCCEEDED);
}

OnInit関数では、グローバルで用意した静的配列に外部プロパティーで設定した値を代入してあげます。

コード解説2:チャートイベント

内容としてはここからが本番。難易度が一気にあがります。できるだけ噛み砕いて解説していきますが、分からなかった場合は一旦休憩を取ったり、次の項目にいくなどして下さい。

フィボナッチの情報を読み取りボタンを生成する処理

 if(ObjectGetInteger(0,sparam,OBJPROP_TYPE)==OBJ_FIBO){
  int tmp;
  string arr[2];                                        //配列の用意
  ArrayResize(arr,2);                                   //配列の領域を確保する
  StringSplit(sparam,StringGetCharacter(" ",0),arr);    //文字列を区切る (Fibo 11111→Fiboと11111を空白で分ける)
  tmp=(int)arr[1];                                      //区切った中での配列を使う Fibo 11111の場合(Fibo:0 11111:1)  
  
  double   FIBO_P1= ObjectGetDouble(0,sparam,OBJPROP_PRICE1,0);           //価格の読み込み
  datetime FIBO_T1=(datetime)ObjectGetInteger(0,sparam,OBJPROP_TIME1,0);  //時間の読み込み
  double   FIBO_P2= ObjectGetDouble(0,sparam,OBJPROP_PRICE2,1);           //価格の読み込み
  datetime FIBO_T2=(datetime)ObjectGetInteger(0,sparam,OBJPROP_TIME2,1);  //時間の読み込み
  int X,Y;
  ChartTimePriceToXY(0,0,FIBO_T2,FIBO_P2,X,Y);
 
  if(ObjectGetInteger(0,sparam,OBJPROP_SELECTED)==true){
   BUTTON("FIBOLvOpen_"+tmp,"Rset",X,Y+10);
  }
  else{ObjectDelete("FIBOLvOpen_"+tmp);   
   for(int i=1;i<=F_COUNT;i++){
    ObjectDelete("FIBOLv"+(string)i+"_"+tmp);
   }  
  }
 }

まずこの部分の解説ですが、大きな工程としては、フィボナッチを検知、フィボナッチの状態によりボタンを生成、削除という流れになります。

少し細かいことを言えばフィボナッチを検知する際にフィボナッチの情報を抜き出します。抜き出す項目は、オブジェクトの名前の一部、フィボナッチの価格、時間の3つです。

オブジェクトの名前の一部に関しては下記の画像をまずは見てください。

表示中のライン等のリスト内に「Fibo 62567」と「FIBOLvOpen_62567」とあるのが分かると思います。今回は画像内でいう「62567」の部分を使用しています。

その為、フィボナッチを検知「Fibo 〇〇」から○○を抜き出し、生成するボタン「FIBOLvOpen_」に○○を足し、「FIBOLvOpen_○○」とするといったイメージです。

○○を抜き出すコードは、

int tmp;
string arr[2];                                        //配列の用意
ArrayResize(arr,2);                                   //配列の領域を確保する
StringSplit(sparam,StringGetCharacter(" ",0),arr);    //文字列を区切る (Fibo 11111→Fiboと11111を空白で分ける)
tmp=(int)arr[1];                                      //区切った中での配列を使う Fibo  11111の場合(Fibo:0 11111:1)  

この部分では、定数 tmp、静的配列 arr[] を用意、文字列を区切る(今回は空白で区切る)。

区切った文字列はarr[0] ( Fibo )とarr[1] ( ○○ )に分かれているので、定数 tmpに〇〇を代入といった流れです。

次に価格と時間の抜き出しですが、

double   FIBO_P1= ObjectGetDouble(0,sparam,OBJPROP_PRICE1,0);           //価格の読み込み
datetime FIBO_T1=(datetime)ObjectGetInteger(0,sparam,OBJPROP_TIME1,0);  //時間の読み込み
double   FIBO_P2= ObjectGetDouble(0,sparam,OBJPROP_PRICE2,1);           //価格の読み込み
datetime FIBO_T2=(datetime)ObjectGetInteger(0,sparam,OBJPROP_TIME2,1);  //時間の読み込み

ObjectGetDouble(0,オブジェクト名,OBJPROP_PRICE1,0); (datetime)ObjectGetInteger(0, オブジェクト名 ,OBJPROP_TIME1,0);の関数を使えば簡単に抜き出せます。

余談ですが、今回は価格、時間ともに2つずつですが、オブジェクトによっては1つしかないものや3つあるものもあります。フィボナッチリトレースメントは2つずつものオブジェクトなので2つ今回抜きだしています。(毎回2つ抜く必要もないです)

オブジェクトの時間や価格の数が分からない方は、調べる方法の一つとして、オブジェクトのパラメーターを見て確認する方法もあります。またボタンやラベルなど一部のオブジェクトでは価格、時間ではなく、X-距離、Y-距離などの表示があります。オブジェクトの表示位置は価格、時間で決まるものと、X座標、Y座標で決まるものの2種類があります。
int X,Y;
ChartTimePriceToXY(0,0,FIBO_T2,FIBO_P2,X,Y);

フィボナッチリトレースメントから時間と価格を抜き出したところですが、ボタンはX座標、Y座標で表示位置が決まっているタイプの為、時間と価格をX座標、Y座標に直す必要があるので、変換後の値を入れる定数X.Yを用意して変換します。

上記では二つの時間、価格を抜き出してますが、実際に変換しているのは一つなのが今回のコードです。もし二つとも変換する場合は定数X1、Y1など用いて変換してください。

本コードの処理ではフィボの位置によってボタンの位置が邪魔になる場合があります。この点が気になる場合は時間、価格の抜き出しを2点ずつとり、Y<Y1などの処理を行えば解決しますが、今回は説明を省略します。

ここまででフィボナッチを検知して名前の一部、時間、価格の抜き出しは終わりです。次は抜き出した情報からボタンを生成するのですが、今回はボタンを生成するタイミングを。フィボナッチリトレースメントの「オブジェクトが選択されている状態」にするため

選択されている場合「ボタンを生成」、選択されていない場合「ボタンを削除」という流れになります。その流れのコードは

if(ObjectGetInteger(0,sparam,OBJPROP_SELECTED)==true){//オブジェクト(フィボ)が選択されている時
 ボタンの生成
}else{
 ボタンの削除
}  

という基本的なif-else文で書けるので後はボタンを生成するコード(今回は自作関数使用)と削除文を書けば大丈夫です。

if(ObjectGetInteger(0,sparam,OBJPROP_SELECTED)==true){
 BUTTON("FIBOLvOpen_"+tmp,"Rset",X,Y+10);
}else{
 ObjectDelete("FIBOLvOpen_"+tmp);   
 for(int i=1;i<=F_COUNT;i++){
  ObjectDelete("FIBOLv"+(string)i+"_"+tmp);
 }
}  

※下項の「ボタンを押してフィボレベルを変更する処理」の方で生成するボタンをここでもDeleteする為for文~ObjectDelete(“FIBOLv”+(string)i+”_”+tmp);が現時点であります。

フィボナッチ削除時の処理

フィボナッチ削除時の処理ですが、コード内容的には、上項の「フィボナッチの情報を読み取りボタンを生成する処理」が理解できれば難しいことはありません。

先に削除時の処理のコードを見てもらいますが

if(id==CHARTEVENT_OBJECT_DELETE){ 
 if(StringSubstr(sparam,0,5)=="Fibo "){
  int tmp;
  string arr[2];                                        //配列の用意
  ArrayResize(arr,2);                                   //配列の領域を確保する
  StringSplit(sparam,StringGetCharacter(" ",0),arr);    //文字列を区切る (Fibo 11111→Fibo と11111を空白で分ける)
  tmp=(int)arr[1];                                      //区切った中での配列を使う Fibo 11111の場合(Fibo:0 11111:1)  
   
  ObjectDelete("FIBOLvOpen_"+tmp);
  for(int i=1;i<=F_COUNT;i++){
   ObjectDelete("FIBOLv"+(string)i+"_"+tmp);
  }  
 }
}

価格や時間を取得したり、if-else文が無いだけで、使用しているコードなどは大きく変わりがありません。

変更点があるとすると

if(id==CHARTEVENT_OBJECT_DELETE){ 
 if(StringSubstr(sparam,0,5)=="Fibo "){

この部分ですが、id==CHARTEVENT_OBJECT_DELETEはMT4内のイベント動作がオブジェクト削除であった場合を意味しており、StringSubstr(sparam,0,5)==”Fibo “ではオブジェクト名の先頭5文字が”Fibo “(この場合空白も1文字に含まれる)だった時となる為

日本語でいうのであれば「もしチャートイベントIDがオブジェクト削除で、そのオブジェクトが(Fibo )の名から始まるオブジェであれば」ぐらいのニュアンスです。

後はまた名前の一部を用いて、ボタンを削除するという動作になるのですが、

ボタンを削除する際、今回if-else文がないのは、ボタンが生成されているタイミングは、フィボが生成されている状態かつ選択されている状態なので、フィボが削除される場合、ボタンも不要である為です。

ボタンを押してフィボレベルを変更する処理

このインジの機能の大半を占める部分になります。今まではある意味この部分に至るまでの準備段階です。

まずはこの処理の部分とコードです。

if(ObjectGetInteger(0,sparam,OBJPROP_TYPE)==OBJ_BUTTON){
  int tmp;
  string arr[2];                                        //配列の用意
  ArrayResize(arr,2);                                   //配列の領域を確保する
  StringSplit(sparam,StringGetCharacter("_",0),arr);    //文字列を区切る (FIBOLvOpen_11111→FIBOLvOpen_と11111を空白で分ける)
  tmp=(int)arr[1];                                      //区切った中での配列を使う FIBOLvOpen_11111の場合(FIBOLvOpen_:0 11111:1)  
    
  if(sparam=="FIBOLvOpen_"+tmp){
   if(ObjectGetInteger(0,sparam,OBJPROP_STATE)){
    int XX = ObjectGetInteger(0,sparam,OBJPROP_XDISTANCE);  //時間の読み込み
    int YY = ObjectGetInteger(0,sparam,OBJPROP_YDISTANCE);  //時間の読み込み
    
    for(int i=1;i<=F_COUNT;i++){
     BUTTON("FIBOLv"+(string)i+"_"+tmp,F_Des[i-1],XX,YY+(15*i));
    }
    ObjectSetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS,2);//元のレベルラインを一つにする
    ObjectSet("Fibo "+tmp,OBJPROP_FIRSTLEVEL+0,0.0);ObjectSetFiboDescription("Fibo "+tmp,0,"0"); 
    ObjectSet("Fibo "+tmp,OBJPROP_FIRSTLEVEL+1,1.0);ObjectSetFiboDescription("Fibo "+tmp,1,"100"); 
   }
   else{
    for(int i=1;i<=F_COUNT;i++){
     ObjectDelete("FIBOLv"+(string)i+"_"+tmp);
    }  
   }
  }
  for(int i=1;i<=F_COUNT;i++){
   if(sparam=="FIBOLv"+(string)i+"_"+tmp){
    int COUNT =ObjectGetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS);
    if(ObjectGetInteger(0,sparam,OBJPROP_STATE)){
     ObjectSetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS,COUNT+1);//元のレベルラインを一つにする
     ObjectSet("Fibo "+tmp,OBJPROP_FIRSTLEVEL+COUNT,F_Lv[i-1]);ObjectSetFiboDescription("Fibo "+tmp,COUNT,F_Des[i-1]); 
    }
    else {
     ObjectSetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS,COUNT-1);//元のレベルラインを一つにする
     int B_COUNT=0;
     for(int j=1;j<=F_COUNT;j++){
      if(i==j)continue;
      if(ObjectGetInteger(0,"FIBOLv"+(string)j+"_"+tmp,OBJPROP_STATE)){
       B_COUNT++;
       ObjectSetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS,2+B_COUNT);//元のレベルラインを一つにする
       ObjectSet("Fibo "+tmp,OBJPROP_FIRSTLEVEL+1+B_COUNT,F_Lv[j-1]);ObjectSetFiboDescription("Fibo "+tmp,1+B_COUNT,F_Des[j-1]); 
      }
     }
    }
   }
  }
 }

全体のコードが多くなっていますが、ここでもまずは大きな流れを解説すると、ボタンを押してボタンのオブジェクト名の一部を取得、押したボタンによって処理を変える

コードは長いですが、大まかな動きはこんな感じでボタンを押した際の流れとしては、A:ボタンを押してボタンを生成、削除、B:ボタンを押してフィボレベルの変更の2点です。

では実際に各種コード説明に入ります。まずはボタンを押してボタンのオブジェクトの一部を取得する部分に関して

if(ObjectGetInteger(0,sparam,OBJPROP_TYPE)==OBJ_BUTTON){
  int tmp;
  string arr[2];                                        //配列の用意
  ArrayResize(arr,2);                                   //配列の領域を確保する
  StringSplit(sparam,StringGetCharacter("_",0),arr);    //文字列を区切る (FIBOLvOpen_11111→FIBOLvOpen_と11111を空白で分ける)
  tmp=(int)arr[1];                                      //区切った中での配列を使う FIBOLvOpen_11111の場合(FIBOLvOpen_:0 11111:1) 

今回は空白ではなくアンダーバーで区切っているという点以外は上項でやっていることと同じです。

if(ObjectGetInteger(0,sparam,OBJPROP_TYPE)==OBJ_BUTTON){

またこのif文に関しては、下記で説明する部分も全てこの一文の{}内であるという点を忘れずにいてください。

その点を踏まえて、押したボタンによって処理を変えるという点の「ボタンを押してボタンを生成、削除」の部分に入ります。

if(sparam=="FIBOLvOpen_"+tmp){
 if(ObjectGetInteger(0,sparam,OBJPROP_STATE)){
  int XX = ObjectGetInteger(0,sparam,OBJPROP_XDISTANCE);  //時間の読み込み
  int YY = ObjectGetInteger(0,sparam,OBJPROP_YDISTANCE);  //時間の読み込み
    
  for(int i=1;i<=F_COUNT;i++){
   BUTTON("FIBOLv"+(string)i+"_"+tmp,F_Des[i-1],XX,YY+(15*i));
  }
  ObjectSetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS,2);//元のレベルラインを一つにする
  ObjectSet("Fibo "+tmp,OBJPROP_FIRSTLEVEL+0,0.0);ObjectSetFiboDescription("Fibo "+tmp,0,"0"); 
  ObjectSet("Fibo "+tmp,OBJPROP_FIRSTLEVEL+1,1.0);ObjectSetFiboDescription("Fibo "+tmp,1,"100"); 
 }
 else{
  for(int i=1;i<=F_COUNT;i++){
   ObjectDelete("FIBOLv"+(string)i+"_"+tmp);
  }  
 }
}

コードとしてはこの部分になります。一つずつ解説していきます

if(sparam=="FIBOLvOpen_"+tmp){
 if(ObjectGetInteger(0,sparam,OBJPROP_STATE)){
  int XX = ObjectGetInteger(0,sparam,OBJPROP_XDISTANCE);  //時間の読み込み
  int YY = ObjectGetInteger(0,sparam,OBJPROP_YDISTANCE);  //時間の読み込み

最初に説明しますが、tmpはボタンを押した際に取得している一部の部分です。

ここでは”FIBOLvOpen_”はフィボが選択されている際に生成されているボタンなので、そのボタンが押されている時に、ボタンのXY座標を取得しているというコードです。

for(int i=1;i<=F_COUNT;i++){
   BUTTON("FIBOLv"+(string)i+"_"+tmp,F_Des[i-1],XX,YY+(15*i));
  }
  ObjectSetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS,2);//元のレベルラインを一つにする
  ObjectSet("Fibo "+tmp,OBJPROP_FIRSTLEVEL+0,0.0);ObjectSetFiboDescription("Fibo "+tmp,0,"0"); 
  ObjectSet("Fibo "+tmp,OBJPROP_FIRSTLEVEL+1,1.0);ObjectSetFiboDescription("Fibo "+tmp,1,"100"); 
 }
 else{
  for(int i=1;i<=F_COUNT;i++){
   ObjectDelete("FIBOLv"+(string)i+"_"+tmp);
  }  
 }

動画も合わせて説明しますが、”FIBOLvOpen_”+tmpボタン(Rset)を押した際に新しいボタン”FIBOLv”+(string)i+”_”+tmp(各数字)が生成、その際フィボレベルを2つに変更(0と100)ボタンの状態を押していない状態に戻せば”FIBOLv”+(string)i+”_”+tmp(各数字)を削除するという流れです。

この部分においてはある意味初心者殺しとなっていますが、for文を無くせば実はそんなに難しくありません。初期化関数内で行った処理ですが、この点においてコード短縮を行う為であり、for文がイマイチわからないという場合、ボタンの下にボタンを並べるコードを一つずつ書いても同じという考え方でも大丈夫です。削除の場合も同じです。

初期化関数内での処理

 F_Lv[0]=F_LV0;F_Des[0]=F_Des0;
 F_Lv[1]=F_LV1;F_Des[1]=F_Des1;
 F_Lv[2]=F_LV2;F_Des[2]=F_Des2;
 F_Lv[3]=F_LV3;F_Des[3]=F_Des3;
 F_Lv[4]=F_LV4;F_Des[4]=F_Des4;
 F_Lv[5]=F_LV5;F_Des[5]=F_Des5;

for文を使わない場合

BUTTON("FIBOLv"+1+"_"+tmp,F_Des[i-1],XX,YY+(15*1));
BUTTON("FIBOLv"+2+"_"+tmp,F_Des[i-1],XX,YY+(15*2));
BUTTON("FIBOLv"+3+"_"+tmp,F_Des[i-1],XX,YY+(15*3));
BUTTON("FIBOLv"+4+"_"+tmp,F_Des[i-1],XX,YY+(15*4));
BUTTON("FIBOLv"+5+"_"+tmp,F_Des[i-1],XX,YY+(15*5));
BUTTON("FIBOLv"+6+"_"+tmp,F_Des[i-1],XX,YY+(15*6));

※YY+(15*i)の15という数字はボタンの高さで、今回は自作関数内で数字を固定していますが、定数などで設定することも可能です。

次にボタンを押してフィボナッチレベルの変更を行う処理ですが

for(int i=1;i<=F_COUNT;i++){
   if(sparam=="FIBOLv"+(string)i+"_"+tmp){
    int COUNT =ObjectGetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS);
    if(ObjectGetInteger(0,sparam,OBJPROP_STATE)){
     ObjectSetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS,COUNT+1);//元のレベルラインを一つにする
     ObjectSet("Fibo "+tmp,OBJPROP_FIRSTLEVEL+COUNT,F_Lv[i-1]);ObjectSetFiboDescription("Fibo "+tmp,COUNT,F_Des[i-1]); 
    }
    else {
     ObjectSetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS,COUNT-1);//元のレベルラインを一つにする
     int B_COUNT=0;
     for(int j=1;j<=F_COUNT;j++){
      if(i==j)continue;
      if(ObjectGetInteger(0,"FIBOLv"+(string)j+"_"+tmp,OBJPROP_STATE)){
       B_COUNT++;
       ObjectSetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS,2+B_COUNT);//元のレベルラインを一つにする
       ObjectSet("Fibo "+tmp,OBJPROP_FIRSTLEVEL+1+B_COUNT,F_Lv[j-1]);ObjectSetFiboDescription("Fibo "+tmp,1+B_COUNT,F_Des[j-1]); 
      }
     }
    }
   }
  }

こちらは文面での説明より簡易図解の方が理解しやすいと思いますので、下記の画像を参照ください。

ではまず、レベルライン用ボタンと押す前の保有の部分のコードです

for(int i=1;i<=F_COUNT;i++){
   if(sparam=="FIBOLv"+(string)i+"_"+tmp){
    int COUNT =ObjectGetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS);

この点は難易度は高くありませんが、今回はレベルライン用のボタンが複数ある為for文を先に処理してコードを短縮しています。

次にボタンの状態が押されている時の処理です。

    if(ObjectGetInteger(0,sparam,OBJPROP_STATE)){
     ObjectSetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS,COUNT+1);//元のレベルラインを一つにする
     ObjectSet("Fibo "+tmp,OBJPROP_FIRSTLEVEL+COUNT,F_Lv[i-1]);ObjectSetFiboDescription("Fibo "+tmp,COUNT,F_Des[i-1]); 
    }

フィボのレベルラインの設定数を増やし、増やしたレベルラインの値と説明を足すとコード内容は凄くシンプルなものですので難しいことはないと思います。しかし、配列やfor文による定数iの存在で頭が混乱する人も少なくはないと思います。実際混乱したという方は、定数iがどの数字を持つかを冷静に考えましょう。

今回のコードではレベルライン用のボタン名を「”FIBOLv”+(string)i+”_”+tmp」で生成しています。生成数は6つですので「”FIBOLv1“_”+tmp」~「”FIBOLv6“_”+tmp」のいずれかのボタンが押されていることになりますよね?例えば「”FIBOLv3“_”+tmp」のボタンを押した場合はi=3となるので、F_Lv[i-1]、F_Des[i-1]はそれぞれF_Lv[2]、F_Des[2]を使用しているということになります。

本記事ボリュームが多い為、F_Lv[2]、F_Des[2]が既に何の値を示しているかなどお忘れの方もいるでしょう。この値はグローバル関数、初期化関数で決めた値になります。

最後にボタンが押されていない場合、押されている状態が解除された場合

else {
     ObjectSetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS,COUNT-1);//元のレベルラインを一つにする
     int B_COUNT=0;
     for(int j=1;j<=F_COUNT;j++){
      if(i==j)continue;
      if(ObjectGetInteger(0,"FIBOLv"+(string)j+"_"+tmp,OBJPROP_STATE)){
       B_COUNT++;
       ObjectSetInteger(0,"Fibo "+tmp,OBJPROP_FIBOLEVELS,2+B_COUNT);//元のレベルラインを一つにする
       ObjectSet("Fibo "+tmp,OBJPROP_FIRSTLEVEL+1+B_COUNT,F_Lv[j-1]);ObjectSetFiboDescription("Fibo "+tmp,1+B_COUNT,F_Des[j-1]); 
      }
     }

B_COUNTやfor文内の定数がjになっていたり、if(i==j)continue、と今までとは違う雰囲気な部分はありますが、先ほどの例えを用いて説明していきます。

「”FIBOLv3“_”+tmp」のボタンの状態が押されていない状態になった際、i=3となります。B_COUNTは「”FIBOLv3“_”+tmp」以外のボタンが押されている場合にカウントを取る定数として設定しています。定数jを用いたfor文では「”FIBOLv3“_”+tmp」以外のボタンが押されている場合レベルライン等を設定していくので、if(i==j)continueでj=3の時の処理をスキップしています。

こちらも処理イメージは図解にて

コード解説3:自作関数

 


void BUTTON(string Button,string Text_button,int X,int Y){
 ObjectCreate    (0,Button,OBJ_BUTTON,0,0,0);
 ObjectSetString (0,Button,OBJPROP_TEXT,Text_button);         // ボタンのテキスト
 ObjectSetInteger(0,Button,OBJPROP_COLOR,clrYellow);          // テキスト色設定
 ObjectSetInteger(0,Button,OBJPROP_BGCOLOR,clrNONE);          // ボタン色
 ObjectSetInteger(0,Button,OBJPROP_BORDER_COLOR,C'40,40,40'); // ボタン枠色
 ObjectSetInteger(0,Button,OBJPROP_XDISTANCE,X);              // X座標
 ObjectSetInteger(0,Button,OBJPROP_YDISTANCE,Y);              // Y座標
 ObjectSetInteger(0,Button,OBJPROP_XSIZE,30);                 // ボタンサイズ幅
 ObjectSetInteger(0,Button,OBJPROP_YSIZE,15);                 // ボタンサイズ高さ
 ObjectSetString (0,Button,OBJPROP_FONT,"Microsoft YaHei");   // フォント
 ObjectSetInteger(0,Button,OBJPROP_FONTSIZE,7);            // フォントサイズ
 ObjectSetInteger(0,Button,OBJPROP_SELECTABLE,false);         // オブジェクトの選択可否設定
 ObjectSetInteger(0,Button,OBJPROP_SELECTED,false);           // オブジェクトの選択状態
 ObjectSetInteger(0,Button,OBJPROP_HIDDEN,true);              // オブジェクトリスト表示設定
 ObjectSetInteger(0,Button,OBJPROP_ZORDER,0);                 // オブジェクトのチャートクリックイベント優先順位
 ObjectSetInteger(0,Button,OBJPROP_STATE,false);              // ボタン押下状態
}

 

まとめ