English Русский Deutsch
preview
ラリー・ウィリアムズの『市場の秘密』(第10回):スマッシュデー反転パターンの自動化

ラリー・ウィリアムズの『市場の秘密』(第10回):スマッシュデー反転パターンの自動化

MetaTrader 5エキスパートアドバイザー |
50 0
Chacha Ian Maroa
Chacha Ian Maroa

はじめに

市場は論理だけで動くものではありません。最も急激な値動きを生み出すのは、人間の感情です。恐怖は売りを加速させ、強欲は投資家に高値での買いを促します。ラリー・ウィリアムズは何十年にもわたり、このような感情が極端に高まる局面を研究し、市場参加者が過剰反応した直後に、短期的なチャンスが生まれやすいことを示しました。

その代表例が「スマッシュデー反転」です。一見すると価格は力強くブレイクアウトしたように見えますが、その動きはすぐに失敗へと転じます。この失敗によって、後追いで買ったトレーダーや売ったトレーダーがポジションに閉じ込められ、その損切り注文が反転相場をさらに加速させることが少なくありません。

もっとも、ウィリアムズ自身も、このパターンはボタン一つで機械的に使えるシグナルではなく、市場環境(コンテキスト)を考慮することが重要だと警告しています。問題は、多くのトレーダーがパターン自体は認識できても、それが市場や時間足、リスク設定によって実際にどのような成績を示すのかを客観的に検証できていないことです。

本記事では、その課題に取り組みます。ラリー・ウィリアムズのルールを可能な限り忠実に再現したEAを構築し、感情に左右されることなく売買を実行します。目的は、単に自動売買をおこなうことではありません。この手法を検証可能な形にすることで、何が機能し、何が機能しないのか、そしてどのような条件下で優位性が発揮されるのかを定量的に評価することです。


スマッシュデー反転パターンを理解する

コードを書き始める前に、まず「スマッシュデー」とは何かを正しく理解する必要があります。このパターンは、都合のよいチャートパターンの解釈や後付けの分析に基づくものではありません。重要なのは、市場が一方向へ大きく動いたにもかかわらず、その動きが継続せずに失敗し、市場参加者の感情的な偏りが露呈する局面を見極めることです。ラリー・ウィリアムズは、この考え方を主観ではなく、観察可能な値動きに基づいて考えました。

スマッシュデーとは

ラリー・ウィリアムズの考え方では、スマッシュデーとは、価格が直近の価格帯を力強く突破したように見え、市場参加者の感情を大きく揺さぶるセッションを指します。このブレイクによって、多くのトレーダーは新たなトレンドが始まったと確信します。ストップ注文が執行され、ブレイクアウト注文が約定し、わずか1日のうちに市場心理は極端な楽観または悲観へと傾きます。

スマッシュデーは、その日の終値が過去のローソク足に対してどこに位置するかによって定義されます。下方向では、終値が過去1本以上の安値を下回ります。上方向では、終値が過去1本以上の高値を上回ります。この日の本質は、値動きの方向そのものではなく、市場参加者に強い感情的反応を引き起こす点にあります。トレーダーは、市場が今後の方向性を明確に示したと錯覚してしまうのです。

このような日は感情の極端な高まりを表しています。恐怖によって保有ポジションの投げ売りや積極的な新規売りが誘発され、一方で強欲によって遅れてブレイクアウトに飛び乗る買いが発生します。市場はあたかも確実な方向性を示しているように見えますが、その「確実性」という幻想こそがチャンスを生み出します。

ここが、スマッシュデーと一般的なブレイクアウト手法との大きな違いです。従来のブレイクアウト戦略は、その後の価格の継続を前提としています。一方、スマッシュデーは、そのブレイクアウトが本当に継続するのかを疑います。ラリー・ウィリアムズは、一見力強く見えるブレイクアウトほど、短期間で失敗に終わるケースが少なくないことを観察しました。その失敗がすぐに起きれば、ブレイクアウトに飛び乗ったトレーダーは身動きが取れなくなります。この「捕らえられたトレーダー」の存在こそが、反転の原動力となるのです。

スマッシュデー買い反転のロジック

スマッシュデーの買い反転は、急激な下落から始まります。

スマッシュデー買いパターン

価格が過去1本以上のローソク足の安値を下回って引けると、市場では売り圧力が完全に優勢になったように見えます。多くのトレーダーにとって、この動きは非常に弱気で説得力のあるものに映ります。その結果、ストップ注文が執行され、下落局面の終盤で新規の売りポジションが追加されます。

その背景にある心理は単純です。市場参加者は、下落がさらに続くと考えます。価格が直近安値を更新すると、多くのトレーダーは、今後もさらに安値を目指すと信じ込みます。しかし、市場が直後に反転する可能性までは考慮していないことがほとんどです。

反転のシグナルは、市場がそのまま下落を継続できなかったときに現れます。価格がスマッシュデーの高値を再び上抜ければ、売り手が主導権を失ったことを意味します。遅れて売ったトレーダーは含み損を抱え、ショートカバーが始まります。同時に新たな買い手も参入し、その結果として価格は急速に上昇へ転じることがあります。

本EAでは、このパターンを客観的な条件で定義します。スマッシュデーのローソク足は、ユーザーが指定した本数分の過去ローソク足の安値よりも下で引けなければなりません。また、そのローソク足はアウトサイドバーであってはなりません。そうしたバーは、無秩序な反転局面というより一方向への値幅拡大型の動きを示しやすいためです。パターンが確認されると、そのスマッシュデーの高値が重要な基準価格となります。ロングポジションは、その高値を価格が上抜けた場合にのみエントリーします。エントリーのタイミングは、選択したエントリーモードに応じて、翌足ですぐにおこなう場合と、追加の確認を待ってからおこなう場合があります。

スマッシュデー売り反転のロジック

売り反転は、買い反転と同じ考え方を、反対方向に適用したものです。

スマッシュデー売りパターン

価格が直近の高値を上回って引けると、市場では買い手の期待感と高揚感が一気に高まります。ブレイクアウトを狙うトレーダーは勢いよく買いに入り、この上昇がさらに続くと確信します。

この上方向のスマッシュデーは、まさに強欲が支配する局面です。トレーダーはチャンスを逃したくないという心理から、高値圏でも積極的に買いを入れます。しかし、一見非常に強そうに見える上昇ほど、実際には失敗しやすいケースも少なくありません。

反転は、そのブレイクアウトが維持できなかったときに確認されます。価格がスマッシュデーの安値を下抜けると、買い手が相場に閉じ込められたことを意味します。高値で買ったポジションは含み損となり、その投げ売りによって売り圧力がさらに強まります。その結果、下落が加速することも珍しくありません。

EAでは、このパターンも同様に客観的なルールで判定します。スマッシュデーのローソク足は、指定した本数分の過去ローソク足の高値を上回って引ける必要があり、さらにアウトサイドバーではないことが条件となります。そのローソク足の安値を重要な基準価格とし、価格がその水準を下抜けた場合にのみショートポジションを建てます。これにより、単なる予測ではなく、ブレイクアウトが実際に失敗したことを確認してからエントリーします。

このように、買い反転と売り反転の両方を同じ原則に基づいて構成することで、このシステムは対称性を保ち、客観的な検証が可能となります。また、その設計思想はラリー・ウィリアムズ本来の考え方にも忠実なものとなっています。



スマッシュデーEAの構造とその理由

結果やパフォーマンスを検証する前に、このエキスパートアドバイザー(EA)がどのような構成になっているのか、そしてなぜ各ルールが採用されているのかを理解することが重要です。本EAの目的は、複雑な売買システムを作ることではなく、ラリー・ウィリアムズのスマッシュデーの考え方を、市場の特性と自動売買における実務上の制約を踏まえた、シンプルで検証可能なフレームワークとして実装することにあります。

このEAは、まず第一にリサーチおよび検証ツールとして設計されています。すべてのルールは、曖昧さを排除し、過剰最適化を防ぎ、検証対象がパターン本来の定義そのものであることを保証するために設けられています。

1つのパターン群を、1つのシステムとして検証する

本EAでは、互いに鏡像関係にある2つのパターン、すなわちスマッシュデー買い反転スマッシュデー売り反転に着目しています。両者は独立して評価され、それぞれ入力パラメータによって有効・無効を切り替えることができます。

EAは、買い反転のみ、売り反転のみ、あるいはその両方を対象として動作させることが可能です。ただし、どの設定であっても、常に保有できるポジションは1つだけです。この設計は意図的なものです。スマッシュデー反転は継続的なシグナルではなく、個々に独立したイベントとして発生します。複数のポジションを同時に保有すると、各取引の因果関係が曖昧になり、パフォーマンスの解釈も難しくなります。そのため、常に1ポジションに限定することで、すべての取引を単一のスマッシュデーイベントに対応付けられるようにしています。

明確で客観的なパターン定義

スマッシュデー買い反転と売り反転は、いずれも厳密で数値化可能なルールによって定義されています。買い反転では、直近で確定したバーの終値が、ユーザーが指定した本数分の過去バーの安値を下回っている必要があります。この本数はパラメータで変更できるため、浅いブレイクダウンと深いブレイクダウンが結果にどのような影響を与えるかを比較・検証できます。また、スマッシュデーとなるバーはアウトサイドバーであってはなりません。上下両方向へ大きくレンジを拡大するローソク足は、方向性よりもノイズを含む場合が多いためです。有効なスマッシュデーが確認された後は、市場がそのブレイクに失敗したことを示す必要があります。具体的には、価格がスマッシュデーの高値を上抜けた場合にのみ買いエントリーを検討します。これは、売り手の支配力が失われ、反転がすでに始まっていることを確認するためです。

売り反転では、これと対称の条件を採用しています。スマッシュデーのバーは過去バーの高値を上回って引けること、アウトサイドバーではないこと、さらにその後価格がスマッシュデーの安値を下抜けることで、上方向へのブレイクが失敗したことを確認します。これらのルールは意図的に厳格に設定されています。パターンを明確に定義できなければ、信頼できる検証をおこなうことはできないからです。

エントリー確認と執行方法

どの程度の確認をもってエントリーすべきかについては、トレーダーによって考え方が異なります。価格がレベルを突破した瞬間にエントリーしたい人もいれば、ブレイクアウトをバーの終値で確認してから入りたい人もいます。この違いに対応するため、本EAでは2種類のエントリーモードを用意しています。

1つ目は、価格がスマッシュデーの基準レベルを突破した時点で即座にエントリーするモードです。2つ目は、現在のバーがそのレベルの外側で引けて確定するのを待ってからエントリーするモードです。どちらの方法にも合理性があり、この選択肢を用意することで、パターン自体は変えずに、確認タイミングの違いが結果へ与える影響を検証できるようになっています。

パターンに適したリスク管理

リスク管理は、ラリー・ウィリアムズの本来の考え方に沿って、シンプルかつ一貫したものとしています。買い反転では、ストップロスはスマッシュデーの安値に設定します。売り反転では、スマッシュデーの高値に設定します。これらはいずれも、パターンが明確に否定されたと判断できる価格水準です。

利益確定は、ユーザーが設定できるリスクリワード比に基づいておこないます。これにより、時間足や市場が異なっても、一貫した客観的な利益確定ルールを維持できます。

ポジションサイズも柔軟に設定できます。シンプルな固定ロットによる運用に加え、口座残高に対する一定割合をリスクとして自動計算するモードも選択可能です。自動ロット計算を使用すれば、すべての取引で一定割合の資金をリスクにさらすことになり、ロットサイズの違いではなく、戦略そのもののパフォーマンスを公平に評価できます。

このような構成により、本EAはスマッシュデー反転を、本来のパターン定義に忠実な形で検証できます。同時に、将来的にフィルタや追加条件、裁量的な要素を組み合わせる場合でも、オリジナルのパターンの整合性を損なうことなく拡張できる、堅牢な基盤となっています。



スマッシュデー反転EAの構築

このセクションでは、アイデアやルールの説明から、実際の実装へと移行します。ここではもはや、スマッシュデー反転とは何か、あるいはそれがなぜ機能するのかについて議論する段階ではありません。これからは、そのロジックを、制御された再現可能な方法で実行、テスト、検証できるEAへと落とし込む段階です。

コードを書く前に、まず期待値を明確にしておくことが重要です。本プロジェクトは実践的な構築作業です。概念は、実装し、観察し、問い直すことで最もよく理解できます。そのため、この解説を読むだけでなく、実際にコードを書きながら読み進めることを推奨します。

そのプロセスを支援するために、完全に動作するソースファイル lwSmashDayReversalExpert.mq5 がこの記事に添付されています。別タブで開きながら進めることで、アイデアを相互参照し、仮定を検証し、それぞれの要素がシステム全体の中でどのように機能しているかを理解しやすくなります。

進めるための前提条件

このセクションを最大限活用するためには、いくつかの基礎スキルが前提となります。まず、MQL5言語への理解が必要です。変数、関数、ループ、条件分岐、列挙型、構造体、標準ライブラリといった基本概念はすでに理解している必要があります。もし基礎がまだ十分でない場合は、公式の MQL5リファレンスが優れた出発点となります。

次に、MetaTrader 5プラットフォームの使用経験が必要です。チャートの操作、EAの適用、ストラテジーテスターの使用、ナビゲーターなど基本的なウィンドウ管理が含まれます。

さらに、MetaEditorに慣れていることが求められます。ソースファイルの作成、コードのコンパイル、エラーの確認と修正といった作業は必須のスキルです。これらの前提が整ったら、EAの構築をゼロから開始できます。

ボイラープレートコードによる基盤構築

すべての堅牢なEAは、整った構造を持つクリーンな基盤から始まります。

//+------------------------------------------------------------------+
//|                                     lwSmashDayReversalExpert.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"

//+------------------------------------------------------------------+
//| Standard Libraries                                               |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>

//+------------------------------------------------------------------+
//| Custom Enumerations                                              |
//+------------------------------------------------------------------+
enum ENUM_SMASH_ENTRY_MODE{
   ENTRY_ON_LEVEL_CROSS,
   ENTRY_ON_BAR_CLOSE
};

enum ENUM_SMASH_TRADE_MODE{
   SMASH_TRADE_BUY_ONLY,
   SMASH_TRADE_SELL_ONLY,
   SMASH_TRADE_BOTH
};

enum ENUM_LOT_SIZE_INPUT_MODE { 
   MODE_MANUAL, 
   MODE_AUTO 
};

//+------------------------------------------------------------------+
//| User input variables                                             |
//+------------------------------------------------------------------+
input group "Information"
input ulong magicNumber         = 254700680002;                 
input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;

input group "Smash Day Pattern Rules"
input int smashBuyLookbackBars  = 1;
input int smashSellLookbackBars = 1;

input group "Smash Day Entry Settings"
input ENUM_SMASH_ENTRY_MODE smashEntryMode = ENTRY_ON_LEVEL_CROSS;
input ENUM_SMASH_TRADE_MODE smashTradeMode = SMASH_TRADE_BOTH;

input group "Trade and Risk Management"
input ENUM_LOT_SIZE_INPUT_MODE lotSizeMode  = MODE_AUTO;
input double riskPerTradePercent            = 1.0;
input double positionSize                   = 0.1;
input double riskRewardRatio                = 3.0;

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
//--- Create a CTrade object to handle trading operations
CTrade Trade;

//--- To hep track current market prices for Buying (Ask) and Selling (Bid)
double askPrice;
double bidPrice;

//--- To store current time
datetime currentTime;

struct MqlSmashDayPatternState{

   //--- Pattern detection flags
   bool hasBuySmashSetup;
   bool hasSellSmashSetup;

   //--- Reference breakout levels from smash bar
   double buyBreakoutLevel;
   double sellBreakoutLevel;

   //--- Pattern bar reference data
   datetime smashBarTime;

   //--- Entry status tracking
   bool entryPending;
   
   //--- Stop-Loss levels
   double buyStopLoss;
   double sellStopLoss;
   
};

MqlSmashDayPatternState smashState;

//--- To store minutes data
double closePriceMinutesData [];

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   //---  Assign a unique magic number to identify trades opened by this EA
   Trade.SetExpertMagicNumber(magicNumber);
   
   //--- Reset
   ZeroMemory(smashState);
   
   //--- Treat the following arrays as timeseries (index 0 becomes the most recent bar)
   ArraySetAsSeries(closePriceMinutesData, true);

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){

   //--- Notify why the program stopped running
   Print("Program terminated! Reason code: ", reason);

}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

   //--- Retrieve current market prices for trade execution
   askPrice    = SymbolInfoDouble (_Symbol, SYMBOL_ASK);
   bidPrice    = SymbolInfoDouble (_Symbol, SYMBOL_BID);
   currentTime = TimeCurrent();
   
   //--- Get some minutes data
   if(CopyClose(_Symbol, PERIOD_M1, 0, 7, closePriceMinutesData) == -1){
      Print("Error while copying minutes datas ", GetLastError());
      return;
   }   
}

//--- UTILITY FUNCTIONS
  
//+------------------------------------------------------------------+

以下のコードは、その基盤を確立します。この段階では、まだ取引ロジックは存在しません。代わりに、アイデンティティ、設定、データ構造、および後にロジックが動作する環境を定義しています。このアプローチにより、複雑性を適切に管理し、各レイヤーのロジックに明確な目的を持たせることができます。

ファイルヘッダーとプロパティ

//+------------------------------------------------------------------+
//|                                     lwSmashDayReversalExpert.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"

冒頭セクションでは、ソースファイルの識別情報を定義し、MetaTraderに表示されるメタデータを提供します。これには、ファイル名、作成者情報、バージョン番号、および参照リンクが含まれます。これらの情報は実行には影響しませんが、長期的な保守、バージョン管理、および配布において重要です。

標準ライブラリのインクルード

//+------------------------------------------------------------------+
//| Standard Libraries                                               |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>

CTrade クラスにアクセスするためにTradeライブラリを含めます。このクラスは、ポジションを建て、管理し、決済するための、クリーンで信頼性の高いインターフェースを提供します。独自の注文処理コードではなく標準ライブラリを利用することで、エラーのリスクを低減し、可読性を向上させます。

カスタム列挙型の定義

//+------------------------------------------------------------------+
//| Custom Enumerations                                              |
//+------------------------------------------------------------------+
enum ENUM_SMASH_ENTRY_MODE{
   ENTRY_ON_LEVEL_CROSS,
   ENTRY_ON_BAR_CLOSE
};

enum ENUM_SMASH_TRADE_MODE{
   SMASH_TRADE_BUY_ONLY,
   SMASH_TRADE_SELL_ONLY,
   SMASH_TRADE_BOTH
};

enum ENUM_LOT_SIZE_INPUT_MODE { 
   MODE_MANUAL, 
   MODE_AUTO 
};

列挙型は、抽象的な判断を明示的で制御された選択肢へと変換するために使用されます。ここでは3つの主要な列挙型を定義します。

1つ目は、エントリーのトリガー方法を制御します。価格がブレイクアウトレベルを超えた瞬間にすぐ取引を開始するか、またはバーが完全に確定してから動きを確認してエントリーするかを選択できます。

2つ目は、取引を許可するスマッシュデーパターンを定義します。買いのみ、売りのみ、または両方を選択可能です。これはリサーチおよびテストにおいて重要であり、方向性の挙動を分離して分析できるようにします。

3つ目は、ポジションサイズの管理方法を制御します。ロットサイズを固定するか、リスクに基づいて自動計算するかを選択できます。これらの列挙型は、実行ロジックに複雑さを持ち込まずにEAの柔軟性を支える基盤となります。

ユーザー入力パラメータ

//+------------------------------------------------------------------+
//| User input variables                                             |
//+------------------------------------------------------------------+
input group "Information"
input ulong magicNumber         = 254700680002;                 
input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;

input group "Smash Day Pattern Rules"
input int smashBuyLookbackBars  = 1;
input int smashSellLookbackBars = 1;

input group "Smash Day Entry Settings"
input ENUM_SMASH_ENTRY_MODE smashEntryMode = ENTRY_ON_LEVEL_CROSS;
input ENUM_SMASH_TRADE_MODE smashTradeMode = SMASH_TRADE_BOTH;

input group "Trade and Risk Management"
input ENUM_LOT_SIZE_INPUT_MODE lotSizeMode  = MODE_AUTO;
input double riskPerTradePercent            = 1.0;
input double positionSize                   = 0.1;
input double riskRewardRatio                = 3.0;

入力パラメータは、設定を直感的に扱えるよう論理的にグループ化されています。Informationグループではマジックナンバーと時間足を定義します。マジックナンバーはこのEAが自分自身のポジションのみを管理することを保証し、時間足はスマッシュデーパターンを評価する対象を決定します。

スマッシュデー Pattern Rulesグループでは、パターンの厳格さを定義します。ルックバックパラメータは、スマッシュデーとして有効と見なすために、どれだけ前のバーを条件として参照・検証するかを制御します。これにより、パターンの出現頻度と選択性が直接変化します。

スマッシュデー Entry Settingsグループでは、実行ロジックと取引方向を定義します。これらの入力により、エントリーロジックの積極性または保守性、そしてロング・ショート・両方の許可設定が決まります。

Trade and Risk Managementグループでは、ポジションサイジングとリターンの期待値を定義します。リスクは固定または動的のいずれかで設定でき、テイクプロフィットレベルは設定可能なリスクリワード比から算出されます。

グローバル取引オブジェクトと市場状態

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
//--- Create a CTrade object to handle trading operations
CTrade Trade;

//--- To hep track current market prices for Buying (Ask) and Selling (Bid)
double askPrice;
double bidPrice;

//--- To store current time
datetime currentTime;

CTradeオブジェクトのインスタンスを1つ作成します。このオブジェクトは、後ほどすべての取引執行を担当することになります。また、現在のAskとBid値、およびサーバー時刻を追跡するための変数も定義しています。これらの値はティックごとに更新され、実行ロジックのためのリアルタイムデータフィードとして機能します。

スマッシュデーパターンステートの構造化

このEAにおける最も重要な設計上の決定事項の一つは、スマッシュデーパターンの状態を追跡するための専用構造を使用することです。

MqlSmashDayPatternState構造体はメモリとして機能します。

struct MqlSmashDayPatternState{

   //--- Pattern detection flags
   bool hasBuySmashSetup;
   bool hasSellSmashSetup;

   //--- Reference breakout levels from smash bar
   double buyBreakoutLevel;
   double sellBreakoutLevel;

   //--- Pattern bar reference data
   datetime smashBarTime;

   //--- Entry status tracking
   bool entryPending;
   
   //--- Stop-Loss levels
   double buyStopLoss;
   double sellStopLoss;
   
};

MqlSmashDayPatternState smashState;

これにより、一度パターンを検出すれば、その後は過去のデータを繰り返し再評価することなく、価格の動向を監視することが可能になります。

この構造の中で、有効な買いまたは売りのスマッシュセットアップが存在するかどうか、スマッシュバーから導き出されたブレイクアウトレベル、スマッシュバーの時刻、およびエントリーが現在保留中かどうかを追跡します。

また、スマッシュバーから直接導き出されたストップロスレベルも保存しています。これにより、パターン関連の情報がすべて一箇所に集約され、グローバル変数の乱立を防ぐことができます。この構造は、後にパターン検出と取引実行をつなぐ架け橋となります。

価格データの処理(イントラバー検出)

レベルのクロスを確実に検出するためには、直近の1分足終値データにアクセスできる必要があります。

//--- To store minutes data
double closePriceMinutesData [];

1分足データを格納するための配列を確保し、明示的に時系列配列として設定します。これにより、インデックス0が常に最新のデータを指すようになります。

このデータは後に、エントリーモードが「即時実行」に設定されている場合に、価格がリアルタイムでブレイクアウトレベルを超えたかどうかを検出するために使用されます。

初期化とクリーンアップ

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   //---  Assign a unique magic number to identify trades opened by this EA
   Trade.SetExpertMagicNumber(magicNumber);
   
   //--- Reset
   ZeroMemory(smashState);
   
   //--- Treat the following arrays as timeseries (index 0 becomes the most recent bar)
   ArraySetAsSeries(closePriceMinutesData, true);

   return(INIT_SUCCEEDED);
}

OnInit関数は、取引オブジェクトにマジックナンバーを割り当て、スマッシュデー状態構造をクリアし、1分足データ配列を準備します。これにより、EAが追加されるたびに、クリーンで予測可能な状態から開始されるようになります。

OnDeinit関数は終了の理由を報告します。これはテストやデバッグの際に役立ちます。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){

   //--- Notify why the program stopped running
   Print("Program terminated! Reason code: ", reason);

}

OnTickフレームワークの確立

OnTick関数は今のところ、基本的なタスクのみを実行します。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

   //--- Retrieve current market prices for trade execution
   askPrice    = SymbolInfoDouble (_Symbol, SYMBOL_ASK);
   bidPrice    = SymbolInfoDouble (_Symbol, SYMBOL_BID);
   currentTime = TimeCurrent();
   
   //--- Get some minutes data
   if(CopyClose(_Symbol, PERIOD_M1, 0, 7, closePriceMinutesData) == -1){
      Print("Error while copying minutes datas ", GetLastError());
      return;
   }
}

Bid価格とAsk価格を更新し、現在のサーバー時刻を取得するとともに、1分足の終値データを更新します。現時点では、まだ取引の判断はおこないません。これは意図的な設計です。この段階では、EAの骨格を構築しているだけです。パターン検出、状態管理、そして取引実行は、このフレームワークの上に次のセクションで段階的に実装していきます。ここまでで、EAは安定性と拡張性を備えた基盤を持つことになります。EAは、自身の識別情報を把握し、どのように動作すべきかを理解し、さらに検出したスマッシュデーパターンに関する情報を保存できるようになります。

この基盤が整ったことで、いよいよスマッシュデー反転パターンを検出し、それを実際に取引可能なセットアップへと変換するロジックの実装を開始します。

新しい取引日の検出とパターン状態の管理

スマッシュデーロジックは、日足が確定するたびに1回だけ評価されます。そのため、最初に追加する基本機能は、選択した時間足で新しいバーが開始されたことを検出する仕組みです。これは、小さなヘルパー関数によって実現します。次の関数を、ソースファイルのユーティリティ関数セクションに追加してください。

//--- UTILITY FUNCTIONS
//+------------------------------------------------------------------+
//| Function to check if there's a new bar on a given chart timeframe|
//+------------------------------------------------------------------+
bool IsNewBar(string symbol, ENUM_TIMEFRAMES tf, datetime &lastTm){

   datetime currentTm = iTime(symbol, tf, 0);
   if(currentTm != lastTm){
      lastTm       = currentTm;
      return true;
   }  
   return false;
}

この関数は、現在のバーの開始時刻と最後に記録されたバーの開始時刻を比較します。時刻が異なれば、新しいバーが形成されたと判断できます。

3番目のパラメータは参照渡しされます。これにより、新しいバーが検出されると、関数は保存されている時刻を即座に更新できます。言い換えれば、この関数は新しいバーを検出するだけでなく、そのバーに対してすでに処理をおこなったことも記憶します。この動作を実現するために、グローバル変数セクションへ次のグローバル変数を追加します。

//--- To help track new bar open
datetime lastBarOpenTime;

この変数には、すでに処理済みである最新バーのタイムスタンプが保持されます。初期化関数では、この値をゼロに設定し、EAをチャートへ適用した直後の最初のティックが新しいバーイベントとして扱われるようにします。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
   
   //--- Initialize global variables
   lastBarOpenTime = 0;

   return(INIT_SUCCEEDED);
}

これ以降、すべての日次の判断は、新しいバーが生成されたときに一度だけ実行されます。これにより、シグナルの重複を防ぎ、ロジックをシンプルかつ予測しやすいものに保つことができます。

アウトサイドバーの除外

スマッシュデー反転の設計ルールの一つとして、スマッシュバーは直前のバー全体を包み込むアウトサイドバーであってはなりません。このようなバーは異なる市場挙動を表すため、除外する必要があります。ユーティリティ関数セクションに次のヘルパー関数を追加します。

//+-----------------------------------------------------------------------------+
//| Checks whether the bar at the given index fully engulfs the prior bar range |
//+-----------------------------------------------------------------------------+
bool IsOutsideBar(string symbol, ENUM_TIMEFRAMES tf, int index){

   double high0 = iHigh(symbol, tf, index);
   double low0  = iLow (symbol, tf, index);

   double high1 = iHigh(symbol, tf, index + 1);
   double low1  = iLow (symbol, tf, index + 1);

   return (high0 > high1 && low0 < low1);
}

この関数は、現在のバーの値幅を直前のバーの値幅と比較します。高値が直前のバーより高く、かつ安値が直前のバーより低い場合、そのバーはアウトサイドバーと判定され、パターン判定の対象から除外されます。このシンプルなフィルタにより、戦略は、私たちが求める明確なスマッシュ構造とは一致しない極端なボラティリティバーに反応することを防ぎます。

スマッシュデー買い反転の検出

ここで、最初の本格的なパターン検出関数を定義します。アウトサイドバー判定関数の直後に、次の関数を配置してください。

//+-----------------------------------------------------------------------------------+
//| Detects a Smash Day Buy Reversal where the close breaks below multiple prior lows |
//+-----------------------------------------------------------------------------------+
bool IsSmashDayBuyReversal(string symbol,
                           ENUM_TIMEFRAMES tf,
                           int index,
                           int lookbackBars)
{
   // Bar must not be an outside bar
   if(IsOutsideBar(symbol, tf, index))
      return false;

   double close1 = iClose(symbol, tf, index);

   // Validate close breaks below N prior lows
   for(int i = 2; i <= lookbackBars + 1; i++)
   {
      double priorLow = iLow(symbol, tf, index + i);

      if(close1 >= priorLow)
         return false;
   }

   return true;
}

このロジックでは、3つの条件を確認します。まず、対象バーがアウトサイドバーではないことを確認します。次に、スマッシュバーの終値を取得します。そして、その終値を複数の過去バーの安値と比較します。終値がそれらすべての安値を下回っていれば、市場は強く下落し、直近のサポートを下抜けて引けたことになります。これが、私たちが検出したい感情的な売りです。入力パラメータによって、比較対象となる過去バーの本数を指定できます。これにより、ユーザーの好みに応じてパターン判定をより厳格にも、より緩やかにも設定できます。

スマッシュデーの売り反転の検出

売り側のロジックは、買い側のロジックを左右反転したものです。買いパターン検出関数の直後に、次の関数を追加します。

//+-------------------------------------------------------------------------------------+
//| Detects a Smash Day Sell Reversal where the close breaks above multiple prior highs |
//+-------------------------------------------------------------------------------------+
bool IsSmashDaySellReversal(string symbol,
                            ENUM_TIMEFRAMES tf,
                            int index,
                            int lookbackBars)
{
   // Bar must not be an outside bar
   if(IsOutsideBar(symbol, tf, index))
      return false;

   double close1 = iClose(symbol, tf, index);

   // Validate close breaks above N prior highs
   for(int i = 2; i <= lookbackBars + 1; i++)
   {
      double priorHigh = iHigh(symbol, tf, index + i);

      if(close1 <= priorHigh)
         return false;
   }

   return true;
}

ここでは、スマッシュバーの終値が複数の過去バーの高値を上回っているかどうかを確認します。これは感情的な買いが急増した状態を表しており、その後上昇が続かなかった場合には、高値で飛び乗った買い手が捕まりやすくなります。これら2つの関数によって、下方向のパニック売りと上方向の過熱した買いを客観的に検出できるようになります。

新しい取引日におけるパターン状態のリセット

新しい日足が始まるたびに、内部状態は初期化されている必要があります。前日のブレイクアウトレベルを翌日に持ち越してはいけません。ユーティリティ関数セクションへ次の関数を追加します。

//+--------------------------------------------------------------------+
//| Resets all Smash Day detection flags and breakout reference levels |
//+--------------------------------------------------------------------+
void ResetSmashDaySetupState(){

   smashState.hasBuySmashSetup  = false;
   smashState.hasSellSmashSetup = false;

   smashState.buyBreakoutLevel  = iHigh(_Symbol, timeframe, 1);
   smashState.sellBreakoutLevel = iLow(_Symbol, timeframe, 1);

   smashState.smashBarTime      = iTime(_Symbol, timeframe, 1);
   
}

この関数は、検出フラグをリセットし、直前に確定したバーを基準として参照レベルを更新します。これ以降、毎日新しい状態からスタートし、再びパターン検出を実行します。これは、スマッシュデーセットアップが1日限り有効であるため不可欠です。

リアルタイムでのブレイクアウト検出

この戦略は2種類のエントリー方法をサポートしています。1つは価格がレベルを突破した瞬間にエントリーする方法、もう1つはバーがレベルの外側で確定するまで待つ方法です。即時エントリーを実現するためには、1分足データを用いてレベルのクロスを検出する必要があります。以下のヘルパー関数を追加してください。

//+------------------------------------------------------------------+
//| To detect a crossover at a given price level                     |                               
//+------------------------------------------------------------------+
bool IsCrossOver(const double price, const double &closePriceMinsData[]){
   if(closePriceMinsData[1] <= price && closePriceMinsData[0] > price){
      return true;
   }
   return false;
}


//+------------------------------------------------------------------+
//| To detect a crossunder at a given price level                    |                               
//+------------------------------------------------------------------+
bool IsCrossUnder(const double price, const double &closePriceMinsData[]){
   if(closePriceMinsData[1] >= price && closePriceMinsData[0] < price){
      return true;
   }
   return false;
}

これらの関数は、直近2本の1分足終値を比較します。価格がレベルより下から上へ抜けた場合はブレイクアウト、上から下へ抜けた場合はブレイクダウンと判定します。これにより、高速かつ正確なイントラデー確認が可能になります。

常に1つのポジションのみを保有する

新しい取引を開始する前に、このEAがすでにポジションを保有していないことを確認する必要があります。次の2つの関数を追加してください。

//+------------------------------------------------------------------+
//| To verify whether this EA currently has an active buy position.  |                                 |
//+------------------------------------------------------------------+
bool IsThereAnActiveBuyPosition(ulong magic){
   
   for(int i = PositionsTotal() - 1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0){
         Print("Error while fetching position ticket ", _LastError);
         continue;
      }else{
         if(PositionGetInteger(POSITION_MAGIC) == magic && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
            return true;
         }
      }
   }
   
   return false;
}

//+------------------------------------------------------------------+
//| To verify whether this EA currently has an active sell position. |                                 |
//+------------------------------------------------------------------+
bool IsThereAnActiveSellPosition(ulong magic){
   
   for(int i = PositionsTotal() - 1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0){
         Print("Error while fetching position ticket ", _LastError);
         continue;
      }else{
         if(PositionGetInteger(POSITION_MAGIC) == magic && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
            return true;
         }
      }
   }
   
   return false;
}

これらの関数は、現在保有しているすべてのポジションを確認し、一致する取引が存在する場合はtrueを返します。後のすべてのエントリー判断では、これらの関数を呼び出し、「一度に1つの取引のみ」というルールを適用します。

口座リスクに基づく自動ポジションサイズ計算

自動ロットサイズが有効な場合、各取引で口座残高の一定割合のみをリスクにさらすよう、取引数量を計算します。ユーティリティ関数セクションに次の関数を追加します。

//+------------------------------------------------------------------+
//| Calculates position size based on a fixed percentage risk of the account balance |
//+------------------------------------------------------------------+
double CalculatePositionSizeByRisk(double stopDistance){
   double amountAtRisk = (riskPerTradePercent / 100.0) * AccountInfoDouble(ACCOUNT_BALANCE);
   double contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE);
   double volume       = amountAtRisk / (contractSize * stopDistance);
   return NormalizeDouble(volume, 2);
}

この関数は、設定したリスク割合を、ストップロスまでの距離に基づいてロットサイズへ変換します。ストップロスが広いほどロットサイズは小さくなり、ストップロスが狭いほどロットサイズは大きくなります。これにより、各取引で負う金額ベースのリスクは常に一定となります。

テイクプロフィット価格の算出

テイクプロフィットは、リスクリワード比に基づいて設定します。次の2つの関数を追加してください。

//+------------------------------------------------------------------+
//| Computes the bullish take profit level based on entry price, stop loss, and risk to reward ratio |
//+------------------------------------------------------------------+
double GetBuyTakeProfit(double entryPrice, double stopLoss){
   double riskDistance = entryPrice - stopLoss;
   double rewardDistance = riskDistance * riskRewardRatio;
   rewardDistance = MathAbs(rewardDistance);
   return NormalizeDouble((entryPrice + rewardDistance), Digits());
}

//+------------------------------------------------------------------+
//| Computes the bearish take profit level based on entry price, stop loss, and risk to reward ratio |
//+------------------------------------------------------------------+
double GetSellTakeProfit(double entryPrice, double stopLoss){
   double riskDistance = stopLoss - entryPrice;
   double rewardDistance = riskDistance * riskRewardRatio;
   rewardDistance = MathAbs(rewardDistance);
   return NormalizeDouble((entryPrice - rewardDistance), Digits());
}

これらの関数は、エントリー価格とストップロス価格の距離を測定し、それに設定したリスクリワード比を掛けて利益目標価格を算出します。これにより、常にリスクに比例したリターンを狙うことができます。

取引開始

次に、取引実行を2つのシンプルな関数にまとめます。

//+------------------------------------------------------------------+
//| Function to open a market buy position                           |
//+------------------------------------------------------------------+
bool OpenBuy(double entryPrice, double stopLoss, double takeProfit, double lotSize){
   
   if(lotSizeMode == MODE_AUTO){
      lotSize = CalculatePositionSizeByRisk(entryPrice - stopLoss);
   }
   
   if(!Trade.Buy(lotSize, _Symbol, entryPrice, stopLoss, takeProfit)){
      Print("Error while executing a market buy order: ", GetLastError());
      Print(Trade.ResultRetcode());
      Print(Trade.ResultComment());
      return false;
   }
   return true;
}

//+------------------------------------------------------------------+
//| Function to open a market sell position                          |
//+------------------------------------------------------------------+
bool OpenSel(double entryPrice, double stopLoss, double takeProfit, double lotSize){
   
   if(lotSizeMode == MODE_AUTO){
      lotSize = CalculatePositionSizeByRisk(stopLoss - entryPrice);
   }
   
   if(!Trade.Sell(lotSize, _Symbol, entryPrice, stopLoss, takeProfit)){
      Print("Error while executing a market sell order: ", GetLastError());
      Print(Trade.ResultRetcode());
      Print(Trade.ResultComment());
      return false;
   }
   return true;
}

これらの関数は、自動ロットサイズを処理した後、CTradeオブジェクトを通じて注文を送信します。これにより、取引実行ロジックはパターン検出ロジックから完全に分離されます。

OnTick関数ですべてを統合する

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

   //--- Retrieve current market prices for trade execution
   askPrice    = SymbolInfoDouble (_Symbol, SYMBOL_ASK);
   bidPrice    = SymbolInfoDouble (_Symbol, SYMBOL_BID);
   currentTime = TimeCurrent();
   
   //--- Get some minutes data
   if(CopyClose(_Symbol, PERIOD_M1, 0, 7, closePriceMinutesData) == -1){
      Print("Error while copying minutes datas ", GetLastError());
      return;
   }
   
   //--- Execute this block on new bar formation
   if(IsNewBar(_Symbol, timeframe, lastBarOpenTime)){
      
      //---
      if(smashEntryMode == ENTRY_ON_BAR_CLOSE){
         
         //---
         if(smashState.hasBuySmashSetup && smashState.entryPending){
            if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){
               if(smashTradeMode == SMASH_TRADE_BUY_ONLY || smashTradeMode == SMASH_TRADE_BOTH){
                  OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize);
                  smashState.entryPending = false;
               }
            }
         }
         
         //---
         if(smashState.hasSellSmashSetup && smashState.entryPending){
            if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){
               if(smashTradeMode == SMASH_TRADE_SELL_ONLY || smashTradeMode == SMASH_TRADE_BOTH){
                  OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize);
                  smashState.entryPending = false;
               }
            }
         }
         
      }
      
      //---
      ResetSmashDaySetupState();
      
      //---
      if(IsSmashDayBuyReversal(_Symbol, timeframe, 1,  smashBuyLookbackBars)){
         smashState.hasBuySmashSetup = true;
         smashState.entryPending     = true;
         smashState.buyStopLoss      = iLow(_Symbol, timeframe, 1);
      }
      
      //---
      if(IsSmashDaySellReversal(_Symbol, timeframe, 1, smashSellLookbackBars)){
         smashState.hasSellSmashSetup = true;
         smashState.entryPending      = true;
         smashState.sellStopLoss      = iHigh(_Symbol, timeframe, 1);
      }
      
   }
   
   //---
   if(smashEntryMode == ENTRY_ON_LEVEL_CROSS){
      
      //---
      if(smashState.hasBuySmashSetup && smashState.entryPending){
         // ---
         if(IsCrossOver(smashState.buyBreakoutLevel, closePriceMinutesData)){
            if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){
               if(smashTradeMode == SMASH_TRADE_BUY_ONLY || smashTradeMode == SMASH_TRADE_BOTH){
                  OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize);       
               }
               smashState.hasBuySmashSetup = false;
               smashState.entryPending     = false;
            }
         }
      }
      
      //---
      if(smashState.hasSellSmashSetup && smashState.entryPending){
         // ---
         if(IsCrossUnder(smashState.sellBreakoutLevel, closePriceMinutesData)){
            if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){
               if(smashTradeMode == SMASH_TRADE_SELL_ONLY || smashTradeMode == SMASH_TRADE_BOTH){
                  OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize);
               }
               smashState.hasSellSmashSetup = false;
               smashState.entryPending      = false;
            }
         }
      }
      
   }   
}

OnTick関数では、まず価格情報と1分足データを更新します。その後、新しい日足に反応します。新しいバーが生成された場合、次の3つの処理をこの順番で実行します。

1. バー確定後にエントリーするモードの場合は、保留中のエントリーを処理する。

2. スマッシュデーの内部状態をリセットする。

3.確定したバーを対象として、新しいスマッシュパターンを検出する。

その後、新しいバーの処理ブロックの外側で、即時エントリーモードが有効な場合のリアルタイムブレイクアウトを監視します。この構成により、次のことが保証されます。

  • パターン検出は1日1回だけ実行される
  • ブレイクアウトは継続的に監視される
  • エントリーは重複しない
  • 同時に保有できる取引は1つだけである

スマッシュ状態フラグは、パターン検出からエントリーまでの流れを管理し、注文が実行されると自動的に無効化されます。このように、検出ロジックと実行ロジックを明確に分離することで、EAの動作は予測しやすく、理解しやすいものになります。

テスト時の視覚的な見やすさの改善

テストを始める前に、チャートの表示を見やすく設定します。

//+------------------------------------------------------------------+
//| 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関数を追加し、 OnInitから呼び出します。

//+------------------------------------------------------------------+
//| Expert 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);
}

これにより、ローソク足や背景色が見やすく設定され、ビジュアルバックテスト中でも取引や値動きを容易に確認できます。この段階で、EAは完成です。すべての主要機能(新しい取引日の検出、スマッシュパターンの認識、ブレイクアウトの監視、リスクベースのポジションサイジング、同時保有を1取引に制限する仕組み、内部状態の自動リセット)が実装されています。

完全なソースファイルはlwSmashDayReversalExpert.mq5として提供されています。これにより、ラリー・ウィリアムズのスマッシュデー反転を、正確で検証可能かつ再現性のある取引ルールへ変換する、完全に機能するリサーチツールが完成しました。



ゴールド価格に対するスマッシュデー買い反転のテスト

この手法が実際にどのようなパフォーマンスを示すのかを評価するため、ゴールド(日足チャート)を対象に管理されたバックテストを実施しました。テスト期間は、2025年1月1日から2025年12月31日までの1年間です。

スマッシュデー買いモードのみを有効にし、システムは買い(ロング)ポジションのみを取るよう設定しました。ポジションサイズは自動リスク管理モードを使用し、各取引で口座残高の1%をリスクに設定しています。これにより、口座残高の増減に応じてポジションサイズも自動的に変化し、各取引で負うリスクを常に一定に保つことができます。

テストは初期残高10,000ドルから開始し、1年間で合計2,307.25ドルの純利益を達成しました。

テストレポート

これは20%を超える投資収益率(ROI)に相当します。勝率は64%でした。

勝率自体は極めて高いわけではありませんが、より重要なのはエクイティカーブの推移です。エクイティカーブを見る限り、口座の成長は比較的安定しており、滑らかです。

エクイティカーブ

急激な資産の減少や長期間にわたる横ばいの状態は見られません。その代わりに、エクイティカーブは緩やかかつ安定したペースで右肩上がりに推移しており、損失が適切に抑えられ、利益が時間とともに複利効果によって積み上がっていることが分かります。このような結果は、2つの設計上の選択によるものです。1つ目は、すべての取引で口座資金のごく一部のみをリスクにさらしていることです。2つ目は、常に市場へ参加するのではなく、明確で客観的なパターンが現れた場合にのみ取引をおこなっていることです。

このテストを完全に再現できるよう、本記事には2つのファイルが付属しています。設定ファイルにはストラテジーテスターの環境設定が含まれ、パラメータファイルにはEAで使用した入力パラメータが保存されています。これらのファイルをストラテジーテスターへ読み込むことで、同じ銘柄と期間において同じ結果を再現できます。

もちろん、この1回のテストだけで、このパターンがあらゆる市場やすべての年度で有効であることを証明するものではありません。しかし、明確に定義された一つの設定条件の下では、この手法がゴールド市場においてリスクを管理しながら安定した資産成長を実現したことを示しています。このプロジェクトの本当の強みは、その柔軟性にあります。ルックバック期間、エントリー方法、リスク水準といった主要な挙動は、すべて入力パラメータから変更できます。そのため、さまざまな条件を自由に検証することが可能です。

異なる銘柄や異なる年度、あるいは別のパラメータ設定で検証をおこなえば、新たな強みや弱点が見つかるかもしれません。異なる組み合わせによって、さらに優れた優位性が発見される可能性もあります。ぜひ独自の検証をおこない、その結果やアイデア、改善案をコメント欄で共有してください。そうすることで、このリサーチは共同の取り組みとしてさらに発展していくでしょう。



結論

本記事では、ラリー・ウィリアムズによる裁量取引のアイデアを、完全に客観的で検証可能な取引システムへと変換しました。感情的なブレイクアウトが短時間で失敗することに着目したスマッシュデー反転パターンを、コンピュータが迷うことなく認識・売買できる明確なルールへ落とし込みました。

さらに、これらの条件をリアルタイムで検出し、自動的にリスクを管理し、規律に従って取引を実行するEAを構築しました。本システムは、一度に1つのポジションのみを保有するルールを徹底し、明確なストップロスと利益目標を設定するとともに、口座リスクの一定割合に基づいてポジションサイズを自動調整します。これにより、曖昧に語られてきた市場の挙動を、体系的なリサーチツールへと発展させました。

ゴールドを対象とした1年間のバックテストでは、安定した資産成長、抑えられたドローダウン、そして継続的な複利効果が確認されました。これは、このパターンがチャート上で視覚的に魅力的であるだけでなく、実際の市場環境において定量的に測定・評価できることを示しています。

さらに重要なのは、このプログラムが単なる自動売買システムではなく、研究・検証のためのプラットフォームとして設計されている点です。主要なルールはすべて入力パラメータから調整できるため、さまざまなルックバック期間、エントリー方法、リスク設定を、あらゆる銘柄や時間足で自由に検証できます。読者は、本記事で紹介した設定だけに限定されることはありません。

本記事で実現したのは、トレーダーの心理とプログラムによるロジックを結び付ける橋渡しです。これまで目視でしか判断できなかった感情的な市場の罠を、数年分のヒストリカルデータに対して数秒で検出できるようになりました。これにより、より深いリサーチ、より迅速なアイデアの検証、そしてより確信を持った意思決定が可能になります。

スマッシュデーの反転パターンは、数あるパターンのうちの一つにすぎません。同じ手法は、古典的な取引文献で紹介されている他の多くの概念にも応用できます。明確なルール、厳格なリスク管理、そして体系的な検証を組み合わせることで、直感と客観的な根拠を区別できるようになります。

次のステップは、読者自身による検証です。新たなテストを実施し、パラメータを変更し、市場ごとの結果を比較することで、このアイデアのさらに優れた応用方法が見つかるかもしれません。その成果を共有することで、本記事は単なる一つの記事ではなく、実践的な知見を積み重ねるリサーチの基盤となっていくでしょう。



添付ファイルと使用方法

本プロジェクトの再現およびさらなるリサーチを容易にするため、本記事には使用した主要なファイルをすべて添付しています。これらのファイルを利用することで、取引ロジック、テスト環境、および各種パラメータ設定を手動で再構成することなく読み込むことができます。

この構成により、示した結果をそのまま再現でき、ソースコードの確認や入力値の調整も容易になり、新たなアイデアを検証することができます。このEAは、完成されたツールであると同時に、さらなる研究の出発点でもあります。

以下の表は、各添付ファイルとその用途をまとめたものです。

ファイル名説明
lwSmashDayReversalExpert.mq5スマッシュデー反転を実装したMQL5 EAの完全なソースコード
configurations.ini一貫したバックテスト設定のためのストラテジーテスター環境構成
parameters.set提示されたバックテスト結果を生成するために使用された入力パラメータプリセット

ソースコードはMetaEditorで直接開き、内容の確認・修正・コンパイルをおこなうことができます。設定ファイルはストラテジーテスターへ読み込むことで、本記事と同じテスト環境を再現できます。また、パラメータファイルはストラテジーテスターの入力設定タブから適用でき、紹介したバックテストと同じ取引設定を即座に読み込むことができます。

これらのファイルを組み合わせることで、すぐに利用できるリサーチパッケージが完成します。これにより、戦略の検証や、さまざまな市場環境におけるストレステストを実施できるほか、追加のフィルタや新たなアイデアを組み込んで拡張することも可能です。

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

添付されたファイル |
configurations.ini (1.54 KB)
parameters.set (1.42 KB)
カスタムインジケータワークショップ(第2回):MQL5で実用的なSupertrend EAを構築する カスタムインジケータワークショップ(第2回):MQL5で実用的なSupertrend EAを構築する
Supertrend駆動型エキスパートアドバイザー(EA)をMQL5でゼロから構築する方法を学びます。本記事では、インジケータのリソースとしての組み込み、確定済みバーからのバッファ値の読み取り、確定したフリップの検出、ポジションの整合と切り替え、ストップロス方式およびポジションサイジングの設定について解説します。最後にストラテジーテスターの設定と再現可能なテストを示し、設定可能なEAと、さらなる研究および拡張のための明確なフレームワークを手にすることができます。
MetaTrader 5機械学習の設計図(第7回):散発的な実験から再現可能な結果へ MetaTrader 5機械学習の設計図(第7回):散発的な実験から再現可能な結果へ
本連載の最新回では、個々の機械学習手法の解説から一歩進み、多くのクオンツトレーダーを悩ませている「リサーチの混沌(Research Chaos)」という問題に焦点を当てます。本記事では、場当たり的なノートブックでの実験から脱却し、再現性・追跡可能性・効率性を備えた、本番運用レベルのパイプラインへ移行する方法について説明します。
MQL5標準ライブラリエクスプローラー(第7回):CCanvasによるインタラクティブなポジションラベル表示 MQL5標準ライブラリエクスプローラー(第7回):CCanvasによるインタラクティブなポジションラベル表示
MQL5標準ライブラリに含まれるCCanvasを使用して、ポジション情報を可視化するツールの構築方法を解説します。このプロジェクトを通して、標準ライブラリの各種モジュールを扱うスキルを高めるとともに、ライブチャート上で保有ポジションを視覚的に確認・操作できる実用的なツールを作成します。ぜひ最後までお読みいただき、議論にもご参加ください。
バックトラッキング探索アルゴリズム(BSA) バックトラッキング探索アルゴリズム(BSA)
もし最適化アルゴリズムが過去の探索経路を記憶し、その記憶を使ってより良い解を見つけられるとしたらどうでしょうか。BSAはまさにそれを実現し、探索と実績のある探索方向の再利用をバランスよく両立させます。本記事では、そのアルゴリズムの秘密を解き明かします。シンプルなアイデア、最小限のパラメータ、そして安定した結果が特徴です。