English Deutsch
preview
ボラティリティベースのブレイクアウトシステムの開発

ボラティリティベースのブレイクアウトシステムの開発

MetaTrader 5トレーディング |
82 0
Hlomohang John Borotho
Hlomohang John Borotho

はじめに

従来型のブレイクアウトシステムは、サポートやレジスタンスを一時的に突破した後に価格がすぐレンジ内へ戻ってしまう「だまし」によって、誤シグナルが発生しやすいという課題があります。こうした誤ったブレイクアウトは、低ボラティリティ環境やノイズの多い相場で特に生じやすく、ストップロスの連続発生、収益性低下、トレーダーのストレス増大につながります。市場環境の変化を考慮しない静的なブレイクアウト戦略は、相場フェーズに応じた適応力が不足し、安定性が低下しやすくなります。

ボラティリティベースのブレイクアウトシステムは、こうした問題を解消するため、ATR (Average True Range)などのボラティリティ指標を用いて市場の強さを測定し、ブレイクアウト条件を動的に調整します。価格がレンジ境界を突破するだけでなく、所定のボラティリティ閾値も同時に満たすことを条件とすることで、実際のブレイクアウトか単なるノイズかを判断しやすくなります。これにより、十分なモメンタムが確認できた局面のみでエントリーがおこなわれ、精度向上、リスク管理の強化、そして全体的なパフォーマンスの安定化が期待できます。



計画と論理

買いブレイクアウト(ボラティリティ未突破)

買いブレイクアウト(ボラティリティ突破)

売りブレイクアウト(ボラティリティ未突破)

売りブレイクアウト(ボラティリティ突破)

ブレイクアウト判定ロジック




導入手順

//+------------------------------------------------------------------+
//|                                          Volatility Breakout.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>
#include <Trade\PositionInfo.mqh>

通常どおり、最初に必要なMQL5の取引ライブラリをインクルードします。「#include <Trade\Trade.mqh>」と「#include <Trade\PositionInfo.mqh>」です。前者はCTradeクラスへのアクセスを提供し、EAが注文のエントリー、修正、クローズ処理をおこなえるようにします。後者はCPositionInfoクラスを提供し、EAがアクティブなポジションの詳細情報(注文タイプ、ロットサイズ、ストップロス、テイクプロフィットなど)を取得できるようにします。

//+------------------------------------------------------------------+
//|                         Input Parameters                         |
//+------------------------------------------------------------------+
input group "--------------- Range Settings ---------------"
input int RangeStartTime = 600;          // Range start time (minutes from midnight)
input int RangeDuration = 120;           // Range duration in minutes
input int RangeCloseTime = 1200;         // Range close time (minutes from midnight)

input group "--------------- Trading Settings ---------------"
input double RiskPerTrade = 1.0;         // Risk percentage per trade
input double StopLossMultiplier = 1.5;   // Stop loss multiplier (range-based)
input double TakeProfitMultiplier = 2.0; // Take profit multiplier (range-based)
input bool UseTrailingStop = true;       // Enable trailing stops
input int BreakEvenAtPips = 250;         // Move to breakeven at this profit (pips)
input int TrailStartAtPips = 500;        // Start trailing at this profit (pips)
input int TrailStepPips = 100;           // Trail by this many pips

input group "--------------- Volatility Settings ---------------"
input int ATRPeriod = 14;                // ATR period for volatility calculation
input double ATRMultiplier = 2.0;        // ATR multiplier for volatility stops

ここでは、トレーダーがエキスパートアドバイザー(EA)を使用する際にカスタマイズ可能な入力パラメータを定義します。最初のグループであるRange Settingsは、レンジ構築に使う時間帯を指定するためのものです。RangeStartTimeはレンジ開始時刻(深夜0時からの分単位)、RangeDurationはレンジを形成する期間、RangeCloseTimeはセッション終了時刻を定義します。これらの設定により、EAは特定の市場時間帯に合わせてブレイクアウトロジックを構築でき、活発な取引時間帯を狙う場合に特に有効です。

2つ目と3つ目のグループは、システムの取引ロジックとボラティリティ条件を設定します。Trading Settingsでは、ポジションサイズを決定するRiskPerTrade、レンジ幅を基準にしたストップロスおよびテイクプロフィットの倍率、さらにブレイクイーブン移動やトレーリングストップ(BreakEvenAtPips、TrailStartAtPips、TrailStepPips)といったリスク管理ロジックを制御します。一方、Volatility SettingsではATR(Average True Range)インジケーターを使用し、ATRPeriodとATRMultiplierに基づいてボラティリティベースのストップレベルを計算します。これにより、ブレイクアウトの条件が市場環境の変動に動的に適応し、だましシグナルの発生リスクを抑制します。

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
long ExpertMagicNumber = 20250908;
CTrade TradeManager;
CPositionInfo PositionInfo;
MqlTick CurrentTick;

// Range structure
struct TradingSession {
   datetime SessionStart;
   datetime SessionEnd;
   datetime SessionClose;
   double SessionHigh;
   double SessionLow;
   bool IsActive;
   bool HighBreakoutTriggered;
   bool LowBreakoutTriggered;
   datetime LastCalculationDate;
   
   TradingSession() : SessionStart(0), SessionEnd(0), SessionClose(0), SessionHigh(0), 
                     SessionLow(DBL_MAX), IsActive(false), HighBreakoutTriggered(false), 
                     LowBreakoutTriggered(false), LastCalculationDate(0) {};
};

TradingSession CurrentSession;

// Volatility stops
double UpperVolatilityStop = 0.0;
double LowerVolatilityStop = 0.0;
int ATRHandle = INVALID_HANDLE;

このセクションでは、EA全体で使用するグローバル変数を宣言します。ExpertMagicNumberは、このEAが発生させた注文を識別するための一意の値で、手動注文や他のEAによる注文と区別するために使用します。CTradeのTradeManagerオブジェクトは、取引の実行と管理を担当し、CPositionInfoのPositionInfoはポジション情報を取得します。MqlTickのCurrentTick変数には、最新のレート(Bid、Ask、タイムスタンプ)が格納され、取引ロジックが常に最新の市場データを基に実行されるようにします。

また、取引セッションに関する情報を整理するための構造体を定義しており、開始および終了の時刻、セッション中の最高値と最安値、そして既にブレイクアウトが発生したかどうかを示すフラグなどを保持します。この構造体のインスタンスであるCurrentSessionは、当日のセッションデータを管理します。さらに、ボラティリティ管理のための変数も設定されます。UpperVolatilityStopとLowerVolatilityStopはATRを基準とした動的な閾値を示し、ATRHandleにはATR値を取得するためのインジケーターハンドルを保持します。これらの変数が、ブレイクアウト検出とトレード執行の基盤となります。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // Validate input parameters
   if(RiskPerTrade <= 0 || RiskPerTrade > 5) {
      Alert("Risk per trade must be between 0.1 and 5.0");
      return INIT_PARAMETERS_INCORRECT;
   }
   
   // Initialize ATR indicator
   ATRHandle = iATR(_Symbol, _Period, ATRPeriod);
   if(ATRHandle == INVALID_HANDLE) {
      Alert("Failed to create ATR indicator");
      return INIT_FAILED;
   }
   
   TradeManager.SetExpertMagicNumber(ExpertMagicNumber);
   
   // Calculate initial session
   CalculateTradingSession();
   
   return INIT_SUCCEEDED;
}

OnInit()関数はEA起動時に実行され、取引の初期準備をおこないます。まず、入力パラメータを検証し、リスク設定が許容範囲内にあるかを確認します。範囲外であればエラーを返してEAを停止します。次に、ボラティリティ算出のためのATRインジケーターを初期化し、ハンドルの取得を試みます。ここでハンドルが正常に作成されなかった場合も、EAは処理を終了します。その後、TradeManagerにExpertMagicNumberを設定し、取引を正しく追跡できるようにします。最後に、初期の取引セッションを構築するためにCalculateTradingSession()を呼び出し、問題がなければ正常終了を示すステータスを返します。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // Clean up indicators
   if(ATRHandle != INVALID_HANDLE) {
      IndicatorRelease(ATRHandle);
   }
   
   // Delete all graphical objects
   ObjectsDeleteAll(0, -1, -1);
}

OnDeinit()関数は、EAがチャートから削除される場合や停止される場合に実行されます。この関数では、最初にATRインジケーターのハンドルを解放して関連リソースを確保し、続いてチャート上のすべてのグラフィカルオブジェクトを削除して、描画要素が残らないよう後処理をおこないます。

//+------------------------------------------------------------------+
//| Check if we need to calculate a new session for the day          |
//+------------------------------------------------------------------+
void CheckForNewSession()
{
   MqlDateTime currentTime;
   TimeToStruct(CurrentTick.time, currentTime);
   
   MqlDateTime lastCalcTime;
   TimeToStruct(CurrentSession.LastCalculationDate, lastCalcTime);
   
   // Check if we're in a new day or if session hasn't been calculated yet
   if (currentTime.day != lastCalcTime.day || 
       currentTime.mon != lastCalcTime.mon || 
       currentTime.year != lastCalcTime.year ||
       CurrentSession.LastCalculationDate == 0) {
      CalculateTradingSession();
   }
}

CheckForNewSession()関数は、新しい日が始まったタイミングで最新の取引セッションを再計算する処理をおこないます。現在のティック時刻と、前回セッションを計算した時刻をそれぞれ日付構造体へ変換し、日、月、年を比較します。現在の日付が前回記録したセッション日と異なる場合、もしくはまだセッションが初期化されていない場合には、CalculateTradingSession()を呼び出して新しいセッションのセットアップを実行します。

//+------------------------------------------------------------------+
//| Calculate trading session for the day                            |
//+------------------------------------------------------------------+
void CalculateTradingSession()
{
   // Reset session variables
   CurrentSession.SessionStart = 0;
   CurrentSession.SessionEnd = 0;
   CurrentSession.SessionClose = 0;
   CurrentSession.SessionHigh = 0;
   CurrentSession.SessionLow = DBL_MAX;
   CurrentSession.IsActive = false;
   CurrentSession.HighBreakoutTriggered = false;
   CurrentSession.LowBreakoutTriggered = false;
   
   // Calculate session times
   int timeCycle = 86400; // Seconds in a day
   CurrentSession.SessionStart = (CurrentTick.time - (CurrentTick.time % timeCycle)) + RangeStartTime * 60;
   
   // Adjust for weekends
   for(int i = 0; i < 8; i++) {
      MqlDateTime tmp;
      TimeToStruct(CurrentSession.SessionStart, tmp);
      int dayOfWeek = tmp.day_of_week;
      if(CurrentTick.time >= CurrentSession.SessionStart || dayOfWeek == 0 || dayOfWeek == 6) {
         CurrentSession.SessionStart += timeCycle;
      }
   }

   // Calculate session end time
   CurrentSession.SessionEnd = CurrentSession.SessionStart + (RangeDuration * 60);
   for(int i = 0; i < 2; i++) {
      MqlDateTime tmp;
      TimeToStruct(CurrentSession.SessionEnd, tmp);
      int dayOfWeek = tmp.day_of_week;
      if(dayOfWeek == 0 || dayOfWeek == 6) {
         CurrentSession.SessionEnd += timeCycle;
      }
   }
   
   // Calculate session close time
   CurrentSession.SessionClose = (CurrentSession.SessionEnd - (CurrentSession.SessionEnd % timeCycle)) + RangeCloseTime * 60;
   for(int i = 0; i < 3; i++) {
      MqlDateTime tmp;
      TimeToStruct(CurrentSession.SessionClose, tmp);
      int dayOfWeek = tmp.day_of_week;
      if(CurrentSession.SessionClose <= CurrentSession.SessionEnd || dayOfWeek == 0 || dayOfWeek == 6) {
         CurrentSession.SessionClose += timeCycle;
      }
   }
   
   // Set last calculation date
   CurrentSession.LastCalculationDate = CurrentTick.time;
   
   // Draw session objects
   DrawSessionObjects();
}

CalculateTradingSession()関数は、セッション関連の変数をすべてリセットし、ユーザー入力に基づいて正しい開始時刻、終了時刻、クローズ時刻を計算することで、1日の取引セッションを設定します。この処理により、セッションは日次サイクルに正しく揃えられ、週末を自動的にスキップすることで、有効な市場営業日にのみ取引がおこなわれるようになります。さらに、ブレイクアウト判定フラグを初期化し、セッション計算日を更新した上で、DrawSessionObjects()を呼び出してチャート上にセッション境界やレベルを視覚的に描画します。

//+------------------------------------------------------------------+
//| Update trading session with current price data                   |
//+------------------------------------------------------------------+
void UpdateTradingSession()
{
   if(CurrentTick.time >= CurrentSession.SessionStart && CurrentTick.time < CurrentSession.SessionEnd) {
      CurrentSession.IsActive = true;
      
      // Update session high
      if(CurrentTick.ask > CurrentSession.SessionHigh) {
         CurrentSession.SessionHigh = CurrentTick.ask;
      }
      
      // Update session low
      if(CurrentTick.bid < CurrentSession.SessionLow) {
         CurrentSession.SessionLow = CurrentTick.bid;
      }
      
      // Draw session on chart
      DrawSessionObjects();
   }
}

UpdateTradingSession()関数は、新しいティックが到着するたびに現在のセッション情報を更新します。現在時刻がセッション期間内であればセッションをアクティブ状態にし、その上で、最も高いask価格と最も低いbid価格を監視し、セッションの高値と安値を更新します。最後に、最新のレベルをチャートへ反映するためにDrawSessionObjects()を呼び出します。

//+------------------------------------------------------------------+
//| Calculate volatility stops using ATR                             |
//+------------------------------------------------------------------+
void CalculateVolatilityStops()
{
   double atrValue[1];
   if(CopyBuffer(ATRHandle, 0, 0, 1, atrValue) <= 0) {
      Print("Failed to get ATR value");
      return;
   }
   
   // Validate ATR value to prevent "inf" errors
   if(atrValue[0] <= 0 || !MathIsValidNumber(atrValue[0]) || atrValue[0] > 1000) {
      Print("Invalid ATR value: ", atrValue[0], ", using default");
      atrValue[0] = 10 * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   }
   
   // Calculate volatility stops
   UpperVolatilityStop = CurrentSession.SessionHigh + (atrValue[0] * ATRMultiplier);
   LowerVolatilityStop = CurrentSession.SessionLow - (atrValue[0] * ATRMultiplier);
   
   // Draw volatility stops on chart
   DrawVolatilityStops();
}

CalculateVolatilityStops()関数は、ATRインジケーターを用いて市場のボラティリティに基づく動的な上限ストップと下限ストップを計算します。まず、最新のATR値を取得し、不正値を避けるために検証をおこない、必要であれば安全なデフォルト値を使用します。その後、セッション高値と安値に対してATR値(ATRMultiplierでスケーリング)を加算または減算することで、上限および下限のボラティリティストップを算出します。最後に、算出したレベルをチャートへ表示するためにDrawVolatilityStops()を呼び出します。

//+------------------------------------------------------------------+
//| Check for breakouts and execute trades                           |
//+------------------------------------------------------------------+
void CheckForBreakouts()
{
   // Only check after session formation period
   if(CurrentTick.time < CurrentSession.SessionEnd || !CurrentSession.IsActive) {
      return;
   }
   
   // Check for high breakout
   if(!CurrentSession.HighBreakoutTriggered && CurrentTick.ask >= CurrentSession.SessionHigh) {
      CurrentSession.HighBreakoutTriggered = true;
      
      // Check if price has cleared volatility stop
      if(CurrentTick.ask >= UpperVolatilityStop) {
         // Regular breakout - execute buy
         ExecuteTrade(ORDER_TYPE_BUY, "Session High Breakout");
      } else {
         // False breakout - execute sell (fade)
         ExecuteTrade(ORDER_TYPE_SELL, "False Breakout - Fade High");
      }
   }
   
   // Check for low breakout
   if(!CurrentSession.LowBreakoutTriggered && CurrentTick.bid <= CurrentSession.SessionLow) {
      CurrentSession.LowBreakoutTriggered = true;
      
      // Check if price has cleared volatility stop
      if(CurrentTick.bid <= LowerVolatilityStop) {
         // Regular breakout - execute sell
         ExecuteTrade(ORDER_TYPE_SELL, "Session Low Breakout");
      } else {
         // False breakout - execute buy (fade)
         ExecuteTrade(ORDER_TYPE_BUY, "False Breakout - Fade Low");
      }
   }
}

CheckForBreakouts()関数は、セッションが形成されアクティブになった後の価格動向を監視し、ブレイクアウトの可能性を判定します。価格がセッション高値または安値をクロスしたかどうかを確認し、各ブレイクアウトがセッション内で一度だけ発生するよう制御します。実際の取引は、価格が対応するボラティリティストップを上回るかどうかに基づいて実行されます。価格がボラティリティ閾値を超えた場合はブレイク方向へエントリーがおこなわれ、反対に、突破が確認できない場合はだましと判断し、逆方向のフェードトレードを実行します。

//+------------------------------------------------------------------+
//| Execute trade with proper risk management                        |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE orderType, string comment)
{
   // Calculate position size based on risk
   double lotSize = CalculatePositionSize();
   if(lotSize <= 0) {
      Print("Failed to calculate position size");
      return;
   }
   
   // Calculate stop loss and take profit
   double stopLoss = 0.0;
   double takeProfit = 0.0;
   CalculateStopLevels(orderType, stopLoss, takeProfit);
   
   // Validate stop levels
   if(!ValidateStopLevels(orderType, stopLoss, takeProfit)) {
      Print("Invalid stop levels - trade not executed");
      return;
   }
   
   // Execute trade
   if(orderType == ORDER_TYPE_BUY) {
      TradeManager.Buy(lotSize, _Symbol, CurrentTick.ask, stopLoss, takeProfit, comment);
   } else {
      TradeManager.Sell(lotSize, _Symbol, CurrentTick.bid, stopLoss, takeProfit, comment);
   }
   
   // Check result
   if(TradeManager.ResultRetcode() != TRADE_RETCODE_DONE) {
      Print("Trade execution failed: ", TradeManager.ResultRetcodeDescription());
   }
}

ExecuteTrade()関数は、適切なリスク管理に基づいて取引を開始する処理を担当します。最初にCalculatePositionSize()を用いてポジションサイズを算出し、次にCalculateStopLevels()でストップロスとテイクプロフィットの水準を決定します。続いてValidateStopLevels()でこれらの水準を検証し、問題がなければTradeManagerオブジェクトを使用して買いまたは売りの注文を実行します。注文が正常に実行されなかった場合には、エラーメッセージを記録します。

//+------------------------------------------------------------------+
//| Calculate position size based on risk percentage                 |
//+------------------------------------------------------------------+
double CalculatePositionSize()
{
   double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
   double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   
   if(accountBalance <= 0 || tickSize <= 0 || tickValue <= 0 || point <= 0) {
      return 0.0;
   }
   
   // Get ATR value for stop loss distance
   double atrValue[1];
   if(CopyBuffer(ATRHandle, 0, 0, 1, atrValue) <= 0) {
      return 0.0;
   }
   
   // Validate ATR value
   if(atrValue[0] <= 0 || !MathIsValidNumber(atrValue[0])) {
      return 0.0;
   }
   
   // Calculate stop distance in points
   double stopDistance = atrValue[0] * StopLossMultiplier / point;
   
   // Calculate risk amount
   double riskAmount = accountBalance * (RiskPerTrade / 100.0);
   
   // Calculate position size
   double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   double lotSize = (riskAmount / (stopDistance * tickValue)) * tickSize;
   
   // Normalize lot size
   lotSize = MathFloor(lotSize / lotStep) * lotStep;
   
   // Apply min/max limits
   double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   lotSize = MathMax(minLot, MathMin(maxLot, lotSize));
   
   return lotSize;
}

この関数は、口座残高、ユーザーが設定したリスク率、そして現在の市場状況に基づいて適切な取引数量を算出します。まず、ティックサイズ、ティック値、ポイントサイズなど、銘柄固有の情報を取得し、次にStopLossMultiplierを乗じたATR値を用いてストップ距離を計算します。算出したリスク許容額を基にロット数量を求め、ブローカーが許容するステップに正規化した上で、最小量と最大量の範囲に収まっているかを確認します。最終的に、条件を満たしたポジションサイズを返します。

//+------------------------------------------------------------------+
//| Calculate stop loss and take profit levels                       |
//+------------------------------------------------------------------+
void CalculateStopLevels(ENUM_ORDER_TYPE orderType, double &stopLoss, double &takeProfit)
{
   // Use range-based stops
   double rangeSize = CurrentSession.SessionHigh - CurrentSession.SessionLow;
   
   if(orderType == ORDER_TYPE_BUY) {
      stopLoss = CurrentTick.bid - (rangeSize * StopLossMultiplier / 100.0);
      takeProfit = CurrentTick.ask + (rangeSize * TakeProfitMultiplier / 100.0);
   } else {
      stopLoss = CurrentTick.ask + (rangeSize * StopLossMultiplier / 100.0);
      takeProfit = CurrentTick.bid - (rangeSize * TakeProfitMultiplier / 100.0);
   }
   
   // Normalize prices
   stopLoss = NormalizeDouble(stopLoss, _Digits);
   takeProfit = NormalizeDouble(takeProfit, _Digits);
}

//+------------------------------------------------------------------+
//| Validate stop loss and take profit levels                        |
//+------------------------------------------------------------------+
bool ValidateStopLevels(ENUM_ORDER_TYPE orderType, double stopLoss, double takeProfit)
{
   // Check if stop levels are valid
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   double minStopDistance = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * point;
   
   if(orderType == ORDER_TYPE_BUY) {
      if(CurrentTick.ask - stopLoss < minStopDistance) {
         Print("Stop loss too close for buy order");
         return false;
      }
      if(takeProfit - CurrentTick.ask < minStopDistance) {
         Print("Take profit too close for buy order");
         return false;
      }
   } else {
      if(stopLoss - CurrentTick.bid < minStopDistance) {
         Print("Stop loss too close for sell order");
         return false;
      }
      if(CurrentTick.bid - takeProfit < minStopDistance) {
         Print("Take profit too close for sell order");
         return false;
      }
   }
   
   return true;
}

CalculateStopLevels()関数は、現在のセッションレンジに基づいて取引のストップロスとテイクプロフィットを算出します。買い注文の場合は、ユーザーが設定した倍率に基づいて、ストップロスを現在のBidより下、テイクプロフィットを現在のAskより上に設定します。売り注文の場合は、この関係が反転します。どちらの水準も、対象銘柄の小数精度に合わせて正規化し、執行時の誤差を防ぎます。

ValidateStopLevels()関数は、算出されたストップロスとテイクプロフィットがブローカーの最小ストップ距離要件を満たしているかを確認します。エントリー価格と各ストップの距離が、SYMBOL_TRADE_STOPS_LEVELにポイントサイズを掛け合わせた値を超えているかを検証し、無効または距離が近すぎる注文を防ぎます。距離が不足している場合はfalseを返し、エラーメッセージを出力することで、有効な取引のみが実行されるようにします。

//+------------------------------------------------------------------+
//| Manage trailing stops for open positions                         |
//+------------------------------------------------------------------+
void ManageTrailingStops()
{
   for(int i = PositionsTotal() - 1; i >= 0; i--) {
      ulong ticket = PositionGetTicket(i);
      
      if(PositionSelectByTicket(ticket) && 
         PositionGetString(POSITION_SYMBOL) == _Symbol && 
         PositionGetInteger(POSITION_MAGIC) == ExpertMagicNumber) {
         
         ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
         double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
         double currentStop = PositionGetDouble(POSITION_SL);
         double currentProfit = PositionGetDouble(POSITION_PROFIT);
         double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
         
         // Calculate profit in pips
         double profitInPips = 0;
         if(positionType == POSITION_TYPE_BUY) {
            profitInPips = (CurrentTick.bid - openPrice) / point;
         } else {
            profitInPips = (openPrice - CurrentTick.ask) / point;
         }
         
         // Check if we should move to breakeven
         if(profitInPips >= BreakEvenAtPips && currentStop != openPrice) {
            if(positionType == POSITION_TYPE_BUY) {
               TradeManager.PositionModify(ticket, openPrice, PositionGetDouble(POSITION_TP));
            } else {
               TradeManager.PositionModify(ticket, openPrice, PositionGetDouble(POSITION_TP));
            }
         }
         
         // Check if we should start trailing
         if(profitInPips >= TrailStartAtPips) {
            double newStop = 0;
            
            if(positionType == POSITION_TYPE_BUY) {
               newStop = CurrentTick.bid - (TrailStepPips * point);
               
               // Only move stop if it's higher than current
               if(newStop > currentStop || currentStop == 0) {
                  TradeManager.PositionModify(ticket, newStop, PositionGetDouble(POSITION_TP));
               }
            } else {
               newStop = CurrentTick.ask + (TrailStepPips * point);
               
               // Only move stop if it's lower than current
               if(newStop < currentStop || currentStop == 0) {
                  TradeManager.PositionModify(ticket, newStop, PositionGetDouble(POSITION_TP));
               }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Draw session objects on chart                                    |
//+------------------------------------------------------------------+
void DrawSessionObjects()
{
   // Draw session high
   ObjectDelete(0, "SessionHigh");
   ObjectCreate(0, "SessionHigh", OBJ_HLINE, 0, 0, CurrentSession.SessionHigh);
   ObjectSetInteger(0, "SessionHigh", OBJPROP_COLOR, clrBlue);
   ObjectSetInteger(0, "SessionHigh", OBJPROP_STYLE, STYLE_DASH);
   ObjectSetInteger(0, "SessionHigh", OBJPROP_WIDTH, 2);
   
   // Draw session low
   ObjectDelete(0, "SessionLow");
   ObjectCreate(0, "SessionLow", OBJ_HLINE, 0, 0, CurrentSession.SessionLow);
   ObjectSetInteger(0, "SessionLow", OBJPROP_COLOR, clrBlue);
   ObjectSetInteger(0, "SessionLow", OBJPROP_STYLE, STYLE_DASH);
   ObjectSetInteger(0, "SessionLow", OBJPROP_WIDTH, 2);
   
   // Draw session start time
   ObjectDelete(0, "SessionStart");
   ObjectCreate(0, "SessionStart", OBJ_VLINE, 0, CurrentSession.SessionStart, 0);
   ObjectSetInteger(0, "SessionStart", OBJPROP_COLOR, clrBlue);
   ObjectSetInteger(0, "SessionStart", OBJPROP_WIDTH, 2);
   
   // Draw session end time
   ObjectDelete(0, "SessionEnd");
   ObjectCreate(0, "SessionEnd", OBJ_VLINE, 0, CurrentSession.SessionEnd, 0);
   ObjectSetInteger(0, "SessionEnd", OBJPROP_COLOR, clrRed);
   ObjectSetInteger(0, "SessionEnd", OBJPROP_WIDTH, 2);
   
   // Draw session close time
   ObjectDelete(0, "SessionClose");
   ObjectCreate(0, "SessionClose", OBJ_VLINE, 0, CurrentSession.SessionClose, 0);
   ObjectSetInteger(0, "SessionClose", OBJPROP_COLOR, clrDarkRed);
   ObjectSetInteger(0, "SessionClose", OBJPROP_WIDTH, 2);
}

//+------------------------------------------------------------------+
//| Draw volatility stops on chart                                   |
//+------------------------------------------------------------------+
void DrawVolatilityStops()
{
   // Draw upper volatility stop
   ObjectDelete(0, "VolatilityStopUpper");
   ObjectCreate(0, "VolatilityStopUpper", OBJ_HLINE, 0, 0, UpperVolatilityStop);
   ObjectSetInteger(0, "VolatilityStopUpper", OBJPROP_COLOR, clrGreen);
   ObjectSetInteger(0, "VolatilityStopUpper", OBJPROP_STYLE, STYLE_DOT);
   ObjectSetInteger(0, "VolatilityStopUpper", OBJPROP_WIDTH, 1);
   
   // Draw lower volatility stop
   ObjectDelete(0, "VolatilityStopLower");
   ObjectCreate(0, "VolatilityStopLower", OBJ_HLINE, 0, 0, LowerVolatilityStop);
   ObjectSetInteger(0, "VolatilityStopLower", OBJPROP_COLOR, clrRed);
   ObjectSetInteger(0, "VolatilityStopLower", OBJPROP_STYLE, STYLE_DOT);
   ObjectSetInteger(0, "VolatilityStopLower", OBJPROP_WIDTH, 1);
}

ManageTrailingStops()関数は、保有中の取引が利益方向へ進んだ際に、動的に管理をおこなうための処理です。すべてのポジションを反復処理し、銘柄とマジックナンバーによってこのEAが管理するポジションだけを抽出します。その後、利益をpips換算で算出し、条件に応じて一定の利益に達した時点でストップロスを建値へ移動したり、トレーリングストップを適用して利益を段階的に確保したりします。これにより、手動操作を必要とせず、リスク低減と利益保護を自動的に実現します。

DrawSessionObjects()関数は、現在の取引セッションをチャート上に視覚的に表示する役割を持ちます。セッション高値と安値の水平ライン、セッション開始、終了、クローズ時刻の垂直ラインを生成し、それぞれ異なる色やスタイルで描画します。これにより、チャート上でセッション境界や主要レベルを明確に把握できるようになります。

最後に、DrawVolatilityStops()関数は、計算済みのボラティリティベースのストップレベルをチャート上に表示します。上側のボラティリティストップは緑の点線、下側は赤の点線で描画されます。これらの点線は、ブレイクアウトの検証やだまし判定に役立つ視覚的な基準を提供し、価格動向とボラティリティを結びつけた判断を容易にします。



バックテスト結果

バックテストは1時間足で実施し、約2か月間の検証期間(2025年7月7日から2025年9月8日)を対象としました。使用した設定は以下のとおりです。


結論

本記事ではセッションレンジ検出、ATRを用いたボラティリティ分析、体系化された取引管理を組み合わせてボラティリティベースのブレイクアウトシステムを構築しました。システムはまず日次セッションを特定し、セッション高値と安値を追跡し、実際のブレイクアウトとだましを区別するためのボラティリティストップを算出します。その後、ブレイクアウト判定ロジックを適用し、動的なストップロスとテイクプロフィットを設定した上で注文を実行し、ポジションサイズ、建値移動、トレーリングストップによる堅牢なリスク管理を実装します。また、セッションマーカーやボラティリティストップラインなどの視覚要素をチャート上に描画し、相場状況を明確に把握できるようにしています。

このシステムは、従来型ブレイクアウト戦略が抱える典型的な問題、例えばボラティリティを考慮せずにだましによってエントリーしてしまうといった課題を回避するのに役立ちます。ATRに基づくボラティリティストップを利用することで、恣意的な水準ではなく、実際の市場ダイナミクスと整合した取引が可能になります。さらに、自動化されたリスク管理とチャート表示機能により、意思決定の明確性と規律性を高め、市場環境の変化に適応できる信頼性の高いブレイクアウト取引フレームワークを提供します。

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

添付されたファイル |
ParafracおよびParafrac V2オシレーターを使用した取引戦略の開発:シングルエントリーパフォーマンスインサイト ParafracおよびParafrac V2オシレーターを使用した取引戦略の開発:シングルエントリーパフォーマンスインサイト
本記事では、ParaFracオシレーターとその後継であるV2モデルを取引ツールとして紹介し、これらを用いて構築した3種類の取引戦略を解説します。各戦略をテストおよび最適化し、それぞれの強みと弱みを明らかにします。比較分析によって両モデルの性能差を明確にしました。
MQL5でのデータベースの簡素化(第2回):メタプログラミングを使用してエンティティを作成する MQL5でのデータベースの簡素化(第2回):メタプログラミングを使用してエンティティを作成する
前回の記事では、MQL5における#defineを活用した高度なメタプログラミング手法を検討し、テーブルや列のメタデータ(データ型、主キー、オートインクリメント、NULL許容など)を表現するエンティティを定義しました。これらの定義はTickORM.mqhに集約し、メタデータクラスを自動生成する仕組みを整えることで、SQLを直接記述することなくORMが効率的にデータ操作を実行できる基盤を構築しています。
MQL5でのAI搭載取引システムの構築(第2回):ChatGPT統合型アプリケーションのUI開発 MQL5でのAI搭載取引システムの構築(第2回):ChatGPT統合型アプリケーションのUI開発
本記事では、MQL5でChatGPTを統合したプログラムを開発します。このプログラムでは、第1回で作成したJSON解析フレームワークを活用してOpenAIのAPIにプロンプトを送信し、MetaTrader 5のチャート上に応答を表示します。入力フィールド、送信ボタン、応答表示を備えたダッシュボードを実装し、API通信やテキストの折り返し処理をおこなうことで、ユーザーとのインタラクションを実現します。
プライスアクション分析ツールキットの開発(第41回):MQL5で統計的価格レベルEAを構築する プライスアクション分析ツールキットの開発(第41回):MQL5で統計的価格レベルEAを構築する
統計は常に金融分析の中心にあります。統計とは、データを収集・分析・解釈・提示し、意味のある情報に変換する学問です。これをローソク足に応用すると、価格の生データを測定可能な洞察に圧縮できます。特定期間における市場の中心傾向、分布、広がりを把握できれば、どれほど有益でしょうか。本記事では、統計的手法を用いてローソク足データを明確で実行可能なシグナルに変換する方法を紹介します。