【MQL4関数】OrdersTotal関数の使い方!保有ポジション数の取得のやり方

【中級編】MQLプログラムの読み方・書き方
朝日奈りさ
朝日奈りさ

プログラムで保有ポジション数を取得するには、OrdersTotal関数を使うよ!

OrdersTotal関数とは

自動売買でトレードするとき、保有ポジション数をチェックしたいですよね!

そのときに OrdersTotal関数を使いましょう。

 

OrdersTotal関数とは、保有ポジションの数を返します。

例えば、ポジション数が2つなら、OrdersTotal関数は2を返します。

 

返り値は保有ポジション数で、引数は何も必要ありません。

返り値は int型なので、基本的な書き方は以下の通りです。

int positionNum = OrdersTotal();

 

上記のプログラムを書くことで、positionNum に保有ポジション数が代入されます。

 

OrdersTotal関数の使用方法と注意点

OrdersTotal関数は、保有ポジション数を返します。

そのため、ポジションの情報をチェックするための for文でよく使われます。

具体的には以下の通りです。

//保有ポジションをチェック

for(int i = OrdersTotal() - 1; i >= 0; i--){
    //ここにポジションビの情報を1つ1つチェックするためのプログラムを書く

}

 

ここでのポイントは OrdersTotal関数ではなく、「i–」の部分です。

for文を「i++」で書くこともできるのですが、「i++」だとエラーが起こる可能性があります。

「i++」で書いた例がこちらです。

//エラーが起こる可能性がある例

for(int i = 0; i <= OrdersTotal()-1; i++){
    //ここにポジションの情報を1つ1つチェックするためのプログラムを書く

}

 

なぜエラーが起こる可能性があるのかというと、for文の中で配列を使っている場合、存在しない配列を参照してしまうからです。

例えば、hairetsu[3] という配列を使っている場合、for文なので hairetsu[i] と書いていると思います。

そのとき、i は「0→1→2」という順に変化します。

そして、for文が途中の「0→1」の段階でポジションが決済された場合を考えます。

ポジションが決済された場合、「2」で参照されるはずだった hairetsu[2] が存在しないものになっているので、エラーが出ます。

そのため、「2→1→0」の順番で参照したほうが、エラーが出ないので、トラブルがなくなるでしょう。

 

配列をマスターしていない方は、こちらの記事が参考になります。

 

OrdersTotal関数の実用的なプログラム例

ここからは、OrdersTotal関数を実際のEAでどのように活用するか、具体的なプログラム例を紹介します。

 

例1:最大ポジション数を制限して新規注文を出すEA

EAを作るとき、「同時に保有するポジションを○個までに制限したい」という場面は非常に多いです。OrdersTotal関数を使えば、現在のポジション数を確認してから注文を出すことができます。

// --- 入力パラメータ ---
input int MaxPositions = 3;       // 最大保有ポジション数
input double LotSize   = 0.1;    // 注文ロット数
input int MagicNumber  = 12345;  // マジックナンバー

void OnTick()
{
    // 現在の自分のEAが保有しているポジション数をカウント
    int myPositions = 0;
    for(int i = OrdersTotal() - 1; i >= 0; i--)
    {
        // ポジション情報を選択
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
        {
            // マジックナンバーと通貨ペアが一致するものだけカウント
            if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol())
            {
                myPositions++;
            }
        }
    }

    // 最大ポジション数に達していなければ新規注文
    if(myPositions < MaxPositions)
    {
        // 例:簡単な買い条件(移動平均のゴールデンクロス)
        double maFast = iMA(Symbol(), 0, 5, 0, MODE_SMA, PRICE_CLOSE, 1);
        double maSlow = iMA(Symbol(), 0, 25, 0, MODE_SMA, PRICE_CLOSE, 1);
        double maFastPrev = iMA(Symbol(), 0, 5, 0, MODE_SMA, PRICE_CLOSE, 2);
        double maSlowPrev = iMA(Symbol(), 0, 25, 0, MODE_SMA, PRICE_CLOSE, 2);

        // ゴールデンクロスが発生したら買い注文
        if(maFastPrev < maSlowPrev && maFast > maSlow)
        {
            int ticket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3, 0, 0,
                                   "MaxPos制限EA", MagicNumber, 0, clrBlue);
            if(ticket < 0)
                Print("注文エラー: ", GetLastError());
            else
                Print("買い注文成功! チケット番号: ", ticket);
        }
    }
    else
    {
        // 最大ポジション数に達している場合は何もしない
        // Print("最大ポジション数に達しています: ", myPositions, " / ", MaxPositions);
    }
}

ポイントは、OrdersTotal関数でループしながらマジックナンバーと通貨ペアでフィルタリングしている点です。他のEAのポジションを誤ってカウントしないようにしましょう。

 

例2:保有中の全ポジションを一括決済する関数

特定の条件(例えば、合計利益が目標額に達した場合など)で、保有ポジションをすべて決済したいことがあります。以下は、自分のEAのポジションだけを一括で決済する関数です。

input int MagicNumber = 12345;  // マジックナンバー

//+------------------------------------------------------------------+
//| 自分のEAの全ポジションを一括決済する関数                          |
//+------------------------------------------------------------------+
void CloseAllMyPositions()
{
    // OrdersTotal()を使って全ポジションを逆順にチェック
    for(int i = OrdersTotal() - 1; i >= 0; i--)
    {
        // ポジション情報を選択
        if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
            continue;  // 選択に失敗したらスキップ

        // マジックナンバーと通貨ペアが一致するかチェック
        if(OrderMagicNumber() != MagicNumber)
            continue;  // 違うEAのポジションはスキップ
        if(OrderSymbol() != Symbol())
            continue;  // 違う通貨ペアのポジションはスキップ

        bool result = false;

        // 買いポジションの場合
        if(OrderType() == OP_BUY)
        {
            result = OrderClose(OrderTicket(), OrderLots(), Bid, 3, clrRed);
        }
        // 売りポジションの場合
        else if(OrderType() == OP_SELL)
        {
            result = OrderClose(OrderTicket(), OrderLots(), Ask, 3, clrBlue);
        }

        // 決済結果をログに出力
        if(result)
            Print("決済成功: チケット番号 ", OrderTicket());
        else
            Print("決済失敗: チケット番号 ", OrderTicket(), " エラー: ", GetLastError());
    }
}

//+------------------------------------------------------------------+
//| OnTick関数での使用例                                              |
//+------------------------------------------------------------------+
void OnTick()
{
    // 例:保有ポジションの合計利益が1万円を超えたら全決済
    double totalProfit = 0;

    for(int i = OrdersTotal() - 1; i >= 0; i--)
    {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
        {
            if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol())
            {
                // 各ポジションの損益を合算
                totalProfit += OrderProfit() + OrderSwap() + OrderCommission();
            }
        }
    }

    // 合計利益が目標に達したら全ポジション決済
    if(totalProfit >= 10000)
    {
        Print("目標利益達成!合計利益: ", totalProfit, " 円 → 全決済実行");
        CloseAllMyPositions();
    }
}

逆順(i--)でループしているのが重要です。決済するとポジション数が減るので、順方向でループするとインデックスがずれてしまいます。

 

例3:ポジション数をチャート上に表示するインジケーター

インジケーターとしてOrdersTotal関数を使い、現在の保有ポジション数や合計損益をチャート上にリアルタイム表示する例です。裁量トレードの補助ツールとしても便利です。

#property indicator_chart_window  // チャートウィンドウに表示
#property strict

//+------------------------------------------------------------------+
//| 初期化関数                                                        |
//+------------------------------------------------------------------+
int OnInit()
{
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| 終了時にオブジェクトを削除                                        |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    ObjectDelete("PosInfoLabel");  // ラベルオブジェクトを削除
}

//+------------------------------------------------------------------+
//| ティックごとに情報を更新                                          |
//+------------------------------------------------------------------+
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 totalPositions = OrdersTotal();

    // 現在の通貨ペアのポジションだけをカウント&損益を合算
    int symbolPositions = 0;
    double symbolProfit = 0;
    int buyCount  = 0;   // 買いポジション数
    int sellCount = 0;   // 売りポジション数

    for(int i = totalPositions - 1; i >= 0; i--)
    {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
        {
            if(OrderSymbol() == Symbol())
            {
                symbolPositions++;
                symbolProfit += OrderProfit() + OrderSwap() + OrderCommission();

                // 買い・売りを別々にカウント
                if(OrderType() == OP_BUY)  buyCount++;
                if(OrderType() == OP_SELL) sellCount++;
            }
        }
    }

    // 表示用のテキストを作成
    string displayText = StringFormat(
        "全ポジション: %d | %s: %d (買:%d / 売:%d) | 損益: %.0f円",
        totalPositions, Symbol(), symbolPositions, buyCount, sellCount, symbolProfit
    );

    // チャート上にラベルとして表示
    if(ObjectFind("PosInfoLabel") < 0)
    {
        // オブジェクトが存在しなければ新規作成
        ObjectCreate("PosInfoLabel", OBJ_LABEL, 0, 0, 0);
        ObjectSet("PosInfoLabel", OBJPROP_CORNER, 0);    // 左上に配置
        ObjectSet("PosInfoLabel", OBJPROP_XDISTANCE, 10);
        ObjectSet("PosInfoLabel", OBJPROP_YDISTANCE, 30);
        ObjectSetInteger(0, "PosInfoLabel", OBJPROP_FONTSIZE, 11);
    }

    // テキストと色を更新(利益ならブルー、損失ならレッド)
    ObjectSetText("PosInfoLabel", displayText, 11, "Arial",
                  symbolProfit >= 0 ? clrDodgerBlue : clrRed);

    return(rates_total);
}

このインジケーターをチャートに適用すると、左上に「全ポジション数」「現在の通貨ペアのポジション数(買い/売り内訳)」「損益」がリアルタイムで表示されます。

 

例4:ポジションがゼロのときだけ新規エントリーするシンプルEA

初心者の方がEAを作るとき、まずは「1ポジションだけ保有する」シンプルな設計から始めるのがおすすめです。OrdersTotal関数を使えば、ポジションがゼロかどうかの判定が簡単にできます。

input double LotSize    = 0.1;    // 注文ロット数
input int    StopLoss   = 100;    // ストップロス(ポイント)
input int    TakeProfit = 200;    // テイクプロフィット(ポイント)
input int    MagicNumber = 99999; // マジックナンバー

//+------------------------------------------------------------------+
//| 自分のEAのポジション数を取得する関数                              |
//+------------------------------------------------------------------+
int CountMyPositions()
{
    int count = 0;
    // OrdersTotal()で全ポジションをチェック
    for(int i = OrdersTotal() - 1; i >= 0; i--)
    {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
        {
            // 自分のEA(マジックナンバー一致)かつ同じ通貨ペアのみカウント
            if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol())
                count++;
        }
    }
    return count;
}

//+------------------------------------------------------------------+
//| OnTick関数                                                        |
//+------------------------------------------------------------------+
void OnTick()
{
    // 自分のEAのポジションが0のときだけエントリーを判断
    if(CountMyPositions() > 0)
        return;  // すでにポジションがあれば何もしない

    // RSIを使ったシンプルな売買判断
    double rsi = iRSI(Symbol(), 0, 14, PRICE_CLOSE, 1);

    double sl, tp;

    // RSIが30以下 → 売られすぎなので買いエントリー
    if(rsi < 30)
    {
        sl = Ask - StopLoss * Point;    // ストップロス価格を計算
        tp = Ask + TakeProfit * Point;  // テイクプロフィット価格を計算
        int ticket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3,
                               sl, tp, "RSI買いエントリー", MagicNumber, 0, clrBlue);
        if(ticket > 0)
            Print("RSI=", rsi, " → 買いエントリー成功");
    }
    // RSIが70以上 → 買われすぎなので売りエントリー
    else if(rsi > 70)
    {
        sl = Bid + StopLoss * Point;    // ストップロス価格を計算
        tp = Bid - TakeProfit * Point;  // テイクプロフィット価格を計算
        int ticket = OrderSend(Symbol(), OP_SELL, LotSize, Bid, 3,
                               sl, tp, "RSI売りエントリー", MagicNumber, 0, clrRed);
        if(ticket > 0)
            Print("RSI=", rsi, " → 売りエントリー成功");
    }
}

CountMyPositions関数を作って、ポジション数のカウント処理を分離しているのがポイントです。こうすることで、OnTick内のコードがすっきりして読みやすくなります。EAが複雑になっても、この関数を使い回せるので便利です。

 

まとめ

OrdersTotal関数は、EAでとてもよく使う関数です。

自動売買がポジションを決済するときに使うなど、使用場面はたくさんあります。

そのため、ほとんどのEAで使われています。

注意点は、配列と組み合わせて使う場合、存在しない配列を参照してしまうことがあることです。

for文の順番をしっかりと考えて、OrdersTotal関数を使っていきましょう。