English Deutsch
preview
ラリー・ウィリアムズの『市場の秘密』(第7回):Trade Day of the Week概念の実証研究

ラリー・ウィリアムズの『市場の秘密』(第7回):Trade Day of the Week概念の実証研究

MetaTrader 5エキスパート |
34 0
Chacha Ian Maroa
Chacha Ian Maroa

はじめに

多くの取引システムは、価格パターン、テクニカル指標、あるいはボラティリティモデルに基づいて構築されています。一方で「時間」は、しばしば中立的な背景として扱われがちです。つまり、価格が主役であり、時間は単に経過するだけの要素と見なされています。そのため、月曜日であろうと木曜日であろうと、同じ条件で同じように取引がおこなわれることが一般的です。あたかも市場が曜日に関係なく均一に振る舞うかのように扱われます。

ラリー・ウィリアムズは数十年前からこの前提に疑問を投げかけていました。彼は長年にわたる観察と検証を通じて、市場は曜日ごとに異なる挙動を示す傾向があることを見出したのです。ある曜日はブレイクアウトに対して反応しやすく、別の曜日では失敗や停滞が起こりやすいといった違いが見られます。この考え方は非常にシンプルですが、短期トレーダーにとっては重要です。

本記事では、エントリーやエグジット、リスク管理の最適化には踏み込まず、より基本的な問いに立ち返ります。同一の戦略を毎日適用した場合、すべての曜日が同じように結果に貢献しているのでしょうか。それとも、特定の曜日が見えにくい形でパフォーマンスに影響しているのでしょうか。この検証のために、不要な複雑さを排除した対照実験を設計します。日次ボラティリティに基づく単一のエントリー条件を用い、ポジションは1セッションのみ保有し、勝率のみを評価指標とします。システムを極力シンプルにすることで、データの示す傾向をより明確に観察できるようにします。

目的は、利益を生む取引システムを構築することではありません。時間という観点から市場の振る舞いを観察することにあります。その過程で、Trade Day of the Week (TDW)の概念を任意の市場に対して検証して再現、改善するための基盤を構築します。


Trade Day of the Week (TDW)の概念

ラリー・ウィリアムズは、市場における機会が時間によって均等に分布していないことを観察しました。彼の研究では、価格変動は曜日ごとに集計するとランダムではなく、特定のパターンが見られました。ある曜日ではトレンドの継続が起こりやすく、別の曜日では動きが停滞したり失敗に終わる傾向が確認されています。重要なのは、この現象が特定のインジケーターやパターンに依存しているわけではなく、「時間そのもの」に起因している点です。

この洞察から導かれるのは、「時間が条件フィルターとして機能し得る」という考え方です。市場が特定の曜日において拡張やトレンドを形成しやすいのであれば、その日におこなう取引は、他の日と異なる確率特性を持つことになります。ウィリアムズは特定の曜日が成功を保証すると主張しているわけではありませんが、統計的に見て、より有利な条件が揃いやすい曜日が存在すると述べています。

この考え方は、特に短期取引システムにおいて重要です。短期戦略は保有期間が短く、許容できる誤差も小さいため、わずかな改善でも結果に大きく影響します。戦略が毎日同じように適用されている場合、良い市場環境と悪い市場環境が平均化されてしまっている可能性があります。曜日によるフィルタリングを導入することで、過去データ上でパフォーマンスが劣る曜日の取引を減らし、戦略の効率を高めることができます。これはエントリーロジック自体を変更することなく実現可能です。

Trade Day of the Week (TDW)の概念は、実証的な検証に非常に適しています。曜日は客観的かつ不変の時間区分であり、プラットフォームやデータソースに依存しません。各取引日は明確に分類でき、勝率などのパフォーマンス指標を直接比較することが可能です。この特性により、本手法は統計分析、ウォークフォワードテスト、さらには異なる市場や期間における再現性検証にも適しています。

時間という変数を切り出し、取引ロジックをシンプルに保つことで、曜日ごとのバイアスが実際に存在するのか、またそれが継続性を持つのかを評価できます。このアプローチは、仮定ではなく観察と検証を重視したWilliamsの思想とも一致しており、時間に基づく市場特性の有無をデータによって判断するための有効な手段となります。


実験の簡略化

時間ベースの市場仮説を検証する際、複雑さはすぐに足かせになります。システムに条件やフィルター、管理ルールを追加すればするほど、実際に何が結果を左右しているのかを特定することが難しくなります。本研究の目的は、特定の曜日が一貫してより良い取引環境を提供しているかどうかを検証することにあるため、本質的でない要素はすべて排除する必要があります。

このため、実験ではストップロス、テイクプロフィット、トレーリングストップ、部分決済といった一般的な取引管理手法を意図的に使用していません。これらのツールは実際の取引において不可欠ですが、意思決定の段階を増やすことで、時間による影響を見えにくくしてしまう可能性があります。たとえば、ストップロスに到達したという事実は、その日の方向性の優位性よりも、単に日中のノイズを反映している可能性があります。

代わりに、すべての取引は単一かつ統一されたルールに従います。ラリー・ウィリアムズのボラティリティベースのエントリーを用いてポジションを建て、そのポジションは損益に関係なく当日の取引終了時まで保有され、決済されます。これにより、各ポジションはその日の値動きを完全に反映し、すべての曜日間で直接比較可能な結果が得られます。

エントリーロジック自体も意図的に最小限に抑えています。スイングの確定やパターン認識に依存するのではなく、前日のレンジを基に当日の始値からブレイクアウト水準を算出します。このアプローチは、ウィリアムズのオリジナルの手法に近い形を維持しつつ、シグナルを客観的かつ再現性の高いものにします。システムはエントリーするかしないかのいずれかであり、解釈の余地を排除します。

このように構造を単純化することで、本実験は単一の変数、すなわち「時間」を切り出します。その結果、曜日ごとの勝率に差が見られた場合、それは取引管理やインジケーターの調整、裁量的フィルターではなく、曜日そのものに起因すると考えることができます。これにより、曜日ベースのバイアスの有無が確認された後に、必要に応じて拡張可能な明確な基準状態が作られます。


TDW検証用EAの構築

実装に入る前に、本セクションの目的を明確にしておきます。ここで目指すのは、完成されたブラックボックス型のシステムを提示することではありません。ラリー・ウィリアムズのTDWというアイデアを、制御された再現可能な形で検証するための研究ツールを構築することにあります。各コンポーネントは明確な役割を持ち、検証フレームワークの中で論理的なステップを支えます。

前提条件

このセクションを円滑に進めるために、いくつかの前提知識を想定しています。

まず、MQL5の基本的な理解があることを前提とします。変数、関数、条件分岐、ループ、列挙型、構造体、標準ライブラリの利用といったコア概念は、すでに把握している必要があります。これらの基礎が不足している場合は、公式のMQL5リファレンスから学習を始めるのが適切です。

次に、MetaTrader 5プラットフォームの使用経験も必要です。本記事では、チャートの表示、エキスパートアドバイザー(EA)の適用、ストラテジーテスターの操作、複数時間足の扱いといった基本操作を前提としています。

さらに、 MetaEditorの操作に慣れていることも重要です。新規ソースファイルの作成、コードの記述、コンパイル、エラーの確認といった作業がスムーズにおこなえることが、セクションを進めるうえで不可欠です。

実践的な理解を補助するため、完成版のEAソースコードをlwTDWStudy.mq5というファイル名で添付しています。ステップごとにEAを構築していく際には、このファイルを別タブで開き、参照しながら進めることを推奨します。プログラミングの習得には、受動的に読むだけでなく、実際に比較しながら手を動かすことが重要です。

EAの基盤作成

まず、MetaEditorで新規の空のEAを作成し、初期のボイラープレートコードを貼り付けるところから始めます。

//+------------------------------------------------------------------+
//|                                                   lwTDWStudy.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 Enumerations                                              |
//+------------------------------------------------------------------+
enum ENUM_TDW_MODE
{
   TDW_ALL_DAYS,     
   TDW_SELECTED_DAYS
};

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

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

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 "Volatility Breakout Parameters"
input double inpBuyRangeMultiplier = 0.50;

input group "Trade and Risk Management"
input double positionSize = 0.1;

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

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

   //---  Assign a unique magic number to identify trades opened by this EA
   Trade.SetExpertMagicNumber(magicNumber);

   return(INIT_SUCCEEDED);
}

この基盤部分では、メタデータの定義、必要なライブラリのインクルード、ユーザー入力の設計、そして後続のロジックを支えるグローバル変数の準備をおこないます。

まず、TDWモードを定義する列挙型を早い段階で導入します。これにより、EAは2つの異なる構成で動作できるようになります。ひとつはフィルタリングをおこなわず毎日取引するモードで、比較用のベースラインとして機能します。もうひとつは、ユーザーが選択した曜日のみに取引を制限するモードで、曜日バイアスを実証的に検証するために使用されます。

また、標準のTradeライブラリを組み込むことで、注文の発行やポジション管理を信頼性の高い既存インターフェースに委ねることができます。これにより、EAの実装は低レベルな売買処理ではなく、検証ロジックそのものに集中できます。

ユーザー入力は論理的にグループ化されています。情報系の入力ではマジックナンバーや使用する時間足を定義します。曜日フィルターでは、取引対象とする曜日を選択できるようにします。ボラティリティブレイクアウトのパラメータでは、エントリーレベルの算出方法を調整可能にします。さらに、取引およびリスク関連の入力では、ポジションサイズを定義します。各グループは、このシステムにおける異なる概念的な階層構造を表しています。

グローバル変数は、価格、時間、状態を追跡するために定義されます。これらはEA内の各コンポーネント間で情報を共有するための共通メモリとして機能します。

新しい取引日の開始検出

この実験は日次の意思決定サイクルに基づいて動作するため、新しいバーの開始を正確に検出することが重要です。この目的のために、最新バーのタイムスタンプと保存済みの値を比較するユーティリティ関数を導入します。

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

この関数は、銘柄、時間足、そして直近で記録されたバーの始値時刻(オープン時刻)への参照を受け取ります。新しいバーが検出された場合、保存されている時刻は更新され、関数は状態の変化を通知します。この仕組みにより、ティックの発生頻度に関係なく、日次の計算が1日につき一度だけ確実に実行されるようになります。

このロジックを支えるために、グローバルなdatetime変数を宣言し、EAの起動時に初期化します。

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

この変数をゼロで初期化することで、最初のバーが確実に新規バーとして検出されるようになります。

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

   ...
   
   //--- Initialize global variables
   lastBarOpenTime = 0;

   return(INIT_SUCCEEDED);
}

ボラティリティのブレイクアウトレベルの表現

ラリー・ウィリアムズのボラティリティ・ブレイクアウトのロジックでは、2つの派生値が必要になります。ひとつは前日の値幅(レンジ)、もうひとつは当日のエントリー予測レベルです。これらを個別の変数として扱うのではなく、専用の構造体としてまとめて定義します。

//--- Holds all price levels derived from Larry Williams' volatility breakout calculations
struct MqlLwVolatilityLevels
{
   double yesterdayRange;      
   double buyEntryPrice;       
};

MqlLwVolatilityLevels lwVolatilityLevels;

この構造体により、関連するデータを単一の論理単位として扱うことができ、コードの可読性が向上するとともに、不整合な状態が発生するリスクを低減できます。この構造体のインスタンスはグローバルに宣言され、初期化時にゼロへリセットされることで、初期状態が整えられます。

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

   ...
   
   //--- Reset volatility levels 
   ZeroMemory(lwVolatilityLevels);

   return(INIT_SUCCEEDED);
}

前日のレンジの計測

日次のボラティリティを算出するために、指定したインデックスのバーの値幅を返す関数を定義します。この関数は履歴データから高値と安値を直接取得し、その差分を返します。

//+------------------------------------------------------------------+
//| Returns the price range (high - low) of a bar at the given index |
//+------------------------------------------------------------------+
double GetBarRange(const string symbol, ENUM_TIMEFRAMES tf, int index){

   double high = iHigh(symbol, tf, index);
   double low  = iLow (symbol, tf, index);

   if(high == 0.0 || low == 0.0){
      return 0.0;
   }

   return NormalizeDouble(high - low, Digits());
}

不正な価格値がシステム全体に伝播しないよう、安全性を考慮したチェックを入れています。銘柄の価格精度に合わせた値に正規化しています。この関数は再利用可能であり、レンジ計算を上位ロジックから分離します。

買いエントリーレベルの算出

ブレイクアウトのエントリーレベルは、前日のレンジの一定割合を当日の始値に加算することで算出されます。この計算は、小さく単機能な関数として切り出されており、必要な入力を受け取り、算出された価格を返します。

//+------------------------------------------------------------------+
//| Calculates the bullish breakout entry price using today's open and yesterday's range |
//+------------------------------------------------------------------+
double CalculateBuyEntryPrice(double todayOpen, double yesterdayRange, double buyMultiplier){

   return todayOpen + (yesterdayRange * buyMultiplier);
}

このロジックを分離することで意図が明確になり、OnTick関数内に計算処理が混在するのを防ぎます。

新規バー開始時の日次レベル更新

すべての補助コンポーネントが揃った段階で、それらをOnTick関数内に統合します。

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

   ...
   
   //--- Run this block only when a new bar is detected on the selected timeframe
   if(IsNewBar(_Symbol, timeframe, lastBarOpenTime)){
      lwVolatilityLevels.yesterdayRange = GetBarRange(_Symbol, timeframe, 1);
      lwVolatilityLevels.buyEntryPrice  = CalculateBuyEntryPrice (askPrice, lwVolatilityLevels.yesterdayRange, inpBuyRangeMultiplier );     
   }   
}

新しい日足が検出された場合にのみ、このブロックが実行されます。その際、EAは前日のレンジを再計算し、買いエントリー価格を算出します。このタイミングでアクティブなポジションはすべて決済されます。この実験フレームワークでは、新しい日の開始が前回の取引の終了を意味します。このアプローチにより、各取引は1日分の値動きを完全に反映することが保証され、日中管理ではなく曜日ベースの挙動に焦点を当てるという本研究の目的と一致します。

日中の価格変動の追跡

予測されたエントリーレベルを価格がブレイクするタイミングを検出するために、分足レベルの終値を監視します。そのために、直近の1分足の終値データを保持するグローバル配列を定義します。

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

EAの初期化時には、この配列を時系列配列として設定し、最新データが常にインデックス0で取得できるようにします。

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

   ...
   
   //--- Treat the following arrays as timeseries (index 0 becomes the most recent bar)
   ArraySetAsSeries(closePriceMinutesData, true);

   return(INIT_SUCCEEDED);
}

ティックごとにCopyCloseを用いて配列を更新します。

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

   ...
   
   //--- Get some minutes data
   if(CopyClose(_Symbol, PERIOD_M1, 0, 5, closePriceMinutesData) == -1){
      Print("Error while copying minutes datas ", GetLastError());
      return;
   }   
}

これにより、過剰にデータを処理することなく、直近の価格挙動をローリングウィンドウとして取得できるようになります。

ブレイクアウトクロスオーバーの検出

クロスオーバー検出ロジックは、直近2本の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;
}

この関数は意図的にシンプルに設計されています。単一の条件のみを判定し、明確なブール値を返します。この単純さにより、取引条件は容易に確認可能であり、ロジックとしても解釈しやすくなっています。

TDWフィルターの実装

TDW概念をサポートするために、2つのヘルパー関数を導入します。1つ目は、datetime値を曜日の数値に変換する関数です。

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

2つ目は、選択されたモードとユーザー定義の曜日フラグに基づいて、取引を許可するかどうかを判定する関数です。

//+-----------------------------------------------------------------------------------------------------+
//| 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つに限定しているため、EAがすでに買いポジションを保有しているかどうかを確認するユーティリティ関数を使用します。

//+------------------------------------------------------------------+
//| 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 ", GetLastError());
         continue;
      }else{
         if(PositionGetInteger(POSITION_MAGIC) == magic && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
            return true;
         }
      }
   }
   
   return false;
}

マジックナンバーにより、このEAインスタンスによって生成されたポジションのみが対象になります。

次に、マジックナンバーを指定してポジションを決済する関数を定義します。

//+------------------------------------------------------------------+
//| To close all position with a specified magic number              |   
//+------------------------------------------------------------------+
void ClosePositionsByMagic(ulong magic) {
    
    for (int i = PositionsTotal() - 1; i >= 0; i--) {
        ulong ticket = PositionGetTicket(i);
        if (PositionSelectByTicket(ticket)) {
            if (PositionGetInteger(POSITION_MAGIC) == magic) {
                ulong positionType = PositionGetInteger(POSITION_TYPE);
                double volume = PositionGetDouble(POSITION_VOLUME);
                if (positionType == POSITION_TYPE_BUY) {
                    Trade.PositionClose(ticket);
                } else if (positionType == POSITION_TYPE_SELL) {
                    Trade.PositionClose(ticket);
                }
            }
        }
    }    
}

この関数は、新しい取引日の開始時に呼び出され、前日のポジションを一貫した形で決済するために使用されます。

注文の実行

注文の実行は専用関数で処理され、指定されたロットサイズで成行の買い注文を出します。

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

エラーは明示的に出力されるため、テスト時の実行問題を特定しやすくなっています。この関数は意思決定プロセスの最終段階に位置し、それ以前のすべての条件が満たされた場合にのみ呼び出されます。

取引ロジックの完成

最終ステップとして、すべての条件をOnTick関数に統合します。このEAはポジションをちょうど1取引日保有する設計になっており、ストップロス、テイクプロフィット、トレーリングロジックはいずれも使用しません。ポジションは取引日の終了時点で必ず決済され、それより早くも遅くもなりません。

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

   ...
   
   //--- Run this block only when a new bar is detected on the selected timeframe
   if(IsNewBar(_Symbol, timeframe, lastBarOpenTime)){

      ...
      
      //--- Close any active long position
      if(IsThereAnActiveBuyPosition(magicNumber)){
         ClosePositionsByMagic(magicNumber);
      }    
   }   
}

EAは予測エントリーレベルを価格が上抜けしたかどうかをチェックします。ベースラインモードでは、条件が成立すれば即座にエントリーされます。曜日フィルタリングが有効な場合は、上抜けが許可された取引日に発生した場合のみエントリーが実行されます。

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

   ...
   
   if(tradeDayMode == TDW_ALL_DAYS){
      if(IsCrossOver(lwVolatilityLevels.buyEntryPrice, closePriceMinutesData )){
         OpenBuy(askPrice, positionSize);
      }
   }
   
   if(tradeDayMode == TDW_SELECTED_DAYS){
      if(IsTradingDayAllowed(currentTime)){
         if(IsCrossOver(lwVolatilityLevels.buyEntryPrice, closePriceMinutesData)){
            OpenBuy(askPrice, positionSize);
         }
      }
   }   
}

以下が、すべてのコンポーネントを統合した最終的なOnTick関数です。

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

   //--- Retrieve current market prices for trade execution
   askPrice      = SymbolInfoDouble (_Symbol, SYMBOL_ASK);
   bidPrice      = SymbolInfoDouble (_Symbol, SYMBOL_BID); 
   
   //--- Run this block only when a new bar is detected on the selected timeframe
   if(IsNewBar(_Symbol, timeframe, lastBarOpenTime)){
   
      UpdateSupertrendBandValues();
      
      if(IsSupertrendBullishSignal()){   
         if(IsThereAnActiveSellPosition(magicNumber)){
            ClosePositionsByMagic(magicNumber);
            Sleep(50);
         }
         if(direction == TRADE_BOTH || direction == ONLY_LONG){
            OpenBuy(askPrice, CalculateAdaptiveStopLossPrice(POSITION_TYPE_BUY), positionSize);
         }
      }
      
      if(IsSupertrendBearishSignal()){
         if(IsThereAnActiveBuyPosition(magicNumber)){
            ClosePositionsByMagic(magicNumber);
            Sleep(50);
         }
         if(direction == TRADE_BOTH || direction == ONLY_SHORT){
            OpenSel(bidPrice, CalculateAdaptiveStopLossPrice(POSITION_TYPE_SELL), positionSize);
         }
      }      
   }   
}

テストに進む前に、まずチャート環境がプライスアクションと売買の動きを明確に表示できる状態になっている必要があります。一貫性のある整ったチャートレイアウトは、エントリーやエグジット、そして全体的な挙動をストラテジーテスト中に視覚的に確認するうえで非常に重要になります。このEAは研究および分析用途を前提としているため、チャートの可読性を高めることは小さいながらも重要な要素です。

これを実現するために、EA初期化時にチャート表示を設定するカスタムユーティリティ関数を定義します。この関数は一連の表示設定を適用し、テストおよびリプレイ時にローソク足、背景、価格変動が明確に識別できるようにします。以下がその関数定義であり、カスタムMQL5関数用のセクションに配置します。

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

この関数は、一連のChartSetInteger呼び出しによって動作します。それぞれのコマンドが、現在アクティブなチャートの特定の表示プロパティを変更します。

まず、チャートの背景色を白に設定します。明るい背景はコントラストを高め、ローソク足の色をより明確に際立たせます。次に、チャートのグリッドを無効化します。グリッドを削除することで視覚的なノイズが減り、補助線ではなくプライスアクションそのものに集中できます。その後、チャートモードをローソク足表示に設定します。ローソク足はラインチャートよりも多くの情報を持ち、ボラティリティや構造、日中の動きを分析するのに適しています。

続いて、前景色を黒に設定します。これにより、白背景上でもテキストや価格スケールが明確に視認できるようになります。その後、陽線と陰線に異なる色を設定します。陽線は上昇を視覚的に強調するためにSeaGreen、陰線はコントラストを保ちつつ読みやすさを維持するために黒に設定されます。同じ色設定は、上昇と下降を示すチャートバーのアウトラインにも適用されます。これにより、ローソク足本体と枠線の視覚的一貫性が保たれ、解釈しやすくなります。

各ChartSetInteger呼び出しは成功判定がおこなわれます。いずれかの設定に失敗した場合はエラーメッセージをログに出力し、即座にfalseを返します。これにより、EAは設定異常を早期に検出でき、意図しないチャート条件での動作を防ぐことができます。すべての設定が正常に適用された場合はtrueが返され、チャートがテストおよび分析に適した状態であることを示します。

関数が定義された後は、EAの初期化関数内から呼び出されます。これにより、EAの起動時に一度だけチャート設定が適用されるようになります。

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

OnInit関数内では、チャート設定ルーチンを呼び出し、その結果を検証します。設定に失敗した場合、EAは初期化を中止し、問題をログに出力します。成功した場合は、そのまま通常の初期化処理が継続されます。

このアプローチにより、すべてのテストが整理された見やすいチャートレイアウトから開始されるため、取引の挙動の評価、過去パフォーマンスの確認、そして戦略ロジックがテスト中に意図通りに動作しているかの視覚的確認が容易になります。

この時点で、EAロジックは完成です。これまでに導入したすべてのコンポーネントは、研究目的を正確に反映した一貫した実行フローとして統合されています。

このセクションでは、TDW仮説を検証するために設計された専用EAの構築プロセスを解説しました。設計においては、明確性、変数の分離、再現性が重視されています。各関数は明確な役割を持ち、日次判断、日中実行、時間ベースフィルタリングが整理された形で分離されています。

完成したソースコードはlwTDWStudy.mq5として提供され、そのままテストに使用することも、さらなる実験のために拡張することも可能です。これにより実装フェーズは完了です。次のステップとして実証的評価へ移行し、特定の曜日に取引上の優位性が存在するかどうかの分析に進む準備が整います。


TDWフィルターの実証テスト

TDW仮説を検証するため、ゴールド(XAUUSD)の日足(D1)を対象に一連のコントロール実験を実施しました。目的は、曜日で取引をフィルタリングすることでパフォーマンスに測定可能な差が生じるかどうかを確認することです。

2種類の実験をおこないました。

1. ベースラインテスト

このテストでは、エントリー条件が成立すれば曜日に関係なく取引をおこないます。時間ベースのフィルタリングは一切適用されません。これにより、基準となる中立的な参照値を構築します。

2. TDWテスト

このテストでは、1回の実行につき特定の曜日のみ取引を許可し、曜日ごとのパフォーマンスを独立して評価します。すべてのテストは四半期ごとのウォークフォワード分割で実施し、特定の相場局面に結果が偏ることを防いでいます。

ベースライン(曜日フィルタなし)

年間全体でベースラインテストを実施しました。

テスト条件

  • 期間:2025年1月1日~2025年12月30日
  • tradeDayMode:TDW_ALL_DAYS
  • 全曜日で取引許可

結果:

指標
勝率(%) 54.25
純利益($) 26102.15

四半期別結果(曜日別取引)

各四半期を独立して検証し、TDW_SELECTED_DAYSを使用しました。各テストでは1曜日のみ有効化しています。

第1四半期:

2025年1月1日~2025年3月31日

曜日 勝率(%) 純利益($)
月曜日 77.00 4444.24
火曜日 59.35 93344.24
水曜日 64.22 -14112.00
木曜日 57.43 9883.21
金曜日 72.45 -1280.82
ベースライン 59.64 26102.15

考察:

高い勝率が必ずしも収益性に直結するわけではありません。水曜と金曜は勝率は良好ですが損失となっており、勝率だけではなくペイオフ構造の重要性が示されています。

第2四半期:

2025年4月1日~2025年6月30日

曜日 勝率(%) 純利益($)
月曜日 100.00 33137.25
火曜日 0.00 -21054.39
水曜日 92.31 44388.16
木曜日 23.53 -13790.47
金曜日 61.40 17674.23
ベースライン 61.16 48514.81

考察:

曜日ごとのパフォーマンス差が極端に分かれています。水曜が収益、精度ともに支配的であり、火曜は完全に機能していません。この分散はランダム市場仮説とは整合しません。

第3四半期:

2025年7月1日~2025年9月30日

曜日 勝率(%) 純利益($)
月曜日 92.787 32767.01
火曜日 13.95 -13797.71
水曜日 5.91 -9466.48
木曜日 54.10 -4330.74
金曜日 58.18 16728.25
ベースライン 52.41 28616.31

考察:

月曜と金曜が優位である一方、週中は著しく弱い結果となっています。曜日依存の挙動が明確に表れています。

第4四半期:

2025年10月1日~2025年12月30日

曜日 勝率(%) 純利益($)
月曜日 61.38 20288.15
火曜日 0.00 -4677.96
水曜日 60.32 -31755.28
木曜日 42.59 3169.84
金曜日 22.88 -2059.73
ベースライン 64.27 -1101.06

考察:

ベースライン自体が赤字となっていますが、曜日選択による取引は依然として相対的な改善を示しています。月曜は引き続き、さまざまな相場環境において安定性を示しています。

数値が示すもの

データから以下の点が明確に読み取れます。

パフォーマンスは週全体で均等に分布しているわけではありません。特定の曜日は一貫して他の曜日を上回る一方で、別の曜日は繰り返し期待値を下回っています。勝率のみでは不十分です。いくつかの曜日は高い精度を示しながらも損失を出しており、勝率に加えて収益性を評価する必要性が強調されます。曜日フィルタリングは、構造的に弱い期間へのエクスポージャーを減らすことができます。

パフォーマンスの低い曜日を除外することで、コアとなる戦略ロジックを変更することなく安定性が向上します。市場には時間ベースの行動バイアスが存在しています。四半期ごとの繰り返し見られるパターンは、ラリー・ウィリアムズの「すべての曜日で均等に取引することは測定可能なエッジを犠牲にする」という主張を強く支持しています。

TDWフィルターは方向予測の手法ではなく、確率的なレンズとして機能します。トレーダーをより機会が発生しやすい期間へと導き、同時にリスクがリターンを上回る曜日からのエクスポージャーを回避する役割を果たします。


結論

本研究は、これまで見過ごされがちでありながらも単純な仮説、すなわち「時間そのものが短期取引においてエッジになり得るのではないか」という点を検証することを目的としました。ラリー・ウィリアムズのTDW概念を測定可能かつ検証可能なフレームワークへと落とし込むことで、議論を主観からエビデンスへと移行させています。

ゴールドを対象としたコントロールされたベースラインテストおよび四半期ごとのウォークフォワード実験を通じて、取引パフォーマンスが週全体で均等に分布していないことを示しました。特定の曜日は一貫して高い勝率と収益性を示す一方で、他の曜日は期待値を継続的に悪化させる結果となりました。これらの差は複数の市場局面にわたって維持されており、すべての取引日が同様に振る舞うという前提に疑問を投げかけるものです。

さらに重要なのは、本記事が結果の提示にとどまらず、こうした時間ベースのバイアスをMQL5を用いてどのように客観的に検証できるかを示した点です。EAの設計、入力構造、テスト手法は再利用可能な基盤として整理されており、読者は他の市場、時間足、フィルター条件にも応用することができます。本記事では収益性とパフォーマンスに焦点を当てていますが、同じフレームワークはボラティリティ、レンジ拡大、ドローダウン挙動、デイトレードの研究にも拡張可能です。

この記事を通じて読者が得るのは、単に「曜日によって有利不利が存在する」という確認だけではありません。市場行動を研究するための実践的な設計図です。それは選択的なエントリー、規律あるフィルタリング、そして無条件の執行ではなく市場挙動の深い理解を促します。

トレーダーおよび開発者の双方にとって、本研究は一つのシンプルだが重要な事実を再確認させます。市場に時間的な傾向が存在するのであれば、それを無視することも一つの選択であり、測定することは優位性になり得るという点です。 

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

添付されたファイル |
lwTDWStudy.mq5 (12.34 KB)
Market Memory Zonesインジケーターの開発:価格が戻りやすい領域 Market Memory Zonesインジケーターの開発:価格が戻りやすい領域
強い市場活動によって形成される価格ゾーンを識別するインジケーターを開発します。具体的には、インパルス的な値動き、構造のシフト、流動性イベントなどによって生成される領域を対象とします。これらのゾーンは、未約定の注文や急激な価格変動によって市場に「記憶」が残されたエリアを表します。チャート上にこれらの領域をマーキングすることで、将来的に価格が再訪し反応する可能性が統計的に高いポイントを可視化します。
MQL5入門(第36回):MQL5のAPIとWebRequest関数の習得(X) MQL5入門(第36回):MQL5のAPIとWebRequest関数の習得(X)
MQL5におけるHMAC-SHA256およびAPI署名の基本概念を紹介し、メッセージと秘密鍵を組み合わせることでリクエストを安全に認証する方法を説明します。これは、機密データを公開することなくAPI呼び出しに署名するための基盤となります。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
古典的な戦略を再構築する(第21回):ボリンジャーバンドとRSIのアンサンブル戦略の発見 古典的な戦略を再構築する(第21回):ボリンジャーバンドとRSIのアンサンブル戦略の発見
EURUSD市場を対象としたアンサンブル型アルゴリズム取引戦略の開発について説明します。この戦略は、ボリンジャーバンドとRSI(相対力指数)を組み合わせたものです。初期のルールベース戦略は高品質なシグナルを生成した一方で、取引頻度が低く、収益性にも限界がありました。その後、複数の戦略バリエーションを反復的に評価した結果、市場に対する理解の誤り、ノイズの増加、パフォーマンスの劣化といった問題が明らかになりました。これらの課題に対し、統計的学習アルゴリズムを適切に活用し、モデリング対象をテクニカル指標へと再定義し、適切なスケーリングを適用したうえで、機械学習による予測と従来の取引ルールを組み合わせることで、最終的には許容可能なシグナル品質を維持しながら、収益性と取引頻度の大幅な改善を達成しました。