上位足は上昇トレンドなのに、下位足だけ見て逆張りエントリーしてしまい、
すぐに逆行された、という経験はないでしょうか。
複数の時間足を1つずつ目で確認するのは面倒ですし、
人によって「揃っている」の判断基準もバラバラになりがちです。
この記事では、複数の時間足のトレンド方向を自動で判定し、
どれだけ揃っているかを-100%〜+100%の「一致度スコア」という1つの数値にまとめて、
チャート上にパネル表示するMT4インジケーターの作り方を解説します。
・複数時間足のトレンドが一致しているとなぜ良いのか
・一致度スコアの計算方法(上昇票・下降票の集計)
・時間足のリストを文字列で指定できるようにする実装
・実際に動くパネル表示インジケーターのコード全体
複数時間足が一致すると何が分かるのか・一致度を数値化したい理由
まず、なぜ複数の時間足を見比べる必要があるのかを整理します。
上位足と下位足の方向がバラバラだと”ダマシ”にあいやすい
下位足だけでシグナルが出ていても、
上位足が反対方向のトレンドを続けている場合、
そのシグナルは一時的な「ダマシ」で終わることがあります。
・下位足の1本のシグナルだけでエントリーすると、上位足の大きな流れに逆らいやすい
・時間足ごとに目視で確認すると、人によって「揃っている」の基準がブレる
・確認する時間足が増えるほど、判断に時間がかかる
既刊「mtf-trend-panel-indicator-mt4.md」との違い
既刊の記事で紹介したインジケーターは、
1つの上位足だけを対象に「上昇」「下降」「レンジ」の3状態を判定し、
パネルに表示するものでした。
この記事のインジケーターは、対象を1つの上位足に絞らず、
複数の時間足(例: 15分足・1時間足・4時間足・日足)をまとめて判定し、
それらがどれだけ同じ方向を向いているかを、1つのスコアに集計する点が異なります。
このインジケーターでできること
このインジケーターを使うと、
「今は4つの時間足のうち3つが上昇方向を向いている(スコア+50%)」のように、
複数時間足の状況を一目で、しかも数値として確認できるようになります。
トレンド一致度スコアの計算方法を考える
次に、一致度をどう数値にするかという考え方を整理します。
各時間足を「上昇/下降/レンジ」の3状態に判定する
既刊`mtf-trend-panel-indicator-mt4.md`と同じく、
短期移動平均線と長期移動平均線の差が一定の閾値を超えていれば「上昇」、
反対に超えていれば「下降」、閾値以内なら「レンジ(方向なし)」と判定します。
一致度スコア = (上昇票数 – 下降票数) ÷ 時間足数 × 100
一致度スコア(%) = (上昇と判定された時間足の数 - 下降と判定された時間足の数) ÷ 集計対象の時間足数 × 100
例えば4つの時間足のうち3つが上昇、1つがレンジと判定された場合、
スコアは`(3 – 0) ÷ 4 × 100 = 75%`になります。
スコアの読み方(+100%〜-100%の意味)
・+100%に近いほど、全ての時間足が上昇方向で一致している
・-100%に近いほど、全ての時間足が下降方向で一致している
・0%に近い場合は、時間足ごとに方向がバラバラ、またはレンジ中心の状態
文字列の時間足リストをENUM_TIMEFRAMESに変換する実装
集計したい時間足を、コードを書き換えずに設定できるようにする部分を説明します。
StringSplitで”M15,H1,H4,D1″を分解する
既刊`currency-correlation-indicator-mt4.md`の、
通貨ペア分解と同じパターンを使います。
カンマ区切りの文字列を`StringSplit()`で配列に分解します。
区切り文字はそのまま文字列として渡せず、
`StringGetCharacter()`で文字コードに変換してから、
渡す必要がある点に注意してください。
文字列をPERIOD_M15等の定数に変換する自作関数
MQL4には、`”M15″`のような文字列を、
`PERIOD_M15`のような定数に変換する、
標準関数が無いため、
`StringToPeriod()`という自作関数で、文字列と定数を1つずつ対応させます。
未対応の文字列が渡された場合のフォールバック
`”M16″`のような、対応表に無い文字列が渡された場合は、
`PERIOD_CURRENT`(現在のチャートの時間足)にフォールバックする、
設計にしておくと、
入力ミスがあっても、コンパイルエラーにはならず安全側に倒せます。
実際に動くMTFトレンド一致度スコアインジケーターのコード全体
ここまでの考え方を組み合わせた、実際に動くコードの全体です。
パネル表示用のオブジェクト初期化(OnInit/OnDeinit)
`OnInit()`で、ヘッダー行・スコア行・時間足ごとの行の分だけ、
あらかじめ`OBJ_LABEL`を作成しておきます。
OnCalculateでスコアを計算しパネルを更新する処理
`OnCalculate()`の中で、時間足ごとに、
`JudgeDirection()`を呼び出し、
集計結果からスコアを計算して、パネルの文字列と色を更新します。
コード全文
//+------------------------------------------------------------------+
//| MTFTrendAgreementScore.mq4 |
//| Copyright 2026, FX-EA System Project Creator |
//| https://creator.fx-ea-system-project.com/ |
//+------------------------------------------------------------------+
#property strict
#property indicator_chart_window
input string InpTimeframes = "M15,H1,H4,D1"; //集計する時間足(カンマ区切り、スペース無し)
input int InpFastPeriod = 10; //短期移動平均の期間
input int InpSlowPeriod = 30; //長期移動平均の期間
input double InpRangeThresholdPips = 5; //レンジ(方向なし)とみなす閾値(pips)
string CompareList[];
int TFCount = 0;
string PanelPrefix = "MtfScore_";
int OnInit()
{
ushort sep = StringGetCharacter(",", 0);
TFCount = StringSplit(InpTimeframes, sep, CompareList);
for(int i = 0; i < TFCount + 2; i++) //+2はヘッダー行とスコア行
{
string name = PanelPrefix + IntegerToString(i);
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_RIGHT_UPPER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 20 + i * 18);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 10);
}
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
for(int i = 0; i < TFCount + 2; i++)
ObjectDelete(0, PanelPrefix + IntegerToString(i));
}
//文字列("M15"等)をENUM_TIMEFRAMES(PERIOD_M15等)に変換する
int StringToPeriod(string tf)
{
if(tf == "M1") return(PERIOD_M1);
if(tf == "M5") return(PERIOD_M5);
if(tf == "M15") return(PERIOD_M15);
if(tf == "M30") return(PERIOD_M30);
if(tf == "H1") return(PERIOD_H1);
if(tf == "H4") return(PERIOD_H4);
if(tf == "D1") return(PERIOD_D1);
if(tf == "W1") return(PERIOD_W1);
if(tf == "MN1") return(PERIOD_MN1);
return(PERIOD_CURRENT); //未対応の文字列は現在のチャートの時間足として扱う(フォールバック)
}
//指定した時間足の、短期MAと長期MAの差から方向を判定する(+1=上昇/-1=下降/0=レンジ)
int JudgeDirection(string symbol, int timeframe)
{
double fastMA = iMA(symbol, timeframe, InpFastPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
double slowMA = iMA(symbol, timeframe, InpSlowPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
double diff = fastMA - slowMA;
double threshold = InpRangeThresholdPips * _Point * 10; //5桁/3桁ブローカーのpips換算
if(diff > threshold)
return(1);
if(diff < -threshold)
return(-1);
return(0);
}
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 upCount = 0, downCount = 0;
int directions[];
ArrayResize(directions, TFCount);
for(int i = 0; i < TFCount; i++)
{
int period = StringToPeriod(CompareList[i]);
int dir = JudgeDirection(_Symbol, period);
directions[i] = dir;
if(dir == 1)
upCount++;
if(dir == -1)
downCount++;
}
double score = (TFCount > 0) ? (double)(upCount - downCount) / TFCount * 100.0 : 0;
string headerName = PanelPrefix + "0";
ObjectSetString(0, headerName, OBJPROP_TEXT, "MTFトレンド一致度スコア");
ObjectSetInteger(0, headerName, OBJPROP_COLOR, clrWhite);
string scoreName = PanelPrefix + "1";
color scoreColor = clrSilver;
string scoreLabel = "方向性バラバラ";
if(score >= 75)
{
scoreColor = clrLimeGreen;
scoreLabel = "強い上昇一致";
}
else if(score <= -75)
{
scoreColor = clrOrangeRed;
scoreLabel = "強い下降一致";
}
else if(score >= 25)
{
scoreColor = clrYellowGreen;
scoreLabel = "やや上昇優勢";
}
else if(score <= -25)
{
scoreColor = clrOrange;
scoreLabel = "やや下降優勢";
}
ObjectSetString(0, scoreName, OBJPROP_TEXT, StringFormat("スコア %.0f%% (%s)", score, scoreLabel));
ObjectSetInteger(0, scoreName, OBJPROP_COLOR, scoreColor);
for(int i = 0; i < TFCount; i++)
{
string name = PanelPrefix + IntegerToString(i + 2);
string dirText = "レンジ";
color dirColor = clrSilver;
if(directions[i] == 1)
{
dirText = "上昇";
dirColor = clrLimeGreen;
}
if(directions[i] == -1)
{
dirText = "下降";
dirColor = clrOrangeRed;
}
ObjectSetString(0, name, OBJPROP_TEXT, StringFormat("%s : %s", CompareList[i], dirText));
ObjectSetInteger(0, name, OBJPROP_COLOR, dirColor);
}
return(rates_total);
}
MetaEditorでのコンパイル・表示手順
実際にインジケーターとして動かすまでの手順です。
手順
- MetaEditorで新規カスタムインジケーター「MTFTrendAgreementScore」を作成し、上のコードを貼り付けてコンパイルする
- 任意の通貨ペアのチャートにドラッグ&ドロップで適用する
- チャート右上に、ヘッダー・スコア・時間足ごとの方向がパネル表示されることを確認する
- `InpTimeframes`を書き換え、集計する時間足の組み合わせを変えて動作を確認する
動作確認のポイント
表示直後は、各時間足のヒストリカルデータの読み込みが追いついておらず、
一時的に不自然な値になることがあります。数秒待ってから再確認してください。
パラメーターを変えて試す
`InpFastPeriod`・`InpSlowPeriod`・
`InpRangeThresholdPips`を変えることで、
スコアの敏感さ(反応の早さ)を調整できます。
よくあるエラーとつまずきポイント
実装・運用でつまずきやすい点をまとめます。
時間足の文字列を間違えるとPERIOD_CURRENTにフォールバックしてしまう
・”H1″を”h1″と小文字で書くと対応表に一致せず、現在の時間足として扱われる
・入力ミスに気づきにくく、意図しない時間足で集計され続ける
・`InpTimeframes`は大文字・スペース無しで正確に入力する
pips換算で3桁/5桁ブローカーの違いにハマる
`_Point * 10`という換算は、小数点以下5桁(または3桁)の、
一般的なブローカーの気配値を前提にしています。
ブローカーの表示桁数(`_Digits`)によっては、この換算式を調整する必要があります。
MQL4とMQL5での違い
・MQL5のiMA()はハンドル返却に変わる(既刊icustom-mql5.md参照)
・MQL5の時間足はENUM_TIMEFRAMES型で明示される点もMQL4との違い
・文字列を定数に変換する考え方自体はMQL4/MQL5共通で使える
トレンド一致度スコアの実務的な使い方と限界
最後に、実際のトレードでどう使うかと、注意点を整理します。
エントリーの”最終確認”として使う
下位足で売買シグナルが出た際、
このスコアが大きくマイナス(下降一致)に傾いているときだけ買いエントリーを見送る、
といった「最終確認」としての使い方が現実的です。
スコアが高いからといって逆行しないわけではない
・一致度スコアは、あくまで過去の移動平均線の位置関係から計算した結果
・スコアが+100%でも、次の瞬間に反転する可能性は常にある
・スコアを過信せず、損切り設定とあわせて使う前提で設計すること
EAの発注条件に組み込む応用
`JudgeDirection()`と一致度スコアの計算部分をそのまま流用すれば、
「スコアが一定以上のときだけ新規エントリーする」という、
EAのフィルター条件としても応用できます。
よくある質問
一致度スコアについて、よく検索される疑問に回答します。
・時間足はいくつ指定するのが良いですか?
・このスコアだけでエントリーしてもいいですか?
・このロジックをEAの発注条件に使えますか?
時間足はいくつ指定するのが良いですか?
多すぎるとパネルの表示領域を圧迫し、
少なすぎるとスコアの変化が粗くなります。
まずはデフォルトの4つ(M15,H1,H4,D1)から試し、
必要に応じて増減することをおすすめします。
このスコアだけでエントリーしてもいいですか?
推奨しません。
このスコアは、あくまで複数時間足の”状況”を数値化したものであり、
エントリーの根拠そのものではありません。
既存の売買シグナルと組み合わせた「最終確認」として使ってください。
このロジックをEAの発注条件に使えますか?
できます。
`JudgeDirection()`とスコア計算部分を関数として切り出せば、
EAのOnTick()内から呼び出すフィルター条件として、そのまま応用できます。
【MQL4】複数時間足のトレンド一致度を点数化して表示するマルチタイムフレーム・インジケーターの作り方(mtf-trend-panel-indicator-mt4.mdとの違い):まとめ
複数の時間足を1つずつ目で確認する代わりに、
上昇票と下降票を集計して-100%〜+100%の一致度スコアにまとめることで、
「今はどれだけ時間足が揃っているか」を一目で、数値として確認できるようになります。
ただし、スコアが高いことは「逆行しないこと」を意味しません。
既存の売買シグナルの最終確認として使い、損切り設定とあわせて運用してください。
コメント