//+------------------------------------------------------------------+
//|                            Jurik_MACD_Cross_Swing_UpDown_MTF.mq5 |
//| Multi-timeframe version. Up to 3 timeframe slots, each one:      |
//|  - computes the Jurik MACD on its own timeframe                  |
//|  - UP swings   = MACD cross UP   -> next cross DOWN              |
//|  - DOWN swings = MACD cross DOWN -> next cross UP                |
//|  - takes the MAXIMUM (or average) swing of each side             |
//|  - draws on the current chart, from the last cross price:        |
//|       last cross + UP swing   /   last cross - DOWN swing        |
//+------------------------------------------------------------------+
#property copyright "2026"
#property version   "2.00"
#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots   0

//------------------------------------------------------------------
// Enums
//------------------------------------------------------------------
enum enPrices
  {
   pr_close,      // Close
   pr_open,       // Open
   pr_high,       // High
   pr_low,        // Low
   pr_median,     // Median
   pr_typical,    // Typical
   pr_weighted,   // Weighted
   pr_average,    // Average (high+low+open+close)/4
   pr_medianb,    // Average median body (open+close)/2
   pr_tbiased,    // Trend biased price
   pr_haclose,    // Heiken ashi close
   pr_haopen,     // Heiken ashi open
   pr_hahigh,     // Heiken ashi high
   pr_halow,      // Heiken ashi low
   pr_hamedian,   // Heiken ashi median
   pr_hatypical,  // Heiken ashi typical
   pr_haweighted, // Heiken ashi weighted
   pr_haaverage,  // Heiken ashi average
   pr_hamedianb,  // Heiken ashi median body
   pr_hatbiased   // Heiken ashi trend biased price
  };

enum ENUM_SWING_MODE
  {
   SWING_CROSS_TO_CROSS = 0,  // Price at cross -> price at next cross
   SWING_EXTREME_RANGE  = 1   // Cross price -> highest high / lowest low before next cross
  };

enum ENUM_STAT_MODE
  {
   STAT_MAXIMUM = 0,  // Maximum swing
   STAT_AVERAGE = 1   // Average swing
  };

//------------------------------------------------------------------
// Inputs
//------------------------------------------------------------------
input group "--- Jurik MACD settings (same as your indicator) ---"
input int             InpFastEMA      = 12;             // Macd fast period
input int             InpSlowEMA      = 26;             // Macd slow period
input int             InpSignalEMA    = 9;              // Signal period (not used for crosses)
input double          InpJurikPhase   = 0;              // Jurik smoothing phase (-100 to 100)
input enPrices        InpAppliedPrice = pr_close;       // Price to use

input group "--- Swing measurement ---"
input ENUM_SWING_MODE InpSwingMode    = SWING_CROSS_TO_CROSS; // Swing distance mode
input ENUM_STAT_MODE  InpStatMode     = STAT_MAXIMUM;   // Distance used for the lines
input int             InpMaxBars      = 5000;           // History bars to analyze (per timeframe)
input int             InpMaxSwings    = 0;              // Use only last N swings per side (0 = all)

input group "--- Timeframe 1 ---"
input bool            InpUse1         = true;           // Enable
input ENUM_TIMEFRAMES InpTf1          = PERIOD_CURRENT; // Timeframe
input color           InpUp1          = clrLimeGreen;   // Upper line color
input color           InpDn1          = clrTomato;      // Lower line color
input color           InpBase1        = clrSilver;      // Last-cross line color

input group "--- Timeframe 2 ---"
input bool            InpUse2         = true;           // Enable
input ENUM_TIMEFRAMES InpTf2          = PERIOD_H1;      // Timeframe
input color           InpUp2          = clrAqua;        // Upper line color
input color           InpDn2          = clrMagenta;     // Lower line color
input color           InpBase2        = clrDimGray;     // Last-cross line color

input group "--- Timeframe 3 ---"
input bool            InpUse3         = false;          // Enable
input ENUM_TIMEFRAMES InpTf3          = PERIOD_H4;      // Timeframe
input color           InpUp3          = clrYellow;      // Upper line color
input color           InpDn3          = clrOrange;      // Lower line color
input color           InpBase3        = clrGray;        // Last-cross line color

input group "--- Drawing ---"
input ENUM_LINE_STYLE InpLineStyle    = STYLE_DASH;     // Line style
input int             InpLineWidth    = 1;              // Line width
input bool            InpShowBase     = true;           // Show last-cross price line
input bool            InpShowLabels   = true;           // Show text labels
input bool            InpShowComment  = true;           // Show stats in chart corner

#define SLOTS 3
string          PREFIX = "JMCSM_";
bool            slotOn[SLOTS];
ENUM_TIMEFRAMES slotTf[SLOTS];
color           slotUp[SLOTS], slotDn[SLOTS], slotBase[SLOTS];
datetime        slotLast[SLOTS];
string          slotStat[SLOTS];

//+------------------------------------------------------------------+
string TfName(const ENUM_TIMEFRAMES tf)
  {
   return StringSubstr(EnumToString(tf), 7);
  }

//+------------------------------------------------------------------+
int OnInit()
  {
   slotOn[0] = InpUse1; slotTf[0] = (InpTf1 == PERIOD_CURRENT) ? _Period : InpTf1;
   slotUp[0] = InpUp1;  slotDn[0] = InpDn1; slotBase[0] = InpBase1;
   slotOn[1] = InpUse2; slotTf[1] = (InpTf2 == PERIOD_CURRENT) ? _Period : InpTf2;
   slotUp[1] = InpUp2;  slotDn[1] = InpDn2; slotBase[1] = InpBase2;
   slotOn[2] = InpUse3; slotTf[2] = (InpTf3 == PERIOD_CURRENT) ? _Period : InpTf3;
   slotUp[2] = InpUp3;  slotDn[2] = InpDn3; slotBase[2] = InpBase3;

   for(int s = 0; s < SLOTS; s++)
     {
      slotLast[s] = 0;
      slotStat[s] = "";
     }
   IndicatorSetString(INDICATOR_SHORTNAME, "Jurik MACD Cross Swing Up/Down MTF");
   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0, PREFIX);
   if(InpShowComment)
      Comment("");
  }

//+------------------------------------------------------------------+
void DrawLevel(const int s, const string name, const double price, const datetime t1,
               const color clr, const string text)
  {
   string obj = PREFIX + "S" + IntegerToString(s + 1) + "_" + name;
   datetime t2 = t1 + PeriodSeconds(slotTf[s]) * 10;

   if(ObjectFind(0, obj) < 0)
      ObjectCreate(0, obj, OBJ_TREND, 0, t1, price, t2, price);

   ObjectSetInteger(0, obj, OBJPROP_TIME, 0, t1);
   ObjectSetDouble(0, obj, OBJPROP_PRICE, 0, price);
   ObjectSetInteger(0, obj, OBJPROP_TIME, 1, t2);
   ObjectSetDouble(0, obj, OBJPROP_PRICE, 1, price);
   ObjectSetInteger(0, obj, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, obj, OBJPROP_STYLE, InpLineStyle);
   ObjectSetInteger(0, obj, OBJPROP_WIDTH, InpLineWidth);
   ObjectSetInteger(0, obj, OBJPROP_RAY_RIGHT, true);
   ObjectSetInteger(0, obj, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, obj, OBJPROP_BACK, true);
   ObjectSetString(0, obj, OBJPROP_TOOLTIP, text);

   string lbl = obj + "_lbl";
   if(InpShowLabels)
     {
      if(ObjectFind(0, lbl) < 0)
         ObjectCreate(0, lbl, OBJ_TEXT, 0, t1, price);
      ObjectSetInteger(0, lbl, OBJPROP_TIME, 0, t1);
      ObjectSetDouble(0, lbl, OBJPROP_PRICE, 0, price);
      ObjectSetString(0, lbl, OBJPROP_TEXT, text);
      ObjectSetInteger(0, lbl, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, lbl, OBJPROP_FONTSIZE, 8);
      ObjectSetInteger(0, lbl, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0, lbl, OBJPROP_SELECTABLE, false);
     }
   else
      ObjectDelete(0, lbl);
  }

//+------------------------------------------------------------------+
// Computes one timeframe slot. Returns false if data is not ready yet.
//+------------------------------------------------------------------+
bool ProcessSlot(const int s)
  {
   ENUM_TIMEFRAMES tf = slotTf[s];
   string tfn = TfName(tf);

   int avail = iBars(_Symbol, tf);
   int count = MathMin(avail, InpMaxBars);
   int warm  = MathMax(InpSlowEMA * 3, 60);
   if(count < warm + 10)
      return false;

   datetime tm[];
   double   op[], hi[], lo[], cl[];
   if(CopyTime(_Symbol, tf, 0, count, tm)  != count) return false;
   if(CopyOpen(_Symbol, tf, 0, count, op)  != count) return false;
   if(CopyHigh(_Symbol, tf, 0, count, hi)  != count) return false;
   if(CopyLow(_Symbol, tf, 0, count, lo)   != count) return false;
   if(CopyClose(_Symbol, tf, 0, count, cl) != count) return false;

   // ---- Jurik MACD line (fast JMA - slow JMA) ----------------------
   double macd[];
   ArrayResize(macd, count);
   for(int i = 0; i < count; i++)
     {
      double price   = getPrice(InpAppliedPrice, op, cl, hi, lo, count, i);
      double fastJMA = iSmooth(price, InpFastEMA, InpJurikPhase, i, count, 0);
      double slowJMA = iSmooth(price, InpSlowEMA, InpJurikPhase, i, count, 1);
      macd[i] = fastJMA - slowJMA;
     }

   // ---- find all centerline crosses on closed bars -----------------
   int    crossBar[];
   double crossPrice[];
   int    crossDir[];      // +1 = crossed up through 0, -1 = crossed down
   int    nCross   = 0;
   int    lastSign = 0;
   int    endI     = count - 2;           // exclude the forming bar

   for(int i = warm; i <= endI; i++)
     {
      int sg = (macd[i] > 0.0) ? 1 : (macd[i] < 0.0 ? -1 : 0);
      if(sg == 0)
         continue;
      if(lastSign != 0 && sg != lastSign)
        {
         ArrayResize(crossBar, nCross + 1);
         ArrayResize(crossPrice, nCross + 1);
         ArrayResize(crossDir, nCross + 1);
         crossBar[nCross]   = i;            // first bar after the cross
         crossPrice[nCross] = cl[i];
         crossDir[nCross]   = sg;
         nCross++;
        }
      lastSign = sg;
     }

   if(nCross < 2)
     {
      slotStat[s] = "[" + tfn + "] not enough crosses found";
      return true;
     }

   // ---- collect up swings and down swings separately ---------------
   double upDist[], dnDist[];
   int nUp = 0, nDn = 0;

   for(int k = 1; k < nCross; k++)
     {
      double dist;
      bool isUp = (crossDir[k - 1] > 0);

      if(InpSwingMode == SWING_CROSS_TO_CROSS)
         dist = MathAbs(crossPrice[k] - crossPrice[k - 1]);
      else
        {
         double hh = -DBL_MAX, ll = DBL_MAX;
         for(int b = crossBar[k - 1]; b <= crossBar[k]; b++)
           {
            if(hi[b] > hh) hh = hi[b];
            if(lo[b] < ll) ll = lo[b];
           }
         dist = isUp ? (hh - crossPrice[k - 1]) : (crossPrice[k - 1] - ll);
         if(dist < 0.0) dist = 0.0;
        }

      if(isUp)
        {
         ArrayResize(upDist, nUp + 1);
         upDist[nUp++] = dist;
        }
      else
        {
         ArrayResize(dnDist, nDn + 1);
         dnDist[nDn++] = dist;
        }
     }

   if(nUp == 0 || nDn == 0)
     {
      slotStat[s] = "[" + tfn + "] need at least one up and one down swing";
      return true;
     }

   // ---- statistics per side (optionally only the last N) -----------
   int fromUp = (InpMaxSwings > 0) ? MathMax(0, nUp - InpMaxSwings) : 0;
   int fromDn = (InpMaxSwings > 0) ? MathMax(0, nDn - InpMaxSwings) : 0;

   double sumUp = 0.0, sumDn = 0.0, maxUp = 0.0, maxDn = 0.0;
   for(int i = fromUp; i < nUp; i++) { sumUp += upDist[i]; if(upDist[i] > maxUp) maxUp = upDist[i]; }
   for(int i = fromDn; i < nDn; i++) { sumDn += dnDist[i]; if(dnDist[i] > maxDn) maxDn = dnDist[i]; }

   int    usedUp = nUp - fromUp;
   int    usedDn = nDn - fromDn;
   double avgUp  = sumUp / usedUp;
   double avgDn  = sumDn / usedDn;

   bool   useMax  = (InpStatMode == STAT_MAXIMUM);
   double distUp  = useMax ? maxUp : avgUp;
   double distDn  = useMax ? maxDn : avgDn;
   string statTxt = useMax ? "Max" : "Avg";

   // ---- levels from the LAST cross ---------------------------------
   double   basePrice = crossPrice[nCross - 1];
   datetime baseT     = tm[crossBar[nCross - 1]];
   double   upper     = basePrice + distUp;
   double   lower     = basePrice - distDn;
   bool     lastUp    = (crossDir[nCross - 1] > 0);

   DrawLevel(s, "upper", upper, baseT, slotUp[s],
             tfn + " +" + statTxt + " UP swing " + DoubleToString(distUp, _Digits));
   DrawLevel(s, "lower", lower, baseT, slotDn[s],
             tfn + " -" + statTxt + " DOWN swing " + DoubleToString(distDn, _Digits));
   if(InpShowBase)
      DrawLevel(s, "base", basePrice, baseT, slotBase[s],
                tfn + " last Jurik MACD cross (" + (lastUp ? "UP" : "DOWN") + ")");
   else
     {
      string b = PREFIX + "S" + IntegerToString(s + 1) + "_base";
      ObjectDelete(0, b);
      ObjectDelete(0, b + "_lbl");
     }

   slotStat[s] = "[" + tfn + "] crosses " + IntegerToString(nCross) +
                 " | up " + IntegerToString(usedUp) + " max " + DoubleToString(maxUp, _Digits) +
                 " avg " + DoubleToString(avgUp, _Digits) +
                 " | down " + IntegerToString(usedDn) + " max " + DoubleToString(maxDn, _Digits) +
                 " avg " + DoubleToString(avgDn, _Digits) + "\n      last " + (lastUp ? "UP" : "DOWN") +
                 " @ " + DoubleToString(basePrice, _Digits) +
                 " | lines (" + statTxt + "): " + DoubleToString(upper, _Digits) +
                 " / " + DoubleToString(lower, _Digits);
   return true;
  }

//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   bool changed = (prev_calculated == 0);
   bool allOk   = true;

   // Recalculate a slot only when a new bar appears on its timeframe
   for(int s = 0; s < SLOTS; s++)
     {
      if(!slotOn[s])
         continue;
      datetime bar = iTime(_Symbol, slotTf[s], 0);
      if(bar == 0)
        {
         allOk = false;
         continue;
        }
      if(bar != slotLast[s] || prev_calculated == 0)
        {
         if(ProcessSlot(s))
           {
            slotLast[s] = bar;
            changed = true;
           }
         else
            allOk = false;
        }
     }

   if(changed)
     {
      if(InpShowComment)
        {
         string txt = "Jurik MACD Cross Swing Up/Down MTF\n";
         for(int s = 0; s < SLOTS; s++)
            if(slotOn[s] && slotStat[s] != "")
               txt += slotStat[s] + "\n";
         Comment(txt);
        }
      ChartRedraw();
     }

   return allOk ? rates_total : 0;   // returning 0 forces a retry until all data is loaded
  }

//------------------------------------------------------------------
// Jurik Smoothing Engine (taken unchanged from your indicator)
//------------------------------------------------------------------
double workSmooth[][30];

double iSmooth(double price, double length, double phase, int i, int totalBars, int instance=0)
  {
   if(ArrayRange(workSmooth, 0) != totalBars)
      ArrayResize(workSmooth, totalBars);

   int _inst = instance * 10;
   int _indC = i;
   int _indP = MathMax(i - 1, 0);
   int _indF = MathMax(i - 10, 0);

   if(i == 0 || length <= 1)
     {
      int k = 0;
      for(; k < 7; k++)  workSmooth[_indC][_inst+k] = price;
      for(; k < 10; k++) workSmooth[_indC][_inst+k] = 0;
      return(price);
     }

   double len1 = MathMax(MathLog(MathSqrt(0.5*(length-1.0)))/MathLog(2.0)+2.0, 0);
   double pow1 = MathMax(len1-2.0, 0.5);
   double del1 = price - workSmooth[_indP][_inst+5];
   double del2 = price - workSmooth[_indP][_inst+6];

   workSmooth[_indC][_inst+7] = (MathAbs(del1) > MathAbs(del2)) ? MathAbs(del1) : ((MathAbs(del1) < MathAbs(del2)) ? MathAbs(del2) : 0);
   workSmooth[_indC][_inst+8] = workSmooth[_indP][_inst+8] + (workSmooth[_indC][_inst+7] - workSmooth[_indF][_inst+7]) * 0.1;
   workSmooth[_indC][_inst+9] = workSmooth[_indP][_inst+9] + (2.0 / (MathMax(4.0*length, 30) + 1.0)) * (workSmooth[_indC][_inst+8] - workSmooth[_indP][_inst+9]);

   double dVolty = (workSmooth[_indC][_inst+9] > 0) ? workSmooth[_indC][_inst+7] / workSmooth[_indC][_inst+9] : 0;
   if(dVolty > MathPow(len1, 1.0/pow1)) dVolty = MathPow(len1, 1.0/pow1);
   if(dVolty < 1.0)                     dVolty = 1.0;

   double pow2 = MathPow(dVolty, pow1);
   double len2 = MathSqrt(0.5*(length-1)) * len1;
   double Kv   = MathPow(len2 / (len2+1), MathSqrt(pow2));

   if(del1 > 0) workSmooth[_indC][_inst+5] = price; else workSmooth[_indC][_inst+5] = price - Kv*del1;
   if(del2 < 0) workSmooth[_indC][_inst+6] = price; else workSmooth[_indC][_inst+6] = price - Kv*del2;

   double corr  = MathMax(MathMin(phase, 100), -100)/100.0 + 1.5;
   double beta  = 0.45*(length-1) / (0.45*(length-1) + 2);
   double alpha = MathPow(beta, pow2);

   workSmooth[_indC][_inst+0] = price + alpha*(workSmooth[_indP][_inst+0]-price);
   workSmooth[_indC][_inst+1] = (price - workSmooth[_indC][_inst+0])*(1-beta) + beta*workSmooth[_indP][_inst+1];
   workSmooth[_indC][_inst+2] = (workSmooth[_indC][_inst+0] + corr*workSmooth[_indC][_inst+1]);
   workSmooth[_indC][_inst+3] = (workSmooth[_indC][_inst+2] - workSmooth[_indP][_inst+4])*MathPow((1-alpha),2) + MathPow(alpha,2)*workSmooth[_indP][_inst+3];
   workSmooth[_indC][_inst+4] = (workSmooth[_indP][_inst+4] + workSmooth[_indC][_inst+3]);

   return(workSmooth[_indC][_inst+4]);
  }

//------------------------------------------------------------------
// HA / Price Engine (taken unchanged from your indicator)
//------------------------------------------------------------------
double workHa[][4];

double getPrice(int price, const double &open[], const double &close[], const double &high[], const double &low[], int bars, int i, int instanceNo=0)
  {
   if(price >= pr_haclose)
     {
      if(ArrayRange(workHa, 0) != bars)
         ArrayResize(workHa, bars);
      double haOpen;
      if(i > 0) haOpen = (workHa[i-1][instanceNo+2] + workHa[i-1][instanceNo+3])/2.0;
      else      haOpen = (open[i]+close[i])/2;

      double haClose = (open[i] + high[i] + low[i] + close[i]) / 4.0;
      double haHigh  = MathMax(high[i], MathMax(haOpen, haClose));
      double haLow   = MathMin(low[i],  MathMin(haOpen, haClose));

      if(haOpen < haClose)
        {
         workHa[i][instanceNo+0] = haLow;
         workHa[i][instanceNo+1] = haHigh;
        }
      else
        {
         workHa[i][instanceNo+0] = haHigh;
         workHa[i][instanceNo+1] = haLow;
        }
      workHa[i][instanceNo+2] = haOpen;
      workHa[i][instanceNo+3] = haClose;

      switch(price)
        {
         case pr_haclose:    return(haClose);
         case pr_haopen:     return(haOpen);
         case pr_hahigh:     return(haHigh);
         case pr_halow:      return(haLow);
         case pr_hamedian:   return((haHigh+haLow)/2.0);
         case pr_hamedianb:  return((haOpen+haClose)/2.0);
         case pr_hatypical:  return((haHigh+haLow+haClose)/3.0);
         case pr_haweighted: return((haHigh+haLow+haClose+haClose)/4.0);
         case pr_haaverage:  return((haHigh+haLow+haClose+haOpen)/4.0);
         case pr_hatbiased:
            if(haClose > haOpen) return((haHigh+haClose)/2.0);
            else                 return((haLow+haClose)/2.0);
        }
     }

   switch(price)
     {
      case pr_close:    return(close[i]);
      case pr_open:     return(open[i]);
      case pr_high:     return(high[i]);
      case pr_low:      return(low[i]);
      case pr_median:   return((high[i]+low[i])/2.0);
      case pr_medianb:  return((open[i]+close[i])/2.0);
      case pr_typical:  return((high[i]+low[i]+close[i])/3.0);
      case pr_weighted: return((high[i]+low[i]+close[i]+close[i])/4.0);
      case pr_average:  return((high[i]+low[i]+close[i]+open[i])/4.0);
      case pr_tbiased:
         if(close[i] > open[i]) return((high[i]+close[i])/2.0);
         else                   return((low[i]+close[i])/2.0);
     }
   return(0);
  }
//+------------------------------------------------------------------+
