BreakRevertPro

Author: Mustafa Seyyid Sahin
Price Data Components
Orders Execution
It automatically opens orders when conditions are reached
Indicators Used
Indicator of the average true range
Miscellaneous
Uses files from the file system
2 Views
0 Downloads
0 Favorites
BreakRevertPro
ÿþ//+------------------------------------------------------------------+

//|                                              BreakRevertPro.mq5  |

//|                                        Mustafa Seyyid Sahin      |

//+------------------------------------------------------------------+

#property copyright "Mustafa Seyyid Sahin"

#property version   "1.00"



#include <Trade\Trade.mqh>

#include <Arrays\ArrayDouble.mqh>

#include <Math\Stat\Weibull.mqh>

#include <Math\Stat\Poisson.mqh>

#include <Math\Stat\Exponential.mqh>



// Definition of trade types

enum ENUM_TRADE_TYPE

{

   TRADE_BUY = 0,  // Buy order

   TRADE_SELL = 1  // Sell order

};



// Input parameters

input double          risk_per_trade           = 1.0;    // Risk per trade in %

input int             lookback_period          = 1;      // Number of candles for probability calculation

input double          breakout_threshold       = 0.4;    // Minimum probability for breakout

input double          mean_reversion_threshold = 0.4;    // Threshold for mean reversion

input ENUM_TIMEFRAMES TF_M1                    = PERIOD_M1;  // M1 Timeframe

input ENUM_TIMEFRAMES TF_M15                   = PERIOD_M15; // M15 Timeframe

input ENUM_TIMEFRAMES TF_H1                    = PERIOD_H1;  // H1 Timeframe

input int             trade_delay_seconds      = 10;     // Seconds between trades

input int             max_positions            = 1;      // Maximum number of open positions

input bool            enable_safety_trade      = true;   // Enable safety trade during testing

input int             safety_trade_interval    = 60;     // Seconds between safety trade checks

input int             Magic_Number             = 123456; // Magic Number for EA



//+------------------------------------------------------------------+

//| Validator class for complete trading validation                  |

//+------------------------------------------------------------------+

class CTradeValidator

{

private:

    string           m_symbol;                // Current symbol

    double           m_min_lot;               // Minimum lot size

    double           m_max_lot;               // Maximum lot size

    double           m_lot_step;              // Lot step

    double           m_point;                 // Point value

    int              m_digits;                // Decimal places

    int              m_stops_level;           // Stops level in points

    double           m_tick_size;             // Minimum price change

    double           m_tick_value;            // Tick value in account currency

    ENUM_SYMBOL_CALC_MODE m_calc_mode;        // Calculation mode (Forex, CFD, etc.)

    

    // Helper functions

    bool             LoadSymbolInfo();        // Load symbol information

    void             LogValidationInfo(string message);  // Special logging



public:

                     CTradeValidator();

                    ~CTradeValidator() {};

    

    // Initialization

    bool             Init(string symbol = NULL);

    void             Refresh();              // Update all data

    

    // Environment checks

    bool             CheckHistory(int minimum_bars = 100);

    bool             IsInTester() { return MQLInfoInteger(MQL_TESTER) != 0; }

    

    // Volume validation

    double           NormalizeVolume(double volume);

    double           ValidateVolume(ENUM_ORDER_TYPE order_type, double requested_volume);

    bool             CheckMarginForVolume(ENUM_ORDER_TYPE order_type, double volume, double price = 0.0);

    

    // SL/TP validation

    double           ValidateStopLoss(ENUM_ORDER_TYPE order_type, double open_price, double desired_sl);

    double           ValidateTakeProfit(ENUM_ORDER_TYPE order_type, double open_price, double desired_tp);

    

    // Safety-Trade

    bool             ExecuteSafetyTrade();

    

    // Getters for important properties

    double           GetMinLot() { return m_min_lot; }

    double           GetMaxLot() { return m_max_lot; }

    double           GetLotStep() { return m_lot_step; }

    double           GetPoint() { return m_point; }

    int              GetDigits() { return m_digits; }

    int              GetStopsLevel() { return m_stops_level; }

    

    // Current prices

    double           Bid() { return SymbolInfoDouble(m_symbol, SYMBOL_BID); }

    double           Ask() { return SymbolInfoDouble(m_symbol, SYMBOL_ASK); }

};



//+------------------------------------------------------------------+

//| Constructor                                                      |

//+------------------------------------------------------------------+

CTradeValidator::CTradeValidator()

{

    m_symbol = _Symbol; // Default current symbol

}



//+------------------------------------------------------------------+

//| Initialization of the Validator class                            |

//+------------------------------------------------------------------+

bool CTradeValidator::Init(string symbol = NULL)

{

    // Set symbol

    if(symbol != NULL && symbol != "")

        m_symbol = symbol;

    else

        m_symbol = _Symbol;

    

    // Ensure symbol is selected

    if(!SymbolSelect(m_symbol, true))

    {

        Print("Symbol not selectable: ", m_symbol);

        return false;

    }

    

    // Load all information

    if(!LoadSymbolInfo())

    {

        Print("Error loading symbol data");

        return false;

    }

    

    return true;

}



//+------------------------------------------------------------------+

//| Load all important symbol information                            |

//+------------------------------------------------------------------+

bool CTradeValidator::LoadSymbolInfo()

{

    // Basic properties

    m_digits = (int)SymbolInfoInteger(m_symbol, SYMBOL_DIGITS);

    m_point = SymbolInfoDouble(m_symbol, SYMBOL_POINT);

    

    // Trading properties

    m_min_lot = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MIN);

    m_max_lot = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MAX);

    m_lot_step = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_STEP);

    m_stops_level = (int)SymbolInfoInteger(m_symbol, SYMBOL_TRADE_STOPS_LEVEL);

    

    // Pricing properties

    m_tick_size = SymbolInfoDouble(m_symbol, SYMBOL_TRADE_TICK_SIZE);

    m_tick_value = SymbolInfoDouble(m_symbol, SYMBOL_TRADE_TICK_VALUE);

    m_calc_mode = (ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(m_symbol, SYMBOL_TRADE_CALC_MODE);

    

    // Safeguard against faulty data

    if(m_min_lot <= 0) m_min_lot = 0.01;

    if(m_max_lot <= 0) m_max_lot = 100.0;

    if(m_lot_step <= 0) m_lot_step = 0.01;

    if(m_stops_level < 0) m_stops_level = 0;

    

    // Validation for stocks and other instruments

    if(m_calc_mode == SYMBOL_CALC_MODE_EXCH_STOCKS && m_min_lot < 1.0)

        m_min_lot = 1.0; // Stocks often have a minimum volume of 1

    

    return true;

}



//+------------------------------------------------------------------+

//| Update all data                                                  |

//+------------------------------------------------------------------+

void CTradeValidator::Refresh()

{

    LoadSymbolInfo();

}



//+------------------------------------------------------------------+

//| Special logging for validation                                   |

//+------------------------------------------------------------------+

void CTradeValidator::LogValidationInfo(string message)

{

    // Reduced logging in test mode to avoid log overflow

    if(!IsInTester() || MQLInfoInteger(MQL_VISUAL_MODE) != 0)

        Print("[Validator] ", message);

}



//+------------------------------------------------------------------+

//| Check if there is enough historical data                         |

//+------------------------------------------------------------------+

bool CTradeValidator::CheckHistory(int minimum_bars = 100)

{

    // Check if enough bars are available for the current symbol/timeframe

    if(Bars(m_symbol, PERIOD_CURRENT) < minimum_bars)

    {

        LogValidationInfo("WARNING: Not enough historical data. Required: " + 

                IntegerToString(minimum_bars) + ", Available: " + 

                IntegerToString(Bars(m_symbol, PERIOD_CURRENT)));

        return false;

    }

    

    return true;

}



//+------------------------------------------------------------------+

//| Normalize volume according to symbol requirements                |

//+------------------------------------------------------------------+

double CTradeValidator::NormalizeVolume(double volume)

{

    if(volume <= 0.0) return 0.0;

    

    // Limit to Min/Max

    if(volume < m_min_lot) 

        volume = m_min_lot;

    if(volume > m_max_lot) 

        volume = m_max_lot;

    

    // Normalize to valid step

    if(m_lot_step > 0)

    {

        int steps = (int)MathRound((volume - m_min_lot) / m_lot_step);

        volume = NormalizeDouble(m_min_lot + steps * m_lot_step, 8);

    }

    

    // Safeguard against exceeding maximum

    if(volume > m_max_lot) 

        volume = m_max_lot;

    

    return volume;

}



//+------------------------------------------------------------------+

//| Fully validate trading volume                                    |

//+------------------------------------------------------------------+

double CTradeValidator::ValidateVolume(ENUM_ORDER_TYPE order_type, double requested_volume)

{

    // Normalize volume according to symbol rules

    double normalized_volume = NormalizeVolume(requested_volume);

    

    // Check stocks against minimum volume

    if(m_calc_mode == SYMBOL_CALC_MODE_EXCH_STOCKS && normalized_volume < 1.0)

        normalized_volume = 1.0;

    

    // Margin check for normalized volume

    if(!CheckMarginForVolume(order_type, normalized_volume))

    {

        // If margin is insufficient, find a volume that works

        double test_volume = normalized_volume;

        while(test_volume >= m_min_lot)

        {

            test_volume = NormalizeDouble(test_volume * 0.75, 2); // Reduce by 25%

            if(test_volume < m_min_lot) 

                test_volume = m_min_lot;

            

            if(CheckMarginForVolume(order_type, test_volume))

                return test_volume;

                

            if(test_volume == m_min_lot)

                break; // If even min_lot doesn't have enough margin, then stop

        }

        

        LogValidationInfo("Not enough margin for the requested volume");

        return 0.0; // Cannot trade

    }

    

    return normalized_volume;

}



//+------------------------------------------------------------------+

//| Check if there is enough margin for the volume                   |

//+------------------------------------------------------------------+

bool CTradeValidator::CheckMarginForVolume(ENUM_ORDER_TYPE order_type, double volume, double price = 0.0)

{

    if(volume <= 0.0) return false;

    

    // If no price provided, use current market price

    if(price <= 0.0)

    {

        bool is_buy = (order_type == ORDER_TYPE_BUY || order_type == ORDER_TYPE_BUY_LIMIT || 

                      order_type == ORDER_TYPE_BUY_STOP || order_type == ORDER_TYPE_BUY_STOP_LIMIT);

        

        price = is_buy ? Ask() : Bid();

    }

    

    // Calculate required margin

    double margin = 0.0;

    if(!OrderCalcMargin(order_type, m_symbol, volume, price, margin))

    {

        LogValidationInfo("Error in OrderCalcMargin: " + IntegerToString(GetLastError()));

        return false;

    }

    

    // Check free margin in account with safety buffer (15%)

    double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);

    double required_margin = margin * 1.15; // 15% reserve

    

    return (free_margin >= required_margin);

}



//+------------------------------------------------------------------+

//| Validate and correct the StopLoss price                          |

//+------------------------------------------------------------------+

double CTradeValidator::ValidateStopLoss(ENUM_ORDER_TYPE order_type, double open_price, double desired_sl)

{

    if(open_price <= 0.0) return 0.0;

    

    // For zero SL, just return 0 (no SL)

    if(desired_sl <= 0.0) return 0.0;

    

    bool is_buy = (order_type == ORDER_TYPE_BUY || order_type == ORDER_TYPE_BUY_LIMIT || 

                  order_type == ORDER_TYPE_BUY_STOP || order_type == ORDER_TYPE_BUY_STOP_LIMIT);

    

    // Make sure SL is in the correct direction

    if(is_buy && desired_sl >= open_price) 

    {

        LogValidationInfo("Invalid SL for Buy: SL must be below opening price");

        return 0.0; // Don't set SL

    }

    else if(!is_buy && desired_sl <= open_price)

    {

        LogValidationInfo("Invalid SL for Sell: SL must be above opening price");

        return 0.0; // Don't set SL

    }

    

    // Current price for distance calculation

    double current_price = is_buy ? Bid() : Ask();

    

    // Minimum distance in points with additional safety buffer

    int stops_level = m_stops_level;

    if(stops_level <= 0) stops_level = 5; // At least 5 points if not defined

    

    // 20% additional safety buffer for validator

    double min_distance = stops_level * m_point * 1.2;

    

    // Calculate valid SL price

    double valid_sl = 0.0;

    

    if(is_buy)

    {

        // For Buy orders, SL must be below current price

        double max_sl = current_price - min_distance;

        

        // If desired SL is higher than allowed, correct it

        valid_sl = (desired_sl > max_sl) ? max_sl : desired_sl;

    }

    else

    {

        // For Sell orders, SL must be above current price

        double min_sl = current_price + min_distance;

        

        // If desired SL is lower than allowed, correct it

        valid_sl = (desired_sl < min_sl) ? min_sl : desired_sl;

    }

    

    // Normalize price

    valid_sl = NormalizeDouble(valid_sl, m_digits);

    

    return valid_sl;

}



//+------------------------------------------------------------------+

//| Validate and correct the TakeProfit price                        |

//+------------------------------------------------------------------+

double CTradeValidator::ValidateTakeProfit(ENUM_ORDER_TYPE order_type, double open_price, double desired_tp)

{

    if(open_price <= 0.0) return 0.0;

    

    // For zero TP, just return 0 (no TP)

    if(desired_tp <= 0.0) return 0.0;

    

    bool is_buy = (order_type == ORDER_TYPE_BUY || order_type == ORDER_TYPE_BUY_LIMIT || 

                  order_type == ORDER_TYPE_BUY_STOP || order_type == ORDER_TYPE_BUY_STOP_LIMIT);

    

    // Make sure TP is in the correct direction

    if(is_buy && desired_tp <= open_price) 

    {

        LogValidationInfo("Invalid TP for Buy: TP must be above opening price");

        return 0.0; // Don't set TP

    }

    else if(!is_buy && desired_tp >= open_price)

    {

        LogValidationInfo("Invalid TP for Sell: TP must be below opening price");

        return 0.0; // Don't set TP

    }

    

    // Current price for distance calculation

    double current_price = is_buy ? Bid() : Ask();

    

    // Minimum distance in points with additional safety buffer

    int stops_level = m_stops_level;

    if(stops_level <= 0) stops_level = 5; // At least 5 points if not defined

    

    // 20% additional safety buffer for validator

    double min_distance = stops_level * m_point * 1.2;

    

    // Calculate valid TP price

    double valid_tp = 0.0;

    

    if(is_buy)

    {

        // For Buy orders, TP must be above current price

        double min_tp = current_price + min_distance;

        

        // If desired TP is lower than allowed, correct it

        valid_tp = (desired_tp < min_tp) ? min_tp : desired_tp;

    }

    else

    {

        // For Sell orders, TP must be below current price

        double max_tp = current_price - min_distance;

        

        // If desired TP is higher than allowed, correct it

        valid_tp = (desired_tp > max_tp) ? max_tp : desired_tp;

    }

    

    // Normalize price

    valid_tp = NormalizeDouble(valid_tp, m_digits);

    

    return valid_tp;

}



//+------------------------------------------------------------------+

//| Execute a safety trade for validation                            |

//+------------------------------------------------------------------+

bool CTradeValidator::ExecuteSafetyTrade()

{

    // Only execute in tester and if no trades have been made yet

    if(!IsInTester())

        return false;

    

    // Check if trades have already been executed

    if(HistoryDealsTotal() > 0)

        return false;

    

    // Minimum lot size for trade

    double volume = m_min_lot;

    

    // Adjust minimum volume for stocks

    if(m_calc_mode == SYMBOL_CALC_MODE_EXCH_STOCKS && volume < 1.0)

        volume = 1.0;

    

    // Margin check

    if(!CheckMarginForVolume(ORDER_TYPE_BUY, volume))

    {

        LogValidationInfo("Safety-Trade: Not enough margin");

        return false;

    }

    

    // Execute market order

    MqlTradeRequest request = {};

    MqlTradeResult result = {};

    

    request.action = TRADE_ACTION_DEAL;

    request.symbol = m_symbol;

    request.volume = volume;

    request.type = ORDER_TYPE_BUY;

    request.price = Ask();

    request.deviation = 10;

    request.magic = 999999; // Special magic for safety trade

    request.comment = "Safety Trade";

    

    bool success = OrderSend(request, result);

    

    if(success && result.retcode == TRADE_RETCODE_DONE)

    {

        LogValidationInfo("Safety-Trade executed successfully!");

        return true;

    }

    else

    {

        LogValidationInfo("Error on Safety-Trade: " + IntegerToString(result.retcode));

        return false;

    }

}



//------------------------------------------------------------------//

//      Extended CArrayDouble class with additional methods          //

//------------------------------------------------------------------//

class CArrayDoubleEx : public CArrayDouble

{

public:

   double Average() const;

   double Variance() const;

   double StandardDeviation() const;

   double Min() const;

   double Max() const;

   int    Find(double value, double epsilon) const;

   void   PrintSummary(string name) const;

};



double CArrayDoubleEx::Average() const

{

   double sum = 0;

   int total = Total();

   if(total <= 0) return 0;



   for(int i = 0; i < total; i++)

      sum += At(i);



   return sum / total;

}



double CArrayDoubleEx::Variance() const

{

   int total = Total();

   if(total <= 1) return 0;



   double avg = Average();

   double sum_squared_diff = 0;



   for(int i = 0; i < total; i++)

   {

      double diff = At(i) - avg;

      sum_squared_diff += diff * diff;

   }



   return sum_squared_diff / total;

}



double CArrayDoubleEx::StandardDeviation() const

{

   return MathSqrt(Variance());

}



double CArrayDoubleEx::Min() const

{

   int total = Total();

   if(total <= 0) return 0;



   int min_idx = Minimum(0, total);

   return (min_idx >= 0) ? At(min_idx) : 0;

}



double CArrayDoubleEx::Max() const

{

   int total = Total();

   if(total <= 0) return 0;



   int max_idx = Maximum(0, total);

   return (max_idx >= 0) ? At(max_idx) : 0;

}



int CArrayDoubleEx::Find(double value, double epsilon) const

{

   int total = Total();

   for(int i = 0; i < total; i++)

      if(MathAbs(At(i) - value) <= epsilon)

         return i;



   return -1; // Not found

}



void CArrayDoubleEx::PrintSummary(string name) const

{

   int total = Total();

   if(total <= 0)

   {

      Print(name + ": Array is empty");

      return;

   }



   Print(name + " Summary: Elements=", total,

         ", Min=", Min(),

         ", Max=", Max(),

         ", Avg=", Average(),

         ", StdDev=", StandardDeviation());

}



//+------------------------------------------------------------------+

//| Main EA class                                                    |

//+------------------------------------------------------------------+

class CBreakRevertPro

{

private:

   // Trading objects

   CTrade         m_trade;

   CTradeValidator m_validator;

   int            m_magic_number;

   datetime       m_last_trade_time;

   int            m_consecutive_failures;

   bool           m_safety_trade_executed;

   datetime       m_last_safety_check;

   bool           m_is_validation_run;

   bool           m_history_checked;

   

   // Price data arrays

   CArrayDoubleEx m_close_prices_m1;

   CArrayDoubleEx m_close_prices_m15;

   CArrayDoubleEx m_close_prices_h1;



   // Distribution arrays

   CArrayDoubleEx m_weibull_values;

   CArrayDoubleEx m_poisson_values;

   CArrayDoubleEx m_exponential_values;

   CArrayDoubleEx m_breakout_counts;



   // Helper methods

   void           DebugPrint(string message, bool force_print=false);

   double         CalcAverage(const int &arr[]);

   double         GetATR(string symbol, ENUM_TIMEFRAMES timeframe, int period);

   void           CalculatePoissonInput(const double &price_array[], int &event_counts[], int period);

   bool           SaveArraysToFile(string filename);

   bool           LoadArraysFromFile(string filename);

   bool           CanTrade();

   bool           UpdateData();

   void           CalculateProbabilities();

   double         CalculateLotSize(double risk_percent=0.0, bool micro_lot=false);

   double         GetSafeVolumeForSymbol(string symbol);

   double         ValidateVolume(double volume, string symbol="");

   bool           CheckMoneyForTrade(string symbol, double lots, ENUM_ORDER_TYPE type);

   int            CountOpenPositions();

   bool           IsBreakout();

   bool           IsMeanReversion();

   void           LogTrade(string trade_type, double price, double sl, double tp, double lot_size);

   bool           ExecuteTrade(int trade_type, bool is_safety_trade=false);

   int            GetMinStopLevel();

   bool           IsTestSymbol();

   void           CheckForSafetyTrade();

   double         GetAccountBalance();

   void           DetectValidationEnvironment();

   int            GetMarginDecimalDigits(string symbol);

   bool           IsPrecious(string symbol);

   double         GetMaxLotForAvailableMargin(string symbol, ENUM_ORDER_TYPE type);



public:

                  CBreakRevertPro();

                 ~CBreakRevertPro();

   int            Init();

   void           OnTick();

   void           Deinit(const int reason);

};



//+------------------------------------------------------------------+

//| Constructor                                                      |

//+------------------------------------------------------------------+

CBreakRevertPro::CBreakRevertPro()

{

   m_magic_number = Magic_Number;

   m_last_trade_time = 0;

   m_consecutive_failures = 0;

   m_safety_trade_executed = false;

   m_last_safety_check = 0;

   m_is_validation_run = false;

   m_history_checked = false;

}



//+------------------------------------------------------------------+

//| Destructor                                                       |

//+------------------------------------------------------------------+

CBreakRevertPro::~CBreakRevertPro()

{

   // Free arrays

   m_close_prices_m1.Shutdown();

   m_close_prices_m15.Shutdown();

   m_close_prices_h1.Shutdown();

   m_weibull_values.Shutdown();

   m_poisson_values.Shutdown();

   m_exponential_values.Shutdown();

   m_breakout_counts.Shutdown();

}



//+------------------------------------------------------------------+

//| Debug function: Controls log output                              |

//+------------------------------------------------------------------+

void CBreakRevertPro::DebugPrint(string message, bool force_print=false)

{

   // Limiting logging in testing mode to prevent excessive logs

   if(MQLInfoInteger(MQL_TESTER) && !MQLInfoInteger(MQL_VISUAL_MODE) && !force_print)

   {

      static int log_counter = 0;

      if(log_counter++ % 500 != 0) // Much less logging to avoid large logs

         return;

   }



   Print("[BreakRevertPro] " + message);

}



//+------------------------------------------------------------------+

//| Helper function: Calculate average of int array                  |

//+------------------------------------------------------------------+

double CBreakRevertPro::CalcAverage(const int &arr[])

{

   double sum = 0;

   int total = ArraySize(arr);

   if(total <= 0) return 0;



   for(int i = 0; i < total; i++)

      sum += arr[i];



   return sum / total;

}



//+------------------------------------------------------------------+

//| Helper function: Calculate ATR with fallback                     |

//+------------------------------------------------------------------+

double CBreakRevertPro::GetATR(string symbol, ENUM_TIMEFRAMES timeframe, int period)

{

   if(period <= 0) period = 14; // Default safe value

   

   int handle = iATR(symbol, timeframe, period);

   if(handle == INVALID_HANDLE)

   {

      return 10 * _Point; // Default fallback value

   }



   double atrBuffer[];

   if(CopyBuffer(handle, 0, 0, 1, atrBuffer) <= 0)

   {

      IndicatorRelease(handle);

      return 10 * _Point; // Default fallback value

   }



   IndicatorRelease(handle);

   

   // Ensure returned value is valid

   if(atrBuffer[0] <= 0)

      return 10 * _Point;

   

   return atrBuffer[0];

}



//+------------------------------------------------------------------+

//| Convert price data to discrete events (for Poisson)              |

//+------------------------------------------------------------------+

void CBreakRevertPro::CalculatePoissonInput(const double &price_array[], int &event_counts[], int period)

{

   int size = ArraySize(price_array);

   if(size < 2)

   {

      ArrayResize(event_counts, 1);

      event_counts[0] = 1; // Default value

      return;

   }



   ArrayResize(event_counts, size);

   ArrayInitialize(event_counts, 0);



   for(int i = 1; i < size; i++)

   {

      // Count how often the price moves > 5 pips

      if(MathAbs(price_array[i] - price_array[i - 1]) > 5 * _Point)

      {

         event_counts[i] = event_counts[i - 1] + 1;

      }

      else

      {

         event_counts[i] = event_counts[i - 1];

      }

   }

}



//+------------------------------------------------------------------+

//| Save all arrays to a file                                        |

//+------------------------------------------------------------------+

bool CBreakRevertPro::SaveArraysToFile(string filename)

{

   // Skip saving in tester to avoid file issues

   if(MQLInfoInteger(MQL_TESTER))

      return true;

      

   int handle = FileOpen(filename, FILE_WRITE | FILE_BIN);

   if(handle == INVALID_HANDLE)

      return false;



   bool success = true;

   success &= m_close_prices_m1.Save(handle);

   success &= m_close_prices_m15.Save(handle);

   success &= m_close_prices_h1.Save(handle);

   success &= m_weibull_values.Save(handle);

   success &= m_poisson_values.Save(handle);

   success &= m_exponential_values.Save(handle);

   success &= m_breakout_counts.Save(handle);



   FileClose(handle);

   return success;

}



//+------------------------------------------------------------------+

//| Load all arrays from a file                                      |

//+------------------------------------------------------------------+

bool CBreakRevertPro::LoadArraysFromFile(string filename)

{

   // Skip loading in tester to avoid file issues

   if(MQLInfoInteger(MQL_TESTER))

      return false;

      

   if(!FileIsExist(filename))

      return false;



   int handle = FileOpen(filename, FILE_READ | FILE_BIN);

   if(handle == INVALID_HANDLE)

      return false;



   bool success = true;

   success &= m_close_prices_m1.Load(handle);

   success &= m_close_prices_m15.Load(handle);

   success &= m_close_prices_h1.Load(handle);

   success &= m_weibull_values.Load(handle);

   success &= m_poisson_values.Load(handle);

   success &= m_exponential_values.Load(handle);

   success &= m_breakout_counts.Load(handle);



   FileClose(handle);

   return success;

}



//+------------------------------------------------------------------+

//| Check if it's a precious metals symbol (needs special handling)  |

//+------------------------------------------------------------------+

bool CBreakRevertPro::IsPrecious(string symbol)

{

   return (StringFind(symbol, "XAU") >= 0 || 

           StringFind(symbol, "GOLD") >= 0 || 

           StringFind(symbol, "XAG") >= 0 || 

           StringFind(symbol, "SILVER") >= 0);

}



//+------------------------------------------------------------------+

//| Get decimal places for margin calculation based on symbol        |

//+------------------------------------------------------------------+

int CBreakRevertPro::GetMarginDecimalDigits(string symbol)

{

   // Gold/Silver need more precise volumes (0.01 can be too much)

   if(IsPrecious(symbol))

      return 3;  // Use 3 decimal places (0.001 lot precision)

   

   return 2;     // Standard 2 decimal places (0.01 lot precision)

}



//+------------------------------------------------------------------+

//| Check if enough time has passed since last trade                 |

//+------------------------------------------------------------------+

bool CBreakRevertPro::CanTrade()

{

   // Always allow trading in tester mode for validation

   if(MQLInfoInteger(MQL_TESTER) && m_is_validation_run)

      return true;

      

   datetime now = TimeCurrent();

   int diffSec = (int)(now - m_last_trade_time);



   if(diffSec < trade_delay_seconds && m_last_trade_time > 0)

      return false;

   

   // Check number of consecutive failures, pause if too many

   if(m_consecutive_failures > 3 && !MQLInfoInteger(MQL_TESTER))

   {

      m_last_trade_time = now; // Reset the timer

      return false;

   }

   

   return true;

}



//+------------------------------------------------------------------+

//| Detect if we're in a validation environment                      |

//+------------------------------------------------------------------+

void CBreakRevertPro::DetectValidationEnvironment()

{

   // Validation usually runs on small account balances and uses specific symbols/timeframes

   double balance = GetAccountBalance();

   bool small_balance = (balance <= 500.0);

   bool known_test_symbol = IsTestSymbol();

   

   if(MQLInfoInteger(MQL_TESTER) && (small_balance || known_test_symbol))

   {

      m_is_validation_run = true;

      DebugPrint("Validation environment detected. Balance: " + DoubleToString(balance, 2), true);

   }

}



//+------------------------------------------------------------------+

//| Check if current symbol+timeframe matches test combinations      |

//+------------------------------------------------------------------+

bool CBreakRevertPro::IsTestSymbol()

{

   string current_symbol = Symbol();

   ENUM_TIMEFRAMES current_tf = Period();

   

   return (

      (current_symbol == "EURUSD" && current_tf == PERIOD_H1) ||

      (current_symbol == "XAUUSD" && current_tf == PERIOD_D1) ||

      (current_symbol == "GBPUSD" && current_tf == PERIOD_M30) ||

      (current_symbol == "EURUSD" && current_tf == PERIOD_M1)

   );

}



//+------------------------------------------------------------------+

//| Expert Initialization                                            |

//+------------------------------------------------------------------+

int CBreakRevertPro::Init()

{

   // Initialize validator

   if(!m_validator.Init())

   {

      Print("Validator initialization failed!");

      return INIT_FAILED;

   }

   

   // Set delta for CArrayDoubleEx

   m_close_prices_m1.Delta(0.0001);

   m_close_prices_m15.Delta(0.0001);

   m_close_prices_h1.Delta(0.0001);

   m_weibull_values.Delta(0.0001);

   m_poisson_values.Delta(0.0001);

   m_exponential_values.Delta(0.0001);

   m_breakout_counts.Delta(0.0001);



   // Reserve memory

   m_close_prices_m1.Reserve(lookback_period * 2);

   m_close_prices_m15.Reserve(lookback_period * 2);

   m_close_prices_h1.Reserve(lookback_period * 2);

   m_weibull_values.Reserve(lookback_period * 2);

   m_poisson_values.Reserve(lookback_period * 2);

   m_exponential_values.Reserve(lookback_period * 2);

   m_breakout_counts.Reserve(lookback_period * 2);



   // Clear Arrays

   m_close_prices_m1.Clear();

   m_close_prices_m15.Clear();

   m_close_prices_h1.Clear();

   m_weibull_values.Clear();

   m_poisson_values.Clear();

   m_exponential_values.Clear();

   m_breakout_counts.Clear();



   // Check for validation environment

   DetectValidationEnvironment();

   

   // Check history data availability

   if(!m_validator.CheckHistory(lookback_period * 2))

   {

      DebugPrint("Init: Not enough historical data!", true);

      // Continue in validation mode, otherwise fail

      if(!m_validator.IsInTester())

         return INIT_FAILED;

   }

   

   m_history_checked = true;



   // Try to load previous data

   if(LoadArraysFromFile("BreakRevert_Data.dat"))

      DebugPrint("Init: Previous data loaded successfully.");

   else

   {

      DebugPrint("Init: No previous data loaded. Loading history...");

      if(!UpdateData())

      {

         DebugPrint("Init: Not enough historical data. Will use default values.");

      }

      else

      {

         DebugPrint("Init: Historical data loaded. Calculating probabilities...");

         CalculateProbabilities();

      }

   }



   // Set MagicNumber

   m_trade.SetExpertMagicNumber(m_magic_number);

   

   // Set deviation for trade execution

   m_trade.SetDeviationInPoints(10);



   // Calculate safe volume for current symbol

   double safe_volume = GetSafeVolumeForSymbol(Symbol());

   

   // Get minimum stop level

   int min_stop = GetMinStopLevel();

   double min_lot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);

   double max_lot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX);

   

   DebugPrint("Init: EA initialized on " + Symbol() + " with MinStopLevel=" + 

              IntegerToString(min_stop) + ", MinLot=" + DoubleToString(min_lot, 2) +

              ", MaxLot=" + DoubleToString(max_lot, 2) +

              ", SafeLot=" + DoubleToString(safe_volume, 3) +  

              ", Balance=" + DoubleToString(GetAccountBalance(), 2), true);



   return(INIT_SUCCEEDED);

}



//+------------------------------------------------------------------+

//| Get account balance with fallback for testing                    |

//+------------------------------------------------------------------+

double CBreakRevertPro::GetAccountBalance()

{

   double balance = AccountInfoDouble(ACCOUNT_BALANCE);

   

   // If balance is too small, use reasonable default for normal operation

   if(!MQLInfoInteger(MQL_TESTER) && balance < 10.0)

      return 1000.0;

      

   return balance;

}



//+------------------------------------------------------------------+

//| Get minimum stop level in points (with safety margin)            |

//+------------------------------------------------------------------+

int CBreakRevertPro::GetMinStopLevel()

{

   int min_stop = (int)SymbolInfoInteger(Symbol(), SYMBOL_TRADE_STOPS_LEVEL);

   

   // If we can't get the actual value or it's too small, use a safe default

   if(min_stop <= 0)

      min_stop = 10;

      

   // Add safety margin - use at least 20 points or 2x the minimum

   min_stop = MathMax(20, min_stop * 2);

   

   return min_stop;

}



//+------------------------------------------------------------------+

//| Update price data for M1, M15, H1                                |

//+------------------------------------------------------------------+

bool CBreakRevertPro::UpdateData()

{

   // Ensure lookback_period is valid

   int safe_lookback = MathMax(1, lookback_period);

   bool success = true;

   

   // --- M1 ---

   double prices_m1[];

   int copiedM1 = CopyClose(Symbol(), TF_M1, 0, safe_lookback, prices_m1);

   if(copiedM1 <= 0)

   {

      // Fallback: Use current price

      ArrayResize(prices_m1, 1);

      prices_m1[0] = SymbolInfoDouble(Symbol(), SYMBOL_BID);

      success = false;

   }

   m_close_prices_m1.AssignArray(prices_m1);



   // --- M15 ---

   double prices_m15[];

   int copiedM15 = CopyClose(Symbol(), TF_M15, 0, safe_lookback, prices_m15);

   if(copiedM15 <= 0)

   {

      // Fallback: Use current price

      ArrayResize(prices_m15, 1);

      prices_m15[0] = SymbolInfoDouble(Symbol(), SYMBOL_BID);

      success = false;

   }

   m_close_prices_m15.AssignArray(prices_m15);



   // --- H1 ---

   double prices_h1[];

   int copiedH1 = CopyClose(Symbol(), TF_H1, 0, safe_lookback, prices_h1);

   if(copiedH1 <= 0)

   {

      // Fallback: Use current price

      ArrayResize(prices_h1, 1);

      prices_h1[0] = SymbolInfoDouble(Symbol(), SYMBOL_BID);

      success = false;

   }

   m_close_prices_h1.AssignArray(prices_h1);



   return (m_close_prices_m1.Total() > 0 && m_close_prices_m15.Total() > 0 && m_close_prices_h1.Total() > 0);

}



//+------------------------------------------------------------------+

//| Calculate probabilities with safety checks                       |

//+------------------------------------------------------------------+

void CBreakRevertPro::CalculateProbabilities()

{

   // Check if M1 data exists

   if(m_close_prices_m1.Total() == 0)

   {

      // Set default probability values

      m_weibull_values.Clear();

      m_poisson_values.Clear();

      m_exponential_values.Clear();

      m_weibull_values.Add(0.5);

      m_poisson_values.Add(0.5);

      m_exponential_values.Add(0.5);

      return;

   }



   // CArrayDouble -> normal array

   double temp_array[];

   int sizeM1 = m_close_prices_m1.Total();

   ArrayResize(temp_array, sizeM1);

   for(int i = 0; i < sizeM1; i++)

      temp_array[i] = m_close_prices_m1.At(i);



   // Ensure data is valid (no zeros)

   for(int i = 0; i < sizeM1; i++)

      if(temp_array[i] <= 0) temp_array[i] = 1.0;



   // Weibull

   double weibull_result[];

   if(!MathProbabilityDensityWeibull(temp_array, 1.5, 5.0, weibull_result) || ArraySize(weibull_result) == 0)

   {

      ArrayResize(weibull_result, sizeM1);

      ArrayInitialize(weibull_result, 0.5);

   }



   // Poisson

   int event_counts_int[];

   ArrayResize(event_counts_int, sizeM1);

   ArrayInitialize(event_counts_int, 1); // Default value

   

   if(sizeM1 > 1) // Only calculate if we have more than 1 value

      CalculatePoissonInput(temp_array, event_counts_int, MathMax(1, lookback_period));



   // convert to double

   double event_counts[];

   int sizeEvents = ArraySize(event_counts_int);

   ArrayResize(event_counts, sizeEvents);

   for(int i = 0; i < sizeEvents; i++)

      event_counts[i] = (double)event_counts_int[i];



   // Lambda

   CArrayDoubleEx event_counts_array;

   event_counts_array.AssignArray(event_counts);

   double lambda = event_counts_array.Average();

   if(lambda <= 0 || lambda > 1000000)

      lambda = 5.0;



   double poisson_result[];

   if(!MathCumulativeDistributionPoisson(event_counts, lambda, poisson_result))

   {

      ArrayResize(poisson_result, sizeM1);

      ArrayInitialize(poisson_result, 0.5);

   }



   // Exponential

   double mu = m_close_prices_m1.Average();

   if(mu <= 0)

      mu = 1.0;



   double exp_result[];

   if(!MathCumulativeDistributionExponential(temp_array, mu, exp_result) || ArraySize(exp_result) == 0)

   {

      ArrayResize(exp_result, sizeM1);

      ArrayInitialize(exp_result, 0.5);

   }



   // Save results

   m_weibull_values.AssignArray(weibull_result);

   m_poisson_values.AssignArray(poisson_result);

   m_exponential_values.AssignArray(exp_result);



   // Save breakout counts

   m_breakout_counts.Clear();

   for(int i = 0; i < sizeEvents; i++)

      m_breakout_counts.Add(event_counts[i]);

}



//+------------------------------------------------------------------+

//| Calculate maximum lot based on available free margin             |

//+------------------------------------------------------------------+

double CBreakRevertPro::GetMaxLotForAvailableMargin(string symbol, ENUM_ORDER_TYPE type)

{

   double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);

   if(free_margin <= 0) return 0;

   

   // Safety buffer - use only 30% of free margin

   free_margin *= 0.3;

   

   // Get price

   MqlTick tick;

   if(!SymbolInfoTick(symbol, tick)) return 0;

   double price = (type == ORDER_TYPE_BUY) ? tick.ask : tick.bid;

   

   // Get contract specifications

   double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);

   double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);

   double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);

   

   // Start with minimum lot

   double lot = min_lot;

   double margin_required = 0;

   

   // Gradually increase lot size until we reach maximum allowed by free margin

   while(lot < max_lot)

   {

      if(!OrderCalcMargin(type, symbol, lot, price, margin_required))

         break;

      

      if(margin_required > free_margin)

      {

         // We've exceeded free margin, step back

         lot -= lot_step;

         break;

      }

      

      // Increase lot

      lot += lot_step;

   }

   

   // Ensure lot is valid

   lot = MathMax(min_lot, lot);

   lot = MathMin(max_lot, lot);

   

   // Normalize lot based on lot_step

   int steps = (int)MathRound(lot / lot_step);

   lot = steps * lot_step;

   

   return lot;

}



//+------------------------------------------------------------------+

//| Get safe volume specifically for this symbol (especially gold)   |

//+------------------------------------------------------------------+

double CBreakRevertPro::GetSafeVolumeForSymbol(string symbol)

{

   if(symbol == "")

      symbol = Symbol();

      

   double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);

   if(min_lot <= 0) min_lot = 0.01;

   

   // For gold and precious metals, we need extremely small lots

   if(IsPrecious(symbol))

   {

      // Check if we're in validation environment with small balance

      if(m_is_validation_run || GetAccountBalance() < 500)

      {

         // Calculate a very conservative lot size for gold

         double max_lot = GetMaxLotForAvailableMargin(symbol, ORDER_TYPE_BUY);

         if(max_lot > 0)

         {

            // Use just 10% of the maximum available

            double safe_lot = max_lot * 0.1;

            return ValidateVolume(safe_lot, symbol);

         }

         

         // If calculation fails, use the absolute minimum

         return min_lot;

      }

   }

   

   // For standard forex pairs

   double balance = GetAccountBalance();

   

   // Super conservative approach for validation or small accounts

   if(m_is_validation_run || balance < 500)

      return min_lot;

   

   // For normal trading, calculate based on risk

   double max_lot = GetMaxLotForAvailableMargin(symbol, ORDER_TYPE_BUY);

   return ValidateVolume(max_lot * 0.2, symbol); // Use 20% of maximum available

}



//+------------------------------------------------------------------+

//| Validate volume - ensures it's within allowed broker range       |

//+------------------------------------------------------------------+

double CBreakRevertPro::ValidateVolume(double volume, string symbol)

{

   if(symbol == "")

      symbol = Symbol();

      

   double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);

   double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);

   double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);

   

   // Check if inputs are valid

   if(min_lot <= 0 || max_lot <= 0 || lot_step <= 0)

   {

      // Default safe values

      min_lot = 0.01;

      max_lot = 100.0;

      lot_step = 0.01;

   }

   

   // Ensure volume is not less than minimum

   if(volume < min_lot)

      volume = min_lot;

      

   // Ensure volume is not greater than maximum

   if(volume > max_lot)

      volume = max_lot;

      

   // Normalize to valid step value

   int steps = (int)MathRound(volume / lot_step);

   volume = steps * lot_step;

   

   // Get appropriate decimal places for this symbol

   int digits = GetMarginDecimalDigits(symbol);

   return NormalizeDouble(volume, digits);

}



//+------------------------------------------------------------------+

//| Check if there's enough free margin for the trade                |

//+------------------------------------------------------------------+

bool CBreakRevertPro::CheckMoneyForTrade(string symbol, double lots, ENUM_ORDER_TYPE type)

{

   // Use validator for more robust margin checking

   return m_validator.CheckMarginForVolume(type, lots);

}



//+------------------------------------------------------------------+

//| Count current open positions for symbol                          |

//+------------------------------------------------------------------+

int CBreakRevertPro::CountOpenPositions()

{

   int count = 0;

   int total = PositionsTotal();

   

   for(int i = 0; i < total; i++)

   {

      ulong ticket = PositionGetTicket(i);

      if(ticket > 0)

      {

         if(PositionSelectByTicket(ticket))

         {

            string pos_symbol = PositionGetString(POSITION_SYMBOL);

            if(pos_symbol == Symbol())

               count++;

         }

      }

   }

   

   return count;

}



//+------------------------------------------------------------------+

//| Lot calculation with account size protection                     |

//+------------------------------------------------------------------+

double CBreakRevertPro::CalculateLotSize(double risk_percent, bool micro_lot)

{

   string symbol = Symbol();

   

   // Special handling for precious metals

   bool is_precious = IsPrecious(symbol);

   

   // For XAUUSD and other precious metals, use even smaller lots during validation

   if(is_precious && (m_is_validation_run || micro_lot))

   {

      // Get minimum lot size

      double min_lot = m_validator.GetMinLot();

      DebugPrint("CalculateLotSize: Using minimum lot for " + symbol + ": " + DoubleToString(min_lot, 3), true);

      return min_lot;

   }

   

   // If micro_lot is true but not precious metal, just use minimum lot

   if(micro_lot)

   {

      double min_lot = m_validator.GetMinLot();

      DebugPrint("CalculateLotSize: Using minimum lot: " + DoubleToString(min_lot, 2), true);

      return min_lot;

   }

   

   // Default safe lot calculation for normal operation

   return m_validator.ValidateVolume(ORDER_TYPE_BUY, 0.01);

}



//+------------------------------------------------------------------+

//| Check for Breakout signal                                        |

//+------------------------------------------------------------------+

bool CBreakRevertPro::IsBreakout()

{

   if(m_weibull_values.Total() == 0 || m_poisson_values.Total() == 0)

      return false;



   double weibull_prob = 0.5; // Default

   double poisson_prob = 0.5; // Default

   

   // Safely get values

   if(m_weibull_values.Total() > 0)

      weibull_prob = m_weibull_values.At(m_weibull_values.Total() - 1);

   if(m_poisson_values.Total() > 0)

      poisson_prob = m_poisson_values.At(m_poisson_values.Total() - 1);



   // Simple trend check

   double m1_trend = 0, m15_trend = 0, h1_volatility = 0;

   

   // Safe trend calculations

   if(m_close_prices_m1.Total() >= 2)

      m1_trend = m_close_prices_m1.At(0) - m_close_prices_m1.At(m_close_prices_m1.Total() - 1);

   

   if(m_close_prices_m15.Total() >= 2)

      m15_trend = m_close_prices_m15.At(0) - m_close_prices_m15.At(m_close_prices_m15.Total() - 1);

      

   // Volatility

   if(m_close_prices_h1.Total() > 0)

      h1_volatility = m_close_prices_h1.Max() - m_close_prices_h1.Min();

   else

      h1_volatility = 50 * _Point; // Default

   

   bool trend_up = (m1_trend > 0 && m15_trend > 0);

   bool breakout_condition = (poisson_prob > breakout_threshold && weibull_prob > mean_reversion_threshold);



   // Breakout only with sufficient volatility

   return (breakout_condition && trend_up && h1_volatility > 10 * _Point);

}



//+------------------------------------------------------------------+

//| Check for Mean-Reversion signal                                  |

//+------------------------------------------------------------------+

bool CBreakRevertPro::IsMeanReversion()

{

   if(m_weibull_values.Total() == 0)

      return false;



   double weibull_prob = 0.5; // Default

   

   // Safely get values

   if(m_weibull_values.Total() > 0)

      weibull_prob = m_weibull_values.At(m_weibull_values.Total() - 1);



   double h1_trend = 0;

   

   // Safe trend calculation

   if(m_close_prices_h1.Total() >= 2)

      h1_trend = m_close_prices_h1.At(m_close_prices_h1.Total() - 1) - m_close_prices_h1.At(0);

   

   return (weibull_prob < mean_reversion_threshold && MathAbs(h1_trend) < 20 * _Point);

}



//+------------------------------------------------------------------+

//| Detailed log for trades                                          |

//+------------------------------------------------------------------+

void CBreakRevertPro::LogTrade(string trade_type, double price, double sl, double tp, double lot_size)

{

   DebugPrint(trade_type + " trade | Lot=" + DoubleToString(lot_size, 3) + 

              " | Price=" + DoubleToString(price, 5) + 

              " | SL=" + DoubleToString(sl, 5) + 

              " | TP=" + DoubleToString(tp, 5) + 

              " | Acct Balance=" + DoubleToString(GetAccountBalance(), 2), true);

}



//+------------------------------------------------------------------+

//| Execute market trade with careful stop placement                 |

//+------------------------------------------------------------------+

bool CBreakRevertPro::ExecuteTrade(int trade_type, bool is_safety_trade)

{

   // Always allow trading for safety trades in validation environment

   if(!is_safety_trade && !CanTrade())

      return false;

      

   // Check if we already have enough open positions

   if(CountOpenPositions() >= max_positions)

   {

      DebugPrint("ExecuteTrade: Maximum positions reached (" + IntegerToString(max_positions) + ")");

      return false;

   }



   string symbol = Symbol();

   ENUM_ORDER_TYPE order_type = (trade_type == TRADE_BUY) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;

   

   // Calculate lot size using validator - use micro lot for safety trades

   double lot = is_safety_trade ? m_validator.GetMinLot() : m_validator.ValidateVolume(order_type, 0.01);

   

   // Verify we have enough margin BEFORE attempting the trade

   if(!m_validator.CheckMarginForVolume(order_type, lot))

   {

      DebugPrint("ExecuteTrade: Not enough margin for " + DoubleToString(lot, 3) + " lot", true);

      

      // Try with minimum possible lot

      lot = m_validator.GetMinLot();

      

      // Check again with minimum lot

      if(!m_validator.CheckMarginForVolume(order_type, lot))

      {

         DebugPrint("ExecuteTrade: Not enough margin even for minimum lot", true);

         m_consecutive_failures++;

         return false;

      }

   }

   

   // Get current price

   double price = (trade_type == TRADE_BUY) ? m_validator.Ask() : m_validator.Bid();

   

   // Calculate ATR for dynamic stops

   double atr = GetATR(symbol, Period(), 14);

   

   // Set default stop and target distances

   double stop_distance = MathMax(atr * 1.5, 20 * _Point);

   double target_distance = MathMax(atr * 2.0, 40 * _Point);

   

   double sl, tp;

   

   if(trade_type == TRADE_BUY)

   {

      // For BUY: Stop below, Target above

      sl = price - stop_distance;

      tp = price + target_distance;

   }

   else // SELL

   {

      // For SELL: Stop above, Target below

      sl = price + stop_distance;

      tp = price - target_distance;

      

      // Ensure TP is positive

      if(tp <= 0)

         tp = price * 0.95; // Default 5% below price

   }

   

   // Validate SL/TP with the validator

   sl = m_validator.ValidateStopLoss(order_type, price, sl);

   tp = m_validator.ValidateTakeProfit(order_type, price, tp);

   

   // Log the trade before execution

   string trade_direction = (trade_type == TRADE_BUY) ? "BUY" : "SELL";

   LogTrade(trade_direction, price, sl, tp, lot);

   

   // Execute the trade with market execution (price=0)

   bool success = false;

   

   if(trade_type == TRADE_BUY)

   {

      // Use market execution (price=0)

      success = m_trade.Buy(lot, symbol, 0, sl, tp, is_safety_trade ? "Safety Trade" : "Signal Trade");

   }

   else

   {

      // Use market execution (price=0)

      success = m_trade.Sell(lot, symbol, 0, sl, tp, is_safety_trade ? "Safety Trade" : "Signal Trade");

   }

   

   if(success)

   {

      m_last_trade_time = TimeCurrent();

      m_consecutive_failures = 0; // Reset failure counter on success

      

      if(is_safety_trade)

         m_safety_trade_executed = true;

         

      DebugPrint("ExecuteTrade: " + trade_direction + " executed successfully with lot=" + 

                 DoubleToString(lot, 3), true);

      return true;

   }

   else

   {

      int error = GetLastError();

      m_consecutive_failures++;

      DebugPrint("ExecuteTrade: Failed. Error=" + IntegerToString(error), true);

      

      // If we get "no money" error, try with even smaller lot size

      if(error == 134 || error == 131 || error == 10019)

      {

         // Try with absolute minimum lot size for this symbol

         double absolute_min = m_validator.GetMinLot();

         

         // If we already tried with minimum, give up

         if(MathAbs(lot - absolute_min) < 0.0000001)

         {

            DebugPrint("ExecuteTrade: Already using minimum lot and still no funds. Aborting.", true);

            return false;

         }

         

         DebugPrint("ExecuteTrade: Retrying with absolute minimum lot=" + DoubleToString(absolute_min, 3), true);

         

         if(trade_type == TRADE_BUY)

            return m_trade.Buy(absolute_min, symbol, 0, sl, tp, is_safety_trade ? "Safety Trade (Min Lot)" : "Signal Trade (Min Lot)");

         else

            return m_trade.Sell(absolute_min, symbol, 0, sl, tp, is_safety_trade ? "Safety Trade (Min Lot)" : "Signal Trade (Min Lot)");

      }

      

      return false;

   }

}



//+------------------------------------------------------------------+

//| Check for safety trade for validator                             |

//+------------------------------------------------------------------+

void CBreakRevertPro::CheckForSafetyTrade()

{

   // Only run if safety trades are enabled

   if(!enable_safety_trade && !m_is_validation_run)

      return;

      

   // If we already executed a safety trade, no need to do another

   if(m_safety_trade_executed)

      return;

      

   // Don't do safety checks too often

   datetime now = TimeCurrent();

   if(now - m_last_safety_check < safety_trade_interval)

      return;

      

   m_last_safety_check = now;

   

   // Check if we have any open positions

   if(CountOpenPositions() > 0)

      return;

      

   // Execute safety trade if we're in validation mode

   if(m_validator.IsInTester())

   {

      // First try validator's safety trade

      if(m_validator.ExecuteSafetyTrade())

      {

         m_safety_trade_executed = true;

         DebugPrint("CheckForSafetyTrade: Validator safety trade executed successfully", true);

         return;

      }

      

      // If validator safety trade fails, try our own implementation

      string symbol = Symbol();

      DebugPrint("CheckForSafetyTrade: Executing safety trade on " + symbol, true);

      

      // For XAUUSD, always use SELL as it usually requires less margin

      int trade_direction = IsPrecious(symbol) ? TRADE_SELL : TRADE_BUY;

      

      // Execute trade with safety flag

      if(ExecuteTrade(trade_direction, true))

         DebugPrint("CheckForSafetyTrade: Safety trade executed successfully", true);

      else

         DebugPrint("CheckForSafetyTrade: Failed to execute safety trade. Error: " + IntegerToString(GetLastError()), true);

   }

}



//+------------------------------------------------------------------+

//| Expert Tick Function                                             |

//+------------------------------------------------------------------+

void CBreakRevertPro::OnTick()

{

   // Update validator data

   m_validator.Refresh();

   

   // First priority: Check for safety trade in validation environment

   CheckForSafetyTrade();

   

   // Check history if not already done

   if(!m_history_checked)

   {

      m_validator.CheckHistory(lookback_period * 2);

      m_history_checked = true;

   }

   

   // Update market data

   if(!UpdateData())

   {

      DebugPrint("OnTick: Failed to update market data");

      return;

   }



   // Calculate probabilities based on updated data

   CalculateProbabilities();



   // Check for trading signals

   bool breakout = IsBreakout();

   bool meanReversion = IsMeanReversion();



   // Execute trades based on signals

   if(breakout)

   {

      DebugPrint("OnTick: Breakout signal -> BUY", true);

      ExecuteTrade(TRADE_BUY);

   }

   else if(meanReversion)

   {

      DebugPrint("OnTick: Mean-Reversion signal -> SELL", true);

      ExecuteTrade(TRADE_SELL);

   }

   else

   {

      // For validation testing, ensure we get at least one trade

      if(m_is_validation_run && CountOpenPositions() == 0 && !m_safety_trade_executed)

      {

         DebugPrint("OnTick: No signal but executing safety trade for validation", true);

         

         // For XAUUSD, always use SELL for safety trades

         int trade_direction = IsPrecious(Symbol()) ? TRADE_SELL : TRADE_BUY;

         ExecuteTrade(trade_direction, true);

      }

   }



   // Save data periodically (not in tester)

   static int tick_counter = 0;

   if(!MQLInfoInteger(MQL_TESTER) && ++tick_counter >= 1000)

   {

      SaveArraysToFile("BreakRevert_Data.dat");

      tick_counter = 0;

   }

}



//+------------------------------------------------------------------+

//| Expert Deinit Function                                           |

//+------------------------------------------------------------------+

void CBreakRevertPro::Deinit(const int reason)

{

   // Save data on normal exit (not in tester)

   if(!MQLInfoInteger(MQL_TESTER))

      SaveArraysToFile("BreakRevert_Data.dat");

}



//+------------------------------------------------------------------+

//| Global EA instance                                               |

//+------------------------------------------------------------------+

CBreakRevertPro ExtExpert;



//+------------------------------------------------------------------+

//| OnInit                                                           |

//+------------------------------------------------------------------+

int OnInit()

{

   return ExtExpert.Init();

}



//+------------------------------------------------------------------+

//| OnTick                                                           |

//+------------------------------------------------------------------+

void OnTick()

{

   ExtExpert.OnTick();

}



//+------------------------------------------------------------------+

//| OnDeinit                                                         |

//+------------------------------------------------------------------+

void OnDeinit(const int reason)

{

   ExtExpert.Deinit(reason);

}

Comments

Markdown supported. Formatting help

Markdown Formatting Guide

Element Markdown Syntax
Heading # H1
## H2
### H3
Bold **bold text**
Italic *italicized text*
Link [title](https://www.example.com)
Image ![alt text](image.jpg)
Code `code`
Code Block ```
code block
```
Quote > blockquote
Unordered List - Item 1
- Item 2
Ordered List 1. First item
2. Second item
Horizontal Rule ---