ラリー・ウィリアムズの『市場の秘密』(第4回):MQL5における短期的スイングハイとスイングローの自動化
はじめに
ローソク足を1本ずつ見ていくと、市場はしばしば混沌としているように見えます。価格は上昇し、次に下落し、その後は横ばいとなり、多くのトレーダーは、これらの動きに何らかの構造があるのかどうか判断できません。しかし、アルゴリズム取引が一般的になるはるか以前から、ラリー・ウィリアムズは異なる見方を提示していました。著書『Long-Term Secrets to Short-Term Trading』の中で彼は、価格の動きは完全にランダムではなく、特定の短期スイングパターンが十分な頻度で繰り返されることで、測定可能な優位性をもたらすと主張しています。これらの考えは、観察、統計、そして数十年にわたる実際の市場経験に基づいています。
前回の記事では、コードと実際の過去データを用いて、ラリー・ウィリアムズのいくつかのアイデアを検証しました。その中で、ひとつの結果が特に際立っていました。短期スイングローが形成された後、市場は強く、かつ一貫して強気に反応したということです。この挙動は、異なる資産クラスや時間軸にわたって確認され、このパターンが偶然ではないことを示唆しています。このような結果は、自然に重要な問いを生み出します。この挙動が測定および検証可能であるならば、それは完全な取引システムとして自動化し、テストすることも可能なのでしょうか。
本記事では、その次のステップに進みます。分析から実行へと移行し、ラリー・ウィリアムズの短期スイングローおよびスイングハイを取引する、完全自動化されたエキスパートアドバイザー(EA)をMQL5で構築します。目的は最適化やカーブフィッティングではなく、構造と明確性です。明確に定義されたプライスアクションの概念を正確なルールとコードに変換することで、トレーダーがこれらのアイデアを独立してテスト、検証、改良できるツールを作成します。この記事を読み終える頃には、観察された市場の挙動を、体系的かつテスト可能な取引アプローチへと変換するための実践的なフレームワークを得ることができるでしょう。
ラリー・ウィリアムズの短期スイングポイントの定義と特定
ラリー・ウィリアムズの研究の中心にはシンプルでありながら強力な観察があります。価格はランダムに動くのではなく、短期的な極値の間でリズミカルに振動するというものです。これらの短期高値と短期安値は最も小さく反応性の高い市場構造の層を形成し、中期および長期トレンドが構築される基礎となります。
『Long-Term Secrets to Short-Term Trading』の中で、ラリー・ウィリアムズは、このマイクロ構造を理解することで、トレーダーが単一のローソク足に感情的に反応するのではなく、市場の自然な上下の流れに合わせてポジションを取ることができると強調しています。このセクションでは、その考え方を運用可能な中核へと分解し、オートメーションに適したルールベースのロジックへと変換します。
図1に示されるように、有効な短期スイングローは明確なピボットとして形成されます。すなわち、そのバーの安値が両側のより高い安値によって挟まれている状態です。

この中央のバーは売り圧力の一時的な枯渇を示し、市場がより大きなリズムを再開する前に一時停止していることを意味します。逆に図2では、短期スイングハイが示されています。中央バーの高値の両側により低い高値が存在し、短期的な買い圧力の枯渇を示しています。

しかし、すべての見かけ上のピボットが取引可能なスイングポイントとして成立するわけではありません。ラリー・ウィリアムズの手法において最も重要でありながら見落とされがちな要素の一つがフィルタリングです。スイングポイントの質は、その頻度よりもはるかに重要です。
図3では、アウトサイドバーの存在により潜在的なスイングローが無効化されています。

アウトサイドバーは定義上、周囲のバーを包含し、過剰なボラティリティを導入します。これは制御された枯渇ではなく、不安定性や不均衡を反映しており、ラリー・ウィリアムズが利用しようとした自然な市場リズムを歪めます。そのため、アウトサイドバーによって形成されたスイング候補は即座に無効となります。
同様に、図4ではインサイドバーの影響が示されています。 
インサイドバーは収縮と迷いを表しており、解決ではありません。このようなバー上またはその近辺で形成されるスイング候補は、市場参加者の確信が欠如しているため、構造として不十分です。ラリー・ウィリアムズの元々の原則に従い、このような形状はフィルタリングされ除外され、短期的な意図が最も明確に表れているものだけが残されます。
これらの除外条件を適用することで、単なる高値や安値の検出ではなく、短期的な注文フローの実質的な変化を反映した意味のある転換点を抽出することができます。この規律あるフィルタリングこそが、視覚的なチャートパターンを再現可能でテスト可能な市場行動へと変換する要素です。これらの定義が確立されたことで、市場の内部リズムを忠実に反映した短期スイングハイとローを検出するための正確なフレームワークが得られます。
EAの全体設計
よく設計されたシステムは、テストしやすく、拡張もしやすく、そして信頼性も高くなります。このセクションでは、ラリー・ウィリアムズの短期スイング戦略EAを構築するうえでの中核となる設計方針を整理します。
このEAは本質的に、主観に依存せず設定可能であるよう設計されています。市場の挙動は銘柄や時間足ごとに同一ではなく、すべてのトレーダーに同じ執行ルールを適用すべきではありません。そのため、EAは主要な挙動の選択肢をユーザー入力として公開し、同じロジックを異なる取引の前提のもとで検証できるようにしています。
取引方向の制御
最初の設計ポイントは取引方向です。EAは、ロングのみ、ショートのみ、あるいは両方向での取引を制限なくおこなう設定をサポートします。これは特に、上位時間足の分析やトレンドフォロー手法から方向性バイアスをすでに持っているトレーダーにとって有用です。
ポジションサイズとリスク管理
このEAは、手動ロット設定と、現在の口座残高に基づくリスクパーセンテージによる自動ロット計算の両方に対応しています。これにより、固定エクスポージャーでの運用と、口座の成長やドローダウンに応じて変化する適応型リスクの両方を選択できます。重要なのは、この設計によって、コアロジックを変更することなく、異なるリスクモデルで同じ戦略を検証できるという点です。
取引終了ロジック
本連載第3回での実験結果を踏まえ、EAは2種類のエグジットモードをサポートします。1つ目は、1本のバーの終了時にポジションをクローズする方法です。これはスイング形成後の短期的な方向バイアスと一致します。2つ目は、設定可能なリスクリワード比から導かれるテイクプロフィットを使用する方法です。
損失防止のストップ配置
EAによるすべての取引には必ずハードストップロスが設定されます。このストップは、短期スイングハイまたはローを形成したバーの極値に配置されます。この設計は意図的なものです。任意のポイント距離ではなく、市場構造そのものにリスクを結びつけています。
モジュラーアーキテクチャ
EAはモジュール構造で設計されています。スイング検出、取引検証、リスク計算、および注文執行は、論理的に分離されています。この設計によりコードの可読性が向上し、MQL5開発におけるベストプラクティスを促進します。さらに重要なのは、読者が各コンポーネントを再利用できる点であり、自分自身のシステム構築や追加フィルターの実装にも応用可能になります。
その他の設計上の考慮点
研究と実験を支援するため、このEAは、完了したバーごとに1回のみ取引判断をおこなうように設計されています。これによりシグナルの重複を防ぎ、ライブ環境とストラテジーテスターの間で一貫した動作が維持されます。
これらの設計方針により、堅牢な基盤が整いました。次のセクションでは設計から実装へ移行し、ラリー・ウィリアムズの短期スイング概念を実行可能なロジックへと変換していきます。
EAの作成手順
設計方針が固まったので、ここからは本記事の実践パートに進み、実際にEAの作成を始めます。このセクションはガイド形式で進めていきます。単に「何を書くか」を示すだけでなく、「なぜそれが必要なのか」、そして「それがシステム全体の中でどのような役割を持つのか」も説明していきます。
先に進む前に、いくつか前提として触れておきたい点があります。本記事は、読者がすでにMQL5プログラミング言語の基本的な知識を持っていることを前提としています。基本的な文法の説明なしで、EAのコードを読み書きできるレベルであることが望まれます。また、MetaTrader 5およびMetaEditorの操作にも慣れている必要があります。具体的には、新しいEAファイルの作成、MQL5プログラムのコンパイル、チャートへの適用、コンパイラメッセージの確認といった操作です。さらに、このEAはさまざまな市場環境でテストおよび評価することを前提としているため、ストラテジーテスターの基本的な使用経験も必要です。
学習を進めやすくするために、記事には「lwShortTermStructureExpert.mq5」という完全なソースファイルが添付されています。このファイルには、これから構築するEAの完成版が含まれています。ダウンロードして手元で開き、参照しながら進めることをおすすめします。不明点があれば、自分で書いたコードと比較して確認できます。プログラミングを習得する最も効果的な方法は、自分でコードを書き、こまめにコンパイルし、その結果としてプラットフォームがどのように反応するかを観察することです。
まず、MetaEditor 5を開き、新しい空のEAファイルを作成します。名前をlwShortTermStructureExpert.mq5とします。ファイルが作成されたら、以下のソースコードをエディタに貼り付けてコンパイルします。
//+------------------------------------------------------------------+ //| lwShortTermStructureExpert.mq5 | //| Copyright 2025, MetaQuotes Ltd. Developer is Chacha Ian | //| https://www.mql5.com/ja/users/chachaian | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd. Developer is Chacha Ian" #property link "https://www.mql5.com/ja/users/chachaian" #property version "1.00" #property description "This Expert Advisor automates Larry Williams’ short-term swing high and swing low trading methodology." #property description "Trades are executed based on validated short-term swing formations derived from market structure." #property description "The EA supports both time-based exits (single-bar holding) and risk-reward–based take-profit models." //+------------------------------------------------------------------+ //| Standard Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> //+------------------------------------------------------------------+ //| User input variables | //+------------------------------------------------------------------+ input group "Information" input ulong magicNumber = 254700680002; input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT; //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ //--- Create a CTrade object to handle trading operations CTrade Trade; //--- Bid and Ask double askPrice; double bidPrice; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- Assign a unique magic number to identify trades opened by this EA Trade.SetExpertMagicNumber(magicNumber); 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); } //+------------------------------------------------------------------+ //| TradeTransaction function | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { //--- To handle trade transaction events } //+------------------------------------------------------------------+
現段階では、このコードは取引やシグナル生成をおこないません。これは、以降のセクションで段階的に機能を追加していくための、クリーンで安定した土台として機能します。
ファイルの最初の部分には、ヘッダとプロパティの定義が含まれています。このセクションでは、EAの目的、作成者、バージョン、および基本的な動作が記述されています。これらのプロパティは、EAをチャートに適用した際にも表示されるため、ユーザーにとっても将来的な保守にとっても有用です。
次に、MQL5で提供されている標準取引ライブラリをインクルードします。これにより、注文執行や取引管理を簡素化するCTradeクラスを使用できるようになります。標準ライブラリを使用することで、信頼性が向上し、コードも整理された状態を保てます。
ユーザー入力セクションでは、トレーダーがソースコードを変更せずに設定できるパラメータを定義します。この段階では、マジックナンバーや使用する時間足といった基本的な情報のみを定義します。後のセクションで、取引方向のフィルター、ポジションサイズのロジック、エグジットモードなどの入力を追加していきます。
グローバル変数セクションには、EA全体で参照する必要があるオブジェクトや値を定義します。ここでは、すべての取引操作を処理するためのCTradeオブジェクトを作成します。また、注文実行時に必要となる現在のBid価格とAsk価格を格納する変数も宣言します。
初期化関数は、EAが起動した際に一度だけ呼び出されます。この段階での主な役割は、取引オブジェクトに一意のマジックナンバーを設定することです。これにより、EAは自分自身が管理すべきポジションを正しく識別できるようになります。
初期化解除関数は、EAが削除または停止された際に実行されます。現時点では、プログラムが終了した理由を出力するのみです。これはデバッグやテストの際に役立ちます。
ティック関数は、EAが市場の動きに反応する中心的な部分です。この段階では、現在のBid価格とAsk価格を取得するだけです。後のセクションで、短期スイング構造の検出や取引実行のロジックがここに追加されます。
最後に、取引操作関数がプレースホルダーとして含まれています。これは、注文の約定やポジションのクローズなど、取引関連のイベントに応答するためのものです。後に、取引の挙動をより詳細に監視および管理するために使用します。
この定型コードにより、整理されたしっかりした出発点が整いました。ここから、ラリー・ウィリアムズの短期スイングハイおよびローを検出し、取引フィルターを適用し、リスクを管理するロジックを段階的に追加していきます。
シグナル検出ロジック
EAの基盤が整ったところで、開発の最も重要な段階であるシグナル検出に進みます。ここでは、市場構造を実行可能なロジックへと変換します。
このEAでは、あえてカスタムインジケーターを呼び出すことはしません。その代わりに、価格データから直接ラリー・ウィリアムズの短期スイング構造を検出します。この設計により、EAは軽量で配布しやすくなり、完全に自己完結型になります。パターン検出に必要なすべての要素がEA内部に含まれる形です。
適切なタイミングでのシグナル検出
ラリー・ウィリアムズの短期スイングは、3本のバーから構成されます。これを正確に検出するためには、必要なバーがすべて確定してから判定をおこなう必要があります。そのため、シグナル検出は新しいバーが形成されたタイミングでのみ実行されます。
新しく出現したバーは、価格系列のインデックス0に配置されます。その直前に確定している3本のバーは、インデックス1、2、3に位置します。これらが短期スイングを構成する可能性のあるバーです。
このロジックを支えるために、新しいバーの開始を検出する小さなユーティリティ関数を定義します。
//--- 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; }
この関数は、現在のバーの時間と、以前に記録されたバーの時間を比較します。時間に変化があれば新しいバーが形成されたことを意味し、その場合はtrueを返します。
この関数は3つのパラメータを受け取ります。銘柄と時間足 は、どのチャートを監視するかを指定します。datetime型の変数は参照渡しで渡され、最後に処理されたバーのタイムスタンプを保持します。これにより、このロジックは毎ティックではなく、バーごとに1回だけ実行されるようになります。
この仕組みを機能させるために、グローバル変数datetimeも定義します。この変数は最後に処理したバーのタイムスタンプを保持し、EAがティックをまたいで状態を維持できるようにします。
//--- To help track new bar open datetime lastBarOpenTime;
変数を宣言した後、以下のようにOnInit関数内で初期値を代入します。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Initialize global variables lastBarOpenTime = 0; return(INIT_SUCCEEDED); }
短期スイングローの検出
新しいバーの形成が確認された時点で、直前の3本のバーを安全に調べ、短期スイングの形成を確認することができます。
//+------------------------------------------------------------------+ //| Detects a Larry Williams short-term low on the last three bars | //| Bar index 2 must be a swing low with higher lows on both sides | //| Bar 2 must NOT be an outside bar | //| Bar 1 must NOT be an inside bar | //+------------------------------------------------------------------+ bool IsLarryWilliamsShortTermLow(string symbol, ENUM_TIMEFRAMES tf){ //--- Price data for the three bars double high1 = iHigh(symbol, tf, 1); double low1 = iLow (symbol, tf, 1); double high2 = iHigh(symbol, tf, 2); double low2 = iLow (symbol, tf, 2); double high3 = iHigh(symbol, tf, 3); double low3 = iLow (symbol, tf, 3); //--- Condition 1: Bar 2 must be a swing low bool isSwingLow = (low2 < low1) && (low2 < low3); if(!isSwingLow){ return false; } //--- Condition 2: Bar 2 must NOT be an outside bar relative to bar 3 bool isOutsideBar = (high2 > high3) && (low2 < low3); if(isOutsideBar){ return false; } //--- Condition 3: Bar 1 must NOT be an inside bar relative to bar 2 bool isInsideBar = (high1 < high2) && (low1 > low2); if(isInsideBar){ return false; } //--- All conditions satisfied lwShortTermSwingLevel = NormalizeDouble(low2, Digits()); return true; }
ラリー・ウィリアムズの短期スイングローを検出する関数は、まずバー1、バー2、バー3の高値と安値を取得することから始まります。バー2が中央のバーであり、スイング候補となるバーです。
最初の条件では、バー2が本当にスイングローであるかを確認します。その安値は、両側のバーの安値よりも低くなければなりません。この条件を満たさない場合、そのパターンは即座に無効となります。
2つ目の条件では、アウトサイドバーを除外します。バー2はバー3を完全に包み込んでいてはいけません。アウトサイドバーは構造を歪めることが多いため、パターンの一貫性と明確さを保つために除外されます。
3つ目の条件では、バー1を確認します。バー1はバー2に対してインサイドバーであってはいけません。インサイドバーは収縮や不確実性を示し、ラリー・ウィリアムズは有効なスイングポイントの定義においてこれを明確に避けています。
これら3つすべての条件が満たされた場合にのみ、有効な短期スイングローと判定されます。この時点で、スイングバーの安値をグローバル変数に保存します。この価格水準は市場において構造的に重要なポイントとなり、後にリスク管理の判断に使用されます。
短期スイングハイの検出
短期スイングハイの検出ロジックは、スイングローのロジックと逆になります。
//+------------------------------------------------------------------+ //| Detects a Larry Williams short-term high on the last three bars | //| Bar index 2 must be a swing high with lower highs on both sides | //| Bar 2 must NOT be an outside bar | //| Bar 1 must NOT be an inside bar | //+------------------------------------------------------------------+ bool IsLarryWilliamsShortTermHigh(string symbol, ENUM_TIMEFRAMES tf){ //--- Price data for the three bars double high1 = iHigh(symbol, tf, 1); double low1 = iLow (symbol, tf, 1); double high2 = iHigh(symbol, tf, 2); double low2 = iLow (symbol, tf, 2); double high3 = iHigh(symbol, tf, 3); double low3 = iLow (symbol, tf, 3); //--- Condition 1: Bar 2 must be a swing high bool isSwingHigh = (high2 > high1) && (high2 > high3); if(!isSwingHigh){ return false; } //--- Condition 2: Bar 2 must NOT be an outside bar relative to bar 3 bool isOutsideBar = (high2 > high3) && (low2 < low3); if(isOutsideBar){ return false; } //--- Condition 3: Bar 1 must NOT be an inside bar relative to bar 2 bool isInsideBar = (high1 < high2) && (low1 > low2); if(isInsideBar){ return false; } //--- All conditions satisfied lwShortTermSwingLevel = NormalizeDouble(high2, Digits()); return true; }
より低い安値を探す代わりに、より高い高値を探します。下方向のアウトサイドバーを除外する代わりに、上方向のアウトサイドバーを除外します。適用される構造的なフィルターは同じであり、単に逆になっているだけです。
ロジックの構造と流れは同じであるため、改めて詳細に分解する必要はありません。スイングローの関数を理解していれば、スイングハイの関数は直感的に理解できます。
有効なスイングハイが検出されると、中央バーの高値が同じグローバル変数に保存されます。これにより、EAは方向に関係なく、直近のスイングポイントを追跡できるようになります。
直近のスイングレベルの追跡
この段階でEAをコンパイルしようとすると、「未宣言の識別子」に関するエラーが発生します。これは、まだ定義されていない変数に値を代入しようとしているためです。
これを解決するために、直近に検出された短期スイング(ハイまたはロー)の価格レベルを保持するグローバル変数を宣言します。
//--- Stores the price level of the most recently detected Larry Williams' short-term swing high or low double lwShortTermSwingLevel;
この変数の役割はただ一つ、最後に確認された構造的な極値を追跡することです。後のEAの処理において、この値は任意の距離ではなく、市場構造に基づいた論理的な位置にストップを配置するために使用されます。
変数を宣言した後、以下のようにOnInit関数内で初期値を代入します。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Initialize global variables ... lwShortTermSwingLevel = DBL_MAX; return(INIT_SUCCEEDED); }
シグナル検出と取引実行を分離することで、ロジックはモジュール化され、拡張しやすくなります。次の段階では、この検出された構造をエントリー、エグジット、リスク管理ルールへと結び付けていきます。
この時点で、EAは市場を監視し、有効なラリー・ウィリアムズの短期スイング形成を認識し、それらの価格レベルを正確かつ一貫して記録できるようになっています。これがシステムの中核となる機能であり、以降のすべての処理はこの基盤の上に構築されます。
取引ロジックと注文執行
このセクションでは、シグナルがどのように実際のポジションへと変換されるのか、そしてそれがこれまでに定めた設計ルールにどのように従うのかに焦点を当てます。
取引ロジックは、明確さと制御性を重視して構築されています。EAがおこなうすべての判断は、ユーザーが設定可能な明示的なルールに基づいています。これを実現するために、いくつかのカスタム列挙型、ユーザー入力、および補助関数を導入し、それらが一体となって機能します。
取引方向の制御
最初に実装する設計要素は、取引方向の柔軟性です。すべてのトレーダーが常に市場の両方向で取引したいわけではありません。中には、主要なトレンドに合わせて、一方向のエントリーに限定したいと考える人もいます。
これに対応するために、EAがロングのみ、ショートのみ、または両方向の3つのモードで動作できるようにするカスタム列挙型を定義します。選択されたモードに応じて、どのシグナルが取引を発生させるかが決まります。
//--- CUSTOM ENUMERATIONS enum ENUM_TRADE_DIRECTION { ONLY_LONG, ONLY_SHORT, TRADE_BOTH };
その後、この選択肢をユーザー入力オプションとして提示します。
input group "Trade And Risk Management" input ENUM_TRADE_DIRECTION direction = TRADE_BOTH;
EAが実行されると、注文を発行する前にこの設定を必ず確認します。選択された方向に一致しないシグナルはすべて完全に無視されます。
ロットサイズ決定モード
次に、ポジションサイズの設定について扱います。リスク管理は任意ではなく、トレーダーによって好まれる方法も異なります。そのため、EAは2つのロットサイズモードをサポートします。手動モードでは、ユーザーが固定ロットサイズを指定します。すべての取引は、ストップ距離や口座サイズに関係なく同じ取引量で実行されます。
enum ENUM_LOT_SIZE_INPUT_MODE
{
MODE_MANUAL,
MODE_AUTO
};
自動モードでは、EAが動的にロットサイズを計算します。この計算は、現在の口座残高の一定割合と、エントリー価格と保護ストップとの距離に基づいておこなわれます。これによって、市場のボラティリティが変化しても、取引ごとのリスクは一貫して維持されます。
どちらのモードもユーザー入力として公開されています。
input ENUM_LOT_SIZE_INPUT_MODE lotSizeMode = MODE_AUTO; input double riskPerTradePercent = 1.0; input double positionSize = 0.01;
自動モードが選択されている場合、EAは固定ロットサイズの入力を無視し、代わりにリスクパーセンテージを使用します。手動モードが選択されている場合は、リスクパーセンテージは無視されます。
利益確定モードとリスクリワード制御
次の柔軟性の層は取引のエグジットロジックです。これまでの実験結果から、短期スイングパターンは次のバー内で方向性のある値動きを生みやすいことが分かっています。しかし一方で、構造化されたリワード目標を好むトレーダーも存在します。
この両方のアプローチをサポートするために、テイクプロフィットモードの列挙型を定義します。
enum ENUM_TAKE_PROFIT_MODE
{
TP_HOLD_ONE_BAR,
TP_FIXED_RRR_1_ONE,
TP_FIXED_RRR_1_ONEptFIVE,
TP_FIXED_RRR_1_TWO,
TP_FIXED_RRR_1_THREE
};
最初のオプションでは、ポジションは正確に1本のバー経過後にクローズされます。このモードではテイクプロフィットは設定されません。ポジションは価格ではなく時間によって終了します。
残りのオプションでは、固定のリスクリワード比が適用されます。これらのモードでは、ストップ距離と選択されたリワード倍率に基づいてテイクプロフィットレベルが計算されます。その後、EAは実行時にストップロスとテイクプロフィットの両方を設定します。
単一の入力パラメータによって、ユーザーは希望するエグジット方式を選択できます。EAはこの設定に応じて動作を自動的に切り替えます。
input ENUM_TAKE_PROFIT_MODE takeProfitMode = TP_HOLD_ONE_BAR;
同時に1ポジションのみを許可する制御
最も重要な設計ルールのひとつは、EAが同時に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; }
これらの関数は、すべての保有ポジションをスキャンし、EAのマジックナンバーと指定された取引タイプに一致するポジションを探します。
一致するポジションが見つかった場合、EAはすでにポジションを保有していると判断し、新しいシグナルをすべて無視します。このロジックにより規律が保たれ、過剰なエクスポージャーが防止されます。
買い注文か売り注文かを確認する関数は同じ構造になっており、一方を理解すればもう一方もすぐに理解できます。
1バー保有取引のクローズ処理
1本のバーのみ保有する取引に対しては、確実にクローズする仕組みが必要です。この目的のために、EAのマジックナンバーに関連付けられたすべてのポジションをクローズする関数を定義します。
//+------------------------------------------------------------------+ //| To close all positions 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); } } } } }
この関数は、すべての保有ポジションをループし、EAに属するものを識別して取引オブジェクトを使ってクローズします。ロングかショートかは関係ありません。マジックナンバーが一致していれば、そのポジションはクローズされます。
この関数は後で新しいバーが始まったタイミングで呼び出され、時間ベースのエグジットが意図通りに動作するようにします。
買いポジションのオープン
すべての補助コンポーネントが揃ったので、いよいよ取引を実行できる段階に入ります。
買い注文の実行関数は、ロングポジションを安全かつ一貫性を持って発注するために必要な処理をすべて担当します。
//+------------------------------------------------------------------+ //| Function to open a market long position | //+------------------------------------------------------------------+ bool OpenBuy(double entryPrice, double lotSize){ double stopLevel = lwShortTermSwingLevel; double stopDistance = entryPrice - stopLevel; double contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); if(takeProfitMode == TP_HOLD_ONE_BAR){ if(lotSizeMode == MODE_AUTO){ double amountAtRisk = (riskPerTradePercent / 100.0) * accountBalance; lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = NormalizeDouble(lotSize, 2); } if(!Trade.Buy(NormalizeDouble(lotSize, 2), _Symbol, entryPrice, stopLevel)){ Print("Error while executing a market buy order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } } else{ double rewardValue = 1.0; switch(takeProfitMode){ case TP_FIXED_RRR_1_ONE: rewardValue = 1.0; break; case TP_FIXED_RRR_1_ONEptFIVE: rewardValue = 1.5; break; case TP_FIXED_RRR_1_TWO: rewardValue = 2.0; break; case TP_FIXED_RRR_1_THREE: rewardValue = 3.0; break; default: rewardValue = 1.0; break; } double targetLevel = NormalizeDouble(entryPrice + stopDistance * rewardValue ,Digits()); if(!Trade.Buy(NormalizeDouble(lotSize, 2), _Symbol, entryPrice, stopLevel, targetLevel)){ Print("Error while executing a market buy order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } } return true; }
この関数では、直近で検出されたスイングレベルを保護ストップとして使用します。建値とこのストップの距離が、その取引のリスクを定義します。
自動ロットサイズが有効な場合、この関数は口座残高に対する事前設定されたリスク割合を維持するようにロットサイズを計算します。手動サイズが選択されている場合は、指定されたロットサイズがそのまま使用されます。
その後、選択されたテイクプロフィットモードを確認します。取引が1バー保有モードの場合はストップロスのみが設定されます。固定リワードモードが選択されている場合は、リスクリワード比に基づいてテイクプロフィットレベルが計算されます。
最後に、取引オブジェクトを使用して買い注文を送信し、成功または失敗のステータスを返します。実行エラーが発生した場合は、デバッグ用にログに記録されます。
売りポジションのオープン
売り注文の実行関数は買い関数と同じロジックに従いますが、方向が逆になります。
//+------------------------------------------------------------------+ //| Function to open a market short position | //+------------------------------------------------------------------+ bool OpenSel(double entryPrice, double lotSize){ double stopLevel = lwShortTermSwingLevel; double stopDistance = stopLevel - entryPrice; double contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); if(takeProfitMode == TP_HOLD_ONE_BAR){ if(lotSizeMode == MODE_AUTO){ double amountAtRisk = (riskPerTradePercent / 100.0) * accountBalance; lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = NormalizeDouble(lotSize, 2); } if(!Trade.Sell(NormalizeDouble(lotSize, 2), _Symbol, entryPrice, stopLevel)){ Print("Error while executing a market buy order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } } else{ double rewardValue = 1.0; switch(takeProfitMode){ case TP_FIXED_RRR_1_ONE: rewardValue = 1.0; break; case TP_FIXED_RRR_1_ONEptFIVE: rewardValue = 1.5; break; case TP_FIXED_RRR_1_TWO: rewardValue = 2.0; break; case TP_FIXED_RRR_1_THREE: rewardValue = 3.0; break; default: rewardValue = 1.0; break; } double targetLevel = NormalizeDouble(entryPrice - stopDistance * rewardValue ,Digits()); if(!Trade.Sell(NormalizeDouble(lotSize, 2), _Symbol, entryPrice, stopLevel, targetLevel)){ Print("Error while executing a market buy order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } } return true; }
保護ストップは直近のスイングハイに配置されます。ストップまでの距離はそれに基づいて計算されます。ロットサイズの決定も同じルールに従い、テイクプロフィットの設定も買いロジックと同様の構造になります。
構造と流れが同じであるため、購入機能を理構造と流れが同一であるため、買い関数を理解していれば売り関数も容易に追うことができます。これらは合わせて、バランスの取れた一貫性のある実行レイヤーを形成します。
OnTickで全体を統合する
この段階で、EAの主要な構成要素はすでにすべて揃っています。有効な短期スイングシグナルの検出、リスク計算、そして取引の発注と管理が可能になっています。残る作業は、これらの要素を連結し、リアルタイムで意図通りにEAが動作するようにすることです。
この最終的な統合は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)){ if(takeProfitMode == TP_HOLD_ONE_BAR){ //--- Close any existing buy positions for this EA before opening a new one if(IsThereAnActiveBuyPosition(magicNumber)){ ClosePositionsByMagic(magicNumber); Sleep(100); } if(IsThereAnActiveSellPosition(magicNumber)){ ClosePositionsByMagic(magicNumber); Sleep(100); } } //--- Enter a buy position when a Larry Williams-defined short-term low swing pattern is detected if(direction == ONLY_LONG || direction == TRADE_BOTH){ if(IsLarryWilliamsShortTermLow(_Symbol, timeframe) && !IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){ OpenBuy(askPrice, positionSize); } } //--- Enter a short position when a Larry Williams-defined short-term high swing pattern is detected if(direction == ONLY_SHORT || direction == TRADE_BOTH){ if(IsLarryWilliamsShortTermHigh(_Symbol, timeframe) && !IsThereAnActiveSellPosition(magicNumber) && !IsThereAnActiveBuyPosition(magicNumber)){ OpenSel(bidPrice, positionSize); } } } }
この関数は、市場の価格が更新されるたびに呼び出され、EAの制御センターとして機能します。ここで、シグナルの評価やポジションのオープンとクローズのタイミングを判断します。
新しいバーでのみ取引する理由
OnTickの中で最初にして最も重要な判断は、すべてのロジックを選択された時間足の新しいバーが検出された場合にのみ実行するという点です。
こうすることで、ローソク足形成途中の価格ノイズに反応することを避けられます。ラリー・ウィリアムズの短期スイングロジックは確定したバーを前提としており、形成中のバーではなく確定したバーに基づいています。新しいバーの開始時のみ処理をおこなうことで、分析対象の価格データが確定した安定した状態になります。
また、この設計はEAの効率性も高めます。毎ティック条件を評価するのではなく、バーごとに1回だけ判断をおこなうため、意図した通りの動作に適しています。
1バーのみの取引管理
テイクプロフィットモードが「1バー保有」に設定されている場合、取引管理は価格ベースではなく時間ベースになります。
新しいバーが検出されると、EAはこのEAに関連するアクティブポジションが存在するかを確認します。存在している場合、それらは即座にクローズされます。結果として、前のバーで開かれたポジションが次のバーへ持ち越されることはありません。
このロジックは規律を強制します。すべての取引は正確に1バーだけ保持され、それ以上は続きません。既存ポジションをクローズした後、EAは新しく形成されたローソク足で新しいシグナル評価に進みます。
買いシグナルの評価
クリーンアップが完了すると、EAはシグナル評価へ移ります。
ロングトレードが許可されている場合、EAは有効なラリー・ウィリアムズの短期スイングローが形成されているかを確認します。同時に、既に買いまたは売りのポジションが存在しないことも確認します。これにより、常に1つのポジションのみが存在する状態が維持されます。
すべての条件が揃った場合、EAは事前に定義された実行ロジックを使用して買いポジションをオープンします。建値、ストップレベル、ロットサイズ、テイクプロフィットの挙動はすべてユーザー設定に基づいて自動的に処理されます。
売りシグナルの評価
売り取引のロジックも同様の構造に従います。
売り取引が許可されている場合、EAは有効なラリー・ウィリアムズの短期スイングハイパターンを探します。その際も、既にアクティブなポジションが存在しないことを必ず確認してから処理を進めます。
条件が満たされたとき、売りポジションは同じリスク管理および実行ルールに従ってオープンされます。ただし方向だけが逆になります。買いと売りのロジックは鏡写しとして設計されているため、その挙動は一貫しており予測可能です。
流れの本質
この構造の強さは、そのシンプルさにあります。
新しいバーが生成されるたびにクリーンな判断サイクルが実行されます。まず既存ポジションが管理され、次にシグナルが評価され、すべてのルールが揃った場合にのみエントリーが実行されます。重複も曖昧さも、隠れた挙動も存在しません。
このEAは、まるで規律あるトレーダーのように振る舞います。忍耐強く待ち、確定した構造だけに反応し、リスクルールを守り、オーバートレードはおこないません。
テストに進む前に、EAをチャートに適用した際に表示がクリーンで読みやすい状態になっていることを確認してください。視覚的に整理された環境は、意図したスイング構造の位置で取引が正しく実行されているかを検証するうえで非常に重要です。特にストラテジーテストやデバッグ時に役立ちます。
ConfigureChartAppearance関数がこのタスクを処理します。
//+------------------------------------------------------------------+ //| 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; }
この関数は、チャートをニュートラルでシンプルなレイアウトにプログラム的に設定します。具体的には、白背景の適用、グリッドの非表示、ローソク足モードへの切り替え、そして強気と弱気ローソクの色を明確に定義する処理をおこないます。これらの設定により視覚的なノイズが取り除かれ、スイングレベルや価格構造がより明確に浮かび上がるようになります。各チャートプロパティは安全に適用され、いずれかの設定が失敗した場合はエラーを報告し、それ以上の処理を停止します。これにより、チャート環境が適切に整っている場合のみEAが実行されることが保証されます。
関数を定義した後は、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が読み込まれた直後からチャートが即座に構成され、いかなる取引ロジックよりも先に環境が整えられます。設定に失敗した場合は、未定義または意図しないチャート条件下で取引が行われることを防ぐため、EAの初期化は中断されます。このアプローチは防御的プログラミングの良い実践であり、ライブ取引やバックテスト時の混乱を避けるのに役立ちます。
この最終ステップにより、EAの開発フェーズは完了します。これで、シグナル検出、取引管理、そして分析のためのクリーンな視覚環境を備えた完全に機能するEAが完成しました。次のフェーズはテストです。複数の金融商品でバックテストをおこない、履歴データにおいて戦略がどのように機能するかを評価し、異なる市場環境下でもロジックが意図通りに動作するかを検証します。
EAのテスト
いかなる戦略も、完成と見なす前に、履歴データに対するテストが必要です。このフェーズでは、同一のルールと制約を一貫して適用しながら、異なる市場環境や資産クラスにおけるEAの挙動を観察します。
今回の検証では、2024年1月1日から2025年11月30日までのデータを使用しました。執筆時点では約23か月分の市場データをカバーしています。すべてのテストは日足のみで実施されています。
EAは、コモディティ、インデックス、暗号資産、主要FXペアという異なる市場タイプを代表する4つの銘柄でテストされました。一貫性と再現性を確保するため、すべてのテストで同一の設定と入力パラメータが使用されています。これらはconfigurations.iniとparameters.setとして本記事に添付されており、読者自身の環境でも同じ結果を再現できます。
各テストは初期資金10,000ドルから開始されています。
ゴールド
ゴールドはテスト対象の中で最も強いパフォーマンスを示しました。テスト期間中の純利益は9,123.25ドル、勝率は55%でした。


添付のエクイティカーブからも分かるように、ゴールドの値動きはラリー・ウィリアムズが定義する短期スイング構造と非常によく一致しています。上位時間足ではトレンドが比較的綺麗に発生しやすく、その結果としてEAはリスクを抑えながら持続的な方向性のある値動きを捉えることができています。
この結果は、ゴールドが日足ベースのスイング戦略において有利な銘柄である理由を示しています。
S&P500
S&P 500は控えめながらも安定した結果を示しました。期間中の純利益は158ドル、勝率は42.11%でした。


全体的な利益は小さいものの、エクイティカーブは比較的スムーズで、大きなドローダウンも限定的です。これは戦略自体の構造がインデックス市場においても破綻していないことを示しています。ただし、より長いトレンドを捉えるためには、エグジットやポジションサイズの調整が有効である可能性があります。
エクイティカーブと詳細レポートのスクリーンショットが、この挙動を視覚的に裏付けています。
ビットコイン
ビットコインでは、EAは良好なパフォーマンスを示しました。初期資金10,000ドルからスタートし、総利益は4,572.63ドル、勝率は42.42%でした。


エクイティカーブは、日足で明確な方向性が出ている期間において安定した成長を示しています。この挙動は短期スイング構造と一致しており、モメンタムが存在する局面で収益機会を捉えています。
ビットコインはボラティリティが高いことで知られていますが、上位時間足で取引することで短期ノイズはある程度平滑化されます。この結果は、適切なリスク管理を前提とすれば暗号資産市場でも戦略が機能することを示唆しています。
英ポンド
英ポンドは同じ初期資金10,000ドルから279ドルの損失となり、勝率は19.05%でした。


エクイティカーブは単一の大きな失敗ではなく、小さな損失の積み重ねを示しています。これは市場がレンジ相場であり、持続的な方向性が欠けている場合に典型的な挙動です。この環境ではスイングベースのエントリーがフォローアップを得にくく、結果として勝率が低下しています。
この結果は2つの方向性を示唆します。一つはこの銘柄へのエクスポージャーを減らすこと。もう一つは、エグジットやリワード設定を調整し、より強いトレンド条件下のみで取引するフィルターを追加することです。
テストの概要
これらの結果はシステム開発における重要な事実を示しています。すなわち、単一の設定がすべての市場で同じように機能することはないということです。ゴールドのようなトレンド性の強い市場で優れていても、レンジ相場や高ボラティリティ環境では同じパフォーマンスが得られない場合があります。
まさにここで、あなた自身の実験が価値を発揮するのです。提供されたconfigurations.iniおよびparameters.setを使用することで、これらのテストを再現し、個別の入力値を調整しながら、マーケットごとのパフォーマンス変化を観察できます。
このEAは完成品ではなく、リサーチフレームワークとして扱うべきです。自分自身でバックテストをおこない、リワード比率を変更し、エグジットモードを試し、エクイティカーブを注意深く観察してください。このプロセスこそが、真の理解と優位性を生み出します。
結論
本記事は、完成したEAを提示すること以上の目的を持っていました。シグナル検出や設計上の意思決定から、実行ロジック、リスク管理、そしてテストに至るまで、完全かつ実践的な開発プロセスを一通り解説しています。この一連の流れを通して、読者は動作するEAを手にするだけでなく、それがなぜそのように振る舞うのかという明確な理解も得られるはずです。
この戦略自体は構造としては意図的にシンプルですが、実行面では規律が重視されています。明確に定義されたスイング形成のみを取引対象とし、ハードストップによってエクスポージャーを制限し、同時に保有できるポジションを1つに制限することで厳格なポジション管理をおこないます。これらの選択は偶然ではなく、資本の成長だけでなく保全も重視するリスク志向の考え方を反映しています。そのため、このEAは実験用途だけでなく、改善や実運用を見据えた基盤としても適しています。
バックテスト結果は重要な教訓をさらに裏付けています。どの戦略もすべての市場で同じように機能するわけではありません。複数の銘柄をテストし、その中でどこが得意でどこが苦手かを観察することで、システマティックトレーディングにおいてはテスト、評価、適応が不可欠であることが改めて示されます。提供された設定ファイルとパラメータファイルを使えば、結果を再現できるだけでなく、新しい実験や改善の検証も容易におこなうことができます。
最終的にこの記事は読者に具体的な成果物を提供しています。学習、検証、改良、拡張が可能な完全なEAです。同時に、MQL5で堅牢な取引システムを構築するための明確なフレームワークも提示しています。学習目的であれ、実験であれ、あるいは将来的な実運用であれ、ここで得られる知識は確かな出発点になります。
以下の表では、本記事に付属するファイルの概要と、それぞれの役割およびMetaTrader 5プラットフォームでの使用方法を示します。
| ファイル名 | 説明 | |
|---|---|---|
| 1. | lwShortTermStructureExpert.mq5 | 本記事で解説したEAのメインソースコード。短期スイング構造ロジックの実装を含む |
| 2. | configurations.ini | EAのテストに必要なグローバル環境設定を含む構成ファイル |
| 3. | parameters.set | バックテストに使用するユーザー入力パラメータを素早く読み込むことができる標準化された入力パラメータファイル |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20716
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
Python-MetaTrader 5ストラテジーテスター(第2回):シミュレーターにおけるバー、ティック、組み込み関数のオーバーロード処理
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
MQL5でボラティリティモデルを構築する(第I回):初期実装
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索