見出し画像

パラメータをチャート上で表示する部分を実装 ハイロー用サインツールを作ってみた⑦

前回は、RSIを条件式に組み込むところを作成してきました。

このパラメータ値って、パラメータ画面をいちいち表示して変更して閉じるって面倒じゃないですか?私、面倒が苦手で、検証はなるべく簡単にしたいんですよね。だから、今回はパラメータ変更をチャート上でポチポチできるように変更してみました。

前回までの内容はこちらから。


パラメータ値を簡単に変更するための動機

パラメータ値の変更の基本は、インジケータのコード内にinputとかで入力項目作成して、チャート上では右クリック → インディケータリスト → インジケータ選択 → プロパティ → インプット変更 → 閉じる までして、ようやく変更されます。

すっごく面倒くさくないですか?

私これめっちゃ嫌いなんです。EAの計算待ちも嫌いです。なんでもっとサクッと表示してくれないのかなとか、細かいパラメータ変更を即時反映できないのかなっていつも思ってます。

パラメータの値を、20じゃだめだから25にしようかなって思った時、毎回上のお作業をするのって耐えられないです。ボタンポチポチしたら結果が分かるようにしたいですよね。

ってことで、パラメータ値をチャート上でポチポチ変更できるようにしました。


チャート上でパラメータを変更させる動き

完成図はこんな感じです。

この部分が本日のテーマ、チャート上で変更可能なパラメータです。

項目が増えた場合は、別のフローティングウィンドウで管理できるように変更します。とりあえずはチャートに直接置いてます。

矢印をクリックすると数字が増えたり、減ったりします。

パラメータが変更されるとチャートが再描画されるので、サイン位置などは即時に反映、勝率計算も再計算されます。


作成に必要なもの

ボタンのオブジェクトと変更された数字を表示するテキストボックス、さらにボタンが押された場合にイベントとして再計算させる仕組みとかが必要です。

このあたりを作るのが面倒な方は、単純にinput使ってるんだと思います。コード作成が面倒でも実用楽な方が良くないですか。

さて、コード内容を書いていきますか。


コード内容

// 設定変更用ボタン関数
void setButton(string name, color clr, int corner, int xdist, int ydist, int xsize, int ysize, string txt, int FontSize)
{
   ObjectCreate(0,name,OBJ_BUTTON,0,0,0);
   ObjectSetInteger(0,name,OBJPROP_XDISTANCE,xdist);
   ObjectSetInteger(0,name,OBJPROP_YDISTANCE,ydist);
   ObjectSetInteger(0,name,OBJPROP_XSIZE,xsize);
   ObjectSetInteger(0,name,OBJPROP_YSIZE,ysize);
   ObjectSetInteger(0,name,OBJPROP_BGCOLOR,clr);
   ObjectSetInteger(0,name,OBJPROP_CORNER,corner);
   ObjectSetString(0,name,OBJPROP_TEXT,txt);
   ObjectSetInteger(0,name,OBJPROP_FONTSIZE,FontSize);
   ObjectSetInteger(0,name,OBJPROP_BACK,false);
}

// 設定変更用ボタン関数
void setEditBox(string name, color clr, int corner, int xdist, int ydist, int xsize, int ysize, string txt, int FontSize)
{
   ObjectCreate(0,name,OBJ_EDIT,0,0,0);
   ObjectSetInteger(0,name,OBJPROP_XDISTANCE,xdist);
   ObjectSetInteger(0,name,OBJPROP_YDISTANCE,ydist);
   ObjectSetInteger(0,name,OBJPROP_XSIZE,xsize);
   ObjectSetInteger(0,name,OBJPROP_YSIZE,ysize);
   ObjectSetInteger(0,name,OBJPROP_COLOR,clrBlack);
   ObjectSetInteger(0,name,OBJPROP_BGCOLOR,clr);
   ObjectSetInteger(0,name,OBJPROP_CORNER,corner);
   ObjectSetString(0,name,OBJPROP_TEXT,txt);
   ObjectSetInteger(0,name,OBJPROP_FONTSIZE,FontSize);
   ObjectSetInteger(0,name,OBJPROP_ALIGN,ALIGN_CENTER);
   ObjectSetInteger(0,name,OBJPROP_READONLY,true);
   ObjectSetInteger(0,name,OBJPROP_BACK,false);
}

まずは、これまでと同じように、オブジェクトを作成していきます。OBJ_BUTTONは数字を増やしたり減らしたりするボタン。OBJ_EDITは増えたり減ったりした数字を表示してくれるテキストボックスです。

OBJ_EDITは手書き入力も可能なのですが、OBJPROP_READONLYで入力不可にしています。キーボード入力も面倒なので。


void setParam(string name, int ydist, int defval)
{
   setLabel("BoTest_Label_"+name, clrWhite, CORNER_LEFT_LOWER, 10, ydist, name, 12);
   setButton("BoTest_Button_"+name+"_low",clrLightGray,CORNER_LEFT_LOWER,70,ydist,15,20,"<",12);
   setEditBox("BoTest_Edit_"+name,clrWhite,CORNER_LEFT_LOWER,85,ydist,55,20,defval,12);
   setButton("BoTest_Button_"+name+"_up",clrLightGray,CORNER_LEFT_LOWER,140,ydist,15,20,">",12);
}

以前に作成したラベル作成の関数とともに、パラメータ作成の関数の形を作っちゃいます。

上のコードでこれ一つ分の意味です。こうすることで、同じ形で内容の違うものを複製可能になります。


// 初期化処理
int OnInit()
{
   // 0.1秒ごとにタイマーイベントを動かす
   EventSetMillisecondTimer(100);
   
   // オブジェクトの初期化処理
   setLabel("BoTest_Timer", clrWhite, CORNER_RIGHT_UPPER, 100, 30, "00:00:00", 16);
   setBox("BoTest_Box",clrNONE,CORNER_RIGHT_UPPER,100,60,90,30);
   setLabel("BoTest_Signal", clrWhite, CORNER_RIGHT_UPPER, 80, 65, "-", 16);
   
   setParam("AFbars",105,AFbars);
   setParam(Param1,80,RSIperiod);
   setParam(Param2,55,RSIUpper);
   setParam(Param3,30,RSILower);

初期化処理のOnInitの中に、さっきのsetParamを入れます。数字が違うのは、y軸の位置です。さらに追加したかったら、次は130、155って25ずつ増えていきます。


// 外部入力パラメータ
input datetime startDate = D'2024/4/15 0:00';      // 計算する期間の開始日付
input int      step = 1;                           // パラメータをいくつずつ動かすか
input string   Param1 = "kikan";                        // パラメータ1 名前
input string   Param2 = "Upper";                        // パラメータ2 名前
input string   Param3 = "Lower";                        // パラメータ3 名前

パラメータのラベルに関しては、inputで入力できるようにしています。名前の好みとかあると思うので。。。


で、ここからがボタンクリックイベントの実装です。

// Event処理
void OnChartEvent(const int id,
                const long &lparam,
                const double &dparam,
                const string &sparam)
{
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
      AFbars = ClickEvent(sparam, "AFbars", "up");
      AFbars = ClickEvent(sparam, "AFbars", "low");
      RSIperiod = ClickEvent(sparam, Param1, "up");
      RSIperiod = ClickEvent(sparam, Param1, "low");
      RSIUpper = ClickEvent(sparam, Param2, "up");
      RSIUpper = ClickEvent(sparam, Param2, "low");
      RSILower = ClickEvent(sparam, Param3, "up");
      RSILower = ClickEvent(sparam, Param3, "low");
   }
   
   InitialCalc();
   ChartRedraw(0);
}

int ClickEvent(string sparam, string name, string updown)
{
   int val = int(ObjectGetString(0,"BoTest_Edit_"+name,OBJPROP_TEXT));
   if(sparam=="BoTest_Button_"+name+"_"+updown)
   {
      if(updown=="up")  val = val + step;
      if(updown=="low")  val = val - step;
      ObjectSetInteger(0,"BoTest_Button_"+name+updown,OBJPROP_STATE,false);
      ObjectSetString(0,"BoTest_Edit_"+name,OBJPROP_TEXT,string(val));
   }
   return val;
}

クリックしたら数字が増えるのは、どのボタンも同じなので、ClickEventって関数を作成しました。upの矢印を押せば増える、lowの矢印を押せば減る、Editが更新されるみたいな感じです。

あとはOnChartEventとして、それぞれのsparamに紐づけておしまい。

最後に再描画させれば完成です。


この最後のChartRedraw(0);が超重要です。

これを入れているのと入れていないので、動きが全然違います。ぜひ試してもらいたいですね。非同期処理とかの話なんでしょうが、入れてないと再描画がめちゃくちゃ重たいです。

でも、ChartRedraw(0);を入れることでサクサク動くようになります。


まとめ

パラメータをチャート上で変更可能にしました。今回はRSIのパラメータ(期間と上限レベルと下限レベル)でしたが、もっと他にも触れるように変更していこうと思います。

良かったらフォローしていただいて、続きを読んでもらえると嬉しいです。

ここまで読んでいただいてありがとうございました。

次回は、検証してみた結果とかを羅列していこうと思います。


前回までのコードの修正

リアルタイム勝率計算の部分で、以前のコードではサインが表示されると加算され続ける不具合がありました。

   if(prev_calculated==0){
      InitialCalc();
   }else{ 
      // 現在足から過去に3本分取得
      CopyOpen(_Symbol,PERIOD_CURRENT,0,3+AFnum,Open);
      CopyHigh(_Symbol,PERIOD_CURRENT,0,3+AFnum,High);
      CopyLow(_Symbol,PERIOD_CURRENT,0,3+AFnum,Low);
      CopyClose(_Symbol,PERIOD_CURRENT,0,3+AFnum,Close);
      CopyTime(_Symbol,PERIOD_CURRENT,0,3+AFnum,Time);

      // 勝率計算に必要な値を減算
      if(ObjectFind(0,"BoTest_"+string(int(Time[0+AFnum])))==0){
         if(ObjectGetInteger(0,"BoTest_"+string(int(Time[0+AFnum])),OBJPROP_ARROWCODE)==161 && 
            ObjectGetInteger(0,"BoTest_"+string(int(Time[0+AFnum])),OBJPROP_ANCHOR)==ANCHOR_BOTTOM){
            lowWin--;
         }else if(ObjectGetInteger(0,"BoTest_"+string(int(Time[0+AFnum])),OBJPROP_ARROWCODE)==174 && 
            ObjectGetInteger(0,"BoTest_"+string(int(Time[0+AFnum])),OBJPROP_ANCHOR)==ANCHOR_BOTTOM){
            lowLost--;
         }
         if(ObjectGetInteger(0,"BoTest_"+string(int(Time[0+AFnum])),OBJPROP_ARROWCODE)==161 && 
            ObjectGetInteger(0,"BoTest_"+string(int(Time[0+AFnum])),OBJPROP_ANCHOR)==ANCHOR_TOP){
            highWin--;
         }else if(ObjectGetInteger(0,"BoTest_"+string(int(Time[0+AFnum])),OBJPROP_ARROWCODE)==174 && 
            ObjectGetInteger(0,"BoTest_"+string(int(Time[0+AFnum])),OBJPROP_ANCHOR)==ANCHOR_TOP){
            highLost--;
         }
         
         // すでにオブジェクトがあった場合は、削除
         ObjectDelete(0,"BoTest_"+string(int(Time[0+AFnum])));
      }
   
      // 最新のバーの動きに合わせて、結果変更
      // 陽線 → 陽線 → 陰線
      if(HLCondition("Low",1+AFnum)==true && Open[0+AFnum]>Close[0]){
         makeArray(int(Time[0+AFnum]), Time[0+AFnum], High[0+AFnum], 161, clrAqua, ANCHOR_BOTTOM);
         lowWin++;
      }else if(HLCondition("Low",1+AFnum)==true && Open[0+AFnum]<Close[0]){
         makeArray(int(Time[0+AFnum]), Time[0+AFnum], High[0+AFnum], 174, clrRed, ANCHOR_BOTTOM);
         lowLost++;
      }
      
      // 陰線 → 陰線 → 陽線
      if(HLCondition("High",1+AFnum)==true && Open[0+AFnum]<Close[0]){
         makeArray(int(Time[0+AFnum]), Time[0+AFnum], Low[0+AFnum], 161, clrAqua, ANCHOR_TOP);
         highWin++;
      }else if(HLCondition("High",1+AFnum)==true && Open[0+AFnum]>Close[0]){
         makeArray(int(Time[0+AFnum]), Time[0+AFnum], Low[0+AFnum], 174, clrRed, ANCHOR_TOP);
         highLost++;
      }

最新足にオブジェクトが表示され続ける限り、tickごとに加算されてしまうので、減算処理を入れていたのですが、うまく機能していませんでした。

if(prev_calculated==0){
   InitialCalc();

で、初回起動時のみのInitializeでした。それ以外の時間はtickごとに判定を行いサインを更新します。この際、以前の状態では、勝率計算のHighの勝ち数やLowの勝ち数などを加算計算させていました。

しかし、加算し続けるなどの不具合が生じたため、以下のように修正しました。

修正したコードは以下の通りです。

// 毎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[])
{
   if(lastBar!=Bars(_Symbol,PERIOD_CURRENT)){
      InitialCalc();
   }else{ 
      // 現在足から過去に3本分取得
      CopyOpen(_Symbol,PERIOD_CURRENT,0,3+AFnum,Open);
      CopyHigh(_Symbol,PERIOD_CURRENT,0,3+AFnum,High);
      CopyLow(_Symbol,PERIOD_CURRENT,0,3+AFnum,Low);
      CopyClose(_Symbol,PERIOD_CURRENT,0,3+AFnum,Close);
      CopyTime(_Symbol,PERIOD_CURRENT,0,3+AFnum,Time);
      
      // 勝率計算に必要な値を減算
      if(ObjectFind(0,"BoTest_"+string(int(Time[0+AFnum])))==0){        
         // すでにオブジェクトがあった場合は、削除
         ObjectDelete(0,"BoTest_"+string(int(Time[0+AFnum])));
      }
   
      // 最新のバーの動きに合わせて、結果変更
      // 陽線 → 陽線 → 陰線
      if(HLCondition("Low",1+AFnum)==true && Open[0+AFnum]>Close[0]){
         makeArray(int(Time[0+AFnum]), Time[0+AFnum], High[0+AFnum], 161, clrAqua, ANCHOR_BOTTOM);
      }else if(HLCondition("Low",1+AFnum)==true && Open[0+AFnum]<Close[0]){
         makeArray(int(Time[0+AFnum]), Time[0+AFnum], High[0+AFnum], 174, clrRed, ANCHOR_BOTTOM);
      }
      
      // 陰線 → 陰線 → 陽線
      if(HLCondition("High",1+AFnum)==true && Open[0+AFnum]<Close[0]){
         makeArray(int(Time[0+AFnum]), Time[0+AFnum], Low[0+AFnum], 161, clrAqua, ANCHOR_TOP);
      }else if(HLCondition("High",1+AFnum)==true && Open[0+AFnum]>Close[0]){
         makeArray(int(Time[0+AFnum]), Time[0+AFnum], Low[0+AFnum], 174, clrRed, ANCHOR_TOP);
      }
      
      
      // 予告サインの表示
      // 陽線 → 陽線 なら点灯
      if(HLCondition("Low",0)==true){
         ObjectSetString(0,"BoTest_Signal",OBJPROP_TEXT,"Low");
         ObjectSetInteger(0,"BoTest_Box",OBJPROP_BGCOLOR,clrRed);
      // 陰線 → 陰線 なら点灯
      }else if(HLCondition("High",0)==true){
         ObjectSetString(0,"BoTest_Signal",OBJPROP_TEXT,"High");
         ObjectSetInteger(0,"BoTest_Box",OBJPROP_BGCOLOR,clrGreen);
      // それ以外はなし
      }else{
         ObjectSetString(0,"BoTest_Signal",OBJPROP_TEXT,"-");
         ObjectSetInteger(0,"BoTest_Box",OBJPROP_BGCOLOR,clrNONE);
      }     
   }

   lastBar=Bars(_Symbol,PERIOD_CURRENT);
   
   showComment();
   return(rates_total);
}


   if(lastBar!=Bars(_Symbol,PERIOD_CURRENT)){
      InitialCalc();

     ・・・

   lastBar=Bars(_Symbol,PERIOD_CURRENT);

このように変更することで、ローソク足の更新時にInitialize処理にて勝率計算をさせます。その代わりにlowLostなどの加算減算処理は、else以下で行わないようにさせています。

これによって、不具合が解消されました。

以上です。



この記事が気に入ったらサポートをしてみませんか?