既刊のiEnvelopes(エンベロープ)インジケーターの記事では、
上下バンドへのタッチを矢印で知らせる設計を解説し、
FAQで「タッチ判定を関数化すれば逆張りEAの条件に組み込める」と予告していました。
この記事では、その予告を実際に動くコードで実装します。
・既刊バンドウォークEAとの『逆張りか、順張りか』という違い
・バンドタッチを逆張りの発注条件に変換する方法
・決済を固定pipsではなく移動平均への到達で行う設計
・損切り必須・ポジション重複防止の書き方
・実際に動くEAのコード全体
なぜ「タッチからの反発」を狙うのか
まず、既刊バンド系EAと本記事の違いを整理します。
既刊iEnvelopesインジケーターのおさらい
既刊インジケーターは、移動平均から固定%(deviation)だけ離れた、
上下バンドに価格がタッチした瞬間を矢印で知らせる設計でした。
バンド幅は標準偏差を使わず、常に同じ%の固定幅です。
既刊バンドウォークEAとの2つの違い
既刊のバンドウォークEAは、ボリンジャーバンド(標準偏差ベース)の外側を、
終値が連続N本「沿って動く」間だけ、トレンドが続くとみなして順張りする設計でした。
| EA | バンドの種類 | タッチ・ウォークの扱い | 方向 |
|---|---|---|---|
| 既刊バンドウォークEA | 標準偏差ベース(可変幅) | 外側に居続けたらトレンド継続 | 順張り |
| 本記事(iEnvelopesリバウンドEA) | 移動平均からの固定% | タッチした瞬間を行き過ぎとみなす | 逆張り |
同じ「バンドの外側」という状況でも、
標準偏差ベースのバンドでは「トレンドの強さの証拠」、
固定%のバンドでは「行き過ぎの基準」として、扱いを変えています。
リバウンド(反発)という設計思想
・固定%バンドは、相場の荒れ具合に関係なく常に同じ基準で行き過ぎを測る
・行き過ぎたあとは移動平均へ戻りやすい、という前提に立つ逆張り手法
・強いトレンド中はバンドに張り付いたまま戻らないこともあるため、万能ではない
タッチ判定と決済条件のロジック
発注と決済、2つの判定ロジックを確認します。
iEnvelopes関数のシグネチャ(既刊記事で確認済み)
double iEnvelopes( string symbol, // 通貨ペア名(NULLで現在のチャート) int timeframe, // 時間足(0で現在の時間足) int ma_period, // 移動平均の期間 int ma_method, // 移動平均の計算方法(MODE_SMA等) int ma_shift, // 移動平均のシフト int applied_price, // 適用価格(PRICE_CLOSE等) double deviation, // 移動平均からの逸脱率(%) int mode, // MODE_MAIN(中心線)/MODE_UPPER(上)/MODE_LOWER(下) int shift // 何本前のバーの値を取得するか );
引数の構成は既刊ienvelopes-mt4.mdが、
2026-10-31にMQL4公式リファレンスで直接確認済みのものと同じです。
確定済みのバーでタッチを判定する関数
int GetTouchSignal(int shift)
{
double upper = iEnvelopes(_Symbol, 0, InpMaPeriod, MODE_SMA, 0, PRICE_CLOSE, InpDeviation, MODE_UPPER, shift);
double lower = iEnvelopes(_Symbol, 0, InpMaPeriod, MODE_SMA, 0, PRICE_CLOSE, InpDeviation, MODE_LOWER, shift);
if(High[shift] >= upper)
return(1);
if(Low[shift] <= lower)
return(-1);
return(0);
}
上バンドにタッチしたら「売り(行き過ぎの反発を狙う)」、
下バンドにタッチしたら「買い」とみなします。
移動平均への到達で決済する関数
iEnvelopesはMODE_MAINを指定すると、
中心線(移動平均そのもの)の値も取得できます。
反発が目標まで届いたとみなし、
このMODE_MAINの値に到達した時点で決済します。
発注条件・決済・ポジション重複の防止
判定だけでは発注にならないため、安全策を組み込みます。
シグナルの”瞬間”だけを検知する
bool SellSignal()
{
return(GetTouchSignal(1) == 1 && GetTouchSignal(2) != 1);
}
bool BuySignal()
{
return(GetTouchSignal(1) == -1 && GetTouchSignal(2) != -1);
}
確定済みの1本前(shift=1)でタッチしていて、
2本前(shift=2)ではまだタッチしていなかった場合だけを、
エントリーの条件にします。
損切りはバンドのさらに外側に置く
double upper1 = iEnvelopes(_Symbol, 0, InpMaPeriod, MODE_SMA, 0, PRICE_CLOSE, InpDeviation, MODE_UPPER, 1); double sl = upper1 + InpStopLossBufferPips * point; OrderSend(_Symbol, OP_SELL, InpLots, Bid, 3, sl, 0, "EnvelopesRebound", InpMagic, 0, clrRed);
反発を狙う逆張りのため、
損切りはタッチしたバンドの値から、さらに外側へ一定pips離した位置に置きます。
利確(tp)はあえて指定せず、次に説明するMODE_MAINへの到達で決済します。
移動平均に到達したら早期決済する
void CloseIfReachedMA()
{
double ma = iEnvelopes(_Symbol, 0, InpMaPeriod, MODE_SMA, 0, PRICE_CLOSE, InpDeviation, MODE_MAIN, 0);
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
continue;
if(OrderSymbol() != _Symbol || OrderMagicNumber() != InpMagic)
continue;
if(OrderType() == OP_SELL && Bid <= ma)
OrderClose(OrderTicket(), OrderLots(), Ask, 3, clrRed);
else if(OrderType() == OP_BUY && Bid >= ma)
OrderClose(OrderTicket(), OrderLots(), Bid, 3, clrBlue);
}
}
移動平均に戻ってきた瞬間に、
利確目標を固定pipsで決めずに決済します。
ポジション重複を防ぐチェック
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の全体です。
//+------------------------------------------------------------------+
//| EnvelopesReboundEA.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 InpMaPeriod = 20; //移動平均の期間
input double InpDeviation = 0.2; //移動平均からの逸脱率(%)
input double InpLots = 0.1; //発注ロット数
input int InpStopLossBufferPips = 150; //バンドからさらに離す損切り幅(pips)
input int InpMagic = 20261104; //マジックナンバー
datetime lastBarTime = 0;
int GetTouchSignal(int shift)
{
double upper = iEnvelopes(_Symbol, 0, InpMaPeriod, MODE_SMA, 0, PRICE_CLOSE, InpDeviation, MODE_UPPER, shift);
double lower = iEnvelopes(_Symbol, 0, InpMaPeriod, MODE_SMA, 0, PRICE_CLOSE, InpDeviation, MODE_LOWER, shift);
if(High[shift] >= upper)
return(1);
if(Low[shift] <= lower)
return(-1);
return(0);
}
bool SellSignal()
{
return(GetTouchSignal(1) == 1 && GetTouchSignal(2) != 1);
}
bool BuySignal()
{
return(GetTouchSignal(1) == -1 && GetTouchSignal(2) != -1);
}
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 CloseIfReachedMA()
{
double ma = iEnvelopes(_Symbol, 0, InpMaPeriod, MODE_SMA, 0, PRICE_CLOSE, InpDeviation, MODE_MAIN, 0);
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
continue;
if(OrderSymbol() != _Symbol || OrderMagicNumber() != InpMagic)
continue;
if(OrderType() == OP_SELL && Bid <= ma)
OrderClose(OrderTicket(), OrderLots(), Ask, 3, clrRed);
else if(OrderType() == OP_BUY && Bid >= ma)
OrderClose(OrderTicket(), OrderLots(), Bid, 3, clrBlue);
}
}
void OnTick()
{
CloseIfReachedMA();
if(Time[0] == lastBarTime)
return;
lastBarTime = Time[0];
if(HasOpenPosition())
return;
double point = _Point;
if(_Digits == 3 || _Digits == 5)
point *= 10;
if(SellSignal())
{
double upper1 = iEnvelopes(_Symbol, 0, InpMaPeriod, MODE_SMA, 0, PRICE_CLOSE, InpDeviation, MODE_UPPER, 1);
double sl = upper1 + InpStopLossBufferPips * point;
int ticket = OrderSend(_Symbol, OP_SELL, InpLots, Bid, 3, sl, 0,
"EnvelopesRebound", InpMagic, 0, clrRed);
if(ticket < 0)
Print("売り発注に失敗しました。エラーコード: ", GetLastError());
}
else if(BuySignal())
{
double lower1 = iEnvelopes(_Symbol, 0, InpMaPeriod, MODE_SMA, 0, PRICE_CLOSE, InpDeviation, MODE_LOWER, 1);
double sl = lower1 - InpStopLossBufferPips * point;
int ticket = OrderSend(_Symbol, OP_BUY, InpLots, Ask, 3, sl, 0,
"EnvelopesRebound", InpMagic, 0, clrBlue);
if(ticket < 0)
Print("買い発注に失敗しました。エラーコード: ", GetLastError());
}
}
バンドにタッチした瞬間に逆方向でエントリーし、
移動平均に戻ってきた時点で決済します。
MetaEditorでのコンパイル・バックテスト手順
実際に動かすまでの手順です。
- MetaEditorで新規Expert Advisor「EnvelopesReboundEA」を作成し、上のコードを貼り付ける
- F7キー(またはコンパイルボタン)でコンパイルし、「0 error(s), 0 warning(s)」になることを確認する
- ストラテジーテスターでこのEAを選択し、任意の通貨ペア・期間でバックテストを実行する
- 「結果」タブで、バンドタッチの直後にエントリーし、移動平均到達で決済していることを確認する
- InpDeviation・InpStopLossBufferPipsを変えて、エントリー頻度と損切りの余裕がどう変わるかを確認する
よくあるエラーと注意点
実際に運用する前に、確認しておきたい点です。
強いトレンド中に損切りが連続する
・強いトレンドが出ると、価格がバンドに張り付いたまま戻らないことがある
・この場合、逆張りの損切りが連続しやすい(逆張り手法に共通する弱点)
・既刊バンドウォークEAのような順張り系との同時運用で、傾向を補い合う考え方もある
利確が固定されていないため予測しづらい
本記事のEAは利確(tp)をあえて指定していません。
バックテストのレポート上、利確額が毎回変わって見えるのは、
決済条件が「移動平均への到達」という仕様によるものです。
コンパイルエラーになる
`iEnvelopes()`の引数は9個すべて指定してください。
`mode`だけを省略することはできません。
よくある質問
エンベロープリバウンドEAについて、よく検索される疑問に回答します。
・既刊iEnvelopesインジケーターを併用すれば、タッチの推移を目視でも確認できる
・InpDeviationは、通貨ペア・時間足でバックテストして調整する
・強いトレンドが続く通貨ペアでは、InpStopLossBufferPipsを広めに取る
既刊バンドウォークEAと同時運用できますか?
できます。
既刊バンドウォークEAは「トレンドが続く間」を順張りで狙う設計、
本記事は「行き過ぎから戻る瞬間」を逆張りで狙う設計です。
方向が逆になる場面もあるため、
マジックナンバーを分けて、どちらのエントリーかを区別して運用してください。
なぜ利確を固定pipsにしないのですか?
反発を狙う逆張りは、
「どこまで戻るか」が毎回変わるという前提に立っているためです。
固定pipsの利確では、
反発が小さいときに利確できず、反発が大きいときに早すぎる決済になります。
移動平均という相場に応じて動く目標にすることで、
この問題を避けています。
既刊のバンドタッチインジケーターと同時に使う必要がありますか?
必須ではありません。
このEAは判定ロジックを内部に持っているため単独で動作しますが、
目視で経過を確認したい場合は既刊インジケーターを併用してください。
【MQL4】iEnvelopesの上下バンドにタッチしたら「反発(リバウンド)」を狙う逆張りEAの作り方(バンドウォークEAとの違いも解説):まとめ
この記事では、既刊iEnvelopesインジケーターのタッチ判定を使い、
バンドにタッチした瞬間に反発を狙う逆張りEAを解説しました。
既刊バンドウォークEAが標準偏差ベースのバンドで順張りを続ける設計だったのに対し、
本記事は固定%バンドへのタッチを行き過ぎとみなし、逆方向にエントリーする設計にしています。
利確を固定pipsにせず、移動平均への到達で決済することで、
反発の大きさに応じて柔軟に利益を確定できる構成にしています。
コメント