EAを動かしていたら、

同じ条件で何度もエントリーしてしまい、

気づけば想定外のロット数を抱えていた、

という経験はありませんか。

この記事では、ポジション重複エントリーを防ぐ3つの書き方と、

それぞれの確実性の違い、

EA再起動時のリスクについて、実際に動くコード付きで解説します。

この記事で分かること
・ポジション重複が起きる典型的な原因
・OrdersTotal()ループでの確認方法(最も確実)
・新しいバー判定・フラグ変数という他2つの方式
・3つの方式の確実性・再起動時のリスクの比較

なぜポジションが重複してしまうのか

まず、重複エントリーが起きる典型的な原因を整理します。

OnTick()は1秒に何度も呼ばれる

OnTick()は新しい価格が来るたびに呼ばれ、

条件を満たす間はティックごとに何度も実行されます。

エントリー条件を満たす間、何も止めなければ連発する

「RSIが30以下ならエントリー」のような条件は、

RSIが30以下である間、

ティックのたびに条件を満たし続けます。

この記事で扱う範囲

この記事では、重複エントリーを防ぐ3つの実装方式と、

それぞれの確実性の違いを中心に扱います。

方式1: OrdersTotal()ループで保有状況を確認する

最も確実性が高い、標準的な方式を確認します。

関数の書式

int OrdersTotal();

OrdersTotal()は、

保有中・予約中の注文の合計数を返す関数です。

自分のEA・シンボルの注文だけを数える実装

bool HasOpenPosition(int magic, string symbol)
  {
   for(int i = 0; i < OrdersTotal(); i++)
     {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
        {
         if(OrderMagicNumber() == magic && OrderSymbol() == symbol)
           {
            return(true);
           }
        }
     }
   return(false);
  }

マジックナンバーとシンボルで絞り込むことで、

他のEAや他の通貨ペアの注文を誤って数えません。

この方式が最も確実な理由

確実性が高い理由
・実際の注文状況をその都度サーバー側のデータから確認する
・EA再起動・端末再起動があっても結果が変わらない
・変数の初期化忘れによる誤動作が起きない

方式2: 新しいバー判定でエントリー回数を制限する

バー単位でエントリー回数を制限する方式です。

新しいバーの判定方法

datetime lastBarTime = 0;

bool IsNewBar()
  {
   if(Time[0] != lastBarTime)
     {
      lastBarTime = Time[0];
      return(true);
     }
   return(false);
  }

最新バーの時刻が前回と変わったかどうかで、

新しいバーが来たかを判定します。

エントリー条件と組み合わせる

IsNewBar()がtrueの間だけエントリー条件を評価すれば、

同じバー内で何度もエントリーすることを防げます。

この方式の限界

1本のバー内で1回のエントリーは防げますが、

複数のバーをまたいでポジションが残っている場合、

重複を防ぐ保証にはなりません。

方式3: フラグ変数でエントリー済みを管理する

bool型の変数で状態を管理する方式です。

フラグ変数の実装

bool hasEntered = false;

void OnTick()
  {
   if(!hasEntered && /* エントリー条件 */ true)
     {
      // OrderSend(...)
      hasEntered = true;
     }
  }

エントリーした直後にフラグをtrueにし、

決済したタイミングでfalseに戻す設計です。

EA再起動でフラグが消えるリスク

注意
・hasEnteredはEA再起動時にfalseへ戻る
・実際にはポジションが残っていても重複発注してしまう
・グローバル変数(GlobalVariableSet)で永続化しない限りこのリスクは残る

この方式を使う場面

フラグ変数は、

OrdersTotal()ループと組み合わせた補助的な判定にのみ使い、

単独の重複防止策としては推奨しません。

実際に動くコード全体(3方式を組み合わせたEA)

最も確実なOrdersTotal()ループを基本に、

新しいバー判定を補助として使う実装です。

設計のポイント(確実な方式を基本にする)

OrdersTotal()ループを必ず通し、

新しいバー判定はティックの処理回数を減らす最適化として使います。

コード全文

//+------------------------------------------------------------------+
//|                                   DuplicatePositionPreventionEA.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 = 20261012;
input double LotSize     = 0.1;
input int    StopLossPips   = 300;
input int    TakeProfitPips = 600;

datetime lastBarTime = 0;

bool IsNewBar()
  {
   if(Time[0] != lastBarTime)
     {
      lastBarTime = Time[0];
      return(true);
     }
   return(false);
  }

bool HasOpenPosition(int magic, string symbol)
  {
   for(int i = 0; i < OrdersTotal(); i++)
     {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
        {
         if(OrderMagicNumber() == magic && OrderSymbol() == symbol)
           {
            return(true);
           }
        }
     }
   return(false);
  }

void OnTick()
  {
   if(!IsNewBar())
      return;

   if(HasOpenPosition(MagicNumber, Symbol()))
     {
      Comment("すでにポジションを保有中のため新規エントリーをスキップします");
      return;
     }

   double sl = Bid - StopLossPips * Point;
   double tp = Bid + TakeProfitPips * Point;

   int ticket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3, sl, tp,
                          "duplicate-check-ea", MagicNumber, 0, clrBlue);

   if(ticket < 0)
     {
      Print("OrderSendに失敗しました。エラーコード: ", GetLastError());
     }
  }

MetaEditorでのコンパイル・実行確認

MetaEditorで保存してコンパイルし、

エラーが0件であることを確認したうえで、

デモ口座で重複発注が起きないことを実際に確認してください。

3つの方式の比較

確実性と再起動時のリスクを表で比較します。

比較表

方式 確実性 EA再起動時の挙動
OrdersTotal()ループ 高い(サーバー側の実データ基準) 影響を受けない
新しいバー判定 中(バー単位のみ防止) 影響を受けない
フラグ変数 低(単独では不十分) falseに戻り誤発注の恐れ

基本はOrdersTotal()ループを必ず通す

新しいバー判定・フラグ変数は、

処理効率を上げる補助的な工夫として使い、

重複防止の最終判断は必ずOrdersTotal()ループに任せます。

既刊マジックナンバー記事との接続

複数のEAを同じ口座で動かす場合は、

既刊のマジックナンバー記事と組み合わせて、

自分のEAの注文だけを正しく数える設計にしてください。

よくある注意点

実装時につまずきやすい点をまとめます。

OrderSelect後の値の取得忘れ

OrderSelect()が成功したかを確認せずに、

OrderMagicNumber()等を呼び出すと、

意図しない値を参照してしまう場合があります。

決済済み注文(履行済み)を含めてしまう

MODE_TRADESを指定すれば、

保有中・予約中の注文だけを対象にできます。

MODE_HISTORYと混同しないよう注意してください。

両建てを許可する場合の考慮

本記事のHasOpenPosition()は、

同じシンボルの保有有無だけを見る設計です。

買いと売りを両方許可したいEAでは、

OP_BUY・OP_SELLそれぞれの保有数を分けて数える必要があります。

よくある質問

ポジション重複防止について、よくある疑問に回答します。

複数ポジションをあえて持ちたい場合はどうしますか?

HasOpenPosition()をそのまま使うと1件でも保有中なら止まるため、

複数ポジションを許可する場合は、

保有数の上限値を比較する条件に変更してください。

OrdersTotal()は重いですか?

保有注文数が極端に多くない限り、

1ティックごとのループ処理は実用上問題にならない負荷です。

新しいバー判定だけでは本当に不十分ですか?

はい。EAを再起動したタイミングで、

既にポジションを保有している状態から再開すると、

新しいバー判定だけでは重複を防げません。

【MQL4】ポジション重複エントリーを防ぐ3つの書き方について解説してみた(確実性で比較):まとめ

ポジションの重複エントリーは、

OnTick()がティックごとに何度も呼ばれる仕組みを踏まえずにEAを書くと、

誰でも起こしてしまう典型的なミスです。

新しいバー判定・フラグ変数はそれぞれ便利な工夫ですが、

単独では確実な防止策にはなりません。

OrdersTotal()ループで実際の保有状況を毎回確認する方式を基本に据え、

他の2つを補助として組み合わせることをおすすめします。

関連記事