English Русский Deutsch
preview
ラリー・ウィリアムズの『市場の秘密』(第15回):相場環境を用いた「隠れスマッシュデー」反転戦略

ラリー・ウィリアムズの『市場の秘密』(第15回):相場環境を用いた「隠れスマッシュデー」反転戦略

MetaTrader 5トレーディングシステム |
19 0
Chacha Ian Maroa
Chacha Ian Maroa

はじめに

前回の記事では、ラリー・ウィリアムズが説明した「隠れスマッシュデー」反転パターンを検出するためのカスタムインジケータを開発しました。このインジケータはチャート上の「隠れスマッシュ」バーを識別し、視覚的にマークすることで、手動の解釈に頼ることなく、パターンを客観的に認識できるようになります。

しかし、パターンを検出することは最初のステップに過ぎません。完全なトレードアプローチには、検出されたセットアップをいつ取引し、いつ無視するかを決定する追加ルールが必要です。これらのルールには、取引方向、現在のトレンド、または曜日による制限などの要素が含まれる場合があります。明確に定義された条件がなければ、パターンを体系的に評価することは困難になります。

本記事では、これらの追加条件を構造化されたトレードシステムへ変換する方法に焦点を当てます。これは、「隠れスマッシュデー」セットアップを明確な条件で自動化し、検証可能なルールとして評価したいアルゴリズムトレーダーを対象としています。

前回の記事のインジケータを基盤として、「隠れスマッシュ」シグナルを読み取り、エントリー前に追加条件を適用するエキスパートアドバイザー(EA)を実装します。これらの条件には、Supertrendインジケータによるトレンド方向の一致、任意設定可能な取引曜日の制限、そしてスマッシュバーの構造またはATRに基づくストップロスモデル、固定またはリスクベースのポジションサイズによるリスク管理が含まれます。

その結果、単なるパターン検出だけに依存せず、市場環境を考慮した「隠れスマッシュデー」セットアップを研究および取引するための、客観的なフレームワークが完成します。


「隠れスマッシュデー」のパターンとシグナル確認

トレードシステムを実装する前に、ラリー・ウィリアムズが説明した「隠れスマッシュデー」パターンの構造を簡単に振り返り、有効な取引シグナルがどのように生成されるかを明確にします。

「隠れスマッシュデー」バーは、そのローソク足の値幅に対する終値の位置によって識別されます。強気の「隠れスマッシュ」セットアップでは、バーはレンジの下側で引けながらも、前のバーの終値より高く終わります。これは、前のセッションより高く引けているにもかかわらず、その日の値幅の下限寄りで終わっていることを示しており、潜在的な弱さを示唆します。

弱気の「隠れスマッシュ」セットアップでは、ロジックは逆になります。バーはレンジの上側で引けながらも、前のバーの終値より低く終わります。この場合、市場は前日比では弱く見えますが、その日のレンジ内部では高値付近で終了しており、潜在的な内部的強さを示しています。

ただし、「隠れスマッシュ」バー自体は売買シグナルではありません。セットアップが実際の取引対象となるには、次のバーによる確認が必要です。この実装では、確定済みバーのみを使用して確認をおこない、イントラバーのノイズを排除し、テスト時の動作を再現可能にします。

有効な買いシグナルは、以下の流れで発生します。

  1. バーインデックス2に強気の「隠れスマッシュ」バーが検出される。
  2. 次のバーが、その「隠れスマッシュ」バーの高値を上回って終値確定する。
  3. 新しいバーの開始時点で、ロングポジションのエントリーが許可される。

買いセットアップ

有効な売りシグナルは、この構造を反転させたものになります。

  1. バーインデックス2に弱気の「隠れスマッシュ」バーが検出される。
  2. 次のバーが、その「隠れスマッシュ」バーの安値を下回って終値確定する。
  3. 新しいバーの開始時点で、ショートポジションのエントリーが許可される。

売りセットアップ

実装を簡略化するため、このEAは「隠れスマッシュ」パターン自体を直接検出しません。その代わり、前回の記事で開発したカスタムインジケータから確定済みの「隠れスマッシュ」シグナルを読み取ります。そのインジケータは、検出されたパターンを専用バッファに記録します。バーインデックス2に「隠れスマッシュ」バーが存在する場合、インジケータバッファにはEMPTY_VALUEではない値が格納され、それを利用してEAは確認条件の評価と取引シグナルの生成をおこないます。


EAの設計と取引実行ルール

本記事で開発するEAは、前回の記事で紹介した「隠れスマッシュデー」インジケータを直接基盤としています。このシステムは、パターンを内部で検出するのではなく、そのインジケータからシグナルを読み取り、その後、追加ルールを評価してから取引を実行します。この設計により、パターン検出とトレード管理の役割を明確に分離できます。インジケータが「隠れスマッシュ」セットアップを検出する一方、EAはフィルタ処理、リスク管理、注文執行を担当します。

すべての取引判断は、新しいバーが開始された時点でのみ評価されます。これにより、「隠れスマッシュ」バーと確認バーの両方が完全に確定した状態でシグナルを処理できます。確定済みバーを待つことで、イントラバー中の価格変動によるノイズを排除し、ヒストリカルテストにおいて一貫した動作を実現します。

シグナル処理

EAは、インジケータバッファの値を読み取り、「隠れスマッシュ」パターンが検出されたかどうかを判断します。インジケータは、検出されたパターンを矢印バッファによってマークします。バーインデックス2において、バッファ値がEMPTY_VALUE以外の場合、システムはそれを「隠れスマッシュ」セットアップが検出された状態として認識します。

インデックス2にセットアップが成立すると、EAは有効なシグナルが確定しているかを評価します。強気シグナルの場合、確認バーが「隠れスマッシュ」バーの高値を上回って終値確定することでエントリー可能になります。弱気シグナルの場合、確認バーが「隠れスマッシュ」バーの安値を下回って終値確定することで有効なシグナルになります。この確認処理が完了した後にのみ、システムはポジションを開くことを許可します。

取引方向の制御

EAでは、許可する取引方向を柔軟に設定できます。取引モードパラメータを使用することで、システムは買い取引のみ、売り取引のみ、または両方向の取引を実行するように設定できます。このオプションにより、異なる方向性の前提条件で「隠れスマッシュ」セットアップがどのように機能するかを検証できます。

トレンドコンテキストフィルタ

オプションのトレンドフィルタとして、Supertrendインジケータを利用できます。このフィルターを有効にした場合、ロング取引はSupertrendが現在強気状態の場合のみ許可され、ショート取引はSupertrendが弱気状態の場合のみ許可されます。フィルタを無効にした場合は、トレンド方向による制限なしでシグナルを評価します。

取引曜日フィルタ

EAには、特定の曜日のみ取引を許可する曜日フィルタも搭載されています。利用可能な動作モードは2種類あります。1つ目のモードでは、制限なしですべての曜日で取引が許可されます。2つ目のモードでは、曜日ごとに個別で有効または無効を設定できます。この機能により、異なる曜日条件の下で戦略の動作を検証できます。

ストップロスモデル

2種類のストップロスモデルが実装されています。1つ目のモデルでは、「隠れスマッシュ」バーの構造を利用します。このモードでは、ロング取引の場合、ストップロスは「隠れスマッシュ」バーの安値に設定され、ショート取引の場合、ストップロスはそのバーの高値に設定されます。

2つ目のモデルでは、ATRインジケータによって測定されるボラティリティを利用します。このオプションを選択した場合、ストップロスの距離はATR値に設定可能な倍率を掛けて計算されます。この方法により、保護ストップを変化する市場ボラティリティに適応させることができます。

テイクプロフィット計算

テイクプロフィットレベルは、設定可能なリワードリスク比率を使用して計算されます。ストップロスまでの距離が決定されると、設定された比率に基づいて利益目標までの距離が算出されます。その距離は、ロング取引の場合はエントリー価格に加算され、ショート取引の場合はエントリー価格から減算されます。これにより、すべてのポジションが一貫したリスクリワード比に基づいて管理されます。

ポジションサイジング

EAは、手動および自動の両方のポジションサイズ設定に対応しています。手動モードでは、ユーザーが指定した固定ポジションサイズがすべての取引に適用されます。自動モードでは、トレーダーが許容する口座残高に対するリスク割合に基づいてポジションサイズが計算されます。

自動計算方式では、OrderCalcProfit関数を使用して1ロットあたりの想定損失額を計算します。その後、設定されたリスク割合に一致するようにポジションサイズが調整されます。また、最小ボリューム、最大ボリューム、ボリュームステップなどのブローカー制限を考慮し、計算されたポジションサイズが有効な値になるように管理されます。

ポジション管理

一貫したトレード管理を維持するため、EAは同時に1つのアクティブポジションのみを許可します。新しい取引を開始する前に、システムは同じマジックナンバーを持つ既存の買いまたは売りポジションがすでに有効になっているかを確認します。ポジションが存在する場合、新しいシグナルは無視されます。

これらの要素を組み合わせることで、パターン検出、コンテキストフィルタ、規律あるリスク管理を統合した構造化されたトレードフレームワークが形成されます。完成したEAは、異なるコンテキスト条件下で取引した場合に、「隠れスマッシュデー」セットアップがどのように機能するかを研究するための柔軟な環境を提供します。


隠れスマッシュデーコンテキスト戦略の実行フロー

実装を確認する前に、EAの完全なトレードロジックを、構造化された手順としてまとめます。この概要では、これまで説明してきた概念的なルールと、ソースコード内で実装されるプログラムロジックを結び付けます。

この手順により、トレードプロセスが明確かつ規律ある構造に従って実行されることが保証されます。

if a new bar has opened

 Update Hidden Smash indicator buffers
 Update Supertrend indicator values
 Update ATR values

 if no open position exists

  Check for a Hidden Smash Buy setup at index two

  if a setup exists

   Check whether buy trades are permitted by the trade direction setting

   if the Supertrend filter is enabled

    Verify that the market is currently in a bullish trend state

   if trading day restrictions are active

    Verify that trading is allowed on the current day

   if all conditions are satisfied

    Compute the stop loss level
    Compute the take profit level
    Determine the position size
    Open a buy order

  Check for a Hidden Smash Sell setup at index two

  if a setup exists

   Check whether sell trades are permitted by the trade direction setting

   if the Supertrend filter is enabled

    Verify that the market is currently in a bearish trend state

   if trading day restrictions are active

    Verify that trading is allowed on the current day

   if all conditions are satisfied

    Compute the stop loss level
    Compute the take profit level
    Determine the position size
    Open a sell order

シグナル検出と取引実行は分離されています。これは、パターン自体が「隠れスマッシュ」インジケータによって識別されるためです。すべての評価処理は、新しいバーが開始された後にのみ実行されます。これにより、実際に処理する前に、スマッシュバーと確認バーの両方が完全に確定していることが保証されます。保護レベル(ストップロス)とポジションサイズは注文送信前に計算されるため、各取引は明確に定義されたリスクパラメータを持った状態で開始されます。最後に、システムは単一ポジションルールを適用し、常に1つの取引のみが存在できるように管理します。


開発環境とプロジェクト基盤の準備

EAを実装する前に、いくつかのコンポーネントを準備する必要があります。本記事で説明するトレードシステムは、本連載の以前の記事で紹介した2つのカスタムインジケータに依存しています。これらのインジケータは、戦略を動作させるために必要なシグナルと相場環境を提供します。

最初のコンポーネントはSupertrendインジケータです。このインジケータはオプションのトレンドフィルターとして機能し、EAが選択されたトレンド条件と市場方向が一致している場合のみ取引を開始できるようにします。このインジケータの構築方法については、以前の記事で詳しく説明されています。インジケータ自体がどのように開発されたかを理解したい読者は、その記事を参照することで内部ロジックの詳細を確認できます。

プロジェクトで使用するインジケータのソースファイルは、本記事にsupertrend.mq5として添付されています。このインジケータは、2つの方法で使用準備できます。

1つ目の方法は、提供されたソースファイルをダウンロードし、メインのMQL5フォルダ内にあるIndicatorsディレクトリへ配置する方法です。手順は以下の通りです。

  1. MetaTrader 5プラットフォームを開きます。
  2. メインメニューから、[データフォルダを開く]オプションを選択します。
  3. 「MQL5」という名前のディレクトリに移動します。
  4. 「Indicators」という名前のフォルダを開きます。
  5. supertrend.mq5ファイルをこのディレクトリにコピーします。
  6. MetaEditorの開発環境に戻り、インジケータをコンパイルします。

コンパイルが完了すると、このインジケータはEAを含む他のプログラムから利用できるようになります。

2つ目の方法は、インジケータのソースファイルを手動で作成する方法です。この場合、Indicatorsディレクトリ内にsupertrend.mq5という名前の新しいインジケータファイルを作成します。その後、添付されたファイルのソースコードを新しいファイルへコピーして貼り付け、MetaEditor内でコンパイルします。

同じ準備手順は、前回の記事で開発した「隠れスマッシュデー」インジケータにも適用されます。ソースファイルのlwHiddenSmashDayIndicator.mq5も本記事に添付されています。このファイルもIndicatorsディレクトリへ配置し、同じ方法でコンパイルする必要があります。

本記事では、両方のインジケータがルートMQL5フォルダ内のIndicatorsディレクトリに配置されていることを前提としています。コンパイル後、これらのインジケータはインジケータハンドルを通じてEAから直接アクセスできます。

この先の実装手順を効果的に進めるためには、いくつかの前提条件を満たしている必要があります。まず、MQL5プログラミング言語の構文と基本構造について理解している必要があります。変数、関数、条件分岐、ループ、列挙型、標準ライブラリなどの概念は、すでに理解していることが望まれます。これらの内容に馴染みがない場合は、公式MQL5ドキュメントが言語を学ぶための信頼できる出発点になります。

次に、MetaTrader 5取引プラットフォームの使用経験も重要です。チャートを開く、プログラムをチャートへ適用する、ストラテジーテスターを起動するといったプラットフォーム操作に慣れている必要があります。これらの操作は、アルゴリズムトレードシステムを開発する際の通常のワークフローの一部です。

さらに、MetaEditorの開発環境にも慣れている必要があります。この環境は、ソースファイルの作成、コードの記述、プログラムのコンパイル、開発中に発生する可能性のあるエラー確認に使用されます。

実装を進める際には、チュートリアルと並行して実際にコードを書くことを強く推奨します。プログラミングの概念は、受動的に見るよりも、実際に適用することでより明確に理解できます。このプロセスを支援するため、完成版EAのソースコードは、本記事にlwHiddenSmashDayContextExpert.mq5として添付されています。このファイルを別のエディタータブで開いた状態にしておくことで、システムを段階的に構築する際の参考資料として利用できます。

環境の準備が完了したら、開発プロセスを開始できます。MetaEditor 5を開き、新しいEAのソースファイルを作成します。ファイル名は自由に設定できますが、この記事で開発するバージョンではlwHiddenSmashDayContextExpert.mq5という名前を使用します。空のファイルを作成した後、以下のコードがシステム全体の基盤となります。

//+------------------------------------------------------------------+
//|                                lwHiddenSmashDayContextExpert.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"
#resource "\\Indicators\\lwHiddenSmashDayIndicator.ex5"
#resource "\\Indicators\\supertrend.ex5"

//+------------------------------------------------------------------+
//| Custom Enumerations                                              |
//+------------------------------------------------------------------+
enum ENUM_TDW_MODE
{
   TDW_ALL_DAYS,     
   TDW_SELECTED_DAYS
};

enum ENUM_SMASH_TRADE_MODE
{
   SMASH_TRADE_BUY_ONLY,
   SMASH_TRADE_SELL_ONLY,
   SMASH_TRADE_BOTH
};

enum ENUM_STOP_LOSS_MODE
{
   SL_ATR_BASED,            
   SL_SMASH_BAR_STRUCTURE
};

enum ENUM_LOT_SIZE_INPUT_MODE 
{ 
   MODE_MANUAL, 
   MODE_AUTO 
};

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

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

input group "Supertrend configuration parameters"
input bool useSupertrendFilter            = false;
input ENUM_TIMEFRAMES supertrendTimeframe = PERIOD_CURRENT;
input int32_t supertrendAtrPeriod         = 10;
input double  supertrendAtrMultiplier     = 1.5;

input group "TDW filters"
input ENUM_TDW_MODE tradeDayMode = TDW_SELECTED_DAYS;
input bool tradeSunday           = false;
input bool tradeMonday           = true;
input bool tradeTuesday          = false;
input bool tradeWednesday        = false;
input bool tradeThursday         = false;
input bool tradeFriday           = false;
input bool tradeSaturday         = false;

input group "Trade and Risk Management"
input ENUM_STOP_LOSS_MODE smashStopLossMode = SL_SMASH_BAR_STRUCTURE;
input int32_t atrPeriod                     = 14;
input double  atrMultiplier                 = 2.0;
input ENUM_SMASH_TRADE_MODE smashTradeMode  = SMASH_TRADE_BOTH;
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 help track current market prices for Buying (Ask) and Selling (Bid)
double askPrice;
double bidPrice;

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

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

//--- Hidden Smash bar indicator handle and values
int hiddenSmashDayIndicatorHandle;
double buySmashArrowBuffer[];
double sellSmashArrowBuffer[];

//--- Supertrend indicator handle and values 
int    supertrendIndicatorHandle;
double upperBandValues[];
double lowerBandValues[];

//--- ATR Values
int atrHandle;
double atrValues [];

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

   //---  Assign a unique magic number to identify trades opened by this EA
   Trade.SetExpertMagicNumber(magicNumber);
   
   //--- Initialize global variables
   lastBarOpenTime = 0;
   
   //--- Initialize the hidden smash day indicator
   hiddenSmashDayIndicatorHandle    = iCustom(_Symbol, timeframe, "::Indicators\\lwHiddenSmashDayIndicator.ex5");
   if(hiddenSmashDayIndicatorHandle == INVALID_HANDLE){
      Print("Error while initializing The Hidden Smash Day Indicator: ", GetLastError());
      return(INIT_FAILED);
   }
   
   // Initialize the Supertrend Indicator
   supertrendIndicatorHandle = iCustom(_Symbol, supertrendTimeframe, "::Indicators\\supertrend.ex5", supertrendAtrPeriod, supertrendAtrMultiplier);
   if(supertrendIndicatorHandle == INVALID_HANDLE){
      Print("Error while initializing the Supertrend indicator: ", GetLastError());
      return(INIT_FAILED);
   }
   
   //--- Initialize the ATR indicator
   atrHandle = iATR(_Symbol, timeframe, atrPeriod);
   if(atrHandle == INVALID_HANDLE){
      Print("Error while initializing the ATR indicator ", GetLastError());
      return(INIT_FAILED);
   }
   
   //--- Set arrays as series
   ArraySetAsSeries(buySmashArrowBuffer, true);
   ArraySetAsSeries(sellSmashArrowBuffer, true);
   ArraySetAsSeries(upperBandValues, true);
   ArraySetAsSeries(lowerBandValues, true);
   ArraySetAsSeries(atrValues, true);

   return(INIT_SUCCEEDED);
}

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

   //--- Release Supertrend
   if(hiddenSmashDayIndicatorHandle != INVALID_HANDLE){
      IndicatorRelease(hiddenSmashDayIndicatorHandle);
   }

   //--- Release Supertrend
   if(supertrendIndicatorHandle != INVALID_HANDLE){
      IndicatorRelease(supertrendIndicatorHandle);
   }
   
   //--- Release ATR
   if(atrHandle != INVALID_HANDLE){
      if(IndicatorRelease(atrHandle)){
         Print("The computer memory tracking ATR has been freed.");
      }
   }

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

この初期コードは、EAの構造的なフレームワークを構築します。プログラムのプロパティを定義し、設定パラメータを宣言し、グローバル変数を初期化するとともに、初期化、実行、終了処理を制御する基本的なプログラム関数を実装します。

ファイル冒頭にあるプロパティ宣言では、プログラムのメタデータ情報を定義します。

//+------------------------------------------------------------------+
//|                                lwHiddenSmashDayContextExpert.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"
#resource "\\Indicators\\lwHiddenSmashDayIndicator.ex5"
#resource "\\Indicators\\supertrend.ex5"

これらの値は、作成者を識別し、参照リンクを提供し、EAのバージョンを指定します。また、このセクションにはリソース宣言も含まれています。これらを使用することで、コンパイル済みのインジケータファイルをEAと一緒にパッケージ化し、実行時に直接アクセスできるようになります。

プロパティ定義の後には、いくつかのカスタム列挙型が宣言されます。

//+------------------------------------------------------------------+
//| Custom Enumerations                                              |
//+------------------------------------------------------------------+
enum ENUM_TDW_MODE
{
   TDW_ALL_DAYS,     
   TDW_SELECTED_DAYS
};

enum ENUM_SMASH_TRADE_MODE
{
   SMASH_TRADE_BUY_ONLY,
   SMASH_TRADE_SELL_ONLY,
   SMASH_TRADE_BOTH
};

enum ENUM_STOP_LOSS_MODE
{
   SL_ATR_BASED,            
   SL_SMASH_BAR_STRUCTURE
};

enum ENUM_LOT_SIZE_INPUT_MODE 
{ 
   MODE_MANUAL, 
   MODE_AUTO 
};

これらの列挙型は、戦略の動作方法を制御する選択可能なモードを定義します。列挙型では、利用可能な取引曜日フィルタ、取引方向の設定、ストップロス計算方法、ポジションサイズモードが定義されています。列挙型を使用することで、コードの可読性が向上し、設定オプションが明確に定義された状態で管理できます。

次のセクションでは、標準取引ライブラリがインクルードされます。

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

このライブラリはCTradeクラスを提供し、取引サーバーへ注文リクエストを送信する処理を簡略化します。低レベルの注文構造を手動で構築する代わりに、CTradeオブジェクトを使用することで、シンプルなメソッド呼び出しによって注文を発注できます。

ライブラリのインクルード後、ユーザー入力パラメータが宣言されま

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

input group "Supertrend configuration parameters"
input bool useSupertrendFilter            = false;
input ENUM_TIMEFRAMES supertrendTimeframe = PERIOD_CURRENT;
input int32_t supertrendAtrPeriod         = 10;
input double  supertrendAtrMultiplier     = 1.5;

input group "TDW filters"
input ENUM_TDW_MODE tradeDayMode = TDW_SELECTED_DAYS;
input bool tradeSunday           = false;
input bool tradeMonday           = true;
input bool tradeTuesday          = false;
input bool tradeWednesday        = false;
input bool tradeThursday         = false;
input bool tradeFriday           = false;
input bool tradeSaturday         = false;

input group "Trade and Risk Management"
input ENUM_STOP_LOSS_MODE smashStopLossMode = SL_SMASH_BAR_STRUCTURE;
input int32_t atrPeriod                     = 14;
input double  atrMultiplier                 = 2.0;
input ENUM_SMASH_TRADE_MODE smashTradeMode  = SMASH_TRADE_BOTH;
input ENUM_LOT_SIZE_INPUT_MODE lotSizeMode  = MODE_AUTO;
input double riskPerTradePercent            = 1.0;
input double positionSize                   = 0.1;
input double riskRewardRatio                = 3.0;

これらのパラメータにより、戦略の重要な要素を取引プラットフォーム上から直接設定できます。パラメータでは、使用する時間足、Supertrendの設定、取引曜日の制限、リスク管理の構成を定義します。

入力パラメータの後には、グローバル変数が宣言されます。

//+------------------------------------------------------------------+
//| 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;

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

//--- Hidden Smash bar indicator handle and values
int hiddenSmashDayIndicatorHandle;
double buySmashArrowBuffer[];
double sellSmashArrowBuffer[];

//--- Supertrend indicator handle and values 
int    supertrendIndicatorHandle;
double upperBandValues[];
double lowerBandValues[];

//--- ATR Values
int atrHandle;
double atrValues [];

これらの変数は、プログラム全体を通じてアクセス可能である必要がある値を保持します。例としては、現在の市場価格、インジケータハンドル、そして後にインジケータデータを格納するために使用される配列などがあります。これらの変数は、EAの異なる部分間で情報を受け渡すために必要な保存領域を提供します。

次に初期化関数が続きます。

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

   //---  Assign a unique magic number to identify trades opened by this EA
   Trade.SetExpertMagicNumber(magicNumber);
   
   //--- Initialize global variables
   lastBarOpenTime = 0;
   
   //--- Initialize the hidden smash day indicator
   hiddenSmashDayIndicatorHandle    = iCustom(_Symbol, timeframe, "::Indicators\\lwHiddenSmashDayIndicator.ex5");
   if(hiddenSmashDayIndicatorHandle == INVALID_HANDLE){
      Print("Error while initializing The Hidden Smash Day Indicator: ", GetLastError());
      return(INIT_FAILED);
   }
   
   // Initialize the Supertrend Indicator
   supertrendIndicatorHandle = iCustom(_Symbol, supertrendTimeframe, "::Indicators\\supertrend.ex5", supertrendAtrPeriod, supertrendAtrMultiplier);
   if(supertrendIndicatorHandle == INVALID_HANDLE){
      Print("Error while initializing the Supertrend indicator", GetLastError());
      return(INIT_FAILED);
   }
   
   //--- Initialize the ATR indicator
   atrHandle = iATR(_Symbol, timeframe, atrPeriod);
   if(atrHandle == INVALID_HANDLE){
      Print("Error while initializing the ATR indicator ", GetLastError());
      return(INIT_FAILED);
   }
   
   //--- Set arrays as series
   ArraySetAsSeries(buySmashArrowBuffer, true);
   ArraySetAsSeries(sellSmashArrowBuffer, true);
   ArraySetAsSeries(upperBandValues, true);
   ArraySetAsSeries(lowerBandValues, true);
   ArraySetAsSeries(atrValues, true);

   return(INIT_SUCCEEDED);
}

この関数は、プログラムが最初にチャートへ適用された際に、動作に必要な準備をおこないます。初期化処理では、EAに固有のマジックナンバーを設定し、インジケータハンドルを作成し、インジケータ値を格納するための配列を準備します。これらの初期化処理のいずれかが失敗した場合、誤った実行を防ぐためにプログラムは早期に終了します。

次に、プログラムの動作停止時にリソースを解放する初期化解除関数が続きます。

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

   //--- Release Supertrend
   if(hiddenSmashDayIndicatorHandle != INVALID_HANDLE){
      IndicatorRelease(hiddenSmashDayIndicatorHandle);
   }

   //--- Release Supertrend
   if(supertrendIndicatorHandle != INVALID_HANDLE){
      IndicatorRelease(supertrendIndicatorHandle);
   }
   
   //--- Release ATR
   if(atrHandle != INVALID_HANDLE){
      if(IndicatorRelease(atrHandle)){
         Print("The computer memory tracking ATR has been freed.");
      }
   }

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

この関数は、市場で新しい価格更新が発生するたびに実行されます。この初期段階では、関数は現在のAsk価格とBid価格を取得し、現在時刻を記録します。これらの値は、後にトレードロジックが実装された際に使用されます。

この時点で、EAの構造的な基盤は完成しています。プログラムには、次のセクションで開発されるトレードロジックを支えるために必要な基本的なフレームワークが備わっています。


シグナル検出、コンテキストフィルタ、取引実行の実装

EAの構造的な基盤がすでに整ったため、次の段階ではトレードシステムを動作させるロジックを実装します。この段階では、取引シグナルの検出、相場環境の評価、リスクパラメータの計算、そして取引の実行を連携しておこなう複数の小さな関数を導入します。

各関数は、特定の1つの処理を担当するように設計されています。このモジュール化されたアプローチにより、コードの明確性が向上し、戦略の各構成要素を理解および保守しやすくなります。まず、新しいバーの検出から始めます。

新しいバーの検出

この戦略では、バーが完全に確定した後にのみ取引条件を評価します。これにより、シグナルが途中まで形成されたローソク足ではなく、確定済みの市場データに基づいて生成されることが保証されます。このイベントを検出するために、以下のヘルパー関数を定義します。

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

この関数は、最新バーの開始時刻を取得します。この時刻が以前記録された値と異なる場合、新しいバーが形成されたことを意味します。その後、保存されている時刻を更新し、trueを返します。このシンプルな仕組みにより、プログラムは1つのバーにつき1回だけシグナル評価を実行します。

「隠れスマッシュデー」インジケータデータの更新

本連載で以前に開発した「隠れスマッシュデー」インジケータは、基となる価格パターンを識別します。EAは、インジケータバッファを読み取り、パターンが存在するかどうかを判断します。これらの値を格納するために、2つの配列が使用されます。1つは強気シグナルを保存し、もう1つは弱気シグナルを保存します。以下の関数は、これらの配列を最新のインジケータ値で更新します。

//+------------------------------------------------------------------+
//| Updates the buySmashArrowBuffer with latest buy arrow values     |
//+------------------------------------------------------------------+
void UpdateBuySmashArrowBuffer()
{
   int copied = CopyBuffer(hiddenSmashDayIndicatorHandle,
                           0,          // Buy buffer index
                           0,          // Start from most recent bar
                           5,          // Number of values to copy
                           buySmashArrowBuffer);

   if(copied <= 0)
   {
      Print("Failed to copy buy smash arrow buffer. Error: ", GetLastError());
   }
}

この関数は、強気の「隠れスマッシュ」パターンを示すインジケータバッファから、最新の値をコピーします。

//+------------------------------------------------------------------+
//| Updates the sellSmashArrowBuffer with latest sell arrow values   |
//+------------------------------------------------------------------+
void UpdateSellSmashArrowBuffer()
{
   int copied = CopyBuffer(hiddenSmashDayIndicatorHandle,
                           1,          // Sell buffer index
                           0,          // Start from most recent bar
                           5,          // Number of values to copy
                           sellSmashArrowBuffer);

   if(copied <= 0)
   {
      Print("Failed to copy sell smash arrow buffer. Error: ", GetLastError());
   }
}

この2つ目の関数は、弱気シグナルに対して同じ処理を実行します。これらの関数は、新しいバーが形成されるたびに実行されます。

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

   ...
   
   if(IsNewBar(_Symbol, timeframe, lastBarOpenTime)){
      UpdateBuySmashArrowBuffer ();
      UpdateSellSmashArrowBuffer();
   }
}

これにより、シグナル評価が開始される前に、配列が常に最新のデータを保持していることが保証されます。

有効な「隠れスマッシュ」シグナルの検出

インジケータバッファを更新した後、プログラムは取引シグナルが存在するかどうかを判断できます。この処理は2つの関数によって実行されます。以下が1つ目の関数です。

//+------------------------------------------------------------------+
//| Returns true if a bullish Hidden Smash signal exists at index    |
//+------------------------------------------------------------------+
bool IsBullishHiddenSmashSignal(int index)
{
   if(index < 0)
      return false;

   if(buySmashArrowBuffer[index] != EMPTY_VALUE)
      return true;

   return false;
}

この関数は、指定されたインデックスの買いバッファを確認します。値が空でない場合、インジケータは強気の「隠れスマッシュ」パターンを識別したことを意味します。以下が2つ目の関数です。

//+------------------------------------------------------------------+
//| Returns true if a bearish Hidden Smash signal exists at index    |
//+------------------------------------------------------------------+
bool IsBearishHiddenSmashSignal(int index)
{
   if(index < 0)
      return false;

   if(sellSmashArrowBuffer[index] != EMPTY_VALUE)
      return true;

   return false;
}

この関数は弱気シグナルに対して同じ評価処理を実行します。これらの関数により、EAは有効なパターンが存在する場合のみ反応できるようになります。

Supertrendコンテキストデータの更新

この戦略では、Supertrendインジケータによって提供されるトレンド方向に基づいて、取引をオプションでフィルタリングできます。この情報を利用するために、まずプログラムはインジケータの値を取得する必要があります。

//+------------------------------------------------------------------+
//| Fetches recent Supertrend upper and lower band values            |
//+------------------------------------------------------------------+
void UpdateSupertrendBandValues(){

   //--- Get a few Supertrend upper band values
   int copiedUpper = CopyBuffer(supertrendIndicatorHandle, 5, 0, 5, upperBandValues);
   if(copiedUpper == -1)
   {
      Print("Error while copying Supertrend upper band values: ", GetLastError());
      return;
   }

   //--- Get a few Supertrend lower band values
   int copiedLower = CopyBuffer(supertrendIndicatorHandle, 6, 0, 5, lowerBandValues);
   if(copiedLower == -1)
   {
      Print("Error while copying Supertrend lower band values: ", GetLastError());
      return;
   }
   
   if(copiedUpper < 5 || copiedLower < 5){
      Print("Insufficient Supertrend indicator data!");
      return;
   }   
}

この関数は、最新のSupertrendバンド値を取得し、後の分析で使用するために配列へ保存します。この関数も、新しいバーが形成されるたびに呼び出されます。

現在のトレンド状態の判定

Supertrendインジケータは、2つの状態のみを持ちます。すなわち、強気状態または弱気状態です。2つの小さな関数によって、現在のトレンド方向を判定します。

//+------------------------------------------------------------------+
//| Returns true if Supertrend is currently in a bullish trend state |
//+------------------------------------------------------------------+
bool IsSupertrendCurrentlyBullish(){

   if(lowerBandValues[1] != EMPTY_VALUE){
      return true;
   }

   return false;
}

この関数は、インジケータが強気な状況を示している場合にtrueを返します。

//+------------------------------------------------------------------+
//| Returns true if Supertrend is currently in a bearish trend state |
//+------------------------------------------------------------------+
bool IsSupertrendCurrentlyBearish(){

   if(upperBandValues[1] != EMPTY_VALUE){
      return true;
   }

   return false;
}

この関数は、弱気条件が存在する場合にtrueを返します。これらの関数により、フィルタが有効になっている場合、EAは現在のトレンド方向に沿った取引のみを実行できるようになります。

ボラティリティベースのストップ用ATR値の更新

この戦略では、ATRインジケータを使用するボラティリティベースのストップロスモードに対応しています。ATR値を取得するために、以下の関数を定義します。

//+------------------------------------------------------------------+
//| To update ATR values                                             |
//+------------------------------------------------------------------+
void UpdateATRValues(){
   
   //--- Get some ATR Values
   int numberOfCopiedATRValues = CopyBuffer(atrHandle, 0, 0, 5, atrValues);
   if(numberOfCopiedATRValues == -1){
      Print("Error while copying ATR values: ", GetLastError());
   }
}

この関数はATR配列を更新し、プログラムが後で現在の市場ボラティリティに基づいてストップロスレベルを計算できるようにします。

取引曜日フィルタの実装

一部のトレーダーは、特定の曜日に取引を制限することを好みます。EAには、この目的のために設定可能なフィルターが含まれています。以下のヘルパー関数は、曜日を判定します。

//+------------------------------------------------------------------+
//| Returns the day of the week (0 = Sunday, 6 = Saturday) for the given datetime value|                               
//+------------------------------------------------------------------+
int TimeDayOfWeek(datetime time){
   MqlDateTime timeStruct = {};
   if(!TimeToStruct(time, timeStruct)){
      Print("TimeDayOfWeek: TimeToStruct failed");
      return -1;
   }      
   return timeStruct.day_of_week;
}

この値を使用することで、プログラムは取引が許可されているかどうかを確認できます。

//+------------------------------------------------------------------+
//| Determines whether trading is permitted for the given datetime based on the selected trade-day mode |                               
//+------------------------------------------------------------------+
bool IsTradingDayAllowed(datetime time)
{
   // Baseline mode: no filtering
   if(tradeDayMode == TDW_ALL_DAYS){
      return true;
   }

   int day = TimeDayOfWeek(time);

   switch(day)
   {
      case 0: return tradeSunday;
      case 1: return tradeMonday;
      case 2: return tradeTuesday;
      case 3: return tradeWednesday;
      case 4: return tradeThursday;
      case 5: return tradeFriday;
      case 6: return tradeSaturday;
   }

   return false;
}

この関数はユーザー設定を読み込み、当日の取引が許可されているかどうかを判断します。

既存ポジションの確認

このシステムでは、同時に1つのオープンポジションのみを許可します。取引を開始する前に、プログラムは他のポジションが存在しないことを確認する必要があります。以下の関数は、アクティブな買いポジションを確認します。

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

これらの関数により、戦略の単一取引ルールが適用されます。

リスクベースのポジションサイズ計算

リスク管理は、動的なポジションサイズ調整によってオプションで実装されています。

//+------------------------------------------------------------------+
//| Calculates position size using OrderCalcProfit for accuracy      |
//+------------------------------------------------------------------+
double CalculatePositionSizeByRisk(ENUM_ORDER_TYPE orderType, double entryPrice, double stopLossPrice){

   //--- Amount willing to risk
   double amountAtRisk = (riskPerTradePercent / 100.0) *
                         AccountInfoDouble(ACCOUNT_BALANCE);

   //--- Calculate loss for 1 lot
   double lossPerLot = 0.0;

   if(!OrderCalcProfit(orderType,
                       _Symbol,
                       1.0,              // 1 lot
                       entryPrice,
                       stopLossPrice,
                       lossPerLot))
   {
      Print("OrderCalcProfit failed: ", GetLastError());
      return 0.0;
   }

   // Loss will be negative for losing scenario
   lossPerLot = MathAbs(lossPerLot);

   if(lossPerLot <= 0.0)
      return 0.0;

   //--- Raw volume
   double volume = amountAtRisk / lossPerLot;

   //--- Apply broker constraints
   double minLot   = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot   = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double lotStep  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

   // Normalize to step
   volume = MathFloor(volume / lotStep) * lotStep;

   if(volume < minLot)
      volume = minLot;

   if(volume > maxLot)
      volume = maxLot;

   return NormalizeDouble(volume, 2);
}

この関数は、口座残高に対して設定された割合のみをリスクとして許容する場合に、取引可能な最大ボリュームを決定します。OrderCalcProfit関数を使用して1ロットあたりの想定損失額を推定し、その後、適切なボリュームになるように調整します。計算結果は、ブローカーが設定している最小および最大ロット制限に合わせて補正されます。

ストップロスとテイクプロフィットレベルの計算

2つの関数が、選択されたストップロスモードに基づいてストップロスレベルを計算します。1つのモードでは、「隠れスマッシュ」バーの極値にストップを配置します。もう1つのモードでは、ATRによるボラティリティを基準としてストップを配置します。

//+--------------------------------------------------------------------------+
//| Computes the bullish stop loss level based on the index of the smash bar |
//+--------------------------------------------------------------------------+
double GetBuyStopLoss(int index){

   if(smashStopLossMode == SL_ATR_BASED){
      double atrValue = atrValues[1] * atrMultiplier;
      double slLevel  = askPrice - atrValue;
      return NormalizeDouble(slLevel, Digits());
   }else{
      return NormalizeDouble(iLow(_Symbol, timeframe, index), Digits());
   }   
}

この関数は、買いポジションのストップロスレベルを返します。

//+--------------------------------------------------------------------------+
//| Computes the bearish stop loss level based on the index of the smash bar |
//+--------------------------------------------------------------------------+
double GetSellStopLoss(int index){

   if(smashStopLossMode == SL_ATR_BASED){
      double atrValue = atrValues[1] * atrMultiplier;
      double slLevel  = bidPrice + atrValue;
      return NormalizeDouble(slLevel, Digits());
   }else{
      return NormalizeDouble(iHigh(_Symbol, timeframe, index), Digits());
   }   
}

この関数は、売り取引に対して同様の計算をおこないます。

テイクプロフィットレベルは、定義されたリスク距離から予測されます。

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

の関数は、設定されたリスクリワード比を適用します。

取引注文の送信

すべての条件が満たされると、EAは注文を取引サーバーに送信します。

//+------------------------------------------------------------------+
//| Function to open a market buy position                           |
//+------------------------------------------------------------------+
bool OpenBuy(double entryPrice, double stopLoss, double takeProfit, double lotSize){
   
   if(lotSizeMode == MODE_AUTO){
      lotSize = CalculatePositionSizeByRisk(ORDER_TYPE_BUY, 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 OpenSell(double entryPrice, double stopLoss, double takeProfit, double lotSize){
   
   if(lotSizeMode == MODE_AUTO){
      lotSize = CalculatePositionSizeByRisk(ORDER_TYPE_SELL, entryPrice, stopLoss);
   }
   
   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;
}

この関数は、成行売りポジションを建てます。両方の関数では、自動モードが有効になっている場合、ポジションサイズの自動計算も実行されます。

トレードロジックの統合

すべてのヘルパー関数が実装されたため、最後のステップとして、OnTick関数内に完全なトレードロジックを組み込みます。

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

   ...
   
   if(IsNewBar(_Symbol, timeframe, lastBarOpenTime)){
      
      ...
      
      double stopLossLevel   = 0.000000;
      double takeProfitLevel = 0.000000;
      
      //--- Act on a bullish signal
      if(IsBullishHiddenSmashSignal(2)){
      
         stopLossLevel   = GetBuyStopLoss(2);
         takeProfitLevel = GetBuyTakeProfit(askPrice, stopLossLevel);
         if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){
            if(smashTradeMode == SMASH_TRADE_BUY_ONLY || smashTradeMode == SMASH_TRADE_BOTH){
               if(useSupertrendFilter){
                  if(IsSupertrendCurrentlyBullish()){
                     //---
                     if(tradeDayMode == TDW_SELECTED_DAYS){
                        if(IsTradingDayAllowed(currentTime)){
                           OpenBuy(askPrice, stopLossLevel, takeProfitLevel, positionSize);
                        }
                     }else{
                        OpenBuy(askPrice, stopLossLevel, takeProfitLevel, positionSize);
                     }
                  }
               }else{
                  //---
                  if(tradeDayMode == TDW_SELECTED_DAYS){
                     if(IsTradingDayAllowed(currentTime)){
                        OpenBuy(askPrice, stopLossLevel, takeProfitLevel, positionSize);
                     }
                  }else{
                     OpenBuy(askPrice, stopLossLevel, takeProfitLevel, positionSize);
                  }
               }
            }
         }
      }
      
      //--- Act on a bearish signal
      if(IsBearishHiddenSmashSignal(2)){
         stopLossLevel   = GetSellStopLoss(2);
         takeProfitLevel = GetSellTakeProfit(bidPrice, stopLossLevel);
         if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){
            if(smashTradeMode == SMASH_TRADE_SELL_ONLY || smashTradeMode == SMASH_TRADE_BOTH){
               if(useSupertrendFilter){
                  if(IsSupertrendCurrentlyBearish()){
                     //---
                     if(tradeDayMode == TDW_SELECTED_DAYS){
                        if(IsTradingDayAllowed(currentTime)){
                           OpenSell(bidPrice, stopLossLevel, takeProfitLevel, positionSize);
                        }
                     }else{
                        OpenSell(bidPrice, stopLossLevel, takeProfitLevel, positionSize);
                     }
                  }
               }else{
                  //---
                  if(tradeDayMode == TDW_SELECTED_DAYS){
                     if(IsTradingDayAllowed(currentTime)){
                        OpenSell(bidPrice, stopLossLevel, takeProfitLevel, positionSize);
                     }
                  }else{
                     OpenSell(bidPrice, stopLossLevel, takeProfitLevel, positionSize);
                  }
               }
            }
         }
      }
   }
}

この関数は、新しいバーが形成された後にシグナルを評価し、すべてのコンテキストフィルターを確認し、リスクパラメータを計算したうえで、すべての条件が満たされた場合のみ取引を実行します。

この構造により、シグナルが検証され、リスクが計算され、取引が一貫性のある管理された方法で実行されることが保証されます。

テスト時のチャート表示の改善

戦略テストへ進む前に、視覚的な明確さを向上させるため、チャート表示を設定しておくことが有用です。以下の関数は、チャートの色や表示設定を調整します。

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

この関数は、グリッド線を削除し、明確なローソク足カラーを設定することで、テスト中にシグナルや値動きをより簡単に観察できるようにします。

この関数は、初期化処理の段階で呼び出されます。

//+------------------------------------------------------------------+
//| 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のコンパイル

この段階で、EAの主要な実装は完成しています。これで、MetaEditor上でソースコードをコンパイルできます。開発手順に従って正しく作成されていれば、プログラムはエラーなしでコンパイルされるはずです。

コンパイル時に問題が発生した場合は、添付のソースファイルlwHiddenSmashDayContextExpert.mq5を参照用として使用し、実装内容を確認できます。

この記事の次の段階では、開発作業から戦略テストへ移り、トレードシステムの評価をおこないます。 


「隠れスマッシュ」コンテキスト戦略のテスト

長期的なパフォーマンスを評価する前に、まずEAが、この記事の前半で説明した「隠れスマッシュ」パターンに従って取引を実行することを確認します。

そのため、最初に2つのスクリーンショットを提示します。1つはアクティブな買いポジションの例です。

ロングポジション

もう1つのスクリーンショットは、アクティブな売りポジションの例です。

ショートポジション

これらの例は、システムが「隠れスマッシュ」セットアップを正しく検出し、有効な確認バーが出現した後にのみ取引を実行することを示しています。

買い取引の例では、スマッシュバーはレンジの下側で終値を形成し、同時に前のバーの終値より上で引けています。その次のバーはスマッシュバーの高値を上回って終値を形成し、これによって有効な買いシグナルが発生します。次のバーの始値で、EAは定義されたルールに従って買いポジションをオープンします。

売り取引の例では、スマッシュバーはレンジの上側付近で終値を形成し、同時に前のバーの終値より下で引けています。次のバーはスマッシュバーの安値を下回って終値を形成し、確認済みの売りシグナルが発生します。その後、EAは次のバーの始値で売りポジションを建てます。

これらの例により、先に実装したシグナル検出ロジックが意図した通りに動作することが確認できます。この検証が完了したため、次のステップでは、過去のデータに適用した場合にシステムがどのようなパフォーマンスを示すかを検証します。

実験1:「隠れスマッシュ」シグナルをすべて取引する場合

最初の実験では、トレンドフィルタを使用しない状態で戦略を評価します。この設定では、EAは有効なすべての「隠れスマッシュデー」パターンを取引することが許可されます。以下の環境設定を使用しました。

時間足:

日次(D1)

テスト期間: 

2025年1月1日から2026年2月28日まで

買いおよび売りの両方のスマッシュセットアップを有効にし、システムがすべての有効なシグナルを取得できるようにしました。ポジションサイズはAUTO_MODE設定を使用して自動的に決定され、1回の取引あたりのリスクは口座残高の1%に制限されました。

これらの結果を再現できるように、この記事には2つのファイルが添付されています。

  • configurations_exp1.iniには、ストラテジーテスター環境の設定が含まれています。
  • parameters_exp1.setは、このテストで使用したEAの正確な入力パラメータを含みます。

実験は、口座残高10,000ドルから開始しました。テスト期間終了時点で、戦略は合計1,058.18ドルの純利益を生み出しました。これは、投資収益率で10%をわずかに上回る結果です。

テスターレポート

テスト期間中の勝率は100%でした。付随するエクイティカーブは、大きなドローダウンを伴うことなく、滑らかな上昇推移を示しています。

Exp1のエクイティカーブ

これは、選択されたテスト期間において、「隠れスマッシュデー」パターンがゴールド市場で一連の明確なシグナルを生成したことを示しています。

実験2:Supertrendによる相場環境の追加

2つ目の実験では、Supertrendフィルタによって相場環境を追加した状態で、同じ戦略を評価します。

この設定では、EAは、週足で計算されたSupertrendインジケータの方向とシグナルが一致する場合のみ取引をおこないます。これにより、意思決定プロセスに上位時間足のトレンド条件が追加されます。

テスト環境は、最初の実験と同一です。

時間足

日次(D1)

テスト期間

2025年1月1日から2026年2月28日まで

買いおよび売りのスマッシュセットアップは引き続き有効にし、ポジションサイズには1回の取引あたり1%のリスクを設定したAUTO_MODEを継続して使用しました。この実験を再現するために必要なファイルも添付されています。

  • configurations_exp2.iniには、ストラテジーテスター環境の設定が含まれています。
  • parameters_exp2.setは、EAのパラメータ設定を保存しています。

再び10,000ドルの口座残高から開始した結果、この戦略はテスト期間中に合計1,862.62ドルの純利益を生み出しました。

Exp2のテスターレポート

これは、投資収益率で18%をわずかに上回る結果です。この実験でも勝率は100%に達しました。

実験結果から得られる考察

これら2つのテストから、いくつかの考察を導くことができます。

最初の実験は、「隠れスマッシュデー」反転が、管理されたリスク管理のもとで機械的に実行された場合でも、利益を生み出す可能性があることを示しています。

2つ目の実験では、上位足のトレンドフィルタによって相場環境を追加することで、結果に大きな影響を与えられることが示されました。このケースでは、Supertrendフィルタによって不要な取引が削減され、全体的なパフォーマンスが向上しました。

ただし、これらの結果は、この研究で使用した特定の市場およびテスト期間だけを反映したものであることを覚えておく必要があります。異なる金融商品、データフィード、または期間では、異なる結果になる可能性があります。

システムの制限

このEAは、「隠れスマッシュ」コンセプトを明確に実装していますが、意図的にシンプルな設計に保たれています。現在の設計には、いくつかの機能が含まれていません。

  • 時間帯による取引フィルタ
  • スプレッド制御または約定フィルタ
  • ニュースイベント保護

これらの要素は、自動売買システムの動作に影響を与える可能性があり、今後の研究で検討できる項目です。

本記事で開発したEAは、柔軟な入力パラメータを持つよう意図的に設計されています。ストップロスモード、取引方向、トレンドフィルター、リスク管理設定はすべて、入力パネルから簡単に調整できます。この柔軟性により、このシステムは固定された取引ソリューションではなく、研究用フレームワークとして利用できます。

異なる市場、時間足、パラメータの組み合わせでさらに実験することで、「隠れスマッシュデー」コンセプトについて、さらなる洞察が得られる可能性があります。

読者には、自身でテストを実施し、その結果を記事のコメント欄で共有することを推奨します。コミュニティからのフィードバックは、研究プロセス全体に役立つ新しいアイデアや改善点を発見する助けとなることがよくあります。



結論

本記事では、「隠れスマッシュデー」反転のコンセプトを、自動的に実行可能な体系化されたトレードシステムへ変換することに焦点を当てました。

本連載で以前に開発されたインジケータを基礎として、「隠れスマッシュ」シグナルを読み取り、市場状況を評価し、リスクパラメータを計算し、明確に定義されたルールに従って取引を実行する完全なEAを構築しました。

開発過程では、以下の実用的な要素を実装しました。

  • 確認済み「隠れスマッシュデー」シグナルの検出
  • Supertrendインジケータを使用したオプションのトレンドフィルタ
  • 取引可能なタイミングを制御する取引曜日フィルタ
  • 構造またはボラティリティに基づく2種類のストップロスモード
  • 口座リスクに基づく自動ポジションサイズ計算
  • 重複した取引を防止する執行ロジック

本記事で示した手順に従うことで、読者はMetaTrader上で直接コンパイルおよびテストできる、完全に機能するEAを利用できるようになります。

このシステムは、さらなる研究のための柔軟なフレームワークも提供します。各コンポーネントはモジュール化されているため、プログラム全体を書き直すことなく、追加のフィルター、リスクモデル、またはコンテキスト条件を統合できます。

最も重要な点は、この取り組みが、チャート上のアイデアを再現可能なアルゴリズムへ変換する方法を示していることです。ルールがコードとして明確に表現されることで、過去のデータを使用した客観的なテストが可能となり、実験を通じて改善を進めることができます。

以下は、この記事に添付されているすべてのファイルの説明です。

ファイル名 説明
lwHiddenSmashDayContextExpert.mq5 
本記事で作成したEAの完全ソースコード
configurations_exp1.ini
実験1で使用した環境設定を含むストラテジーテスター構成ファイル
parameters_exp1.set 
実験1で使用した入力パラメータ
configurations_exp2.ini
実験2で使用した環境設定を含むストラテジーテスター構成ファイル
parameters_exp2.set
実験2で使用した入力パラメータ

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

プライスアクション分析ツールキットの開発(第64回):手動で描画したトレンドラインと自動監視の同期 プライスアクション分析ツールキットの開発(第64回):手動で描画したトレンドラインと自動監視の同期
手動で描画したトレンドラインを監視するには、常にチャートを確認し続ける必要があり、その結果、重要な価格反応を見逃す可能性があります。本記事では、MQL5 において手動で描画したトレンドラインと自動監視ロジックを同期させるトレンドライン監視エキスパートアドバイザーを開発し、価格が監視対象ラインに接近、タッチ、またはブレイクした際にアラートを生成します。
MetaTrader 5機械学習の設計図(第8回):パージ済み交差検証と試行枝刈りを用いたベイズ最適化によるハイパーパラメータ最適化 MetaTrader 5機械学習の設計図(第8回):パージ済み交差検証と試行枝刈りを用いたベイズ最適化によるハイパーパラメータ最適化
GridSearchCVとRandomizedSearchCVは、金融機械学習(金融ML)における本質的な制約を共有しています。それは、各試行が独立しているため、計算資源を増やしても探索そのものが賢くならないという点です。本記事では、TPE (Tree-structured Parzen Estimator)を用いるOptunaを、PurgedKFold交差検証、HyperbandPrunerによる早期終了、および学習用重みと評価用重みを分離する二重重み規約と統合する方法を紹介します。その結果として、フォールド単位で枝刈りをおこなう目的関数、モデルのハイパーパラメータと重み付けスキームを同時に最適化する提案層、金融タスク向けに調整されたプルーナー、SQLiteをバックエンドとする、再開可能なオーケストレーター、Optunaの結果をscikit-learnのcv_results_形式へ変換するコンバータの5つの要素から構成されるシステムが得られます。さらに本記事では、Timothy Mastersが示した境界についても説明します。すなわち、「統計的な目的関数では誘導的な探索が有益である一方、金融目的関数では逆に有害となる」という考え方です。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
初心者からエキスパートへ:MQL5におけるマルチタイムフレーム構造確認による流動性戦略の強化 初心者からエキスパートへ:MQL5におけるマルチタイムフレーム構造確認による流動性戦略の強化
上位足の流動性構造と下位足の反転パターンが一致すると、次の価格変動の方向性と発生確率の両方に大きな影響を与えます。上位足で形成された構造的な流動性ゾーンと、下位足での明確な反転確認シグナルを組み合わせることで、エントリータイミングの精度とトレード全体の質を向上させることができます。本記事では、上位足の構造的な確認を活用して流動性ベースのトレード戦略を強化する方法と、そのアプローチをMQL5で効果的に実装する方法について解説します。