English Deutsch
preview
MQL5でカスタムインジケータを作成する(第8回):出来高統合によるより深いマーケットプロファイル分析

MQL5でカスタムインジケータを作成する(第8回):出来高統合によるより深いマーケットプロファイル分析

MetaTrader 5トレーディングシステム |
18 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

第7回では、MetaQuotes Language 5 (MQL5)を使用して、ハイブリッドTime Price Opportunity (TPO)マーケットプロファイル指標を開発しました。この指標は、タイムゾーン調整に対応した日中、日次、週次、月次、および固定期間を含む複数のセッション時間足をサポートしていました。また、価格をグリッドへ量子化し、セッションごとの高値、安値、始値、終値データを追跡し、TPOカウントからポイントオブコントロール(PoC)とバリューエリア(VA)を計算しました。さらに、詳細なセッション分析を可能にするため、カスタマイズ可能な色設定によるチャート上への視覚的な描画機能も実装しました。

第8回となる今回は、この指標に出来高統合機能を追加し、より深いマーケットプロファイル分析を可能にします。これには、出来高ベースのPoC、VA、出来高加重平均価格(VWAP)計算、およびハイライト機能が含まれます。今回のアップグレードでは、イニシャルバランス(IB)検出、重要レベルの延長ライン、分割プロファイル、四角形や円形などの代替TPO文字、境界線、固定範囲用の背景矩形、動的な出来高ラベルなどの高度な機能を追加します。これらの機能は、時間足全体で柔軟性を維持しながら利用できます。本記事では以下のトピックを扱います。

  1. ハイブリッドTPOマーケットプロファイルへの出来高と高度な機能の統合
  2. MQL5での実装
  3. バックテスト
  4. 結論

記事を読み終える頃には、出来高強化分析に対応した高度なMQL5ハイブリッドTPOマーケットプロファイル指標が完成し、カスタマイズ可能な状態で利用できるようになります。それでは始めましょう。


ハイブリッドTPOマーケットプロファイルへの出来高と高度な機能の統合

ハイブリッドTPOマーケットプロファイルへ出来高データを統合することで、従来の価格と時間による分析に、各価格レベルでの取引出来高情報を加えることができます。これにより、最も高い出来高が発生した価格レベルを示す出来高ベースのPoCを特定できるようになり、単純な滞在時間ではなく、実際の市場参加状況を反映したVAの調整が可能になります。この強化により、市場参加の強さや確度に関する示唆を得ることができます。例えば、高出来高の適正価格ゾーンを潜在的なサポートやレジスタンスとして識別したり、低出来高領域を関心の弱いエリアやブレイクアウトの可能性がある領域として判断したりできます。また、VWAPのような機能は、イントラデイの方向感を判断するための動的な基準を提供します。

今回は、コアとなるセッション管理機能を拡張し、各価格レベルでの出来高累積処理を追加します。さらに、出来高PoCとVWAPをオプションで計算できるようにし、初期期間からIBを検出します。また、高値、安値、VA、ミッドポイントなどの重要レベルに対する延長ライン描画、文字形式の変更、始値のマーキングオプションを追加します。さらに、より明確なアルファベット表示を実現するため、余白付きの分割表示を有効化し、固定範囲背景や出来高ラベルにも対応します。以下に、目標を視覚的に示した図を掲載します。

ボリュームマーケットプロファイルロードマップ


MQL5での実装

拡張機能の実装を開始するために、まず、新たな出来高関連指標・計算項目に対応できるよう、列挙型、入力パラメータ、およびグローバル変数を拡張します。

//+------------------------------------------------------------------+
//|                             Hybrid TPO Market Profile PART 2.mq5 |
//|                           Copyright 2026, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"
#property strict

#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots 0

//+------------------------------------------------------------------+
//| Enums                                                            |
//+------------------------------------------------------------------+
enum MarketProfileTimeframe { // Define market profile timeframe enum
   INTRADAY,                  // Intraday
   DAILY,                     // Daily
   WEEKLY,                    // Weekly
   MONTHLY,                   // Monthly
   FIXED                      // Fixed
};

enum TpoCharacterType {      // Define TPO character type enum
   SQUARE,                   // ■ Square
   CIRCLE,                   // ● Circle
   ALPHABETIC                // A-Za-z
};

enum MidpointAlgorithm {     // Define midpoint algorithm enum
   HIGH_LOW_MID,             // High/Low mid
   TPO_COUNT_BASED           // Number of TPOs
};

enum MarkPeriodOpens {       // Define mark period opens enum
   NONE,                     // No
   SWAP_CASE,                // Swap case
   USE_ZERO                  // Use '0'
};

enum TextSize {              // Define text size enum
   TINY,                     // Tiny
   SMALL,                    // Small
   NORMAL                    // Normal
};

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
sinput group "Settings"
input double ticksPerTpoLetter = 10;             // Ticks per letter
input bool highlightVolumeProfilePoc = true;     // Highlight POC based on VP?
input bool useVolumeProfilePocForValueArea = true; // Use VP POC for Value Area?
input bool highlightSessionVwap = false;         // Highlight session VWAP?
input bool showExtensionLines = false;           // Show extension lines?
input bool splitProfile = false;                 // Split MP?

sinput group "Time"
input MarketProfileTimeframe profileTimeframe = DAILY; // Timeframe
input string timezone = "Exchange";              // Timezone
input string dailySessionRange = "0830-1500";    // Daily session
input int intradayProfileLengthMinutes = 60;     // Profile length in minutes (Intraday)
input datetime fixedTimeRangeStart = D'2026.02.01 08:30'; // From (Fixed)
input datetime fixedTimeRangeEnd = D'2026.02.02 15:00'; // Till (Fixed)
input bool renderVolumes = true;                 // Render volume numbers? (Fixed)

sinput group "Rendering"
input TpoCharacterType tpoCharacterType = SQUARE; // TPO characters
input int valueAreaPercent = 70;                 // Value Area Percent
input int initialBalancePeriods = 2;             // IB periods
input int initialBalanceLineWidth = 2;           // IB line width
input int priceMarkerWidth = 2;                  // Price marker width
input int priceMarkerLength = 1;                 // Price marker length
input TextSize textSize = NORMAL;                // Font size
input MarkPeriodOpens markPeriodOpens = NONE;    // Mark period open?
input MidpointAlgorithm midpointAlgorithm = HIGH_LOW_MID; // Midpoint algo

sinput group "Colors"
input color defaultTpoColor = clrGray;           // Default
input color singlePrintColor = 0xd56a6a;         // Single Print
input color valueAreaColor = clrBlack;           // Value Area
input color pointOfControlColor = 0x3f7cff;      // POC
input color volumeProfilePocColor = 0x87c74c;    // VP POC
input color openColor = clrDodgerBlue;           // Open
input color closeColor = clrRed;                 // Close
input color initialBalanceHighlightColor = clrDodgerBlue; // IB
input color initialBalanceBackgroundColor = 0x606D79;     // IB background
input color sessionVwapColor = 0xFF9925;                  // Session VWAP
input color pocExtensionColor = 0x87c74c;                 // POC extension
input color valueAreaHighExtensionColor = clrBlack;       // VAH extension
input color valueAreaLowExtensionColor = clrBlack;        // VAL extension
input color highExtensionColor = clrRed;                  // High extension
input color lowExtensionColor = clrGreen;                 // Low extension
input color midpointExtensionColor = 0x7649ff;            // Midpoint extension
input color fixedRangeBackgroundColor = 0x3179f5;         // Fixed range background

//+------------------------------------------------------------------+
//| Constants                                                        |
//+------------------------------------------------------------------+
#define MAX_BARS_BACK 5000
#define TPO_CHARACTERS_STRING "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

//+------------------------------------------------------------------+
//| Structures                                                       |
//+------------------------------------------------------------------+
struct TpoPriceLevel {       // Define TPO price level structure
   double price;             // Store price level
   string tpoString;         // Store TPO string
   int tpoCount;             // Store TPO count
   double volume;            // Store volume
};

struct ProfileSessionData {  // Define profile session data structure
   datetime startTime;       // Store start time
   datetime endTime;         // Store end time
   double sessionOpen;       // Store session open price
   double sessionClose;      // Store session close price
   double sessionHigh;       // Store session high price
   double sessionLow;        // Store session low price
   double initialBalanceHigh;// Store initial balance high
   double initialBalanceLow; // Store initial balance low
   double vwap;              // Store VWAP
   int pointOfControlIndex;  // Store point of control index
   int volumeProfilePocIndex;// Store volume profile POC index
   TpoPriceLevel levels[];   // Store array of price levels
   int periodCount;          // Store period count
   double periodHighs[];     // Store array of period highs
   double periodLows[];      // Store array of period lows
   double periodOpens[];     // Store array of period opens
   double volumeProfilePrices[]; // Store array of volume profile prices
   double volumeProfileVolumes[]; // Store array of volume profile volumes
};

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
string objectPrefix = "HTMP_";     //--- Set object prefix
ProfileSessionData sessions[];     //--- Declare sessions array
int activeSessionIndex = -1;       //--- Initialize active session index
double tpoPriceGridStep = 0;       //--- Initialize TPO price grid step
string tpoCharacterSet[];          //--- Declare TPO character set array
datetime previousBarTime = 0;      //--- Initialize previous bar time
datetime lastCompletedBarTime = 0; //--- Initialize last completed bar time
bool isNewSession = false;         //--- Initialize new session flag
int labelFontSize = 10;            //--- Set label font size
int maxSessionHistory = 20;        //--- Set maximum session history
int timezoneOffsetSeconds = 0;     //--- Initialize timezone offset in seconds

実装の開始にあたり、拡張版ハイブリッドTPOマーケットプロファイルの設定項目を広げるため、追加の列挙型を定義します。TpoCharacterType列挙型では、異なる表示スタイルに対応するための選択肢を提供します。SQUAREはブロック記号、CIRCLEはドット表示、ALPHABETICは文字ベースのラベル表示を使用します。これにより、従来の単一のラベル表示だけでなく、複数の視覚表現を選べるようになります。次に、MidpointAlgorithmでは、ミッドポイント計算用のアルゴリズムを提供します。HIGH_LOW_MIDはセッション範囲を基準に計算し、TPO_COUNT_BASEDは累積されたTPOカウントを使用して計算します。MarkPeriodOpensには、始値マーキング用のオプションを追加します。NONEはマーキングを無効化し、SWAP_CASEは始値を示すために文字の大文字・小文字を切り替え、USE_ZEROはゼロ文字を使用して始値を示します。最後に、TextSizeでは、TINY、SMALL、NORMALの設定を使用して文字サイズを調整できるようにします。

入力設定については、各セクションごとにグループ化されたユーザー入力を拡張します。新しい設定として、出来高ベースのPoC機能を切り替えるためにhighlightVolumeProfilePocとuseVolumeProfilePocForValueAreaを追加します。また、VWAPを強調表示するhighlightSessionVwap、重要レベルを延長表示するshowExtensionLines、分割されたアルファベット表示をおこなうsplitProfileも追加します。時間設定グループには、固定範囲の出来高ラベル表示を制御するrenderVolumesを追加します。描画設定では、TPO文字タイプ、IB期間、ライン幅、価格マーカーのサイズ、文字サイズ、期間始値マーキング、ミッドポイント計算方法の選択項目を追加します。カラー設定グループでは、出来高プロファイルPoC、始値マーカー、IBのハイライトおよび背景、セッションVWAP、各種延長ライン、固定範囲背景用のカラー入力を追加して拡張します。

構造体についても、出来高統合に対応するため更新します。TpoPriceLevelには、価格、TPO文字列、カウントに加えて出来高フィールドを追加します。ProfileSessionDataには、IBの高値と安値、VWAP、出来高プロファイルPoCのインデックス、期間ごとの高値・安値配列、さらに出来高プロファイル用の価格配列と出来高配列を追加します。グローバル変数もそれに合わせて調整します。isNewSessionをフラグとして追加し、後ほど動的に設定するlabelFontSizeを導入します。一方で、オブジェクト接頭辞、セッション配列、アクティブインデックス、グリッド幅、文字セット、タイムスタンプ、履歴制限、タイムゾーンオフセットなどの既存要素は維持します。次に、初期化イベントハンドラを更新し、以下のようにフォントサイズを設定します。変更箇所を明確にするため、該当部分をハイライトしています。

//+------------------------------------------------------------------+
//| Initialize custom indicator                                      |
//+------------------------------------------------------------------+
int OnInit() {
   IndicatorSetString(INDICATOR_SHORTNAME, "Hybrid TPO Market Profile"); //--- Set indicator short name
   
   tpoPriceGridStep = ticksPerTpoLetter * _Point; //--- Calculate TPO price grid step
   
   ArrayResize(tpoCharacterSet, 52);              //--- Resize TPO character set array
   for(int i = 0; i < 52; i++) {                  //--- Loop through characters
      tpoCharacterSet[i] = StringSubstr(TPO_CHARACTERS_STRING, i, 1); //--- Assign character to array
   }
   
   switch(textSize) {                             //--- Switch on text size
      case TINY: labelFontSize = 7; break;        //--- Set tiny font size
      case SMALL: labelFontSize = 9; break;       //--- Set small font size
      case NORMAL: labelFontSize = 11; break;     //--- Set normal font size
   }
   
   if(timezone != "Exchange") {                    //--- Check if timezone is not exchange
      string tzString = StringSubstr(timezone, 3); //--- Extract timezone string
      int offset = (int)StringToInteger(tzString); //--- Convert offset to integer
      timezoneOffsetSeconds = offset * 3600;       //--- Calculate timezone offset in seconds
   }
   
   ArrayResize(sessions, 0);                       //--- Resize sessions array to zero
   
   return(INIT_SUCCEEDED);                         //--- Return initialization success
}

ここでフォントサイズを設定することで、異なるチャート上でも視認性を向上させるために文字サイズを調整できるようになります。次におこなうことは、セッション作成関数を調整して、新しい保存用配列に対応し、古いセッションを削除できるようにすることです。これにより、より簡単に管理できるようになります。

//+------------------------------------------------------------------+
//| Delete session objects                                           |
//+------------------------------------------------------------------+
void DeleteSessionObjects(datetime sessionTime) {
   string sessionString = IntegerToString(sessionTime); //--- Convert session time to string
   
   int total = ObjectsTotal(0, 0, -1);            //--- Get total number of objects
   for(int i = total - 1; i >= 0; i--) {          //--- Loop through objects in reverse
      string name = ObjectName(0, i, 0, -1);      //--- Get object name
      if(StringFind(name, objectPrefix) == 0 && StringFind(name, sessionString) > 0) //--- Check if matches session
         ObjectDelete(0, name);                   //--- Delete object
   }
}

//+------------------------------------------------------------------+
//| Create new session                                               |
//+------------------------------------------------------------------+
int CreateNewSession() {
   int size = ArraySize(sessions);                //--- Get size of sessions array
   
   if(size >= maxSessionHistory) {                //--- Check if size exceeds history limit
      DeleteSessionObjects(sessions[0].startTime); //--- Delete old session objects
      
      for(int i = 0; i < size - 1; i++) {         //--- Loop to shift sessions
         sessions[i] = sessions[i + 1];           //--- Copy next session to current
      }
      ArrayResize(sessions, size - 1);            //--- Resize sessions array
      size = size - 1;                            //--- Update size
   }
   
   ArrayResize(sessions, size + 1);               //--- Resize sessions array for new session
   int newIndex = size;                           //--- Set new index
   
   sessions[newIndex].startTime = 0;              //--- Initialize start time
   sessions[newIndex].endTime = 0;                //--- Initialize end time
   sessions[newIndex].sessionOpen = 0;            //--- Initialize session open
   sessions[newIndex].sessionClose = 0;           //--- Initialize session close
   sessions[newIndex].sessionHigh = 0;            //--- Initialize session high
   sessions[newIndex].sessionLow = 0;             //--- Initialize session low
   sessions[newIndex].initialBalanceHigh = 0;     //--- Initialize initial balance high
   sessions[newIndex].initialBalanceLow = 0;      //--- Initialize initial balance low
   sessions[newIndex].vwap = 0;                   //--- Initialize VWAP
   sessions[newIndex].pointOfControlIndex = -1;   //--- Initialize point of control index
   sessions[newIndex].volumeProfilePocIndex = -1; //--- Initialize volume profile POC index
   sessions[newIndex].periodCount = 0;            //--- Initialize period count
   ArrayResize(sessions[newIndex].levels, 0);     //--- Resize levels array
   ArrayResize(sessions[newIndex].periodHighs, 0);//--- Resize period highs array
   ArrayResize(sessions[newIndex].periodLows, 0); //--- Resize period lows array
   ArrayResize(sessions[newIndex].periodOpens, 0);//--- Resize period opens array
   ArrayResize(sessions[newIndex].volumeProfilePrices, 0); //--- Resize volume profile prices array
   ArrayResize(sessions[newIndex].volumeProfileVolumes, 0); //--- Resize volume profile volumes array
   
   return newIndex;                               //--- Return new index
}

まず、特定のセッションに関連付けられたチャートオブジェクトを削除するために、DeleteSessionObjects関数を追加します。この関数では、セッション時間をIntegerToStringを使用して文字列へ変換します。その後、ObjectsTotalで取得したすべてのオブジェクトを後方からループ処理し、ObjectNameを使用してオブジェクト名を取得します。そして、StringFind関数を使用して、オブジェクト名が接頭辞で始まり、かつセッション文字列を含んでいるかを確認し、条件に一致するものを削除します。

次に、新しいプロファイルを管理するためにCreateNewSession関数を拡張します。まず、ArraySizeを使用して現在のセッション配列のサイズを取得します。履歴制限に達している場合は、最も古いセッションの開始時間を引数としてDeleteSessionObjectsを呼び出し、表示されているオブジェクトを削除します。その後、残りのセッションを前方へ移動させ、配列サイズを縮小します。続いて、新しいエントリー用に配列を拡張し、各フィールドを初期化します。これには、IB、高値・安値、VWAP、出来高プロファイルPoCのインデックスなどの新しい項目が含まれます。また、カウントやインデックス値をリセットします。さらに、期間ごとの高値配列、安値配列、出来高プロファイル価格配列、出来高配列などの追加配列サイズを調整します。これらの処理が完了した後、新しく作成されたセッションのインデックスを返します。次に、期間開始を示すために使用する文字の大文字・小文字を変更するヘルパー関数を追加します。

//+------------------------------------------------------------------+
//| Convert string to upper case                                     |
//+------------------------------------------------------------------+
string ConvertToUpperCase(string str) {
   string result = str;                           //--- Copy string
   StringToUpper(result);                         //--- Convert to upper case
   return result;                                 //--- Return result
}

//+------------------------------------------------------------------+
//| Convert string to lower case                                     |
//+------------------------------------------------------------------+
string ConvertToLowerCase(string str) {
   string result = str;                           //--- Copy string
   StringToLower(result);                         //--- Convert to lower case
   return result;                                 //--- Return result
}

//+------------------------------------------------------------------+
//| Check if character is upper case                                 |
//+------------------------------------------------------------------+
bool IsUpperCaseCharacter(string character) {
   return character == ConvertToUpperCase(character) && character != ConvertToLowerCase(character); //--- Check and return if upper case
}

ここでは、文字列の大文字・小文字処理を行うためのユーティリティ関数を追加し、期間開始を示すための大文字・小文字切り替えなどの機能をサポートします。ConvertToUpperCase関数では、入力された文字列をコピーし、StringToUpperを適用して文字列全体を大文字へ変換してから返します。同様に、ConvertToLowerCaseでは、文字列を複製し、StringToLowerを使用して完全な小文字へ変換します。文字の大文字判定をおこなうために、IsUpperCaseCharacter関数では、1つの文字がConvertToUpperCaseによる大文字変換後の文字と一致し、かつConvertToLowerCaseによる小文字変換後の文字とは異なるかを確認します。この条件を満たす場合、その文字が大文字であるとしてtrueを返します。次に、ドット文字や四角形文字の選択肢が追加されたため、レベルへTPO文字を追加する方法を変更します。

//+------------------------------------------------------------------+
//| Add TPO character to level                                       |
//+------------------------------------------------------------------+
void AddTpoCharacterToLevel(int sessionIndex, int levelIndex, int periodIndex) {
   if(sessionIndex < 0 || sessionIndex >= ArraySize(sessions)) return; //--- Return if session index invalid
   if(levelIndex < 0 || levelIndex >= ArraySize(sessions[sessionIndex].levels)) return; //--- Return if level index invalid
   
   string tpoCharacter = "";                      //--- Initialize TPO character
   
   switch(tpoCharacterType) {                     //--- Switch on TPO character type
      case SQUARE:                                //--- Handle square case
         tpoCharacter = "■";                      //--- Set square character
         break;                                   //--- Exit case
      case CIRCLE:                                //--- Handle circle case
         tpoCharacter = "●";                      //--- Set circle character
         break;                                   //--- Exit case
      case ALPHABETIC:                            //--- Handle alphabetic case
         tpoCharacter = tpoCharacterSet[periodIndex % 52]; //--- Get alphabetic character
         
         if(markPeriodOpens != NONE && periodIndex < ArraySize(sessions[sessionIndex].periodOpens)) { //--- Check if mark opens and valid index
            double periodOpen = sessions[sessionIndex].periodOpens[periodIndex]; //--- Get period open
            double levelPrice = sessions[sessionIndex].levels[levelIndex].price; //--- Get level price
            
            if(MathAbs(levelPrice - periodOpen) < tpoPriceGridStep / 2) { //--- Check if matches open
               if(markPeriodOpens == SWAP_CASE) {                         //--- Check swap case
                  if(IsUpperCaseCharacter(tpoCharacter))                  //--- Check if upper
                     tpoCharacter = ConvertToLowerCase(tpoCharacter);     //--- Convert to lower
                  else                                                    //--- Handle lower
                     tpoCharacter = ConvertToUpperCase(tpoCharacter);     //--- Convert to upper
               } else if(markPeriodOpens == USE_ZERO) {                   //--- Check use zero
                  tpoCharacter = "0";                                     //--- Set zero character
               }
            }
         }
         break;                                                           //--- Exit case
   }
   
   sessions[sessionIndex].levels[levelIndex].tpoString += tpoCharacter;   //--- Append character to TPO string
   sessions[sessionIndex].levels[levelIndex].tpoCount++;                  //--- Increment TPO count
}

ここでは、カスタマイズ可能なTPO表現と始値マーキング機能を組み込むために、AddTpoCharacterToLevel関数を拡張します。まず、ArraySizeを使用してセッションとレベルのインデックスを検証し、無効な場合は早期に処理を終了します。次に、空のTPO文字列を初期化し、tpoCharacterType列挙型を使用して分岐を処理します。SQUAREまたはCIRCLEの場合は、ブロック記号やドットなどの固定シンボルを割り当てます。ALPHABETICの場合は、期間インデックスに対して52の剰余演算を使用し、文字セットから対応する文字を選択します。文字については、Wingdings記号を使用するよりも描画結果が良かったため、固定文字を使用する方法を採用しました。ただし、動的性を持たせるためにWingdings記号を使用することも可能です。

アルファベット形式で、かつmarkPeriodOpensがNONEではなく、期間開始配列のサイズが有効な場合は、レベル価格と期間開始価格を比較します。この判定では、MathAbsを使用し、グリッド幅の半分を許容範囲として一致を確認します。一致した場合は、SWAP_CASEではIsUpperCaseCharacterを使用して現在の文字が大文字かを確認し、ConvertToLowerCaseまたはConvertToUpperCaseによって大文字・小文字を切り替えます。USE_ZEROの場合は、文字をゼロに変更します。最後に、生成した文字をレベルの文字列へ追加し、TPOのカウントを増加させてプロファイルを構築します。次に、分割プロファイルが有効な場合にTPO文字列へスペースを追加するための別のヘルパー関数を定義します。これにより、アルファベットを使用した場合に期間ごとの区切りを明確にした分割表示を作成できます。

//+------------------------------------------------------------------+
//| Pad levels for split profile                                     |
//+------------------------------------------------------------------+
void PadLevelsForSplitProfile(int sessionIndex) {
   if(!splitProfile || tpoCharacterType != ALPHABETIC) return;         //--- Return if not split or not alphabetic
   if(sessionIndex < 0 || sessionIndex >= ArraySize(sessions)) return; //--- Return if index invalid
   
   int periodCount = sessions[sessionIndex].periodCount;               //--- Get period count
   int levelCount = ArraySize(sessions[sessionIndex].levels);          //--- Get level count
   
   for(int i = 0; i < levelCount; i++) {                               //--- Loop through levels
      int currentLength = StringLen(sessions[sessionIndex].levels[i].tpoString); //--- Get current length
      if(currentLength < periodCount) {                                //--- Check if needs padding
         for(int j = currentLength; j < periodCount; j++) {            //--- Loop to pad
            sessions[sessionIndex].levels[i].tpoString += " ";         //--- Add space
         }
      }
   }
}

PadLevelsForSplitProfile関数を追加し、分割プロファイル表示用にアルファベット形式のTPO文字列を準備します。まず、分割モードが無効、または文字タイプがアルファベット形式ではない場合は処理を終了します。また、セッションインデックスがセッション配列のサイズに対して無効な場合も早期に終了します。次に、期間数とレベル数を取得します。その後、各レベルをループ処理し、StringLenを使用して現在の文字列長を確認します。文字列の長さが期間数より短い場合は、内部ループでスペースを追加し、文字列の長さを調整します。これにより、すべての文字列が同じ長さに揃えられ、描画されたプロファイル上で期間ごとの区切りが明確になるよう、一定の間隔を確保できます。次に、レベル情報から出来高プロファイルを作成し、出来高ベースのPoCを検出するための関数を構築します。

//+------------------------------------------------------------------+
//| Build volume profile and find POC                                |
//+------------------------------------------------------------------+
void BuildVolumeProfileAndFindPoc(int sessionIndex) {
   if(!highlightVolumeProfilePoc) {                //--- Check if not highlight VP POC
      if(sessionIndex >= 0 && sessionIndex < ArraySize(sessions)) //--- Check valid session
         sessions[sessionIndex].volumeProfilePocIndex = -1; //--- Reset VP POC index
      return;                                     //--- Return
   }
   
   if(sessionIndex < 0 || sessionIndex >= ArraySize(sessions)) return; //--- Return if index invalid
   if(sessions[sessionIndex].startTime == 0) return; //--- Return if no start time
   
   ENUM_TIMEFRAMES lowerTimeframe = PERIOD_M1;    //--- Set default lower timeframe
   int currentTimeframeSeconds = PeriodSeconds(_Period); //--- Get current timeframe seconds
   
   if(currentTimeframeSeconds < 30 * 60) {        //--- Check for M1 lower
      lowerTimeframe = PERIOD_M1;                 //--- Set M1
   } else if(currentTimeframeSeconds < 60 * 60) { //--- Check for M3 lower
      lowerTimeframe = PERIOD_M3;                 //--- Set M3
   } else if(currentTimeframeSeconds == 60 * 60) {//--- Check for M10 lower
      lowerTimeframe = PERIOD_M10;                //--- Set M10
   } else {                                       //--- Default to H1
      lowerTimeframe = PERIOD_H1;                 //--- Set H1
   }
   
   if(PeriodSeconds(_Period) <= 60) {             //--- Check for 1-minute timeframe
      int size = ArraySize(sessions[sessionIndex].levels); //--- Get levels size
      for(int i = 0; i < size; i++) {             //--- Loop through levels
         int volumeProfileIndex = -1;             //--- Initialize VP index
         for(int j = 0; j < ArraySize(sessions[sessionIndex].volumeProfilePrices); j++) { //--- Loop through VP prices
            if(MathAbs(sessions[sessionIndex].volumeProfilePrices[j] - sessions[sessionIndex].levels[i].price) < _Point / 2) { //--- Check match
               volumeProfileIndex = j;            //--- Set index
               break;                             //--- Exit loop
            }
         }
         
         if(volumeProfileIndex == -1) {         //--- Check if new
            int volumeProfileSize = ArraySize(sessions[sessionIndex].volumeProfilePrices); //--- Get VP size
            ArrayResize(sessions[sessionIndex].volumeProfilePrices, volumeProfileSize + 1); //--- Resize prices
            ArrayResize(sessions[sessionIndex].volumeProfileVolumes, volumeProfileSize + 1); //--- Resize volumes
            sessions[sessionIndex].volumeProfilePrices[volumeProfileSize] = sessions[sessionIndex].levels[i].price; //--- Set price
            sessions[sessionIndex].volumeProfileVolumes[volumeProfileSize] = sessions[sessionIndex].levels[i].volume; //--- Set volume
         } else {                                 //--- Handle existing
            sessions[sessionIndex].volumeProfileVolumes[volumeProfileIndex] += sessions[sessionIndex].levels[i].volume; //--- Add volume
         }
      }
   } else {                                       //--- Handle higher timeframes
      datetime sessionEnd = (sessions[sessionIndex].endTime > 0) ? sessions[sessionIndex].endTime : TimeCurrent(); //--- Get session end
      
      int startBar = iBarShift(_Symbol, lowerTimeframe, sessions[sessionIndex].startTime); //--- Get start bar
      int endBar = iBarShift(_Symbol, lowerTimeframe, sessionEnd); //--- Get end bar
      
      if(startBar < 0 || endBar < 0) return;      //--- Return if invalid bars
      
      int barCount = startBar - endBar + 1;       //--- Calculate bar count
      if(barCount <= 0) return;                   //--- Return if no bars
      
      double highs[], lows[];                     //--- Declare highs and lows arrays
      long volumes[];                             //--- Declare volumes array
      
      ArraySetAsSeries(highs, true);              //--- Set highs as series
      ArraySetAsSeries(lows, true);               //--- Set lows as series
      ArraySetAsSeries(volumes, true);            //--- Set volumes as series
      
      if(CopyHigh(_Symbol, lowerTimeframe, endBar, barCount, highs) <= 0) return; //--- Copy highs
      if(CopyLow(_Symbol, lowerTimeframe, endBar, barCount, lows) <= 0) return; //--- Copy lows
      if(CopyTickVolume(_Symbol, lowerTimeframe, endBar, barCount, volumes) <= 0) return; //--- Copy volumes
      
      for(int i = 0; i < barCount; i++) {         //--- Loop through bars
         double quantizedHigh = QuantizePriceToGrid(highs[i]); //--- Quantize high
         double quantizedLow = QuantizePriceToGrid(lows[i]); //--- Quantize low
         
         int priceCount = (int)MathMax(1, (quantizedHigh - quantizedLow) / tpoPriceGridStep + 1); //--- Calculate price count
         double volumePerLevel = (double)volumes[i] / priceCount; //--- Calculate volume per level
         
         for(double price = quantizedLow; price <= quantizedHigh; price += tpoPriceGridStep) { //--- Loop through prices
            int volumeProfileIndex = -1;          //--- Initialize VP index
            for(int j = 0; j < ArraySize(sessions[sessionIndex].volumeProfilePrices); j++) { //--- Loop through VP prices
               if(MathAbs(sessions[sessionIndex].volumeProfilePrices[j] - price) < _Point / 2) { //--- Check match
                  volumeProfileIndex = j;         //--- Set index
                  break;                          //--- Exit loop
               }
            }
            
            if(volumeProfileIndex == -1) {      //--- Check if new
               int volumeProfileSize = ArraySize(sessions[sessionIndex].volumeProfilePrices); //--- Get VP size
               ArrayResize(sessions[sessionIndex].volumeProfilePrices, volumeProfileSize + 1); //--- Resize prices
               ArrayResize(sessions[sessionIndex].volumeProfileVolumes, volumeProfileSize + 1); //--- Resize volumes
               sessions[sessionIndex].volumeProfilePrices[volumeProfileSize] = price; //--- Set price
               sessions[sessionIndex].volumeProfileVolumes[volumeProfileSize] = volumePerLevel; //--- Set volume
            } else {                              //--- Handle existing
               sessions[sessionIndex].volumeProfileVolumes[volumeProfileIndex] += volumePerLevel; //--- Add volume
            }
         }
      }
   }
   
   double maxVolume = 0;                          //--- Initialize max volume
   double volumeProfilePocPrice = 0;              //--- Initialize VP POC price
   
   for(int i = 0; i < ArraySize(sessions[sessionIndex].volumeProfileVolumes); i++) { //--- Loop through volumes
      if(sessions[sessionIndex].volumeProfileVolumes[i] > maxVolume) { //--- Check if max
         maxVolume = sessions[sessionIndex].volumeProfileVolumes[i]; //--- Update max
         volumeProfilePocPrice = sessions[sessionIndex].volumeProfilePrices[i]; //--- Update price
      }
   }
   
   sessions[sessionIndex].volumeProfilePocIndex = -1; //--- Reset VP POC index
   for(int i = 0; i < ArraySize(sessions[sessionIndex].levels); i++) { //--- Loop through levels
      if(MathAbs(sessions[sessionIndex].levels[i].price - volumeProfilePocPrice) < tpoPriceGridStep / 2) { //--- Check match
         sessions[sessionIndex].volumeProfilePocIndex = i; //--- Set index
         break;                                    //--- Exit loop
      }
   }
}

BuildVolumeProfileAndFindPoc関数を実装し、出来高ベースのPoCをハイライト表示するための独立した出来高プロファイルを構築します。まず、highlightVolumeProfilePocを確認し、この機能が有効かどうかを判定します。無効の場合は、有効なセッションに対するインデックスをリセットし、早期に処理を終了します。次に、ArraySizeを使用してセッションインデックスを検証し、開始時間が存在することを確認します。続いて、現在の時間足に応じて動的に下位時間足を選択します。PeriodSecondsを使用し、30分未満の場合は PERIOD_M1、1時間未満の場合はPERIOD_M3、ちょうど1時間の場合はPERIOD_M10、それ以外の場合はPERIOD_H1に設定します。これにより、より細かいデータへアクセスし、正確な出来高分布を取得します。現在の時間足が60秒以下の場合は、既存のレベルをループ処理し、出来高プロファイル配列内に一致する価格があるかを確認します。この判定では、MathAbsを使用し、半ポイント分の許容範囲で比較します。一致する価格が見つからない場合は、ArrayResizeで配列サイズを拡張し、価格と出来高を追加します。一致する場合は、既存の出来高へ加算します。

上位足の場合は、セッション終了時間を設定済みの終了時間、またはTimeCurrentから取得します。その後、下位足を基準にiBarShiftを使用して開始バーと終了バーのインデックスを取得します。有効なインデックスで、かつバー数が正の場合は、高値、安値、出来高用の配列を宣言し、時系列配列として設定します。続いて、CopyHigh、CopyLow、CopyTickVolume を使用してデータを取得します。その後、各バーをループ処理し、高値と安値をQuantizePriceToGridでグリッド化します。価格ステップを計算し、最低でも1以上になるよう調整します。バーの出来高は各価格レベルへ均等に分配し、価格範囲をループ処理して、先ほどと同様に許容範囲を使用した比較によって、出来高プロファイル配列へ追加または累積します。

出来高PoCを特定するために、最大出来高とその価格を0で初期化します。その後、出来高配列をスキャンし、最大値を更新します。最後に、インデックスをリセットし、最大出来高となった価格を基準に最も近いレベルのインデックスを許容範囲で検索します。見つかった場合は、そのインデックスを保存し、描画時に出来高ベースのハイライト表示を有効にします。次に、価格レベルの出来高を使用してVWAPを計算します。これは、取引された量を重みとして使用した平均価格を求める処理になります。

//+------------------------------------------------------------------+
//| Calculate session VWAP                                           |
//+------------------------------------------------------------------+
void CalculateSessionVwap(int sessionIndex) {
   if(!highlightSessionVwap) return;              //--- Return if not highlight VWAP
   if(sessionIndex < 0 || sessionIndex >= ArraySize(sessions)) return; //--- Return if index invalid
   
   double sumPriceVolume = 0;                     //--- Initialize sum price volume
   double sumVolume = 0;                          //--- Initialize sum volume
   
   int size = ArraySize(sessions[sessionIndex].levels); //--- Get levels size
   for(int i = 0; i < size; i++) {                //--- Loop through levels
      sumPriceVolume += sessions[sessionIndex].levels[i].price * sessions[sessionIndex].levels[i].volume; //--- Accumulate price volume
      sumVolume += sessions[sessionIndex].levels[i].volume; //--- Accumulate volume
   }
   
   if(sumVolume > 0)                              //--- Check if volume positive
      sessions[sessionIndex].vwap = sumPriceVolume / sumVolume; //--- Calculate VWAP
}

セッションのVWAPを求めるために、CalculateSessionVwap関数を実装します。まず、機能が無効の場合、またはセッションインデックスが有効範囲外の場合は、早期に処理を終了します。次に、価格と出来高の積の合計、および総出来高を格納するための累積変数を設定します。その後、各レベルをループ処理し、各価格に出来高を掛けて積の合計へ加算し、出来高も合計します。総出来高が0より大きい場合は、積の合計を出来高の合計で割り、加重平均値を計算してセッションのvwapフィールドへ保存します。これで計算処理を補助するすべてのヘルパー関数が揃いました。残る作業は、データ表示のための視覚的な描画処理です。まず、アルファベット形式の場合に開始TPO文字をハイライト表示する関数を追加します。これは開始文字にスポットライトを当てるような処理です。必須ではありませんが、素早い視覚的な識別を向上させます。

//+------------------------------------------------------------------+
//| Render open TPO highlight                                        |
//+------------------------------------------------------------------+
void RenderOpenTpoHighlight(int sessionIndex, int openLevelIndex, string &displayStrings[]) {
   if(tpoCharacterType != ALPHABETIC) return;                          //--- Return if not alphabetic
   if(sessionIndex < 0 || sessionIndex >= ArraySize(sessions)) return; //--- Return if session invalid
   if(openLevelIndex < 0 || openLevelIndex >= ArraySize(sessions[sessionIndex].levels)) return; //--- Return if level invalid
   
   string fullString = displayStrings[openLevelIndex];                 //--- Get full display string
   if(StringLen(fullString) == 0) return;                              //--- Return if empty string
   
   string openCharacter = StringSubstr(fullString, 0, 1);              //--- Extract open character
   string remainingCharacters = StringSubstr(fullString, 1);           //--- Extract remaining characters
   
   string objectName = objectPrefix + "OpenTPO_" + IntegerToString(sessions[sessionIndex].startTime); //--- Create object name
   int barIndex = iBarShift(_Symbol, _Period, sessions[sessionIndex].startTime); //--- Get bar index
   if(barIndex < 0) return;                                            //--- Return if invalid bar index
   
   datetime labelTime = iTime(_Symbol, _Period, barIndex);             //--- Get label time
   int x, y;                                                           //--- Declare coordinates
   ChartTimePriceToXY(0, 0, labelTime, sessions[sessionIndex].levels[openLevelIndex].price, x, y); //--- Convert to XY
   
   if(ObjectFind(0, objectName) < 0) {                                 //--- Check if object not found
      ObjectCreate(0, objectName, OBJ_LABEL, 0, 0, 0);                 //--- Create label object
   }
   
   ObjectSetInteger(0, objectName, OBJPROP_XDISTANCE, x);              //--- Set X distance
   ObjectSetInteger(0, objectName, OBJPROP_YDISTANCE, y);              //--- Set Y distance
   ObjectSetInteger(0, objectName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner
   ObjectSetInteger(0, objectName, OBJPROP_ANCHOR, ANCHOR_LEFT);       //--- Set anchor
   ObjectSetInteger(0, objectName, OBJPROP_COLOR, openColor);          //--- Set color
   ObjectSetInteger(0, objectName, OBJPROP_FONTSIZE, labelFontSize);   //--- Set font size
   ObjectSetString(0, objectName, OBJPROP_FONT, "Arial");              //--- Set font
   ObjectSetString(0, objectName, OBJPROP_TEXT, openCharacter);        //--- Set text
   ObjectSetInteger(0, objectName, OBJPROP_SELECTABLE, false);         //--- Set selectable false
   ObjectSetInteger(0, objectName, OBJPROP_HIDDEN, true);              //--- Set hidden true
   
   if(StringLen(remainingCharacters) > 0) {                            //--- Check if remaining
      displayStrings[openLevelIndex] = " " + remainingCharacters;      //--- Prepend space
   } else {                                                            //--- Handle no remaining
      displayStrings[openLevelIndex] = " ";                            //--- Set space
   }
}

ここでは、アルファベットモードで開始TPO文字を視覚的に強調するために、RenderOpenTpoHighlight関数を追加します。文字タイプがアルファベット形式ではない場合、またはセッションと開始レベルのインデックスが配列サイズに対して無効な場合は、早期に処理を終了します。開始レベルの表示文字列を取得し、StringLenにより空の場合は処理を終了します。その後、StringSubstrを使用して最初の文字を開始マーカーとして取得し、残りの文字をremainingとして取得します。IntegerToStringを使用して接頭辞とセッション開始時間を組み合わせたオブジェクト名を作成し、iBarShiftでバーインデックスを取得します。無効な場合は処理を終了します。iTimeを使用してラベル時間を取得し、ChartTimePriceToXYを使用して時間と価格を座標へ変換します。ObjectFindでオブジェクトが存在しない場合は、ObjectCreateOBJ_LABELを使用してラベルを作成します。

ObjectSetIntegerとObjectSetStringを使用して、位置、コーナー、アンカー、入力された開始色、動的なフォントサイズ、Arialフォント、開始文字テキスト、選択不可および非表示プロパティを設定します。最後に、プロファイル描画時の配置を維持するため、残りの文字がある場合はその先頭にスペースを追加して表示文字列を更新します。残りの文字がない場合は、単一のスペースを設定します。次に、プロファイルの境界線、IBライン、および重要レベルの延長線を描画します。

//+------------------------------------------------------------------+
//| Render profile border line                                       |
//+------------------------------------------------------------------+
void RenderProfileBorderLine(int sessionIndex, int barIndex) {
   if(sessionIndex < 0 || sessionIndex >= ArraySize(sessions)) return; //--- Return if index invalid
   if(sessions[sessionIndex].sessionHigh == 0 || sessions[sessionIndex].sessionLow == 0) return; //--- Return if no high low
   
   datetime startTime = iTime(_Symbol, _Period, barIndex);             //--- Get start time
   
   color backgroundEdgeColor = 0x606D79;                               //--- Set background edge color
   color initialBalanceEdgeColor = initialBalanceHighlightColor;       //--- Set IB edge color
   
   if(initialBalancePeriods > 0 && sessions[sessionIndex].initialBalanceHigh > 0 && sessions[sessionIndex].initialBalanceLow > 0) { //--- Check IB
      
      string edge1Name = objectPrefix + "Edge1_" + IntegerToString(sessions[sessionIndex].startTime); //--- Create edge1 name
      if(ObjectFind(0, edge1Name) < 0) {                               //--- Check if not found
         ObjectCreate(0, edge1Name, OBJ_TREND, 0, startTime, sessions[sessionIndex].sessionLow, 
                      startTime, sessions[sessionIndex].initialBalanceLow); //--- Create trend
         ObjectSetInteger(0, edge1Name, OBJPROP_RAY_RIGHT, false);     //--- Set ray right false
         ObjectSetInteger(0, edge1Name, OBJPROP_SELECTABLE, false);    //--- Set selectable false
         ObjectSetInteger(0, edge1Name, OBJPROP_HIDDEN, true);         //--- Set hidden true
      }
      ObjectSetDouble(0, edge1Name, OBJPROP_PRICE, 0, sessions[sessionIndex].sessionLow); //--- Set price 0
      ObjectSetDouble(0, edge1Name, OBJPROP_PRICE, 1, sessions[sessionIndex].initialBalanceLow); //--- Set price 1
      ObjectSetInteger(0, edge1Name, OBJPROP_COLOR, backgroundEdgeColor); //--- Set color
      ObjectSetInteger(0, edge1Name, OBJPROP_WIDTH, 3);                //--- Set width
      ObjectSetInteger(0, edge1Name, OBJPROP_STYLE, STYLE_SOLID);      //--- Set style
      
      string edge2Name = objectPrefix + "Edge2_" + IntegerToString(sessions[sessionIndex].startTime); //--- Create edge2 name
      if(ObjectFind(0, edge2Name) < 0) {                               //--- Check if not found
         ObjectCreate(0, edge2Name, OBJ_TREND, 0, startTime, sessions[sessionIndex].initialBalanceLow, 
                      startTime, sessions[sessionIndex].initialBalanceHigh); //--- Create trend
         ObjectSetInteger(0, edge2Name, OBJPROP_RAY_RIGHT, false);     //--- Set ray right false
         ObjectSetInteger(0, edge2Name, OBJPROP_SELECTABLE, false);    //--- Set selectable false
         ObjectSetInteger(0, edge2Name, OBJPROP_HIDDEN, true);         //--- Set hidden true
      }
      ObjectSetDouble(0, edge2Name, OBJPROP_PRICE, 0, sessions[sessionIndex].initialBalanceLow);  //--- Set price 0
      ObjectSetDouble(0, edge2Name, OBJPROP_PRICE, 1, sessions[sessionIndex].initialBalanceHigh); //--- Set price 1
      ObjectSetInteger(0, edge2Name, OBJPROP_COLOR, initialBalanceEdgeColor);                     //--- Set color
      ObjectSetInteger(0, edge2Name, OBJPROP_WIDTH, 3);                //--- Set width
      ObjectSetInteger(0, edge2Name, OBJPROP_STYLE, STYLE_SOLID);      //--- Set style
      
      string edge3Name = objectPrefix + "Edge3_" + IntegerToString(sessions[sessionIndex].startTime); //--- Create edge3 name
      if(ObjectFind(0, edge3Name) < 0) {                               //--- Check if not found
         ObjectCreate(0, edge3Name, OBJ_TREND, 0, startTime, sessions[sessionIndex].initialBalanceHigh, 
                      startTime, sessions[sessionIndex].sessionHigh);  //--- Create trend
         ObjectSetInteger(0, edge3Name, OBJPROP_RAY_RIGHT, false);     //--- Set ray right false
         ObjectSetInteger(0, edge3Name, OBJPROP_SELECTABLE, false);    //--- Set selectable false
         ObjectSetInteger(0, edge3Name, OBJPROP_HIDDEN, true);         //--- Set hidden true
      }
      ObjectSetDouble(0, edge3Name, OBJPROP_PRICE, 0, sessions[sessionIndex].initialBalanceHigh);   //--- Set price 0
      ObjectSetDouble(0, edge3Name, OBJPROP_PRICE, 1, sessions[sessionIndex].sessionHigh);          //--- Set price 1
      ObjectSetInteger(0, edge3Name, OBJPROP_COLOR, backgroundEdgeColor);                           //--- Set color
      ObjectSetInteger(0, edge3Name, OBJPROP_WIDTH, 3);                                             //--- Set width
      ObjectSetInteger(0, edge3Name, OBJPROP_STYLE, STYLE_SOLID);                                   //--- Set style
      
   } else {                                                                                         //--- Handle no IB
      string edgeName = objectPrefix + "Edge_" + IntegerToString(sessions[sessionIndex].startTime); //--- Create edge name
      if(ObjectFind(0, edgeName) < 0) {                                                             //--- Check if not found
         ObjectCreate(0, edgeName, OBJ_TREND, 0, startTime, sessions[sessionIndex].sessionLow, 
                      startTime, sessions[sessionIndex].sessionHigh);                               //--- Create trend
         ObjectSetInteger(0, edgeName, OBJPROP_RAY_RIGHT, false);                                   //--- Set ray right false
         ObjectSetInteger(0, edgeName, OBJPROP_SELECTABLE, false);                                  //--- Set selectable false
         ObjectSetInteger(0, edgeName, OBJPROP_HIDDEN, true);                                       //--- Set hidden true
      }
      ObjectSetDouble(0, edgeName, OBJPROP_PRICE, 0, sessions[sessionIndex].sessionLow);            //--- Set price 0
      ObjectSetDouble(0, edgeName, OBJPROP_PRICE, 1, sessions[sessionIndex].sessionHigh);           //--- Set price 1
      ObjectSetInteger(0, edgeName, OBJPROP_COLOR, backgroundEdgeColor);                            //--- Set color
      ObjectSetInteger(0, edgeName, OBJPROP_WIDTH, 3);                                              //--- Set width
      ObjectSetInteger(0, edgeName, OBJPROP_STYLE, STYLE_SOLID);                                    //--- Set style
   }
}

//+------------------------------------------------------------------+
//| Render initial balance lines                                     |
//+------------------------------------------------------------------+
void RenderInitialBalanceLines(int sessionIndex, int barIndex) {
   if(sessionIndex < 0 || sessionIndex >= ArraySize(sessions)) return;       //--- Return if index invalid
   if(sessions[sessionIndex].initialBalanceHigh == 0 || sessions[sessionIndex].initialBalanceLow == 0) return; //--- Return if no IB
   
   datetime startTime = iTime(_Symbol, _Period, barIndex);                   //--- Get start time
   
   string initialBalanceHighName = objectPrefix + "IB_High_" + IntegerToString(sessions[sessionIndex].startTime); //--- Create IB high name
   if(ObjectFind(0, initialBalanceHighName) < 0) { //--- Check if not found
      ObjectCreate(0, initialBalanceHighName, OBJ_TREND, 0, startTime, sessions[sessionIndex].initialBalanceHigh, 
                   startTime, sessions[sessionIndex].initialBalanceHigh);     //--- Create trend
      ObjectSetInteger(0, initialBalanceHighName, OBJPROP_RAY_RIGHT, false);  //--- Set ray right false
      ObjectSetInteger(0, initialBalanceHighName, OBJPROP_SELECTABLE, false); //--- Set selectable false
      ObjectSetInteger(0, initialBalanceHighName, OBJPROP_HIDDEN, true);      //--- Set hidden true
   }
   ObjectSetInteger(0, initialBalanceHighName, OBJPROP_COLOR, initialBalanceHighlightColor); //--- Set color
   ObjectSetInteger(0, initialBalanceHighName, OBJPROP_WIDTH, initialBalanceLineWidth);      //--- Set width
   
   string initialBalanceLowName = objectPrefix + "IB_Low_" + IntegerToString(sessions[sessionIndex].startTime); //--- Create IB low name
   if(ObjectFind(0, initialBalanceLowName) < 0) { //--- Check if not found
      ObjectCreate(0, initialBalanceLowName, OBJ_TREND, 0, startTime, sessions[sessionIndex].initialBalanceLow, 
                   startTime, sessions[sessionIndex].initialBalanceLow);      //--- Create trend
      ObjectSetInteger(0, initialBalanceLowName, OBJPROP_RAY_RIGHT, false);   //--- Set ray right false
      ObjectSetInteger(0, initialBalanceLowName, OBJPROP_SELECTABLE, false);  //--- Set selectable false
      ObjectSetInteger(0, initialBalanceLowName, OBJPROP_HIDDEN, true);       //--- Set hidden true
   }
   ObjectSetInteger(0, initialBalanceLowName, OBJPROP_COLOR, initialBalanceHighlightColor); //--- Set color
   ObjectSetInteger(0, initialBalanceLowName, OBJPROP_WIDTH, initialBalanceLineWidth);      //--- Set width
}

//+------------------------------------------------------------------+
//| Render key level extensions                                      |
//+------------------------------------------------------------------+
void RenderKeyLevelExtensions(int sessionIndex, int barIndex, int valueAreaUpperIndex, int valueAreaLowerIndex) {
   if(sessionIndex < 0 || sessionIndex >= ArraySize(sessions)) return; //--- Return if index invalid
   if(ArraySize(sessions[sessionIndex].levels) == 0) return;           //--- Return if no levels
   
   datetime startTime = iTime(_Symbol, _Period, barIndex);             //--- Get start time
   
   bool isCurrentSession = (sessionIndex == ArraySize(sessions) - 1);  //--- Check if current session
   
   datetime endTime;                                                   //--- Declare end time
   bool rayRight;                                                      //--- Declare ray right
   
   if(isCurrentSession) {                                              //--- Handle current session
      endTime = startTime + PeriodSeconds(_Period) * 100;              //--- Set end time
      rayRight = true;                                                 //--- Set ray right true
   } else {                                                            //--- Handle past session
      if(sessionIndex + 1 < ArraySize(sessions)) {                     //--- Check next session
         int nextBarIndex = iBarShift(_Symbol, _Period, sessions[sessionIndex + 1].startTime); //--- Get next bar
         endTime = iTime(_Symbol, _Period, nextBarIndex);              //--- Set end time
      } else {                                                         //--- Handle last
         endTime = startTime + PeriodSeconds(_Period) * 100;           //--- Set end time
      }
      rayRight = false;                                                //--- Set ray right false
   }
   
   if(sessions[sessionIndex].volumeProfilePocIndex >= 0 && sessions[sessionIndex].volumeProfilePocIndex < ArraySize(sessions[sessionIndex].levels)) { //--- Check VP POC
      double pocPrice = sessions[sessionIndex].levels[sessions[sessionIndex].volumeProfilePocIndex].price; //--- Get POC price
      string pocExtensionName = objectPrefix + "POC_Ext_" + IntegerToString(sessions[sessionIndex].startTime); //--- Create POC ext name
      if(ObjectFind(0, pocExtensionName) < 0) {                             //--- Check if not found
         ObjectCreate(0, pocExtensionName, OBJ_TREND, 0, startTime, pocPrice, endTime, pocPrice); //--- Create trend
         ObjectSetInteger(0, pocExtensionName, OBJPROP_SELECTABLE, false);  //--- Set selectable false
         ObjectSetInteger(0, pocExtensionName, OBJPROP_HIDDEN, true);       //--- Set hidden true
      } else {                                    //--- Handle existing
         ObjectSetInteger(0, pocExtensionName, OBJPROP_TIME, 0, startTime); //--- Set time 0
         ObjectSetDouble(0, pocExtensionName, OBJPROP_PRICE, 0, pocPrice);  //--- Set price 0
         ObjectSetInteger(0, pocExtensionName, OBJPROP_TIME, 1, endTime);   //--- Set time 1
         ObjectSetDouble(0, pocExtensionName, OBJPROP_PRICE, 1, pocPrice);  //--- Set price 1
      }
      ObjectSetInteger(0, pocExtensionName, OBJPROP_RAY_RIGHT, rayRight);   //--- Set ray right
      ObjectSetInteger(0, pocExtensionName, OBJPROP_COLOR, pocExtensionColor); //--- Set color
      ObjectSetInteger(0, pocExtensionName, OBJPROP_WIDTH, 2);              //--- Set width
      ObjectSetInteger(0, pocExtensionName, OBJPROP_STYLE, STYLE_SOLID);    //--- Set style
   }
   
   if(valueAreaUpperIndex >= 0 && valueAreaUpperIndex < ArraySize(sessions[sessionIndex].levels)) { //--- Check VAH
      double valueAreaHighPrice = sessions[sessionIndex].levels[valueAreaUpperIndex].price;         //--- Get VAH price
      string valueAreaHighExtensionName = objectPrefix + "VAH_Ext_" + IntegerToString(sessions[sessionIndex].startTime); //--- Create VAH ext name
      if(ObjectFind(0, valueAreaHighExtensionName) < 0) {                                           //--- Check if not found
         ObjectCreate(0, valueAreaHighExtensionName, OBJ_TREND, 0, startTime, valueAreaHighPrice, endTime, valueAreaHighPrice); //--- Create trend
         ObjectSetInteger(0, valueAreaHighExtensionName, OBJPROP_SELECTABLE, false);                //--- Set selectable false
         ObjectSetInteger(0, valueAreaHighExtensionName, OBJPROP_HIDDEN, true);                     //--- Set hidden true
      } else {                                    //--- Handle existing
         ObjectSetInteger(0, valueAreaHighExtensionName, OBJPROP_TIME, 0, startTime);               //--- Set time 0
         ObjectSetDouble(0, valueAreaHighExtensionName, OBJPROP_PRICE, 0, valueAreaHighPrice);      //--- Set price 0
         ObjectSetInteger(0, valueAreaHighExtensionName, OBJPROP_TIME, 1, endTime);                 //--- Set time 1
         ObjectSetDouble(0, valueAreaHighExtensionName, OBJPROP_PRICE, 1, valueAreaHighPrice);      //--- Set price 1
      }
      ObjectSetInteger(0, valueAreaHighExtensionName, OBJPROP_RAY_RIGHT, rayRight);                 //--- Set ray right
      ObjectSetInteger(0, valueAreaHighExtensionName, OBJPROP_COLOR, valueAreaHighExtensionColor);  //--- Set color
      ObjectSetInteger(0, valueAreaHighExtensionName, OBJPROP_WIDTH, 1);                            //--- Set width
      ObjectSetInteger(0, valueAreaHighExtensionName, OBJPROP_STYLE, STYLE_DOT);                    //--- Set style
   }
   
   if(valueAreaLowerIndex >= 0 && valueAreaLowerIndex < ArraySize(sessions[sessionIndex].levels)) { //--- Check VAL
      double valueAreaLowPrice = sessions[sessionIndex].levels[valueAreaLowerIndex].price;          //--- Get VAL price
      string valueAreaLowExtensionName = objectPrefix + "VAL_Ext_" + IntegerToString(sessions[sessionIndex].startTime); //--- Create VAL ext name
      if(ObjectFind(0, valueAreaLowExtensionName) < 0) {                                            //--- Check if not found
         ObjectCreate(0, valueAreaLowExtensionName, OBJ_TREND, 0, startTime, valueAreaLowPrice, endTime, valueAreaLowPrice); //--- Create trend
         ObjectSetInteger(0, valueAreaLowExtensionName, OBJPROP_SELECTABLE, false);                 //--- Set selectable false
         ObjectSetInteger(0, valueAreaLowExtensionName, OBJPROP_HIDDEN, true);                      //--- Set hidden true
      } else {                                                                                      //--- Handle existing
         ObjectSetInteger(0, valueAreaLowExtensionName, OBJPROP_TIME, 0, startTime);                //--- Set time 0
         ObjectSetDouble(0, valueAreaLowExtensionName, OBJPROP_PRICE, 0, valueAreaLowPrice);        //--- Set price 0
         ObjectSetInteger(0, valueAreaLowExtensionName, OBJPROP_TIME, 1, endTime);                  //--- Set time 1
         ObjectSetDouble(0, valueAreaLowExtensionName, OBJPROP_PRICE, 1, valueAreaLowPrice);        //--- Set price 1
      }
      ObjectSetInteger(0, valueAreaLowExtensionName, OBJPROP_RAY_RIGHT, rayRight);                  //--- Set ray right
      ObjectSetInteger(0, valueAreaLowExtensionName, OBJPROP_COLOR, valueAreaLowExtensionColor);    //--- Set color
      ObjectSetInteger(0, valueAreaLowExtensionName, OBJPROP_WIDTH, 1);                             //--- Set width
      ObjectSetInteger(0, valueAreaLowExtensionName, OBJPROP_STYLE, STYLE_DOT);                     //--- Set style
   }
   
   string highExtensionName = objectPrefix + "High_Ext_" + IntegerToString(sessions[sessionIndex].startTime); //--- Create high ext name
   if(ObjectFind(0, highExtensionName) < 0) {                                                       //--- Check if not found
      ObjectCreate(0, highExtensionName, OBJ_TREND, 0, startTime, sessions[sessionIndex].sessionHigh, 
                   endTime, sessions[sessionIndex].sessionHigh);                                    //--- Create trend
      ObjectSetInteger(0, highExtensionName, OBJPROP_SELECTABLE, false);                            //--- Set selectable false
      ObjectSetInteger(0, highExtensionName, OBJPROP_HIDDEN, true);                                 //--- Set hidden true
      ObjectSetInteger(0, highExtensionName, OBJPROP_STYLE, STYLE_DOT);                             //--- Set style
   } else {                                                                                         //--- Handle existing
      ObjectSetInteger(0, highExtensionName, OBJPROP_TIME, 0, startTime);                           //--- Set time 0
      ObjectSetDouble(0, highExtensionName, OBJPROP_PRICE, 0, sessions[sessionIndex].sessionHigh);  //--- Set price 0
      ObjectSetInteger(0, highExtensionName, OBJPROP_TIME, 1, endTime);                             //--- Set time 1
      ObjectSetDouble(0, highExtensionName, OBJPROP_PRICE, 1, sessions[sessionIndex].sessionHigh);  //--- Set price 1
   }
   ObjectSetInteger(0, highExtensionName, OBJPROP_RAY_RIGHT, rayRight);       //--- Set ray right
   ObjectSetInteger(0, highExtensionName, OBJPROP_COLOR, highExtensionColor); //--- Set color
   ObjectSetInteger(0, highExtensionName, OBJPROP_WIDTH, 1);                  //--- Set width
   
   string lowExtensionName = objectPrefix + "Low_Ext_" + IntegerToString(sessions[sessionIndex].startTime); //--- Create low ext name
   if(ObjectFind(0, lowExtensionName) < 0) {                                                                //--- Check if not found
      ObjectCreate(0, lowExtensionName, OBJ_TREND, 0, startTime, sessions[sessionIndex].sessionLow, 
                   endTime, sessions[sessionIndex].sessionLow);                                             //--- Create trend
      ObjectSetInteger(0, lowExtensionName, OBJPROP_SELECTABLE, false);                                     //--- Set selectable false
      ObjectSetInteger(0, lowExtensionName, OBJPROP_HIDDEN, true);                                          //--- Set hidden true
      ObjectSetInteger(0, lowExtensionName, OBJPROP_STYLE, STYLE_DOT);                                      //--- Set style
   } else {                                                                                                 //--- Handle existing
      ObjectSetInteger(0, lowExtensionName, OBJPROP_TIME, 0, startTime);                                    //--- Set time 0
      ObjectSetDouble(0, lowExtensionName, OBJPROP_PRICE, 0, sessions[sessionIndex].sessionLow);            //--- Set price 0
      ObjectSetInteger(0, lowExtensionName, OBJPROP_TIME, 1, endTime);                                      //--- Set time 1
      ObjectSetDouble(0, lowExtensionName, OBJPROP_PRICE, 1, sessions[sessionIndex].sessionLow);            //--- Set price 1
   }
   ObjectSetInteger(0, lowExtensionName, OBJPROP_RAY_RIGHT, rayRight);      //--- Set ray right
   ObjectSetInteger(0, lowExtensionName, OBJPROP_COLOR, lowExtensionColor); //--- Set color
   ObjectSetInteger(0, lowExtensionName, OBJPROP_WIDTH, 1);                 //--- Set width
   
   double midpointPrice = 0;                      //--- Initialize midpoint price
   if(midpointAlgorithm == HIGH_LOW_MID) {        //--- Check high low mid
      midpointPrice = sessions[sessionIndex].sessionHigh - (sessions[sessionIndex].sessionHigh - sessions[sessionIndex].sessionLow) / 2; //--- Calculate midpoint
   } else {                                       //--- Handle TPO count based
      int totalTpoCount = GetTotalTpoCount(sessionIndex); //--- Get total TPO
      int targetTpoCount = totalTpoCount / 2;     //--- Calculate target
      int currentTpoCount = 0;                    //--- Initialize current
      
      for(int i = 0; i < ArraySize(sessions[sessionIndex].levels); i++) { //--- Loop through levels
         currentTpoCount += sessions[sessionIndex].levels[i].tpoCount; //--- Accumulate TPO
         if(currentTpoCount >= targetTpoCount) {  //--- Check if reached
            midpointPrice = sessions[sessionIndex].levels[i].price; //--- Set midpoint
            break;                                //--- Exit loop
         }
      }
   }
   
   if(midpointPrice > 0) {                        //--- Check if midpoint set
      string midpointExtensionName = objectPrefix + "Mid_Ext_" + IntegerToString(sessions[sessionIndex].startTime); //--- Create mid ext name
      if(ObjectFind(0, midpointExtensionName) < 0) { //--- Check if not found
         ObjectCreate(0, midpointExtensionName, OBJ_TREND, 0, startTime, midpointPrice, endTime, midpointPrice); //--- Create trend
         ObjectSetInteger(0, midpointExtensionName, OBJPROP_SELECTABLE, false);  //--- Set selectable false
         ObjectSetInteger(0, midpointExtensionName, OBJPROP_HIDDEN, true);       //--- Set hidden true
         ObjectSetInteger(0, midpointExtensionName, OBJPROP_STYLE, STYLE_DOT);   //--- Set style
      } else {                                                                   //--- Handle existing
         ObjectSetInteger(0, midpointExtensionName, OBJPROP_TIME, 0, startTime); //--- Set time 0
         ObjectSetDouble(0, midpointExtensionName, OBJPROP_PRICE, 0, midpointPrice); //--- Set price 0
         ObjectSetInteger(0, midpointExtensionName, OBJPROP_TIME, 1, endTime);   //--- Set time 1
         ObjectSetDouble(0, midpointExtensionName, OBJPROP_PRICE, 1, midpointPrice); //--- Set price 1
      }
      ObjectSetInteger(0, midpointExtensionName, OBJPROP_RAY_RIGHT, rayRight);   //--- Set ray right
      ObjectSetInteger(0, midpointExtensionName, OBJPROP_COLOR, midpointExtensionColor); //--- Set color
      ObjectSetInteger(0, midpointExtensionName, OBJPROP_WIDTH, 1);              //--- Set width
   }
}

RenderProfileBorderLine関数を追加し、セッションの価格範囲を示す垂直境界線をチャート上へ描画します。まず、セッションインデックスを検証し、高値と安値が存在することを確認してから処理を進めます。iTimeで開始時間を取得し、背景エッジ用の色とIBハイライト用の色を設定します。IB期間が正の値で、かつ値が設定されている場合は、ObjectCreateでOBJ_TRENDを使用して3つのトレンドラインを作成または更新します。1つ目は、セッション安値からIB安値までを背景色で描画します。2つ目は、IB安値からIB高値までをハイライト色で描画します。3つ目は、IB高値からセッション高値までを背景色で描画します。各ラインは、ObjectFindで存在を確認し、ObjectSetDoubleで価格を設定します。また、ObjectSetIntegerを使用して、レイ延長なし、選択不可、非表示、ソリッドスタイル、幅3に設定します。IBがない場合は、安値から高値までを背景色で結ぶ1本のトレンドラインを描画し、同じプロパティを設定します。

次に、RenderInitialBalanceLines関数では、IB範囲を視覚化します。値が存在する場合、開始時間を取得し、ObjectCreateでOBJ_TRENDを使用して高値と安値用の2本の水平トレンドラインを作成または更新します。これらのラインには、同じ座標を設定し、レイ延長なし、選択不可、非表示、入力されたハイライト色、ライン幅を設定します。

重要レベルを先へ延長表示するために、RenderKeyLevelExtensions関数を実装します。まず、有効性を確認して開始時間を取得します。次に、現在のセッションかどうかを判定します。現在のセッションの場合は、PeriodSeconds、TimeCurrent、iBarShiftを使用して延長終了時間を計算し、レイ表示を有効にします。過去のセッションの場合は、次のセッション開始時間を使用し、レイ表示なしで処理します。出来高プロファイルPoCについては、有効な場合、その価格位置に入力された延長色と幅2のソリッドラインを描画します。同様に、Value Area HighとValue Area Lowについては、インデックスが有効範囲内の場合、それぞれ対応する色を使用した点線を幅1で作成します。

常にセッション高値と安値については、入力された色を使用した点線の延長ラインを追加します。ミッドポイントについては、midpointAlgorithm列挙型に基づいて計算します。単純な価格範囲の中央値を使用する方法、またはGetTotalTpoCountでTime Price Opportunityカウントを累積し、半分の位置となるレベルを検索する方法のいずれかを使用します。値が正の場合は、ミッドポイント色を使用した点線を描画します。これらすべてのラインは、他のラインと同じ方法でオブジェクトの存在を確認または作成した後、ObjectSetIntegerで時間、レイ、スタイル、その他のプロパティを設定し、ObjectSetDoubleで価格を設定します。次に、固定モードで各レベルの横に出来高数値を表示するためのロジックを定義します。これは、各価格がどれだけ取引されたかを色の強度で示すラベル表示になります。

//+------------------------------------------------------------------+
//| Render fixed range volume labels                                 |
//+------------------------------------------------------------------+
void RenderFixedRangeVolumeLabels(int sessionIndex, int barIndex) {
   if(sessionIndex < 0 || sessionIndex >= ArraySize(sessions)) return;     //--- Return if index invalid
   if(ArraySize(sessions[sessionIndex].volumeProfilePrices) == 0) return;  //--- Return if no VP prices
   
   double maxVolume = 0;                                                   //--- Initialize max volume
   for(int i = 0; i < ArraySize(sessions[sessionIndex].volumeProfileVolumes); i++) { //--- Loop through volumes
      if(sessions[sessionIndex].volumeProfileVolumes[i] > maxVolume)       //--- Check if max
         maxVolume = sessions[sessionIndex].volumeProfileVolumes[i];       //--- Update max
   }
   
   if(maxVolume == 0) return;                                              //--- Return if no volume
   
   datetime labelTime = iTime(_Symbol, _Period, barIndex);                 //--- Get label time
   
   for(int i = 0; i < ArraySize(sessions[sessionIndex].volumeProfilePrices); i++) { //--- Loop through VP prices
      double price = sessions[sessionIndex].volumeProfilePrices[i];        //--- Get price
      double volumeValue = sessions[sessionIndex].volumeProfileVolumes[i]; //--- Get volume
      double ratio = volumeValue / maxVolume;     //--- Calculate ratio
      
      color volumeColor;                          //--- Declare color
      int percent = (int)(100 * ratio);           //--- Calculate percent
      
      if(percent <= 8) {                          //--- Check low percent
         volumeColor = singlePrintColor;          //--- Set single print color
      } else {                                    //--- Handle higher
         int transparency = (int)MathMax(0, 80 - percent);                     //--- Calculate transparency
         volumeColor = ApplyTransparencyToColor(valueAreaColor, transparency); //--- Apply transparency
      }
      
      string objectName = objectPrefix + "Vol_" + IntegerToString(sessions[sessionIndex].startTime) + "_" + IntegerToString(i); //--- Create object name
      
      int x, y;                                           //--- Declare coordinates
      ChartTimePriceToXY(0, 0, labelTime, price, x, y);   //--- Convert to XY
      
      if(ObjectFind(0, objectName) < 0) {                 //--- Check if not found
         ObjectCreate(0, objectName, OBJ_LABEL, 0, 0, 0); //--- Create label
      }
      
      ObjectSetInteger(0, objectName, OBJPROP_XDISTANCE, x - 50);         //--- Set X distance
      ObjectSetInteger(0, objectName, OBJPROP_YDISTANCE, y);              //--- Set Y distance
      ObjectSetInteger(0, objectName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner
      ObjectSetInteger(0, objectName, OBJPROP_ANCHOR, ANCHOR_RIGHT);      //--- Set anchor
      ObjectSetInteger(0, objectName, OBJPROP_COLOR, volumeColor);        //--- Set color
      ObjectSetInteger(0, objectName, OBJPROP_FONTSIZE, labelFontSize);   //--- Set font size
      ObjectSetString(0, objectName, OBJPROP_FONT, "Arial");              //--- Set font
      ObjectSetString(0, objectName, OBJPROP_TEXT, IntegerToString((int)MathRound(volumeValue))); //--- Set text
      ObjectSetInteger(0, objectName, OBJPROP_SELECTABLE, false);         //--- Set selectable false
      ObjectSetInteger(0, objectName, OBJPROP_HIDDEN, true);              //--- Set hidden true
   }
}

//+------------------------------------------------------------------+
//| Apply transparency to color                                      |
//+------------------------------------------------------------------+
color ApplyTransparencyToColor(color baseColor, int transparency) {
   int red = (int)(baseColor & 0xFF);             //--- Extract red
   int green = (int)((baseColor >> 8) & 0xFF);    //--- Extract green
   int blue = (int)((baseColor >> 16) & 0xFF);    //--- Extract blue
   
   transparency = (int)MathMin(100, MathMax(0, transparency)); //--- Clamp transparency
   int alpha = 255 - (transparency * 255 / 100); //--- Calculate alpha
   
   return (color)((alpha << 24) | (blue << 16) | (green << 8) | red); //--- Return color with alpha
}

RenderFixedRangeVolumeLabels関数を実装し、固定時間足モードでプロファイルの横に出来高値をラベル表示します。まず、セッションインデックスを検証し、出来高プロファイル価格が存在することを確認してから処理を進めます。出来高配列をループ処理し、最大出来高を取得します。最大値を更新しながら確認し、0の場合は除算エラーを防ぐため処理を終了します。次に、iTimeでラベル時間を取得します。その後、各出来高プロファイルエントリーをループ処理し、最大出来高に対する比率を計算し、パーセンテージを求めます。ラベル色は、8%以下の場合はシングルプリント用の色を使用します。それ以上の場合は、ApplyTransparencyToColorを使用してValue Area色へ透明度を適用します。透明度は、MathMaxを使用してパーセント値に基づき0から80の範囲で逆方向に変化します。

各ラベルについて、IntegerToStringを使用して接頭辞、開始時間、インデックスを組み合わせた一意のオブジェクト名を作成します。ChartTimePriceToXYを使用して時間と価格を座標へ変換します。ObjectFindでオブジェクトが存在しない場合は、ObjectCreateとOBJ_LABELを使用してラベルを作成します。ObjectSetIntegerとObjectSetStringを使用して、X座標を左側へオフセットし、左上コーナー、右アンカー、計算された色、動的フォントサイズ、Arialフォント、MathRoundとIntegerToStringを使用して丸めた出来高テキスト、選択不可および非表示プロパティを設定します。

視覚表示で透明度を変更できるようにするため、ApplyTransparencyToColor関数を定義します。この関数では、ビット演算のANDとシフトを使用して、ベースカラーから赤、緑、青の各成分を取得します。入力された透明度は、MathMinとMathMaxを使用して0から100の範囲に制限します。その後、アルファ値を255からスケールされたパーセント値を引いて計算し、アルファ値を24ビット左へシフトして、青・緑・赤の配置とOR演算することで新しいカラー値を返します。最後に、固定時間足プロファイルをチャート上で視覚的に区別するため、固定時間足用の色付き背景矩形を描画できます。

//+------------------------------------------------------------------+
//| Render fixed range background rectangle                          |
//+------------------------------------------------------------------+
void RenderFixedRangeBackgroundRectangle(int sessionIndex) {
   if(profileTimeframe != FIXED) return;                               //--- Return if not fixed
   if(sessionIndex < 0 || sessionIndex >= ArraySize(sessions)) return; //--- Return if index invalid
   
   string objectName = objectPrefix + "FixedBG_" + IntegerToString(sessions[sessionIndex].startTime); //--- Create object name
   
   if(ObjectFind(0, objectName) < 0) {                                 //--- Check if not found
      ObjectCreate(0, objectName, OBJ_RECTANGLE, 0, sessions[sessionIndex].startTime, sessions[sessionIndex].sessionHigh,
                   sessions[sessionIndex].endTime > 0 ? sessions[sessionIndex].endTime : TimeCurrent(),
                   sessions[sessionIndex].sessionLow);                 //--- Create rectangle
      ObjectSetInteger(0, objectName, OBJPROP_SELECTABLE, false);      //--- Set selectable false
      ObjectSetInteger(0, objectName, OBJPROP_HIDDEN, true);           //--- Set hidden true
      ObjectSetInteger(0, objectName, OBJPROP_BACK, true);             //--- Set back true
   }
   
   ObjectSetInteger(0, objectName, OBJPROP_COLOR, fixedRangeBackgroundColor); //--- Set color
   ObjectSetInteger(0, objectName, OBJPROP_FILL, true); //--- Set fill true
}

RenderFixedRangeBackgroundRectangle関数を追加し、固定時間足プロファイル用の塗りつぶし背景を描画します。これにより、チャート上で固定範囲プロファイルを視覚的に区別しやすくします。まず、時間足がFIXEDではない場合、または配列サイズの確認によってセッションインデックスが無効な場合は、早期に処理を終了します。IntegerToStringを使用して接頭辞とセッション開始時間を組み合わせ、オブジェクト名を作成します。その後、ObjectFindでオブジェクトが存在しない場合は、ObjectCreateOBJ_RECTANGLEを使用して矩形オブジェクトを作成します。矩形は、セッション高値にある開始時間から、終了時間(終了時間が0の場合はTimeCurrent を使用)までを範囲とし、セッション安値まで描画します。また、選択不可、非表示、他の描画要素の背面表示に設定します。オブジェクトの有無に関係なく、 ObjectSetIntegerを使用して入力された固定範囲背景色を適用し、ソリッド塗りつぶしを有効化します。これにより、プロファイル範囲の背景を色付きで表示できます。これで、プロファイル描画用のメイン関数内でこれらの関数を呼び出し、主要な処理を実行できるようになります。あとは、ティック計算イベントハンドラで各ロジックを呼び出します。

//+------------------------------------------------------------------+
//| Render session profile                                           |
//+------------------------------------------------------------------+
void RenderSessionProfile(int sessionIndex) {
   if(sessionIndex < 0 || sessionIndex >= ArraySize(sessions)) return; //--- Return if index invalid
   
   int size = ArraySize(sessions[sessionIndex].levels); //--- Get levels size
   if(size == 0 || sessions[sessionIndex].startTime == 0) return; //--- Return if no levels or no start time
   
   int barIndex = iBarShift(_Symbol, _Period, sessions[sessionIndex].startTime); //--- Get bar index
   if(barIndex < 0) return;                       //--- Return if invalid
   
   PadLevelsForSplitProfile(sessionIndex);        //--- Pad levels for split
   SortPriceLevelsDescending(sessionIndex);       //--- Sort levels descending
   CalculatePointOfControl(sessionIndex);         //--- Calculate POC
   BuildVolumeProfileAndFindPoc(sessionIndex);    //--- Build VP and find POC
   
   int totalTpoCount = GetTotalTpoCount(sessionIndex); //--- Get total TPO count
   int pointOfControlIndex = (useVolumeProfilePocForValueArea && sessions[sessionIndex].volumeProfilePocIndex >= 0) ? 
                             sessions[sessionIndex].volumeProfilePocIndex : sessions[sessionIndex].pointOfControlIndex; //--- Select POC index
   
   int valueAreaUpperIndex = pointOfControlIndex; //--- Initialize value area upper index
   int valueAreaLowerIndex = pointOfControlIndex; //--- Initialize value area lower index
   
   if(pointOfControlIndex >= 0) {                 //--- Check valid POC index
      int targetTpoCount = (int)(totalTpoCount * valueAreaPercent / 100.0); //--- Calculate target TPO count
      int currentTpoCount = sessions[sessionIndex].levels[pointOfControlIndex].tpoCount; //--- Set current TPO count
      
      while(currentTpoCount < targetTpoCount && (valueAreaUpperIndex > 0 || valueAreaLowerIndex < size - 1)) { //--- Loop to expand value area
         int upperTpoCount = (valueAreaUpperIndex > 0) ? sessions[sessionIndex].levels[valueAreaUpperIndex - 1].tpoCount : 0; //--- Get upper TPO count
         int lowerTpoCount = (valueAreaLowerIndex < size - 1) ? sessions[sessionIndex].levels[valueAreaLowerIndex + 1].tpoCount : 0; //--- Get lower TPO count
         
         if(upperTpoCount >= lowerTpoCount && valueAreaUpperIndex > 0) { //--- Check upper expansion
            valueAreaUpperIndex--;                //--- Decrement upper index
            currentTpoCount += upperTpoCount;     //--- Add upper TPO
         } else if(valueAreaLowerIndex < size - 1) { //--- Check lower expansion
            valueAreaLowerIndex++;                //--- Increment lower index
            currentTpoCount += lowerTpoCount;     //--- Add lower TPO
         } else if(valueAreaUpperIndex > 0) {     //--- Fallback upper expansion
            valueAreaUpperIndex--;                //--- Decrement upper index
            currentTpoCount += upperTpoCount;     //--- Add upper TPO
         } else {                                 //--- Break if no more
            break;                                //--- Exit loop
         }
      }
   }
   
   string displayStrings[];                       //--- Declare display strings array
   ArrayResize(displayStrings, size);             //--- Resize display strings
   for(int i = 0; i < size; i++) {                //--- Loop through levels
      displayStrings[i] = sessions[sessionIndex].levels[i].tpoString; //--- Copy TPO string
   }
   
   int openLevelIndex  = -1;                      //--- Initialize open level index
   int closeLevelIndex = -1;                      //--- Initialize close level index
   
   if(tpoCharacterType == ALPHABETIC) {           //--- Check alphabetic
      double openPrice  = sessions[sessionIndex].sessionOpen; //--- Get open price
      double closePrice = sessions[sessionIndex].sessionClose; //--- Get close price
      
      for(int i = 0; i < size; i++) {             //--- Loop to find levels
         if(openLevelIndex < 0  && MathAbs(sessions[sessionIndex].levels[i].price - openPrice)  < tpoPriceGridStep / 2) //--- Check open match
            openLevelIndex = i;                   //--- Set open index
         if(closeLevelIndex < 0 && MathAbs(sessions[sessionIndex].levels[i].price - closePrice) < tpoPriceGridStep / 2) //--- Check close match
            closeLevelIndex = i;                  //--- Set close index
      }
      
      RenderOpenTpoHighlight(sessionIndex, openLevelIndex, displayStrings); //--- Render open highlight
      RenderCloseTpoHighlight(sessionIndex, closeLevelIndex, displayStrings); //--- Render close highlight
   }
   
   for(int i = 0; i < size; i++) {                //--- Loop to render levels
      string objectName = objectPrefix + "TPO_" + IntegerToString(sessions[sessionIndex].startTime) + "_" + IntegerToString(i); //--- Create object name
      
      color textColor = defaultTpoColor;          //--- Set default color
      
      if(sessions[sessionIndex].levels[i].tpoCount == 1) { //--- Check single print
         textColor = singlePrintColor;            //--- Set single print color
      }
      
      if(i >= valueAreaUpperIndex && i <= valueAreaLowerIndex) { //--- Check value area
         textColor = valueAreaColor;              //--- Set value area color
      }
      
      if(i == sessions[sessionIndex].pointOfControlIndex && sessions[sessionIndex].volumeProfilePocIndex != sessions[sessionIndex].pointOfControlIndex) { //--- Check TPO POC
         textColor = pointOfControlColor;         //--- Set POC color
      }
      
      if(highlightVolumeProfilePoc && i == sessions[sessionIndex].volumeProfilePocIndex) { //--- Check VP POC
         textColor = volumeProfilePocColor;       //--- Set VP POC color
      }
      
      if(highlightSessionVwap && MathAbs(sessions[sessionIndex].levels[i].price - sessions[sessionIndex].vwap) < tpoPriceGridStep / 2) { //--- Check VWAP
         textColor = sessionVwapColor;            //--- Set VWAP color
      }
      
      if(ObjectFind(0, objectName) < 0) {         //--- Check if object not found
         ObjectCreate(0, objectName, OBJ_LABEL, 0, 0, 0);       //--- Create label
         ObjectSetInteger(0, objectName, OBJPROP_XDISTANCE, 0); //--- Set X distance
         ObjectSetInteger(0, objectName, OBJPROP_YDISTANCE, 0); //--- Set Y distance
      }
      
      datetime labelTime = iTime(_Symbol, _Period, barIndex); //--- Get label time
      int x, y;                                               //--- Declare coordinates
      ChartTimePriceToXY(0, 0, labelTime, sessions[sessionIndex].levels[i].price, x, y); //--- Convert to XY
      
      ObjectSetInteger(0, objectName, OBJPROP_XDISTANCE, x); //--- Set X distance
      ObjectSetInteger(0, objectName, OBJPROP_YDISTANCE, y); //--- Set Y distance
      ObjectSetInteger(0, objectName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner
      ObjectSetInteger(0, objectName, OBJPROP_ANCHOR, ANCHOR_LEFT);       //--- Set anchor
      ObjectSetInteger(0, objectName, OBJPROP_COLOR, textColor);          //--- Set color
      ObjectSetInteger(0, objectName, OBJPROP_FONTSIZE, labelFontSize);   //--- Set font size
      ObjectSetString(0, objectName, OBJPROP_FONT, "Arial");              //--- Set font
      ObjectSetString(0, objectName, OBJPROP_TEXT, displayStrings[i]);    //--- Set text
      ObjectSetInteger(0, objectName, OBJPROP_SELECTABLE, false);         //--- Set selectable false
      ObjectSetInteger(0, objectName, OBJPROP_HIDDEN, true);              //--- Set hidden true
   }
   
   RenderOpenCloseMarkers(sessionIndex, barIndex);  //--- Render open close markers
   RenderProfileBorderLine(sessionIndex, barIndex); //--- Render profile border
   
   if(initialBalancePeriods > 0)                    //--- Check if IB periods
      RenderInitialBalanceLines(sessionIndex, barIndex); //--- Render IB lines
   
   if(showExtensionLines)                           //--- Check if show extensions
      RenderKeyLevelExtensions(sessionIndex, barIndex, valueAreaUpperIndex, valueAreaLowerIndex); //--- Render extensions
   
   if(profileTimeframe == FIXED && renderVolumes)   //--- Check fixed and render volumes
      RenderFixedRangeVolumeLabels(sessionIndex, barIndex); //--- Render volume labels
   
   RenderFixedRangeBackgroundRectangle(sessionIndex); //--- Render fixed background
}

ここでは、マーケットプロファイル描画に高度な機能を組み込むために、RenderSessionProfile関数を拡張します。まず、インデックスとデータの有効性を確認し、無効な場合、レベルが存在しない場合、または開始時間が存在しない場合は処理を終了します。その後、iBarShift関数を使用してバーインデックスを取得します。プロファイルの準備として、分割モードでの配置調整のためにPadLevelsForSplitProfileを呼び出します。次に、SortPriceLevelsDescendingでレベルを降順に並べ替え、CalculatePointOfControlでTime Price OpportunityのPoCを計算し、BuildVolumeProfileAndFindPocで出来高プロファイルを構築して、そのPoCを取得します。

GetTotalTpoCountからTime Price Opportunityの合計カウントを取得した後、PoCのインデックスを条件に応じて選択します。useVolumeProfilePocForValueAreaがtrueで、かつ出来高PoCが存在する場合は、それを使用します。それ以外の場合は、Time Price OpportunityのPoCを使用します。その後、VAインデックスを初期化し、PoCの周囲へ拡張します。valueAreaPercentに基づく目標値へ到達するまで、隣接するレベルのうちTime Price Opportunity数が多い方を優先して選択し、ループ内でカウントを累積します。描画準備として、ArrayResizeを使用して表示用配列のサイズを変更し、Time Price Opportunity文字列をコピーします。アルファベットモードの場合は、グリッド幅の半分を許容範囲としてMathAbsでレベルを確認し、開始レベルと終了レベルのインデックスを検索します。その後、RenderOpenTpoHighlightとRenderCloseTpoHighlightを呼び出してハイライト表示をおこないます。

各レベルを処理するメイン描画ループでは、一意のオブジェクト名を作成し、初期色をデフォルト色に設定します。その後、シングルプリントの場合、Value Area範囲内の場合、独立したTime Price Opportunity PoCの場合、ハイライトされた出来高PoCの場合、または許容範囲を使用したVolume Weighted Average Priceとの一致の場合に応じて色を変更します。ObjectFindでオブジェクトが存在しない場合は、ObjectCreateとOBJ_LABELを使用してラベルを作成します。iTimeでラベル時間を取得した後、ChartTimePriceToXYを使用して時間と価格を座標へ変換します。そして、位置、コーナー、アンカー、色、フォントサイズ、フォント、表示文字列、選択不可および非表示設定を適用します。

最後に、RenderOpenCloseMarkers、RenderProfileBorderLine、期間が設定されている場合のRenderInitialBalanceLines、拡張が有効な場合にValue Areaインデックスを使用するRenderKeyLevelExtensions、固定時間足で出来高がある場合のRenderFixedRangeVolumeLabels、背景用のRenderFixedRangeBackgroundRectangleを呼び出して補助要素を追加します。これで、ティック計算イベントハンドラを更新するだけです。各期間の高値・安値・始値を保存し、期間内でInitial Balanceを計算し、レベルへ出来高を追加し、全体の計算処理としてVWAPを呼び出します。

//+------------------------------------------------------------------+
//| Calculate custom indicator                                       |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[]) {
   
   if(rates_total < 2) return 0;                              //--- Return if insufficient rates
   
   datetime currentBarTime = time[rates_total - 1];           //--- Get current bar time
   bool isNewBar = (currentBarTime != lastCompletedBarTime);  //--- Check if new bar
   
   if(IsNewSessionStarted(currentBarTime, previousBarTime) || previousBarTime == 0) { //--- Check new session
      if(activeSessionIndex >= 0 && activeSessionIndex < ArraySize(sessions)) { //--- Check active session
         sessions[activeSessionIndex].endTime = previousBarTime; //--- Set end time
         RenderSessionProfile(activeSessionIndex);             //--- Render session profile
      }
      
      activeSessionIndex = CreateNewSession();                 //--- Create new session
      sessions[activeSessionIndex].startTime = currentBarTime; //--- Set start time
      sessions[activeSessionIndex].sessionOpen = open[rates_total - 1]; //--- Set session open
      sessions[activeSessionIndex].sessionHigh = high[rates_total - 1]; //--- Set session high
      sessions[activeSessionIndex].sessionLow = low[rates_total - 1]; //--- Set session low
      lastCompletedBarTime = currentBarTime;                   //--- Update last completed bar time
   }
   
   previousBarTime = currentBarTime;                           //--- Update previous bar time
   
   if(isNewBar && IsBarEligibleForProcessing(currentBarTime) && activeSessionIndex >= 0) { //--- Check if process bar
      sessions[activeSessionIndex].sessionHigh = MathMax(sessions[activeSessionIndex].sessionHigh, high[rates_total - 1]); //--- Update session high
      sessions[activeSessionIndex].sessionLow = MathMin(sessions[activeSessionIndex].sessionLow, low[rates_total - 1]); //--- Update session low
      sessions[activeSessionIndex].sessionClose = close[rates_total - 1]; //--- Update session close
      
      int periodIndex = sessions[activeSessionIndex].periodCount;             //--- Get period index
      ArrayResize(sessions[activeSessionIndex].periodHighs, periodIndex + 1); //--- Resize period highs
      ArrayResize(sessions[activeSessionIndex].periodLows, periodIndex + 1);  //--- Resize period lows
      ArrayResize(sessions[activeSessionIndex].periodOpens, periodIndex + 1); //--- Resize period opens
      
      sessions[activeSessionIndex].periodHighs[periodIndex] = high[rates_total - 1]; //--- Set period high
      sessions[activeSessionIndex].periodLows[periodIndex] = low[rates_total - 1];   //--- Set period low
      sessions[activeSessionIndex].periodOpens[periodIndex] = open[rates_total - 1]; //--- Set period open
      sessions[activeSessionIndex].periodCount++; //--- Increment period count
      
      if(periodIndex < initialBalancePeriods) {   //--- Check if within IB periods
         if(periodIndex == 0) {                   //--- Handle first period
            sessions[activeSessionIndex].initialBalanceHigh = high[rates_total - 1]; //--- Set IB high
            sessions[activeSessionIndex].initialBalanceLow = low[rates_total - 1];   //--- Set IB low
         } else {                                 //--- Handle subsequent
            sessions[activeSessionIndex].initialBalanceHigh = MathMax(sessions[activeSessionIndex].initialBalanceHigh, high[rates_total - 1]); //--- Update IB high
            sessions[activeSessionIndex].initialBalanceLow = MathMin(sessions[activeSessionIndex].initialBalanceLow, low[rates_total - 1]); //--- Update IB low
         }
      }
      
      double quantizedHigh = QuantizePriceToGrid(high[rates_total - 1]); //--- Quantize high
      double quantizedLow = QuantizePriceToGrid(low[rates_total - 1]);   //--- Quantize low
      
      for(double price = quantizedLow; price <= quantizedHigh; price += tpoPriceGridStep) { //--- Loop through prices
         int levelIndex = GetOrCreatePriceLevel(activeSessionIndex, price);                 //--- Get or create level
         if(levelIndex >= 0) {                    //--- Check valid level
            AddTpoCharacterToLevel(activeSessionIndex, levelIndex, periodIndex);            //--- Add TPO character
            sessions[activeSessionIndex].levels[levelIndex].volume += (double)tick_volume[rates_total - 1] / 
                                                       MathMax(1, (quantizedHigh - quantizedLow) / tpoPriceGridStep + 1); //--- Add volume
         }
      }
      
      CalculateSessionVwap(activeSessionIndex);   //--- Calculate VWAP
      lastCompletedBarTime = currentBarTime;      //--- Update last completed bar time
   }
   
   if(IsBarEligibleForProcessing(currentBarTime) && activeSessionIndex >= 0) { //--- Check if update session
      sessions[activeSessionIndex].sessionClose = close[rates_total - 1];      //--- Update close
      sessions[activeSessionIndex].sessionHigh = MathMax(sessions[activeSessionIndex].sessionHigh, high[rates_total - 1]); //--- Update high
      sessions[activeSessionIndex].sessionLow = MathMin(sessions[activeSessionIndex].sessionLow, low[rates_total - 1]); //--- Update low
   }
   
   for(int i = 0; i < ArraySize(sessions); i++) { //--- Loop through sessions
      RenderSessionProfile(i);                    //--- Render profile
   }
   
   return rates_total;                            //--- Return rates total
}

OnCalculateイベントハンドラでは、データ処理中にIB追跡や出来高累積などの新しい機能を統合するためにロジックを更新します。まず、利用可能なレート数が不足している場合は0を返します。現在のバー時間を取得し、新しいバーかどうかを確認します。その後、IsNewSessionStartedによって新しいセッションが開始された場合、または以前の時間が存在しない場合は、アクティブなセッションの終了時間を確定し、ArraySizeによって有効範囲内であることを確認した上でRenderSessionProfileを使用して描画します。続いて、CreateNewSessionを使用して新しいセッションを作成し、現在のバーから基本フィールドを初期化し、前回完了時間を更新します。これは前回のバージョンでおこなった処理と同様です。

前回のバー時間を設定した後、IsBarEligibleForProcessingによって新しい処理対象バーであり、かつアクティブセッションが存在する場合は、MathMaxMathMin、および終値を使用してセッションの高値・安値を更新します。さらに、期間ごとの高値、安値、始値を配列にリサイズして保存し、期間カウントを増加させます。initialBalancePeriods内の場合は、期間インデックスに基づいてIBの高値と安値を設定または更新します。バーの高値と安値を量子化し、その範囲をループしてGetOrCreatePriceLevelを使用してレベルを取得または作成します。その後、AddTpoCharacterToLevelを使用してTime Price Opportunity文字を追加します。また、MathMaxで最低1に確保した価格ステップを使用し、ティック出来高を価格ステップで割って比例配分した出来高を累積します。その後、CalculateSessionVwapによってVWAPを計算し、前回完了時間を更新します。バーが処理対象の場合は、ライブ更新用にセッションの終値、高値、安値を更新します。その後、すべてのセッションをループして描画し、合計レート数を返します。コンパイルすると、次の結果が得られます。

ハイブリッドボリュームベース市場プロファイル - ドット文字

画像から、出来高ベースのプロファイルロジックと拡張された可視化機能を追加することでインジケーターを更新し、ハイブリッド版にしたことが確認できます。これにより、目的を達成しました。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。


バックテスト

テストを実施しました。以下は、コンパイル後の動作を示す単一のGraphics Interchange Format (GIF)画像です。

バックテストGIF


結論

結論として、 MQL5のハイブリッドTime Price Opportunity (TPO)マーケットプロファイルインジケーターを拡張し、出来高データを統合することで、出来高ベースのPoC、VA、VWAPを計算できるようにし、カスタマイズ可能なハイライト機能を追加しました。このシステムには、IB検出、重要レベルの延長ライン、分割プロファイル、四角形や円形などの代替TPO文字、境界線、固定範囲背景、動的出来高ラベルなどの高度な機能が追加され、複数の時間足にわたる包括的な視覚分析を可能にします。このアップグレードされたハイブリッドTime Price Opportunityマーケットプロファイルインジケーターにより、市場構造と出来高の動きをより深く理解するための環境が整い、取引におけるさらなる最適化へ進む準備ができました。それでは、よいトレードを。

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

添付されたファイル |
初心者からエキスパートへ:イントラデイ戦略の自動化 初心者からエキスパートへ:イントラデイ戦略の自動化
EMA-50リテストの考え方を、日中取引向けの動作ベースのエキスパートアドバイザー(EA)へ変換します。この研究では、トレンドバイアス、EMAとの相互作用(EMAを貫き、所定の側で引ける動き)、反応の確認、オプションのフィルタを体系化し、それらをモジュール化された関数と安全なリソース管理を意識したハンドル運用を使用してMQL5で実装します。ストラテジーテスターでのビジュアルテストにより、シグナルの正確性を確認します。その結果、裁量判断による反発手法をコード化するための明確なテンプレートが完成します。
MQL5取引ツール(第20回):統計的相関分析と回帰分析を用いたCanvasグラフ作成 MQL5取引ツール(第20回):統計的相関分析と回帰分析を用いたCanvasグラフ作成
MQL5を用いて、2つの銘柄間の統計的相関および線形回帰分析をおこなう、Canvasベースのグラフ描画ツールを作成します。本ツールには、ドラッグ操作やリサイズ機能を備えたインタラクティブなインターフェースを実装し、回帰計算にはALGLIBを活用します。また、動的な目盛りラベル、データポイント、傾き・切片・相関係数・決定係数を表示する統計パネルも組み込みます。このインタラクティブな可視化ツールにより、ペアトレード分析に役立つ洞察を得ることができ、テーマや枠線のカスタマイズ、新しいバーの生成に応じたリアルタイム更新にも対応します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
MQL5でカスタムインジケータを作成する(第7回):セッション分析のためのハイブリッドTime Price Opportunity (TPO)マーケットプロファイル MQL5でカスタムインジケータを作成する(第7回):セッション分析のためのハイブリッドTime Price Opportunity (TPO)マーケットプロファイル
ハイブリッドTime Price Opportunity (TPO)マーケットプロファイル用のカスタムインジケータをMQL5で開発します。このインジケータは、タイムゾーン調整に対応し、イントラデイ、日足、週足、月足、固定期間など、複数のセッション時間足をサポートします。このインジケータでは、価格をグリッド化し、各セッションの高値、安値、始値、終値などのデータを追跡します。また、TPOカウントに基づいて、ポイントオブコントロール(PoC)やバリューエリア(VA)などの主要要素を計算します。さらに、TPO文字、単独プリント、VA、PoC、終値マーカーの色をカスタマイズ可能にし、チャート上にプロファイルを視覚的に描画することで、詳細なセッション分析を可能にします。