前回の記事では、パラボリックSARの反転だけを矢印表示するインジケーターを作りましたが、
まとめで「矢印は売買命令ではなく環境を再確認する合図として使う」
と書きました。
SARにはもう1つ、本来の用途である「損切りラインとして使う」という使い方があります。
SARは価格が進むほど点が価格に近づいていく性質を持つため、
含み益が伸びるほど損切りラインも自動的に近づけていく「トレーリングストップ」
の材料として設計されています。
この記事では、SARの反転をエントリーの合図に使い、
エントリー後は同じSARの値を使って損切りラインを動かし続けるEAを作ります。
設計思想: SARの値をそのままOrderModifyに渡さない
もっとも単純な実装は、毎ティックSARの現在値を取得し、
そのままOrderModify()の損切り価格に渡すことです。
しかしSARは価格に張り付く指標である一方、一時的な押し戻しの局面では、
直前の値より不利な位置(価格から遠い側)に一瞬だけ動くことがあります。
このとき無条件にSARの値を損切りに設定すると、含み益を守るはずのトレーリングストップが、
かえって損切りラインを価格から遠ざけてしまう事故が起きます。
この記事のEAは、新しく計算したSARの値が「今の損切りより有利な位置」
にあるときだけOrderModify()を呼び、不利な位置には絶対に動かさない、
というガード条件を必ず通します。
エントリー条件: 前回記事のSAR反転判定をそのまま使う
エントリーの判定は、前回の記事で作ったインジケーターと同じ考え方です。
確定足(1本前)で、SARが価格の上から下(買い転換)、
または下から上(売り転換)に切り替わった瞬間だけをエントリーの合図にします。
bool SarFlippedBullish(int shift)
{
double sarNow = iSAR(NULL,0,SarStep,SarMaximum,shift);
double sarPrev = iSAR(NULL,0,SarStep,SarMaximum,shift+1);
double closeNow = iClose(NULL,0,shift);
double closePrev = iClose(NULL,0,shift+1);
return(sarPrev > closePrev && sarNow < closeNow);
}
bool SarFlippedBearish(int shift)
{
double sarNow = iSAR(NULL,0,SarStep,SarMaximum,shift);
double sarPrev = iSAR(NULL,0,SarStep,SarMaximum,shift+1);
double closeNow = iClose(NULL,0,shift);
double closePrev = iClose(NULL,0,shift+1);
return(sarPrev < closePrev && sarNow > closeNow);
}
エントリー直後の初期損切りには、転換した足のSAR値をそのまま使います。
買い転換の足のSARは価格の下側にあるため、
そのまま損切りラインとして自然な位置になります。
トレーリング関数: 近づける方向にしか動かさない
保有中のポジションは、毎ティック形成中の足のSAR値(shift=0)を確認し、
「今の損切りより有利な位置」かつ「ブローカーの最小ストップ距離を満たしている」
の両方を満たしたときだけ損切りを更新します。
void TrailStopWithSAR(int ticket)
{
if(!OrderSelect(ticket,SELECT_BY_TICKET)) return;
double sarLive = iSAR(NULL,0,SarStep,SarMaximum,0);//形成中の足のSAR(常に更新される)
double minDistance = MarketInfo(Symbol(),MODE_STOPLEVEL)*Point;//ブローカーの最小ストップ距離
if(OrderType()==OP_BUY)
{
bool improves = (sarLive > OrderStopLoss());//買いは損切りを上げる方向だけ許可
bool farEnough = (Bid - sarLive >= minDistance);
if(improves && farEnough)
if(!OrderModify(ticket,OrderOpenPrice(),sarLive,OrderTakeProfit(),0,clrLimeGreen))
Print("買いポジションのSL更新に失敗しました。エラーコード=",GetLastError());
}
if(OrderType()==OP_SELL)
{
bool improves = (sarLive < OrderStopLoss());//売りは損切りを下げる方向だけ許可
bool farEnough = (sarLive - Ask >= minDistance);
if(improves && farEnough)
if(!OrderModify(ticket,OrderOpenPrice(),sarLive,OrderTakeProfit(),0,clrTomato))
Print("売りポジションのSL更新に失敗しました。エラーコード=",GetLastError());
}
}
`MarketInfo(Symbol(),MODE_STOPLEVEL)`は、
ブローカーが許可する「現在値から損切りラインまでの最小距離」
をポイント単位で返します。
これを確認せずにOrderModify()を呼ぶと、
gc-dc.mdで解説したエラー130(Invalid stops)と、
同じ原因で更新が拒否されることがあります。
実際に動くコード全体
SARの反転でエントリーし、以降は同じSARの値でトレーリングストップを行う、
実際にMetaEditorでコンパイルできるEAです。
//+------------------------------------------------------------------+
//| SarTrailingStopEA.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 MagicNumber = 45678;//マジックナンバー
input double Lots = 0.01;//ロット数
input int TakeProfit = 100;//利確(pips、0で利確なし)
input int Slippage = 3;//スリッページ
input double SarStep = 0.02;//SARの加速係数の増分
input double SarMaximum = 0.2;//SARの加速係数の最大値
//+------------------------------------------------------------------+
//| このEA(マジックナンバー)の保有チケットを返す(なければ-1) |
//+------------------------------------------------------------------+
int GetMyTicket()
{
for(int i=OrdersTotal()-1; i>=0; i--)
{
if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue;
if(OrderSymbol()==Symbol() && OrderMagicNumber()==MagicNumber)
return(OrderTicket());
}
return(-1);
}
//+------------------------------------------------------------------+
//| shift本前の足でSARが上昇転換したか(価格の上→下へ移動) |
//+------------------------------------------------------------------+
bool SarFlippedBullish(int shift)
{
double sarNow = iSAR(NULL,0,SarStep,SarMaximum,shift);
double sarPrev = iSAR(NULL,0,SarStep,SarMaximum,shift+1);
double closeNow = iClose(NULL,0,shift);
double closePrev = iClose(NULL,0,shift+1);
return(sarPrev > closePrev && sarNow < closeNow);
}
//+------------------------------------------------------------------+
//| shift本前の足でSARが下降転換したか(価格の下→上へ移動) |
//+------------------------------------------------------------------+
bool SarFlippedBearish(int shift)
{
double sarNow = iSAR(NULL,0,SarStep,SarMaximum,shift);
double sarPrev = iSAR(NULL,0,SarStep,SarMaximum,shift+1);
double closeNow = iClose(NULL,0,shift);
double closePrev = iClose(NULL,0,shift+1);
return(sarPrev < closePrev && sarNow > closeNow);
}
//+------------------------------------------------------------------+
//| 保有中のポジションをSARの現在値でトレーリングする(近づける方向のみ) |
//+------------------------------------------------------------------+
void TrailStopWithSAR(int ticket)
{
if(!OrderSelect(ticket,SELECT_BY_TICKET)) return;
double sarLive = iSAR(NULL,0,SarStep,SarMaximum,0);
double minDistance = MarketInfo(Symbol(),MODE_STOPLEVEL)*Point;
if(OrderType()==OP_BUY)
{
bool improves = (sarLive > OrderStopLoss());
bool farEnough = (Bid - sarLive >= minDistance);
if(improves && farEnough)
if(!OrderModify(ticket,OrderOpenPrice(),sarLive,OrderTakeProfit(),0,clrLimeGreen))
Print("買いポジションのSL更新に失敗しました。エラーコード=",GetLastError());
}
if(OrderType()==OP_SELL)
{
bool improves = (sarLive < OrderStopLoss());
bool farEnough = (sarLive - Ask >= minDistance);
if(improves && farEnough)
if(!OrderModify(ticket,OrderOpenPrice(),sarLive,OrderTakeProfit(),0,clrTomato))
Print("売りポジションのSL更新に失敗しました。エラーコード=",GetLastError());
}
}
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
int ticket = GetMyTicket();
if(ticket < 0)
{
int shift = 1;//直近の確定足で判定する
if(SarFlippedBullish(shift))
{
double sl = iSAR(NULL,0,SarStep,SarMaximum,shift);
double tp = (TakeProfit>0) ? Ask+TakeProfit*Point*10 : 0;
int result = OrderSend(Symbol(),OP_BUY,Lots,Ask,Slippage,sl,tp,"SAR Trail Buy",MagicNumber,0,Blue);
if(result < 0)
Print("買い注文に失敗しました。エラーコード=",GetLastError());
}
if(SarFlippedBearish(shift))
{
double sl = iSAR(NULL,0,SarStep,SarMaximum,shift);
double tp = (TakeProfit>0) ? Bid-TakeProfit*Point*10 : 0;
int result = OrderSend(Symbol(),OP_SELL,Lots,Bid,Slippage,sl,tp,"SAR Trail Sell",MagicNumber,0,Red);
if(result < 0)
Print("売り注文に失敗しました。エラーコード=",GetLastError());
}
}
else
{
TrailStopWithSAR(ticket);
}
}
//+------------------------------------------------------------------+
MetaEditorで実際に動かす手順
- MT4のMetaEditorで「ファイル」→「新規作成」→「Expert Advisor(テンプレートを使用)」を選び、名前を「SarTrailingStopEA」にして作成する
- テンプレートの中身を全て削除し、上のコードを丸ごと貼り付ける
- F7キー(またはコンパイルボタン)でコンパイルし、「0 error(s), 0 warning(s)」になることを確認する
- デモ口座のチャートへドラッグ&ドロップし、ストラテジーテスターでSARの反転タイミングでエントリーし、含み益が伸びるほど損切りラインが価格に近づいていく様子を確認する
この設計の限界
このEAは、レンジ相場でSARが頻繁に反転する局面では、
エントリーと損切りが短時間で繰り返され、
スプレッドとスリッページのコストが積み重なりやすくなります。
損切りを「近づける」方向にしか動かさないガードは含み益を守る一方で、
エントリー直後にSARが逆行方向へ一瞬動いただけでも損切りは全く動かないため、
トレンドの初動でSARが不安定な期間は、
通常より損切り幅が広いまま保有する時間が生まれます。
また、このコードは1通貨ペア・1ポジションのみを想定しており、
複数ポジションを同時に持つ「両建て」や「ピラミッディング」には対応していません。
意図通りに動かないときに確認すること
- エントリー直後にOrderModifyが失敗する: 転換した足のSAR値が、発注時点の現在値からブローカーの最小ストップ距離(MODE_STOPLEVEL)より近い位置にある可能性がある。エラーコードが130(Invalid stops)の場合はこれが原因
- 損切りが全く動かない: `improves`の条件は「今の損切りより有利な位置」だけを許可しているため、SARが不利な位置に動いた局面では意図通り更新されない(これは事故ではなく設計通りの挙動)
- OrderModifyが短時間に連続で失敗する: MT4には「フリーズレベル」という、現在値に近すぎる注文の変更そのものを一時的に拒否する仕組みがあるブローカーもある。連続失敗が続く場合はブローカーの仕様を確認する
- バックテストと実運用でトレーリングのタイミングがずれる: `iSAR(NULL,0,SarStep,SarMaximum,0)`は形成中の足の値を毎ティック取得するため、ティックの発生頻度(実運用とテスターのモデル)によって更新回数自体が変わる
まとめ
パラボリックSARは、エントリーの合図だけでなく、
含み益が伸びるほど損切りラインを自動的に近づけていく、
トレーリングストップの材料としても使えます。
ただしSARの値をそのままOrderModify()に渡すと、
一時的な押し戻しで損切りが不利な位置へ動く事故が起きるため、
「今より有利な位置かどうか」を必ず確認してから更新するガード条件が欠かせません。
ブローカーの最小ストップ距離のチェックも忘れず、
デモ口座で挙動を確認してから実運用を検討してください。
コメント