//+------------------------------------------------------------------+
//|                                ad-trend-confirmation-ea.mq4        |
//|  A breakout EA: enters when price makes a new N-bar high/low at   |
//|  the same time Accumulation/Distribution also makes a new N-bar   |
//|  high/low, confirming the breakout is backed by real volume.      |
//|  ATR-based Stop Loss/Take Profit and risk-based position sizing.  |
//|  One position at a time.                                          |
//|  EDUCATIONAL — test on a demo account first. Past performance     |
//|  does not guarantee future results. This is not financial advice.|
//|  Source: web-forex (educational, free to use and modify)          |
//+------------------------------------------------------------------+
#property copyright "web-forex"
#property strict

extern string ___Strategy___       = "--- A/D Trend Confirmation Strategy ---";
extern int    ConfirmPeriod        = 20;

extern string ___RiskManagement___ = "--- Risk Management ---";
extern int    ATRPeriod            = 14;
extern double ATRMultiplier        = 2.0;
extern double RiskRewardRatio      = 2.0;
extern bool   UseFixedLot          = false;
extern double FixedLotSize         = 0.01;
extern double RiskPercent          = 1.0;

extern string ___Filters___        = "--- Filters ---";
extern int    MaxSpreadPoints      = 30;

extern string ___General___        = "--- General ---";
extern int    MagicNumber          = 20260803;
extern bool   EnableTrading        = true;

datetime lastBarTime = 0;

int OnInit()
{
   return(INIT_SUCCEEDED);
}

double HighestClose(int period, int startShift)
{
   int idx = iHighest(NULL, 0, MODE_CLOSE, period, startShift);
   return(iClose(NULL, 0, idx));
}

double LowestClose(int period, int startShift)
{
   int idx = iLowest(NULL, 0, MODE_CLOSE, period, startShift);
   return(iClose(NULL, 0, idx));
}

double HighestAd(int period, int startShift)
{
   double best = -DBL_MAX;
   for (int i = startShift; i < startShift + period; i++)
   {
      double v = iAD(NULL, 0, i);
      if (v > best) best = v;
   }
   return(best);
}

double LowestAd(int period, int startShift)
{
   double best = DBL_MAX;
   for (int i = startShift; i < startShift + period; i++)
   {
      double v = iAD(NULL, 0, i);
      if (v < best) best = v;
   }
   return(best);
}

void OnTick()
{
   if (!EnableTrading)
      return;

   if (Time[0] == lastBarTime)
      return; // only evaluate once per new bar
   lastBarTime = Time[0];

   if (CountOpenPositions() > 0)
      return; // one position at a time

   double spreadPoints = MarketInfo(Symbol(), MODE_SPREAD);
   if (spreadPoints > MaxSpreadPoints)
      return; // spread too wide right now, skip this bar

   double closeLast    = iClose(NULL, 0, 1);
   double adLast        = iAD(NULL, 0, 1);
   double highestClose  = HighestClose(ConfirmPeriod, 1);
   double lowestClose   = LowestClose(ConfirmPeriod, 1);
   double highestAd     = HighestAd(ConfirmPeriod, 1);
   double lowestAd      = LowestAd(ConfirmPeriod, 1);
   double atr             = iATR(NULL, 0, ATRPeriod, 1);

   bool bullishConfirm = (closeLast >= highestClose && adLast >= highestAd);
   bool bearishConfirm = (closeLast <= lowestClose && adLast <= lowestAd);

   if (bullishConfirm)
      OpenTrade(OP_BUY, atr);
   else if (bearishConfirm)
      OpenTrade(OP_SELL, atr);
}

int CountOpenPositions()
{
   int count = 0;
   for (int i = 0; i < OrdersTotal(); i++)
   {
      if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
            count++;
      }
   }
   return(count);
}

void OpenTrade(int type, double atr)
{
   double slDistance = atr * ATRMultiplier;
   double tpDistance = slDistance * RiskRewardRatio;
   double lots       = CalculateLotSize(slDistance);
   double price, sl, tp;

   if (type == OP_BUY)
   {
      price = Ask;
      sl = price - slDistance;
      tp = price + tpDistance;
   }
   else
   {
      price = Bid;
      sl = price + slDistance;
      tp = price - tpDistance;
   }

   int ticket = OrderSend(Symbol(), type, lots, price, 3, sl, tp, "A/D Trend Confirmation EA", MagicNumber, 0,
                          type == OP_BUY ? clrDodgerBlue : clrOrangeRed);
   if (ticket < 0)
      Print("A/D Trend Confirmation EA: OrderSend failed, error ", GetLastError());
}

double CalculateLotSize(double slDistance)
{
   if (UseFixedLot || slDistance <= 0)
      return(NormalizeLotSize(FixedLotSize));

   double riskAmount = AccountBalance() * (RiskPercent / 100.0);
   double tickValue   = MarketInfo(Symbol(), MODE_TICKVALUE);
   double tickSize    = MarketInfo(Symbol(), MODE_TICKSIZE);
   if (tickSize <= 0 || tickValue <= 0)
      return(NormalizeLotSize(FixedLotSize));

   double slTicks = slDistance / tickSize;
   double lots    = riskAmount / (slTicks * tickValue);

   return(NormalizeLotSize(lots));
}

double NormalizeLotSize(double lots)
{
   double minLot  = MarketInfo(Symbol(), MODE_MINLOT);
   double maxLot  = MarketInfo(Symbol(), MODE_MAXLOT);
   double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
   if (lotStep <= 0)
      return(minLot);

   lots = MathFloor(lots / lotStep) * lotStep;
   if (lots < minLot)
      lots = minLot;
   if (lots > maxLot)
      lots = maxLot;
   return(lots);
}
