ラリー・ウィリアムズの『市場の秘密』(第11回):カスタムインジケータによるスマッシュデー反転の検出
はじめに
チャート上に現れるすべてのスマッシュデー反転を取引対象にしようとすると、シグナルのノイズが増え、判断に迷いが生じ、一貫性のない結果につながりやすくなります。一見すると、それは理にかなっているように思えます。急激なブレイクの直後にその動きが失敗すれば、反対方向への大きな値動きにつながることが少なくありません。しかし、市場環境を考慮せず、すべてのシグナルをそのまま取引すると、多くの場合はノイズが増え、混乱し、安定した成果を得ることは難しくなります。
ラリー・ウィリアムズは著書「Long-term Secrets to Short-term Trading」の中で、このパターンは、ブレイクアウトだと考えた市場参加者が飛び乗った直後に急反転し、その結果として逃げ場を失う、市場心理の極端な局面を表していると述べています。この「トラップ」は非常に強力ですが、無条件に売買すべきものではありません。ウィリアムズは繰り返し、このパターンは市場全体の流れやトレンド、その他の裏付け条件と一致した場合に、最も高い効果を発揮すると強調しています。
ここで裁量トレーダーにとって実践的な課題が生まれます。こうした出現頻度は低いものの重要なパターンを、相場環境を無視した機械的な自動売買システムにしてしまうことなく、どのように素早く、そして客観的に見つければよいのでしょうか。
本記事では、その解決策として、スマッシュデー反転を可視化するカスタムMQL5インジケータを実装します。このインジケータは自動で注文を出すのではなく、有効なパターンが形成され、さらに価格がスマッシュバーの水準を突破して反転が確認されたタイミングをチャート上で明確に示します。強気反転では買いシグナルの矢印が、弱気反転では売りシグナルの矢印が表示され、その後の最終的な売買判断はトレーダー自身に委ねられます。
ラリー・ウィリアムズの客観的なパターンルールを、分かりやすくタイムリーなチャートシグナルへ変換することで、このインジケータはパターン検出時の曖昧さを排除しつつ、トレンド方向、ニュース、市場構造など、各自の裁量フィルターを組み合わせる余地を残します。つまり、価格変動をそのまま機械的に売買するブラックボックスではなく、生のプライスアクションと、文脈を踏まえた熟慮ある裁量トレードをつなぐためのツールとして機能します。
スマッシュデー反転をひと目で見分ける
スマッシュデー反転は、一方向への強く感情的な値動きから始まります。この動きは直近レンジを突破してクローズするため、一見すると勢いがそのまま続くように見えます。しかし、その期待に反して市場はすぐに失速し、翌バーで反対方向へ急反転します。この突然の失敗によって、遅れてブレイクアウトに乗ったトレーダーがポジションを閉じ込められ、その巻き戻しが反対方向への大きな値動きを生み出すことがあります。
強気のスマッシュデー反転は、急激な下落の後に現れます。スマッシュバーは、直前の1本以上のバーの安値を下回ってクローズしますが、アウトサイドバーではありません。この条件は重要です。アウトサイドバーではなく、売り圧力によってレンジを下抜けたことを意味するためです。その次のバーがスマッシュバーの高値を上抜ければ、下方向へのブレイクが失敗したことが確認され、上昇方向への反転シグナルとなります。
弱気のスマッシュデー反転は、その逆のパターンです。スマッシュバーは、直前の複数バーの高値を上回ってクローズしますが、やはりアウトサイドバーではありません。その次のバーがスマッシュバーの安値を下抜けた時点で、上方向へのブレイクが失敗したことが確認され、下落方向への反転がトリガーされます。
考え方は非常にシンプルです。継続しそうに見えた強い値動きが、その水準を維持できずに失敗したとき、その失敗自体が新たなトレード機会となります。
これらのルールの詳細と、それぞれの条件が持つ意味については、本連載の前回の記事で詳しく解説しました。前回は、これらのパターンを自動売買するエキスパートアドバイザー(EA)を構築しました。本記事では、自動発注ではなく視覚的な検出に焦点を当てます。使用する判定ルールは前回と同じですが、取引を実行する代わりに、反転が確認された時点でチャート上に矢印を表示します。これにより、パターンを即座に認識しつつ、最終的な売買判断はトレーダー自身が下せるようになります。
スマッシュパターンを分かりやすい視覚シグナルへ変換する
このインジケータの目的は、トレーダーの代わりに売買することではなく、重要なチャート局面を見落とせない形で可視化することです。ローソク足を一本ずつ確認しながら、スマッシュデー反転が発生したかどうかを判断する代わりに、インジケータが継続的に条件を監視し、反転が確認されたバーを正確にマーキングします。
強気のスマッシュデー反転が確認されると、スマッシュバーの下にシーグリーンの上向き矢印が表示されます。これは、現在形成中のバーがスマッシュバーの高値を上抜けた瞬間に描画されます。この上方向へのブレイクは、前日の強い売り圧力が失敗し、価格が上昇へ転じようとしていることを示しています。
弱気のスマッシュデー反転が確認されると、スマッシュバーの上に黒色の下向き矢印が表示されます。この矢印は、現在形成中のバーがスマッシュバーの安値を下抜けた時点で表示されます。この下方向へのブレイクは、強い買い圧力が維持できなかったことを示し、価格が下落へ転じつつあることを意味します。
これらの矢印は、即座に状況を把握できる視覚的なシグナルとして機能します。ブレイクアウトの失敗が明確になったローソク足を正確に示すことで、その後、実際にエントリーするかどうかは各トレーダーの裁量に委ねられます。あるトレーダーは長期トレンドと一致した場合のみシグナルを利用するかもしれません。また別のトレーダーは、重要な経済ニュースによるボラティリティや特定の曜日まで待つかもしれません。インジケータが提供するのは「イベント」であり、その意味付けを行うのはトレーダーです。
ラリー・ウィリアムズは、スマッシュバーの定義は客観的でありながら柔軟性も備えるべきだと述べています。実際には、スマッシュバーは指定した本数の過去バーの高値または安値を更新している必要があります。より多くのシグナルを得るために短いルックバック期間を好むトレーダーもいれば、より強い極値だけを対象にするために長いルックバック期間を選ぶトレーダーもいます。その両方に対応できるよう、このインジケータではルックバック期間を自由に調整できる入力パラメータを用意しています。
この設計により、判定ロジックは厳密かつ機械的でありながら、それぞれのトレードスタイルにも対応できます。インジケータが正確に答えるのは、ただ一つの問いです。「有効なスマッシュデー反転が、たった今確認されたかどうか」です。その後の行動は、各トレーダー自身のフィルターや判断に委ねられます。
結果をイメージしやすいように、以下は今回作成するインジケータを実際のチャートに適用した画面です。

インジケータを段階的に構築する
始める前に必要な前提知識
実際のコーディングに進む前に、作業をスムーズに進めるための基本的な知識が必要です。本記事では、MQL5プログラミング言語の基本要素である変数、関数、配列、条件分岐などについて理解していることを前提としています。また、MetaTrader 5デスクトップターミナルの基本操作として、チャートを開き、インジケータを適用した経験があることも前提としています。
さらに、開発はMetaEditor上でおこなうため、新しいソースファイルの作成、コードのコンパイル、およびコンパイル時に表示されるエラーの確認と修正に慣れていることが望まれます。これらの基本を押さえていれば、ゼロからインジケータを構築する準備は整っています。
完成版を参照しながらコーディングする
完成済みで動作確認済みのインジケータソースコードはlwSmashDayReversalIndicator.mq5という名前で本記事に添付されています。チュートリアルを読み進めながら実際にコードを書いていくことを強くお勧めします。一行ずつ入力し、その結果を完成版ソースと比較することで、実践的かつ確実に理解を深めることができます。期待どおりにコンパイルできなかったり、動作が異なったりした場合でも、添付されたソースコードを正しい参照として利用すれば、問題をすばやく修正できます。本記事の目的は、単に動作するインジケータを完成させることではなく、それぞれのコードがどのような役割を持ち、どのように構成されているかを理解することにあります。
インジケータファイルの作成
最初の実践的なステップは、MetaEditorを起動し、新しいカスタムインジケータのソースファイルを作成することです。ファイル名は、内容が分かる任意の名前で構いません。空のソースファイルを作成したら、まず以下のベースコードを貼り付けます。
//+------------------------------------------------------------------+ //| lwSmashDayReversalIndicator.mq5 | //| Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian | //| https://www.mql5.com/ja/users/chachaian | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian" #property link "https://www.mql5.com/ja/users/chachaian" #property version "1.00" //+------------------------------------------------------------------+ //| Custom Indicator specific directives | //+------------------------------------------------------------------+ #property indicator_chart_window #property indicator_plots 2 #property indicator_buffers 2 //+------------------------------------------------------------------+ //| User input variables | //+------------------------------------------------------------------+ input int smashBuyLookbackBars = 1; input int smashSellLookbackBars = 1; //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT; //+------------------------------------------------------------------+ //| Indicator buffers | //+------------------------------------------------------------------+ double buySmashArrowBuffer []; double sellSmashArrowBuffer[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- To configure the chart's appearance if(!ConfigureChartAppearance()){ Print("Error while configuring chart appearance", GetLastError()); return INIT_FAILED; } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t 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 int32_t &spread[]) { return(rates_total); } //+------------------------------------------------------------------+ //| This function configures the chart's appearance. | //+------------------------------------------------------------------+ bool ConfigureChartAppearance() { if(!ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite)){ Print("Error while setting chart background, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_SHOW_GRID, false)){ Print("Error while setting chart grid, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_MODE, CHART_CANDLES)){ Print("Error while setting chart mode, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrBlack)){ Print("Error while setting chart foreground, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, clrSeaGreen)){ Print("Error while setting bullish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrBlack)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CHART_UP, clrSeaGreen)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CHART_DOWN, clrBlack)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } return true; } //+------------------------------------------------------------------+
この初期コードは、インジケータの基本的な構造を定義するものです。この段階では、まだパターン検出は実装されていません。代わりに、後で追加する判定ロジックが動作するための基盤を準備します。
このベースコードは、いくつかの論理的なセクションに分けられます。それぞれのセクションには明確な役割があり、今後機能を追加していくための土台となります。
インジケータのプロパティ
//+------------------------------------------------------------------+ //| lwSmashDayReversalIndicator.mq5 | //| Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian | //| https://www.mql5.com/ja/users/chachaian | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian" #property link "https://www.mql5.com/ja/users/chachaian" #property version "1.00" //+------------------------------------------------------------------+ //| Custom Indicator specific directives | //+------------------------------------------------------------------+ #property indicator_chart_window #property indicator_plots 2 #property indicator_buffers 2
プロパティセクションでは、インジケータがチャート上でどのように動作するかを定義します。このインジケータは、サブウィンドウではなくメインチャート上に直接描画されます。また、2つのプロットと2つのバッファを確保します。これらのバッファには、後ほど買いシグナルと売りシグナルの矢印を表示する価格が格納されます。この段階では、まだ描画スタイルは定義されていません。バッファは、後で値が設定されるのを待つ空のコンテナとして機能します。
ユーザー入力設定
続いて、2つの入力変数を宣言します。
//+------------------------------------------------------------------+ //| User input variables | //+------------------------------------------------------------------+ input int smashBuyLookbackBars = 1; input int smashSellLookbackBars = 1;
これらの値は、スマッシュバーの終値が、過去何本分のバーを突破する必要があるかを制御します。入力パラメータとして保持することで、インジケータの柔軟性が高まります。同じコードを使用したまま、ソースコードを変更することなく、異なる厳格度の条件でテストできます。
これは、ラリー・ウィリアムズの考え方にも一致しています。つまり、パターンの強さは、市場がどれだけ過去の水準を押し広げ、極端な状態まで到達したかによって変化するという考えです。
時間足の参照
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;
時間足を格納する変数を定義し、現在表示しているチャートの時間足を参照するように設定します。これにより、インジケータは適用されたチャートの時間足に自動的に対応します。インジケータ設定画面で時間足を手動指定する必要はありません。そのため、すべての価格判定は、現在表示されているチャートと一致した状態で実行されます。
インジケータバッファ
続いて、2つの配列を宣言します。
//+------------------------------------------------------------------+ //| Indicator buffers | //+------------------------------------------------------------------+ double buySmashArrowBuffer []; double sellSmashArrowBuffer[];
各配列には、後ほど矢印を描画するための価格レベルが格納されます。特定のバーインデックスに対してバッファへ値を書き込むと、そのバー上に矢印が表示されます。一方、空の値を書き込んだ場合は何も描画されません。これらのバッファが、インジケータ全体の視覚的な出力部分になります。
初期化フェーズ
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- To configure the chart's appearance if(!ConfigureChartAppearance()){ Print("Error while configuring chart appearance", GetLastError()); return INIT_FAILED; } return(INIT_SUCCEEDED); }
OnInit関数は、インジケータがチャートへ適用された際に一度だけ実行されます。ここでは、ヘルパー関数を使用してチャートの表示設定を構成します。背景色は白に設定され、上昇ローソク足は緑色、下降ローソク足は黒色で表示され、さらにグリッドは非表示にされます。これにより、シンプルでコントラストの高いチャート環境が作られ、後から表示される矢印やラインをテスト時に確認しやすくなります。チャート設定の変更に失敗した場合は、初期化処理を停止し、エラーを通知します。これにより、設定が不完全な状態のままインジケータが動作することを防ぎます。
計算ループ
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t 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 int32_t &spread[]) { return(rates_total); }
OnCalculate関数には、今後すべての検出ロジックが実装されます。現時点では、単純に処理対象となるバーの総数を返すだけの状態です。つまり、この段階ではインジケータ自体は有効化されていますが、まだ市場データの分析やパターン判定は実行していません。後のセクションでは、この関数を拡張し、新しいティックや新しいバーが発生するたびに、矢印バッファをリアルタイムで更新できるようにします。
チャート表示設定用ヘルパー関数
//+------------------------------------------------------------------+ //| This function configures the chart's appearance. | //+------------------------------------------------------------------+ bool ConfigureChartAppearance() { if(!ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite)){ Print("Error while setting chart background, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_SHOW_GRID, false)){ Print("Error while setting chart grid, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_MODE, CHART_CANDLES)){ Print("Error while setting chart mode, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrBlack)){ Print("Error while setting chart foreground, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, clrSeaGreen)){ Print("Error while setting bullish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrBlack)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CHART_UP, clrSeaGreen)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CHART_DOWN, clrBlack)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } return true; }
ConfigureChartAppearance関数は、チャート表示に関するすべての設定を1か所にまとめる役割を持ちます。この処理を分離することで、初期化処理をシンプルで読みやすく保つことができます。後から表示設定を変更する必要が発生した場合でも、メインロジックに影響を与えることなく、この関数内だけを修正すれば対応できます。また、インジケータをチャートへ適用するたびに、同じ表示環境が確実に準備されるという利点もあります。
この段階では、まだ矢印は描画されません。しかし、このコードはインジケータ構築において重要な基盤となります。ここでは、どれだけの視覚的出力を用意するかを定義し、そのためのメモリ領域を確保し、チャートを見やすい状態に整え、さらに後でスマッシュデー反転の検出ロジックを組み込むための計算ループを準備しています。明確で最小限の基礎構造から開始することで、今後追加する各機能を混乱なく段階的に実装できます。
スマッシュデー反転インジケータのロジック構築この段階で、インジケータには基本構造と空のバッファが用意されています。次の目標は、これらのバッファをMetaTrader 5のターミナルへ正しく接続し、その後、適切なバーに意味のある値を書き込むロジックを作成することです。処理は、以下の3つのシンプルなステップで進めます。まず、十分な履歴データが存在することを確認します。
次に、配列をインジケータバッファへバインドします。最後に、パターンを検出し、過去データとリアルタイムの両方でバッファを更新する、小さく独立した関数を作成します。
十分な履歴データがあるかの確認
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Ensure there are enough historical bars available before running smash pattern detection int totalRates = iBars(_Symbol, timeframe); if(totalRates <= smashBuyLookbackBars || totalRates <= smashSellLookbackBars){ Print("There is not enough historical data!"); return INIT_FAILED; } return(INIT_SUCCEEDED); }
パターン検出を開始する前に、チャート上に必要なルックバック判定を安全に実行できるだけの十分なバー数が存在するかを確認します。利用可能なバー数が、設定したルックバック期間以下の場合、インジケータの初期化処理を停止します。これにより、過去のローソク足へアクセスする際に、範囲外のインデックスを参照することを防ぎます。このシンプルなチェック処理によって、後続のすべての関数が有効なデータを前提として動作できるようになり、実行時エラーを回避できます。
配列をインジケータバッファに接続する
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Bind arrays to indicator buffers SetIndexBuffer(0, buySmashArrowBuffer, INDICATOR_DATA); SetIndexBuffer(1, sellSmashArrowBuffer, INDICATOR_DATA); return(INIT_SUCCEEDED); }
データの有効性を確認した後、矢印表示用の値を保持する配列を、初期化処理内でSetIndexBufferを使用してバッファインデックスへ関連付けます。各バッファインデックスは、チャート上の1つの視覚的なプロットに対応します。バッファ0は買い矢印用で、バッファ1は売り矢印用です。この時点以降、バッファ要素へ価格値を書き込むことで、そのバーの正確な位置にグラフィカルなシグナルが表示されます。一方、EMPTY_VALUEを書き込んだ場合、その位置には何も描画されません。
アウトサイドバーの検出
次に、ローソク足が直前のローソク足の値幅全体を包み込んでいるかを確認する、小さなヘルパー関数を作成します。
//+-----------------------------------------------------------------------------+ //| Checks whether the bar at the given index fully engulfs the prior bar range | //+-----------------------------------------------------------------------------+ bool IsOutsideBar(double &open[], double &high[], double &low[], double &close[], int index){ double high0 = high[index]; double low0 = low [index]; double high1 = high[index - 1]; double low1 = low [index - 1]; return (high0 > high1 && low0 < low1); }
アウトサイドバーはスマッシュデー反転の検出対象から除外します。これは、アウトサイドバーが一方向への明確な感情的な動きではなく、上下両方向への値幅拡大を表すためです。このようなバーを早い段階で除外することで、後続の判定ロジックをよりシンプルかつ客観的に保つことができます。この関数は、強気・弱気の両方のスマッシュデー反転判定で利用される共通の基本コンポーネントになります。
強気スマッシュデー反転を検出する
//+-----------------------------------------------------------------------------------+ //| Detects a Smash Day Buy Reversal where the close breaks below multiple prior lows | //+-----------------------------------------------------------------------------------+ bool IsSmashDayBuyReversal(double &open[], double &high[], double &low[], double &close[], int index, int lookbackBars) { // Bar must not be an outside bar if(IsOutsideBar(open, high, low, close, index)){ return false; } double close1 = close[index]; double close2 = close[index + 1]; double high1 = high[index]; // Validate close breaks below N prior lows for(int i = index-1; i>=(index - lookbackBars); i--){ double priorLow = low[i]; if(close1 >= priorLow){ return false; } } return close2 > high1; }
強気のスマッシュデー反転は、3つの条件が満たされた場合に検出されます。1つ目は、そのバーがアウトサイドバーではないことです。2つ目は、その終値が、設定した本数分の過去バーの安値を下抜けていることです。3つ目は、次のバーがスマッシュバーの高値を上回って確定することです。この最終確認によって、下方向へのブレイクが失敗し、買い手が主導権を取り戻したことが示されます。すべての条件が成立すると、関数はtrueを返し、そのバーの安値が買いシグナル用バッファへ書き込まれます。
弱気スマッシュデー反転を検出する
//+-------------------------------------------------------------------------------------+ //| Detects a Smash Day Sell Reversal where the close breaks above multiple prior highs | //+-------------------------------------------------------------------------------------+ bool IsSmashDaySellReversal(double &open[], double &high[], double &low[], double &close[], int index, int lookbackBars) { // Bar must not be an outside bar if(IsOutsideBar(open, high, low, close, index)){ return false; } double close1 = close[index]; double close2 = close[index + 1]; double low1 = low [index]; // Validate close breaks above N prior highs for(int i = index-1; i>=(index - lookbackBars); i--){ double priorHigh = high[i]; if(close1 <= priorHigh){ return false; } } return close2 < low1; }
弱気の判定ロジックは、強気の場合と対になる形で構成されます。1つ目は、そのバーがアウトサイドバーではないことです。次に、その終値が過去のバーの高値を上抜けている必要があります。そして、その次のバーがスマッシュバーの安値を下回って確定することで確認されます。この動きは、上方向へのブレイクアウトが失敗し、売り手が市場の主導権を握ったことを示します。パターンが検出されると、そのバーの高値が売りシグナル用バッファへ書き込まれます。
過去のすべてのパターンをマッピングする
インジケータを初めてチャートへ適用した際には、過去の履歴全体をスキャンし、有効なスマッシュデー反転パターンをすべて表示する必要があります。この処理は、2つのマッピング関数によって実行されます。
//+-------------------------------------------------------------------------------------------------+ //| Scans historical price data to identify buy-side smash day reversals and populate the buy arrow | //+-------------------------------------------------------------------------------------------------+ void MapBuySmashDayReversals(double &open[], double &high[], double &low[], double &close[]){ int totalRates = ArraySize(open); for(int i = smashBuyLookbackBars; i < totalRates - 1; i++){ if(IsSmashDayBuyReversal(open, high, low, close, i, smashBuyLookbackBars)){ buySmashArrowBuffer[i] = low[i]; }else{ buySmashArrowBuffer[i] = EMPTY_VALUE; } } } //+--------------------------------------------------------------------------------------------------------------------+ //| Scans historical price data to identify sell-side smash day reversals and populate the sell arrow and line buffers | //+--------------------------------------------------------------------------------------------------------------------+ void MapSelSmashDayReversals(double &open[], double &high[], double &low[], double &close[]){ int totalRates = ArraySize(open); for(int i = smashSellLookbackBars; i < totalRates - 1; i++){ if(IsSmashDaySellReversal(open, high, low, close, i, smashSellLookbackBars)){ sellSmashArrowBuffer[i] = high[i]; }else{ sellSmashArrowBuffer[i] = EMPTY_VALUE; } } }
1つの関数は買いシグナル用バッファをスキャンして値を書き込み、もう1つの関数は売りシグナル用バッファをスキャンして値を書き込みます。各バーは検出関数を使用して個別にチェックされます。有効なパターンが見つかった場合、そのローソク足の極値(高値または安値)がバッファへ格納されます。一方、条件を満たさない場合、そのバッファ要素にはEMPTY_VALUEが設定されます。この処理によって、過去チャート全体のスマッシュデー反転シグナルが一度に構築されます。
バッファをリアルタイムで更新する
初回の履歴マッピングが完了した後も、新しい価格データが到着するたびに、インジケータは継続して反応する必要があります。そのため、2つの更新モードを使用します。新しいバーが開始された場合、直前に確定したバーをチェックし、確認済みのスマッシュデー反転パターンが形成されていれば、そのシグナルをバッファへ確定します。
//+----------------------------------------------------------------------------------------------------------------+ //|Updates the buy arrow buffer in real time when a bullish smash reversal is detected on the latest completed bar | //+----------------------------------------------------------------------------------------------------------------+ void UpdateBuySmashArrowBufferOnTick (double &open[], double &high[], double &low[], double &close[], int32_t rates_total){ if(IsSmashDayBuyReversal(open, high, low, close, rates_total - 2, smashBuyLookbackBars)){ buySmashArrowBuffer[rates_total - 2] = low[rates_total - 2]; }else{ buySmashArrowBuffer[rates_total - 2] = EMPTY_VALUE; } } //+-----------------------------------------------------------------------------------------------------------------+ //|Updates the sell arrow buffer in real time when a bearish smash reversal is detected on the latest completed bar | //+-----------------------------------------------------------------------------------------------------------------+ void UpdateSellSmashArrowBufferOnTick(double &open[], double &high[], double &low[], double &close[], int32_t rates_total){ if(IsSmashDaySellReversal(open, high, low, close, rates_total - 2, smashSellLookbackBars)){ sellSmashArrowBuffer[rates_total - 2] = high[rates_total - 2]; }else{ sellSmashArrowBuffer[rates_total - 2] = EMPTY_VALUE; } }
価格ティックが現在のバー内に到達すると、直近に確定したバーが再度チェックされます。
//+-----------------------------------------------------------------------------------------------------+ //|Updates the buy arrow buffer at the moment a new bar forms to mark confirmed bullish smash reversals | //+-----------------------------------------------------------------------------------------------------+ void UpdateBuySmashArrowBufferOnNewBar (double &open[], double &high[], double &low[], double &close[], int32_t rates_total){ if(IsSmashDayBuyReversal(open, high, low, close, rates_total - 3, smashBuyLookbackBars)){ buySmashArrowBuffer[rates_total - 3] = low[rates_total - 3]; }else{ buySmashArrowBuffer[rates_total - 3] = EMPTY_VALUE; } } //+------------------------------------------------------------------------------------------------------+ //|Updates the sell arrow buffer at the moment a new bar forms to mark confirmed bearish smash reversals | //+------------------------------------------------------------------------------------------------------+ void UpdateSellSmashArrowBufferOnNewBar(double &open[], double &high[], double &low[], double &close[], int32_t rates_total){ if(IsSmashDaySellReversal(open, high, low, close, rates_total - 3, smashSellLookbackBars)){ sellSmashArrowBuffer[rates_total - 3] = high[rates_total - 3]; }else{ sellSmashArrowBuffer[rates_total - 3] = EMPTY_VALUE; } }
これにより、確認条件が成立した瞬間に矢印を表示でき、次のバーが形成されるまで待つ必要がありません。これらの小さな更新関数は、最新の関連するインデックスだけを処理対象とします。そのため、計算負荷を軽く保ち、高速な動作を実現できます。
安全な処理のために価格データを準備する
プラットフォームから提供される元の価格配列は読み取り専用です。そのままでは自由に操作できないため、値をローカル配列へコピーして使用します。
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t 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 int32_t &spread[]) { ... //--- Temporary buffers used to store price data for rendering custom colored candles double lwOpen []; double lwHigh []; double lwLow []; double lwClose[]; //--- Copy price data into local working buffers to safely manipulate candle values without altering the original price arrays ArrayCopy(lwOpen, open); ArrayCopy(lwHigh, high); ArrayCopy(lwLow, low); ArrayCopy(lwClose, close); return(rates_total); }
インデックスの順序も変更されます。これにより、インデックス0が最も古いバーを指し、最後のインデックスが最新のバーを指す順序になります。すべての補助関数は、この前向きのインデックス順序を前提として動作します。この準備処理によって、インデックス管理が一貫したものになり、プラットフォームが管理する元データを直接変更することを防げます。
初回読み込みとリアルタイム更新の違い
計算処理では、2つの異なるタイミングを区別します。インジケータを初めてチャートへ適用した場合、まず過去データ全体をスキャンしてスマッシュデー反転パターンをマッピングします。
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t 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 int32_t &spread[]) { ... //--- This block will be executed whenever the indicator is initially attached on a chart if(prev_calculated == 0){ ArrayInitialize(buySmashArrowBuffer, EMPTY_VALUE); ArrayInitialize(sellSmashArrowBuffer, EMPTY_VALUE); //--- MapBuySmashDayReversals(lwOpen, lwHigh, lwLow, lwClose); MapSelSmashDayReversals(lwOpen, lwHigh, lwLow, lwClose); } return(rates_total); }
その前に、2つのバッファをEMPTY_VALUEで初期化し、その後で検出されたシグナルを書き込みます。
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t 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 int32_t &spread[]) { ... //--- This block is executed on new bar open if(prev_calculated != rates_total && prev_calculated != 0){ //--- UpdateBuySmashArrowBufferOnNewBar (lwOpen, lwHigh, lwLow, lwClose, rates_total); UpdateSellSmashArrowBufferOnNewBar(lwOpen, lwHigh, lwLow, lwClose, rates_total); } //--- This block is executed on arrival of new price (tick) data if(prev_calculated == rates_total){ //--- UpdateBuySmashArrowBufferOnTick (lwOpen, lwHigh, lwLow, lwClose, rates_total); UpdateSellSmashArrowBufferOnTick(lwOpen, lwHigh, lwLow, lwClose, rates_total); } return(rates_total); }
この分割によって、論理が明確かつ効率的になります。
以下は、OnCalculate関数の最終形です。
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t 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 int32_t &spread[]) { ArraySetAsSeries(open, false); ArraySetAsSeries(high, false); ArraySetAsSeries(low, false); ArraySetAsSeries(close, false); //--- Temporary buffers used to store price data for rendering custom colored candles double lwOpen []; double lwHigh []; double lwLow []; double lwClose[]; //--- Copy price data into local working buffers to safely manipulate candle values without altering the original price arrays ArrayCopy(lwOpen, open); ArrayCopy(lwHigh, high); ArrayCopy(lwLow, low); ArrayCopy(lwClose, close); //--- This block will be executed whenever the indicator is initially attached on a chart if(prev_calculated == 0){ ArrayInitialize(buySmashArrowBuffer, EMPTY_VALUE); ArrayInitialize(sellSmashArrowBuffer, EMPTY_VALUE); //--- MapBuySmashDayReversals(lwOpen, lwHigh, lwLow, lwClose); MapSelSmashDayReversals(lwOpen, lwHigh, lwLow, lwClose); } //--- This block is executed on new bar open if(prev_calculated != rates_total && prev_calculated != 0){ //--- UpdateBuySmashArrowBufferOnNewBar (lwOpen, lwHigh, lwLow, lwClose, rates_total); UpdateSellSmashArrowBufferOnNewBar(lwOpen, lwHigh, lwLow, lwClose, rates_total); } //--- This block is executed on arrival of new price (tick) data if(prev_calculated == rates_total){ //--- UpdateBuySmashArrowBufferOnTick (lwOpen, lwHigh, lwLow, lwClose, rates_total); UpdateSellSmashArrowBufferOnTick(lwOpen, lwHigh, lwLow, lwClose, rates_total); } return(rates_total); }
この関数は、インジケータの実行エンジンとして機能し、すべてのパターン検出処理とバッファ更新処理を、効率的かつ制御された形で統括します。
各呼び出しの開始時に、価格配列は非シリーズ形式のインデックスへ変換されます。これは意図的な処理です。すべての補助関数は、古いバーが先頭にあり、新しいバーが後方に配置される前向きのインデックス形式を前提としているためです。インデックス方式を統一して維持することで、後の検出処理で発生する微妙なロジックエラーを防ぐことができます。
次に、ローカル作業用配列を作成し、価格データのコピーを格納します。ターミナルから提供される元の価格配列は読み取り専用であるため、コピーを作成することで、元データを変更することなく安全に操作でき、複数の補助関数間で再利用できます。
インジケータを初めてチャートへ適用した場合、この関数は履歴全体のスキャンを実行します。まず、すべてのインジケータバッファを空の値で初期化し、クリーンな開始状態を確保します。その後、利用可能な履歴データ内に存在するすべての有効なスマッシュデー反転を検出し、チャート上へマッピングします。これにより、チャートを開いた直後から、過去に発生した関連シグナルがすべて表示されます。
2回目以降の呼び出しでは、関数は処理を2つのケースに分けます。1つ目は、新しいバーが形成された場合です。この場合、直前に確定したバーにおけるスマッシュデー反転の確認処理をおこない、該当する場合はバッファへシグナルを確定します。2つ目は、現在形成中のバーに新しい価格データ(ティック)が到着した場合です。この場合、リアルタイムでスマッシュデー反転の確認条件をチェックし、最新のバッファ値を必要に応じて更新します。
この構造によって、過去のパターン、新たに確定したバー、そしてリアルタイムの価格変化を、重複処理やシグナルの取りこぼしなく正しく管理できます。また、この関数は常に計算済みバーの総数を返します。これにより、MetaTrader 5ターミナルは差分更新を効率的に管理できます。
チャート上で矢印を表示する
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Configure Graphic Plots PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_ARROW); PlotIndexSetInteger(0, PLOT_ARROW, 233); PlotIndexSetInteger(0, PLOT_LINE_COLOR, clrSeaGreen); PlotIndexSetInteger(0, PLOT_ARROW_SHIFT, +20); PlotIndexSetInteger(0, PLOT_LINE_WIDTH, 2); PlotIndexSetDouble (0, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetString (0, PLOT_LABEL, "Smash Buy Bar"); PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_ARROW); PlotIndexSetInteger(1, PLOT_ARROW, 234); PlotIndexSetInteger(1, PLOT_LINE_COLOR, clrBlack); PlotIndexSetInteger(1, PLOT_ARROW_SHIFT, -20); PlotIndexSetInteger(1, PLOT_LINE_WIDTH, 2); PlotIndexSetDouble (1, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetString (1, PLOT_LABEL, "Smash Sell bar"); return(INIT_SUCCEEDED); }
バッファに値が書き込まれたとしても、ターミナル側へ描画方法を指定しなければ、チャート上には何も表示されません。そこで、各バッファを矢印用のプロットとして設定します。強気のスマッシュデー反転では、スマッシュバーの下にシーグリーンの上向き矢印を表示します。弱気のスマッシュデー反転では、スマッシュバーの上に黒色の下向き矢印を表示します。矢印コード、色、線幅、表示位置のオフセット、空の値の扱いなどは、すべて初期化処理の中で定義されます。これにより、単なるバッファ内の数値データが、実際にチャート上で確認できる視覚的なシグナルへ変換されます。
インジケータに分かりやすい名前を設定する
インジケータをチャート上やナビゲーターパネル内で簡単に識別できるよう、短く分かりやすい名前を設定します。
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- General indicator configurations IndicatorSetString(INDICATOR_SHORTNAME, "Larry Williams Market Structure Indicator"); return(INIT_SUCCEEDED); }
このちょっとした工夫が使いやすさを向上させ、ツールの完成度を高めます。
最終結果
この一連の処理を完了すると、インジケータは主に3つの重要な役割を果たします。1つ目は、過去のチャートデータをスキャンし、有効なスマッシュデー反転をすべてマークすることです。2つ目は、新しいバーが形成されるたびに監視し、確認されたシグナルを更新することです。3つ目は、リアルタイムの価格変化に反応し、新しいシグナルが発生した場合に即座に表示することです。
すべての矢印は、反転が発生した正確なバー位置に、チャート上へ直接描画されます。これにより、トレンド、タイミング、その他のフィルタと組み合わせて利用できる、明確な視覚的ガイドが提供されます。
この基盤によって、スマッシュデー反転という考え方は、迅速かつ客観的な裁量判断をサポートする実用的なビジュアルツールへと変換されます。
インジケータ出力のテストと検証
インジケータのロジックが完全に実装された後、次のステップは、すべてが正常にコンパイルされ、期待どおりに描画されることを確認することです。この段階では、MetaEditor内でソースコードをコンパイルします。これまでの手順が正しく実行されていれば、コンパイル処理はエラーや警告なしで完了するはずです。正常にコンパイルできることは、インジケータの構造、バッファの関連付け、計算ロジックがすべて一貫していることを確認する重要な指標になります。
コンパイルエラーが発生した場合は、現在のソースコードと添付されている参照ファイルlwSmashDayReversalIndicator.mq5と比較する方法が最も効果的です。この比較によって、不足しているコード行、配置位置を誤ったブロック、小さな構文ミスのような実装中に入り込んだ問題を簡単に発見できます。
インジケータのコンパイルが正常に完了したら、MetaTraderターミナルからチャートへ適用できます。この時点で、有効なスマッシュデー反転ポイントに矢印が表示され始めます。
最終的な動作を確認しやすくするため、このセクションでは、インジケータをNASDAQ-100の1時間足チャートへ適用した画面を紹介します。

表示された矢印は、価格チャート上に強気および弱気のスマッシュデー反転ポイントを明確に示します。これにより、実際の市場環境においてインジケータがどのように動作するかを簡単に確認できます。これでテストフェーズは完了し、インジケータが意図したとおりに機能していることが確認できます。
スマッシュデー反転インジケータを実際のトレードで活用する
インジケータの実装とテストが完了した後の次のステップは、実際のトレード環境でどのように活用できるかを理解することです。このインジケータは、単独で売買を行うシステムではなく、意思決定を支援するツールとして設計されています。主な役割は、チャート上にスマッシュデー反転パターンの形成を客観的に表示し、トレーダーが状況や相場背景を考慮したうえで判断できるようにすることです。
強気のスマッシュデー反転が形成されると、価格がスマッシュバーの高値を上抜けて反転を確認した後、そのバーの下にシーグリーンの矢印が表示されます。同様に、弱気のスマッシュデー反転では、価格が下方向へブレイクして確認された時点で、スマッシュバーの上に黒色の矢印が表示されます。これらの視覚的なサインによって、過去のローソク足を手作業で確認する必要がなくなり、相場の転換点となる可能性がある場所へ自然に注意を向けることができます。
実際の運用では、トレーダーはこれらのシグナルを、より広い市場環境と組み合わせて利用できます。たとえば、主要なトレンド方向と一致するシグナルのみを採用したり、上位時間足の市場構造を確認してシグナルを絞り込んだりすることができます。また、直近のニュースイベント、ボラティリティの状況、市場セッションのタイミングなどを考慮することで、さらに判断精度を高めることも可能です。このインジケータ自体は、これらの条件に対して中立的です。つまり、インジケータ自体はそれらのフィルタには関与せず、元のパターンだけを一貫して再現性高く提示します。
調整可能なルックバック入力によって、スマッシュデーの定義の厳格さを変更できます。短いルックバック期間では、より多くのシグナルが発生します。一方、長いルックバック期間では、より強い市場の極端な動きだけが検出されます。この柔軟性により、基本ロジックを変更することなく、さまざまな金融商品やトレードスタイルに対応できます。
最も重要なのは、このインジケータによって、曖昧になりやすい視覚的なパターンが、明確で客観的なチャート要素へ変換されることです。その結果、規律ある分析を支援し、感情的な判断を減らし、パターンを探す作業ではなく、実際のエントリーや管理の質に集中できるようになります。
結論
記事では、理論的な概念にとどまらず、ラリー・ウィリアムズのスマッシュデー反転の考え方を、実用的なビジュアル取引ツールへ変換しました。その結果として完成したのが、スマッシュデー反転を客観的に検出し、市場心理が極端な状態になった正確なバーを示し、さらに価格が反転を確認した瞬間を表示するカスタムMQL5インジケータです。これだけでも、裁量的なパターントレードで発生しやすい曖昧さや推測の多くを取り除くことができます。
しかし、より重要なのは、このインジケータが判断そのものを置き換えるのではなく、判断を支援する目的で設計されている点です。スマッシュデー反転は、トレンド方向、市場環境、タイミング要素などの背景と合わせて評価したときに、最も大きな意味を持ちます。チャート上へパターンを明確に表示することで、トレーダーはその市場背景を落ち着いて一貫性を持って判断できるようになります。その場の感情的な反応ではなく、事前に決めた分析基準に基づいて行動できます。
また、この開発プロセス自体も1つの設計例になります。裁量的な市場アイデアを分解し、客観的なルールとして定義し、さらに整理されたモジュール型のMQL5コードとして段階的に実装しました。このアプローチは、他のプライスパターンを研究したり、条件を変更した検証を行ったり、自分自身のトレード理念に合った追加ツールを作成したりする際にも応用できます。
この時点で、このインジケータををさらに検証する準備が整っています。異なるルックバック値、時間足、そして市場を試すことで、これまで見えていなかった新しい特徴を発見できる可能性があります。。読者の皆様には、さまざまな相場環境でパターンがどのように機能するかを観察し、検証結果や発見を共有することをお勧めします。実際のトレードエッジは、このような継続的な検証と観察の過程から見つかります。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/21128
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
プライスアクション分析ツールキットの開発(第61回):3スイング検証による構造的傾斜トレンドラインブレイクアウト
MQL5における二変量コピュラ(第3回):混合コピュラモデルの実装とチューニング
MQL5取引ツール(第17回):ベクトルベースの角丸長方形と三角形を探る
MQL5取引ツール(第16回):改良版スーパーサンプリング・アンチエイリアシング(SSAA)と高解像度レンダリング
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
このインジケーター用のEAを開発することはできますか?
このインジケーターはリペイントしますか、それとも遅延がありますか?
そうじゃない
矢印の表示が遅れる
針が止まっている