既刊の曜日別勝率フィルターEAの記事は、
曜日(7パターン)ごとの勝率をコメント欄に表示し、
人が手動でブロックする曜日を決める設計でした。
この記事では、曜日×時間帯(7×24=168パターン)まで細かく集計し、
「今エントリーしていい時間帯か」を、
信号機のような背景色でチャートに常時表示するインジケーターの作り方を解説します。
・既刊EAの曜日別集計を曜日×時間帯に拡張する考え方
・TimeHourで時間帯ごとに勝敗を仕分ける方法
・OBJ_RECTANGLE_LABELで信号機のような背景色パネルを作る方法
・実際に動くインジケーターのコード全体
既刊EAとの関係・本記事の設計
まず、既刊EAとの役割の違いを整理します。
既刊EAは曜日(7パターン)・手動フラグ
既刊の曜日別勝率フィルターEAは、
曜日ごとに取引を自動でブロックするかどうかを、
人が集計結果を見てから入力パラメータで決める設計でした。
本記事は曜日×時間帯(168パターン)・自動の視覚表示
本記事のインジケーターは、
曜日と時間帯を掛け合わせた168区分で勝率を集計し、
発注をブロックするのではなく、
今の区分が「良い時間帯か」を信号機の色で知らせるだけの設計にします。
なぜ自動判定の表示にしたのか
・既刊EAは手動フラグで慎重に運用する設計だった
・本記事は『発注をブロックしない』表示専用のため、自動の色分けにしても実害が小さい
・取引件数が少ない区分は『黄色(判断材料不足)』として扱い、安易に緑・赤と判定しない
曜日×時間帯で勝敗を集計する
集計のロジックを確認します。
2次元配列で曜日×時間帯を管理する
int TotalCount[7][24]; int WinCount[7][24];
既刊EAの7要素の配列を、曜日×時間帯の2次元配列に拡張します。
TimeHourで時間帯を取得する
int TimeHour(datetime date);
引数に渡した日時の時(0〜23)をint型で返します。
`OrderCloseTime()`をそのまま渡せば、決済した時間帯が分かります。
決済履歴を1件ずつ仕分ける
void BuildWeekdayHourMatrix()
{
for(int d = 0; d < 7; d++)
for(int h = 0; h < 24; h++)
{
TotalCount[d][h] = 0;
WinCount[d][h] = 0;
}
int historyTotal = OrdersHistoryTotal();
for(int i = 0; i < historyTotal; i++)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)) continue;
if(OrderType() != OP_BUY && OrderType() != OP_SELL) continue;
int wday = TimeDayOfWeek(OrderCloseTime());
int hour = TimeHour(OrderCloseTime());
TotalCount[wday][hour]++;
if(OrderProfit() + OrderSwap() + OrderCommission() > 0)
WinCount[wday][hour]++;
}
}
`ArrayInitialize()`は1次元配列専用の関数であり、
本記事の`TotalCount[7][24]`のような2次元配列には使えないため、
二重for文で1マスずつ0に戻しています。
既刊EAの集計ロジックに、`TimeHour()`の仕分けを加えた設計です。
現在の区分を信号機の色で判定する
集計結果から、今の区分がどの色にあたるかを判定します。
3色の判定基準
| 色 | 条件 | 意味 |
|---|---|---|
| 緑 | 取引件数が十分かつ勝率が高い | 過去の傾向が良い時間帯 |
| 黄 | 取引件数が不足している | 判断材料が足りない |
| 赤 | 取引件数が十分かつ勝率が低い | 過去の傾向が悪い時間帯 |
判定関数
color GetSignalColor(int wday, int hour, string &label)
{
int total = TotalCount[wday][hour];
if(total < InpMinSampleSize)
{
label = "判断材料不足(" + IntegerToString(total) + "件)";
return(clrGray);
}
double rate = (double)WinCount[wday][hour] / total * 100.0;
label = DoubleToString(rate, 1) + "%(" + IntegerToString(WinCount[wday][hour]) + "/" + IntegerToString(total) + ")";
if(rate >= InpGoodThreshold)
return(clrLimeGreen);
if(rate <= InpBadThreshold)
return(clrOrangeRed);
return(clrGray);
}
取引件数が`InpMinSampleSize`未満の区分は、
勝率が0%や100%でも信用せず、必ず黄色(灰色)扱いにします。
取引件数を軽んじない理由
既刊EAと同じく、1〜2件しかない区分の勝率は統計として意味を持ちません。
本記事では既定値10件をサンプル数の下限としています。
OBJ_RECTANGLE_LABELで信号機パネルを表示する
判定結果をチャート上に表示する方法を確認します。
背景色だけの四角をOBJ_RECTANGLE_LABELで作る
ObjectCreate(0, "SignalBox", OBJ_RECTANGLE_LABEL, 0, 0, 0); ObjectSetInteger(0, "SignalBox", OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, "SignalBox", OBJPROP_YDISTANCE, 20); ObjectSetInteger(0, "SignalBox", OBJPROP_XSIZE, 160); ObjectSetInteger(0, "SignalBox", OBJPROP_YSIZE, 24);
`OBJPROP_BGCOLOR`に色を設定すると、
四角形の塗りつぶし色を信号機のように切り替えられます。
OBJ_LABELで文字情報も重ねる
既刊のMTFトレンドパネルと同じ`OBJ_LABEL`を使い、
「月曜12時: 62.0%(13/21)」のような文字を背景の上に重ねて表示します。
実際に動くコード全体
曜日×時間帯の勝率を集計し、信号機パネルで表示するインジケーターです。
//+------------------------------------------------------------------+
//| WeekdayHourTrafficLight.mq4 |
//| Copyright 2026, FX-EA System Project Creator |
//| https://creator.fx-ea-system-project.com/ |
//+------------------------------------------------------------------+
#property strict
#property indicator_chart_window
input int InpMinSampleSize = 10; //判定に必要な最低取引件数
input double InpGoodThreshold = 55.0; //緑と判定する勝率(%)
input double InpBadThreshold = 45.0; //赤と判定する勝率(%)
input int InpRefreshSeconds = 300; //再集計の間隔(秒)
int TotalCount[7][24];
int WinCount[7][24];
datetime LastBuildTime = 0;
string BoxName = "WeekdayHourSignalBox";
string LabelName = "WeekdayHourSignalLabel";
string WeekdayName(int wday)
{
string names[7] = {"日","月","火","水","木","金","土"};
return(names[wday]);
}
void BuildWeekdayHourMatrix()
{
for(int d = 0; d < 7; d++)
for(int h = 0; h < 24; h++)
{
TotalCount[d][h] = 0;
WinCount[d][h] = 0;
}
int historyTotal = OrdersHistoryTotal();
for(int i = 0; i < historyTotal; i++)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)) continue;
if(OrderType() != OP_BUY && OrderType() != OP_SELL) continue;
int wday = TimeDayOfWeek(OrderCloseTime());
int hour = TimeHour(OrderCloseTime());
TotalCount[wday][hour]++;
if(OrderProfit() + OrderSwap() + OrderCommission() > 0)
WinCount[wday][hour]++;
}
LastBuildTime = TimeCurrent();
}
color GetSignalColor(int wday, int hour, string &label)
{
int total = TotalCount[wday][hour];
if(total < InpMinSampleSize)
{
label = "判断材料不足(" + IntegerToString(total) + "件)";
return(clrGray);
}
double rate = (double)WinCount[wday][hour] / total * 100.0;
label = DoubleToString(rate, 1) + "%(" + IntegerToString(WinCount[wday][hour]) + "/" + IntegerToString(total) + ")";
if(rate >= InpGoodThreshold)
return(clrLimeGreen);
if(rate <= InpBadThreshold)
return(clrOrangeRed);
return(clrGray);
}
int OnInit()
{
ObjectCreate(0, BoxName, OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, BoxName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, BoxName, OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, BoxName, OBJPROP_YDISTANCE, 20);
ObjectSetInteger(0, BoxName, OBJPROP_XSIZE, 200);
ObjectSetInteger(0, BoxName, OBJPROP_YSIZE, 24);
ObjectCreate(0, LabelName, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, LabelName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, LabelName, OBJPROP_XDISTANCE, 16);
ObjectSetInteger(0, LabelName, OBJPROP_YDISTANCE, 24);
ObjectSetInteger(0, LabelName, OBJPROP_FONTSIZE, 11);
BuildWeekdayHourMatrix();
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
ObjectDelete(0, BoxName);
ObjectDelete(0, LabelName);
}
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(TimeCurrent() - LastBuildTime >= InpRefreshSeconds)
BuildWeekdayHourMatrix();
int wday = TimeDayOfWeek(TimeCurrent());
int hour = TimeHour(TimeCurrent());
string label;
color clr = GetSignalColor(wday, hour, label);
ObjectSetInteger(0, BoxName, OBJPROP_BGCOLOR, clr);
ObjectSetString(0, LabelName, OBJPROP_TEXT, WeekdayName(wday) + hour + "時: " + label);
return(rates_total);
}
再集計の間隔を設けた理由
決済履歴の全件走査は、ティックのたびに実行すると負荷が大きいため、
`InpRefreshSeconds`で再集計の間隔を空ける設計にしています。
MetaEditorでのコンパイル・適用手順
- MetaEditorで新規カスタムインジケーター「WeekdayHourTrafficLight」を作成し、上のコードを貼り付けてコンパイルする
- MT4のナビゲーターからチャートへドラッグ&ドロップして適用する
- チャート左上に、曜日・時間・勝率の文字と背景色のパネルが表示されることを確認する
- 端末の「口座履歴」タブの表示期間が短いと集計件数も少なくなるため、「すべての履歴」に変更しておく
よくあるエラーと注意点
実際に使う前に確認しておきたい点をまとめます。
ほとんどの時間帯が黄色(灰色)になる
・決済履歴が少ない口座では、168区分の大半が取引件数不足になりやすい
・`InpMinSampleSize`を小さくしすぎると偶然を信号と誤認しやすくなる
・ある程度の取引件数が貯まってから判断材料として使う
単独で発注判断に使わない
このインジケーターは表示専用であり、
既刊EAのように発注そのものをブロックする機能は持ちません。
MQL5版は未着手(今後の課題)
本記事はMQL4版のみを扱いました。
MQL5版は、
決済履歴の取得方法(HistorySelect等)が、
異なる点に注意してください。
MQL4とMQL5の違い
決済履歴の集計方法に関する2言語の違いを確認します。
MQL4はOrdersHistoryTotalで直接ループする
MQL4の`OrdersHistoryTotal()`・
`OrderSelect(MODE_HISTORY)`は、
既刊weekday-hour-filter-ea-mt4.mdで解説した通りの方式です。
MQL5はHistorySelectで期間を指定する
MQL5では、先に`HistorySelect()`で集計対象の期間を指定してから、
`HistoryDealsTotal()`・
`HistoryDealGetInteger(ticket, DEAL_TIME)`で、
決済時刻を取得する設計に変わります。
曜日×時間帯の判定ロジックはそのまま応用できる
決済時刻を取得したあとの、
「曜日×時間帯の配列に仕分けて勝率を計算する」という考え方自体は、
MQL5に移植する場合でもそのまま応用できます。
よくある質問
曜日・時間帯の信号機インジケーターについて、よくある疑問に回答します。
・取引件数がある程度貯まるまでは黄色(灰色)が多くても気にしない
・緑/赤のしきい値は口座ごとにバックテストで調整する
・既刊EAと併用し、赤の時間帯だけ手動でブロックする運用もできる
既刊の曜日別勝率フィルターEAと何が違いますか?
既刊EAは曜日(7パターン)の集計を手動フラグでブロックする設計でした。
本記事は曜日×時間帯(168パターン)まで細かく集計し、
発注はブロックせず信号機の色で知らせるだけの設計です。
このインジケーターの判定をEAの発注条件にできますか?
できます。
`GetSignalColor()`が緑を返すときだけ発注を許可する関数に書き換えれば、
既刊EAと同じ発注フィルターとして転用できます。
デモ口座で始めたばかりで履歴が少ない場合はどうすればよいですか?
取引件数が少ないうちは、大半の区分が判断材料不足(黄色)になります。
これは誤動作ではなく、安易に緑・赤と判定しないための設計です。
ある程度の取引件数が貯まってから参考にしてください。
【MQL4】曜日・時間帯ごとの勝率を信号機(背景色)で表示するインジケーターの作り方(weekday-hour-filter-ea-mt4.mdの発展形):まとめ
既刊の曜日別勝率フィルターEAは、
曜日(7パターン)の集計を人が手動でブロックする設計でした。
曜日×時間帯(168パターン)まで集計を細かくし、
発注をブロックせず信号機の色で知らせるだけの設計にすることで、
既刊EAより細かい粒度で、かつ安全に「今の時間帯の傾向」を確認できます。
コメント