既刊のiStdDev(標準偏差)インジケーターの記事では、標準偏差が「普段より急に広がった瞬間」
を検知する設計で解説しました。
この記事では同じiStdDevの比率を使い、値動きが「収縮(スクイーズ)」
したあとに広がった瞬間を、ブレイクアウトのエントリー条件にするEAを作ります。
・既刊ATRボラティリティフィルターEAとの役割の違い
・標準偏差の「収縮」を検知して値幅を記録するロジック
・収縮後のブレイクアウトを発注条件に変換する方法
・損切り必須・ポジション重複防止の書き方
・実際に動くEAのコード全体
なぜ「収縮」からの拡大を狙うのか
まず、既刊のボラティリティ系EAと本記事の違いを整理します。
既刊iStdDevインジケーターのおさらい
既刊インジケーターは、直近の平均的な標準偏差と比べて、現在値が何倍かという「ratio」
を常時表示する設計でした。
このratioが一定の倍率を超えたら「広がっている」と判定していました。
既刊ATRボラティリティフィルターEAとの違い
既刊のATRボラティリティフィルターEAは、
ボラティリティが低いときに新規エントリーを「見送る」フィルターでした。
本記事は逆に、ボラティリティが低い状態(収縮)そのものを、エントリーの「準備段階」
として積極的に使います。
| EA | 低ボラティリティの扱い | 役割 |
|---|---|---|
| 既刊ATRボラティリティフィルターEA | エントリーを見送る理由 | 他の判定ロジックへのフィルター |
| 本記事(iStdDevスクイーズEA) | エントリーの準備段階として記録する | 単独で発注判定まで行う起点 |
同じ「ボラティリティが低い」という状態でも、使い方の発想が異なります。
スクイーズ→ブレイクアウトという設計思想
・値動きが収縮している間は、エネルギーを溜めている状態とみなす
・収縮中の値幅(高値・安値)を記録する
・そこを抜けた瞬間を「解放」のタイミングとみなす
・いわゆる「スクイーズ」と呼ばれる考え方をiStdDevで実装したもの
スクイーズ判定とブレイクアウト判定のロジック
発注条件の土台になる、2段階の判定ロジックを確認します。
iStdDev関数のシグネチャ(既刊記事で確認済み)
double iStdDev( string symbol, // 通貨ペア名(NULLで現在のチャート) int timeframe, // 時間足(0で現在の時間足) int ma_period, // 移動平均の期間 int ma_shift, // 移動平均のシフト int ma_method, // 移動平均の計算方法(MODE_SMA等) int applied_price, // 適用価格(PRICE_CLOSE等) int shift // 何本前のバーの値を取得するか );
引数の構成は既刊istddev-mt4.mdが、
2026-10-30にMQL4公式リファレンスで直接確認済みのものと同じです。
直近N本の平均との比率でスクイーズを判定する関数
bool IsSqueeze(int shift)
{
double current = iStdDev(_Symbol, 0, InpStdDevPeriod, 0, MODE_SMA, PRICE_CLOSE, shift);
double sum = 0;
for(int j = shift + 1; j <= shift + InpAvgLookback; j++)
sum += iStdDev(_Symbol, 0, InpStdDevPeriod, 0, MODE_SMA, PRICE_CLOSE, j);
double avg = sum / InpAvgLookback;
if(avg <= 0)
return(false);
double ratio = current / avg;
return(ratio < InpSqueezeRatio);
}
既刊インジケーターと同じ比率の計算式を使い、基準値の何倍かではなく「何倍未満か」
でスクイーズを判定します。
スクイーズ中の高値・安値を記録してブレイクアウトを検知
スクイーズが続いている間、その期間の高値・安値を記録しておきます。
スクイーズが終わった瞬間に、その範囲をどちらに抜けたかで方向を決めます。
発注条件・損切り・ポジション重複の防止
判定だけでは発注にならないため、安全策を組み込みます。
スクイーズ終了とブレイク方向を同時に確認する
static bool squeezeActive = false;
static int squeezeBars = 0;
static double squeezeHigh = 0;
static double squeezeLow = 0;
static int breakoutSignal = 0;
void UpdateSqueezeState()
{
breakoutSignal = 0;
if(IsSqueeze(1))
{
if(!squeezeActive)
{
squeezeActive = true;
squeezeBars = 0;
squeezeHigh = High[1];
squeezeLow = Low[1];
}
squeezeHigh = MathMax(squeezeHigh, High[1]);
squeezeLow = MathMin(squeezeLow, Low[1]);
squeezeBars++;
return;
}
if(squeezeActive && squeezeBars >= InpMinSqueezeBars)
{
if(Close[1] > squeezeHigh)
breakoutSignal = 1;
else if(Close[1] < squeezeLow)
breakoutSignal = -1;
}
squeezeActive = false;
squeezeBars = 0;
}
スクイーズが一定本数(InpMinSqueezeBars)以上続いたあと、
確定済みの終値がスクイーズ中の高値・安値を抜けた瞬間だけを、
シグナルとして扱います。
一瞬だけの収縮をノイズとして除外するための条件です。
損切り・利確を必ず指定する
double sl = Ask - InpStopLossPips * point; double tp = Ask + InpTakeProfitPips * point; OrderSend(_Symbol, OP_BUY, InpLots, Ask, 3, sl, tp, "StdDevSqueezeBreakout", InpMagic, 0, clrBlue);
損切り(`sl`)・利確(`tp`)を発注時点で必ず指定し、未設定のまま放置しません。
ポジション重複を防ぐチェック
bool HasOpenPosition()
{
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) &&
OrderSymbol() == _Symbol && OrderMagicNumber() == InpMagic)
return(true);
}
return(false);
}
同じマジックナンバー・同じ通貨ペアでポジションが既にあるかを確認し、
あれば新規エントリーをスキップします。
実際に動くスクイーズブレイクアウトEAのコード全体
ここまでの要素をまとめた、EAの全体です。
//+------------------------------------------------------------------+
//| StdDevSqueezeBreakoutEA.mq4 |
//| Copyright 2026, FX-EA System Project Creator |
//| https://creator.fx-ea-system-project.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, FX-EA System Project Creator"
#property link "https://creator.fx-ea-system-project.com/"
#property version "1.00"
#property strict
input int InpStdDevPeriod = 20; //標準偏差の計算期間
input int InpAvgLookback = 100; //「普段」とみなす平均の参照本数
input double InpSqueezeRatio = 0.7; //普段の何倍未満で「収縮」と判定するか
input int InpMinSqueezeBars = 5; //収縮と認める最低連続本数
input double InpLots = 0.1; //発注ロット数
input int InpStopLossPips = 300; //損切り(pips)
input int InpTakeProfitPips = 600; //利確(pips)
input int InpMagic = 20261104; //マジックナンバー
static bool squeezeActive = false;
static int squeezeBars = 0;
static double squeezeHigh = 0;
static double squeezeLow = 0;
static int breakoutSignal = 0;
datetime lastBarTime = 0;
bool IsSqueeze(int shift)
{
double current = iStdDev(_Symbol, 0, InpStdDevPeriod, 0, MODE_SMA, PRICE_CLOSE, shift);
double sum = 0;
for(int j = shift + 1; j <= shift + InpAvgLookback; j++)
sum += iStdDev(_Symbol, 0, InpStdDevPeriod, 0, MODE_SMA, PRICE_CLOSE, j);
double avg = sum / InpAvgLookback;
if(avg <= 0)
return(false);
double ratio = current / avg;
return(ratio < InpSqueezeRatio);
}
void UpdateSqueezeState()
{
breakoutSignal = 0;
if(IsSqueeze(1))
{
if(!squeezeActive)
{
squeezeActive = true;
squeezeBars = 0;
squeezeHigh = High[1];
squeezeLow = Low[1];
}
squeezeHigh = MathMax(squeezeHigh, High[1]);
squeezeLow = MathMin(squeezeLow, Low[1]);
squeezeBars++;
return;
}
if(squeezeActive && squeezeBars >= InpMinSqueezeBars)
{
if(Close[1] > squeezeHigh)
breakoutSignal = 1;
else if(Close[1] < squeezeLow)
breakoutSignal = -1;
}
squeezeActive = false;
squeezeBars = 0;
}
bool HasOpenPosition()
{
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) &&
OrderSymbol() == _Symbol && OrderMagicNumber() == InpMagic)
return(true);
}
return(false);
}
void OnTick()
{
if(Time[0] == lastBarTime)
return;
lastBarTime = Time[0];
int needed = InpStdDevPeriod + InpAvgLookback + InpMinSqueezeBars + 2;
if(Bars < needed)
return;
UpdateSqueezeState();
if(HasOpenPosition())
return;
double point = _Point;
if(_Digits == 3 || _Digits == 5)
point *= 10;
if(breakoutSignal == 1)
{
double sl = Ask - InpStopLossPips * point;
double tp = Ask + InpTakeProfitPips * point;
int ticket = OrderSend(_Symbol, OP_BUY, InpLots, Ask, 3, sl, tp,
"StdDevSqueezeBreakout", InpMagic, 0, clrBlue);
if(ticket < 0)
Print("買い発注に失敗しました。エラーコード: ", GetLastError());
}
else if(breakoutSignal == -1)
{
double sl = Bid + InpStopLossPips * point;
double tp = Bid - InpTakeProfitPips * point;
int ticket = OrderSend(_Symbol, OP_SELL, InpLots, Bid, 3, sl, tp,
"StdDevSqueezeBreakout", InpMagic, 0, clrRed);
if(ticket < 0)
Print("売り発注に失敗しました。エラーコード: ", GetLastError());
}
}
スクイーズが一定本数続いたあと、記録した高値・安値を終値ベースで抜けた瞬間だけ、
新規エントリーが発生します。
MetaEditorでのコンパイル・バックテスト手順
実際に動かすまでの手順です。
- MetaEditorで新規Expert Advisor「StdDevSqueezeBreakoutEA」を作成し、上のコードを貼り付ける
- F7キー(またはコンパイルボタン)でコンパイルし、「0 error(s), 0 warning(s)」になることを確認する
- ストラテジーテスターでこのEAを選択し、任意の通貨ペア・期間でバックテストを実行する
- 「結果」タブで、収縮期間が続いたあとにエントリーが発生していることを確認する
- InpSqueezeRatio・InpMinSqueezeBarsを変えて、シグナルの頻度がどう変わるかを確認する
よくあるエラーと注意点
実際に運用する前に、確認しておきたい点です。
InpSqueezeRatioが厳しすぎてシグナルが出ない
・0.7は目安であり、通貨ペアや時間足によって「普段」の感覚が異なる
・シグナルが出ない場合は0.8〜0.9側に緩め、出すぎる場合は0.5〜0.6側に厳しくする
・InpMinSqueezeBarsを増やすほど収縮の確からしさは上がる
・ただしシグナル自体は減る
ブレイク直後の戻り(フェイク)で損切りに引っかかる
収縮後のブレイクアウトは、抜けた直後に戻ってくる「フェイクブレイク」
が発生することがあります。
このEA単体では戻りを予測できないため、
InpStopLossPipsを極端に狭くしすぎないことをおすすめします。
コンパイルエラーになる
`iStdDev()`の引数は7個すべて指定してください。
`ma_shift`や`applied_price`だけを省略することはできません。
よくある質問
標準偏差スクイーズブレイクアウトEAについて、よく検索される疑問に回答します。
・既刊iStdDevインジケーターを併用すれば、収縮の推移を目視でも確認できる
・InpStdDevPeriod・InpAvgLookbackは、
・通貨ペア・時間足でバックテストして調整する
・スクイーズ中の値幅が極端に狭い場合、ブレイク後の値幅も小さくなりやすい
既刊のATRボラティリティフィルターEAと一緒に使えますか?
はい。
既刊のATRボラティリティフィルターEAは「他の判定ロジック用のフィルター」
なので、本記事のEAとは役割が重なりません。
両方を別のマジックナンバーで同時運用することもできますが、
発注する戦略の総数は事前に把握しておいてください。
ボリンジャーバンドのスクイーズと何が違いますか?
考え方は近いですが、判定対象が異なります。
ボリンジャーバンドのスクイーズは「バンドの幅」で収縮を判定するのに対し、
本記事は「標準偏差の値そのもの」を直近平均と比較して収縮を判定します。
いずれも最終的には同じ標準偏差を参照していますが、見ている数値の形が違います。
既刊のバンドウォークEAと同時運用するとどうなりますか?
既刊のバンドウォークEAは「トレンドが続いている間」を狙う設計で、
本記事は「収縮から抜けた瞬間」だけを狙う設計です。
タイミングが重なることもありますが、マジックナンバーを分けて運用すれば、
どちらのエントリーかを区別できます。
【MQL4】iStdDevの標準偏差で「スクイーズ(収縮)」を検知し、ブレイクアウトでエントリーするEAの作り方(ATRフィルターEAとの違いも解説):まとめ
この記事では、既刊iStdDevインジケーターの比率計算を使い、
値動きの「収縮」を記録してブレイクアウトのエントリー条件に変換する方法を解説しました。
既刊ATRボラティリティフィルターEAが「見送るためのフィルター」
だったのに対し、本記事は「収縮そのものを起点にする」設計にすることで役割を分けています。
損切り必須・ポジション重複防止という最低限の安全策を備えた上で、
InpSqueezeRatio・InpMinSqueezeBarsの調整で、
感度を変えられる構成にしています。
コメント