ハイロー用サインツールを作ってみた③ 残り時間の実装
昨日の続き書いていきます。
ハイローオーストラリアとかのバイナリーオプションで使えるサインツールを作っていく第3弾です。
さすがに今日書かなかったら3日坊主になるので、書いていくのですが、前回の最後にお伝えしたアラート部分の実装は時間が足りなかったので、残り時間部分だけ実装しました。
今日は相場が大きく動いて忙しかったので。。。
ハイローはこのままいけば20くらいの儲け。FXも良い感じに拾えてますが、介入ひやひやモードですね。
ってことで、残り時間の表示を実装していきます。
前回までの記事はこちらをご覧ください。
バイナリーオプションで必須の残り時間表示
MT5とかに標準実装してくれてもいいくらいに必要なツールだと思うんですけどね。ローソク足の切り替わりって多くの人が注目してますしね。
4時間足で大きく伸びた場合、残り時間が3時間の時と3分の時だったら違いますよね。上髭にして戻すのか、大陽線で終わるのかみたいな。
ハイローとかのバイナリーでも時間切り替わりでエントリーするロジックを使ってるなら、めっちゃ重要です。時間の残り時間が10秒やから、そろそろHighLowボタンにマウスを置いて待とうかなってするのに必須です。
実装したコード
#property indicator_chart_window
input datetime startDate = D'2024/4/15 0:00'; // 計算する期間の開始日付
double highWin,highLost,lowWin,lowLost;
int OnInit()
{
// 0.1秒ごとにタイマーイベントを動かす
EventSetMillisecondTimer(100);
setLabel("BoTest_Timer", clrWhite, CORNER_RIGHT_UPPER, 100, 30, "00:00:00", 16);
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
// BoTestで始まるオブジェクトを全削除
ObjectsDeleteAll(0,"BoTest_");
// タイマーイベントを削除
EventKillTimer();
}
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[])
{
// 開始日付のバー番号を取得する
int startBar = iBarShift(_Symbol,PERIOD_CURRENT,startDate);
if(prev_calculated==0){
// 開始日付のバー番号から計算を開始する
for(int i=rates_total-startBar; i<rates_total; i++){
MqlDateTime dt;
TimeToStruct(time[i],dt);
// 陽線 → 陽線 → 陰線
if(open[i-2]<close[i-2] && open[i-1]<close[i-1] && open[i]>close[i]){
makeArray(int(time[i]), time[i], high[i], 161, clrAqua, ANCHOR_BOTTOM);
lowWin++;
}else if(open[i-2]<close[i-2] && open[i-1]<close[i-1] && open[i]<close[i]){
makeArray(int(time[i]), time[i], high[i], 174, clrRed, ANCHOR_BOTTOM);
lowLost++;
}
// 陰線 → 陰線 → 陽線
if(open[i-2]>close[i-2] && open[i-1]>close[i-1] && open[i]<close[i]){
makeArray(int(time[i]), time[i], low[i], 161, clrAqua, ANCHOR_TOP);
highWin++;
}else if(open[i-2]>close[i-2] && open[i-1]>close[i-1] && open[i]>close[i]){
makeArray(int(time[i]), time[i], low[i], 174, clrRed, ANCHOR_TOP);
highLost++;
}
}
}else{
// 最新のバー番号
int lastBar = rates_total-1;
// 勝率計算に必要な値を減算
if(ObjectFind(0,"BoTest_"+string(int(time[lastBar])))==0){
if(ObjectGetInteger(0,"BoTest_"+string(int(time[lastBar])),OBJPROP_ARROWCODE)==161 &&
ObjectGetInteger(0,"BoTest_"+string(int(time[lastBar])),OBJPROP_ANCHOR)==ANCHOR_BOTTOM){
lowWin--;
}else if(ObjectGetInteger(0,"BoTest_"+string(int(time[lastBar])),OBJPROP_ARROWCODE)==174 &&
ObjectGetInteger(0,"BoTest_"+string(int(time[lastBar])),OBJPROP_ANCHOR)==ANCHOR_BOTTOM){
lowLost--;
}
if(ObjectGetInteger(0,"BoTest_"+string(int(time[lastBar])),OBJPROP_ARROWCODE)==161 &&
ObjectGetInteger(0,"BoTest_"+string(int(time[lastBar])),OBJPROP_ANCHOR)==ANCHOR_TOP){
highWin--;
}else if(ObjectGetInteger(0,"BoTest_"+string(int(time[lastBar])),OBJPROP_ARROWCODE)==174 &&
ObjectGetInteger(0,"BoTest_"+string(int(time[lastBar])),OBJPROP_ANCHOR)==ANCHOR_TOP){
highLost--;
}
// すでにオブジェクトがあった場合は、削除
ObjectDelete(0,"BoTest_"+string(int(time[lastBar])));
}
// 最新のバーの動きに合わせて、結果変更
// 陽線 → 陽線 → 陰線
if(open[lastBar-2]<close[lastBar-2] && open[lastBar-1]<close[lastBar-1] && open[lastBar]>close[lastBar]){
makeArray(int(time[lastBar]), time[lastBar], high[lastBar], 161, clrAqua, ANCHOR_BOTTOM);
lowWin++;
}else if(open[lastBar-2]<close[lastBar-2] && open[lastBar-1]<close[lastBar-1] && open[lastBar]<close[lastBar]){
makeArray(int(time[lastBar]), time[lastBar], high[lastBar], 174, clrRed, ANCHOR_BOTTOM);
lowLost++;
}
// 陰線 → 陰線 → 陽線
if(open[lastBar-2]>close[lastBar-2] && open[lastBar-1]>close[lastBar-1] && open[lastBar]<close[lastBar]){
makeArray(int(time[lastBar]), time[lastBar], low[lastBar], 161, clrAqua, ANCHOR_TOP);
highWin++;
}else if(open[lastBar-2]>close[lastBar-2] && open[lastBar-1]>close[lastBar-1] && open[lastBar]>close[lastBar]){
makeArray(int(time[lastBar]), time[lastBar], low[lastBar], 174, clrRed, ANCHOR_TOP);
highLost++;
}
}
showComment();
return(rates_total);
}
void makeArray(int barnum, datetime time, double price, int code, color clr, int anch)
{
ObjectCreate(0,"BoTest_"+string(barnum), OBJ_ARROW, 0, time, price);
ObjectSetInteger(0, "BoTest_"+string(barnum), OBJPROP_ARROWCODE, code);
ObjectSetInteger(0, "BoTest_"+string(barnum), OBJPROP_COLOR, clr);
ObjectSetInteger(0, "BoTest_"+string(barnum), OBJPROP_ANCHOR, anch);
}
void showComment()
{
string HW = DoubleToString( 100* highWin / (highWin+highLost), 1);
string LW = DoubleToString( 100* lowWin / (lowWin+lowLost), 1);
string TW = DoubleToString( 100* (highWin+lowWin) / (highWin+highLost+lowWin+lowLost), 1);
string Hsum = DoubleToString( highWin+highLost, 0);
string Lsum = DoubleToString( lowWin+lowLost, 0);
string Tsum = DoubleToString( highWin+highLost+lowWin+lowLost, 0);
Comment( "│ High Win : ", HW, " % ( " +Hsum+ " ) |\n",
"| Low Win : ", LW, " % ( " +Lsum+ " ) |\n",
"| TotalWin : ", TW, " % ( " +Tsum+ " ) |\n");
}
void OnTimer()
{
int now = TimeCurrent();
int PerInt = PeriodToInteger()*60;
int amari = PerInt-now%PerInt;
int rhour = amari>3600 ? amari/3600 : 0;
int rmin = amari-rhour*3600>60 ? (amari-rhour*3600)/60 : 0;
int rsec = amari-3600*rhour-60*rmin!=0 ? amari-3600*rhour-60*rmin : 0;
string txt = StringFormat("%02d:%02d:%02d", rhour, rmin, rsec);
ObjectSetString(0,"BoTest_Timer",OBJPROP_TEXT,txt);
}
void setLabel(string name, color clr, int corner, int x, int y, string txt, int FontSize)
{
ObjectCreate(0,name,OBJ_LABEL,0,0,0);
ObjectSetInteger(0,name,OBJPROP_FONTSIZE,FontSize);
ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
ObjectSetInteger(0,name,OBJPROP_CORNER,corner);
ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
ObjectSetString(0,name,OBJPROP_TEXT,txt);
}
int PeriodToInteger()
{
int PerInt = _Period;
if(_Period==16385) PerInt=60;
if(_Period==16386) PerInt=120;
if(_Period==16387) PerInt=180;
if(_Period==16388) PerInt=240;
if(_Period==16390) PerInt=360;
if(_Period==16392) PerInt=480;
if(_Period==16396) PerInt=720;
if(_Period==16408) PerInt=1440;
if(_Period==32769) PerInt=10080;
if(_Period==49153) PerInt=43200;
return (PerInt) ;
}
前回より大分コード内容がうるさくなってきました。
とりあえず順番に説明すると、時間の動きはOnTimer関数で動かします。このOnTimer関数は、OnInit関数で指定した時間ごとにイベントを行います。
EventSetMillisecondTimer(100);
これがOnInitに書かれているのですが、100msec(0.1秒)に1回動くようにって命令です。1000なら1秒に1回です。
void OnTimer()
{
int now = TimeCurrent();
int PerInt = PeriodToInteger()*60;
int amari = PerInt-now%PerInt;
int rhour = amari>3600 ? amari/3600 : 0;
int rmin = amari-rhour*3600>60 ? (amari-rhour*3600)/60 : 0;
int rsec = amari-3600*rhour-60*rmin!=0 ? amari-3600*rhour-60*rmin : 0;
string txt = StringFormat("%02d:%02d:%02d", rhour, rmin, rsec);
ObjectSetString(0,"BoTest_Timer",OBJPROP_TEXT,txt);
}
で、これがOnTimer関数で、残り時間を計算させてます。
現在時間をTimeCurrentで拾ってきて整数表示させると、1970年1月1日0時0分0秒からの秒数が表示されます。これをチャートの時間軸の秒数で割った余りを求めます。
チャートの時間軸の秒数とは、1分足なら60秒、1時間足なら3600秒みたいな感じです。
チャートの時間軸の秒数から余りを引けば、残りの秒数が分かります。
1時間足なら3600秒なので、余りが3000秒なら、残りの時間は600秒です。
これを時間と分と秒に分けた文字列をtxtとして作成。
最後にラベルのオブジェクトのテキスト部分を書き換えさせます。
MT5の落とし穴 注意点
MT5の注意点として、_periodで呼び出される値が無茶苦茶だってことがあります。
_Periodは、現在表示しているチャートの時間足を表示してくれます。PERIOD_M1(1分足)とかPERIOD_H1(1時間足)とか。
これを整数表示にするためintで囲むと、1分足などの分足はPERIOD_M1=1、PERIOD_M15=15のように時間と分数が一致します。
しかし、1時間足以降はPERIOD_H1=16385のようにわけのわからない値が返ってきます。なので、これをわけのわかる値に変えるため、PeriodToIntegerって関数を作りました。
int PeriodToInteger()
{
int PerInt = _Period;
if(_Period==16385) PerInt=60;
if(_Period==16386) PerInt=120;
if(_Period==16387) PerInt=180;
if(_Period==16388) PerInt=240;
if(_Period==16390) PerInt=360;
if(_Period==16392) PerInt=480;
if(_Period==16396) PerInt=720;
if(_Period==16408) PerInt=1440;
if(_Period==32769) PerInt=10080;
if(_Period==49153) PerInt=43200;
return (PerInt) ;
}
ラベル表時
ラベル表示は今後もサイン表示とかで使用するので、関数化しておきました。
void setLabel(string name, color clr, int corner, int x, int y, string txt, int FontSize)
{
ObjectCreate(0,name,OBJ_LABEL,0,0,0);
ObjectSetInteger(0,name,OBJPROP_FONTSIZE,FontSize);
ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
ObjectSetInteger(0,name,OBJPROP_CORNER,corner);
ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
ObjectSetString(0,name,OBJPROP_TEXT,txt);
}
ObjectCreateでLABELを作成。
CONERでチャートのどの4隅に表示するか決めて、XDISTANCEとYDISTANCEで4隅からの距離を設定します。
ここで設定されたtxt部分をOnTimer関数で、時間の経過とともに上書きしていく仕様になります。
オブジェクトとOnTimerの終了処理
オブジェクトとイベント処理は、OnDeinit関数に削除することを書いておかないと、インジケータを消してもオブジェクトが残り続けたり、イベントが動き続けたりします。
なので、OnDeinitで消しています。
void OnDeinit(const int reason)
{
// BoTestで始まるオブジェクトを全削除
ObjectsDeleteAll(0,"BoTest_");
// タイマーイベントを削除
EventKillTimer();
}
まとめ
今日のおさらい。
バイナリーオプションはローソク足の切り替わりが大事。
時間の切り替わりを知るために、残り時間を表示する部分を実装した。
以上です。
次回は、ようやくサイン予告のアラート部分を作ります。
また、このインジケータを使っていくつかの通貨ペアや時間足で検証した結果も紹介していきますので、また読んでもらえると嬉しいです。
例えばこんな感じで出してます。
ここまで読んでいただきありがとうございました。
ってところで1万文字を超えてきたので、今日はここまで。
お疲れさまでした。
こんな感じのサインツール欲しいみたいなんがあれば、XにDMください。
https://twitter.com/highlow_oyaji
この記事が気に入ったらサポートをしてみませんか?