English Deutsch
preview
Market Memory Zonesインジケーターの自動化:価格が戻りやすい領域

Market Memory Zonesインジケーターの自動化:価格が戻りやすい領域

MetaTrader 5 |
30 0
Hlomohang John Borotho
Hlomohang John Borotho

目次

  1. はじめに
  2. 自動化の概要
  3. 導入手順
  4. バックテスト結果
  5. 結論


はじめに

前回は、価格が明確な意図を示した重要な領域、すなわちディスプレイスメント、市場構造転換 (CHoCH)、流動性スイープを特定することで、Market Memory Zonesインジケーターを開発しました。生の価格変動を、インバランス、市場支配の変化、あるいは流動性イベントが発生した場所を視覚的に表現する構造化されたゾーンへ変換しました。任意のサポートやレジスタンスを示すのではなく、このインジケーターは、市場の挙動に基づく根拠を持つ領域を強調表示するよう設計されています。具体的には、通常のボラティリティを超える衝動的なディスプレイスメント、明確な市場構造転換 (CHoCH)の発生、反転を伴う明確な流動性狩り(Liquidity Grab)を検出します。

今回は、その分析フレームワークを、実際に取引を実行できる完全自動のEAへ発展させます。ゾーンを単なる視覚的な参照ポイントとして扱うのではなく、自動化によって、それらを体系的な売買判断ポイントへ変換します。具体的には、ゾーンの形成をリアルタイムで検出し、その有効性を評価し、価格がゾーンと相互作用し、その意図が確認された場合にのみ取引を実行します。確認ロジック、市場行動に基づくフィルタ、そしてリスク管理ルールをシステムへ直接組み込むことで、単なる観察から実際の執行へと移行します。


自動化の概要

ディスプレイスメントゾーンは、市場における緊急性やインバランスを示す、通常よりも強い衝動的なローソク足を検出することで自動化されます。EAは、ローソク足の値幅がATRを大きく上回り、全体の値幅に対して実体が大きく、直前のローソク足との重複が少なく、さらに終値が高値または安値付近で形成された場合に、これらのゾーンを検出します。検出された場合、そのローソク足全体の値幅がゾーンとして設定され、ローソク足の終値方向によってバイアス(方向性)が決定されます。EAは、単純にブレイクアウト自体を取引するのではなく、価格がゾーンから一度離れ、その後再びゾーンへ戻ってくるのを待ちます。買いの場合、強気のディスプレイスメントが発生した後、価格がゾーンへ再訪し、下位足での確認、たとえばリジェクションやマイクロストラクチャーの変化を示した場合にエントリーします。ストップロスはゾーンの外側に配置され、ターゲットは近くの市場構造上の高値へ設定されます。売りの場合は、弱気のディスプレイスメントに対して同じロジックを反対方向へ適用します。

ディスプレイスメントゾーン

市場構造転換ゾーンは、過去の市場構造のブレイクを検出し、買い手と売り手の支配力が変化したことを示すことで自動化されます。EAはスイングハイとスイングローを追跡し、現在のトレンドとは逆方向へのブレイクが発生した場合にChange of Character (CHoCH)を検出します。たとえば、下降トレンド中にスイングハイが突破された場合、これは市場構造の変化として認識されます。ゾーンは、構造ブレイクが発生する直前の最後の反対方向のローソク足によって定義されます。強気の市場構造転換では、EAは価格がこのゾーンへ押し戻されるのを待ち、Higher Low(安値切り上げ)や強気の終値によって強さが確認された後、買いエントリーを実行します。ストップロスは転換点となったスイングの外側へ配置され、ターゲットは以前のレンジ高値またはトレンド継続レベルに設定されます。売りの場合は、このロジックを反対方向へ適用します。

市場構造転換 ゾーン

流動性スイープゾーンは、過去の高値を一時的に上抜け、または過去の安値を一時的に下抜けた後、すぐにレンジ内部へ戻るローソク足を検出することで自動化されます。これらのスイープは、多くの場合、ストップ狩りの後に強い方向性が発生する動きを表します。EAはスイープが発生したローソク足の値幅をゾーンとして設定し、価格が一度ゾーンから離れた後、再び戻ってくるまで監視します。買いの場合、価格が安値を下抜けて流動性を取得した後に上昇へ転じ、その後ゾーンへ再訪します。この時、EAは確認条件として、たとえば新たな安値を形成できない動きや、強気包み足パターンなどを確認してからエントリーします。ストップロスはスイープで形成された極値の外側へ配置され、ターゲットは反対側の流動性領域やレジスタンスレベルに設定されます。売りの場合は、高値をスイープした後に同じロジックを反対方向へ適用します。

流動性スイープ


導入手順

//+------------------------------------------------------------------+
//|                                                     Auto MMZ.mq5 |
//|                        GIT under Copyright 2025, MetaQuotes Ltd. |
//|                     https://www.mql5.com/ja/users/johnhlomohang/ |
//+------------------------------------------------------------------+
#property copyright "GIT under Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/ja/users/johnhlomohang/"
#property version   "1.00"

#include <Trade/Trade.mqh>
#include <Math/Stat/Math.mqh>
CTrade trade;

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input ENUM_TIMEFRAMES HTF = PERIOD_H1;          // Higher Timeframe
input ENUM_TIMEFRAMES LTF = PERIOD_M5;          // Lower Timeframe
input ENUM_TIMEFRAMES ATF = PERIOD_M15;         // Analysis Timeframe (for zones)
input int             SwingBars = 3;            // Bars for swing detection

// Displacement Zone Parameters
input double          Displacement_ATR_Multiplier = 1.8;    // ATR multiplier for displacement
input double          Displacement_Body_Percent = 60.0;     // Minimum body percentage
input double          Displacement_Overlap_Max = 30.0;      // Maximum overlap with previous candle
input double          Displacement_Close_Extreme = 20.0;    // Close near extreme percentage

// Risk Management
input double          RiskPercent = 1.0;        // Risk per trade (%)
input int             StopLoss_Pips = 150;      // Stop Loss in pips
input double          RiskRewardRatio = 2.0;    // Risk/Reward ratio
input bool            UseDynamicSL = true;      // Use zone-based SL
input bool            UseStructureTP = true;    // Use structure for TP

// Trade Management
input int             MaxTradesPerDay = 10;     // Maximum trades per day
input bool            AllowConsecutiveTrades = true;  // Allow multiple trades in same direction
input double          TrailingStop_ATR = 1.5;   // ATR multiple for trailing stop

// Visualization
input bool            ShowZones = true;         // Show zone rectangles
input bool            ShowArrows = true;        // Show entry arrows
input bool            ShowLabels = true;        // Show trade labels
input bool            DebugMode = true;         // Show debug information

//+------------------------------------------------------------------+
//| Zone Structure                                                   |
//+------------------------------------------------------------------+
enum ZONE_TYPE
{
   ZONE_DISPLACEMENT,
   ZONE_STRUCTURE_TRANSITION,
   ZONE_LIQUIDITY_SWEEP
};

struct TradeZone
{
   datetime          time;
   double            high;
   double            low;
   double            mid;
   ZONE_TYPE         type;
   int               direction;      // 1 for bullish, -1 for bearish
   double            atr;            // ATR at time of zone formation
   datetime          expiry;         // Zone expiry time
   bool              active;
   bool              triggered;
   string            symbol;
   ENUM_TIMEFRAMES   tf;
   
   // Constructor to initialize with default values
   TradeZone()
   {
      Reset();
   }
   
   // Reset method
   void Reset()
   {
      time = 0;
      high = 0;
      low = 0;
      mid = 0;
      type = ZONE_DISPLACEMENT;
      direction = 0;
      atr = 0;
      expiry = 0;
      active = false;
      triggered = false;
      symbol = _Symbol;
      tf = PERIOD_CURRENT;
   }
};

まず、EAの基盤を構築するために、取引ライブラリと統計ライブラリをインポートし、注文執行用のCTradeオブジェクトを初期化します。また、マルチタイムフレーム分析、ディスプレイスメント検出ルール、リスク管理、トレード管理、そして可視化の動作を制御する、すべての設定可能な入力項目を定義します。これらの入力によって、システムは上位足、下位足、分析用時間足においてゾーンを検出できるようになります。さらに、ATRを基準とした条件によってディスプレイスメントの強さを評価し、パーセントベースのポジションサイズ計算、ストップロス設定ロジック、リスクリワード比率によってリスク管理を制御します。

次に、ディスプレイスメントゾーン(急激な価格移動)、市場構造転換ゾーン、流動性スイープゾーンを分類するために、列挙型のZONE_TYPEを定義します。その後、価格範囲、方向性、形成時のATR、期限、アクティブ状態、メタデータなど、ゾーンに関連するすべての情報を格納するための構造化されたTradeZoneデータモデルを作成します。コンストラクタおよびReset()メソッドにより、各ゾーンは常にクリーンで制御された初期状態から開始されるように設計されています。これにより、EA内で複数の動的なMarket Memory Zones(マーケットメモリゾーン)を効率的かつ拡張可能な形で管理するための、体系的なフレームワークを構築します。

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
SwingPoint htf_lastHigh, htf_prevHigh;
SwingPoint htf_lastLow, htf_prevLow;
SwingPoint atf_lastHigh, atf_prevHigh;
SwingPoint atf_lastLow, atf_prevLow;

TradeZone zones[100];
int zoneCount = 0;

TradeInfo activeTrades[10];
int activeTradeCount = 0;

datetime lastTradeDay = 0;
int tradesToday = 0;

double currentATR = 0;
int atrHandle = -1;

MqlDateTime today;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // Initialize ATR indicator
   atrHandle = iATR(_Symbol, PERIOD_CURRENT, 14);
   if(atrHandle == INVALID_HANDLE)
   {
      Print("Failed to create ATR indicator");
      return INIT_FAILED;
   }
   
   // Initialize swing points
   htf_lastHigh.Reset();
   htf_prevHigh.Reset();
   htf_lastLow.Reset();
   htf_prevLow.Reset();
   atf_lastHigh.Reset();
   atf_prevHigh.Reset();
   atf_lastLow.Reset();
   atf_prevLow.Reset();
   
   // Initialize zones array
   for(int i = 0; i < ArraySize(zones); i++)
      zones[i].Reset();
   zoneCount = 0;
   
   // Initialize active trades array
   for(int i = 0; i < ArraySize(activeTrades); i++)
      activeTrades[i].Reset();
   activeTradeCount = 0;
   
   // Initialize trade tracking
   lastTradeDay = 0;
   tradesToday = 0;
   
   // Set magic number for trades
   trade.SetExpertMagicNumber(12345);
   
   Print("EA Initialized. Zone Types: Displacement, Structure Transition, Liquidity Sweep");
   
   return INIT_SUCCEEDED;
}

このセクションでは、EAの中核となるグローバル状態を構築します。具体的には、上位足および分析用時間足の両方に対応したスイングポイント追跡用の構造体を定義し、検出された最大100個のトレードゾーンと、最大10件のアクティブトレードを保存するための配列を宣言します。また、1日の取引回数制限を管理する変数や、ATRを基にしたボラティリティ追跡用の変数も準備します。OnInit()関数では、ボラティリティ計算を処理するためにATRインジケーターハンドルを初期化します。さらに、すべてのスイング構造をリセットして初期状態をクリーンに保ち、ゾーン配列およびアクティブトレード配列をクリアして準備します。そして、日次取引カウンターもリセットします。全体として、この初期設定により、システムは制御された整理済みの状態で起動し、リアルタイムでのゾーン検出およびトレード実行に必要なデータ構造が正しく初期化された状態を確保します。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(atrHandle != INVALID_HANDLE)
      IndicatorRelease(atrHandle);
   
   ObjectsDeleteAll(0, "ZONE_");
   ObjectsDeleteAll(0, "TRADE_");
   ChartRedraw();
   
   Print("EA Deinitialized");
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // Update current ATR
   UpdateATR();
   
   // Check for new bars on analysis timeframe
   static datetime lastAtfBar = 0;
   datetime currentAtfBar = iTime(_Symbol, ATF, 0);
   
   if(currentAtfBar != lastAtfBar)
   {
      lastAtfBar = currentAtfBar;
      
      if(DebugMode) Print("New ATF bar: ", TimeToString(currentAtfBar));
      
      // Update market structure
      UpdateMarketStructure();
      
      // Detect new zones
      DetectDisplacementZones();
      DetectStructureTransitionZones();
      DetectLiquiditySweepZones();
      
      // Clean up expired zones
      CleanupZones();
   }
   
   // Check zone entries on every tick
   CheckZoneEntries();
   
   // Manage open positions
   ManageOpenTrades();
   
   // Update visualization
   if(ShowZones) UpdateVisualization();
}

//+------------------------------------------------------------------+
//| Update Market Structure                                          |
//+------------------------------------------------------------------+
void UpdateMarketStructure()
{
   // Update HTF structure
   UpdateSwingPoints(HTF, htf_lastHigh, htf_prevHigh, htf_lastLow, htf_prevLow);
   
   // Update Analysis TF structure
   UpdateSwingPoints(ATF, atf_lastHigh, atf_prevHigh, atf_lastLow, atf_prevLow);
   
   if(DebugMode && atf_lastHigh.price > 0 && atf_lastLow.price > 0)
   {
      Print("Market Structure - ATF High: ", atf_lastHigh.price, 
            " Low: ", atf_lastLow.price,
            " ATR: ", currentATR);
   }
}

OnDeinit()関数は、EAの初期化解除処理を管理し、ATRインジケーターハンドルを解放することでシステムリソースを確保します。また、ゾーンやトレードに関連するすべてのチャートオブジェクトを、それぞれのプレフィックスを使用して削除します。その後、チャートを更新して残存しているグラフィカルな表示要素を取り除き、処理完了の確認メッセージをログへ出力します。これにより、EAが削除または再起動された際に、不要なインジケーター、描画オブジェクト、またはメモリ割り当てが次回の実行に影響を与えることを防ぎ、システムの安定性を維持するとともに、リソースリークを防止します。

OnTick()関数は、EAの動作エンジンとして機能し、リアルタイムで検出、実行、管理の各ロジックを統合的に制御します。ティックごとに、現在のATRを更新し、分析時間足で新しいバーが形成されたかどうかを確認してから、市場構造を再計算し、新しいディスプレイスメント、市場構造転換ゾーン、および流動性スイープゾーンをスキャンします。期限切れとなったゾーンは体系的に削除され、エントリー条件はすべてのティックで継続的に評価されることで、適切なタイミングでのトレード実行を可能にします。また、保有中のトレードは常時管理され、設定が有効な場合にはチャート上のビジュアル要素も更新されます。UpdateMarketStructure()関数は、この処理フローを補助する役割を持ち、上位足および分析用時間足の両方でスイングポイントを更新します。これにより、EAは変化し続ける市場構造やボラティリティ環境に常に適応した状態を維持します。

//+------------------------------------------------------------------+
//| Detect Displacement Zones                                        |
//+------------------------------------------------------------------+
void DetectDisplacementZones()
{
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   int copied = CopyRates(_Symbol, ATF, 0, 5, rates);
   
   if(copied < 2) return;
   
   // Check latest candle for displacement
   MqlRates candle = rates[0];
   MqlRates prevCandle = rates[1];
   
   // Calculate candle metrics
   double range = candle.high - candle.low;
   if(range <= 0) return;
   
   double body = MathAbs(candle.close - candle.open);
   double bodyPercent = (body / range) * 100;
   
   // Calculate overlap with previous candle
   double overlapHigh = MathMin(candle.high, prevCandle.high);
   double overlapLow = MathMax(candle.low, prevCandle.low);
   double overlap = (overlapHigh > overlapLow) ? overlapHigh - overlapLow : 0;
   double overlapPercent = (overlap / range) * 100;
   
   // Check close near extreme (for bullish or bearish)
   double closeToHigh = candle.high - candle.close;
   double closeToLow = candle.close - candle.low;
   double closePercent;
   
   if(candle.close > candle.open) // Bullish candle
      closePercent = (closeToHigh / range) * 100;
   else // Bearish candle
      closePercent = (closeToLow / range) * 100;
   
   // Debug information
   if(DebugMode)
   {
      Print("Checking displacement - Range: ", range, 
            " ATR*Mult: ", currentATR * Displacement_ATR_Multiplier,
            " Body%: ", bodyPercent, " Req: ", Displacement_Body_Percent,
            " Overlap%: ", overlapPercent, " Max: ", Displacement_Overlap_Max,
            " Close%: ", closePercent, " Max: ", Displacement_Close_Extreme);
   }
   
   // Check displacement conditions
   bool isDisplacement = 
      (range >= currentATR * Displacement_ATR_Multiplier) &&
      (bodyPercent >= Displacement_Body_Percent) &&
      (overlapPercent <= Displacement_Overlap_Max) &&
      (closePercent <= Displacement_Close_Extreme);
   
   if(isDisplacement)
   {
      // Check if similar zone already exists (within last 5 bars)
      bool zoneExists = false;
      for(int i = 0; i < zoneCount; i++)
      {
         if(zones[i].active && MathAbs(zones[i].time - candle.time) < PeriodSeconds(ATF) * 5)
         {
            zoneExists = true;
            break;
         }
      }
      
      if(!zoneExists)
      {
         // Create displacement zone
         TradeZone zone;
         zone.time = candle.time;
         zone.high = candle.high;
         zone.low = candle.low;
         zone.mid = (zone.high + zone.low) / 2.0;
         zone.type = ZONE_DISPLACEMENT;
         zone.direction = (candle.close > candle.open) ? 1 : -1;
         zone.atr = currentATR;
         zone.expiry = zone.time + (24 * 60 * 60);  // 24 hour expiry
         zone.active = true;
         zone.triggered = false;
         zone.symbol = _Symbol;
         zone.tf = ATF;
         
         // Add to zones array
         int index = zoneCount % 100;
         zones[index] = zone;
         zoneCount++;
         
         Print("Displacement Zone Detected: ", 
               (zone.direction == 1) ? "BULLISH" : "BEARISH",
               " | High: ", zone.high, " Low: ", zone.low,
               " | Time: ", TimeToString(zone.time));
      }
   }
}

//+------------------------------------------------------------------+
//| Detect Structure Transition Zones (CHoCH)                        |
//+------------------------------------------------------------------+
void DetectStructureTransitionZones()
{
   // Check for bullish CHoCH (downtrend to uptrend)
   if(atf_prevHigh.price > 0 && atf_lastHigh.price > atf_prevHigh.price &&
      atf_prevLow.price > 0 && atf_lastLow.price > atf_prevLow.price)
   {
      // Bullish transition - look for last bearish candle before break
      MqlRates rates[];
      ArraySetAsSeries(rates, true);
      int startBar = MathMax(0, atf_lastHigh.barIndex - 10);
      int copied = CopyRates(_Symbol, ATF, startBar, 20, rates);
      
      if(DebugMode) Print("Checking Bullish CHoCH - PrevHigh: ", atf_prevHigh.price, 
                        " LastHigh: ", atf_lastHigh.price);
      
      for(int i = 0; i < copied; i++)
      {
         if(rates[i].close < rates[i].open)  // Bearish candle
         {
            CreateStructureTransitionZone(rates[i], 1);
            break;
         }
      }
   }
   
   // Check for bearish CHoCH (uptrend to downtrend)
   if(atf_prevLow.price > 0 && atf_lastLow.price < atf_prevLow.price &&
      atf_prevHigh.price > 0 && atf_lastHigh.price < atf_prevHigh.price)
   {
      // Bearish transition - look for last bullish candle before break
      MqlRates rates[];
      ArraySetAsSeries(rates, true);
      int startBar = MathMax(0, atf_lastLow.barIndex - 10);
      int copied = CopyRates(_Symbol, ATF, startBar, 20, rates);
      
      if(DebugMode) Print("Checking Bearish CHoCH - PrevLow: ", atf_prevLow.price, 
                        " LastLow: ", atf_lastLow.price);
      
      for(int i = 0; i < copied; i++)
      {
         if(rates[i].close > rates[i].open)  // Bullish candle
         {
            CreateStructureTransitionZone(rates[i], -1);
            break;
         }
      }
   }
}

DetectDisplacementZones()関数は、分析用時間足における最新のローソク足をスキャンし、それが厳格な定量的フィルタ条件を満たす真のディスプレイスメント(急激な価格移動)であるかを判定します。この処理では、ローソク足の全体レンジ、実体サイズの割合、直前のローソク足との重複率、そして終値がローソク足の高値または安値にどれだけ近い位置で形成されているかを計算します。これらの指標は、ATRを基準とした条件およびパーセンテージ閾値と比較され、強い方向性、低い重複率、高いモメンタムを持つ価格移動であるかを確認します。すべてのディスプレイスメント条件を満たし、かつ直近に同様のゾーンがすでに存在しない場合、この関数は新しいTradeZoneオブジェクトを生成します。その後、価格上限・下限、ミッドポイント、方向性、形成時のATR環境、期限、アクティブ状態などの情報を設定し、zones配列へ保存します。これにより、統計的に有意なインパルス的価格移動のみが、将来的なリターンゾーン候補として記録されるようになります。

DetectStructureTransitionZones()関数は、分析用時間足におけるスイング構造の変化を解析し、市場構造転換(CHoCH)イベントを検出することを目的としています。強気の構造転換は、以前の下降トレンドの後にHigher High(高値切り上げ)とHigher Lowの両方が形成された場合に検出されます。一方、弱気の構造転換は、以前の上昇トレンドに対してLower Low(安値切り下げ)とLower High(高値切り下げ)が形成され、構造が崩れた場合に識別されます。構造変化が確定すると、この関数はブレイク発生前の最後の逆方向ローソク足を過去へ向かって検索します。具体的には、強気転換の場合は最後の陰線、弱気転換の場合は最後の陽線を対象とします。そして、そのローソク足を基準として、専用の作成関数を使用し、Structure Transition Zoneを生成します。

//+------------------------------------------------------------------+
//| Detect Liquidity Sweep Zones                                     |
//+------------------------------------------------------------------+
void DetectLiquiditySweepZones()
{
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   int copied = CopyRates(_Symbol, ATF, 0, 5, rates);
   
   if(copied < 3) return;
   
   // Check for buy-side liquidity sweep (sweep of lows)
   if(atf_lastLow.price > 0 && rates[1].low < atf_lastLow.price)
   {
      // Check if there's a bullish reversal after the sweep
      bool bullishReversal = rates[1].close > rates[1].open || 
                            (rates[0].close > rates[0].open && rates[0].close > rates[1].high);
      
      if(bullishReversal)
      {
         if(DebugMode) Print("Buy-side Liquidity Sweep detected - Swept Low: ", atf_lastLow.price,
                           " Sweep Candle Low: ", rates[1].low);
         
         CreateLiquiditySweepZone(rates[1], 1);  // Bullish reversal
      }
   }
   
   // Check for sell-side liquidity sweep (sweep of highs)
   if(atf_lastHigh.price > 0 && rates[1].high > atf_lastHigh.price)
   {
      // Check if there's a bearish reversal after the sweep
      bool bearishReversal = rates[1].close < rates[1].open || 
                            (rates[0].close < rates[0].open && rates[0].close < rates[1].low);
      
      if(bearishReversal)
      {
         if(DebugMode) Print("Sell-side Liquidity Sweep detected - Swept High: ", atf_lastHigh.price,
                           " Sweep Candle High: ", rates[1].high);
         
         CreateLiquiditySweepZone(rates[1], -1);  // Bearish reversal
      }
   }
}

//+------------------------------------------------------------------+
//| Create Structure Transition Zone                                 |
//+------------------------------------------------------------------+
void CreateStructureTransitionZone(MqlRates &candle, int direction)
{
   // Check if similar zone already exists (within last 5 bars)
   bool zoneExists = false;
   for(int i = 0; i < zoneCount; i++)
   {
      if(zones[i].active && zones[i].type == ZONE_STRUCTURE_TRANSITION &&
         MathAbs(zones[i].time - candle.time) < PeriodSeconds(ATF) * 5)
      {
         zoneExists = true;
         break;
      }
   }
   
   if(!zoneExists)
   {
      TradeZone zone;
      zone.time = candle.time;
      zone.high = candle.high;
      zone.low = candle.low;
      zone.mid = (zone.high + zone.low) / 2.0;
      zone.type = ZONE_STRUCTURE_TRANSITION;
      zone.direction = direction;
      zone.atr = currentATR;
      zone.expiry = zone.time + (12 * 60 * 60);  // 12 hour expiry
      zone.active = true;
      zone.triggered = false;
      zone.symbol = _Symbol;
      zone.tf = ATF;
      
      // Add to zones array
      int index = zoneCount % 100;
      zones[index] = zone;
      zoneCount++;
      
      Print("Structure Transition Zone Detected: ",
            (zone.direction == 1) ? "BULLISH (CHoCH)" : "BEARISH (CHoCH)",
            " | High: ", zone.high, " Low: ", zone.low,
            " | Time: ", TimeToString(zone.time));
   }
}

DetectLiquiditySweepZones()関数は、過去のスイングハイまたはスイングローを一時的に突破する動きを検出することで、ストップ狩りの挙動を自動的に識別します。この処理では、直近のローソク足をスキャンし、価格が既知のスイングレベルを一時的に超えたかどうかを確認します。その後、同一ローソク足内、または次のローソク足で反転が発生しているかを検証します。過去の安値が掃引(Sweep)され、その後に強気の確認が発生した場合、買い手側(buy-side)流動性スイープゾーンが生成されます。一方、過去の高値が掃引され、その後に弱気の確認が発生した場合、 売り手側(Sell-side)流動性スイープゾーンが形成されます。また、デバッグ出力を提供することで、ゾーン検出プロセスの透明性を確保し、検出ロジックの確認や分析を容易にします。

CreateStructureTransitionZone()関数は、市場構造のブレイクが確認された後、CHoCH(Change of Character:市場構造転換)に基づくゾーン生成処理を正式に実行します。新しいゾーンを作成する前に、まず最近の一定期間内に同様のアクティブなStructure Transitionゾーンがすでに存在していないかを確認し、重複したゾーン生成を防止します。同等のゾーンが存在しない場合、この関数は構造ブレイク直前のローソク足を基準として、新しいTradeZoneオブジェクトを初期化します。そして、価格上限・下限、ミッドポイント、方向性(強気または弱気)、形成時のATR環境、および12時間の有効期限を設定します。生成されたゾーンは、ローリングインデックス方式を使用してzones配列へ保存され、監視対象としてアクティブ化されます。その後、ターミナルへログを出力することで記録を残します。これにより、各市場構造転換がEA内部のマーケットメモリーフレームワーク内で整理された状態で記録・管理され、効率的かつ一貫したゾーン管理が実現されます。

//+------------------------------------------------------------------+
//| Check Zone Entries                                               |
//+------------------------------------------------------------------+
void CheckZoneEntries()
{
   // Reset daily trade count if new day
   TimeToStruct(TimeCurrent(), today);
   if(lastTradeDay != today.day)
   {
      tradesToday = 0;
      lastTradeDay = today.day;
      if(DebugMode) Print("New day started. Trade counter reset.");
   }
   
   // Check if we can take more trades today
   if(MaxTradesPerDay > 0 && tradesToday >= MaxTradesPerDay)
   {
      if(tradesToday == MaxTradesPerDay)
         Print("Maximum daily trades (", MaxTradesPerDay, ") reached for today.");
      return;
   }
   
   double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   
   for(int i = 0; i < zoneCount; i++)
   {
      if(!zones[i].active || zones[i].triggered) continue;
      
      // Check if zone is expired
      if(TimeCurrent() > zones[i].expiry)
      {
         zones[i].active = false;
         continue;
      }
      
      // Check BUY entries (bullish zones)
      if(zones[i].direction == 1)
      {
         // Check if price is returning to zone (using Ask for buys)
         if(currentAsk >= zones[i].low && currentAsk <= zones[i].high)
         {
            // Get LTF confirmation
            if(CheckLTFConfirmation(true))
            {
               Print("BUY Signal: Price in ", ZoneTypeToString(zones[i].type), 
                     " zone | Entry: ", currentAsk, " Zone: [", zones[i].low, "-", zones[i].high, "]");
               ExecuteZoneTrade(true, zones[i], currentAsk);
               zones[i].triggered = true;
               tradesToday++;
               break;  // Take only one trade per tick
            }
         }
      }
      // Check SELL entries (bearish zones)
      else if(zones[i].direction == -1)
      {
         // Check if price is returning to zone (using Bid for sells)
         if(currentBid >= zones[i].low && currentBid <= zones[i].high)
         {
            // Get LTF confirmation
            if(CheckLTFConfirmation(false))
            {
               Print("SELL Signal: Price in ", ZoneTypeToString(zones[i].type), 
                     " zone | Entry: ", currentBid, " Zone: [", zones[i].low, "-", zones[i].high, "]");
               ExecuteZoneTrade(false, zones[i], currentBid);
               zones[i].triggered = true;
               tradesToday++;
               break;  // Take only one trade per tick
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Check LTF Confirmation                                           |
//+------------------------------------------------------------------+
bool CheckLTFConfirmation(bool forBuy)
{
   MqlRates ltfRates[];
   ArraySetAsSeries(ltfRates, true);
   int copied = CopyRates(_Symbol, LTF, 0, 5, ltfRates);
   
   if(copied < 3) return false;
   
   // For BUY confirmation
   if(forBuy)
   {
      // Simple confirmation: bullish candle closing above midpoint
      if(ltfRates[0].close > ltfRates[0].open && 
         ltfRates[0].close > ((ltfRates[0].high + ltfRates[0].low) / 2))
      {
         return true;
      }
      
      // Alternative: bullish engulfing
      if(ltfRates[0].close > ltfRates[0].open && 
         ltfRates[0].close > ltfRates[1].high && 
         ltfRates[0].open < ltfRates[1].low)
      {
         return true;
      }
   }
   // For SELL confirmation
   else
   {
      // Simple confirmation: bearish candle closing below midpoint
      if(ltfRates[0].close < ltfRates[0].open && 
         ltfRates[0].close < ((ltfRates[0].high + ltfRates[0].low) / 2))
      {
         return true;
      }
      
      // Alternative: bearish engulfing
      if(ltfRates[0].close < ltfRates[0].open && 
         ltfRates[0].close < ltfRates[1].low && 
         ltfRates[0].open > ltfRates[1].high)
      {
         return true;
      }
   }
   
   return false;
}

ここでは、検出されたMarket Memory Zone(マーケットメモリーゾーン)が実際のトレードへ移行するタイミングを判断する、EAの中核となる実行エンジンを実装します。この関数は、まず日次取引制限の管理から開始します。新しい取引日の開始時にはカウンターをリセットし、設定された最大取引回数に到達した場合、それ以上の新規エントリーを防止します。次に、現在のBid価格およびAsk価格を取得し、すべてのアクティブかつ未発動のゾーンを順番に確認します。各ゾーンについて、最初に有効期限をチェックして、期限切れとなったゾーンを無効化します。その後、価格が有効なゾーンの範囲内へ戻ってきた場合、EAはそのセットアップが強気条件または弱気条件のどちらに該当するかを評価し、トレード実行前に下位足(LTF)での確認処理を要求します。トレードが正常に発注されると、そのゾーンはTriggered(発動済み)状態として記録され、同一ゾーンからの重複エントリーを防止します。同時に、日次取引回数カウンターを加算することで、規律ある取引実行制御を維持します。

CheckLTFConfirmation()関数は、注文送信前に価格反応を検証する役割を持ち、単純な価格到達だけによる無条件エントリーを防ぐための追加フィルタとして機能します。買いエントリーの場合、この関数は大陽線がローソク足のミッドポイントより上で終値を形成していること、または強気包み足パターンの発生を確認します。売りエントリーの場合は、それに対応する弱気条件を確認します。下位足からのモメンタムの証拠を要求することで、EAは単なるゾーンへの価格接触ではなく、市場参加者による実際の反応や方向性の意図に基づいたエントリー判断を下します。

//+------------------------------------------------------------------+
//| Execute Zone Trade                                               |
//+------------------------------------------------------------------+
void ExecuteZoneTrade(bool isBuy, TradeZone &zone, double entryPrice)
{
   // Check if position already exists for this symbol
   if(PositionSelect(_Symbol))
   {
      if(!AllowConsecutiveTrades)
      {
         Print("Position already exists and AllowConsecutiveTrades is false, skipping trade");
         return;
      }
   }
   
   ENUM_ORDER_TYPE orderType = isBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
   
   // Calculate stop loss
   double stopLoss = CalculateZoneStopLoss(isBuy, zone, entryPrice);
   
   // Calculate take profit
   double takeProfit = CalculateZoneTakeProfit(isBuy, zone, entryPrice, stopLoss);
   
   // Calculate lot size
   double volume = CalculateLotSize(entryPrice, stopLoss);
   
   if(volume <= 0)
   {
      Print("Invalid lot size calculated: ", volume, ". Trade cancelled.");
      return;
   }
   
   // Execute trade
   string comment = StringFormat("%s_%s_%s",
      (isBuy ? "BUY" : "SELL"),
      ZoneTypeToString(zone.type),
      TimeToString(TimeCurrent(), TIME_MINUTES));
   
   bool success = trade.PositionOpen(_Symbol, orderType, volume, 
                                     entryPrice, stopLoss, takeProfit, comment);
   
   if(success)
   {
      ulong ticket = trade.ResultOrder();
      Print("Trade Executed Successfully: ", comment,
            " | Ticket: ", ticket,
            " | Entry: ", entryPrice,
            " | SL: ", stopLoss,
            " | TP: ", takeProfit,
            " | Lots: ", volume);
      
      // Store trade info
      if(activeTradeCount < ArraySize(activeTrades))
      {
         activeTrades[activeTradeCount].entryTime = TimeCurrent();
         activeTrades[activeTradeCount].entryPrice = entryPrice;
         activeTrades[activeTradeCount].stopLoss = stopLoss;
         activeTrades[activeTradeCount].takeProfit = takeProfit;
         activeTrades[activeTradeCount].direction = isBuy ? 1 : -1;
         activeTrades[activeTradeCount].zone = zone;
         activeTrades[activeTradeCount].ticket = ticket;
         activeTradeCount++;
      }
   }
   else
   {
      Print("Trade failed: Error Code=", trade.ResultRetcode(),
            " Description=", trade.ResultRetcodeDescription());
   }
}

//+------------------------------------------------------------------+
//| Calculate Zone Stop Loss                                         |
//+------------------------------------------------------------------+
double CalculateZoneStopLoss(bool isBuy, TradeZone &zone, double entryPrice)
{
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   
   if(UseDynamicSL)
   {
      // Zone-based stop loss with buffer
      double buffer = 50 * point;  // 50 pip buffer
      if(isBuy)
      {
         double sl = zone.low - buffer;
         // Ensure SL is below entry
         if(sl >= entryPrice)
            sl = entryPrice - (100 * point);
         return NormalizeDouble(sl, _Digits);
      }
      else
      {
         double sl = zone.high + buffer;
         // Ensure SL is above entry
         if(sl <= entryPrice)
            sl = entryPrice + (100 * point);
         return NormalizeDouble(sl, _Digits);
      }
   }
   else
   {
      // Fixed pip stop loss
      double slDistance = StopLoss_Pips * 10 * point;
      if(isBuy)
         return NormalizeDouble(entryPrice - slDistance, _Digits);
      else
         return NormalizeDouble(entryPrice + slDistance, _Digits);
   }
}

//+------------------------------------------------------------------+
//| Calculate Zone Take Profit                                       |
//+------------------------------------------------------------------+
double CalculateZoneTakeProfit(bool isBuy, TradeZone &zone, double entry, double sl)
{
   double risk = MathAbs(entry - sl);
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   
   if(UseStructureTP)
   {
      if(isBuy)
      {
         // Take profit at next structure high
         double target = entry + (risk * RiskRewardRatio);
         
         if(atf_lastHigh.price > entry)
            target = atf_lastHigh.price;
         else if(htf_lastHigh.price > entry)
            target = htf_lastHigh.price;
         
         // Ensure minimum distance
         double minDistance = 50 * point;
         if(target - entry < minDistance)
            target = entry + (risk * RiskRewardRatio);
         
         return NormalizeDouble(target, _Digits);
      }
      else
      {
         // Take profit at next structure low
         double target = entry - (risk * RiskRewardRatio);
         
         if(atf_lastLow.price < entry)
            target = atf_lastLow.price;
         else if(htf_lastLow.price < entry)
            target = htf_lastLow.price;
         
         // Ensure minimum distance
         double minDistance = 50 * point;
         if(entry - target < minDistance)
            target = entry - (risk * RiskRewardRatio);
         
         return NormalizeDouble(target, _Digits);
      }
   }
   else
   {
      // Fixed risk/reward ratio
      if(isBuy)
         return NormalizeDouble(entry + (risk * RiskRewardRatio), _Digits);
      else
         return NormalizeDouble(entry - (risk * RiskRewardRatio), _Digits);
   }
}

ExecuteZoneTrade()関数は、有効なゾーンエントリーが確認された後に、トレード発注処理全体を管理する役割を担います。まず、同一銘柄ですでにポジションが保有されているかを確認し、AllowConsecutiveTrades設定を考慮することで、不要な複数ポジションの積み増しを防止します。次に、注文タイプ(買いまたは売り)を判定し、専用のゾーンベースロジックを使用してストップロス(SL)およびテイクプロフィット(TP)を計算します。また、リスク管理パラメータに基づいて適切なロットサイズを算出します。計算された取引数量が有効な範囲内である場合、PositionOpen()を使用して注文を送信します。その際、方向性、ゾーンタイプ、タイムスタンプを含む構造化されたコメントを注文へ付加します。トレードが正常に実行された場合、取引情報は将来の管理および追跡処理のためにactiveTrades配列へ保存されます。一方、注文が失敗した場合は、デバッグ目的でブローカーから返されたエラーコードおよび詳細メッセージをログへ記録します。

ストップロスおよびテイクプロフィットの計算ロジックは、Market Memoryの概念に沿うよう設計されています。UseDynamicSLが有効な場合、ストップロスはゾーン境界の外側に安全バッファを加えた位置へ設定されます。これにより、価格がゾーンを完全に突破した場合に、論理的な無効化ポイントとして機能します。一方、この機能が無効の場合は、固定pipsベースのストップロスが適用されます。テイクプロフィットについては、UseStructureTPが有効な場合、分析用時間足または上位足における直近の構造的な高値・安値をターゲットとして動的に設定できます。ただし、過度に近い利確目標を防ぐため、最低距離条件も考慮されます。構造ベースのターゲット設定が無効の場合は、あらかじめ設定された固定リスクリワード倍率に基づいてテイクプロフィットが計算されます。

//+------------------------------------------------------------------+
//| Update Visualization                                             |
//+------------------------------------------------------------------+
void UpdateVisualization()
{
   ObjectsDeleteAll(0, "ZONE_");
   ObjectsDeleteAll(0, "TRADE_");
   
   // Draw zones
   int activeZoneCount = 0;
   for(int i = 0; i < zoneCount; i++)
   {
      if(zones[i].active)
      {
         DrawZone(zones[i], i);
         activeZoneCount++;
      }
   }
   
   // Draw structure levels
   if(ShowLabels)
   {
      if(atf_lastHigh.price > 0)
         CreateHorizontalLine("ATF_High", atf_lastHigh.price, clrRed, STYLE_DASH);
      
      if(atf_lastLow.price > 0)
         CreateHorizontalLine("ATF_Low", atf_lastLow.price, clrBlue, STYLE_DASH);
      
      if(htf_lastHigh.price > 0)
         CreateHorizontalLine("HTF_High", htf_lastHigh.price, clrDarkRed, STYLE_DOT);
      
      if(htf_lastLow.price > 0)
         CreateHorizontalLine("HTF_Low", htf_lastLow.price, clrDarkBlue, STYLE_DOT);
   }
   
   // Show zone count in corner
   if(ShowLabels)
   {
      string labelText = "Active Zones: " + IntegerToString(activeZoneCount);
      CreateLabel("ZoneCount", labelText, 10, 180, clrWhite);
   }
}

//+------------------------------------------------------------------+
//| Draw Zone                                                        |
//+------------------------------------------------------------------+
void DrawZone(TradeZone &zone, int index)
{
   string name = "ZONE_" + IntegerToString(index);
   
   // Calculate zone width (4 bars forward)
   datetime endTime = zone.time + (PeriodSeconds(zone.tf) * 4);
   
   // Draw rectangle
   if(ObjectCreate(0, name, OBJ_RECTANGLE, 0, zone.time, zone.high, endTime, zone.low))
   {
      // Set color based on zone type and direction
      color zoneColor;
      int zoneStyle = STYLE_SOLID;
      
      switch(zone.type)
      {
         case ZONE_DISPLACEMENT:
            zoneColor = (zone.direction == 1) ? clrLimeGreen : clrIndianRed;
            zoneStyle = STYLE_SOLID;
            break;
         case ZONE_STRUCTURE_TRANSITION:
            zoneColor = (zone.direction == 1) ? clrDodgerBlue : clrOrangeRed;
            zoneStyle = STYLE_DASH;
            break;
         case ZONE_LIQUIDITY_SWEEP:
            zoneColor = (zone.direction == 1) ? clrGold : clrMagenta;
            zoneStyle = STYLE_DOT;
            break;
         default:
            zoneColor = clrGray;
      }
      
      ObjectSetInteger(0, name, OBJPROP_COLOR, zoneColor);
      ObjectSetInteger(0, name, OBJPROP_STYLE, zoneStyle);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);
      ObjectSetInteger(0, name, OBJPROP_BACK, true);
      ObjectSetInteger(0, name, OBJPROP_FILL, true);
      ObjectSetInteger(0, name, OBJPROP_ZORDER, 0);
      
      // Add label
      if(ShowLabels)
      {
         string labelName = name + "_LABEL";
         if(ObjectCreate(0, labelName, OBJ_TEXT, 0, zone.time, zone.high))
         {
            string typeText;
            switch(zone.type)
            {
               case ZONE_DISPLACEMENT: typeText = "DISP"; break;
               case ZONE_STRUCTURE_TRANSITION: typeText = "CHoCH"; break;
               case ZONE_LIQUIDITY_SWEEP: typeText = "LIQ"; break;
               default: typeText = "UNK";
            }
            
            string directionText = (zone.direction == 1) ? "BULL" : "BEAR";
            ObjectSetString(0, labelName, OBJPROP_TEXT, typeText + " " + directionText);
            ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrWhite);
            ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8);
            ObjectSetInteger(0, labelName, OBJPROP_BACK, true);
         }
      }
      
      if(DebugMode && ShowZones)
         Print("Drawing zone: ", name, " Type: ", ZoneTypeToString(zone.type), 
               " Dir: ", zone.direction, " High: ", zone.high, " Low: ", zone.low);
   }
}

UpdateVisualization()関数は、チャート上に表示されるすべてのグラフィカル要素を管理し、EA内部の現在の状態が正確に視覚化されるよう制御します。この処理では、まず以前に描画されたゾーンおよびトレード関連オブジェクトを削除し、重複表示や古い情報が残ることを防止します。その後、保存されているすべてのゾーンを順番に確認し、現在もアクティブなゾーンのみを再描画します。これにより、チャート表示とリアルタイムのMarket Memoryロジックが常に同期した状態に保たれます。また、ラベル表示機能が有効になっている場合、この関数は分析用時間足および上位足の最新スイングハイ・安値を示す水平ラインも描画します。これにより、市場構造の状況を明確に把握できる視覚的なコンテキストを提供します。

DrawZone()関数は、個別ゾーンの詳細な描画処理を担当します。この関数では、ゾーンの生成時刻および価格境界を基準として、将来方向へ延長された長方形オブジェクトを作成します。この矩形は複数のバー先まで投影され、ゾーンが影響を及ぼす価格領域を視覚的に表現します。各ゾーンタイプであるディスプレイスメント(急激な価格移動)、市場構造転換(CHoCH)、流動性スイープには、それぞれ異なるカラーおよびラインスタイルが割り当てられます。また、強気方向と弱気方向についても視覚的に区別されるよう設計されています。さらに、オプションのテキストラベル機能により、ゾーンタイプおよび方向性を示す識別表示(例:「DISP BULL」や「CHoCH BEAR」)を追加できます。これにより、チャート上でゾーンの意味を一目で理解できるようになり、分析およびトレード判断の効率が向上します。


バックテスト結果

バックテストは、H1(1時間足)時間軸を使用し、2025年6月1日から2025年8月14日までの2か月間のテスト期間で評価されました。設定はデフォルトパラメータを使用しています。

エクイティカーブ

バックテスト結果


結論

総括すると、Market Memory Zonesの概念を、単なる視覚的な分析ツールから完全自動化されたトレードエンジンへと発展させるため、その構造的ロジックをEAへ直接組み込みました。EAには、ディスプレイスメント(急激な価格移動)、市場構造転換(CHoCH)、流動性スイープの体系的な検出機能を実装し、検出されたゾーンを管理された循環バッファへ保存することで、バックテストおよびリアルタイム運用における長期的な安定性を確保しました。各ゾーンには、方向性、ATR環境、有効期限、構造情報などのコンテキストデータが保持されるため、EAはリアルタイムで価格の動きを監視し、市場状況に応じた判断を下すことが可能になります。さらに、自動エントリーロジック、下位足による確認処理、動的ストップロス設定、構造ベースのテイクプロフィット設定、リスク管理に基づくポジションサイジングを統合することで、裁量的な市場解釈から、明確なルールに基づく自動執行システムへの変換を実現しました。

結論として、Market Memory Zonesの自動化により、戦略の構造的優位性を維持しながら、トレード判断における感情的バイアスを排除することができます。このシステムは、価格が将来的に反応する可能性のある領域を継続的に追跡し、複数時間足による確認を通じて市場反応を検証した上で、あらかじめ定義されたリスク管理ルールに基づいてトレードを実行します。これにより、プロセス全体が一貫性、拡張性、検証可能性を備えたものとなります。価格の記憶(Price Memory)という概念を構造化されたプログラムロジックへ変換することで、トレーダーはより明確な判断基準、再現性の高い運用、そして複数の金融商品や時間足におけるパフォーマンス最適化の可能性を得ることができます。

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

添付されたファイル |
Auto_MMZ.mq5 (41.48 KB)
MQL5入門(第40回):MQL5におけるファイル処理入門(II) MQL5入門(第40回):MQL5におけるファイル処理入門(II)
MQL5で、指定した期間の口座履歴を読み取り、構造化されたレコードをファイルに書き込むことで、CSV形式の取引ジャーナルを作成します。約定数の取得、チケット番号の取得、銘柄や注文タイプの判別、そしてエントリー(ロット、時間、価格、SL/TP)および決済(時間、価格、利益、結果)データの取得方法を、動的配列を使用して解説します。その結果、分析やレポート作成に適した、整理された永続的なログを作成できます。
MQL5経済指標カレンダーを用いたニュースフィルタリング(第1回):MQL5におけるニュース前・ニュース後ウィンドウの実装 MQL5経済指標カレンダーを用いたニュースフィルタリング(第1回):MQL5におけるニュース前・ニュース後ウィンドウの実装
本システムでは、Webリクエストや外部DLLを使用せず、MQL5のみでカレンダー連動型ニュースフィルタを構築します。第1回は、経済イベントの読み込みとキャッシュ処理、通貨を基準とした銘柄へのイベント紐付け、重要度レベルによるフィルタリング、ニュース前後の監視時間(ウィンドウ)の設定、そしてニュース発生中の新規トレード制限について解説します。また、必要に応じてニュース前に保有ポジションを決済する機能も実装します。この結果、不要な取引停止を減らし、ボラティリティが高まる局面でのエントリーを保護する、設定可能でプロップファーム向けにも適したリスク管理機能を実現できます。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
MQL5取引ツール(第18回):向き制御対応の角丸吹き出し MQL5取引ツール(第18回):向き制御対応の角丸吹き出し
角丸長方形と三角形のポインタを組み合わせることで、MQL5における角丸の吹き出しを作成する方法を紹介します。また、ポインタの向きを上、下、左、右に制御する方法についても解説します。さらに、形状のジオメトリの事前計算、スーパーサンプリングによる塗りつぶし、頂点部の丸みを持たせた円弧処理、および継ぎ目を滑らかに接続するための拡張比を用いた分割枠線の描画について詳しく説明します。サイズ、角丸半径、色、不透明度、線の太さなどを自由に設定できるコードを提供しており、トレーディングインターフェースのアラートやツールチップなどにそのまま利用できます。