既刊のパーシャルクローズEAは、

利益が一定pipsに達すると自動で部分決済してくれますが、

「今のポジションは、あと何pipsで次の段階に届くのか」

をEAを見ているだけでは把握できません。

この記事では発注を一切行わず、

保有中の各ポジションの利益pipsと部分決済の段階を

チャート上に一覧表示する確認専用インジケーターを作ります。

この記事で分かること
・保有ポジションごとの利益pipsをリアルタイムで計算する方法
・既刊EAと同じ2段階のしきい値を、発注せずに再計算する設計
・次の段階まで残り何pipsかを一覧表示する方法
・複数のポジションをOBJ_LABELのパネルに並べる実装

設計思想: なぜ「確認専用」にしたのか

まず、このインジケーターの役割を整理します。

既刊EAのGlobalVariableを読み取る方式にしなかった理由

既刊partial-close-scale-out-ea-mt4.mdは、

チケット番号ごとにGlobalVariableで段階を記録していますが、

このインジケーターはそのGlobalVariableを読み取るのではなく、

同じ計算式を自分でもう一度計算する設計にしました。

手動で部分決済している裁量トレーダーにも使える

補足
・既刊EAを使っていない裁量トレーダーの手元のポジションにも使える
・EAが未適用でも、同じ考え方の『目安』として確認できる
・あくまで表示のみで、実際の決済判断は自分で行う前提

既刊martingale-margin-simulator-indicator-mt4.mdと同じ役割分担

既刊のマーチンゲール検証EA・証拠金シミュレーターインジケーターの組と同じように、

「EAは実行する、インジケーターは確認だけする」という役割分担にしています。

保有ポジションの利益pipsを計算する

まず、各ポジションの利益をpips単位で計算します。

売買方向ごとに計算式を分ける

double GetProfitPips(double point)
  {
   if(OrderType() == OP_BUY)
      return((Bid - OrderOpenPrice()) / point);
   if(OrderType() == OP_SELL)
      return((OrderOpenPrice() - Ask) / point);
   return(0);
  }

買いはBidとの差、売りはAskとの差で利益を計算します。

5桁/3桁ブローカーのpoint補正

double point = _Point;
if(_Digits == 3 || _Digits == 5)
   point *= 10;

既刊記事群と同じ_Digitsによる補正を入れ、

1pipsの単位をブローカーの桁数にかかわらず揃えます。

段階の判定と推奨決済ロット数の計算

既刊EAと同じ2段階のしきい値で、現在の段階を判定します。

しきい値未達・第1段階・第2段階の3状態

状態 条件 表示
未達 利益 < InpStage1Pips 第1段階までの残りpips
第1段階到達 InpStage1Pips 以上 InpStage2Pips 未満 推奨決済ロット数+第2段階までの残りpips
第2段階到達 InpStage2Pips 以上 推奨決済ロット数+トレーリング推奨の表示

推奨決済ロット数をブローカーの最小単位に合わせる

double NormalizeLotsToStep(double lots)
  {
   double step   = MarketInfo(_Symbol, MODE_LOTSTEP);
   double minLot = MarketInfo(_Symbol, MODE_MINLOT);
   double rounded = MathFloor(lots / step) * step;
   if(rounded < minLot)
      rounded = 0;
   return(NormalizeDouble(rounded, 2));
  }

既刊EAと同じ計算式を使うことで、

実際にそのEAを使った場合に決済される数量と表示がズレないようにしています。

OBJ_LABELでパネル表示する

計算した内容を、チャートの左上にパネルとして表示します。

ポジションごとに1行、OBJ_LABELを配置する

string objName = "PCPI_Row" + (string)row;
ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, InpPanelX);
ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, InpPanelY + 16 * (row + 1));

行番号(row)に応じてYDISTANCEをずらすことで、

ポジションの数が増えても縦に並んで表示されます。

段階に応じて文字色を変える

補足
・未達: 白
・第1段階到達: 黄
・第2段階到達: ライム(緑)

毎tick、古いラベルを消してから再描画する

for(int i = ObjectsTotal(0, -1, OBJ_LABEL) - 1; i >= 0; i--)
  {
   string name = ObjectName(0, i, -1, OBJ_LABEL);
   if(StringFind(name, "PCPI_") == 0)
      ObjectDelete(0, name);
  }

ポジションの数が減ったときに古い行が残り続けないよう、

毎回「PCPI_」で始まるオブジェクトを全て消してから再描画します。

実際に動くコード全体

ここまでの要素をまとめた、実際にMetaEditorでコンパイルできるコードです。

//+------------------------------------------------------------------+
//|                                PartialCloseProgressIndicator.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
#property indicator_chart_window
#property indicator_buffers 0

input int    InpStage1Pips    = 150; //第1段階の利益しきい値(pips)
input double InpStage1Percent = 50.0; //第1段階の推奨決済割合(%)
input int    InpStage2Pips    = 300; //第2段階の利益しきい値(pips)
input double InpStage2Percent = 50.0; //第2段階の推奨決済割合(残量に対する%)
input int    InpMagicFilter   = 0;    //対象のマジックナンバー(0なら全てのポジションを対象にする)
input int    InpMaxRows       = 10;   //表示する最大行数
input int    InpPanelX        = 10;   //パネルのX座標(ピクセル)
input int    InpPanelY        = 20;   //パネルのY座標(ピクセル)

double PointAdjust()
  {
   double point = _Point;
   if(_Digits == 3 || _Digits == 5)
      point *= 10;
   return(point);
  }

double NormalizeLotsToStep(double lots)
  {
   double step   = MarketInfo(_Symbol, MODE_LOTSTEP);
   double minLot = MarketInfo(_Symbol, MODE_MINLOT);
   double rounded = MathFloor(lots / step) * step;
   if(rounded < minLot)
      rounded = 0;
   return(NormalizeDouble(rounded, 2));
  }

double GetProfitPips(double point)
  {
   if(OrderType() == OP_BUY)
      return((Bid - OrderOpenPrice()) / point);
   if(OrderType() == OP_SELL)
      return((OrderOpenPrice() - Ask) / point);
   return(0);
  }

string StageText(double profitPips, double lots)
  {
   if(profitPips < InpStage1Pips)
      return(StringFormat("第1段階まで %.1f pips", InpStage1Pips - profitPips));

   if(profitPips < InpStage2Pips)
     {
      double closeLots = NormalizeLotsToStep(lots * (InpStage1Percent / 100.0));
      return(StringFormat("第1段階到達済(推奨決済 %.2f lot) / 第2段階まで %.1f pips",
                          closeLots, InpStage2Pips - profitPips));
     }

   double closeLots2 = NormalizeLotsToStep(lots * (InpStage2Percent / 100.0));
   return(StringFormat("第2段階到達済(推奨決済 %.2f lot) / 以降はトレーリング推奨", closeLots2));
  }

void ClearPanel()
  {
   for(int i = ObjectsTotal(0, -1, OBJ_LABEL) - 1; i >= 0; i--)
     {
      string name = ObjectName(0, i, -1, OBJ_LABEL);
      if(StringFind(name, "PCPI_") == 0)
         ObjectDelete(0, name);
     }
  }

void DrawPanel()
  {
   ClearPanel();
   double point = PointAdjust();

   ObjectCreate(0, "PCPI_Title", OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, "PCPI_Title", OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, "PCPI_Title", OBJPROP_XDISTANCE, InpPanelX);
   ObjectSetInteger(0, "PCPI_Title", OBJPROP_YDISTANCE, InpPanelY);
   ObjectSetString(0, "PCPI_Title", OBJPROP_TEXT, "パーシャルクローズ進行状況(確認専用)");
   ObjectSetInteger(0, "PCPI_Title", OBJPROP_COLOR, clrWhite);

   int row = 0;
   for(int i = 0; i < OrdersTotal() && row < InpMaxRows; i++)
     {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
         continue;
      if(OrderSymbol() != _Symbol)
         continue;
      if(OrderType() != OP_BUY && OrderType() != OP_SELL)
         continue;
      if(InpMagicFilter != 0 && OrderMagicNumber() != InpMagicFilter)
         continue;

      double profitPips = GetProfitPips(point);
      string line = "#" + (string)OrderTicket() + " " +
                    (OrderType() == OP_BUY ? "買い " : "売り ") +
                    DoubleToString(profitPips, 1) + "pips: " +
                    StageText(profitPips, OrderLots());

      string objName = "PCPI_Row" + (string)row;
      ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0);
      ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
      ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, InpPanelX);
      ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, InpPanelY + 16 * (row + 1));
      ObjectSetString(0, objName, OBJPROP_TEXT, line);
      ObjectSetInteger(0, objName, OBJPROP_COLOR,
                       profitPips >= InpStage2Pips ? clrLime :
                       profitPips >= InpStage1Pips ? clrYellow : clrWhite);
      row++;
     }

   if(row == 0)
     {
      ObjectCreate(0, "PCPI_Empty", OBJ_LABEL, 0, 0, 0);
      ObjectSetInteger(0, "PCPI_Empty", OBJPROP_CORNER, CORNER_LEFT_UPPER);
      ObjectSetInteger(0, "PCPI_Empty", OBJPROP_XDISTANCE, InpPanelX);
      ObjectSetInteger(0, "PCPI_Empty", OBJPROP_YDISTANCE, InpPanelY + 16);
      ObjectSetString(0, "PCPI_Empty", OBJPROP_TEXT, "対象のポジションはありません");
      ObjectSetInteger(0, "PCPI_Empty", OBJPROP_COLOR, clrGray);
     }
  }

int OnInit()
  {
   return(INIT_SUCCEEDED);
  }

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[])
  {
   DrawPanel();
   return(rates_total);
  }

void OnDeinit(const int reason)
  {
   ClearPanel();
   ObjectDelete(0, "PCPI_Title");
   ObjectDelete(0, "PCPI_Empty");
  }
//+------------------------------------------------------------------+

ポジションが増減しても、毎tickで最新の状態に合わせてパネルが再描画される構成です。

MetaEditorでのコンパイル・動作確認手順

実際に動かすまでの手順です。

  1. MetaEditorで新規インジケーター「PartialCloseProgressIndicator」を作成し、上のコードを貼り付けてコンパイルする
  2. 「0 error(s), 0 warning(s)」になることを確認する
  3. デモ口座でポジションを1つ持ち、チャートにインジケーターを適用する
  4. 利益がInpStage1Pipsを超えると、文字色が黄色に変わり推奨決済ロット数が表示されることを確認する

よくあるエラーと注意点

実装・運用でつまずきやすいポイントをまとめます。

このインジケーターは発注も決済も行わない

注意
・表示されている「推奨決済ロット数」はあくまで目安
・実際の決済は自分で手動注文するか、既刊EAを併用する必要がある
・このインジケーター単体では何も実行されない

InpMagicFilterを指定すると他のEAのポジションが表示されない

デフォルトの0は「全てのポジションを対象にする」設定です。

既刊EAと組み合わせて使う場合は、

そのEAのInpMagicと同じ値をInpMagicFilterに指定すると、

このインジケーターの対象を絞り込めます。

同じ通貨ペアに複数のポジションがあると行数が増える

InpMaxRowsを超えるポジション数がある場合、

超えた分は表示されません(発注・決済自体には影響しません)。

よくある質問

パーシャルクローズ進行状況インジケーターについて、よくある疑問に回答します。

既刊partial-close-scale-out-ea-mt4.mdと数値がズレることはありますか?

基本的にはズレません。

同じInpStage1Pips/InpStage2Pips/

InpStage1Percent/InpStage2Percentを設定すれば、

同じ計算式を使っているため、EAが決済する数量と表示は一致します。

他の通貨ペアのポジションも一覧表示できますか?

できません。

OrderSymbol() == _Symbolで絞り込んでいるため、

このインジケーターが適用されているチャートの通貨ペアのみが対象です。

3段階以上のしきい値に対応できますか?

できます。

StageText関数に第3段階の判定を追加すれば、

同じ考え方で3段階目以降にも対応できます。

【MQL4】パーシャルクローズ(部分決済)の進行状況をチャート上に表示する確認専用インジケーターの作り方:まとめ

この記事では、

既刊partial-close-scale-out-ea-mt4.mdと同じ計算式を使い、

発注を一切行わずに保有ポジションの部分決済の進行状況を

チャート上に一覧表示する確認専用インジケーターを解説しました。

既刊EAを使っている場合は答え合わせとして、

EAを使わず手動で部分決済している場合は目安として、

どちらの使い方にも対応できる設計にしています。

OBJ_LABELを使ったパネル表示は、

既刊の信号機インジケーター・証拠金維持率インジケーターと同じ実装パターンのため、

他の確認専用ツールを作る際にも応用できます。

くわしい自動化の開示方針は編集方針ページもご覧ください。

関連記事