
//+------------------------------------------------------------------+
//| Ichimoku Oscillator.mq5                                          |
//| MQL5 port of "Ichimoku Oscillator [IO]"                          |
//| Original Pine Script v5 by LonesomeTheBlue, MPL 2.0               |
//| https://mozilla.org/MPL/2.0/                                      |
//+------------------------------------------------------------------+
#property copyright "Ported from Pine Script (LonesomeTheBlue), MPL 2.0"
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 17
#property indicator_plots   11
#property indicator_level1      0.0
#property indicator_levelcolor  clrGray
#property indicator_levelstyle  STYLE_DOT

//--- NOTE on background: Pine's bgcolor() paints the entire sub-pane height.
//--- MQL5 indicator buffers have no such concept (a buffer's value only ever
//--- spans the plotted data range, never "the whole visible pane"), so the
//--- background is implemented separately below via OBJ_RECTANGLE chart
//--- objects sized to the pane's live autoscaled price range (see
//--- UpdateBackgroundRects()), not as an indicator buffer/plot.

//--- Plot 0: cloud (4th/outer layer)
#property indicator_label1  "Cloud"
#property indicator_type1   DRAW_COLOR_HISTOGRAM
#property indicator_color1  clrRed,clrGreen
#property indicator_width1  2

//--- Plot 1: ConvBase (3rd layer)
#property indicator_label2  "ConvBase"
#property indicator_type2   DRAW_COLOR_HISTOGRAM
#property indicator_color2  clrRed,clrGreen
#property indicator_width2  2

//--- Plot 2: Lagging (2nd layer)
#property indicator_label3  "Lagging"
#property indicator_type3   DRAW_COLOR_HISTOGRAM
#property indicator_color3  clrRed,clrGreen
#property indicator_width3  2

//--- Plot 3: Oscline (1st/inner layer)
#property indicator_label4  "Oscline"
#property indicator_type4   DRAW_COLOR_HISTOGRAM
#property indicator_color4  clrSilver,clrGreen,clrRed
#property indicator_width4  2

//--- Plot 4: close-position xcross (plotted at cloud value)
#property indicator_label5  "Close Trade"
#property indicator_type5   DRAW_COLOR_ARROW
#property indicator_color5  clrWhite,clrYellow
#property indicator_width5  2

//--- Plot 5: long entry triangle (at y=0)
#property indicator_label6  "Long Entry"
#property indicator_type6   DRAW_ARROW
#property indicator_color6  clrWhite
#property indicator_width6  2

//--- Plot 6: long "bounce off support" tiny triangle (at y=0)
#property indicator_label7  "Long Bounce"
#property indicator_type7   DRAW_ARROW
#property indicator_color7  clrWhite
#property indicator_width7  1

//--- Plot 7: short entry triangle (at y=0)
#property indicator_label8  "Short Entry"
#property indicator_type8   DRAW_ARROW
#property indicator_color8  clrYellow
#property indicator_width8  2

//--- Plot 8: short "bounce off resistance" tiny triangle (at y=0)
#property indicator_label9  "Short Bounce"
#property indicator_type9   DRAW_ARROW
#property indicator_color9  clrYellow
#property indicator_width9  1

//--- Plot 9: EMA of cloud
#property indicator_label10 "EMA"
#property indicator_type10  DRAW_LINE
#property indicator_color10 C'209,196,233'
#property indicator_width10 1

//--- Plot 10: partial-profit xcross (plotted at cloud value)
#property indicator_label11 "Partial Profit"
#property indicator_type11  DRAW_COLOR_ARROW
#property indicator_color11 clrWhite,clrYellow,C'209,196,233'
#property indicator_width11 1

//＋ＩＮＰＵＴＳ｜――――――――――――――――――――――――――――――――――――――――――――――――――――――――

enum ENUM_BG_CHANNEL
{
   BG_GREEN, // Green
   BG_RED,   // Red
   BG_BLUE   // Blue
};

enum ENUM_ALERT_FREQ
{
   FREQ_BAR_CLOSE, // Once per bar close
   FREQ_BAR        // Once per bar
};

input group "Setup"
input int    InpConversionPeriods   = 8;     // Conversion Line Length
input int    InpBasePeriods         = 13;    // Base Line Length
input int    InpLaggingSpan2Periods = 26;    // Leading Span B Length
input int    InpDisplacement        = 13;    // Lagging Span
input bool   InpUseATR              = true;  // Use ATR (Protection from Whipsaw)
input int    InpATRLen              = 9;     //  Length
input double InpATRMult             = 2.0;   //  Mult
input bool   InpBounceOff           = false; // Bounce Off Support/Resistance
input bool   InpUseEMA              = true;  // Show EMA
input int    InpEMALen              = 9;     //  Length
input color  InpEMAColor            = C'209,196,233'; //  Color
input int    InpEMAWidth            = 1;     //  Width
input bool   InpEMATakePartialProfit = true; // Take Partial Profit by EMA

input group "Colors"
input color  InpEntryLongColor  = clrWhite;          // Entry/Exit Long Color
input color  InpEntryShortColor = clrYellow;         // Entry/Exit Short Color
input bool   InpColoredBg       = true;              // Colored Background (Set to false for low-performance PCs)
input bool   InpAutoWidth       = true;              // Auto-adjust Histogram Width (Smooth area look)
input int    InpManualWidth     = 2;                 //  Manual Width (if Auto-adjust is false, 1-5)
input ENUM_BG_CHANNEL InpBgUpChannel = BG_GREEN;      //  Uptrend Channel
input ENUM_BG_CHANNEL InpBgDnChannel = BG_BLUE;       //  Downtrend Channel
input int    InpBgTransparency  = 60;                //  Transparency (0-100, approximated)
input color  InpUpColor1 = C'0,250,0';   // Uptrend Color 1
input color  InpUpColor2 = C'0,190,0';   // Uptrend Color 2
input color  InpUpColor3 = C'0,130,0';   // Uptrend Color 3
input color  InpUpColor4 = C'0,70,0';    // Uptrend Color 4
input color  InpDownColor1 = C'250,0,0'; // Downtrend Color 1
input color  InpDownColor2 = C'190,0,0'; // Downtrend Color 2
input color  InpDownColor3 = C'130,0,0'; // Downtrend Color 3
input color  InpDownColor4 = C'70,0,0';  // Downtrend Color 4
input color  InpInSRColor = C'209,212,220'; // In Support/Resistance Zone

input group "Alerts"
input bool   InpMainAlert  = true;  // Main Buy/Sell Alert
input bool   InpAddAlert   = true;  // Additional Buy/Sell Alert
input string InpAlertLongMsg     = "Buy / Main, Close = close";      //  Long message
input string InpAlertShortMsg    = "Sell / Main, Close = close";     //  Short message
input string InpAlertAddLongMsg  = "Buy / Additonal, Close = close"; //  Long message
input string InpAlertAddShortMsg = "Sell / Additonal, Close = close";//  Short message
input ENUM_ALERT_FREQ InpMainFrequency = FREQ_BAR_CLOSE; //  Frequency
input bool   InpCloseAlert     = true; // Close Position Alert
input bool   InpClosePartAlert = true; // Take Partial Profit Alert
input string InpCloseMainMsg = "Close Position, Close = close";        //  Close Position Message
input string InpClosePartMsg = "Take Partial Profit, Close = close";   //  Take Partial Profit Message
input ENUM_ALERT_FREQ InpCloseFrequency = FREQ_BAR; //  Frequency

//＋ＢＵＦＦＥＲＳ｜――――――――――――――――――――――――――――――――――――――――――――――――――――――――

double cloudBuf[], cloudColorBuf[];
double convBaseBuf[], convBaseColorBuf[];
double laggingBuf[], laggingColorBuf[];
double osclineBuf[], osclineColorBuf[];
double closeTradeBuf[], closeTradeColorBuf[];
double longEntryBuf[], longBounceBuf[], shortEntryBuf[], shortBounceBuf[];
double emaBuf[];
double partExitBuf[], partExitColorBuf[];

//--- internal per-bar state arrays (0 = oldest), sized to rates_total
double g_conversionLine[], g_baseLine[], g_leadLine1[], g_leadLine2[];
double g_cloudMin[], g_cloudMax[];
bool   g_inCloud[];
int    g_mtrend[];
int    g_trend[];
double g_entryLevel[];

int g_atrHandle = INVALID_HANDLE;
int g_lastAlertBar = -1;

//--- background rectangle-object state (see UpdateBackgroundRects)
#define BG_OBJ_PREFIX "IO_BG_"
int      g_bgWin = -1;
int      g_bgObjCount = 0;
datetime g_bgTime[];
int      g_bgRatesTotal = 0;

#define IO_NA EMPTY_VALUE

//+------------------------------------------------------------------+
//| True alpha compositing of color c (with Pine-style 0-100         |
//| transparency, 0=opaque, 100=fully invisible) over the ACTUAL     |
//| chart background color, exactly reproducing what                |
//| color.rgb(r,g,b,transparency) looks like over a TradingView pane |
//| (MQL5 colors carry no alpha channel natively, so this is done by |
//| hand against the real CHART_COLOR_BACKGROUND rather than a fixed |
//| gray guess).                                                     |
//+------------------------------------------------------------------+
color BlendOverChartBg(color c, int transp)
{
   transp = MathMax(0, MathMin(100, transp));
   color bg = (color)ChartGetInteger(0, CHART_COLOR_BACKGROUND);
   double a = (100 - transp) / 100.0;
   int fr = (int)(c & 0xFF),  fg = (int)((c >> 8) & 0xFF),  fb = (int)((c >> 16) & 0xFF);
   int br = (int)(bg & 0xFF), bgc = (int)((bg >> 8) & 0xFF), bb = (int)((bg >> 16) & 0xFF);
   int r = (int)MathRound(fr * a + br * (1.0 - a));
   int g = (int)MathRound(fg * a + bgc * (1.0 - a));
   int b = (int)MathRound(fb * a + bb * (1.0 - a));
   return (color)(r | (g << 8) | (b << 16));
}

//+------------------------------------------------------------------+
//| Reproduces bgcolup/bgcoldn for a given trend value (-4..4, !=0)   |
//+------------------------------------------------------------------+
color ComputeBgColor(int trendVal)
{
   int mag = (int)MathPow(MathAbs(trendVal), 3); // 1,8,27,64
   int mult = 159 + mag; // 160..223, clipped below
   mult = MathMax(0, MathMin(255, mult));
   int r = 0, g = 0, b = 0;
   ENUM_BG_CHANNEL ch = (trendVal > 0) ? InpBgUpChannel : InpBgDnChannel;
   if(ch == BG_RED)   r = mult;
   if(ch == BG_GREEN) g = mult;
   if(ch == BG_BLUE)  b = mult;
   color c = (color)(r | (g << 8) | (b << 16));
   return BlendOverChartBg(c, InpBgTransparency);
}

//+------------------------------------------------------------------+
int OnInit()
{
   SetIndexBuffer(0,  cloudBuf,        INDICATOR_DATA);
   SetIndexBuffer(1,  cloudColorBuf,   INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2,  convBaseBuf,     INDICATOR_DATA);
   SetIndexBuffer(3,  convBaseColorBuf,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(4,  laggingBuf,      INDICATOR_DATA);
   SetIndexBuffer(5,  laggingColorBuf, INDICATOR_COLOR_INDEX);
   SetIndexBuffer(6,  osclineBuf,      INDICATOR_DATA);
   SetIndexBuffer(7,  osclineColorBuf, INDICATOR_COLOR_INDEX);
   SetIndexBuffer(8,  closeTradeBuf,     INDICATOR_DATA);
   SetIndexBuffer(9,  closeTradeColorBuf,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(10, longEntryBuf,   INDICATOR_DATA);
   SetIndexBuffer(11, longBounceBuf,  INDICATOR_DATA);
   SetIndexBuffer(12, shortEntryBuf,  INDICATOR_DATA);
   SetIndexBuffer(13, shortBounceBuf, INDICATOR_DATA);
   SetIndexBuffer(14, emaBuf,         INDICATOR_DATA);
   SetIndexBuffer(15, partExitBuf,      INDICATOR_DATA);
   SetIndexBuffer(16, partExitColorBuf, INDICATOR_COLOR_INDEX);

   ArraySetAsSeries(cloudBuf, false);        ArraySetAsSeries(cloudColorBuf, false);
   ArraySetAsSeries(convBaseBuf, false);     ArraySetAsSeries(convBaseColorBuf, false);
   ArraySetAsSeries(laggingBuf, false);      ArraySetAsSeries(laggingColorBuf, false);
   ArraySetAsSeries(osclineBuf, false);      ArraySetAsSeries(osclineColorBuf, false);
   ArraySetAsSeries(closeTradeBuf, false);   ArraySetAsSeries(closeTradeColorBuf, false);
   ArraySetAsSeries(longEntryBuf, false);    ArraySetAsSeries(longBounceBuf, false);
   ArraySetAsSeries(shortEntryBuf, false);   ArraySetAsSeries(shortBounceBuf, false);
   ArraySetAsSeries(emaBuf, false);
   ArraySetAsSeries(partExitBuf, false);     ArraySetAsSeries(partExitColorBuf, false);

   ArraySetAsSeries(g_conversionLine, false); ArraySetAsSeries(g_baseLine, false);
   ArraySetAsSeries(g_leadLine1, false);      ArraySetAsSeries(g_leadLine2, false);
   ArraySetAsSeries(g_cloudMin, false);       ArraySetAsSeries(g_cloudMax, false);
   ArraySetAsSeries(g_inCloud, false);        ArraySetAsSeries(g_mtrend, false);
   ArraySetAsSeries(g_trend, false);          ArraySetAsSeries(g_entryLevel, false);

   for(int p = 0; p < 11; p++)
      PlotIndexSetDouble(p, PLOT_EMPTY_VALUE, IO_NA);

   PlotIndexSetInteger(0, PLOT_LINE_COLOR, 0, InpDownColor4);
   PlotIndexSetInteger(0, PLOT_LINE_COLOR, 1, InpUpColor4);

   PlotIndexSetInteger(1, PLOT_LINE_COLOR, 0, InpDownColor3);
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, 1, InpUpColor3);

   PlotIndexSetInteger(2, PLOT_LINE_COLOR, 0, InpDownColor2);
   PlotIndexSetInteger(2, PLOT_LINE_COLOR, 1, InpUpColor2);

   PlotIndexSetInteger(3, PLOT_LINE_COLOR, 0, InpInSRColor);
   PlotIndexSetInteger(3, PLOT_LINE_COLOR, 1, InpUpColor1);
   PlotIndexSetInteger(3, PLOT_LINE_COLOR, 2, InpDownColor1);

   PlotIndexSetInteger(4, PLOT_ARROW, 251);
   PlotIndexSetInteger(4, PLOT_LINE_COLOR, 0, InpEntryLongColor);
   PlotIndexSetInteger(4, PLOT_LINE_COLOR, 1, InpEntryShortColor);

   PlotIndexSetInteger(5, PLOT_ARROW, 233);
   PlotIndexSetInteger(5, PLOT_LINE_COLOR, InpEntryLongColor);
   PlotIndexSetInteger(6, PLOT_ARROW, 233);
   PlotIndexSetInteger(6, PLOT_LINE_COLOR, InpEntryLongColor);
   PlotIndexSetInteger(7, PLOT_ARROW, 234);
   PlotIndexSetInteger(7, PLOT_LINE_COLOR, InpEntryShortColor);
   PlotIndexSetInteger(8, PLOT_ARROW, 234);
   PlotIndexSetInteger(8, PLOT_LINE_COLOR, InpEntryShortColor);

   PlotIndexSetInteger(9, PLOT_LINE_WIDTH, InpEMAWidth);
   PlotIndexSetInteger(9, PLOT_LINE_COLOR, InpUseEMA ? InpEMAColor : clrNONE);

   PlotIndexSetInteger(10, PLOT_ARROW, 251);
   PlotIndexSetInteger(10, PLOT_LINE_COLOR, 0, InpEntryLongColor);
   PlotIndexSetInteger(10, PLOT_LINE_COLOR, 1, InpEntryShortColor);
   PlotIndexSetInteger(10, PLOT_LINE_COLOR, 2, InpEMAColor);

   g_atrHandle = InpUseATR ? iATR(_Symbol, _Period, InpATRLen) : INVALID_HANDLE;
   g_lastAlertBar = -1;

   ClearBgObjects();
   UpdateHistogramWidths();
   IndicatorSetString(INDICATOR_SHORTNAME, "Ichimoku Oscillator");
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Dynamically scales histogram bar widths based on chart zoom scale|
//| so the oscillator layers render as a solid, smooth filled area   |
//| without gaps or crowding.                                        |
//+------------------------------------------------------------------+
void UpdateHistogramWidths()
{
   int w = InpManualWidth;
   if(InpAutoWidth)
   {
      int scale = (int)ChartGetInteger(0, CHART_SCALE);
      if(scale <= 0)      w = 1;
      else if(scale == 1) w = 2;
      else if(scale == 2) w = 3;
      else if(scale == 3) w = 4;
      else                w = 5;
   }
   for(int p = 0; p < 4; p++)
      PlotIndexSetInteger(p, PLOT_LINE_WIDTH, w);
}

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(g_atrHandle != INVALID_HANDLE) IndicatorRelease(g_atrHandle);
   ClearBgObjects();
}

//+------------------------------------------------------------------+
void ClearBgObjects()
{
   ObjectsDeleteAll(0, BG_OBJ_PREFIX);
   g_bgObjCount = 0;
}

//+------------------------------------------------------------------+
//| Paints the sub-pane background using rectangle objects sized to  |
//| the pane's live autoscaled price range (ChartGetDouble PRICE_MAX |
//| /MIN). Works reliably across all timeframes.                      |
//+------------------------------------------------------------------+
void UpdateBackgroundRects()
{
   if(g_bgRatesTotal <= 1) return;

   if(!InpColoredBg)
   {
      ClearBgObjects();
      return;
   }

   int subWin = ChartWindowFind();
   if(subWin < 0) subWin = 0;

   double top    = ChartGetDouble(0, CHART_PRICE_MAX, subWin);
   double bottom = ChartGetDouble(0, CHART_PRICE_MIN, subWin);

   int rt = g_bgRatesTotal;

   // If chart price bounds are not yet ready (common on initial timeframe switch),
   // estimate bounds from the plotted indicator buffer values so background renders immediately.
   if(top <= bottom)
   {
      double maxVal = -1e9;
      double minVal =  1e9;
      int scanStart = MathMax(0, rt - 300);
      for(int idx = scanStart; idx < rt; idx++)
      {
         if(cloudBuf[idx] != IO_NA)    { if(cloudBuf[idx] > maxVal) maxVal = cloudBuf[idx]; if(cloudBuf[idx] < minVal) minVal = cloudBuf[idx]; }
         if(osclineBuf[idx] != IO_NA)  { if(osclineBuf[idx] > maxVal) maxVal = osclineBuf[idx]; if(osclineBuf[idx] < minVal) minVal = osclineBuf[idx]; }
      }
      if(maxVal > minVal)
      {
         double margin = (maxVal - minVal) * 0.2;
         if(margin == 0.0) margin = 1.0;
         top = maxVal + margin;
         bottom = minVal - margin;
      }
      else
      {
         top = 100.0;
         bottom = -100.0;
      }
   }

   int firstSeries = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR);
   int visBars      = (int)ChartGetInteger(0, CHART_WIDTH_IN_BARS);

   int idxOldest = 0;
   int idxNewest = rt - 1;

   if(firstSeries > 0 && visBars > 0)
   {
      int sMax = firstSeries + 10;
      int sMin = MathMax(0, firstSeries - visBars - 10);
      idxOldest = rt - 1 - sMax; if(idxOldest < 0) idxOldest = 0;
      idxNewest = rt - 1 - sMin; if(idxNewest > rt - 1) idxNewest = rt - 1;
   }
   else
   {
      idxOldest = MathMax(0, rt - 300);
      idxNewest = rt - 1;
   }

   if(idxNewest < idxOldest) return;

   long periodSecs = PeriodSeconds();
   int need = idxNewest - idxOldest + 1;

   for(int k = 0; k < need; k++)
   {
      int idx = idxOldest + k;
      string name = BG_OBJ_PREFIX + IntegerToString(k);
      datetime t1 = g_bgTime[idx];
      datetime t2 = (idx + 1 < rt) ? g_bgTime[idx+1] : (datetime)((long)g_bgTime[idx] + periodSecs);

      int trendVal = g_trend[idx];
      color c = (trendVal == 0) ? InpInSRColor : ComputeBgColor(trendVal);

      if(ObjectFind(0, name) < 0)
      {
         ObjectCreate(0, name, OBJ_RECTANGLE, subWin, t1, bottom, t2, top);
         ObjectSetInteger(0, name, OBJPROP_BACK, true);
         ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
         ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
         ObjectSetInteger(0, name, OBJPROP_FILL, true);
         ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
         ObjectSetInteger(0, name, OBJPROP_ZORDER, -100);
      }
      ObjectMove(0, name, 0, t1, bottom);
      ObjectMove(0, name, 1, t2, top);
      ObjectSetInteger(0, name, OBJPROP_COLOR, c);
   }
   for(int k = need; k < g_bgObjCount; k++)
      ObjectDelete(0, BG_OBJ_PREFIX + IntegerToString(k));
   g_bgObjCount = need;
}

//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   if(id == CHARTEVENT_CHART_CHANGE)
   {
      UpdateHistogramWidths();
      UpdateBackgroundRects();
   }
}

//+------------------------------------------------------------------+
double Highest(const double &arr[], int i, int len)
{
   int from = i - len + 1;
   if(from < 0) return IO_NA;
   double m = arr[from];
   for(int k = from + 1; k <= i; k++) if(arr[k] > m) m = arr[k];
   return m;
}

double Lowest(const double &arr[], int i, int len)
{
   int from = i - len + 1;
   if(from < 0) return IO_NA;
   double m = arr[from];
   for(int k = from + 1; k <= i; k++) if(arr[k] < m) m = arr[k];
   return m;
}

//+------------------------------------------------------------------+
string GetCloseMsg(const string &tmpl, double closePx)
{
   string s = tmpl;
   StringReplace(s, "close", DoubleToString(closePx, _Digits));
   return s;
}

//+------------------------------------------------------------------+
//| Fires alerts for one newly-closed bar i                          |
//+------------------------------------------------------------------+
void ProcessAlerts(int i, const double &close[])
{
   if(i <= 0) return;

   bool closetrade = (g_trend[i] != g_trend[i-1]) && (MathAbs(g_trend[i-1]) == 4);
   if(closetrade && InpCloseAlert)
   {
      string msg = GetCloseMsg(InpCloseMainMsg, close[i]);
      Alert(msg); SendNotification(msg);
   }

   bool uptrend = (g_trend[i] == 4) && (g_trend[i] != g_trend[i-1]);
   bool bouncbackup = InpBounceOff && g_trend[i] == 4 && close[i] > g_cloudMax[i] && g_inCloud[i-1];
   bool downtrend = (g_trend[i] == -4) && (g_trend[i] != g_trend[i-1]);
   bool bouncbackdn = InpBounceOff && g_trend[i] == -4 && close[i] < g_cloudMin[i] && g_inCloud[i-1];

   if(g_trend[i] == 4)
   {
      if(uptrend && InpMainAlert)
      { string msg = GetCloseMsg(InpAlertLongMsg, close[i]); Alert(msg); SendNotification(msg); }
      if(bouncbackup && InpAddAlert)
      { string msg = GetCloseMsg(InpAlertAddLongMsg, close[i]); Alert(msg); SendNotification(msg); }
   }
   if(g_trend[i] == -4)
   {
      if(downtrend && InpMainAlert)
      { string msg = GetCloseMsg(InpAlertShortMsg, close[i]); Alert(msg); SendNotification(msg); }
      if(bouncbackdn && InpAddAlert)
      { string msg = GetCloseMsg(InpAlertAddShortMsg, close[i]); Alert(msg); SendNotification(msg); }
   }

   // partial-profit crossover conditions (no "profitable" gate on the alert itself)
   bool convcrossbase = i > 0 && g_conversionLine[i] > g_baseLine[i] && g_conversionLine[i-1] <= g_baseLine[i-1];
   bool basecrosscons  = i > 0 && g_baseLine[i] > g_conversionLine[i] && g_baseLine[i-1] <= g_conversionLine[i-1];
   bool emacrosscloud = i > 0 && emaBuf[i] > cloudBuf[i] && emaBuf[i-1] <= cloudBuf[i-1];
   bool cloudcrossema = i > 0 && cloudBuf[i] > emaBuf[i] && cloudBuf[i-1] <= emaBuf[i-1];

   bool partclose = (g_trend[i] == 4 && basecrosscons) ||
                     (g_trend[i] == 4 && emacrosscloud && InpEMATakePartialProfit) ||
                     (g_trend[i] == -4 && convcrossbase) ||
                     (g_trend[i] == -4 && cloudcrossema && InpEMATakePartialProfit);

   if(partclose && InpClosePartAlert)
   { string msg = GetCloseMsg(InpClosePartMsg, close[i]); Alert(msg); SendNotification(msg); }
}

//+------------------------------------------------------------------+
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[])
{
   int minBars = MathMax(InpLaggingSpan2Periods, MathMax(InpConversionPeriods, InpBasePeriods)) + InpDisplacement * 2 + 2;
   if(rates_total < minBars) return 0;

   ArrayResize(g_conversionLine, rates_total); ArrayResize(g_baseLine, rates_total);
   ArrayResize(g_leadLine1, rates_total);      ArrayResize(g_leadLine2, rates_total);
   ArrayResize(g_cloudMin, rates_total);       ArrayResize(g_cloudMax, rates_total);
   ArrayResize(g_inCloud, rates_total);        ArrayResize(g_mtrend, rates_total);
   ArrayResize(g_trend, rates_total);          ArrayResize(g_entryLevel, rates_total);

   double atrArr[];
   ArraySetAsSeries(atrArr, false);
   int atrCopied = 0;
   if(InpUseATR && g_atrHandle != INVALID_HANDLE)
      atrCopied = CopyBuffer(g_atrHandle, 0, 0, rates_total, atrArr);

   int start = (prev_calculated > 1) ? prev_calculated - 2 : 0;
   int dOff = InpDisplacement - 1;

   for(int i = start; i < rates_total; i++)
   {
      double ch = Highest(high, i, InpConversionPeriods);
      double cl = Lowest(low, i, InpConversionPeriods);
      double bh = Highest(high, i, InpBasePeriods);
      double bl = Lowest(low, i, InpBasePeriods);
      double lh = Highest(high, i, InpLaggingSpan2Periods);
      double ll = Lowest(low, i, InpLaggingSpan2Periods);

      bool haveConv = (ch != IO_NA && cl != IO_NA);
      bool haveBase = (bh != IO_NA && bl != IO_NA);
      bool haveLead2 = (lh != IO_NA && ll != IO_NA);

      g_conversionLine[i] = haveConv ? (ch + cl) / 2.0 : IO_NA;
      g_baseLine[i]       = haveBase ? (bh + bl) / 2.0 : IO_NA;
      g_leadLine1[i]      = (haveConv && haveBase) ? (g_conversionLine[i] + g_baseLine[i]) / 2.0 : IO_NA;
      g_leadLine2[i]      = haveLead2 ? (lh + ll) / 2.0 : IO_NA;

      int prevMtrend = (i > 0) ? g_mtrend[i-1] : 0;
      int prevTrend  = (i > 0) ? g_trend[i-1]  : 0;
      double prevEntryLevel = (i > 0) ? g_entryLevel[i-1] : IO_NA;

      bool haveShift1 = (i - dOff >= 0) && g_leadLine1[i-dOff] != IO_NA && g_leadLine2[i-dOff] != IO_NA;

      if(!haveShift1)
      {
         g_cloudMin[i] = IO_NA; g_cloudMax[i] = IO_NA; g_inCloud[i] = false;
         g_mtrend[i] = prevMtrend; g_trend[i] = prevTrend; g_entryLevel[i] = prevEntryLevel;

         cloudBuf[i] = IO_NA; cloudColorBuf[i] = 0;
         convBaseBuf[i] = IO_NA; convBaseColorBuf[i] = 0;
         laggingBuf[i] = IO_NA; laggingColorBuf[i] = 0;
         osclineBuf[i] = IO_NA; osclineColorBuf[i] = 0;
         closeTradeBuf[i] = IO_NA; closeTradeColorBuf[i] = 0;
         longEntryBuf[i] = IO_NA; longBounceBuf[i] = IO_NA;
         shortEntryBuf[i] = IO_NA; shortBounceBuf[i] = IO_NA;
         partExitBuf[i] = IO_NA; partExitColorBuf[i] = 0;
         emaBuf[i] = IO_NA;
         continue;
      }

      double ll1s = g_leadLine1[i-dOff];
      double ll2s = g_leadLine2[i-dOff];
      double cloudMin = MathMin(ll1s, ll2s);
      double cloudMax = MathMax(ll1s, ll2s);
      g_cloudMin[i] = cloudMin; g_cloudMax[i] = cloudMax;

      bool inCloud = (close[i] >= cloudMin && close[i] <= cloudMax);
      g_inCloud[i] = inCloud;

      int mt = prevMtrend;
      if(close[i] > cloudMax) mt = 1;
      else if(close[i] < cloudMin) mt = -1;
      g_mtrend[i] = mt;

      bool cloudup  = g_leadLine1[i] >= g_leadLine2[i];
      bool clouddn  = g_leadLine1[i] <= g_leadLine2[i];

      // first layer: Oscline
      double oscline = (mt == 1) ? (close[i] - cloudMin) : (close[i] - cloudMax);
      int osccolIdx = inCloud ? 0 : (close[i] > cloudMax ? 1 : 2);
      osclineBuf[i] = oscline; osclineColorBuf[i] = osccolIdx;

      // second layer: Lagging
      bool haveShift2 = (i - dOff - dOff >= 0) && g_cloudMax[i-dOff] != IO_NA && g_cloudMin[i-dOff] != IO_NA;
      double lagging;
      if(haveShift2)
      {
         double cloudMaxShift2 = g_cloudMax[i-dOff];
         double cloudMinShift2 = g_cloudMin[i-dOff];
         double extra = (mt == 1) ? MathMax(close[i] - cloudMaxShift2, 0.0) : MathMin(close[i] - cloudMinShift2, 0.0);
         lagging = oscline + extra;
      }
      else
      {
         lagging = oscline;
      }

      int lagColIdx = (close[i] > cloudMax) ? 1 : (close[i] < cloudMin ? 0 : -1);
      if(haveShift2 && lagColIdx >= 0)
      {
         laggingBuf[i] = lagging;
         laggingColorBuf[i] = lagColIdx;
      }
      else
      {
         laggingBuf[i] = IO_NA;
         laggingColorBuf[i] = 0;
      }

      // third layer: ConvBase
      double convBaseExtra = (mt == 1) ? MathMax(g_conversionLine[i] - g_baseLine[i], 0.0) : MathMin(g_conversionLine[i] - g_baseLine[i], 0.0);
      double convBase = lagging + convBaseExtra;
      convBaseBuf[i] = haveShift2 ? convBase : IO_NA;
      convBaseColorBuf[i] = (g_conversionLine[i] >= g_baseLine[i]) ? 1 : 0;

      // fourth layer: cloud
      double cloudExtra = (mt == 1) ? MathMax(g_leadLine1[i] - g_leadLine2[i], 0.0) : MathMin(g_leadLine1[i] - g_leadLine2[i], 0.0);
      double cloudVal = convBase + cloudExtra;
      cloudBuf[i] = haveShift2 ? cloudVal : IO_NA;
      cloudColorBuf[i] = (mt == 1) ? 1 : 0;

      bool anyRising  = i > 0 && g_conversionLine[i-1] != IO_NA && g_baseLine[i-1] != IO_NA &&
                         ((g_conversionLine[i] - g_conversionLine[i-1] > 0) || (g_baseLine[i] - g_baseLine[i-1] > 0));
      bool anyFalling = i > 0 && g_conversionLine[i-1] != IO_NA && g_baseLine[i-1] != IO_NA &&
                         ((g_conversionLine[i] - g_conversionLine[i-1] < 0) || (g_baseLine[i] - g_baseLine[i-1] < 0));
      bool convoverbase = g_conversionLine[i] >= g_baseLine[i];
      bool baseoverconv = g_conversionLine[i] <= g_baseLine[i];

      double tole = 0.0;
      if(InpUseATR && i < atrCopied && atrArr[i] > 0.0) tole = atrArr[i] * InpATRMult;

      int trend = prevTrend;
      double entryLevel = prevEntryLevel;

      if(mt == 1)
      {
         if(prevMtrend == -1) trend = 0;
         if(trend < 4 && close[i] > cloudMax)
         {
            trend = (lagging > oscline ? 1 : 0) +
                    ((convoverbase && anyRising) ? 1 : 0) +
                    (cloudup ? 1 : 0) + 1;
            if(trend == 4) entryLevel = close[i];
         }
         else
         {
            trend = (g_conversionLine[i] < g_baseLine[i] - tole) ? 0 : trend;
         }
      }
      else if(mt == -1)
      {
         if(prevMtrend == 1) trend = 0;
         if(trend > -4 && close[i] < cloudMin)
         {
            trend = (lagging < oscline ? -1 : 0) -
                    ((baseoverconv && anyFalling) ? 1 : 0) -
                    (clouddn ? 1 : 0) - 1;
            if(trend == -4) entryLevel = close[i];
         }
         else
         {
            trend = (g_conversionLine[i] > g_baseLine[i] + tole) ? 0 : trend;
         }
      }

      g_trend[i] = trend;
      g_entryLevel[i] = entryLevel;

      // EMA of the cloud layer (seeds from first valid cloudBuf value)
      if(cloudBuf[i] == IO_NA)
      {
         emaBuf[i] = IO_NA;
      }
      else
      {
         if(i == 0 || emaBuf[i-1] == IO_NA)
         {
            emaBuf[i] = cloudBuf[i];
         }
         else
         {
            double k = 2.0 / (InpEMALen + 1.0);
            emaBuf[i] = cloudBuf[i] * k + emaBuf[i-1] * (1.0 - k);
         }
      }

      // shapes (computed on every bar so the still-forming last bar previews correctly)
      bool closetrade = i > 0 && (trend != g_trend[i-1]) && (MathAbs(g_trend[i-1]) == 4);
      if(closetrade)
      {
         closeTradeBuf[i] = cloudBuf[i];
         closeTradeColorBuf[i] = (g_trend[i-1] > 0) ? 0 : 1;
      }
      else { closeTradeBuf[i] = IO_NA; closeTradeColorBuf[i] = 0; }

      bool uptrend = (trend == 4) && (i > 0 ? trend != g_trend[i-1] : true);
      longEntryBuf[i] = uptrend ? 0.0 : IO_NA;

      bool bouncbackup = InpBounceOff && trend == 4 && close[i] > cloudMax && (i > 0 && g_inCloud[i-1]);
      longBounceBuf[i] = bouncbackup ? 0.0 : IO_NA;

      bool downtrend = (trend == -4) && (i > 0 ? trend != g_trend[i-1] : true);
      shortEntryBuf[i] = downtrend ? 0.0 : IO_NA;

      bool bouncbackdn = InpBounceOff && trend == -4 && close[i] < cloudMin && (i > 0 && g_inCloud[i-1]);
      shortBounceBuf[i] = bouncbackdn ? 0.0 : IO_NA;

      // partial-profit exit marker (visual only requires the "profitable" gate)
      bool profitable = (trend == 4 && entryLevel != IO_NA && close[i] > entryLevel) ||
                         (trend == -4 && entryLevel != IO_NA && close[i] < entryLevel);
      bool convcrossbase = i > 0 && g_conversionLine[i] != IO_NA && g_conversionLine[i-1] != IO_NA &&
                            g_conversionLine[i] > g_baseLine[i] && g_conversionLine[i-1] <= g_baseLine[i-1];
      bool basecrosscons  = i > 0 && g_baseLine[i] != IO_NA && g_baseLine[i-1] != IO_NA &&
                            g_baseLine[i] > g_conversionLine[i] && g_baseLine[i-1] <= g_conversionLine[i-1];
      bool emacrosscloud = i > 0 && cloudBuf[i] != IO_NA && cloudBuf[i-1] != IO_NA &&
                            emaBuf[i] > cloudBuf[i] && emaBuf[i-1] <= cloudBuf[i-1];
      bool cloudcrossema = i > 0 && cloudBuf[i] != IO_NA && cloudBuf[i-1] != IO_NA &&
                            cloudBuf[i] > emaBuf[i] && cloudBuf[i-1] <= emaBuf[i-1];

      int posexitIdx = -1;
      if(profitable)
      {
         if(trend == 4 && basecrosscons) posexitIdx = 0;
         else if(trend == 4 && emacrosscloud && InpEMATakePartialProfit) posexitIdx = 2;
         else if(trend == -4 && convcrossbase) posexitIdx = 1;
         else if(trend == -4 && cloudcrossema && InpEMATakePartialProfit) posexitIdx = 2;
      }
      if(posexitIdx >= 0 && cloudBuf[i] != IO_NA)
      {
         partExitBuf[i] = cloudBuf[i];
         partExitColorBuf[i] = posexitIdx;
      }
      else { partExitBuf[i] = IO_NA; partExitColorBuf[i] = 0; }
   }

   // alerts: only ever evaluated once per fully-closed bar
   int closedEnd = rates_total - 2;
   for(int i = MathMax(g_lastAlertBar + 1, start); i <= closedEnd; i++)
      ProcessAlerts(i, close);
   if(closedEnd > g_lastAlertBar) g_lastAlertBar = closedEnd;

   ArrayResize(g_bgTime, rates_total);
   ArrayCopy(g_bgTime, time);
   g_bgRatesTotal = rates_total;
   UpdateBackgroundRects();

   return rates_total;
}
//+------------------------------------------------------------------+
