English
preview
ダイナミックスイングアーキテクチャ:スイングから自動売買までの市場構造認識

ダイナミックスイングアーキテクチャ:スイングから自動売買までの市場構造認識

MetaTrader 5 |
7 4
Hlomohang John Borotho
Hlomohang John Borotho

目次

  1. はじめに
  2. システムの概要
  3. 導入手順
  4. バックテスト結果
  5. 結論


はじめに

常に変化し続ける市場のリズムの中で、すべての価格変動は一つの物語を語っています。それは、買い手と売り手の主導権がどこで、どのように移り変わったのかを示すスイングハイとスイングローの連続です。多くのトレーダーにとって、これらのスイングポイントを把握することは、市場構造を理解するための重要な基盤となります。しかし、ローソク足を一本ずつ目視で確認していく作業は、一貫性を欠きやすく、主観やバイアスが入り込む余地も少なくありません。

「ダイナミックスイングアーキテクチャ」は、そうした課題を解決するために開発されたシステムです。変化し続けるプライスアクションに動的に適応しながら、新たなスイングをリアルタイムで検出し、市場構造に変化が生じた瞬間に自動で取引を実行します。スイングローが形成されると強気の兆候を捉えて買いエントリーをおこない、スイングハイが形成されると弱気圧力のシグナルとして売りポジションを構築します。

スイングハイ検出

スイングロー検出

このアプローチにより、感情に左右される判断を排除し、市場本来の流れと戦略を常に同期させることができます。ゴールド、FX、インデックスなど、どの市場を取引する場合でも、システムは共通の原則を認識します。それは市場構造こそがチャンスを支配するという点です。このアーキテクチャを用いることで、トレーダーは人間の市場直感とアルゴリズムによる実行を橋渡しできます。これにより、構造ベースの取引は、より一貫性があり、適応力が高く、強力なものとなります。


システムの概要

「ダイナミックスイングアーキテクチャ」システムは、シンプルでありながら強力な原則に基づいて設計されています。それは、市場は常にスイングハイとスイングローを交互に形成しながら推移しているという考え方です。これらの転換点は、買い手と売り手の主導権争いを表しており、それをリアルタイムで捉えることで、各取引を価格変動の自然なリズムに沿って実行できます。

システムの中核では、直近のプライスアクションを継続的にスキャンし、あるローソク足が隣接するローソク足と比較して新たな構造上の高値または安値を形成したかどうかを判定します。スイングローが形成される、すなわち価格がそれ以上の安値を更新しなくなる局所的なポイントを検出した場合、強気のモメンタムが始まる可能性があると判断し、買いエントリーをおこないます。一方で、スイングハイが形成される、つまり高値更新に失敗した局所的な天井を検出した場合には、弱気圧力の兆候として売りポジションをトリガーします。

このシステムが動的である理由は、その適応性にあります。固定されたルックバック期間や静的なスイング判定ルールに依存するのではなく、新しいローソク足が形成されるたびに、その時点の市場構造と文脈を評価します。これにより、エキスパートアドバイザー(EA)が市場の動きに遅れたり、古いデータに依存したりすることを防ぎ、現在進行中の値動きをそのまま取引します。また、可読性と透明性を高めるため、このシステムはスイングをチャート上に直接可視化し、検出された各スイングポイントおよび対応する取引を、明確なラベルと矢印で表示します。これにより、トレーダーは即座にフィードバックを得ることができ、アルゴリズムが市場構造をどのように解釈しているかを観察できます。


導入手順

//+------------------------------------------------------------------+
//|                                      Dynamic Swing Detection.mq5 |
//|                        GIT under Copyright 2025, MetaQuotes Ltd. |
//|                     https://www.mql5.com/ja/users/johnhlomohang/ |
//+------------------------------------------------------------------+
#property copyright "GIT under Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/ja/users/johnhlomohang/"
#property version   "1.00"
#include <Trade/Trade.mqh>

CTrade TradeManager;
MqlTick currentTick;

input group "General Trading Parameters"
input int     MinSwingBars = 3;          // Minimum bars for swing formation
input double  MinSwingDistance = 100.0;  // Minimum distance for swing (in points)
input bool    UseWickForTrade = false;   // Use wick or body for trade levels

input group "Trailing Stop Parameters"
input bool UseTrailingStop = true;              // Enable trailing stops
input int BreakEvenAtPips = 500;                // Move to breakeven at this profit (pips) 
input int TrailStartAtPips = 600;               // Start trailing at this profit (pips)
input int TrailStepPips = 100;                  // Trail by this many pips 

まず、取引ユーティリティを読み込み、EAが実行時に使用するオブジェクトを準備します。「#include <Trade/Trade.mqh>」によってCTradeクラスを利用可能にし、「CTrade TradeManager」でそのインスタンスを生成します。このオブジェクトは、注文の送信、変更、決済を安全かつ高レベルに管理するためのもので、生のチケット操作を直接扱う必要がありません。また、「MqlTick currentTick」を宣言し、注文判断に使用する最新のティック(Bid/Ask/時刻)を保持します。次に、コードを変更せずにトレーダーが挙動を調整できるよう、一般的な取引パラメータをinputとして公開します。MinSwingBarsは、あるローソク足をスイングと判定するために必要な隣接ローソク足の数を指定し、値を大きくすることで誤検出されるスイングを減らすことができます。MinSwingDistanceは、スイングに必要な最小幅(ポイント単位)を設定し、小さなノイズによる取引を防ぎます。UseWickForTradeは、取引レベルの算出にローソク足のヒゲを使用するか(よりアグレッシブで極値を捉えやすい)、実体を使用するか(より保守的)を選択するためのフラグです。

続いて、リスク管理およびトレーリングに関する入力パラメータを定義し、取引管理を柔軟かつ明確にします。UseTrailingStopはトレーリングストップ機能の有効と無効を切り替えます。BreakEvenAtPipsは、一定の利益(pips)に到達した時点でストップロス(SL)を建値に移動し、下方向のリスクを排除するための設定です。TrailStartAtPipsは、トレーリングが実際に開始される利益水準を指定します。TrailStepPipsは、ストップロスを何pipsごとに更新するかを定義し、価格に追随する細かさを制御します。 これらのパラメータにより、いつ利益を保護するか、そしてどの程度タイトにストップを価格に追随させるかを細かくコントロールできます。

//+------------------------------------------------------------------+
//| Swing detection structure                                        |
//+------------------------------------------------------------------+
struct SwingPoint
{
    int       barIndex;
    double    price;
    datetime  time;
    bool      isHigh;
    double    bodyHigh;
    double    bodyLow;
};

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // Clean up all objects on deinit
    ObjectsDeleteAll(0, -1, -1);
}

次に、SwingPointというカスタム構造体を定義します。これは、検出された各スイングをシステムがどのように保持し追跡するかの基盤となるものです。この構造体は、スイングに関する重要な情報をすべて一か所にまとめて管理します。barIndexはスイングが発生した正確なローソク足の位置を記録し、priceはスイングの基準となる価格レベル(高値または安値)を保持します。timeは、そのスイングが形成された時刻を示します。isHighフラグは、スイングハイかスイングローかを区別するために使用され、アルゴリズムが売りエントリーを準備するのか、買いエントリーを準備するのかを判断する材料となります。さらに、bodyHighおよびbodyLowはローソク足の実体部分の価格レンジを保持します。これらは、トレーダーが入力設定で選択した内容に応じて、ヒゲを使用するか実体を使用するかを切り替えて取引を実行する際に役立ちます。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    if(!isNewBar())
        return;
    
    // Detect swings and manage trading logic
    ManageTradingLogic();
    
    if(UseTrailingStop) ManageOpenTrades();
}

OnTick()関数は、あらゆるMQL5 EAの中核となる処理であり、ティックごとに呼び出されます。関数内部ではまずisNewBar()をチェックし、新しいローソク足が確定したタイミングでのみロジックが実行されるようにします。これにより、同一ローソク足内でシグナルが重複して発生することを防ぎます。新しいローソク足であることが確認されると、システムはManageTradingLogic()を呼び出し、新たなスイングポイントの検出および取引をおこないます。さらに、トレーリングストップが有効な場合にはManageOpenTrades()も呼び出され、ストップレベルを更新しながら、利益を動的に保護します。

//+------------------------------------------------------------------+
//| Manage trading logic                                             |
//+------------------------------------------------------------------+
void ManageTradingLogic()
{
    static SwingPoint lastSwingLow = {-1, 0, 0, false, 0, 0};
    static SwingPoint lastSwingHigh = {-1, 0, 0, true, 0, 0};
    static bool lookingForHigh = false;
    static bool lookingForLow = false;
    
    // Detect current swings
    SwingPoint currentSwing;
    if(!DetectSwing(currentSwing))
        return;
    
    // Bullish scenario logic
    if(!lookingForHigh && !currentSwing.isHigh) // New swing low detected
    {
        lastSwingLow = currentSwing;
        lookingForHigh = true;
        lookingForLow = false;
        Print("Swing Low detected at: ", DoubleToString(currentSwing.price, _Digits), " Time: ", TimeToString(currentSwing.time));
        ExecuteTrade(ORDER_TYPE_BUY, "SwingLow_Buy");
        // Draw swing low
        string swingName = "SwingLow_" + IntegerToString(currentSwing.time);
        drawswing(swingName, currentSwing.time, currentSwing.price, 217, clrBlue, 1);
    }
    else if(lookingForHigh && currentSwing.isHigh) // Swing high after swing low
    {
        lastSwingHigh = currentSwing;
        lookingForHigh = false;
        Print("Swing High detected after Swing Low.");
        
        // Draw swing high
        string swingName = "SwingHigh_" + IntegerToString(currentSwing.time);
        drawswing(swingName, currentSwing.time, currentSwing.price, 218, clrRed, -1);
    }
    
    // Bearish scenario logic
    if(!lookingForLow && currentSwing.isHigh) // New swing high detected
    {
        lastSwingHigh = currentSwing;
        lookingForLow = true;
        lookingForHigh = false;
        Print("Swing High detected at: ", DoubleToString(currentSwing.price, _Digits), " Time: ", TimeToString(currentSwing.time));
        ExecuteTrade(ORDER_TYPE_SELL, "SwingHigh_Sell");
        // Draw swing high
        string swingName = "SwingHigh_" + IntegerToString(currentSwing.time);
        drawswing(swingName, currentSwing.time, currentSwing.price, 218, clrRed, -1);
    }
    else if(lookingForLow && !currentSwing.isHigh) // Swing low after swing high
    {
        lastSwingLow = currentSwing;
        lookingForLow = false;
        Print("Swing Low detected after Swing High.");
        
        // Draw swing low
        string swingName = "SwingLow_" + IntegerToString(currentSwing.time);
        drawswing(swingName, currentSwing.time, currentSwing.price, 217, clrBlue, 1);
    }
}

ManageTradingLogic()関数は、システムの中枢となるロジックであり、新たに検出されたスイングに基づくすべての判断を担当します。関数内部ではまず、lastSwingLowとlastSwingHighという2つのstatic変数を初期化し、直近のスイングポイントを保持します。これにより、システムは常に最後に発生した構造上の転換点がどこであったかを把握できます。また、lookingForHighやlookingForLowといったフラグを用いることで、次にどの種類のスイングを待っているのかをEAが記憶します。これにより、価格の動きに対して無作為に反応するのではなく、論理的な順序に基づいて処理をおこないます。新しいローソク足が確定すると、システムはDetectSwing()を呼び出し、有効なスイングが発生したかどうかを判定します。スイングが検出されなかった場合は、何もおこなわず次のローソク足更新を待ちます。

強気シナリオでは、新たなスイングローが検出された時点で、買い圧力が発生している可能性があると判断します。システムはlastSwingLowを更新し、次のスイングハイを探す状態に切り替えたうえで、ExecuteTrade(ORDER_TYPE_BUY, "SwingLow_Buy")を使用して即座に買い注文を出します。このイベントを視覚的に明確にするため、EAはスイングローが形成された位置に青色のマーカーをチャート上に描画し、アルゴリズムが市場構造をどのように解釈しているかをリアルタイムで確認できるようにします。その後、スイングハイが出現した場合は、強気の値動きが完了したことの確認として、赤色で描画されます。

弱気シナリオでは、このロジックが対称的に適用されます。スイングハイが検出されると、売り圧力の兆候として認識され、EAはそのスイングを記録し、方向フラグを切り替えたうえで、「ExecuteTrade(ORDER_TYPE_SELL, "SwingHigh_Sell")」を用いて売り注文を出します。同様に、このスイングは視認性を高めるため赤色でチャートに描画され、その後、市場が次の安値を形成すると青色のスイングマーカーが表示されます。このようにスイングハイとスイングローを交互に認識することで、システムは現在の価格の流れに継続的に適応します。結果として、最新の市場構造に沿った取引を人手を介さず自動的に実行することが可能となります。

//+------------------------------------------------------------------+
//| Detect swing points dynamically                                  |
//+------------------------------------------------------------------+
bool DetectSwing(SwingPoint &swing)
{
    swing.barIndex = -1;
    
    int barsToCheck = MinSwingBars * 2 + 1;
    if(Bars(_Symbol, _Period) < barsToCheck) 
        return false;
    
    // Check multiple recent bars for swings
    for(int i = MinSwingBars; i <= MinSwingBars + 5; i++)
    {
        if(i >= Bars(_Symbol, _Period)) break;
        
        // Check for swing high
        if(IsSwingHigh(i))
        {
            swing.barIndex = i;
            swing.price = UseWickForTrade ? iHigh(_Symbol, _Period, i) : 
                          MathMax(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            swing.time = iTime(_Symbol, _Period, i);
            swing.isHigh = true;
            swing.bodyHigh = MathMax(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            swing.bodyLow = MathMin(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            return true;
        }
        // Check for swing low
        else if(IsSwingLow(i))
        {
            swing.barIndex = i;
            swing.price = UseWickForTrade ? iLow(_Symbol, _Period, i) : 
                          MathMin(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            swing.time = iTime(_Symbol, _Period, i);
            swing.isHigh = false;
            swing.bodyHigh = MathMax(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            swing.bodyLow = MathMin(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            return true;
        }
    }
    
    return false;
}

DetectSwing()関数は、直近の価格ローソク足を動的にスキャンし、新しいスイングハイまたはスイングローを検出する役割を担います。まず、MinSwingBarsに基づいて評価可能な十分な本数のローソク足がチャート上に存在するかを確認します。その後、直近のローソク足の小範囲をループ処理し、潜在的な転換点を探します。各ローソク足に対して、IsSwingHigh()またはIsSwingLow()を呼び出し、そのローソク足が隣接ローソク足に比べて構造的に突出しているかどうかを判定します。有効なスイングが検出されると、関数はそのローソク足のインデックス、価格、時刻を記録し、スイングがハイかローかを判断します。また、トレーダーが選択した設定に応じて、ヒゲまたは実体の価格情報を保存します。スイングが確定すると、関数は即座にtrueを返し、メインロジックに構造上の転換が検出されたことを通知します。これにより、取引が実行可能であることがシステムに伝えられます。

//+------------------------------------------------------------------+
//| Check if bar is swing high                                       |
//+------------------------------------------------------------------+
bool IsSwingHigh(int barIndex)
{
    if(barIndex < MinSwingBars || barIndex >= Bars(_Symbol, _Period) - MinSwingBars)
        return false;
        
    double currentHigh = iHigh(_Symbol, _Period, barIndex);
    
    // Check left side
    for(int i = 1; i <= MinSwingBars; i++)
    {
        double leftHigh = iHigh(_Symbol, _Period, barIndex + i);
        if(leftHigh >= currentHigh - MinSwingDistance * _Point)
            return false;
    }
    
    // Check right side
    for(int i = 1; i <= MinSwingBars; i++)
    {
        double rightHigh = iHigh(_Symbol, _Period, barIndex - i);
        if(rightHigh >= currentHigh - MinSwingDistance * _Point)
            return false;
    }
    
    return true;
}

//+------------------------------------------------------------------+
//| Check if bar is swing low                                        |
//+------------------------------------------------------------------+
bool IsSwingLow(int barIndex)
{
    if(barIndex < MinSwingBars || barIndex >= Bars(_Symbol, _Period) - MinSwingBars)
        return false;
        
    double currentLow = iLow(_Symbol, _Period, barIndex);
    
    // Check left side
    for(int i = 1; i <= MinSwingBars; i++)
    {
        double leftLow = iLow(_Symbol, _Period, barIndex + i);
        if(leftLow <= currentLow + MinSwingDistance * _Point)
            return false;
    }
    
    // Check right side
    for(int i = 1; i <= MinSwingBars; i++)
    {
        double rightLow = iLow(_Symbol, _Period, barIndex - i);
        if(rightLow <= currentLow + MinSwingDistance * _Point)
            return false;
    }
    
    return true;
}

IsSwingHigh()関数とIsSwingLow()関数は、スイング検出の分析コアを形成する関数で、特定のローソク足が局所的な高値または安値として際立っているかどうかを判定します。IsSwingHigh()では、まず判定対象のローソク足の両側に十分な本数の隣接ローソク足が存在するかを確認します。その後、ローソク足の高値を取得し、周囲のローソク足の高値と比較します。もし近隣のいずれかの高値がMinSwingDistance内に収まる場合、そのローソク足はスイングハイとしては不適格と判断されます。これは、その周囲の値動きが十分に特徴的でなく、市場が強気から弱気へ明確に転換したとは言えないためです。こうして、市場が実際に強気から弱気へ転換した明確なピークのみをスイングハイとして認識します。

同様に、IsSwingLow()は安値について同じ構造的ロジックを逆に適用します。ローソク足の安値が隣接ローソク足に対してMinSwingDistance以上低くなっているかをチェックし、周囲の安値が近すぎる場合は、市場が本当に上昇方向に転換していないと判断して、そのローソク足は無視されます。このように距離と隣接ローソク足のチェックを用いることで、システムはノイズを排除し、一時的な値動きではなく、本当に方向性のある構造的転換点に焦点を当てて取引判断をおこないます。

//+------------------------------------------------------------------+
//| Draw swing point on chart                                        |
//+------------------------------------------------------------------+
void drawswing(string objName, datetime time, double price, int arrCode, color clr, int direction)
{
   if(ObjectFind(0, objName) < 0)
   {
      // Create arrow object
      if(!ObjectCreate(0, objName, OBJ_ARROW, 0, time, price))
      {
         Print("Error creating swing object: ", GetLastError());
         return;
      }
      
      ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, arrCode);
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, objName, OBJPROP_WIDTH, 3);
      ObjectSetInteger(0, objName, OBJPROP_BACK, false);
      
      if(direction > 0)
      {
         ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_TOP);
      }
      else if(direction < 0)
      {
         ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
      }
      
      // Create text label
      string textName = objName + "_Text";
      string text = DoubleToString(price, _Digits);
      
      if(ObjectCreate(0, textName, OBJ_TEXT, 0, time, price))
      {
         ObjectSetString(0, textName, OBJPROP_TEXT, text);
         ObjectSetInteger(0, textName, OBJPROP_COLOR, clr);
         ObjectSetInteger(0, textName, OBJPROP_FONTSIZE, 8);
         
         // Adjust text position based on direction
         double offset = (direction > 0) ? - (100 * _Point) : (100 * _Point);
         ObjectSetDouble(0, textName, OBJPROP_PRICE, price + offset);
      }
   }
}

drawswing()関数は、スイングポイントをチャート上に視覚的に表示する処理を担当します。これにより、トレーダーはアルゴリズムが検出した重要な転換レベルを確認できます。関数はまず、指定された名前のオブジェクトがすでに存在するかをチェックし、重複を避けます。存在しない場合は、検出されたスイングの時刻と価格に基づいて矢印を作成します。矢印の見た目(色、太さ、アンカー位置)は、スイングハイかスイングローかに応じて調整され、チャート上で直感的に識別できるようにします(例:スイングハイは赤、スイングローは青)。さらに、矢印の横に価格レベルを示すテキストラベルを作成し、読みやすさを考慮してわずかに位置をずらします。この可視化により、EAが構造を正しく検出していることを確認できるだけでなく、トレーダーは市場のリズムや転換点をリアルタイムで明確に把握できます。

//+------------------------------------------------------------------+
//| Helper functions                                                 |
//+------------------------------------------------------------------+
bool isNewBar()
{
   static datetime lastBar;
   datetime currentBar = iTime(_Symbol, _Period, 0);
   if(lastBar != currentBar)
   {
      lastBar = currentBar;
      return true;
   }
   return false;
}

//+------------------------------------------------------------------+
//| Execute trade with proper risk management                        |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE orderType, string comment)
{
   // Calculate position size based on risk
   double lotSize = 0.03;
   if(lotSize <= 0)
   {
      Print("Failed to calculate position size");
      return;
   }
   
   // Get current tick data
   if(!SymbolInfoTick(_Symbol, currentTick))
   {
      Print("Failed to get current tick data. Error: ", GetLastError());
      return;
   }
   
   // Define stop levels in points (adjust these values as needed)
   int stopLossPoints = 600;
   int takeProfitPoints = 2555;
   
   // Calculate stop loss and take profit prices
   double stopLoss = 0.0, takeProfit = 0.0;
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   
   if(orderType == ORDER_TYPE_BUY)
   {
      stopLoss = NormalizeDouble(currentTick.bid - (stopLossPoints * point), digits);
      takeProfit = NormalizeDouble(currentTick.ask + (takeProfitPoints * point), digits);
   }
   else if(orderType == ORDER_TYPE_SELL)
   {
      stopLoss = NormalizeDouble(currentTick.ask + (stopLossPoints * point), digits);
      takeProfit = NormalizeDouble(currentTick.bid - (takeProfitPoints * point), digits);
   }
   
   // Validate stop levels before sending the trade
   if(!ValidateStopLevels(orderType, currentTick.ask, currentTick.bid, stopLoss, takeProfit))
   {
      Print("Invalid stop levels. Trade not executed.");
      return;
   }
   
   // Execute trade
   bool requestSent;
   if(orderType == ORDER_TYPE_BUY)
   {
      requestSent = TradeManager.Buy(lotSize, _Symbol, 0, stopLoss, takeProfit, comment);
   }
   else
   {
      requestSent = TradeManager.Sell(lotSize, _Symbol, 0, stopLoss, takeProfit, comment);
   }
   
   // Check if the request was sent successfully and then check the server's result
   if(requestSent)
   {
      // Check the server's return code from the trade operation
      uint result = TradeManager.ResultRetcode();
      if(result == TRADE_RETCODE_DONE || result == TRADE_RETCODE_DONE_PARTIAL)
      {
         Print("Trade executed successfully. Ticket: ", TradeManager.ResultDeal());
      }
      else if(result == TRADE_RETCODE_REQUOTE || result == TRADE_RETCODE_TIMEOUT || result == TRADE_RETCODE_PRICE_CHANGED)
      {
         Print("Trade failed due to price change. Consider implementing a retry logic. Retcode: ", TradeManager.ResultRetcodeDescription());
         // Here you can add logic to re-check prices and re-send the request
      }
      else
      {
         Print("Trade execution failed. Retcode: ", TradeManager.ResultRetcodeDescription());
         // Handle other specific errors like TRADE_RETCODE_INVALID_STOPS (10016)
      }
   }
   else
   {
      Print("Failed to send trade request. Last Error: ", GetLastError());
   }
}

ヘルパー関数は、EAが効率的に動作し、必要なときにのみ反応することを保証する上で重要な役割を果たします。たとえば、isNewBar()関数は、新しいローソク足が形成されたことを確認することで、ティックごとに同じロジックが繰り返し実行されることを防ぎます。この関数は、最後に処理したローソク足の時刻を保持し、現在のローソク足と比較します。異なれば新しいローソク足が生成されたことを意味し、関数はtrueを返します。このシンプルながら重要な仕組みにより、不要な計算を減らし、取引はローソク足ごとに一度だけ評価されるため、システムはクリーンかつ効率的に、新しい市場データに同期した状態で動作します。

一方、ExecuteTrade()関数は、注文の実行を構造的かつ安全に管理します。まず、ロットサイズを計算します(現在は固定ですが、リスクベースの計算式に適応可能です)。次に最新のティックデータを取得し、ストップロスおよびテイクプロフィット(TP)のレベルをポイント単位で定義します。シグナルが買いか売りかに応じて、対応する価格レベルを計算して正規化し、距離の妥当性を確認したうえで、CTradeマネージャーを通じて注文を送信します。注文送信後は、ブローカーの応答コードを確認し、約定が成功したか、リクオートや価格変動などの問題が発生していないかを検証します。この詳細な処理により、システムは自動で安全に取引を実行し、すべての注文を確認し、エラーを適切に処理することで、プロフェッショナルグレードの取引アルゴリズムと同等の信頼性を確保しています。

//+------------------------------------------------------------------+
//| Validate stop levels against broker requirements                 |
//+------------------------------------------------------------------+
bool ValidateStopLevels(ENUM_ORDER_TYPE orderType, double ask, double bid, double &sl, double &tp)
{
   double spread = ask - bid;
   // Get the minimum allowed stop distance in points
   int stopLevel = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
   double minDist = stopLevel * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   
   if(orderType == ORDER_TYPE_BUY)
   {
      // Check if Stop Loss is too close to or above the current Bid price
      if(sl >= bid - minDist) 
      {
         // Option 1: Adjust SL to the minimum allowed distance
         sl = NormalizeDouble(bid - minDist, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
         Print("Buy Stop Loss adjusted to minimum allowed level.");
      }
      // Check if Take Profit is too close to or below the current Ask price
      if(tp <= ask + minDist)
      {
         // Option 1: Adjust TP to the minimum allowed distance
         tp = NormalizeDouble(ask + minDist, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
         Print("Buy Take Profit adjusted to minimum allowed level.");
      }
   }
   else // ORDER_TYPE_SELL
   {
      // Check if Stop Loss is too close to or below the current Ask price
      if(sl <= ask + minDist)
      {
         // Option 1: Adjust SL to the minimum allowed distance
         sl = NormalizeDouble(ask + minDist, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
         Print("Sell Stop Loss adjusted to minimum allowed level.");
      }
      // Check if Take Profit is too close to or above the current Bid price
      if(tp >= bid - minDist)
      {
         // Option 1: Adjust TP to the minimum allowed distance
         tp = NormalizeDouble(bid - minDist, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
         Print("Sell Take Profit adjusted to minimum allowed level.");
      }
   }
   
   return true;
}

ValidateStopLevels()関数は、すべての取引におけるSLおよびTPが、ブローカーの定める最小距離要件を満たしていることを確認します。これにより、無効な注文による拒否を防ぎます。関数はまず、ブローカーが提供するSYMBOL_TRADE_STOPS_LEVELを取得し、ポイント単位で許容される最小のストップ距離を計算します。その後、SLやTPが市場のBid/Ask価格に近すぎる場合は、自動で調整します。この保護機能により、ブローカーのルールに準拠することが保証されるだけでなく、リスクの高いまたは無効な価格レベルを自動的に修正してからサーバーに注文を送信するため、注文の約定も安定化します。

//+------------------------------------------------------------------+
//| Trailing stop function                                           |
//+------------------------------------------------------------------+
void ManageOpenTrades()
{
   if(!UseTrailingStop) return;

   int total = PositionsTotal();
   for(int i = total - 1; i >= 0; i--)
   {
      // get ticket (PositionGetTicket returns ulong; it also selects the position)
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;

      // ensure the position is selected (recommended)
      if(!PositionSelectByTicket(ticket)) continue;

      // Optional: only operate on same symbol or your EA's magic number
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      // if(PositionGetInteger(POSITION_MAGIC) != MyMagicNumber) continue;

      // read position properties AFTER selecting
      double open_price   = PositionGetDouble(POSITION_PRICE_OPEN);
      double current_price= PositionGetDouble(POSITION_PRICE_CURRENT);
      double current_sl   = PositionGetDouble(POSITION_SL);
      double current_tp   = PositionGetDouble(POSITION_TP);
      ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

      // pip size
      double pip_price = PipsToPrice(1);

      // profit in pips (use current_price returned above)
      double profit_price = (pos_type == POSITION_TYPE_BUY) ? (current_price - open_price)
                                                             : (open_price - current_price);
      double profit_pips = profit_price / pip_price;
      if(profit_pips <= 0) continue;

      // get broker min stop distance (in price units)
      double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
      double stop_level_points = (double)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
      double stopLevelPrice = stop_level_points * point;

      // get market Bid/Ask for stop-level checks
      double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

      // -------------------------
      // 1) Move to breakeven
      // -------------------------
      if(profit_pips >= BreakEvenAtPips)
      {
         double breakeven = open_price;
         // small adjustment to help account for spread/commissions (optional)
         if(pos_type == POSITION_TYPE_BUY)  breakeven += point; 
         else                                breakeven -= point;

         // Check stop-level rules: for BUY SL must be >= (bid - stopLevelPrice) distance below bid
         if(pos_type == POSITION_TYPE_BUY)
         {
            if((bid - breakeven) >= stopLevelPrice) // allowed by server
            {
               if(breakeven > current_sl) // only move SL up
               {
                  if(!TradeManager.PositionModify(ticket, NormalizeDouble(breakeven, _Digits), current_tp))
                     PrintFormat("PositionModify failed (BE) ticket %I64u error %d", ticket, GetLastError());
               }
            }
         }
         else // SELL
         {
            if((breakeven - ask) >= stopLevelPrice)
            {
               if(current_sl == 0.0 || breakeven < current_sl) // move SL down
               {
                  if(!TradeManager.PositionModify(ticket, NormalizeDouble(breakeven, _Digits), current_tp))
                     PrintFormat("PositionModify failed (BE) ticket %I64u error %d", ticket, GetLastError());
               }
            }
         }
      } // end breakeven

      // -------------------------
      // 2) Trailing in steps after TrailStartAtPips
      // -------------------------
      if(profit_pips >= TrailStartAtPips)
      {
         double extra_pips = profit_pips - TrailStartAtPips;
         int step_count = (int)(extra_pips / TrailStepPips);

         // compute desired SL relative to open_price (as per your original request)
         double desiredOffsetPips = (double)(TrailStartAtPips + step_count * TrailStepPips);
         double new_sl_price;

         if(pos_type == POSITION_TYPE_BUY)
         {
            new_sl_price = open_price + PipsToPrice((int)desiredOffsetPips);
            // ensure new SL respects server min distance from current Bid
            if((bid - new_sl_price) < stopLevelPrice)
               new_sl_price = bid - stopLevelPrice;

            if(new_sl_price > current_sl) // only move SL up
            {
               if(!TradeManager.PositionModify(ticket, NormalizeDouble(new_sl_price, _Digits), current_tp))
                  PrintFormat("PositionModify failed (Trail Buy) ticket %I64u error %d", ticket, GetLastError());
            }
         }
         else // SELL
         {
            new_sl_price = open_price - PipsToPrice((int)desiredOffsetPips);
            // ensure new SL respects server min distance from current Ask
            if((new_sl_price - ask) < stopLevelPrice)
               new_sl_price = ask + stopLevelPrice;

            if(current_sl == 0.0 || new_sl_price < current_sl) // only move SL down (more profitable)
            {
               if(!TradeManager.PositionModify(ticket, NormalizeDouble(new_sl_price, _Digits), current_tp))
                  PrintFormat("PositionModify failed (Trail Sell) ticket %I64u error %d", ticket, GetLastError());
            }
         }
      }
   } 
}

//+------------------------------------------------------------------+
//| Helper: convert pips -> price (taking 3/5-digit fractional pips) |
//+------------------------------------------------------------------+
double PipsToPrice(int pips)
{
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   int digits   = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   double pip   = (digits == 3 || digits == 5) ? point * 10.0 : point;
   return(pips * pip);
}

関数は、利益確定とリスク低減を動的なSL調整によって自動化する処理を担当します。関数はまず、すべてのポジションを確認し、同じ銘柄またはマジックナンローソク足に属していることを確認します。そして、現在のポジションが何pips利益になっているかを測定します。利益が事前に定義された閾値に達すると、関数はまずSLを建値(ブレイクイーブン)に移動させ、リスクを事実上ゼロにします。この際、すべての修正がブローカーの最小ストップレベル距離要件に準拠していることを確認します。

建値移動後、システムは構造化されたトレーリングストップモードに移行し、利益が伸びるに従ってストップを追跡します。この追跡は、ユーザーが指定したTrailStepPipsに基づく制御されたステップでおこなわれ、意味のある利益が積み上がった場合にのみストップが移動されます。関数はポジション建値に対して新しいSLレベルを計算し、サーバーのルールを満たしているかを確認したうえで、新しいレベルがポジションの利益を改善する場合のみ更新します。

このトレーリングストップ機能により、取引管理に適応的な層が加わり、利益を保護しつつ最大限の利得を狙うことが可能になります。利益を伸ばしながらリスク管理を維持するバランスを提供し、取引の進行に応じてストップレベルを体系的に前進させます。このような動的管理により、トレーダーは持続的なトレンドを捉えつつ、感情や手動操作による介入を最小限に抑えることができます。これは、堅牢な自動取引システムにおいて重要な特性です。


バックテスト結果

バックテストは、デフォルト設定を使用し、1時間足(1H)において、約2か月間の検証期間(2025年4月8日から2025年7月29日まで)で実施されました。

エクイティカーブ

バックテスト結果


結論

本記事では、スイング検出から自動執行までを一貫しておこなう市場構造認識システムである「ダイナミックスイングアーキテクチャ」を開発し、紹介しました。このシステムは、スイングベースのロジックを用いて市場の生の価格変動を読み解き、それを高度な取引判断へと変換することを目的としています。このアーキテクチャは、まず高精度なスイング検出によって、市場構造の基盤となる重要な高値と安値を特定することから始まります。次に、リスク管理を組み込んだ実行ロジックを統合し、すべての取引が適切な条件検証を経たうえでエントリーされるよう設計されています。さらに、取引効率を維持し向上させるため、価格変動に適応する自動トレーリング機構を採用し、ストップロスを建値へ移動させるとともに、トレンドの進行に応じて利益を合理的に追従させます。構造分析、エントリー、ポジション管理の各要素が有機的に連携することで、自己完結型かつ適応性の高い取引フレームワークを実現しています。

まとめると、「ダイナミックスイングアーキテクチャ」は、技術的な精度、市場構造への深い理解、そして適応的な取引管理を融合させた完全自動型の取引システムです。価格スイングを実行可能なインサイトへと変換し、ポジション管理を自動化することで、人為的なミスや感情的バイアスを最小限に抑えながら、収益機会の最大化を図ります。本フレームワークは、取引の一貫性を高めるだけでなく、高度な市場インテリジェンスの基盤としても機能し、あらゆるスイング、押し戻し、構造転換が、よりスマートでデータ駆動型の取引判断へとつながっていきます。

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

添付されたファイル |
最後のコメント | ディスカッションに移動 (4)
TahianaBE
TahianaBE | 22 10月 2025 において 11:20
素晴らしい記事をありがとう。
Hlomohang John Borotho
Hlomohang John Borotho | 23 10月 2025 において 22:11
TahianaBE #:
素晴らしい記事をありがとう。
どういたしまして。
Tonij Trisno
Tonij Trisno | 4 11月 2025 において 06:47
この素晴らしいEAとバックテスト 結果をありがとうございます。
そうすることで、私たちも正しい同じ/類似の結果を持っていることを確認することができます。ありがとうございます。
Hlomohang John Borotho
Hlomohang John Borotho | 6 11月 2025 において 21:51
Tonij Trisno バックテスト 結果をありがとうございます。
そうすることで、私たちも正しい同じ/類似の結果を持っていることを確認することができます。ありがとうございます。
同じ結果を得るためには、テスト・ウィンドウ「(2025年4月8日~2025年7月29日)」に注意してください。
MQL5入門(第25回):チャートオブジェクトで取引するEAの構築(II) MQL5入門(第25回):チャートオブジェクトで取引するEAの構築(II)
本記事では、チャートオブジェクト、特にトレンドラインと連携するエキスパートアドバイザー(EA)を構築し、ブレイクアウトおよび反転の取引機会を検出し、実行する方法を解説します。EAが有効なシグナルをどのように判定するのか、取引頻度をどのように制御するのか、そしてユーザーが選択した取引戦略との一貫性をどのように維持するのかを学ぶことができます。
知っておくべきMQL5ウィザードのテクニック(第85回):ストキャスティクスとFrAMAのパターンを用いたβ-VAEによる推論 知っておくべきMQL5ウィザードのテクニック(第85回):ストキャスティクスとFrAMAのパターンを用いたβ-VAEによる推論
本記事は、ストキャスティクスとフラクタル適応型移動平均の組み合わせを紹介した「第84回」の続きです。今回は推論フェーズでの学習結果の活用に焦点を移し、前回の記事で取り上げた低調なパターンの成績を改善できるかどうかを検討します。ストキャスティクスとFrAMAは、モメンタムとトレンドを補完する関係にあります。推論フェーズでの学習結果の活用では、以前に考察したβ変分オートエンコーダ(β-VAE)のアルゴリズムを再度利用します。また、いつものように、MQL5ウィザードとの統合を目的として設計されたカスタムシグナルクラスの実装も継続します。
機械学習の限界を克服する(第6回):効果的なメモリクロスバリデーション 機械学習の限界を克服する(第6回):効果的なメモリクロスバリデーション
本記事では、時系列クロスバリデーションにおける従来のアプローチと、その前提に疑問を投げかける新しい考え方を比較します。特に、市場環境が時間とともに変化するという点を十分に扱えていないという、古典的手法の弱点に焦点を当てます。これらの問題を踏まえ、Effective Memory Cross-Validation (EMCV)という、ドメインを意識した検証手法を紹介します。このアプローチは、「過去データは多ければ多いほど良い」という長年の常識を見直すものです。
プライスアクション分析ツールキットの開発(第46回):MQL5におけるスマートな可視化を備えたインタラクティブフィボナッチリトレースメントEAの設計 プライスアクション分析ツールキットの開発(第46回):MQL5におけるスマートな可視化を備えたインタラクティブフィボナッチリトレースメントEAの設計
フィボナッチツールは、テクニカル分析で最も人気のあるツールのひとつです。本記事では、価格の動きに応じて動的に反応するリトレースメントおよびエクステンションレベルを描画し、リアルタイムアラート、スタイリッシュなライン、ニュース風のスクロールヘッドラインを提供するインタラクティブフィボナッチEAの作成方法をご紹介します。このEAのもうひとつの大きな利点は柔軟性です。チャート上で高値(A)と安値(B)のスイング値を直接入力できるため、分析したい価格範囲を正確にコントロールできます。