
プライスアクション分析ツールキットの開発(第19回):ZigZag Analyzer
はじめに
トレンドラインは、テクニカル分析および現代のプライスアクションの基盤を形成します。これらは、FX、暗号資産、コモディティ、株式、デリバティブなどの市場でトレーダーが頼りにする価格パターンの骨格として機能します。正しく描かれたトレンドラインは、市場のトレンドを効率的に明らかにし、取引の機会を見つけるのに役立ちます。
あなたがデイトレーダーであれ、短期トレーダーであれ、トレンドラインは多くの取引システムや戦略において重要な役割を果たします。本連載「プライスアクション分析ツールキット開発」では、「ZigZag Analyzer」というツールを紹介します。このツールは、ジグザグインジケーターを使用してスイングポイントを特定し、そこからトレンドラインを構築することに焦点を当てています。MQL5は取引システムの自動化に強力なプログラミング言語であり、ZigZag Analyzerのように、市場状況に適応しリアルタイムで意思決定をサポートする高度なツールの開発を可能にします。
まずはトレンドラインについて解説し、次にジグザグインジケーターを検討し、システムアルゴリズムの概要、MQL5コードの提示、結果の確認、そして最終的な洞察へと進みます。以下に目次を示します。
トレンドライン
トレンドラインとは、重要な価格ポイント、通常は「高値と安値」を結び、将来に向かってサポートまたはレジスタンスとして機能する傾斜した線のことです。これは、方向性や勢いを視覚的に示します。トレンドラインを描くには、まず市場全体の方向性を把握することから始めます。上昇トレンドは「高値更新」と「安値切り上げ」の連続で構成されます。一方、下降トレンドは「高値切り下げ」と「安値更新」が続くのが特徴です。例として、下降トレンドのチャートを考えてみましょう。この場合は、下向きのスイングハイ(戻り高値)を結ぶことでトレンドラインを引きます。価格がレンジ相場に入ると、複数のトレンドラインが形成され、これらのラインが組み合わさって取引シグナルの信頼性を高めるパターンが現れることもあります。以下は、その視覚的な例です。
図1:トレンドライン
このチャートは、サポートとレジスタンスという 2 つの重要な概念を示しています。サポートレベルとは、価格が下落してきた際に、買い手が活発になって売り圧力を吸収するため、価格の下落が一時的に止まりやすい領域のことです。トレンドラインを使用する場合、サポートは通常、重要な安値を結ぶ上向きの傾斜線として表現されます。一方、レジスタンスレベルは、価格が上昇している最中に、売り圧力が強まることで上昇が一時的に抑えられやすい領域を指します。トレンドラインでは、主要な高値を結ぶ下向きの傾斜線がレジスタンスを示します。 トレーダーは一般的に、複数の時間軸(短期・中期・長期)にわたってトレンドラインを引くことで、市場が上昇傾向にあるのか下降傾向にあるのかを判断します。トレンドラインはテクニカル分析において非常に有用なツールです。なぜなら、それによって資産価格の全体的な方向性を把握できるからです。選んだ時間枠の中で、重要な高値や安値を結ぶことで、市場が上昇・下降・横ばいのいずれの傾向にあるのかを視覚的に示すことができます。このような洞察は、価格の動きに基づいて意思決定をおこなうトレーダーや短期投資家にとって特に有用です。
ジグザグインジケーター
ジグザグインジケーターの正確な起源は明確に記録されていませんが、一部の情報源では、S&P500トレーダーでありWolfe Wavesの開発者として知られるBill Wolfeに起源を求める説があります。Wolfe Wavesはエリオット波動に似ていますが、チャートの描き方に独自の特徴があり、需要と供給が均衡価格に向かって収束する様子を5つの波で表現します(参照)。ジグザグインジケーターは、一定の閾値(通常はパーセンテージ)を超える大きな価格の反転を強調するインジケーターです。この閾値を価格が超えた場合、インジケーターは新たなポイントをプロットし、前のポイントから直線を引いてその変化を表します。これにより、小さなノイズを除去し、基調となるトレンドを見つけやすくします。時間軸を問わず有効です。
トレーダーは、たとえば4%や5%といったように、この閾値を自由に調整することができます。資産のボラティリティや自身の取引スタイルに応じて、より多くまたは少ないスイング(波)を捉えるための調整が可能です。この柔軟性により、ジグザグインジケーターが示す反転ポイントの精度を高めることができます。エリオット波動理論などの波動分析においても、ジグザグインジケーターは波の構造を明確化するための補助ツールとして活用できます。最終的には、ノイズをどの程度除去するかと、意味のある値動きをどれだけ正確に捉えるかのバランスを見つけるために、異なる設定で試行錯誤することが重要です。
図2:ジグザグインジケーター
上の図2は、ジグザグインジケーターを使用したチャートを示しています。この図は、ジグザグのスイングからどのようにトレンドラインを構築できるかを示しています。ジグザグインジケーターは、小さな価格変動を除外し、主要な転換点を強調することで、トレンド分析を簡素化します。インジケーターは重要な高値と安値をマークし、それらがトレンドラインを描くための基準点(アンカーポイント)となります。上昇トレンドでは、これらのスイングローを結ぶことでサポートラインが形成され、下降トレンドでは、スイングハイを結ぶことでレジスタンスラインが形成されます。この手法により、雑音(細かい値動き)を排除しつつ、市場全体の方向性を強調できます。また、インジケーターの感度を調整することで、これらのトレンドラインの精度をさらに高めることが可能となり、より洗練された取引判断に役立ちます。
システムアルゴリズム
1. グローバル宣言と入力パラメータ
この最初のセクションでは、コードを直接編集することなくアナライザーをカスタマイズできるように、主要な入力値とグローバル変数を設定します。ここでは、チャートの時間足や、ジグザグインジケーターの深さ、偏差、バックステップなどの特性をパラメータとして定義します。これらの設定によって、アナライザーがスイングポイントをどのように抽出するかが決まります。また、ジグザグのデータを格納する配列や、重要な転換点の時間と価格を記録する配列もここで宣言します。
この構成の優れている点は、シンプルでモジュール化された設計にあります。たとえば、時間足のパラメータを変更するだけで、短期トレンドから長期トレンドまで簡単に切り替えることができます。また、ジグザグの特性を調整することで、銘柄のボラティリティや市場環境に応じた分析が可能になります。グローバル変数と配列を使用することで、動的データの中枢を一元管理できます。この設計により、パフォーマンスの向上や保守性の向上が実現できます。新しいデータが到着すると、それは効率的に保存され、すぐに分析に活用できる状態になります。これにより、カスタマイズやデバッグも簡単になります。
総じて、これらの重要な入力や宣言を一元化することで、柔軟で明快なツールが構築され、ユーザーは自身のニーズに合わせてアナライザーを自由に調整できるようになります。一方で、基礎となる処理は堅牢かつ信頼性の高い状態が維持されます。
// Input parameters input ENUM_TIMEFRAMES InpTimeFrame = PERIOD_CURRENT; // Timeframe to analyze input int ZZ_Depth = 12; // ZigZag depth input int ZZ_Deviation = 5; // ZigZag deviation input int ZZ_Backstep = 3; // ZigZag backstep input int LookBackBars = 200; // Bars to search for pivots input int ExtendFutureBars = 100; // Bars to extend trendlines into the future // Global indicator handle for ZigZag int zzHandle; // Arrays for ZigZag data and pivot storage double zzBuffer[]; datetime pivotTimes[]; double pivotPrices[]; bool pivotIsPeak[]; // Variable to detect new bars datetime lastBarTime = 0;
- InpTimeFrame:分析の期間
- ZZ_Depth、ZZ_Deviation、ZZ_Backstep:ジグザグインジケーターの感度と動作に影響を与えるパラメータ
zzBufferのような配列は、ジグザグインジケーターの出力を格納するために使用されます。さらに、pivotTimes、pivotPrices、pivotIsPeakといった配列は、検出された各ピボット(転換点)に関する詳細情報を保持するために予約されています。また、lastBarTimeという変数は、新しいバーが始まったかどうかを判断するために使われ、必要なときだけ分析が更新されるようにします。
2. 初期化関数(OnInit)
初期化関数では、インジケーターが読み込まれたときに一度だけコードを実行します。この関数の主な目的は、ユーザーの入力に基づいてジグザグインジケーターを作成し、設定することです。iCustom関数を使ってジグザグインジケーターを呼び出し、そのハンドル(識別子)を保存しようとします。もしインジケーターの初期化に失敗して無効なハンドルが返ってきた場合は、エラーログを記録し、それ以上の処理を停止します。
この関数は、分析を開始する前にすべてが正しく設定されているかどうかを確認する門番のような役割を果たします。初期段階でエラーを検出できれば、後の工程で起こる問題を避けることができ、時間の節約にもなります。初期化処理をここに集中させることで、システム全体の管理やデバッグも容易になります。また、ユーザーが指定したすべての設定がこの段階で反映されるため、後続の分析処理において安定した土台が築かれます。
int OnInit() { zzHandle = iCustom(_Symbol, InpTimeFrame, "ZigZag", ZZ_Depth, ZZ_Deviation, ZZ_Backstep); if(zzHandle == INVALID_HANDLE) { Print("Error creating ZigZag handle"); return(INIT_FAILED); } return(INIT_SUCCEEDED); }
- インジケーターの作成:iCustomを使用して、特定のパラメータを持つジグザグインジケーターを読み込みます。
- エラー処理:返されたハンドルが有効かどうかを確認します。初期化に失敗した場合はエラー メッセージを記録して中止します。
3. 初期化解除関数(OnDeinit)
インジケーターがチャートから削除されたり、プラットフォームが終了された際には、作成されたオブジェクトの削除や割り当てたリソースの解放をおこなうことが重要です。そのための処理を担当するのがOnDeinit関数です。この関数では、チャート上に描画されたトレンドラインや水平線などのグラフィカルオブジェクトを削除し、さらにジグザグインジケーターのハンドルも解放します。これにより、チャート上に不要な要素が残らず、システムリソースが無駄なく解放されます。
リソースのクリーンアップ:チャートに作成されたオブジェクトをすべて削除し、チャートを散らかさないようにします。ハンドルの解放:IndicatorRelease関数を使って、ジグザグインジケーターのハンドルを解放し、システムリソースを適切に管理します。
void OnDeinit(const int reason) { ObjectDelete(0, "Downtrend_HighLine"); ObjectDelete(0, "Uptrend_LowLine"); ObjectDelete(0, "Major_Resistance"); ObjectDelete(0, "Major_Support"); ObjectDelete(0, "Minor_Resistance"); ObjectDelete(0, "Minor_Support"); IndicatorRelease(zzHandle); }
4. メイン実行関数(OnTick)
これはアルゴリズムの中核であり、新しいティックごとに実行される関数です。この関数ではまず、新しいバー(ローソク足)が形成されたかどうかを、現在のバーのタイムスタンプと前回記録した時間を比較することでチェックします。新しいバーが形成されていない場合は、処理負荷を抑えるために早期に関数を終了します。新しいバーが確認された場合は、古いチャートオブジェクトを削除し、CopyBufferを使って最新のジグザグデータを取得し、トレンドラインを描画する関数とサポートおよびレジスタンスレベルを描画する関数の2つの補助関数を呼び出します。
void OnTick() { datetime currentBarTime = iTime(_Symbol, InpTimeFrame, 0); if(currentBarTime == lastBarTime) return; lastBarTime = currentBarTime; // Remove previous objects ObjectDelete(0, "Downtrend_HighLine"); ObjectDelete(0, "Uptrend_LowLine"); ObjectDelete(0, "Major_Resistance"); ObjectDelete(0, "Major_Support"); ObjectDelete(0, "Minor_Resistance"); ObjectDelete(0, "Minor_Support"); if(CopyBuffer(zzHandle, 0, 0, LookBackBars, zzBuffer) <= 0) { Print("Failed to copy ZigZag data"); return; } ArraySetAsSeries(zzBuffer, true); DrawZigZagTrendlines(); DrawSupportResistance(); }
- 新しいバーの確認:iTime関数を使用してバーの変化を検出します。
- バッファの更新:zzBufferを最新のジグザグデータで更新します。
- チャートの更新:新しいグラフィカルオブジェクトを準備するために、以前のグラフィカルオブジェクトをクリアします。トレンドラインとサポート/レジスタンスラインを描画する関数を呼び出します。
5. ジグザグベースのトレンドラインの描画(DrawZigZagTrendlines)
この関数は、ジグザグデータから重要なスイングポイントを特定し、それをもとにトレンドラインを算出する役割を担います。バッファ内の値を走査し、現在のジグザグの値がバーの高値または安値と一致するかどうかに基づいて、高値・安値を抽出します。スイングポイントが集まった後、それぞれのポイント群に対して線形回帰を用いてトレンドラインを導出します。回帰計算には以下の数式が使用されます。
1. 勾配(m)
図3:回帰式
2. 切片(b)
図4:切片
- tは時間値(または独立変数)
- pは価格値(または従属変数)
- Nは回帰で使用されるデータポイントの数
スイングポイント抽出:zzBufferをループして最大 10 個の高値と安値を収集します。
最新スイングの除外:ノイズを減らすために最新のスイングを破棄します。
回帰分析:上記の式を使用して傾き(m)と切片(b)を計算します。
void DrawZigZagTrendlines() { double highPrices[10], lowPrices[10]; datetime highTimes[10], lowTimes[10]; int highCount = 0, lowCount = 0; // Extract swing points from the ZigZag buffer for(int i = 0; i < LookBackBars - 1; i++) { if(zzBuffer[i] != 0) { if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && highCount < 10) { highPrices[highCount] = zzBuffer[i]; highTimes[highCount] = iTime(_Symbol, InpTimeFrame, i); highCount++; } else if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && lowCount < 10) { lowPrices[lowCount] = zzBuffer[i]; lowTimes[lowCount] = iTime(_Symbol, InpTimeFrame, i); lowCount++; } } } // Exclude the most recent swing if possible int usedHighCount = (highCount >= 4) ? highCount - 1 : highCount; int usedLowCount = (lowCount >= 4) ? lowCount - 1 : lowCount; double mHigh = 0, bHigh = 0, mLow = 0, bLow = 0; bool validHigh = false, validLow = false; // Regression for highs if(usedHighCount >= 3) { double sumT = 0, sumP = 0, sumTP = 0, sumT2 = 0; for(int i = 0; i < usedHighCount; i++) { double t = (double)highTimes[i]; double p = highPrices[i]; sumT += t; sumP += p; sumTP += t * p; sumT2 += t * t; } int N = usedHighCount; double denominator = N * sumT2 - sumT * sumT; if(denominator != 0) { mHigh = (N * sumTP - sumT * sumP) / denominator; bHigh = (sumP - mHigh * sumT) / N; } else bHigh = sumP / N; validHigh = true; } // Regression for lows if(usedLowCount >= 3) { double sumT = 0, sumP = 0, sumTP = 0, sumT2 = 0; for(int i = 0; i < usedLowCount; i++) { double t = (double)lowTimes[i]; double p = lowPrices[i]; sumT += t; sumP += p; sumTP += t * p; sumT2 += t * t; } int N = usedLowCount; double denominator = N * sumT2 - sumT * sumT; if(denominator != 0) { mLow = (N * sumTP - sumT * sumP) / denominator; bLow = (sumP - mLow * sumT) / N; } else bLow = sumP / N; validLow = true; } // Define time limits for trendlines datetime pastTime = iTime(_Symbol, InpTimeFrame, LookBackBars - 1); datetime futureTime = lastBarTime + ExtendFutureBars * PeriodSeconds(); // Draw trendlines if both regressions are valid if(validHigh && validLow) { // When slopes have the same sign, use average slope for parallel lines if(mHigh * mLow > 0) { double mParallel = (mHigh + mLow) / 2.0; double bHighParallel = highPrices[0] - mParallel * (double)highTimes[0]; double bLowParallel = lowPrices[0] - mParallel * (double)lowTimes[0]; datetime highStartTime = pastTime; double highStartPrice = mParallel * (double)highStartTime + bHighParallel; double highEndPrice = mParallel * (double)futureTime + bHighParallel; if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice)) Print("Failed to create High Trendline"); else { ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true); } datetime lowStartTime = pastTime; double lowStartPrice = mParallel * (double)lowStartTime + bLowParallel; double lowEndPrice = mParallel * (double)futureTime + bLowParallel; if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice)) Print("Failed to create Low Trendline"); else { ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true); } } else // Draw separate trendlines if slopes differ { datetime highStartTime = pastTime; double highStartPrice = mHigh * (double)highStartTime + bHigh; double highEndPrice = mHigh * (double)futureTime + bHigh; if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice)) Print("Failed to create High Trendline"); else { ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true); } datetime lowStartTime = pastTime; double lowStartPrice = mLow * (double)lowStartTime + bLow; double lowEndPrice = mLow * (double)futureTime + bLow; if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice)) Print("Failed to create Low Trendline"); else { ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true); } } } else { // Draw only available regression if only one set of points is valid if(validHigh) { datetime highStartTime = pastTime; double highStartPrice = mHigh * (double)highStartTime + bHigh; double highEndPrice = mHigh * (double)futureTime + bHigh; if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice)) Print("Failed to create High Trendline"); else { ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true); } } if(validLow) { datetime lowStartTime = pastTime; double lowStartPrice = mLow * (double)lowStartTime + bLow; double lowEndPrice = mLow * (double)futureTime + bLow; if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice)) Print("Failed to create Low Trendline"); else { ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true); } } } }
トレンドラインの描画(両方のトレンドが有効な場合)
同じ標識の傾斜:平行線には平均傾斜を使用します。 さまざまなサイン:個別のトレンドラインを描画します。ObjectCreateとObjectSetIntegerを使用して、線を配置し、スタイルを設定します。
6. サポートレベルとレジスタンスレベルの描画(DrawSupportResistance)
このコードのサポートおよびレジスタンスに関する部分は、ジグザグインジケーターによって確定されたスイングポイントに基づいて、主要な水平価格レベルを動的に特定するよう設計されています。単に「最高値を主要なレジスタンス、最安値を主要なサポート」として設定するだけにとどまらず、アルゴリズムはさらに進んで、意味のある高値・安値をフィルタリングし、主要および副次的なレベルの両方を導き出します。このプロセスは非常に重要です。なぜなら、サポートとレジスタンスはテクニカル分析の基本概念であり、サポートは需要の増加によって価格が下げ止まるレベル、レジスタンスは供給の圧力によって価格の上昇が抑えられるレベルを意味するからです。
この実装では、すべての確定スイングポイントを調べ、最も極端な値を抽出するように厳密にソート・比較します。これにより、チャート上に描かれるレベルが一時的な値動きではなく、過去に実際に価格の反転に影響した重要なゾーンであることが保証されます。また、2番目に高い・低い値を副次的なレジスタンスおよびサポートとして識別することで、トレーダーに対して先行的なシグナルや追加のターゲットを提供し、主要レベルに達する前の価格反応をより繊細に捉えることができます。
この手法は、常に変化する市場環境において非常に有効です。新しい価格データが入るたびに、 ジグザグインジケーターが新たなスイングポイントを検出し、それに応じてサポートとレジスタンスレベルが自動で更新され、最新のマーケット構造を反映します。このリアルタイムな調整機能は、エントリーやイグジットの判断に直結するため、トレーダーにとって非常に重要です。レベルはチャート上に識別しやすい色の水平線として視覚的に描画されるため、一目で重要ポイントが分かります。これにより、損切り注文や利確ターゲットの設定にも自信が持てるようになります。最終的に、このコードは単にサポートとレジスタンスを「計算」するのではなく、市場の生データを明確で実用的なインサイトに変換する役割を果たし、テクニカル分析をおこなうすべてのトレーダーにとって欠かせないツールとなります。
void DrawSupportResistance() { double confirmedHighs[10], confirmedLows[10]; int confHighCount = 0, confLowCount = 0; for(int i = 0; i < LookBackBars - 1; i++) { if(zzBuffer[i] != 0) { if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confHighCount < 10) { confirmedHighs[confHighCount] = zzBuffer[i]; confHighCount++; } else if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confLowCount < 10) { confirmedLows[confLowCount] = zzBuffer[i]; confLowCount++; } } } int usedHighCount = (confHighCount >= 4) ? confHighCount - 1 : confHighCount; int usedLowCount = (confLowCount >= 4) ? confLowCount - 1 : confLowCount; double majorResistance = -1e9, majorSupport = 1e9; double minorResistance = -1e9, minorSupport = 1e9; double tempHigh = -1e9, tempLow = -1e9; for(int i = 0; i < usedHighCount; i++) { if(confirmedHighs[i] > majorResistance) { tempHigh = majorResistance; majorResistance = confirmedHighs[i]; } else if(confirmedHighs[i] > tempHigh) { tempHigh = confirmedHighs[i]; } } if(tempHigh > -1e9) minorResistance = tempHigh; for(int i = 0; i < usedLowCount; i++) { if(confirmedLows[i] < majorSupport) { tempLow = majorSupport; majorSupport = confirmedLows[i]; } else if(confirmedLows[i] < tempLow) { tempLow = confirmedLows[i]; } } if(tempLow < 1e9) minorSupport = tempLow; if(usedHighCount > 0) { if(!ObjectCreate(0, "Major_Resistance", OBJ_HLINE, 0, 0, majorResistance)) Print("Failed to create Major Resistance"); else ObjectSetInteger(0, "Major_Resistance", OBJPROP_COLOR, clrMagenta); if(minorResistance > -1e9 && minorResistance < majorResistance) { if(!ObjectCreate(0, "Minor_Resistance", OBJ_HLINE, 0, 0, minorResistance)) Print("Failed to create Minor Resistance"); else ObjectSetInteger(0, "Minor_Resistance", OBJPROP_COLOR, clrFuchsia); } } if(usedLowCount > 0) { if(!ObjectCreate(0, "Major_Support", OBJ_HLINE, 0, 0, majorSupport)) Print("Failed to create Major Support"); else ObjectSetInteger(0, "Major_Support", OBJPROP_COLOR, clrAqua); if(minorSupport < 1e9 && minorSupport > majorSupport) { if(!ObjectCreate(0, "Minor_Support", OBJ_HLINE, 0, 0, minorSupport)) Print("Failed to create Minor Support"); else ObjectSetInteger(0, "Minor_Support", OBJPROP_COLOR, clrBlue); } } }
データ収集では、バッファ内を繰り返し処理し、確定した高値と安値を抽出します。レベルの判定では、値を比較することで極端な値を特定します。最も極端な値が主要なレジスタンス/サポートとして設定され、次に極端な値が副次的なレベルとなります。グラフィカルな描画では、OBJ_HLINE(水平線オブジェクト)を使用して、それぞれの識別されたレベルをチャート上に表示します。各線には識別しやすいように色分けが施されます。
MQL5コード
//+------------------------------------------------------------------+ //| ZigZag Analyzer.mq5| //| Copyright 2025, MetaQuotes Ltd.| //| https://www.mql5.com/ja/users/lynnchris| //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/ja/users/lynnchris" #property version "1.0" #property strict // Input parameters input ENUM_TIMEFRAMES InpTimeFrame = PERIOD_CURRENT; // Timeframe to analyze input int ZZ_Depth = 12; // ZigZag depth input int ZZ_Deviation = 5; // ZigZag deviation input int ZZ_Backstep = 3; // ZigZag backstep input int LookBackBars = 200; // Bars to search for pivots input int ExtendFutureBars = 100; // Bars to extend trendlines into the future // Global indicator handle for ZigZag int zzHandle; // Arrays for ZigZag data and pivot storage double zzBuffer[]; datetime pivotTimes[]; double pivotPrices[]; bool pivotIsPeak[]; // Variable to detect new bars datetime lastBarTime = 0; //+------------------------------------------------------------------+ //| Initialization function | //+------------------------------------------------------------------+ int OnInit() { zzHandle = iCustom(_Symbol, InpTimeFrame, "ZigZag", ZZ_Depth, ZZ_Deviation, ZZ_Backstep); if(zzHandle == INVALID_HANDLE) { Print("Error creating ZigZag handle"); return(INIT_FAILED); } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectDelete(0, "Downtrend_HighLine"); ObjectDelete(0, "Uptrend_LowLine"); ObjectDelete(0, "Major_Resistance"); ObjectDelete(0, "Major_Support"); ObjectDelete(0, "Minor_Resistance"); ObjectDelete(0, "Minor_Support"); IndicatorRelease(zzHandle); } //+------------------------------------------------------------------+ //| Tick function | //+------------------------------------------------------------------+ void OnTick() { datetime currentBarTime = iTime(_Symbol, InpTimeFrame, 0); if(currentBarTime == lastBarTime) return; lastBarTime = currentBarTime; // Remove previous objects ObjectDelete(0, "Downtrend_HighLine"); ObjectDelete(0, "Uptrend_LowLine"); ObjectDelete(0, "Major_Resistance"); ObjectDelete(0, "Major_Support"); ObjectDelete(0, "Minor_Resistance"); ObjectDelete(0, "Minor_Support"); if(CopyBuffer(zzHandle, 0, 0, LookBackBars, zzBuffer) <= 0) { Print("Failed to copy ZigZag data"); return; } ArraySetAsSeries(zzBuffer, true); DrawZigZagTrendlines(); DrawSupportResistance(); } //+------------------------------------------------------------------+ //| Draw ZigZag-based Trendlines | //+------------------------------------------------------------------+ void DrawZigZagTrendlines() { double highPrices[10], lowPrices[10]; datetime highTimes[10], lowTimes[10]; int highCount = 0, lowCount = 0; // Extract swing points from the ZigZag buffer for(int i = 0; i < LookBackBars - 1; i++) { if(zzBuffer[i] != 0) { if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && highCount < 10) { highPrices[highCount] = zzBuffer[i]; highTimes[highCount] = iTime(_Symbol, InpTimeFrame, i); highCount++; } else if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && lowCount < 10) { lowPrices[lowCount] = zzBuffer[i]; lowTimes[lowCount] = iTime(_Symbol, InpTimeFrame, i); lowCount++; } } } // Exclude the most recent swing if possible int usedHighCount = (highCount >= 4) ? highCount - 1 : highCount; int usedLowCount = (lowCount >= 4) ? lowCount - 1 : lowCount; double mHigh = 0, bHigh = 0, mLow = 0, bLow = 0; bool validHigh = false, validLow = false; // Regression for highs if(usedHighCount >= 3) { double sumT = 0, sumP = 0, sumTP = 0, sumT2 = 0; for(int i = 0; i < usedHighCount; i++) { double t = (double)highTimes[i]; double p = highPrices[i]; sumT += t; sumP += p; sumTP += t * p; sumT2 += t * t; } int N = usedHighCount; double denominator = N * sumT2 - sumT * sumT; if(denominator != 0) { mHigh = (N * sumTP - sumT * sumP) / denominator; bHigh = (sumP - mHigh * sumT) / N; } else bHigh = sumP / N; validHigh = true; } // Regression for lows if(usedLowCount >= 3) { double sumT = 0, sumP = 0, sumTP = 0, sumT2 = 0; for(int i = 0; i < usedLowCount; i++) { double t = (double)lowTimes[i]; double p = lowPrices[i]; sumT += t; sumP += p; sumTP += t * p; sumT2 += t * t; } int N = usedLowCount; double denominator = N * sumT2 - sumT * sumT; if(denominator != 0) { mLow = (N * sumTP - sumT * sumP) / denominator; bLow = (sumP - mLow * sumT) / N; } else bLow = sumP / N; validLow = true; } // Define time limits for trendlines datetime pastTime = iTime(_Symbol, InpTimeFrame, LookBackBars - 1); datetime futureTime = lastBarTime + ExtendFutureBars * PeriodSeconds(); // Draw trendlines if both regressions are valid if(validHigh && validLow) { // When slopes have the same sign, use average slope if(mHigh * mLow > 0) { double mParallel = (mHigh + mLow) / 2.0; double bHighParallel = highPrices[0] - mParallel * (double)highTimes[0]; double bLowParallel = lowPrices[0] - mParallel * (double)lowTimes[0]; datetime highStartTime = pastTime; double highStartPrice = mParallel * (double)highStartTime + bHighParallel; double highEndPrice = mParallel * (double)futureTime + bHighParallel; if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice)) Print("Failed to create High Trendline"); else { ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true); } datetime lowStartTime = pastTime; double lowStartPrice = mParallel * (double)lowStartTime + bLowParallel; double lowEndPrice = mParallel * (double)futureTime + bLowParallel; if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice)) Print("Failed to create Low Trendline"); else { ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true); } } else { datetime highStartTime = pastTime; double highStartPrice = mHigh * (double)highStartTime + bHigh; double highEndPrice = mHigh * (double)futureTime + bHigh; if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice)) Print("Failed to create High Trendline"); else { ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true); } datetime lowStartTime = pastTime; double lowStartPrice = mLow * (double)lowStartTime + bLow; double lowEndPrice = mLow * (double)futureTime + bLow; if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice)) Print("Failed to create Low Trendline"); else { ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true); } } } else { if(validHigh) { datetime highStartTime = pastTime; double highStartPrice = mHigh * (double)highStartTime + bHigh; double highEndPrice = mHigh * (double)futureTime + bHigh; if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice)) Print("Failed to create High Trendline"); else { ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true); } } if(validLow) { datetime lowStartTime = pastTime; double lowStartPrice = mLow * (double)lowStartTime + bLow; double lowEndPrice = mLow * (double)futureTime + bLow; if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice)) Print("Failed to create Low Trendline"); else { ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true); } } } } //+------------------------------------------------------------------+ //| Draw Support and Resistance Levels | //+------------------------------------------------------------------+ void DrawSupportResistance() { double confirmedHighs[10], confirmedLows[10]; int confHighCount = 0, confLowCount = 0; for(int i = 0; i < LookBackBars - 1; i++) { if(zzBuffer[i] != 0) { if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confHighCount < 10) { confirmedHighs[confHighCount] = zzBuffer[i]; confHighCount++; } else if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confLowCount < 10) { confirmedLows[confLowCount] = zzBuffer[i]; confLowCount++; } } } int usedHighCount = (confHighCount >= 4) ? confHighCount - 1 : confHighCount; int usedLowCount = (confLowCount >= 4) ? confLowCount - 1 : confLowCount; double majorResistance = -1e9, majorSupport = 1e9; double minorResistance = -1e9, minorSupport = 1e9; double tempHigh = -1e9, tempLow = -1e9; for(int i = 0; i < usedHighCount; i++) { if(confirmedHighs[i] > majorResistance) { tempHigh = majorResistance; majorResistance = confirmedHighs[i]; } else if(confirmedHighs[i] > tempHigh) { tempHigh = confirmedHighs[i]; } } if(tempHigh > -1e9) minorResistance = tempHigh; for(int i = 0; i < usedLowCount; i++) { if(confirmedLows[i] < majorSupport) { tempLow = majorSupport; majorSupport = confirmedLows[i]; } else if(confirmedLows[i] < tempLow) { tempLow = confirmedLows[i]; } } if(tempLow < 1e9) minorSupport = tempLow; if(usedHighCount > 0) { if(!ObjectCreate(0, "Major_Resistance", OBJ_HLINE, 0, 0, majorResistance)) Print("Failed to create Major Resistance"); else ObjectSetInteger(0, "Major_Resistance", OBJPROP_COLOR, clrMagenta); if(minorResistance > -1e9 && minorResistance < majorResistance) { if(!ObjectCreate(0, "Minor_Resistance", OBJ_HLINE, 0, 0, minorResistance)) Print("Failed to create Minor Resistance"); else ObjectSetInteger(0, "Minor_Resistance", OBJPROP_COLOR, clrFuchsia); } } if(usedLowCount > 0) { if(!ObjectCreate(0, "Major_Support", OBJ_HLINE, 0, 0, majorSupport)) Print("Failed to create Major Support"); else ObjectSetInteger(0, "Major_Support", OBJPROP_COLOR, clrAqua); if(minorSupport < 1e9 && minorSupport > majorSupport) { if(!ObjectCreate(0, "Minor_Support", OBJ_HLINE, 0, 0, minorSupport)) Print("Failed to create Minor Support"); else ObjectSetInteger(0, "Minor_Support", OBJPROP_COLOR, clrBlue); } } } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+
結果
結果を分析する前に、まずはジグザグインジケーターをチャートに追加しましょう。プロセスをより明確に理解するために、以下のGIF図解を参照してください。このビジュアル資料は、各ステップの流れを視覚的に示してくれます。図解を見ながら進めることで、インジケーターがどのように統合・設定されるかが分かりやすくなるはずです。手順を把握した後で、結果の分析に進みましょう。
図5:ジグザグインジケーターの初期化
この一連の図は、結果を示すものであり、それぞれの図には詳細な説明が添えられています。まず最初の図は、ZigZag AnalyzerツールをStep Indexに適用した後の結果を示しています。ご覧のとおり、EAをチャートに適用すると、トレンドラインが正確に描画されていることが確認できます。ローライン(下の線)とトップライン(上の線)の両方が下向きに傾いており、下降トレンドを示しています。さらに、図には主要および副次的なサポート/レジスタンスレベルも明確に表示されており、視覚的にも分析しやすくなっています。
図6:Step Indexの結果
このスクリーンショットは、分析に関するさらなる洞察を提供します。画像では、トレンドラインの構築に使用された重要なスイングポイント(切り下げ高値、安値の更新)が確認できます。また、主要および副次的なサポート/レジスタンスレベルも明確にマークされています。特に注目すべき点は、トレンドラインと主要レベルが交差する箇所で、強い反転ポイントが示されています。たとえば、最も高い切り下げ高値が主要レジスタンスにぶつかる地点や、切り下げ安値の中で最も深い安値が主要サポートに到達する地点では、価格の反転が発生しています。
図7:Step Index
この USDCHF のチャートでは、トレンドラインが描画されている様子が確認できます。それらのラインは市場の中に存在していますが、過度に主張しすぎることはありません。市場は、サポートおよびレジスタンスレベルをしっかりと意識して動いており、このことが本システムの有効性を裏付けています。また、トレンドラインがスイングポイントに接している箇所をハイライトしてあります。
図8:USDCHF
最後に、Volatility 25 Indexにおける本ツールのパフォーマンスを確認します。トレンドラインはスイングポイントを明確に結び、市場の動きを精密に示しています。この視認性の高さは、さまざまな市場環境においても本システムが信頼できることを裏付けています。結果として、私たちのアプローチの有効性が改めて確認されました。
図9:Volatility 25 Index
結論
Zig Zag Analyzerツールは、MQL5を使ってプライスアクション分析を自動化する強力な手段です。テストの結果、トレンド相場においてトレンドラインが効果的に描画されることが確認されました。このアプローチを発展させれば、フラッグやトライアングルなどのパターン検出ツールの開発にもつながると考えています。Zig Zag Analyzerは、FX初心者がトレンドを観察したり、サポート/レジスタンスの候補を見つけたりする出発点として非常に有用です。また、トレンドラインがプライスアクション分析の中心的要素であることから、経験豊富なトレーダーにとっても価値のあるツールです。学習用として優れているだけでなく、他の確認手法と組み合わせたり、さまざまな取引戦略に合わせてカスタマイズしたりすることも可能です。
日付 | ツール名 | 詳細 | バージョン | アップデート | 備考 |
---|---|---|---|---|---|
01/10/24 | ChartProjector | 前日のプライスアクションをゴースト効果でオーバーレイするスクリプト | 1.0 | 初回リリース | ツール番号1 |
18/11/24 | Analytical Comment | 前日の情報を表形式で提供し、市場の将来の方向性を予測する | 1.0 | 初回リリース | ツール番号2 |
27/11/24 | Analytics Master | 2時間ごとに市場指標を定期的に更新 | 1.01 | v.2 | ツール番号3 |
02/12/24 | Analytics Forecaster | Telegram統合により、2時間ごとに市場指標を定期的に更新 | 1.1 | v.3 | ツール番号4 |
09/12/24 | Volatility Navigator | ボリンジャーバンド、RSI、ATR指標を使用して市場の状況を分析するEA | 1.0 | 初回リリース | ツール番号5 |
19/12/24 | Mean Reversion Signal Reaper | 平均回帰戦略を用いて市場を分析し、シグナルを提供する | 1.0 | 初回リリース | ツール番号6 |
9/01/25 | Signal Pulse | 多時間枠分析ツール | 1.0 | 初回リリース | ツール番号7 |
17/01/25 | Metrics Board | 分析用のボタン付きパネル | 1.0 | 初回リリース | ツール番号8 |
21/01/25 | External Flow | 外部ライブラリによる分析 | 1.0 | 初回リリース | ツール番号9 |
27/01/25 | VWAP | 出来高加重平均価格 | 1.3 | 初回リリース | ツール番号10 |
02/02/25 | Heikin Ashi | トレンドの平滑化と反転シグナルの識別 | 1.0 | 初回リリース | ツール番号11 |
04/02/25 | FibVWAP | Python分析によるシグナル生成 | 1.0 | 初回リリース | ツール番号12 |
14/02/25 | RSI DIVERGENCE | プライスアクションとRSIのダイバージェンス | 1.0 | 初回リリース | ツール番号13 |
17/02/25 | Parabolic Stop and Reverse (PSAR) | PSAR戦略の自動化 | 1.0 | 初回リリース | ツール番号14 |
20/02/25 | Quarters Drawerスクリプト | チャートにクォーターレベルを描く | 1.0 | 初回リリース | ツール番号15 |
27/02/25 | Intrusion Detector | 価格がクォーターレベルに達したときに検出して警告する | 1.0 | 初回リリース | ツール番号16 |
27/02/25 | TrendLoom Tool | 多時間枠分析パネル | 1.0 | 初回リリース | ツール番号17 |
11/03/25 | Quarters Board | クォーターレベルを有効または無効にするボタン付きのパネル | 1.0 | 初回リリース | ツール番号18 |
26/03/25 | ZigZag Analyzer | ジグザグインジケーターを使ったトレンドラインの描画 | 1.0 | 初回リリース | ツール番号19 |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/17625





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
このインジケーターはとても面白いと思います。正しいトレンド、サポート、レジスタンスを見つけるのに最も時間を費やすので、テクニカル分析に大いに役立ちそうです。コードをダウンロードし、正しくコンパイルされましたが、チャートに追加しても情報が反映されません。何か間違っているのでしょうか?もう一度ビデオを添付します。よろしくお願いします。
>クリスティーナ、こんにちは。このインジケーターはとても面白いと思います。正しいトレンド、サポート、レジスタンスを見つけるのに最も時間を費やすので、テクニカル分析に大いに役立ちそうです。コードをダウンロードし、正しくコンパイルされましたが、チャートに追加しても情報が反映されません。何か間違っているのでしょうか?もう一度ビデオを添付します。よろしくお願いします。
ディエゴ・ エレラ
>クリスティーナ、こんにちは。このインジケーターはとても面白いと思います。正しいトレンド、サポート、レジスタンスを見つけるのに最も時間を費やすので、テクニカル分析に大いに役立ちそうです。コードをダウンロードし、正しくコンパイルされましたが、チャートに追加しても情報が反映されません。何か間違っているのでしょうか?もう一度ビデオを添付します。よろしくお願いします。
>クリスティーナ、こんにちは。このインジケーターはとても面白いと思います。正しいトレンド、サポート、レジスタンスを見つけるのに最も時間を費やすので、テクニカル分析に大いに役立ちそうです。コードをダウンロードし、正しくコンパイルされましたが、チャートに追加しても情報が反映されません。何か間違っているのでしょうか?もう一度ビデオを添付します。よろしくお願いします。
これを試す