Market Memory Zonesインジケーターの開発:価格が戻りやすい領域
目次
はじめに
多くのトレーダーにとって、市場において本当に重要となる価格レベルを特定することは困難です。従来のサポート・レジスタンスライン、遅行性のインジケーター、主観的なゾーン描画は、チャートを複雑にし、矛盾したシグナルを生み出す原因となりがちです。その結果、トレーダーは価格変動に対して遅れて反応しやすくなり、期待値の低いエリアでエントリーしたり、市場ノイズを構造として誤認したりするケースが発生します。このような状況は、取引の一貫性を損ない、過剰売買や意思決定への不信につながります。
Market Memory Zonesのアプローチは、市場が実際にどのように動き、その過程でどのような痕跡を残すかに着目することで、この問題に対処します。恣意的なレベルに依存するのではなく、インジケーターは強い価格変動、構造転換、流動性イベントによって形成されたゾーンを客観的に特定します。これらのゾーンは、市場が統計的に再訪しやすい領域を示しており、トレーダーは価格を追いかけるのではなく、あらかじめ反応を想定できるようになります。その結果、より明確な分析、適切なタイミングでの判断、そして規律あるトレード実行につながります。
インジケーターの概要:Market Memory Zones
ディスプレイスメントゾーンは、価格が強い方向性を伴って急激に動いた領域を特定することに重点を置きます。これらのゾーンは、平均的な変動幅を明らかに上回るレンジを持つインパルス的なローソク足によって形成されます。多くの場合、そのレンジはATRの倍数を超える規模になります。この動きが通常のボラティリティではなく本質的なディスプレイスメントであることを確認するためには、ローソク足同士の重なりが小さいことが条件となります。つまり、そのローソク足のレンジの大部分が直前のローソク足のレンジ外に位置している必要があります。この「拡張されたレンジ」と「重複の少なさ」の組み合わせにより、価格が急速に移動しすぎた結果として未消化の流動性や未約定の注文が残された領域が明確になります。これらの領域は、後に価格が再訪する可能性が高いポイントとして機能します。

市場構造転換ゾーンは、市場構造が変化する重要な局面、いわゆるCHoCH(性格の変化)で形成されます。これらのゾーンは、下降トレンドにおいてスイングハイが上抜けされる場合や上昇トレンドにおいてスイングローが下抜けされる場合など、既存の市場構造が破壊されるポイントで発生します。この構造変化が起きた周辺領域は特に重要となります。なぜなら、それは買い手と売り手の支配関係が切り替わる転換点を示しており、その後の価格が再びその領域を訪れて再評価をおこなう可能性があるためです。

流動性スイープの起点ゾーンは、市場が明確な高値・安値をターゲットにして流動性を回収した後に反転するという性質に基づいています。これらのゾーンは、過去の高値を上抜ける、または安値を下抜けるようなスイープが発生し、その直後に価格が反転した場合に特定されます。流動性スイープを引き起こしたローソク足のレンジがゾーンとしてマークされます。これはしばしば、スマートマネー的な参加者が市場に入った起点である可能性があり、そのため価格がその後再びその領域に戻った際に反応が発生する確率が高くなります。

導入手順
//+------------------------------------------------------------------+ //| MarketMemoryZones.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" #property indicator_chart_window #property indicator_chart_window #property indicator_buffers 0 #property indicator_plots 0 //+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ input ENUM_TIMEFRAMES AnalysisTF = PERIOD_CURRENT; // Analysis Timeframe input int LookbackBars = 200; // Bars to analyze input double MinCandleSizeATR = 1.5; // Min candle size (ATR multiplier) input bool ShowDisplacementZones = true; // Show displacement zones input bool ShowUnfilledAreas = true; // Show unfilled price areas input bool ShowStructureZones = true; // Show structure transition zones input bool ShowLiquidityZones = true; // Show liquidity sweep origins input bool FilterByVolume = false; // Require volume spike input int MaxActiveZones = 15; // Max zones to show input bool ExtendZonesForward = true; // Extend zones forward input int ZoneOpacity = 20; // Zone opacity (0-100)
このセクションでは、Market Memory Zonesインジケーターのコアとなるセットアップと設定オプションを定義します。このインジケーターはインジケーターバッファやプロットを使用せず、すべてチャートウィンドウ上に直接描画される設計とします。そのため、ゾーンの可視化には矩形などのグラフィカルオブジェクトのみを使用します。ユーザーは入力パラメータによって分析方法を自由に制御できます。具体的には、検出に用いる時間足、分析対象とする履歴バーの本数、そしてATR倍率を用いた「有意なディスプレイスメント」として認定するための最小ローソク足サイズなどを設定できます。
さらにトグル設定により、ディスプレイスメントゾーン、未埋めの価格領域、市場構造転換ゾーン、流動性スイープの起点ゾーンといった各ゾーンタイプを個別に有効化・無効化できます。また、任意で出来高フィルタリングを有効にすることで、シグナルの質を向上させることも可能です。チャートの視認性については、最大ゾーン数の制限、透明度設定、ゾーンを時間方向へ延長する機能などにより管理されます。これにより、インジケーターは柔軟性を維持しつつ、表示の過密状態を防ぎ、常にクリーンな表示を保つ設計となっています。
//+------------------------------------------------------------------+ //| Zone Types | //+------------------------------------------------------------------+ enum ZONE_TYPE { ZONE_DISPLACEMENT, // Large impulsive candles ZONE_UNFILLED, // Price inefficiency gaps ZONE_STRUCTURE, // Structure transition ZONE_LIQUIDITY // Liquidity sweep origin }; //+------------------------------------------------------------------+ //| Zone Structure | //+------------------------------------------------------------------+ struct MemoryZone { datetime startTime; datetime endTime; double high; double low; ZONE_TYPE type; color zoneColor; bool isActive; int creationBar; double strength; // 0-1 strength rating void Reset() { startTime = 0; endTime = 0; high = 0.0; low = 0.0; type = ZONE_DISPLACEMENT; zoneColor = clrNONE; isActive = false; creationBar = 0; strength = 0.5; } bool IsPriceInZone(double price) { return price >= low && price <= high; } double GetZoneMiddle() { return (high + low) / 2.0; } bool IsZoneMitigated(double currentHigh, double currentLow) { // Zone is considered mitigated if price has traded through entire zone return (currentLow <= low && currentHigh >= high); } }; //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ MemoryZone zones[]; int zoneCount = 0; double atrBuffer[]; datetime lastProcessedBar = 0; string indicatorName; double currentAtrValue = 0.0; // Colors for different zone types color displacementColor = C'0,100,255'; // Blue color unfilledColor = C'255,128,0'; // Orange color structureColor = C'128,0,255'; // Purple color liquidityColor = C'255,50,50'; // Red
このセクションでは、Market Memory Zonesインジケーターで使用される各ゾーン分類を定義します。ZONE_TYPE列挙型は、各ゾーンをその生成起点に基づいて明確かつ構造的に区分するための仕組みです。具体的には、インパルス的なローソク足によって形成されるディスプレイスメントゾーン、未埋めの価格非効率領域、市場構造転換ゾーン、流動性スイープの起点などが含まれます。列挙型を使用することでコードの可読性が向上し、各ゾーンが市場での役割に基づいて一貫した方法で識別、処理、可視化されるようになります。
MemoryZone構造体は、検出された各ゾーンの中心となるデータコンテナとして機能します。ここには、時間的な境界、価格レンジ、ゾーンタイプ、表示色、生成されたバー番号、さらに必要に応じてゾーンの強度スコアなどが格納されます。このスコアはゾーンのランク付けやフィルタリングに利用することが可能です。構造体内に含まれるユーティリティ関数により、ゾーン管理は簡素化されます。現在価格がゾーン内部に存在するかどうかの判定、ゾーンの中央値の計算、また価格がゾーンの全レンジを通過したかどうか(ミティゲーション完了)の確認などが可能です。また、Resetメソッドにより、ゾーンデータは安全に初期化または再利用することができ、過去の値が残存することを防ぎます。
グローバル変数は、インジケーター全体の実行状態を管理します。可変長のゾーン配列は現在有効なゾーンおよび履歴ゾーンを保持し、カウンタやバッファはゾーン数、ATR値、直近処理済みバーなどを追跡することで重複計算を防ぎます。さらに、インジケーター名や現在のATR値を保持する変数はゾーン検証ロジックに使用されます。最後に定義されるカラー設定により、各ゾーンタイプはチャート上で明確に識別できるようになり、トレーダーは一目でゾーンの性質を判別できるようになります。
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { indicatorName = "MMZ_" + IntegerToString(GetTickCount()); // Set indicator properties IndicatorSetString(INDICATOR_SHORTNAME, "Market Memory Zones"); IndicatorSetInteger(INDICATOR_DIGITS, _Digits); // Initialize ATR buffer ArrayResize(atrBuffer, LookbackBars); ArrayInitialize(atrBuffer, 0.0); // Initialize zones array ArrayResize(zones, MaxActiveZones * 2); // Double for safety for(int i = 0; i < ArraySize(zones); i++) zones[i].Reset(); // Calculate initial ATR currentAtrValue = iATR(_Symbol, AnalysisTF, 14); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Clean up all zone objects ObjectsDeleteAll(0, "MMZ_"); ChartRedraw(); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ 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[]) { // Check if we have enough bars if(rates_total < LookbackBars) return 0; // Process only on new bar if(prev_calculated > 0 && prev_calculated == rates_total) return rates_total; // Set array as series ArraySetAsSeries(time, true); ArraySetAsSeries(open, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(close, true); ArraySetAsSeries(tick_volume, true); // Update ATR value currentAtrValue = iATR(_Symbol, AnalysisTF, 14); // Detect zones starting from the most recent data DetectMemoryZones(rates_total, time, open, high, low, close, tick_volume); // Update and draw zones UpdateZones(rates_total, time, high[0], low[0]); return rates_total; }
このコードセクションでは、Market Memory Zonesインジケーターのライフサイクル管理を扱います。処理はOnInit()関数から開始されます。初期化時には、オブジェクトの競合を防ぐために一意のインジケーター名が生成されます。また、表示名や価格精度などの基本的なインジケーター属性が設定されます。その後、ATRバッファおよびゾーン配列のメモリが確保および初期化され、計算開始前にクリーンな状態が保証されます。各ゾーン構造体はデフォルト状態へリセットされ、さらに選択された分析時間足に基づいて初期ATR値が計算されます。このATR値は後続のディスプレイスメント判定およびゾーン強度評価に使用されます。
OnDeinit()関数とOnCalculate()関数は、それぞれクリーンアップ処理とリアルタイム更新処理を担当します。インジケーターがチャートから削除される際には、当該インジケーターによって生成されたすべてのグラフィカルゾーンオブジェクトが削除され、チャートがクリーンな状態に保たれます。一方、OnCalculate()では各計算サイクルごとに処理が実行されます。まず十分な履歴データが存在するかを確認し、新規バーが形成されたタイミングでのみロジックを実行することでパフォーマンスを最適化します。価格配列は最新データへのアクセスを容易にするため時系列モードに設定され、ATR値は定期的に更新されます。その後、コアとなる検出処理および更新ルーチンが実行され、新たなマーケットメモリーゾーンの特定および既存ゾーンの管理がおこなわれます。これにより、チャートは常に最新の市場挙動と同期した状態に保たれます。
//+------------------------------------------------------------------+ //| Detect All Memory Zones | //+------------------------------------------------------------------+ void DetectMemoryZones(int rates_total, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[]) { // Clear old zones for(int i = 0; i < zoneCount; i++) zones[i].Reset(); zoneCount = 0; // Calculate average volume for volume filter double avgVolume = CalculateAverageVolume(tick_volume, rates_total, 20); // Start from older bars to newer (but leave room for analysis) int startBar = MathMin(LookbackBars, rates_total - 10); for(int i = startBar; i >= 3; i--) // Start from 3 to have previous bars for context { // 1. Check for Displacement Zones if(ShowDisplacementZones && zoneCount < MaxActiveZones) { if(IsDisplacementCandle(i, high, low, open, close, tick_volume, avgVolume)) { AddDisplacementZone(i, time, high, low, open, close); } } // 2. Check for Unfilled Price Areas if(ShowUnfilledAreas && zoneCount < MaxActiveZones) { if(IsUnfilledArea(i, high, low, open, close)) { AddUnfilledZone(i, time, high, low, open, close); } } // 3. Check for Structure Transition Zones if(ShowStructureZones && zoneCount < MaxActiveZones && i > 5) { if(IsStructureTransition(i, high, low)) { AddStructureZone(i, time, high, low); } } // 4. Check for Liquidity Sweep Origins if(ShowLiquidityZones && zoneCount < MaxActiveZones && i > 1) { if(IsLiquiditySweep(i, high, low, close, open)) { AddLiquidityZone(i, time, high, low, close); } } // Break if we reached max zones if(zoneCount >= MaxActiveZones) break; } } //+------------------------------------------------------------------+ //| Check for Displacement Candle | //+------------------------------------------------------------------+ bool IsDisplacementCandle(int bar, const double &high[], const double &low[], const double &open[], const double &close[], const long &tick_volume[], double avgVolume) { // 1. Check minimum candle size (ATR-based) double candleSize = high[bar] - low[bar]; double minSize = currentAtrValue * MinCandleSizeATR; if(candleSize < minSize) return false; // 2. Check for minimal overlap with prior candle double overlapWithPrev = CalculateOverlap(high[bar], low[bar], high[bar+1], low[bar+1]); if(overlapWithPrev > 0.3) return false; // More than 30% overlap // 3. Check for minimal overlap with next candle if(bar > 0) { double overlapWithNext = CalculateOverlap(high[bar], low[bar], high[bar-1], low[bar-1]); if(overlapWithNext > 0.3) return false; } // 4. Volume spike confirmation (optional) if(FilterByVolume) { double volumeRatio = (double)tick_volume[bar] / avgVolume; if(volumeRatio < 1.5) return false; // Less than 150% of average } // 5. Check if it's an impulsive candle (strong directional move) bool isBullishImpulse = (close[bar] > open[bar]) && ((close[bar] - open[bar]) > candleSize * 0.6); bool isBearishImpulse = (close[bar] < open[bar]) && ((open[bar] - close[bar]) > candleSize * 0.6); return (isBullishImpulse || isBearishImpulse); } //+------------------------------------------------------------------+ //| Check for Unfilled Price Area | //+------------------------------------------------------------------+ bool IsUnfilledArea(int bar, const double &high[], const double &low[], const double &open[], const double &close[]) { // Look for Fair Value Gap pattern (3-candle formation) if(bar < 2) return false; // Pattern: Middle candle has range not overlapped by candles before and after // Bullish FVG if(low[bar-1] > high[bar] && low[bar-2] > high[bar]) { return true; } // Bearish FVG if(high[bar-1] < low[bar] && high[bar-2] < low[bar]) { return true; } // Alternative: Large candle followed by small overlapping candles double candle1Size = high[bar] - low[bar]; double candle2Size = high[bar-1] - low[bar-1]; if(candle1Size > currentAtrValue && candle2Size < currentAtrValue * 0.5) { // Check if candle2 didn't fill much of candle1's range double fillRatio = CalculateOverlap(high[bar], low[bar], high[bar-1], low[bar-1]); if(fillRatio < 0.3) return true; } return false; } //+------------------------------------------------------------------+ //| Check for Structure Transition | //+------------------------------------------------------------------+ bool IsStructureTransition(int bar, const double &high[], const double &low[]) { // Need at least 5 bars to determine structure if(bar < 5) return false; // Detect swing points bool isSwingHigh = IsSwingHigh(bar, high, 3); bool isSwingLow = IsSwingLow(bar, low, 3); if(!isSwingHigh && !isSwingLow) return false; // Check for structure break if(isSwingHigh) { // Check if breaking previous structure (Higher High to Lower High) double prevSwingHigh = FindPrevSwingHigh(bar, high, 3); if(prevSwingHigh > 0 && high[bar] < prevSwingHigh) { return true; // Lower High formed } } if(isSwingLow) { // Check if breaking previous structure (Lower Low to Higher Low) double prevSwingLow = FindPrevSwingLow(bar, low, 3); if(prevSwingLow > 0 && low[bar] > prevSwingLow) { return true; // Higher Low formed } } return false; } //+------------------------------------------------------------------+ //| Check for Liquidity Sweep | //+------------------------------------------------------------------+ bool IsLiquiditySweep(int bar, const double &high[], const double &low[], const double &close[], const double &open[]) { if(bar < 2) return false; // Bullish liquidity sweep: Sweeps previous low then closes above if(low[bar] < low[bar+1] && close[bar] > low[bar+1] && close[bar] > close[bar+1]) { // Check for reversal pattern if(close[bar] > open[bar] && (close[bar] - open[bar]) > (high[bar] - low[bar]) * 0.5) { return true; } } // Bearish liquidity sweep: Sweeps previous high then closes below if(high[bar] > high[bar+1] && close[bar] < high[bar+1] && close[bar] < close[bar+1]) { // Check for reversal pattern if(close[bar] < open[bar] && (open[bar] - close[bar]) > (high[bar] - low[bar]) * 0.5) { return true; } } return false; }
このコード部分は、Market Memory Zonesを検出するためのコア処理を担当しており、過去の価格データを走査し、市場行動の種類ごとに重要な領域を分類します。DetectMemoryZones関数は、まず前回の計算サイクルで保存されたゾーンをすべてクリアし、毎回の分析をクリーンな状態から開始できるようにします。その後、平均出来高を計算します。この値は後続処理において、強い参加を伴う値動きをフィルタリングするための任意条件として使用されます。次に、定義されたルックバック範囲内で古いバーから最新バーへ向かってループ処理を行います。これにより、時系列に沿って価格変動を正しく解釈しながら、最大ゾーン数の制限によってパフォーマンスが維持されます。
ディスプレイスメントゾーンは最初に検出されます。これは市場における強い意図を伴った値動きを表します。IsDisplacementCandle関数では複数の条件が組み合わされます。具体的には、ATRに基づくローソク足サイズ、隣接ローソク足との重なりの少なさ、任意の出来高スパイク確認、そして強い方向性を示す実体比などが評価されます。これらの条件を満たすことで、通常のボラティリティやノイズを除外し、純粋なインパルス的値動きのみが抽出されます。条件をすべて満たした場合、そのローソク足の価格レンジがディスプレイスメントゾーンとして記録されます。これは価格が急速に移動し、十分に取引されなかった領域を意味します。
次に、未埋めの価格領域がIsUnfilledArea関数によって検出されます。このロジックは、フェアバリューギャップ(FVG)や急激な拡張の後に弱いリトレースメントしか発生しないような非効率性に注目します。具体的には、3本のローソク足構造において中央のローソク足のレンジが前後のローソク足によって埋められていないケースや、大きなローソク足の後に小さなローソク足が続き、十分な戻りが発生していないケースを検出します。これらのパターンは価格の不均衡を示しており、市場が後にその領域へ戻り再バランスを取る可能性が高いゾーンとして機能します。
続いて、市場構造転換ゾーンおよびリクイディティスイープの起点ゾーンが検出されます。これらは市場構造の変化および流動性を狙った動きを捉えます。IsStructureTransition関数はスイングポイント分析を用い、高値切り下げや安値切り上げなど、市場の転換を示すChange of Characterを検出します。IsLiquiditySweep関数は、直近の高値または安値を一時的にブレイクした後に強く反転するローソク足を検出します。これはストップロスの回収やスマートマネーの参入を示唆する動きです。これらの検出ロジックを組み合わせることで、インジケーターは構造、流動性、意図といった異なる市場レイヤーに基づいたゾーンを包括的にマッピングし、価格が将来的に反応しやすい領域を体系的に可視化します。
//+------------------------------------------------------------------+ //| Add Displacement Zone | //+------------------------------------------------------------------+ void AddDisplacementZone(int bar, const datetime &time[], const double &high[], const double &low[], const double &open[], const double &close[]) { if(zoneCount >= MaxActiveZones) return; zones[zoneCount].startTime = time[bar]; zones[zoneCount].endTime = ExtendZonesForward ? TimeCurrent() + PeriodSeconds(_Period) * 100 : time[bar]; zones[zoneCount].high = high[bar]; zones[zoneCount].low = low[bar]; zones[zoneCount].type = ZONE_DISPLACEMENT; zones[zoneCount].zoneColor = displacementColor; zones[zoneCount].isActive = true; zones[zoneCount].creationBar = bar; // Calculate strength based on candle size relative to ATR double candleSize = high[bar] - low[bar]; zones[zoneCount].strength = MathMin(candleSize / (currentAtrValue * 2), 1.0); // Adjust zone boundaries for better visualization double range = zones[zoneCount].high - zones[zoneCount].low; zones[zoneCount].high += range * 0.05; // Add 5% padding zones[zoneCount].low -= range * 0.05; zoneCount++; } //+------------------------------------------------------------------+ //| Add Unfilled Zone | //+------------------------------------------------------------------+ void AddUnfilledZone(int bar, const datetime &time[], const double &high[], const double &low[], const double &open[], const double &close[]) { if(zoneCount >= MaxActiveZones) return; // For FVG pattern (3-candle) if(bar >= 2) { // Bullish FVG if(low[bar-1] > high[bar] && low[bar-2] > high[bar]) { zones[zoneCount].startTime = time[bar]; zones[zoneCount].endTime = ExtendZonesForward ? TimeCurrent() + PeriodSeconds(_Period) * 100 : time[bar-1]; zones[zoneCount].high = MathMax(high[bar-1], high[bar-2]); zones[zoneCount].low = low[bar]; } // Bearish FVG else if(high[bar-1] < low[bar] && high[bar-2] < low[bar]) { zones[zoneCount].startTime = time[bar]; zones[zoneCount].endTime = ExtendZonesForward ? TimeCurrent() + PeriodSeconds(_Period) * 100 : time[bar-1]; zones[zoneCount].high = high[bar]; zones[zoneCount].low = MathMin(low[bar-1], low[bar-2]); } zones[zoneCount].type = ZONE_UNFILLED; zones[zoneCount].zoneColor = unfilledColor; zones[zoneCount].isActive = true; zones[zoneCount].creationBar = bar; zones[zoneCount].strength = 0.7; zoneCount++; } } //+------------------------------------------------------------------+ //| Add Structure Zone | //+------------------------------------------------------------------+ void AddStructureZone(int bar, const datetime &time[], const double &high[], const double &low[]) { if(zoneCount >= MaxActiveZones) return; zones[zoneCount].startTime = time[bar]; zones[zoneCount].endTime = ExtendZonesForward ? TimeCurrent() + PeriodSeconds(_Period) * 100 : time[bar]; if(IsSwingHigh(bar, high, 3)) { zones[zoneCount].high = high[bar]; zones[zoneCount].low = high[bar] - (high[bar] - low[bar]) * 0.5; // 50% of candle range } else if(IsSwingLow(bar, low, 3)) { zones[zoneCount].low = low[bar]; zones[zoneCount].high = low[bar] + (high[bar] - low[bar]) * 0.5; // 50% of candle range } zones[zoneCount].type = ZONE_STRUCTURE; zones[zoneCount].zoneColor = structureColor; zones[zoneCount].isActive = true; zones[zoneCount].creationBar = bar; zones[zoneCount].strength = 0.8; zoneCount++; } //+------------------------------------------------------------------+ //| Add Liquidity Zone | //+------------------------------------------------------------------+ void AddLiquidityZone(int bar, const datetime &time[], const double &high[], const double &low[], const double &close[]) { if(zoneCount >= MaxActiveZones) return; zones[zoneCount].startTime = time[bar]; zones[zoneCount].endTime = ExtendZonesForward ? TimeCurrent() + PeriodSeconds(_Period) * 100 : time[bar]; // Determine sweep direction if(low[bar] < low[bar+1]) // Bullish sweep { zones[zoneCount].high = high[bar]; zones[zoneCount].low = low[bar+1]; // The level that was swept } else if(high[bar] > high[bar+1]) // Bearish sweep { zones[zoneCount].high = high[bar+1]; // The level that was swept zones[zoneCount].low = low[bar]; } zones[zoneCount].type = ZONE_LIQUIDITY; zones[zoneCount].zoneColor = liquidityColor; zones[zoneCount].isActive = true; zones[zoneCount].creationBar = bar; zones[zoneCount].strength = 0.9; // Liquidity sweeps are high probability zoneCount++; } //+------------------------------------------------------------------+ //| Update and Draw Zones | //+------------------------------------------------------------------+ void UpdateZones(int rates_total, const datetime &time[], double currentHigh, double currentLow) { // First, remove all old objects ObjectsDeleteAll(0, "MMZ_"); // Draw active zones for(int i = 0; i < zoneCount; i++) { if(!zones[i].isActive) continue; // Check if zone is still valid (not fully mitigated) if(zones[i].IsZoneMitigated(currentHigh, currentLow)) { zones[i].isActive = false; continue; } // Draw zone as rectangle DrawZone(zones[i], i); } // Draw legend DrawLegend(); }
ここでは、検出された各マーケットメモリーゾーンを作成し管理することに焦点を当てます。各Add*Zone関数は、特定のゾーンタイプを初期化し、それを共通のzones配列に格納する役割を持ちます。また、チャートの過度なオブジェクト表示を防ぐために、最大アクティブゾーン数の制限を考慮します。開始時刻と終了時刻、価格範囲、ゾーンタイプ、色、アクティブ状態、作成バーといった共通プロパティは一貫して設定され、すべてのゾーンが同じ内部構造を持つことで、後続の処理を統一的におこなえるようになっています。
ディスプレイスメントゾーンと未埋めゾーンは、それぞれ独自の価格挙動に基づいて構築されます。ディスプレイスメントゾーンは、強いインパルスを伴うローソク足の全レンジを基に定義され、そのサイズが現在のATRと比較してどれだけ大きいかに応じてストレングススコアが算出されます。また、視覚的な分かりやすさを向上させるためにわずかなパディングが追加されます。一方、未埋めゾーンはFVGや急激な拡張の後に十分な戻りが見られない構造から導出され、複数本のローソク足間に残された不均衡領域を境界として定義されます。これらのゾーンには中程度のストレングスが割り当てられ、即時反応というよりも、価格が後にリバランスのために再訪しやすい領域であることを反映しています。
ストラクチャーゾーンとと流動性ゾーンは、市場の意図と確率に重点を置いています。ストラクチャーゾーンはスイングハイまたはスイングロー付近で形成され、市場の挙動が変化したポイント(CoC)を基に構築されます。特定のローソク足レンジの一部を利用して、正確な反応領域を定義します。流動性ゾーンはストップ狩りの起点を示し、スイープされた高値または安値周辺のレンジを捉えることで作成されます。これらは過去の反応性が強いため、より高いストレングススコアが与えられます。最後にUpdateZones関数はチャートの更新処理を担当し、古くなったオブジェクトを削除し、すでにミティゲートされたゾーンを非アクティブ化し、有効なゾーンのみを再描画します。これにより、チャート上の表示は常に最新の価格挙動に同期され、不要な情報が排除されたクリーンで一貫した可視化が維持されます。
//+------------------------------------------------------------------+ //| Draw Zone as Rectangle | //+------------------------------------------------------------------+ void DrawZone(MemoryZone &zone, int index) { string objName = StringFormat("MMZ_%s_%d_%d", EnumToString(zone.type), (int)zone.startTime, index); // Create rectangle if(ObjectCreate(0, objName, OBJ_RECTANGLE, 0, zone.startTime, zone.high, zone.endTime, zone.low)) { // Set properties ObjectSetInteger(0, objName, OBJPROP_COLOR, zone.zoneColor); ObjectSetInteger(0, objName, OBJPROP_BACK, true); ObjectSetInteger(0, objName, OBJPROP_FILL, true); ObjectSetInteger(0, objName, OBJPROP_WIDTH, 1); // Set opacity based on strength and input int alpha = (int)(zone.strength * ZoneOpacity); alpha = MathMax(alpha, 10); alpha = MathMin(alpha, 80); color zoneColorWithAlpha = ColorToARGB(zone.zoneColor, alpha); ObjectSetInteger(0, objName, OBJPROP_COLOR, zoneColorWithAlpha); // Add tooltip string tooltip = StringFormat("MMZ Type: %s\nStrength: %.1f\nRange: %.5f - %.5f", GetZoneTypeName(zone.type), zone.strength, zone.low, zone.high); ObjectSetString(0, objName, OBJPROP_TOOLTIP, tooltip); } } //+------------------------------------------------------------------+ //| Draw Legend | //+------------------------------------------------------------------+ void DrawLegend() { int yPos = 20; int xPos = 20; if(ShowDisplacementZones) { CreateLabel("Legend_Displacement", "• Displacement Zones", xPos, yPos, displacementColor); yPos += 20; } if(ShowUnfilledAreas) { CreateLabel("Legend_Unfilled", "• Unfilled Areas", xPos, yPos, unfilledColor); yPos += 20; } if(ShowStructureZones) { CreateLabel("Legend_Structure", "• Structure Transitions", xPos, yPos, structureColor); yPos += 20; } if(ShowLiquidityZones) { CreateLabel("Legend_Liquidity", "• Liquidity Sweeps", xPos, yPos, liquidityColor); yPos += 20; } } void CreateLabel(string name, string text, int x, int y, color clr) { ObjectCreate(0, "MMZ_" + name, OBJ_LABEL, 0, 0, 0); ObjectSetString(0, "MMZ_" + name, OBJPROP_TEXT, text); ObjectSetInteger(0, "MMZ_" + name, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, "MMZ_" + name, OBJPROP_YDISTANCE, y); ObjectSetInteger(0, "MMZ_" + name, OBJPROP_COLOR, clr); ObjectSetInteger(0, "MMZ_" + name, OBJPROP_FONTSIZE, 10); ObjectSetString(0, "MMZ_" + name, OBJPROP_FONT, "Arial"); ObjectSetInteger(0, "MMZ_" + name, OBJPROP_HIDDEN, true); }
このセクションは、Market Memory Zonesをチャート上に視覚的に分かりやすく描画する役割を持ちます。DrawZone関数は、ゾーンタイプ、開始時刻、インデックスを組み合わせて各ゾーンに一意の名前を付け、オブジェクトの競合が発生しないようにしながら、アクティブな各ゾーンに対して矩形を作成します。この矩形はゾーンの価格範囲と時間範囲の間に描画され、チャートの背面に配置されることでプライスアクションを妨げず、塗りつぶし表示によって視認性が確保されます。ゾーンの不透明度は、算出されたストレングスとユーザー設定の透明度に応じて動的に調整され、強いゾーンほどより目立ち、弱いゾーンは控えめに表示されるようになっています。また、各ゾーンにはツールチップが付与されており、ホバー時にゾーンタイプ、ストレングス、正確な価格レンジといった情報を確認できるようになっています。
凡例関連の関数は、各ゾーンカラーの意味を明確に示すことで使いやすさを向上させています。DrawLegend関数は、ユーザーが有効化しているゾーンタイプのみに応じてラベルを表示するため、チャートが不要な情報で煩雑になることを防ぎ、必要な情報だけを保持する設計になっています。各ラベルはCreateLabelヘルパー関数によって生成され、チャート上に整然と配置されるとともに、統一されたスタイルが適用され、誤操作を防ぐためにインタラクション対象外として扱われます。これらの仕組みにより、インジケーターは分析的な精度だけでなく視覚的な直感性も兼ね備えており、トレーダーが表示されるゾーンを容易に解釈し、信頼できる形で利用できるようになっています。
Market Memory Zonesのデモ

結論
Market Memory Zonesインジケーターの開発では、価格が将来的に再び反応・回帰しやすい重要な領域をチャート上で体系的に特定する仕組みを構築しました。このインジケーターでは、ディスプレイスメントゾーン、未埋めの価格領域、市場構造転換ゾーン、流動性スイープの起点ゾーンといった複数のゾーンタイプを設計しており、それぞれがインパルス的な値動き、非効率性、構造変化、流動性の収集といった異なる市場挙動を反映しています。検出ロジックの実装、堅牢なMemoryZone構造の定義、ストレングス評価と視覚的プロパティによる管理を通じて、これらのゾーンを自動的に検出し、矩形表示、ツールチップ、凡例といった形で直感的に可視化できるインジケーターを実現しました。
結論として、このインジケーターは過去の価格行動に基づく統計的な重要性を持つ領域を可視化することで、将来的な市場反応を予測するための強力なツールを提供します。任意のサポート・レジスタンスではなく、市場の「記憶」に基づくゾーンに着目することで、トレーダーはより根拠のある判断を下すことができ、エントリータイミングの改善やリスク管理の精度向上につながります。ゾーンの視覚的表示とストレングス評価により、高確率な反発・転換・継続が起こりやすい領域を迅速に把握でき、最終的には取引戦略全体の一貫性と信頼性の向上に寄与します。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20973
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
ラリー・ウィリアムズの『市場の秘密』(第7回):Trade Day of the Week概念の実証研究
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
MQL5入門(第36回):MQL5のAPIとWebRequest関数の習得(X)
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索