//+------------------------------------------------------------------+
//|                                          Volatility Breakout.mq5 |
//|                        GIT under Copyright 2025, MetaQuotes Ltd. |
//|                     https://www.mql5.com/en/users/johnhlomohang/ |
//+------------------------------------------------------------------+
#property copyright "GIT under Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/johnhlomohang/"
#property version   "1.00"

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>

//+------------------------------------------------------------------+
//|                         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

//+------------------------------------------------------------------+
//| 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;

//+------------------------------------------------------------------+
//| 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;
}

//+------------------------------------------------------------------+
//| 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);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // Get current tick data
   SymbolInfoTick(_Symbol, CurrentTick);
   
   // Check if we need to calculate a new session (new day)
   CheckForNewSession();
   
   // Update trading session with current price data
   UpdateTradingSession();
   
   // Calculate volatility stops
   CalculateVolatilityStops();
   
   // Check for breakouts
   CheckForBreakouts();
   
   // Manage open positions
   if(UseTrailingStop) {
      ManageTrailingStops();
   }
}

//+------------------------------------------------------------------+
//| 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();
   }
}

//+------------------------------------------------------------------+
//| 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();
}

//+------------------------------------------------------------------+
//| 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();
   }
}

//+------------------------------------------------------------------+
//| 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();
}

//+------------------------------------------------------------------+
//| 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_SELL, "Session High Breakout");
      } else {
         // False breakout - execute sell (fade)
         ExecuteTrade(ORDER_TYPE_BUY, "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_BUY, "Session Low Breakout");
      } else {
         // False breakout - execute buy (fade)
         ExecuteTrade(ORDER_TYPE_SELL, "False Breakout - Fade Low");
      }
   }
}

//+------------------------------------------------------------------+
//| 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());
   }
}

//+------------------------------------------------------------------+
//| 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;
}

//+------------------------------------------------------------------+
//| 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;
}

//+------------------------------------------------------------------+
//| 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);
}
