English Deutsch
preview
MQL5での取引戦略の自動化(第26回):複数ポジション取引のためのピンバーナンピンシステムの構築

MQL5での取引戦略の自動化(第26回):複数ポジション取引のためのピンバーナンピンシステムの構築

MetaTrader 5トレーディング |
114 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第25回)では、最小二乗法を用いてサポートラインとレジスタンスラインを検出し、価格がラインに接触した際に自動でエントリーを生成し、視覚的なフィードバックも提供するトレンドライン取引システムをMetaQuotes Language 5 (MQL5)で開発しました。第26回となる今回は、ピンバーを検出して取引を開始し、複数ポジションをナンピン戦略で管理する「ピンバーナンピン」プログラムを作成します。これには、トレーリングストップ、ブレークイーブン調整、リアルタイム監視用のダッシュボードが含まれます。本記事では以下のトピックを扱います。

  1. ピンバーナンピンフレームワークの理解
  2. MQL5での実装
  3. バックテスト
  4. 結論

この記事を読み終える頃には、ピンバーを基盤とした強力なMQL5取引戦略を構築できるようになります。さっそく始めましょう。


ピンバーナンピンフレームワークの理解

今回構築するのは、ピンバーと呼ばれるローソク足パターンを活用した自動売買システムです。ピンバーは長いヒゲと小さな実体を持つ単一のローソク足で構成されており、市場の重要なレベルで強い反転を示すことが多いため、価格の拒否(プライスリジェクション)が起こったタイミングを高い精度で捉えられる点で人気があります。特にサポートやレジスタンスのレベルと組み合わせた場合、エントリー精度が高まります。以下は一般的なピンバー形成の例です。

ピンバーフレームワーク

本システムでは、現在の時間足でこれらのピンバーを検出し、最初の取引が逆行した場合にナンピン戦略を用いて追加ポジションを段階的に開きます。これは、トレーリングストップやブレークイーブン調整を用いたリスク管理をおこないつつ、取引全体の結果を改善することを目的としています。これを実現するために、まず前回のH4足の終値から算出したサポートまたはレジスタンスレベルに対してピンバーを特定し、取引が重要な市場ゾーンに沿うようにします。

次に、あらかじめ設定した価格間隔でポジションを追加するナンピン機構を実装し、ボラティリティの高い相場でも柔軟に対応できるようにします。最後に、リアルタイムの取引指標を表示するダッシュボードを組み込み、ラインなどの視覚的指標を用いて重要なレベルをマークし、戦略を効果的に監視し、調整できるようにします。まず、目指す機能の概要を示したうえで、実装の説明に進みます。

戦略フレームワーク


MQL5での実装

MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲータに移動して、Indicatorsフォルダを見つけ、[新規作成]タブをクリックして、表示される手順に従ってファイルを作成します。ファイルが作成されたら、コーディング環境でプログラムをより柔軟にするための入力パラメータやグローバル変数を宣言していきます。

//+------------------------------------------------------------------+
//|                                      a. Pin Bar Averaging EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright   "Copyright 2025, Allan Munene Mutiiria."
#property link        "https://t.me/Forex_Algo_Trader"
#property version     "1.00"
#property strict

#include <Trade\Trade.mqh>                         //--- Include Trade library for trading operations
CTrade obj_Trade;                                  //--- Instantiate trade object

//+------------------------------------------------------------------+
//| Trading signal enumeration                                       |
//+------------------------------------------------------------------+
enum EnableTradingBySignal {                       //--- Define trading signal enum
   ENABLED  = 1,                                   // Enable trading signals
   DISABLED = 0                                    // Disable trading signals
};

//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input bool   useSignalMode = DISABLED;             // Set signal mode (ENABLED/DISABLED)
input int    orderDistancePips = 50;               // Set order distance (pips)
input double lotMultiplier = 1;                    // Set lot size multiplier
input bool   useRSIFilter = false;                 // Enable RSI filter
input int    magicNumber = 123456789;              // Set magic number
input double initialLotSize = 0.01;                // Set initial lot size
input int    compoundPercent = 2;                  // Set compounding percent (0 for fixed lots)
input int    maxOrders = 5;                        // Set maximum orders
input double stopLossPips = 400;                   // Set stop loss (pips)
input double takeProfitPips = 200;                 // Set take profit (pips)
input bool   useAutoTakeProfit = true;             // Enable auto take profit
input bool   useTrailingStop = true;               // Enable trailing stop
input double trailingStartPips = 15;               // Set trailing start (pips)
input double breakevenPips = 10;                   // Set breakeven (pips)
input string orderComment = "Forex_Algo_Trader";   // Set order comment
input color  lineColor = clrBlue;                  // Set line color
input int    lineWidth = 2;                        // Set line width

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
bool   isTradingAllowed();                         //--- Declare trading allowed check
double slBreakevenMinus = 0;                       //--- Initialize breakeven minus
double normalizedPoint;                            //--- Declare normalized point
ulong  currentTicket = 0;                          //--- Initialize current ticket
double buyCount, currentBuyLot, totalBuyLots;      //--- Declare buy metrics
double sellCount, currentSellLot, totalSellLots;   //--- Declare sell metrics
double totalSum, totalSwap;                        //--- Declare total sum and swap
double buyProfit, sellProfit, totalOperations;     //--- Declare profit and operations
double buyWeightedSum, sellWeightedSum;            //--- Declare weighted sums
double buyBreakEvenPrice, sellBreakEvenPrice;      //--- Declare breakeven prices
double minBuyLot, minSellLot;                      //--- Declare minimum lot sizes
double maxSellPrice, minBuyPrice;                  //--- Declare price extremes

ピンバーナンピンシステムをMQL5で構築し、ピンバーパターンに基づく自動取引と堅牢なポジション管理システムを実現するための基盤として、まず<Trade\Trade.mqh>ライブラリをインクルードし、obj_TradeをCTradeオブジェクトとしてインスタンス化して、ポジション建てや決済などの取引操作を扱います。次に、EnableTradingBySignal列挙型を定義し、ENABLED(1)とDISABLED(0)でポジション管理に取引シグナルを使用するかどうかを制御します。続いて、EAをカスタマイズするための入力パラメータを設定します。シグナルモードの切替用ブール値、注文間隔(pips)、ロットサイズ倍率、RSIフィルタの切替、取引識別用マジックナンバー、初期ロットサイズ、複利率(0は固定ロット)、最大注文数、SL(ストップロス)およびTP(テイクプロフィット)(pips)、自動TPとトレーリングストップの切替、トレーリング開始およびブレークイーブン(pips)、注文コメント、視覚的指標用のラインカラーと幅です。

最後に、グローバル変数を宣言します。取引条件を確認する関数isTradingAllowed、SL調整用に初期化されたslBreakevenMinus=0、価格スケーリング用のnormalizedPoint、取引追跡用のcurrentTicket、buyCount、currentBuyLot、totalBuyLots、sellCount、currentSellLot、totalSellLots、totalSum、totalSwap、buyProfit、sellProfit、totalOperations、buyWeightedSum、sellWeightedSum、buyBreakEvenPrice、sellBreakEvenPrice、minBuyLot、minSellLot、maxSellPrice、minBuyPriceなどのポジションメトリクス用カウンタと合計値を宣言し、ピンバー検出とナンピン戦略のEAコアフレームワークを構築します。これで、ほとんどの処理がティックベースで実行されるため、プログラムの初期化に進むことができます。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   normalizedPoint = _Point;                       //--- Initialize point value
   if (_Digits == 5 || _Digits == 3) {             //--- Check for 5 or 3 digit symbols
      normalizedPoint *= 10;                       //--- Adjust point value
   }
   ChartSetInteger(0, CHART_SHOW_GRID, false);     //--- Disable chart grid
   obj_Trade.SetExpertMagicNumber(magicNumber);    //--- Set magic number for trade object
   obj_Trade.SetTypeFilling(ORDER_FILLING_IOC);    //--- Set order filling type
   return(INIT_SUCCEEDED);                         //--- Return success
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   ObjectsDeleteAll(0);                            //--- Delete all chart objects
   ChartRedraw(0);                                 //--- Redraw chart
}

まず、OnInitイベントハンドラでは、normalizedPointを_Pointで初期化し、_Digitsを使用して5桁または3桁の銘柄に対して10倍して価格計算の精度を確保します。次に、ChartSetIntegerCHART_SHOW_GRIDをfalseに設定してチャートグリッドを無効化し、表示をすっきりさせます。obj_TradeにはSetExpertMagicNumberでmagicNumberを設定して取引識別をおこない、SetTypeFillingで注文埋め方式をORDER_FILLING_IOCに設定します。最後にINIT_SUCCEEDEDを返して初期化が正常に完了したことを確認します。次に、OnDeinitイベントハンドラでは、ObjectsDeleteAllですべてのチャートオブジェクトを削除して、後で定義するダッシュボードやラインなどの視覚要素をクリアします。これは、チャートを確実に初期状態に戻すためだけにおこないます。さらに、ChartRedrawを呼び出してチャートを再描画し、クリーンな終了処理を保証します。複雑な取引ロジックに進む前に、プログラムを動的で保守しやすくするために必要なヘルパー関数をいくつか定義しておきましょう。

//+------------------------------------------------------------------+
//| Count total trades                                               |
//+------------------------------------------------------------------+
int CountTrades() {
   int positionCount = 0;                         //--- Initialize position count
   for (int trade = PositionsTotal() - 1; trade >= 0; trade--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(trade);    //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol() || PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching positions
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL || PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check trade type
         positionCount++;                         //--- Increment position count
      }
   }
   return(positionCount);                         //--- Return total count
}

//+------------------------------------------------------------------+
//| Count buy trades                                                 |
//+------------------------------------------------------------------+
int CountTradesBuy() {
   int buyPositionCount = 0;                      //--- Initialize buy position count
   for (int trade = PositionsTotal() - 1; trade >= 0; trade--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(trade);    //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol() || PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching positions
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
         buyPositionCount++;                      //--- Increment buy count
      }
   }
   return(buyPositionCount);                      //--- Return buy count
}

//+------------------------------------------------------------------+
//| Count sell trades                                                |
//+------------------------------------------------------------------+
int CountTradesSell() {
   int sellPositionCount = 0;                     //--- Initialize sell position count
   for (int trade = PositionsTotal() - 1; trade >= 0; trade--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(trade);    //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol() || PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching positions
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
         sellPositionCount++;                     //--- Increment sell count
      }
   }
   return(sellPositionCount);                     //--- Return sell count
}

//+------------------------------------------------------------------+
//| Normalize price                                                  |
//+------------------------------------------------------------------+
double NormalizePrice(double price) {
   return(NormalizeDouble(price, _Digits));       //--- Normalize price to symbol digits
}

//+------------------------------------------------------------------+
//| Get lot digit for normalization                                  |
//+------------------------------------------------------------------+
int fnGetLotDigit() {
   double lotStepValue = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP); //--- Get lot step value
   if (lotStepValue == 1) return(0);              //--- Return 0 for step 1
   if (lotStepValue == 0.1) return(1);            //--- Return 1 for step 0.1
   if (lotStepValue == 0.01) return(2);           //--- Return 2 for step 0.01
   if (lotStepValue == 0.001) return(3);          //--- Return 3 for step 0.001
   if (lotStepValue == 0.0001) return(4);         //--- Return 4 for step 0.0001
   return(1);                                     //--- Default to 1
}

//+------------------------------------------------------------------+
//| Check buy orders for specific magic number                       |
//+------------------------------------------------------------------+
int CheckBuyOrders(int magic) {
   int buyOrderCount = 0;                         //--- Initialize buy order count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);         //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magic) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
            buyOrderCount++;                      //--- Increment buy count
            break;                                //--- Exit loop
         }
      }
   }
   return(buyOrderCount);                         //--- Return buy order count
}

//+------------------------------------------------------------------+
//| Check sell orders for specific magic number                      |
//+------------------------------------------------------------------+
int CheckSellOrders(int magic) {
   int sellOrderCount = 0;                         //--- Initialize sell order count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);         //--- Get position ticket
      if (ticket == 0) continue;                   //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magic) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
            sellOrderCount++;                      //--- Increment sell count
            break;                                 //--- Exit loop
         }
      }
   }
   return(sellOrderCount);                         //--- Return sell order count
}

//+------------------------------------------------------------------+
//| Check total buy orders                                           |
//+------------------------------------------------------------------+
int CheckTotalBuyOrders(int magic) {
   int totalBuyOrderCount = 0;                      //--- Initialize total buy order count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);          //--- Get position ticket
      if (ticket == 0) continue;                    //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magic) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
            totalBuyOrderCount++;                   //--- Increment buy count
         }
      }
   }
   return(totalBuyOrderCount);                      //--- Return total buy count
}

//+------------------------------------------------------------------+
//| Check total sell orders                                          |
//+------------------------------------------------------------------+
int CheckTotalSellOrders(int magic) {
   int totalSellOrderCount = 0;                      //--- Initialize total sell order count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);           //--- Get position ticket
      if (ticket == 0) continue;                     //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magic) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
            totalSellOrderCount++;                   //--- Increment sell count
         }
      }
   }
   return(totalSellOrderCount);                      //--- Return total sell count
}

//+------------------------------------------------------------------+
//| Check market buy orders                                          |
//+------------------------------------------------------------------+
int CheckMarketBuyOrders() {
   int marketBuyCount = 0;                        //--- Initialize market buy count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);         //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
            marketBuyCount++;                     //--- Increment buy count
         }
      }
   }
   return(marketBuyCount);                        //--- Return market buy count
}

//+------------------------------------------------------------------+
//| Check market sell orders                                         |
//+------------------------------------------------------------------+
int CheckMarketSellOrders() {
   int marketSellCount = 0;                       //--- Initialize market sell count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);         //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
            marketSellCount++;                    //--- Increment sell count
         }
      }
   }
   return(marketSellCount);                       //--- Return market sell count
}

//+------------------------------------------------------------------+
//| Close all buy positions                                          |
//+------------------------------------------------------------------+
void CloseBuy() {
   while (CheckMarketBuyOrders() > 0) {           //--- Check buy orders exist
      for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
         ulong ticket = PositionGetTicket(i);      //--- Get position ticket
         if (ticket == 0) continue;               //--- Skip invalid tickets
         if (PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check symbol and magic
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
               obj_Trade.PositionClose(ticket);   //--- Close position
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Close all sell positions                                         |
//+------------------------------------------------------------------+
void CloseSell() {
   while (CheckMarketSellOrders() > 0) {          //--- Check sell orders exist
      for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
         ulong ticket = PositionGetTicket(i);      //--- Get position ticket
         if (ticket == 0) continue;               //--- Skip invalid tickets
         if (PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check symbol and magic
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
               obj_Trade.PositionClose(ticket);   //--- Close position
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Calculate lot size                                               |
//+------------------------------------------------------------------+
double GetLots() {
   double calculatedLot;                          //--- Initialize calculated lot
   double minLot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN); //--- Get minimum lot
   double maxLot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX); //--- Get maximum lot
   if (compoundPercent != 0) {                    //--- Check compounding
      calculatedLot = NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE) * compoundPercent / 100 / 10000, fnGetLotDigit()); //--- Calculate compounded lot
      if (calculatedLot < minLot) calculatedLot = minLot; //--- Enforce minimum lot
      if (calculatedLot > maxLot) calculatedLot = maxLot; //--- Enforce maximum lot
   } else {
      calculatedLot = initialLotSize;             //--- Use fixed lot size
   }
   return(calculatedLot);                         //--- Return calculated lot
}

//+------------------------------------------------------------------+
//| Check account free margin                                        |
//+------------------------------------------------------------------+
double AccountFreeMarginCheck(string symbol, int orderType, double volume) {
   double marginRequired = 0.0;                   //--- Initialize margin required
   double price = orderType == ORDER_TYPE_BUY ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID); //--- Get price
   double calculatedMargin;                       //--- Declare calculated margin
   bool success = OrderCalcMargin(orderType == ORDER_TYPE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL, symbol, volume, price, calculatedMargin); //--- Calculate margin
   if (success) marginRequired = calculatedMargin; //--- Set margin if successful
   return AccountInfoDouble(ACCOUNT_MARGIN_FREE) - marginRequired; //--- Return free margin
}

//+------------------------------------------------------------------+
//| Check if trading is allowed                                      |
//+------------------------------------------------------------------+
bool isTradingAllowed() {
   bool isAllowed = false;                        //--- Initialize allowed flag
   return(true);                                  //--- Return true
}

ここでは、取引のカウント、ポジションのクローズ、ロットサイズ計算、証拠金チェック、取引許可管理などをおこなうユーティリティ関数を実装し、堅牢な取引処理を実現します。まず、取引をカウントする関数を作成します。CountTradesはPositionsTotalをループし、PositionGetTicketで有効なチケットを確認し、SymbolとmagicNumberが一致するかどうかを確認したうえで、買いまたは売りポジションごとにpositionCountをインクリメントします。CountTradesBuyとCountTradesSellはそれぞれ買いと売りポジションをカウントし、POSITION_TYPE_BUYまたはPOSITION_TYPE_SELLでフィルタリングします。CheckBuyOrdersとCheckSellOrdersは、特定のmagicNumberを持つ買いまたは売りポジションが1つ以上存在するかどうかを検出し、最初の一致後にループを抜けます。CheckTotalBuyOrdersとCheckTotalSellOrdersは、magicNumberを持つすべての買いまたは売りポジションをカウントします。CheckMarketBuyOrdersとCheckMarketSellOrdersは、magicNumberを持つ買いまたは売りポジションをカウントします。

次に、NormalizeDoubleを実装して価格を_Digitsに正規化(NormalizeDouble使用)、fnGetLotDigitを実装してSYMBOL_VOLUME_STEPに基づくロットサイズの小数桁を返します(例:1なら0、0.1なら1)。さらに、CloseBuyとCloseSellを作成し、SymbolとmagicNumberを確認しながらポジションをループし、obj_Trade.PositionCloseを使用して、CheckMarketBuyOrdersまたはCheckMarketSellOrdersが0になるまですべての買いまたは売りポジションをクローズします。最後に、GetLotsを実装し、compoundPercentの場合は「AccountInfoDouble (ACCOUNT_BALANCE)*compoundPercent/100/10000」をfnGetLotDigitで正規化し、SYMBOL_VOLUME_MINおよびSYMBOL_VOLUME_MAXで制約、またはinitialLotSizeを使用してロットサイズを算出します。AccountFreeMarginCheckは、OrderCalcMarginで指定の注文タイプとボリュームに必要な証拠金を計算して利用可能証拠金を求めます。isTradingAllowedはプレースホルダとしてtrueを返します。可視化のために、チャート上にラインやラベルを描画する関数も必要となります。

//+------------------------------------------------------------------+
//| Draw support/resistance line                                     |
//+------------------------------------------------------------------+
void MakeLine(double price) {
   string name = "level";                         //--- Set line name
   if (ObjectFind(0, name) != -1) {               //--- Check if line exists
      ObjectMove(0, name, 0, iTime(Symbol(), PERIOD_CURRENT, 0), price); //--- Move line
      return;                                     //--- Exit function
   }
   ObjectCreate(0, name, OBJ_HLINE, 0, 0, price); //--- Create horizontal line
   ObjectSetInteger(0, name, OBJPROP_COLOR, lineColor); //--- Set color
   ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   ObjectSetInteger(0, name, OBJPROP_WIDTH, lineWidth); //--- Set width
   ObjectSetInteger(0, name, OBJPROP_BACK, true); //--- Set to background
}

//+------------------------------------------------------------------+
//| Create dashboard label                                           |
//+------------------------------------------------------------------+
void LABEL(string labelName, string fontName, int fontSize, int xPosition, int yPosition, color textColor, int corner, string labelText) {
   if (ObjectFind(0, labelName) < 0) {            //--- Check if label exists
      ObjectCreate(0, labelName, OBJ_LABEL, 0, 0, 0); //--- Create label
   }
   ObjectSetString(0, labelName, OBJPROP_TEXT, labelText); //--- Set label text
   ObjectSetString(0, labelName, OBJPROP_FONT, fontName); //--- Set font
   ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, fontSize); //--- Set font size
   ObjectSetInteger(0, labelName, OBJPROP_COLOR, textColor); //--- Set text color
   ObjectSetInteger(0, labelName, OBJPROP_CORNER, corner); //--- Set corner
   ObjectSetInteger(0, labelName, OBJPROP_XDISTANCE, xPosition); //--- Set x position
   ObjectSetInteger(0, labelName, OBJPROP_YDISTANCE, yPosition); //--- Set y position
}

プログラムの視覚要素を作成するために、MakeLine関数を開発します。この関数は指定されたpriceで水平線を描画し、サポートやレジスタンスレベルを示すlevelとして名前を設定します。ObjectFindで存在を確認し、見つかった場合はObjectMoveiTimeから現在のバー時間に移動させ、存在しない場合はObjectCreateOBJ_HLINEとして作成します。その後、ObjectSetIntegerでOBJPROP_COLORをlineColor、OBJPROP_STYLEをSTYLE_SOLID、OBJPROP_WIDTHをlineWidth、OBJPROP_BACKをtrueに設定して背景に配置します。

次にLABEL関数を実装します。この関数はダッシュボードのラベルを作成または更新し、labelNameが存在するか確認し、存在しなければObjectCreateでOBJ_LABELとして作成します。プロパティはObjectSetStringでOBJPROP_TEXTをlabelText、OBJPROP_FONTをfontNameに設定し、ObjectSetIntegerでOBJPROP_FONTSIZEをfontSize、OBJPROP_COLORをtextColor、OBJPROP_CORNERをcorner、OBJPROP_XDISTANCEをxPosition、OBJPROP_YDISTANCEをyPositionに設定します。これにより、使用するインジケーターのユーティリティ関数を定義できます。

//+------------------------------------------------------------------+
//| Calculate ATR indicator                                          |
//+------------------------------------------------------------------+
double MyiATR(string symbol, ENUM_TIMEFRAMES timeframe, int period, int shift) {
   int handle = iATR(symbol, timeframe, period);  //--- Create ATR handle
   if (handle == INVALID_HANDLE) return 0;        //--- Check invalid handle
   double buffer[1];                              //--- Declare buffer
   if (CopyBuffer(handle, 0, shift, 1, buffer) != 1) buffer[0] = 0; //--- Copy ATR value
   IndicatorRelease(handle);                      //--- Release handle
   return buffer[0];                              //--- Return ATR value
}

//+------------------------------------------------------------------+
//| Check bullish engulfing pattern                                  |
//+------------------------------------------------------------------+
bool BullishEngulfingExists() {
   if (iOpen(Symbol(), PERIOD_CURRENT, 1) <= iClose(Symbol(), PERIOD_CURRENT, 2) && iClose(Symbol(), PERIOD_CURRENT, 1) >= iOpen(Symbol(), PERIOD_CURRENT, 2) && iOpen(Symbol(), PERIOD_CURRENT, 2) - iClose(Symbol(), PERIOD_CURRENT, 2) >= 10 * _Point && iClose(Symbol(), PERIOD_CURRENT, 1) - iOpen(Symbol(), PERIOD_CURRENT, 1) >= 10 * _Point) { //--- Check bullish engulfing conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check bullish harami pattern                                     |
//+------------------------------------------------------------------+
bool BullishHaramiExists() {
   if (iClose(Symbol(), PERIOD_CURRENT, 2) < iOpen(Symbol(), PERIOD_CURRENT, 2) && iOpen(Symbol(), PERIOD_CURRENT, 1) < iClose(Symbol(), PERIOD_CURRENT, 1) && iOpen(Symbol(), PERIOD_CURRENT, 2) - iClose(Symbol(), PERIOD_CURRENT, 2) > MyiATR(Symbol(), PERIOD_CURRENT, 14, 2) && iOpen(Symbol(), PERIOD_CURRENT, 2) - iClose(Symbol(), PERIOD_CURRENT, 2) > 4 * (iClose(Symbol(), PERIOD_CURRENT, 1) - iOpen(Symbol(), PERIOD_CURRENT, 1))) { //--- Check bullish harami conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check doji at bottom pattern                                     |
//+------------------------------------------------------------------+
bool DojiAtBottomExists() {
   if (iOpen(Symbol(), PERIOD_CURRENT, 3) - iClose(Symbol(), PERIOD_CURRENT, 3) >= 8 * _Point && MathAbs(iClose(Symbol(), PERIOD_CURRENT, 2) - iOpen(Symbol(), PERIOD_CURRENT, 2)) <= 1 * _Point && iClose(Symbol(), PERIOD_CURRENT, 1) - iOpen(Symbol(), PERIOD_CURRENT, 1) >= 8 * _Point) { //--- Check doji at bottom conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check doji at top pattern                                        |
//+------------------------------------------------------------------+
bool DojiAtTopExists() {
   if (iClose(Symbol(), PERIOD_CURRENT, 3) - iOpen(Symbol(), PERIOD_CURRENT, 3) >= 8 * _Point && MathAbs(iClose(Symbol(), PERIOD_CURRENT, 2) - iOpen(Symbol(), PERIOD_CURRENT, 2)) <= 1 * _Point && iOpen(Symbol(), PERIOD_CURRENT, 1) - iClose(Symbol(), PERIOD_CURRENT, 1) >= 8 * _Point) { //--- Check doji at top conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check bearish harami pattern                                     |
//+------------------------------------------------------------------+
bool BearishHaramiExists() {
   if (iClose(Symbol(), PERIOD_CURRENT, 2) > iClose(Symbol(), PERIOD_CURRENT, 1) && iOpen(Symbol(), PERIOD_CURRENT, 2) < iOpen(Symbol(), PERIOD_CURRENT, 1) && iClose(Symbol(), PERIOD_CURRENT, 2) > iOpen(Symbol(), PERIOD_CURRENT, 2) && iOpen(Symbol(), PERIOD_CURRENT, 1) > iClose(Symbol(), PERIOD_CURRENT, 1) && iClose(Symbol(), PERIOD_CURRENT, 2) - iOpen(Symbol(), PERIOD_CURRENT, 2) > MyiATR(Symbol(), PERIOD_CURRENT, 14, 2) && iClose(Symbol(), PERIOD_CURRENT, 2) - iOpen(Symbol(), PERIOD_CURRENT, 2) > 4 * (iOpen(Symbol(), PERIOD_CURRENT, 1) - iClose(Symbol(), PERIOD_CURRENT, 1))) { //--- Check bearish harami conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check long up candle pattern                                     |
//+------------------------------------------------------------------+
bool LongUpCandleExists() {
   if (iOpen(Symbol(), PERIOD_CURRENT, 2) < iClose(Symbol(), PERIOD_CURRENT, 2) && iHigh(Symbol(), PERIOD_CURRENT, 2) - iLow(Symbol(), PERIOD_CURRENT, 2) >= 40 * _Point && iHigh(Symbol(), PERIOD_CURRENT, 2) - iLow(Symbol(), PERIOD_CURRENT, 2) > 2.5 * MyiATR(Symbol(), PERIOD_CURRENT, 14, 2) && iClose(Symbol(), PERIOD_CURRENT, 1) < iOpen(Symbol(), PERIOD_CURRENT, 1) && iOpen(Symbol(), PERIOD_CURRENT, 1) - iClose(Symbol(), PERIOD_CURRENT, 1) > 10 * _Point) { //--- Check long up candle conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check long down candle pattern                                   |
//+------------------------------------------------------------------+
bool LongDownCandleExists() {
   if (iOpen(Symbol(), PERIOD_CURRENT, 1) > iClose(Symbol(), PERIOD_CURRENT, 1) && iHigh(Symbol(), PERIOD_CURRENT, 1) - iLow(Symbol(), PERIOD_CURRENT, 1) >= 40 * _Point && iHigh(Symbol(), PERIOD_CURRENT, 1) - iLow(Symbol(), PERIOD_CURRENT, 1) > 2.5 * MyiATR(Symbol(), PERIOD_CURRENT, 14, 1)) { //--- Check long down candle conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check bearish engulfing pattern                                  |
//+------------------------------------------------------------------+
bool BearishEngulfingExists() {
   if (iOpen(Symbol(), PERIOD_CURRENT, 1) >= iClose(Symbol(), PERIOD_CURRENT, 2) && iClose(Symbol(), PERIOD_CURRENT, 1) <= iOpen(Symbol(), PERIOD_CURRENT, 2) && iOpen(Symbol(), PERIOD_CURRENT, 2) - iClose(Symbol(), PERIOD_CURRENT, 2) >= 10 * _Point && iClose(Symbol(), PERIOD_CURRENT, 1) - iOpen(Symbol(), PERIOD_CURRENT, 1) >= 10 * _Point) { //--- Check bearish engulfing conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Calculate average range over 4 days                              |
//+------------------------------------------------------------------+
double AveRange4() {
   double rangeSum = 0;                           //--- Initialize range sum
   int count = 0;                                 //--- Initialize count
   int index = 1;                                 //--- Initialize index
   while (count < 4) {                            //--- Loop until 4 days
      MqlDateTime dateTime;                       //--- Declare datetime structure
      TimeToStruct(iTime(Symbol(), PERIOD_CURRENT, index), dateTime); //--- Convert time
      if (dateTime.day_of_week != 0) {            //--- Check non-Sunday
         rangeSum += iHigh(Symbol(), PERIOD_CURRENT, index) - iLow(Symbol(), PERIOD_CURRENT, index); //--- Add range
         count++;                                 //--- Increment count
      }
      index++;                                    //--- Increment index
   }
   return(rangeSum / 4.0);                        //--- Return average range
}

//+------------------------------------------------------------------+
//| Check buy pinbar                                                 |
//+------------------------------------------------------------------+
bool IsBuyPinbar() {
   double currentOpen, currentClose, currentHigh, currentLow; //--- Declare current candle variables
   double previousHigh, previousLow, previousClose, previousOpen; //--- Declare previous candle variables
   double currentRange, previousRange, currentHigherPart, currentHigherPart1; //--- Declare range variables
   currentOpen = iOpen(Symbol(), PERIOD_CURRENT, 1); //--- Get current open
   currentClose = iClose(Symbol(), PERIOD_CURRENT, 1); //--- Get current close
   currentHigh = iHigh(Symbol(), PERIOD_CURRENT, 0); //--- Get current high
   currentLow = iLow(Symbol(), PERIOD_CURRENT, 1); //--- Get current low
   previousOpen = iOpen(Symbol(), PERIOD_CURRENT, 2); //--- Get previous open
   previousClose = iClose(Symbol(), PERIOD_CURRENT, 2); //--- Get previous close
   previousHigh = iHigh(Symbol(), PERIOD_CURRENT, 2); //--- Get previous high
   previousLow = iLow(Symbol(), PERIOD_CURRENT, 2); //--- Get previous low
   currentRange = currentHigh - currentLow;       //--- Calculate current range
   previousRange = previousHigh - previousLow;    //--- Calculate previous range
   currentHigherPart = currentHigh - currentRange * 0.4; //--- Calculate higher part
   currentHigherPart1 = currentHigh - currentRange * 0.4; //--- Calculate higher part
   double averageDailyRange = AveRange4();        //--- Get average daily range
   if ((currentClose > currentHigherPart1 && currentOpen > currentHigherPart) && //--- Check close/open in higher third
       (currentRange > averageDailyRange * 0.5) && //--- Check pinbar size
       (currentLow + currentRange * 0.25 < previousLow)) { //--- Check nose length
      double lowArray[3];                         //--- Declare low array
      CopyLow(Symbol(), PERIOD_CURRENT, 3, 3, lowArray); //--- Copy low prices
      int minIndex = ArrayMinimum(lowArray);      //--- Find minimum low index
      if (lowArray[minIndex] > currentLow) return(true); //--- Confirm buy pinbar
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check sell pinbar                                                |
//+------------------------------------------------------------------+
bool IsSellPinbar() {
   double currentOpen, currentClose, currentHigh, currentLow; //--- Declare current candle variables
   double previousHigh, previousLow, previousClose, previousOpen; //--- Declare previous candle variables
   double currentRange, previousRange, currentLowerPart, currentLowerPart1; //--- Declare range variables
   currentOpen = iOpen(Symbol(), PERIOD_CURRENT, 1); //--- Get current open
   currentClose = iClose(Symbol(), PERIOD_CURRENT, 1); //--- Get current close
   currentHigh = iHigh(Symbol(), PERIOD_CURRENT, 1); //--- Get current high
   currentLow = iLow(Symbol(), PERIOD_CURRENT, 1); //--- Get current low
   previousOpen = iOpen(Symbol(), PERIOD_CURRENT, 2); //--- Get previous open
   previousClose = iClose(Symbol(), PERIOD_CURRENT, 2); //--- Get previous close
   previousHigh = iHigh(Symbol(), PERIOD_CURRENT, 2); //--- Get previous high
   previousLow = iLow(Symbol(), PERIOD_CURRENT, 2); //--- Get previous low
   currentRange = currentHigh - currentLow;       //--- Calculate current range
   previousRange = previousHigh - previousLow;    //--- Calculate previous range
   currentLowerPart = currentLow + currentRange * 0.4; //--- Calculate lower part
   currentLowerPart1 = currentLow + currentRange * 0.4; //--- Calculate lower part
   double averageDailyRange = AveRange4();        //--- Get average daily range
   if ((currentClose < currentLowerPart1 && currentOpen < currentLowerPart) && //--- Check close/open in lower third
       (currentRange > averageDailyRange * 0.5) && //--- Check pinbar size
       (currentHigh - currentRange * 0.25 > previousHigh)) { //--- Check nose length
      double highArray[3];                        //--- Declare high array
      CopyHigh(Symbol(), PERIOD_CURRENT, 3, 3, highArray); //--- Copy high prices
      int maxIndex = ArrayMaximum(highArray);     //--- Find maximum high index
      if (highArray[maxIndex] < currentHigh) return(true); //--- Confirm sell pinbar
   }
   return(false);                                 //--- Return false
}

ここでは、ローソク足パターンを検出し、システム用にATRを計算する関数を実装します。まず、MyiATR関数を作成します。この関数は、指定された銘柄、時間足、期間に対してiATR関数でハンドルを作成し、ハンドルが無効な場合は0を返します。CopyBufferでATR値をバッファにコピーし、IndicatorReleaseでハンドルを解放してATR値を返します。

次に、ローソク足パターン検出関数を実装します。BullishEngulfingExistsは、現在のローソク足が前の陰線を実体サイズで包み込むかどうかを確認します。BullishHaramiExistsは、MyiATRを使用して大きな陰線内に小さな陽線があるかを判定します。DojiAtBottomExistsは、陰線と陽線の間にある十字線をモーニングスターとして検出します。DojiAtTopExistsは、陽線と陰線の間の十字線を宵の明星として判定します。BearishHaramiExistsは、大きな陽線内に小さな陰線があるかどうかを確認します。LongUpCandleExistsは、ATRを使って強い陽線の後に陰線が続くかどうかを確認します。LongDownCandleExistsは強い陰線を検出します。BearishEngulfingExistsは、陰線が陽線を包み込むかどうかを確認します。

最後に、IsBuyPinbarとIsSellPinbarを実装します。これらは、現在のローソク足の終値と始値がレンジの上部または下部三分の一に位置するか、レンジがAveRange4(日曜日を除く直近4日間の高値-安値の平均)で計算した平均日幅の半分を超えているか、ピンバーのヒゲが前のローソク足の高値または安値を超えているかどうかを確認することでピンバーを判定します。最近の安値や高値はCopyLowCopyHigh、ArrayMinimumやArrayMaximumを使用して比較します。これにより、表示用のシグナルタイプ取得関数やポジション管理用の加重価格取得関数も定義できるようになります。

//+------------------------------------------------------------------+
//| Analyze candlestick patterns                                     |
//+------------------------------------------------------------------+
string CandleStick_Analyzer() {
   string candlePattern, comment1 = "", comment2 = "", comment3 = ""; //--- Initialize pattern strings
   string comment4 = "", comment5 = "", comment6 = "", comment7 = ""; //--- Initialize pattern strings
   string comment8 = "", comment9 = "";                               //--- Initialize pattern strings
   if (BullishEngulfingExists()) comment1 = " Bullish Engulfing ";    //--- Check bullish engulfing
   if (BullishHaramiExists()) comment2 = " Bullish Harami ";          //--- Check bullish harami
   if (LongUpCandleExists()) comment3 = " Bullish LongUp ";           //--- Check long up candle
   if (DojiAtBottomExists()) comment4 = " MorningStar Doji ";         //--- Check morning star doji
   if (DojiAtTopExists()) comment5 = " EveningStar Doji ";            //--- Check evening star doji
   if (BearishHaramiExists()) comment6 = " Bearish Harami ";          //--- Check bearish harami
   if (BearishEngulfingExists()) comment7 = " Bearish Engulfing ";    //--- Check bearish engulfing
   if (LongDownCandleExists()) comment8 = " Bearish LongDown ";       //--- Check long down candle
   candlePattern = comment1 + comment2 + comment3 + comment4 + comment5 + comment6 + comment7 + comment8 + comment9; //--- Combine patterns
   return(candlePattern);                                             //--- Return combined pattern
}

//+------------------------------------------------------------------+
//| Calculate average price for order type                           |
//+------------------------------------------------------------------+
double rata_price(int orderType) {
   double totalVolume = 0;                        //--- Initialize total volume
   double weightedOpenSum = 0;                    //--- Initialize weighted open sum
   double averagePrice = 0;                       //--- Initialize average price
   for (int positionIndex = 0; positionIndex < PositionsTotal(); positionIndex++) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(positionIndex); //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber && (PositionGetInteger(POSITION_TYPE) == orderType)) { //--- Check position match
         totalVolume += PositionGetDouble(POSITION_VOLUME); //--- Add volume
         weightedOpenSum += (PositionGetDouble(POSITION_VOLUME) * PositionGetDouble(POSITION_PRICE_OPEN)); //--- Add weighted open
      }
   }
   if (totalVolume != 0) {                        //--- Check non-zero volume
      averagePrice = weightedOpenSum / totalVolume; //--- Calculate average price
   }
   return(averagePrice);                          //--- Return average price
}

ポジション管理を強化するため、CandleStick_Analyzer関数を作成します。本関数では、comment1からcomment9までの文字列変数を空で初期化し、既に定義したBullishEngulfingExistsなどのローソク足パターン検出関数を用いてパターンの有無を確認します。検出された場合は、該当する変数に「Bullish Engulfing」などの説明文字列を代入し、最終的にこれらを結合してcandlePatternとして返します。これにより、ダッシュボードに表示する検出パターンの文字列をまとめることができます。

次に、rata_price関数を実装します。本関数は指定されたorderType(買いまたは売り)に対して加重平均価格を計算します。まずtotalVolumeとweightedOpenSumを0で初期化し、PositionsTotalですべてのポジションを順番に処理します。対象の銘柄、magicNumber、orderTypeに一致するポジションについて、PositionGetTicketでチケット番号を取得し、PositionGetStringで銘柄を確認、PositionGetIntegerでPOSITION_VOLUMEを取得し、PositionGetDoubleでPOSITION_PRICE_OPENを取得します。weightedOpenSumにはPOSITION_VOLUMEとPOSITION_PRICE_OPENの積を加算し、totalVolumeにはPOSITION_VOLUMEを加算します。totalVolumeが0でなければ、averagePriceを「weightedOpenSum / totalVolume」で算出して返します。この関数により、取引シグナルの重要なパターン分析や、ナンピンや利確の調整のための正確な平均取得価格を計算することができます。ポジションの情報は、まず各ポジションの指標を取得してから処理する必要があります。そのためのロジックを定義しましょう。

//+------------------------------------------------------------------+
//| Calculate position metrics                                       |
//+------------------------------------------------------------------+
void calculatePositionMetrics() {
   buyCount = 0;                                  //--- Reset buy count
   currentBuyLot = 0;                             //--- Reset current buy lot
   totalBuyLots = 0;                              //--- Reset total buy lots
   sellCount = 0;                                 //--- Reset sell count
   currentSellLot = 0;                            //--- Reset current sell lot
   totalSellLots = 0;                             //--- Reset total sell lots
   totalSum = 0;                                  //--- Reset total sum
   totalSwap = 0;                                 //--- Reset total swap
   buyProfit = 0;                                 //--- Reset buy profit
   sellProfit = 0;                                //--- Reset sell profit
   buyWeightedSum = 0;                            //--- Reset buy weighted sum
   sellWeightedSum = 0;                           //--- Reset sell weighted sum
   buyBreakEvenPrice = 0;                         //--- Reset buy breakeven price
   sellBreakEvenPrice = 0;                        //--- Reset sell breakeven price
   minBuyLot = 9999;                              //--- Initialize min buy lot
   minSellLot = 9999;                             //--- Initialize min sell lot
   maxSellPrice = 0;                              //--- Initialize max sell price
   minBuyPrice = 999999999;                       //--- Initialize min buy price
   for (int i = 0; i < PositionsTotal(); i++) {   //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);        //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol()) continue; //--- Skip non-matching symbols
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
         buyCount++;                              //--- Increment buy count
         totalOperations++;                       //--- Increment total operations
         currentBuyLot = PositionGetDouble(POSITION_VOLUME); //--- Set current buy lot
         buyProfit += PositionGetDouble(POSITION_PROFIT); //--- Add buy profit
         totalBuyLots += PositionGetDouble(POSITION_VOLUME); //--- Add to total buy lots
         minBuyLot = MathMin(minBuyLot, PositionGetDouble(POSITION_VOLUME)); //--- Update min buy lot
         buyWeightedSum += PositionGetDouble(POSITION_VOLUME) * PositionGetDouble(POSITION_PRICE_OPEN); //--- Add weighted open price
         minBuyPrice = MathMin(minBuyPrice, PositionGetDouble(POSITION_PRICE_OPEN)); //--- Update min buy price
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
         sellCount++;                             //--- Increment sell count
         totalOperations++;                       //--- Increment total operations
         currentSellLot = PositionGetDouble(POSITION_VOLUME); //--- Set current sell lot
         sellProfit += PositionGetDouble(POSITION_PROFIT); //--- Add sell profit
         totalSellLots += PositionGetDouble(POSITION_VOLUME); //--- Add to total sell lots
         minSellLot = MathMin(minSellLot, PositionGetDouble(POSITION_VOLUME)); //--- Update min sell lot
         sellWeightedSum += PositionGetDouble(POSITION_VOLUME) * PositionGetDouble(POSITION_PRICE_OPEN); //--- Add weighted open price
         maxSellPrice = MathMax(maxSellPrice, PositionGetDouble(POSITION_PRICE_OPEN)); //--- Update max sell price
      }
   }
   if (totalBuyLots > 0) {                        //--- Check buy lots
      buyBreakEvenPrice = buyWeightedSum / totalBuyLots; //--- Calculate buy breakeven
   }
   if (totalSellLots > 0) {                       //--- Check sell lots
      sellBreakEvenPrice = sellWeightedSum / totalSellLots; //--- Calculate sell breakeven
   }
}

複数ポジションを効果的に管理するための重要な指標を計算するために、calculatePositionMetrics関数を実装します。まず、正確な追跡のために主要な変数を0またはそれぞれの初期値にリセットします。次に、PositionsTotalですべてのポジションを順番に処理し、PositionGetTicketで各ポジションのチケット番号を取得します。無効なチケットや対象銘柄と一致しない場合はPositionGetStringを使用してスキップします。買いポジション(POSITION_TYPE_BUY)の場合は、buyCountおよびtotalOperationsをインクリメントし、currentBuyLotを設定し、POSITION_PROFITをbuyProfitに加算、POSITION_VOLUMEをtotalBuyLotsに加算します。minBuyLotはMathMinで更新し、加重平均の計算用にbuyWeightedSumにPOSITION_VOLUMEとPOSITION_PRICE_OPENの積を加算し、minBuyPriceを更新します。売りポジション(POSITION_TYPE_SELL)の場合も同様に、売り指標を更新します。最後に、totalBuyLotsが正の値であればbuyBreakEvenPriceを「buyWeightedSum / totalBuyLots」で算出し、totalSellLotsが正の値であればsellBreakEvenPriceを「sellWeightedSum / totalSellLots」で算出します。これにより、ブレークイーブン管理のための加重平均価格を算出し、ナンピンやリスク管理のためのポジション指標を正確に追跡することができます。これらの関数が揃ったことで、ポジション建てのロジックを開始する準備が整いました。実装はOnTickイベントハンドラ内でおこないます。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   static datetime previousBarTime = 0;           //--- Store previous bar time
   if (previousBarTime != iTime(Symbol(), PERIOD_CURRENT, 0)) { //--- Check new bar
      previousBarTime = iTime(Symbol(), PERIOD_CURRENT, 0); //--- Update previous bar time
      ChartRedraw(0);                             //--- Redraw chart
   } else {
      return;                                     //--- Exit if not new bar
   }
   if (iVolume(Symbol(), PERIOD_H4, 0) > iVolume(Symbol(), PERIOD_H4, 1)) return; //--- Exit if volume increased
   double supportResistanceLevel = NormalizeDouble(iClose(Symbol(), PERIOD_H4, 1), _Digits); //--- Get support/resistance level
   ObjectDelete(0, "level");                      //--- Delete existing level line
   MakeLine(supportResistanceLevel);              //--- Draw support/resistance line
   if (SymbolInfoInteger(Symbol(), SYMBOL_SPREAD) > 150) return; //--- Exit if spread too high
   int totalBuyPositions = 0;                     //--- Initialize buy positions count
   int totalSellPositions = 0;                    //--- Initialize sell positions count
   for (int i = 0; i < PositionsTotal(); i++) {   //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);        //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol() || PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching positions
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
         totalBuyPositions++;                     //--- Increment buy count
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
         totalSellPositions++;                    //--- Increment sell count
      }
   }
}

OnTickイベントハンドラ内では、ピンバーを利用したナンピンシステムの初期ロジックを実装し、各新しいバーごとに取引判断と視覚的な更新を管理します。まず、previousBarTime(staticで初期値0)と、iTimeで取得した現在の銘柄、時間足、shift 0のバー時間を比較し、新しいバーが形成されているかどうかを確認します。新しいバーであればpreviousBarTimeを更新し、ChartRedrawを呼び出します。新しいバーでなければ処理を終了します。

次に、現在のH4バーの出来高をiVolumeで取得し、前のバーの出来高を上回る場合は高ボラティリティ期間として処理を終了します。続いて、サポート/レジスタンスレベルを計算します。前のH4バーの終値をiCloseで取得し、NormalizeDoubleで正規化します。既存のlevelラインがあればObjectDeleteで削除し、MakeLine関数で新たに水平線を描画します。最後に、SymbolInfoIntegerでスプレッドを確認し、150ポイントを超える場合は処理を終了します。開いているポジションの数はPositionsTotalで反復処理し、PositionGetTicketでチケットを取得、無効または対象銘柄・magicNumberと一致しないポジションはスキップします。PositionGetIntegerで判定した買いポジションはtotalBuyPositionsを、売りポジションはtotalSellPositionsをインクリメントします。この初期設定により、EAは有利な条件下でのみ新しいバーに対して取引を処理し、視覚的な参照情報を常に更新できます。コンパイルすると、次の結果が得られます。

サポートレジスタンスレベルマーカー

画像からわかるように、サポートおよびレジスタンスレベルをチャート上に動的に表示しています。次のステップとして、ポジションも動的に反映させる処理を追加する必要があります。

if (CheckMarketBuyOrders() < 70 && CheckMarketSellOrders() < 70) { //--- Check order limits
   if (supportResistanceLevel > iOpen(Symbol(), PERIOD_CURRENT, 0) && useSignalMode == DISABLED) { //--- Check buy condition
      if (IsBuyPinbar() && totalBuyPositions < maxOrders && (isTradingAllowed() || totalBuyPositions > 0)) { //--- Check buy pinbar and limits
         double buyStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) - stopLossPips * normalizedPoint, _Digits); //--- Calculate buy stop loss
         double buyTakeProfit = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + takeProfitPips * normalizedPoint, _Digits); //--- Calculate buy take profit
         if (AccountFreeMarginCheck(Symbol(), ORDER_TYPE_BUY, GetLots()) > 0) { //--- Check margin
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, GetLots(), SymbolInfoDouble(_Symbol, SYMBOL_ASK), buyStopLoss, buyTakeProfit, orderComment); //--- Open buy position
            if (useAutoTakeProfit) {             //--- Check auto take profit
               ModifyTP(ORDER_TYPE_BUY, rata_price(ORDER_TYPE_BUY) + takeProfitPips * normalizedPoint); //--- Modify take profit
            }
            CloseSell();                         //--- Close sell positions
         }
      }
   }
   if (supportResistanceLevel < iOpen(Symbol(), PERIOD_CURRENT, 0) && useSignalMode == DISABLED) { //--- Check sell condition
      if (IsSellPinbar() && totalSellPositions < maxOrders && (isTradingAllowed() || totalSellPositions > 0)) { //--- Check sell pinbar and limits
         double sellStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) + stopLossPips * normalizedPoint, _Digits); //--- Calculate sell stop loss
         double sellTakeProfit = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - takeProfitPips * normalizedPoint, _Digits); //--- Calculate sell take profit
         if (AccountFreeMarginCheck(Symbol(), ORDER_TYPE_SELL, GetLots()) > 0) { //--- Check margin
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, GetLots(), SymbolInfoDouble(_Symbol, SYMBOL_BID), sellStopLoss, sellTakeProfit, orderComment); //--- Open sell position
            if (useAutoTakeProfit) {             //--- Check auto take profit
               ModifyTP(ORDER_TYPE_SELL, rata_price(ORDER_TYPE_SELL) - takeProfitPips * normalizedPoint); //--- Modify take profit
            }
            CloseBuy();                          //--- Close buy positions
         }
      }
   }
}
if (CountTrades() == 0) {                       //--- Check no trades
   if (supportResistanceLevel > iOpen(Symbol(), PERIOD_CURRENT, 0) && useSignalMode == ENABLED) { //--- Check buy signal mode
      if (IsBuyPinbar() && CountTrades() < maxOrders) { //--- Check buy pinbar and limit
         obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, GetLots(), SymbolInfoDouble(_Symbol, SYMBOL_ASK), SymbolInfoDouble(_Symbol, SYMBOL_ASK) - stopLossPips * normalizedPoint, SymbolInfoDouble(_Symbol, SYMBOL_ASK) + (takeProfitPips * normalizedPoint), orderComment); //--- Open buy position
      }
   }
}
if (CountTrades() == 0) {                       //--- Check no trades
   if (supportResistanceLevel < iOpen(Symbol(), PERIOD_CURRENT, 0) && useSignalMode == ENABLED) { //--- Check sell signal mode
      if (IsSellPinbar() && CountTrades() < maxOrders) { //--- Check sell pinbar and limit
         obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, GetLots(), SymbolInfoDouble(_Symbol, SYMBOL_BID), SymbolInfoDouble(_Symbol, SYMBOL_BID) + stopLossPips * normalizedPoint, SymbolInfoDouble(_Symbol, SYMBOL_BID) - (takeProfitPips * normalizedPoint), orderComment); //--- Open sell position
      }
   }
}

OnTick関数の実装を続け、ピンバーシグナルと市場状況に基づいて新規ポジションを建てるロジックを追加します。まず、CheckMarketBuyOrdersおよびCheckMarketSellOrdersを使用して、買いおよび売りの保有ポジション数が70未満であることを確認し、EAが実務上の制限を超えないようにします。次に、useSignalModeがDISABLEDの場合、買い条件を評価します。supportResistanceLevelがiOpenで取得した現在の始値を上回り、IsBuyPinbarで買いピンバーが検出され、totalBuyPositionsがmaxOrders未満で、isTradingAllowedがtrueまたは既存の買いポジションが存在する場合、SymbolInfoDoubleを使用してstopLossPipsおよびtakeProfitPipsをnormalizedPointで調整し、buyStopLossおよびbuyTakeProfitを計算します。次に、AccountFreeMarginCheckで証拠金を確認し、obj_Trade.PositionOpenを用いて買いポジションを建てます。

useAutoTakeProfitがtrueの場合はModifyTPで利確を修正し、CloseSellで売りポジションをクローズします。売り条件の場合も同様で、supportResistanceLevelが始値を下回り、IsSellPinbarで売りピンバーが検出され、totalSellPositionsがmaxOrders未満で、isTradingAllowedがtrueまたは既存の売りポジションが存在する場合、stopLossおよびtakeProfitを計算し、証拠金を確認した上でobj_Trade.PositionOpenで売りポジションを建てます。useAutoTakeProfitがtrueの場合はModifyTPで利確を修正し、CloseBuyで買いポジションをクローズします。さらに、既存取引が存在せず(CountTradesが0)かつuseSignalModeがENABLEDの場合は、買いピンバーが検出されCountTradesがmaxOrders未満であれば、obj_Trade.PositionOpenで買いポジションを建て、売りピンバーが検出されCountTradesがmaxOrders未満であれば売りポジションを建てます。これにより、EAはピンバーシグナルに基づき、主要レベルで適切なリスク管理をおこないながらポジションを建てることができます。コンパイルすると、次の結果が得られます。

確認信号

これでシグナルを確認してポジションを建てられるようになったので、次はシグナルの管理をおこなう必要があります。そのためにいくつかの関数を定義します。

//+------------------------------------------------------------------+
//| Update stop loss and take profit                                 |
//+------------------------------------------------------------------+
void updateStopLossTakeProfit() {
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);           //--- Get position ticket
      if (ticket == 0) continue;                     //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol()) continue; //--- Skip non-matching symbols
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
         double buyTakeProfitLevel = (buyBreakEvenPrice + takeProfitPips * _Point) * (takeProfitPips > 0); //--- Calculate buy take profit
         double buyStopLossLevel = PositionGetDouble(POSITION_SL); //--- Get current stop loss
         if (slBreakevenMinus > 0) {                 //--- Check breakeven adjustment
            buyStopLossLevel = (buyBreakEvenPrice - slBreakevenMinus * _Point); //--- Set breakeven stop loss
         }
         if (buyCount == 1) {                        //--- Check single buy position
            buyTakeProfitLevel = NormalizePrice(PositionGetDouble(POSITION_PRICE_OPEN) + takeProfitPips * _Point) * (takeProfitPips > 0); //--- Set take profit
            if (laterUseSL > 0) {                    //--- Check unused stop loss
               buyStopLossLevel = (PositionGetDouble(POSITION_PRICE_OPEN) - laterUseSL * _Point); //--- Set stop loss
            }
         }
         buyTakeProfitLevel = NormalizePrice(buyTakeProfitLevel); //--- Normalize take profit
         buyStopLossLevel = NormalizePrice(buyStopLossLevel); //--- Normalize stop loss
         if (SymbolInfoDouble(_Symbol, SYMBOL_BID) >= buyTakeProfitLevel && buyTakeProfitLevel > 0) { //--- Check take profit hit
            obj_Trade.PositionClose(ticket);         //--- Close position
         }
         if (SymbolInfoDouble(_Symbol, SYMBOL_BID) <= buyStopLossLevel) { //--- Check stop loss hit
            obj_Trade.PositionClose(ticket);         //--- Close position
         }
         if (NormalizePrice(PositionGetDouble(POSITION_TP)) != buyTakeProfitLevel || NormalizePrice(PositionGetDouble(POSITION_SL)) != buyStopLossLevel) { //--- Check modification needed
            obj_Trade.PositionModify(ticket, buyStopLossLevel, buyTakeProfitLevel); //--- Modify position
         }
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
         double sellTakeProfitLevel = (sellBreakEvenPrice - takeProfitPips * _Point) * (takeProfitPips > 0); //--- Calculate sell take profit
         double sellStopLossLevel = PositionGetDouble(POSITION_SL); //--- Get current stop loss
         if (slBreakevenMinus > 0) {                //--- Check breakeven adjustment
            sellStopLossLevel = (sellBreakEvenPrice + slBreakevenMinus * _Point); //--- Set breakeven stop loss
         }
         if (sellCount == 1) {                      //--- Check single sell position
            sellTakeProfitLevel = (PositionGetDouble(POSITION_PRICE_OPEN) - takeProfitPips * _Point) * (takeProfitPips > 0); //--- Set take profit
            if (laterUseSL > 0) {                   //--- Check unused stop loss
               sellStopLossLevel = (PositionGetDouble(POSITION_PRICE_OPEN) + laterUseSL * _Point); //--- Set stop loss
            }
         }
         sellTakeProfitLevel = NormalizePrice(sellTakeProfitLevel); //--- Normalize take profit
         sellStopLossLevel = NormalizePrice(sellStopLossLevel); //--- Normalize stop loss
         if (SymbolInfoDouble(_Symbol, SYMBOL_ASK) <= sellTakeProfitLevel) { //--- Check take profit hit
            obj_Trade.PositionClose(ticket);        //--- Close position
         }
         if (SymbolInfoDouble(_Symbol, SYMBOL_ASK) >= sellStopLossLevel && sellStopLossLevel > 0) { //--- Check stop loss hit
            obj_Trade.PositionClose(ticket);        //--- Close position
         }
         if (NormalizePrice(PositionGetDouble(POSITION_TP)) != sellTakeProfitLevel || NormalizePrice(PositionGetDouble(POSITION_SL)) != sellStopLossLevel) { //--- Check modification needed
            obj_Trade.PositionModify(ticket, sellStopLossLevel, sellTakeProfitLevel); //--- Modify position
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Add averaging order                                              |
//+------------------------------------------------------------------+
void addAveragingOrder() {
   int positionIndex = 0;                         //--- Initialize position index
   double lastOpenPrice = 0;                      //--- Initialize last open price
   double lastLotSize = 0;                        //--- Initialize last lot size
   bool isLastBuy = false;                        //--- Initialize buy flag
   int totalBuyPositions = 0;                     //--- Initialize buy positions count
   int totalSellPositions = 0;                    //--- Initialize sell positions count
   long currentSpread = SymbolInfoInteger(Symbol(), SYMBOL_SPREAD); //--- Get current spread
   double supportResistanceLevel = iClose(Symbol(), PERIOD_H4, 1); //--- Get support/resistance level
   for (positionIndex = 0; positionIndex < PositionsTotal(); positionIndex++) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(positionIndex); //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check buy position
         if (lastOpenPrice == 0) {                //--- Check initial price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set initial price
         }
         if (lastOpenPrice > PositionGetDouble(POSITION_PRICE_OPEN)) { //--- Check lower price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Update last price
         }
         if (lastLotSize < PositionGetDouble(POSITION_VOLUME)) { //--- Check larger lot
            lastLotSize = PositionGetDouble(POSITION_VOLUME); //--- Update lot size
         }
         isLastBuy = true;                        //--- Set buy flag
         totalBuyPositions++;                     //--- Increment buy count
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check sell position
         if (lastOpenPrice == 0) {                //--- Check initial price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set initial price
         }
         if (lastOpenPrice < PositionGetDouble(POSITION_PRICE_OPEN)) { //--- Check higher price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Update last price
         }
         if (lastLotSize < PositionGetDouble(POSITION_VOLUME)) { //--- Check larger lot
            lastLotSize = PositionGetDouble(POSITION_VOLUME); //--- Update lot size
         }
         isLastBuy = false;                       //--- Clear buy flag
         totalSellPositions++;                    //--- Increment sell count
      }
   }
   if (isLastBuy) {                               //--- Check buy position
      if (supportResistanceLevel > iOpen(Symbol(), PERIOD_CURRENT, 0)) { //--- Check buy condition
         if (IsBuyPinbar() && SymbolInfoDouble(_Symbol, SYMBOL_BID) <= lastOpenPrice - (orderDistancePips * _Point)) { //--- Check buy pinbar and distance
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, NormalizeDouble((lastLotSize * lotMultiplier), fnGetLotDigit()), SymbolInfoDouble(_Symbol, SYMBOL_ASK), SymbolInfoDouble(_Symbol, SYMBOL_ASK) - stopLossPips * normalizedPoint, SymbolInfoDouble(_Symbol, SYMBOL_ASK) + (takeProfitPips * normalizedPoint), orderComment); //--- Open buy position
            isLastBuy = false;                    //--- Clear buy flag
            return;                               //--- Exit function
         }
      }
   } else if (!isLastBuy) {                       //--- Check sell position
      if (supportResistanceLevel < iOpen(Symbol(), PERIOD_CURRENT, 0)) { //--- Check sell condition
         if (IsSellPinbar() && SymbolInfoDouble(_Symbol, SYMBOL_ASK) >= lastOpenPrice + (orderDistancePips * _Point)) { //--- Check sell pinbar and distance
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, NormalizeDouble((lastLotSize * lotMultiplier), fnGetLotDigit()), SymbolInfoDouble(_Symbol, SYMBOL_BID), SymbolInfoDouble(_Symbol, SYMBOL_BID) + stopLossPips * normalizedPoint, SymbolInfoDouble(_Symbol, SYMBOL_BID) - (takeProfitPips * normalizedPoint), orderComment); //--- Open sell position
            return;                               //--- Exit function
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Add averaging order with auto take profit                        |
//+------------------------------------------------------------------+
void addAveragingOrderWithAutoTP() {
   int positionIndex = 0;                         //--- Initialize position index
   double lastOpenPrice = 0;                      //--- Initialize last open price
   double lastLotSize = 0;                        //--- Initialize last lot size
   bool isLastBuy = false;                        //--- Initialize buy flag
   int totalBuyPositions = 0;                     //--- Initialize buy positions count
   int totalSellPositions = 0;                    //--- Initialize sell positions count
   long currentSpread = SymbolInfoInteger(Symbol(), SYMBOL_SPREAD); //--- Get current spread
   double supportResistanceLevel = iClose(Symbol(), PERIOD_H4, 1); //--- Get support/resistance level
   for (positionIndex = 0; positionIndex < PositionsTotal(); positionIndex++) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(positionIndex); //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check buy position
         if (lastOpenPrice == 0) {                //--- Check initial price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set initial price
         }
         if (lastOpenPrice > PositionGetDouble(POSITION_PRICE_OPEN)) { //--- Check lower price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Update last price
         }
         if (lastLotSize < PositionGetDouble(POSITION_VOLUME)) { //--- Check larger lot
            lastLotSize = PositionGetDouble(POSITION_VOLUME); //--- Update lot size
         }
         isLastBuy = true;                        //--- Set buy flag
         totalBuyPositions++;                     //--- Increment buy count
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check sell position
         if (lastOpenPrice == 0) {                //--- Check initial price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set initial price
         }
         if (lastOpenPrice < PositionGetDouble(POSITION_PRICE_OPEN)) { //--- Check higher price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Update last price
         }
         if (lastLotSize < PositionGetDouble(POSITION_VOLUME)) { //--- Check larger lot
            lastLotSize = PositionGetDouble(POSITION_VOLUME); //--- Update lot size
         }
         isLastBuy = false;                       //--- Clear buy flag
         totalSellPositions++;                    //--- Increment sell count
      }
   }
   if (isLastBuy) {                               //--- Check buy position
      if (supportResistanceLevel > iOpen(Symbol(), PERIOD_CURRENT, 0)) { //--- Check buy condition
         if (IsBuyPinbar() && SymbolInfoDouble(_Symbol, SYMBOL_BID) <= lastOpenPrice - (orderDistancePips * _Point)) { //--- Check buy pinbar and distance
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, NormalizeDouble((lastLotSize * lotMultiplier), fnGetLotDigit()), SymbolInfoDouble(_Symbol, SYMBOL_ASK), 0, 0, orderComment); //--- Open buy position
            calculatePositionMetrics();           //--- Calculate position metrics
            updateStopLossTakeProfit();           //--- Update stop loss and take profit
            isLastBuy = false;                    //--- Clear buy flag
            return;                               //--- Exit function
         }
      }
   } else if (!isLastBuy) {                       //--- Check sell position
      if (supportResistanceLevel < iOpen(Symbol(), PERIOD_CURRENT, 0)) { //--- Check sell condition
         if (IsSellPinbar() && SymbolInfoDouble(_Symbol, SYMBOL_ASK) >= lastOpenPrice + (orderDistancePips * _Point)) { //--- Check sell pinbar and distance
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, NormalizeDouble((lastLotSize * lotMultiplier), fnGetLotDigit()), SymbolInfoDouble(_Symbol, SYMBOL_BID), 0, 0, orderComment); //--- Open sell position
            calculatePositionMetrics();           //--- Calculate position metrics
            updateStopLossTakeProfit();           //--- Update stop loss and take profit
            return;                               //--- Exit function
         }
      }
   }
}

ここでは、SL、TP、およびナンピン注文を管理するために、updateStopLossTakeProfit関数、addAveragingOrder関数、およびaddAveragingOrderWithAutoTP関数を実装します。これにより、ポジションの動的な調整が可能となります。まず、updateStopLossTakeProfit関数を作成します。本関数ではすべてのポジションを順番に処理します。買いポジション(POSITION_TYPE_BUY)の場合、buyBreakEvenPriceに「takeProfitPips * _Point」を加算した値をbuyTakeProfitLevelとして計算します(takeProfitPipsが正の場合)。現在のSLはPositionGetDoubleで取得し、slBreakevenMinusが正の場合は「buyBreakEvenPrice - slBreakevenMinus * _Point」に調整します。また、単一ポジション(buyCount == 1)の場合は、POSITION_PRICE_OPENに対してTPおよびSLを設定します。両レベルはNormalizePriceで正規化し、Bid価格がTPまたはSLに到達した場合はobj_Trade.PositionCloseでポジションをクローズし、レベルが異なる場合はobj_Trade.PositionModifyで修正します。売りポジションについても、sellBreakEvenPriceおよびAsk価格を用いて同様のロジックを適用します。

次に、addAveragingOrder関数を実装します。本関数ではPositionsTotalを反復処理して最新ポジションを追跡し、lastOpenPriceを買いでは最安値、売りでは最高値に更新し、lastLotSizeを最大ロットに更新します。isLastBuyも設定します。買いの場合、supportResistanceLevelが現在の始値を上回り、IsBuyPinbarで買いピンバーが検出され、Bid価格がlastOpenPriceより「orderDistancePips * _Point」だけ下回っている場合、obj_Trade.PositionOpenでロットサイズを「lastLotSize * lotMultiplier」でfnGetLotDigitに基づき正規化し、計算済みのSLとTPで買いポジションを建て、isLastBuyをクリアします。売りの場合も同様に、Ask価格がlastOpenPriceより「orderDistancePips * _Point」だけ上回っている場合に売りポジションを建てます。

最後に、addAveragingOrderWithAutoTP関数を実装します。本関数はaddAveragingOrderと同じロジックを使用しますが、初期のSLやTPを0に設定してポジションを建てます。その後、calculatePositionMetricsを呼び出してbuyBreakEvenPriceなどの指標を更新し、updateStopLossTakeProfitを呼び出してブレークイーブンベースのレベルを設定します。これにより、ナンピンポジションの動的調整が可能となります。これらの関数は、ティックロジック内で呼び出すことで、実際の取引に反映されます。

if (useSignalMode == ENABLED && CountTradesBuy() >= 1 && CountTradesBuy() < maxOrders && useAutoTakeProfit == false) { //--- Check buy averaging
   addAveragingOrder();                        //--- Add buy averaging order
}
if (useSignalMode == ENABLED && CountTradesSell() >= 1 && CountTradesSell() < maxOrders && useAutoTakeProfit == false) { //--- Check sell averaging
   addAveragingOrder();                        //--- Add sell averaging order
}
if (useSignalMode == ENABLED && CountTradesBuy() >= 1 && CountTradesBuy() < maxOrders && useAutoTakeProfit == true) { //--- Check buy averaging with auto TP
   addAveragingOrderWithAutoTP();              //--- Add buy averaging order with auto TP
}
if (useSignalMode == ENABLED && CountTradesSell() >= 1 && CountTradesSell() < maxOrders && useAutoTakeProfit == true) { //--- Check sell averaging with auto TP
   addAveragingOrderWithAutoTP();              //--- Add sell averaging order with auto TP
}

ティックロジックの実装を完了するため、特定条件下でのナンピンポジションの処理ロジックを追加し、EAがポジションを動的にスケールさせる能力を強化します。まず、useSignalModeがENABLEDの場合、CountTradesBuyで少なくとも1つの買いポジションが存在し、買いポジション数がmaxOrders未満であるかどうかどうかを確認します。useAutoTakeProfitがfalseの場合、addAveragingOrderを呼び出し、ピンバー検出および価格距離の条件に基づいて追加の買いポジションを、乗算されたロットサイズで建てます。

次に、同様のロジックを売りポジションに適用します。CountTradesSellを確認し、useAutoTakeProfitがfalseであれば、addAveragingOrderを呼び出して同様の条件下で売りポジションを追加します。続いて、useAutoTakeProfitがtrueの場合、買いポジションに対してaddAveragingOrderWithAutoTPを呼び出し、初期のSLおよびTPなしで買いポジションを建て、その後calculatePositionMetricsで指標を更新し、ブレークイーブンベースのレベルを調整します。最後に、売りポジションでも同様に、useAutoTakeProfitがtrueの場合はaddAveragingOrderWithAutoTPを呼び出し、動的なSLおよびTPの調整をおこないながら売りポジションを追加します。このロジックにより、EAはシグナルモードでナンピンポジションを効果的に管理し、市場の動きに応じて柔軟に対応することが可能となります。コンパイルすると、次の結果が得られます。

平均サンプル

ナンピン機能を追加したので、次に残るのはリスク管理のためのトレーリングストップロジックの追加です。このロジックは正確なリスク管理のために毎ティック実行する必要があるため、バー制限ロジックの外で実装します。

double setPointValue = normalizedPoint;         //--- Set point value for calculations
if (useTrailingStop && trailingStartPips > 0 && breakevenPips < trailingStartPips) { //--- Check trailing stop conditions
   double averageBuyPrice = rata_price(ORDER_TYPE_BUY); //--- Calculate average buy price
   double trailingReference = 0;                //--- Initialize trailing reference
   for (int iTrade = 0; iTrade < PositionsTotal(); iTrade++) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(iTrade); //--- Get position ticket
      if (ticket == 0) continue;                //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check buy position
         if (useAutoTakeProfit) {               //--- Check auto take profit
            trailingReference = averageBuyPrice; //--- Use average buy price
         } else {                               //--- Use open price
            trailingReference = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set open price
         }
         if (SymbolInfoDouble(_Symbol, SYMBOL_BID) - trailingReference > trailingStartPips * setPointValue) { //--- Check trailing condition
            if (SymbolInfoDouble(_Symbol, SYMBOL_BID) - ((trailingStartPips - breakevenPips) * setPointValue) > PositionGetDouble(POSITION_SL)) { //--- Check stop loss adjustment
               obj_Trade.PositionModify(ticket, SymbolInfoDouble(_Symbol, SYMBOL_BID) - ((trailingStartPips - breakevenPips) * setPointValue), PositionGetDouble(POSITION_TP)); //--- Modify position
            }
         }
      }
   }
   double averageSellPrice = rata_price(ORDER_TYPE_SELL); //--- Calculate average sell price
   for (int iTrade2 = 0; iTrade2 < PositionsTotal(); iTrade2++) { //--- Iterate through positions
      ulong ticket2 = PositionGetTicket(iTrade2); //--- Get position ticket
      if (ticket2 == 0) continue;               //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check sell position
         if (useAutoTakeProfit) {               //--- Check auto take profit
            trailingReference = averageSellPrice; //--- Use average sell price
         } else {                               //--- Use open price
            trailingReference = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set open price
         }
         if (trailingReference - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > trailingStartPips * setPointValue) { //--- Check trailing condition
            if (SymbolInfoDouble(_Symbol, SYMBOL_ASK) + ((trailingStartPips - breakevenPips) * setPointValue) < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0) { //--- Check stop loss adjustment
               obj_Trade.PositionModify(ticket2, SymbolInfoDouble(_Symbol, SYMBOL_ASK) + ((trailingStartPips - breakevenPips) * setPointValue), PositionGetDouble(POSITION_TP)); //--- Modify position
            }
         }
      }
   }
}

トレーリングストップのロジックは、まずsetPointValueをnormalizedPointに設定して価格計算の一貫性を確保し、useTrailingStopがtrueであり、trailingStartPipsが正の値で、かつbreakevenPipsがtrailingStartPips未満であることを確認して、トレーリング条件が有効であることをチェックするところから実装します。次に、買いポジションを処理します。rata_priceを使用してORDER_TYPE_BUYのaverageBuyPriceを計算し、すべてのポジションを反復処理してSymbolおよびmagicNumberに一致する有効な買いポジションのチケットを取得します。trailingReferenceはuseAutoTakeProfitがtrueの場合はaverageBuyPrice、そうでなければPOSITION_PRICE_OPENに設定します。Bid価格がtrailingReferenceより「trailingStartPips * setPointValue」だけ上回り、新しいSLが現在のSLより高い場合は、obj_Trade.PositionModifyでSLを「SYMBOL_BID - (trailingStartPips - breakevenPips) * setPointValue」に修正します。

売りポジションについても同様のロジックを適用します。ORDER_TYPE_SELLのaverageSellPriceをrata_priceで計算し、すべてのポジションを反復処理してtrailingReferenceをaverageSellPriceまたはPPOSITION_PRICE_OPENに設定します。Ask価格がtrailingReferenceより「trailingStartPips * setPointValue」だけ下回り、新しいSLが現在のSLより低い、または未設定の場合は、obj_Trade.PositionModifyでSLを「SYMBOL_ASK + (trailingStartPips - breakevenPips) * setPointValue」に修正します。最後に、「PositionGetDouble(POSITION_TP)」を介して変更によって既存のTPが維持されることを確認し、親関数でChartRedrawを呼び出してチャートを更新します。コンパイルすると、次の結果が得られます。

トレーリングストップ適用前

トレーリングストップ適用前

トレーリングストップ適用後

トレーリングストップ適用後

ポジション管理ロジックが完成したので、次は口座の各種指標を可視化するダッシュボードを作成します。管理を容易にするため、これも専用の関数として実装します。

//+------------------------------------------------------------------+
//| Display dashboard information                                    |
//+------------------------------------------------------------------+
void Display_Info() {
   buyCount = 0;                                  //--- Reset buy count
   currentBuyLot = 0;                             //--- Reset current buy lot
   totalBuyLots = 0;                              //--- Reset total buy lots
   sellCount = 0;                                 //--- Reset sell count
   currentSellLot = 0;                            //--- Reset current sell lot
   totalSellLots = 0;                             //--- Reset total sell lots
   totalSum = 0;                                  //--- Reset total sum
   totalSwap = 0;                                 //--- Reset total swap
   buyProfit = 0;                                 //--- Reset buy profit
   sellProfit = 0;                                //--- Reset sell profit
   buyWeightedSum = 0;                            //--- Reset buy weighted sum
   sellWeightedSum = 0;                           //--- Reset sell weighted sum
   buyBreakEvenPrice = 0;                         //--- Reset buy breakeven price
   sellBreakEvenPrice = 0;                        //--- Reset sell breakeven price
   minBuyLot = 9999;                              //--- Initialize min buy lot
   minSellLot = 9999;                             //--- Initialize min sell lot
   maxSellPrice = 0;                              //--- Initialize max sell price
   minBuyPrice = 999999999;                       //--- Initialize min buy price
   for (int i = 0; i < PositionsTotal(); i++) {   //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);        //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol()) continue; //--- Skip non-matching symbols
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
         buyCount++;                              //--- Increment buy count
         totalOperations++;                       //--- Increment total operations
         currentBuyLot = PositionGetDouble(POSITION_VOLUME); //--- Set current buy lot
         buyProfit += PositionGetDouble(POSITION_PROFIT); //--- Add buy profit
         totalBuyLots += PositionGetDouble(POSITION_VOLUME); //--- Add to total buy lots
         minBuyLot = MathMin(minBuyLot, PositionGetDouble(POSITION_VOLUME)); //--- Update min buy lot
         buyWeightedSum += PositionGetDouble(POSITION_VOLUME) * PositionGetDouble(POSITION_PRICE_OPEN); //--- Add weighted open price
         minBuyPrice = MathMin(minBuyPrice, PositionGetDouble(POSITION_PRICE_OPEN)); //--- Update min buy price
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
         sellCount++;                             //--- Increment sell count
         totalOperations++;                       //--- Increment total operations
         currentSellLot = PositionGetDouble(POSITION_VOLUME); //--- Set current sell lot
         sellProfit += PositionGetDouble(POSITION_PROFIT); //--- Add sell profit
         totalSellLots += PositionGetDouble(POSITION_VOLUME); //--- Add to total sell lots
         minSellLot = MathMin(minSellLot, PositionGetDouble(POSITION_VOLUME)); //--- Update min sell lot
         sellWeightedSum += PositionGetDouble(POSITION_VOLUME) * PositionGetDouble(POSITION_PRICE_OPEN); //--- Add weighted open price
         maxSellPrice = MathMax(maxSellPrice, PositionGetDouble(POSITION_PRICE_OPEN)); //--- Update max sell price
      }
   }
   if (totalBuyLots > 0) {                        //--- Check buy lots
      buyBreakEvenPrice = buyWeightedSum / totalBuyLots; //--- Calculate buy breakeven
   }
   if (totalSellLots > 0) {                       //--- Check sell lots
      sellBreakEvenPrice = sellWeightedSum / totalSellLots; //--- Calculate sell breakeven
   }
   int minutesRemaining, secondsRemaining;        //--- Declare time variables
   minutesRemaining = (int)(PeriodSeconds() - (TimeCurrent() - iTime(Symbol(), PERIOD_CURRENT, 0))); //--- Calculate remaining time
   secondsRemaining = minutesRemaining % 60;      //--- Calculate seconds
   minutesRemaining = minutesRemaining / 60;      //--- Calculate minutes
   long currentSpread = SymbolInfoInteger(Symbol(), SYMBOL_SPREAD); //--- Get current spread
   string spreadPrefix = "", minutesPrefix = "", secondsPrefix = ""; //--- Initialize prefixes
   if (currentSpread < 10) spreadPrefix = "..";   //--- Set spread prefix for single digit
   else if (currentSpread < 100) spreadPrefix = "."; //--- Set spread prefix for double digit
   if (minutesRemaining < 10) minutesPrefix = "0"; //--- Set minutes prefix
   if (secondsRemaining < 10) secondsPrefix = "0"; //--- Set seconds prefix
   int blinkingColorIndex;                        //--- Declare blinking color index
   color equityColor = clrGreen;                  //--- Initialize equity color
   if (AccountInfoDouble(ACCOUNT_EQUITY) - AccountInfoDouble(ACCOUNT_BALANCE) < 0.0) { //--- Check negative equity
      equityColor = clrRed;                       //--- Set equity color to red
   }
   color profitColor = (buyProfit + sellProfit >= 0) ? clrGreen : clrRed; //--- Set profit color
   MqlDateTime currentDateTime;                   //--- Declare datetime structure
   TimeToStruct(TimeCurrent(), currentDateTime);  //--- Convert current time
   if (currentDateTime.sec >= 0 && currentDateTime.sec < 10) { //--- Check first 10 seconds
      blinkingColorIndex = clrRed;                //--- Set red color
   }
   if (currentDateTime.sec >= 10 && currentDateTime.sec < 20) { //--- Check next 10 seconds
      blinkingColorIndex = clrOrange;             //--- Set orange color
   }
   if (currentDateTime.sec >= 20 && currentDateTime.sec < 30) { //--- Check next 10 seconds
      blinkingColorIndex = clrBlue;               //--- Set blue color
   }
   if (currentDateTime.sec >= 30 && currentDateTime.sec < 40) { //--- Check next 10 seconds
      blinkingColorIndex = clrDodgerBlue;         //--- Set dodger blue color
   }
   if (currentDateTime.sec >= 40 && currentDateTime.sec < 50) { //--- Check next 10 seconds
      blinkingColorIndex = clrYellow;             //--- Set yellow color
   }
   if (currentDateTime.sec >= 50 && currentDateTime.sec <= 59) { //--- Check last 10 seconds
      blinkingColorIndex = clrYellow;             //--- Set yellow color
   }
   if (ObjectFind(0, "DashboardBG") < 0) {        //--- Check dashboard background
      ObjectCreate(0, "DashboardBG", OBJ_RECTANGLE_LABEL, 0, 0, 0); //--- Create dashboard background
      ObjectSetInteger(0, "DashboardBG", OBJPROP_CORNER, 0); //--- Set corner
      ObjectSetInteger(0, "DashboardBG", OBJPROP_XDISTANCE, 100); //--- Set x distance
      ObjectSetInteger(0, "DashboardBG", OBJPROP_YDISTANCE, 20); //--- Set y distance
      ObjectSetInteger(0, "DashboardBG", OBJPROP_XSIZE, 260); //--- Set width
      ObjectSetInteger(0, "DashboardBG", OBJPROP_YSIZE, 300); //--- Set height
      ObjectSetInteger(0, "DashboardBG", OBJPROP_BGCOLOR, clrLightGray); //--- Set background color
      ObjectSetInteger(0, "DashboardBG", OBJPROP_BORDER_TYPE, BORDER_FLAT); //--- Set border type
      ObjectSetInteger(0, "DashboardBG", OBJPROP_COLOR, clrBlack); //--- Set border color
      ObjectSetInteger(0, "DashboardBG", OBJPROP_BACK, false); //--- Set to foreground
   }
   if (ObjectFind(0, "CLOSE ALL") < 0) {          //--- Check close all button
      ObjectCreate(0, "CLOSE ALL", OBJ_BUTTON, 0, 0, 0); //--- Create close all button
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_CORNER, 0); //--- Set corner
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_XDISTANCE, 110); //--- Set x distance
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_YDISTANCE, 280); //--- Set y distance
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_XSIZE, 240); //--- Set width
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_YSIZE, 25); //--- Set height
      ObjectSetString(0, "CLOSE ALL", OBJPROP_TEXT, "Close All Positions"); //--- Set button text
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_COLOR, clrWhite); //--- Set text color
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_BGCOLOR, clrRed); //--- Set background color
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_BORDER_COLOR, clrBlack); //--- Set border color
   }
   string headerText = "Pin Bar Averaging EA";    //--- Set header text
   LABEL("Header", "Impact", 20, 110, 20, clrNavy, 0, headerText); //--- Create header label
   string copyrightText = "Copyright 2025, Allan Munene Mutiiria"; //--- Set copyright text
   LABEL("Copyright", "Arial", 9, 110, 55, clrBlack, 0, copyrightText); //--- Create copyright label
   string linkText = "https://t.me/Forex_Algo_Trader"; //--- Set link text
   LABEL("Link", "Arial", 9, 110, 70, clrBlue, 0, linkText); //--- Create link label
   string accountHeader = "Account Information";  //--- Set account header
   LABEL("AccountHeader", "Arial Bold", 10, 110, 90, clrBlack, 0, accountHeader); //--- Create account header label
   string balanceText = "Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2); //--- Set balance text
   LABEL("Balance", "Arial", 9, 120, 105, clrBlack, 0, balanceText); //--- Create balance label
   string equityText = "Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2); //--- Set equity text
   LABEL("Equity", "Arial", 9, 120, 120, equityColor, 0, equityText); //--- Create equity label
   string marginText = "Free Margin: " + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_FREE), 2); //--- Set margin text
   LABEL("Margin", "Arial", 9, 120, 135, clrBlack, 0, marginText); //--- Create margin label
   string profitText = "Open Profit: " + DoubleToString(buyProfit + sellProfit, 2); //--- Set profit text
   LABEL("Profit", "Arial", 9, 120, 150, profitColor, 0, profitText); //--- Create profit label
   string positionsText = "Buy Positions: " + IntegerToString((int)buyCount) + " Sell Positions: " + IntegerToString((int)sellCount); //--- Set positions text
   LABEL("Positions", "Arial", 9, 120, 165, clrBlack, 0, positionsText); //--- Create positions label
   string buyBEText = "Buy Break Even: " + (buyCount > 0 ? DoubleToString(buyBreakEvenPrice, _Digits) : "-"); //--- Set buy breakeven text
   LABEL("BuyBE", "Arial", 9, 120, 180, clrBlack, 0, buyBEText); //--- Create buy breakeven label
   string sellBEText = "Sell Break Even: " + (sellCount > 0 ? DoubleToString(sellBreakEvenPrice, _Digits) : "-"); //--- Set sell breakeven text
   LABEL("SellBE", "Arial", 9, 120, 195, clrBlack, 0, sellBEText); //--- Create sell breakeven label
   string spreadText = "Spread: " + spreadPrefix + IntegerToString((int)currentSpread) + " points"; //--- Set spread text
   LABEL("Spread", "Arial", 9, 120, 210, clrBlack, 0, spreadText); //--- Create spread label
   string timeText = "Time to next bar: " + minutesPrefix + IntegerToString(minutesRemaining) + ":" + secondsPrefix + IntegerToString(secondsRemaining); //--- Set time text
   LABEL("Time", "Arial", 9, 120, 225, clrBlack, 0, timeText); //--- Create time label
   string pinbarText;                             //--- Declare pinbar text
   if (IsBuyPinbar()) pinbarText = "Buy Pinbar";  //--- Check buy pinbar
   else if (IsSellPinbar()) pinbarText = "Sell Pinbar"; //--- Check sell pinbar
   else pinbarText = "None";                      //--- Set no pinbar
   LABEL("Pinbar", "Arial", 9, 120, 240, clrBlack, 0, "Pinbar Signal: " + pinbarText); //--- Create pinbar label
   string patternText = "Candle Pattern: " + CandleStick_Analyzer(); //--- Set candlestick pattern text
   LABEL("Pattern", "Arial", 9, 120, 255, clrBlack, 0, patternText); //--- Create pattern label
}

Display_Info関数を実装し、リアルタイムでの取引モニタリング用の包括的なダッシュボードを作成します。まず、buyCountなどの主要な指標をそれぞれ初期化値にリセットし、すべてのポジションを反復処理して、Symbolに一致する買い・売りポジションの指標を更新します。カウントのインクリメント、利益や取引量、加重平均取得価格の合計、最小/最大価格の追跡をおこない、必要に応じてブレークイーブン価格を計算します。

次に、次のバーまでの残り時間を計算します。PeriodSecondsから現在時刻とiTimeの差を引き、minutesRemainingおよびsecondsRemainingに変換し、スプレッド表示や時間表示のフォーマット用プレフィックスを設定します。さらに、equityColorを(資産が残高を上回る場合は緑、下回る場合は赤)、profitColorを(総利益が正なら緑、負なら赤)に設定し、視覚効果として現在秒数に基づく点滅色インデックスを設定します。最後に、ダッシュボードの背景を作成します。DashboardBGが存在しない場合はObjectCreateOBJ_RECTANGLE_LABELとして作成し、[CLOSE ALL]ボタンをOBJ_BUTTONとして作成します。また、LABEL関数を使用して複数のラベルを作成し、EAのタイトル、著作権情報、リンク、口座情報(残高、資産、余剰証拠金、利益)、ポジション数、ブレークイーブン価格、スプレッド、次のバーまでの時間、ピンバーシグナル、およびCandleStick_Analyzerから取得したローソク足パターンを表示します。これにより、取引情報を明確かつ動的に可視化できます。ボタンに関しては、OnChartEventイベントハンドラ内でロジックを実装します。

//+------------------------------------------------------------------+
//| Handle chart events                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   if (id == CHARTEVENT_OBJECT_CLICK) {           //--- Check object click event
      if (sparam == "CLOSE ALL") {                //--- Check close all button
         ObjectSetInteger(0, "CLOSE ALL", OBJPROP_STATE, false); //--- Reset button state
         for (int positionIndex = PositionsTotal() - 1; positionIndex >= 0; positionIndex--) { //--- Iterate through positions
            ulong ticket = PositionGetTicket(positionIndex); //--- Get position ticket
            if (ticket == 0) continue;            //--- Skip invalid tickets
            if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
               obj_Trade.PositionClose(ticket);   //--- Close position
            }
         }
      }
   }
}

OnChartEvent関数内では、イベントidがCHARTEVENT_OBJECT_CLICKであるかどうかを確認し、チャート上のオブジェクトクリックを検出します。次に、クリックされたオブジェクトsparamが[CLOSE ALL]ボタンであるかどうかを確認し、該当する場合はObjectSetIntegerでOBJPROP_STATEをfalseにリセットしてボタンの状態を初期化します。その後、すべてのポジションを反復処理し、PositionGetTicketで各ポジションのチケットを取得します。無効なチケットはスキップし、PositionGetStringでポジションの銘柄が現在のSymbolと一致するかどうかを確認します。最後に、条件に一致したポジションについて、obj_Trade.PositionCloseを使用してクローズ処理を実行します。これにより、ダッシュボード上の[Close All Positions]ボタンから、開いているすべてのポジションを手動で即座に管理できるようになります。OnTick内で本関数を呼び出し、コンパイル後には上記の処理が反映されます。

完全なピンバーナンピンシステム

画像からわかるように、本プログラムはサポートおよびレジスタンスレベルの検出と可視化、ポジションの新規エントリーおよびナンピンによる追加エントリー、トレーリングによるポジション追随、そして口座メタデータのパネル表示をおこなうことができます。これにより、本記事で設定した目的を達成できていることが確認できます。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。


バックテスト

徹底的なバックテストの結果、次の結果が得られました。

バックテストグラフ

グラフ

バックテストレポート

レポート


結論

まとめとして、本記事ではMQL5でのピンバーナンピンシステムを開発しました。本システムはピンバーのローソク足パターンを用いて取引を開始し、ナンピン戦略で複数ポジションを管理します。さらに、トレーリングストップやブレークイーブン調整、リアルタイム監視用の動的ダッシュボードを組み合わせることで、より高度なポジション管理を実現しています。CandleStick_AnalyzerやaddAveragingOrderといったモジュール化された関数を活用することで、反転取引を体系的におこない、リスクコントロールをカスタマイズ可能な形で提供します。

免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場の変動によって損失が生じる可能性があります。本プログラムを実際の市場で運用する前に、十分なバックテストと慎重なリスク管理が不可欠です。

提示された概念と実装を活用することで、このピンバーシステムを自分の取引スタイルに適応させ、アルゴリズム戦略を強化できます。取引をお楽しみください。 

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/19087

添付されたファイル |
プライスアクション分析ツールキットの開発(第36回):MetaTrader 5マーケットストリームへ直接アクセスするPython活用法 プライスアクション分析ツールキットの開発(第36回):MetaTrader 5マーケットストリームへ直接アクセスするPython活用法
MetaTrader 5ターミナルの潜在能力を最大限に引き出すために、Pythonのデータサイエンスエコシステムと公式のMetaTrader 5クライアントライブラリを活用する方法を紹介します。本記事では、認証をおこない、ライブティックおよび分足データを直接Parquetストレージにストリーミングする手法を解説し、taやProphetを用いた高度な特徴量エンジニアリングをおこない、時間依存型の勾配ブースティングモデルを学習させる方法を示します。その後、軽量なFlaskサービスを展開して、リアルタイムで取引シグナルを提供します。ハイブリッドクオンツフレームワークを構築する場合でも、エキスパートアドバイザー(EA)に機械学習を組み込む場合でも、データ駆動型アルゴリズム取引のための堅牢なエンドツーエンドパイプラインを習得できます。
MQL5での取引戦略の自動化(第25回):最小二乗法と動的シグナル生成を備えたTrendline Trader MQL5での取引戦略の自動化(第25回):最小二乗法と動的シグナル生成を備えたTrendline Trader
本記事では、最小二乗法を用いてサポートおよびレジスタンスのトレンドラインを検出し、価格がこれらのラインに触れた際に動的な売買シグナルを生成するTrendline Traderプログラムを開発します。また、生成されたシグナルに基づきポジションをオープンする仕組みも構築します。
MQL5での取引戦略の自動化(第27回):視覚的なフィードバックによるプライスアクションクラブハーモニックパターンの作成 MQL5での取引戦略の自動化(第27回):視覚的なフィードバックによるプライスアクションクラブハーモニックパターンの作成
本記事では、MQL5で弱気、強気両方のクラブ(Crab)ハーモニックパターンを、ピボットポイントとフィボナッチ比率を用いて識別し、正確なエントリー、ストップロス、テイクプロフィットレベルを使用して取引を自動化するクラブパターンシステムを開発します。また、XABCDパターン構造やエントリーレベルを表示するために、三角形やトレンドラインなどのチャートオブジェクトを使った視覚的な表示機能を追加します。
MQL5とデータ処理パッケージの統合(第5回):適応学習と柔軟性 MQL5とデータ処理パッケージの統合(第5回):適応学習と柔軟性
今回は、過去のXAU/USDデータを用いて柔軟で適応的な取引モデルを構築し、ONNX形式でのエクスポートや実際の取引システムへの統合に備えることに焦点を当てます。