//+------------------------------------------------------------------+
//| 75_TII_Threshold_Reentry_Alert_v1_00.mq5                       |
//| Compares two TII baselines and alerts on confirmed re-entries. |
//+------------------------------------------------------------------+
#property copyright "FXおもしろラボ"
#property version   "1.00"
#property strict
#property description "Trend Intensity Index with confirmed 20-up and 80-down notifications."

#property indicator_separate_window
#property indicator_minimum 0.0
#property indicator_maximum 100.0
#property indicator_buffers 6
#property indicator_plots   5
#property indicator_level1  20.0
#property indicator_level2  50.0
#property indicator_level3  80.0
#property indicator_levelcolor clrDimGray
#property indicator_levelstyle STYLE_DASH

#property indicator_label1 "TII current baseline"
#property indicator_type1  DRAW_LINE
#property indicator_color1 clrGold
#property indicator_width1 3

#property indicator_label2 "TII rolling baseline"
#property indicator_type2  DRAW_LINE
#property indicator_color2 clrDeepSkyBlue
#property indicator_width2 1

#property indicator_label3 "Cross up 20"
#property indicator_type3  DRAW_ARROW
#property indicator_color3 clrLime
#property indicator_width3 1

#property indicator_label4 "Cross down 80"
#property indicator_type4  DRAW_ARROW
#property indicator_color4 clrTomato
#property indicator_width4 1

#property indicator_label5 "Signal code"
#property indicator_type5  DRAW_NONE

enum ENUM_TII_SIGNAL_FORMULA
{
   TII_CURRENT_BASELINE = 0, // All recent prices versus the current MA
   TII_ROLLING_BASELINE = 1  // Each recent price versus its own MA
};

input int InpMajorPeriod = 60;                         // Moving-average period
input int InpMinorPeriod = 30;                         // Deviation summation period
input ENUM_MA_METHOD InpMAMethod = MODE_SMA;           // Baseline average method
input double InpLowLevel = 20.0;                       // Notify after crossing upward
input double InpHighLevel = 80.0;                      // Notify after crossing downward
input ENUM_TII_SIGNAL_FORMULA InpSignalFormula = TII_CURRENT_BASELINE;
input bool InpEnablePopup = false;                     // Show terminal alert
input bool InpEnableEmail = false;                     // Queue terminal email

double g_current_baseline[];
double g_rolling_baseline[];
double g_cross_low_up[];
double g_cross_high_down[];
double g_signal_code[];
double g_ma[];

int g_ma_handle = INVALID_HANDLE;
datetime g_last_checked_bar = 0;

bool FiniteValue(const double value)
{
   return value != EMPTY_VALUE && MathIsValidNumber(value);
}

double RatioToTii(const double positive, const double negative)
{
   const double denominator = positive + negative;
   if(!MathIsValidNumber(denominator) || denominator <= 1.0e-15)
      return 50.0;
   return MathMax(0.0, MathMin(100.0, 100.0 * positive / denominator));
}

bool CalculateTiiPoint(const int index,
                       const double &price[],
                       const bool rolling_baseline,
                       double &result)
{
   const int first = index - InpMinorPeriod + 1;
   if(first < InpMajorPeriod - 1 || index >= ArraySize(price) ||
      index >= ArraySize(g_ma) || !FiniteValue(g_ma[index]))
      return false;

   double positive = 0.0;
   double negative = 0.0;
   for(int source = first; source <= index; ++source)
   {
      const double baseline = rolling_baseline ? g_ma[source] : g_ma[index];
      if(!FiniteValue(price[source]) || !FiniteValue(baseline))
         return false;
      const double deviation = price[source] - baseline;
      if(deviation >= 0.0)
         positive += deviation;
      else
         negative -= deviation;
   }
   result = RatioToTii(positive, negative);
   return FiniteValue(result);
}

void NotifySignal(const int signal,
                  const datetime bar_time,
                  const double value)
{
   const string direction = signal > 0 ? "CROSS UP 20" : "CROSS DOWN 80";
   const string formula = InpSignalFormula == TII_CURRENT_BASELINE
                          ? "CURRENT BASELINE"
                          : "ROLLING BASELINE";
   const string message = StringFormat(
      "%s %s TII %s | %s | value %.2f | %s",
      _Symbol,
      EnumToString(_Period),
      direction,
      formula,
      value,
      TimeToString(bar_time, TIME_DATE | TIME_MINUTES)
   );
   if(InpEnablePopup)
      Alert(message);
   if(InpEnableEmail)
   {
      ResetLastError();
      if(!SendMail("TII threshold notification", message))
         PrintFormat("TII #75: SendMail failed, error=%d", GetLastError());
   }
   Print(message);
}

int OnInit()
{
   if(InpMajorPeriod < 2 || InpMajorPeriod > 1000 ||
      InpMinorPeriod < 2 || InpMinorPeriod > 1000 ||
      InpLowLevel <= 0.0 || InpHighLevel >= 100.0 ||
      InpLowLevel >= InpHighLevel)
      return INIT_PARAMETERS_INCORRECT;

   if(!SetIndexBuffer(0, g_current_baseline, INDICATOR_DATA) ||
      !SetIndexBuffer(1, g_rolling_baseline, INDICATOR_DATA) ||
      !SetIndexBuffer(2, g_cross_low_up, INDICATOR_DATA) ||
      !SetIndexBuffer(3, g_cross_high_down, INDICATOR_DATA) ||
      !SetIndexBuffer(4, g_signal_code, INDICATOR_DATA) ||
      !SetIndexBuffer(5, g_ma, INDICATOR_CALCULATIONS))
      return INIT_FAILED;

   ArraySetAsSeries(g_current_baseline, false);
   ArraySetAsSeries(g_rolling_baseline, false);
   ArraySetAsSeries(g_cross_low_up, false);
   ArraySetAsSeries(g_cross_high_down, false);
   ArraySetAsSeries(g_signal_code, false);
   ArraySetAsSeries(g_ma, false);

   const int first_valid = InpMajorPeriod + InpMinorPeriod - 2;
   for(int plot = 0; plot < 5; ++plot)
   {
      PlotIndexSetInteger(plot, PLOT_DRAW_BEGIN, first_valid);
      PlotIndexSetInteger(plot, PLOT_SHOW_DATA, false);
      PlotIndexSetDouble(plot, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   }
   PlotIndexSetInteger(2, PLOT_ARROW, 233);
   PlotIndexSetInteger(3, PLOT_ARROW, 234);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, InpLowLevel);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 1, 50.0);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 2, InpHighLevel);
   IndicatorSetInteger(INDICATOR_DIGITS, 2);

   ResetLastError();
   g_ma_handle = iMA(_Symbol, _Period, InpMajorPeriod, 0, InpMAMethod, PRICE_CLOSE);
   if(g_ma_handle == INVALID_HANDLE)
   {
      PrintFormat("TII #75: iMA failed, error=%d", GetLastError());
      return INIT_FAILED;
   }

   IndicatorSetString(INDICATOR_SHORTNAME, "TII #75 | waiting for history");
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   if(g_ma_handle != INVALID_HANDLE)
   {
      IndicatorRelease(g_ma_handle);
      g_ma_handle = INVALID_HANDLE;
   }
}

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[])
{
   const int first_valid = InpMajorPeriod + InpMinorPeriod - 2;
   if(rates_total <= first_valid + 1 || BarsCalculated(g_ma_handle) < rates_total)
      return 0;

   ArraySetAsSeries(time, false);
   ArraySetAsSeries(open, false);
   ArraySetAsSeries(high, false);
   ArraySetAsSeries(low, false);
   ArraySetAsSeries(close, false);
   ArraySetAsSeries(tick_volume, false);
   ArraySetAsSeries(volume, false);
   ArraySetAsSeries(spread, false);

   ResetLastError();
   const int copied = CopyBuffer(g_ma_handle, 0, 0, rates_total, g_ma);
   if(copied != rates_total)
   {
      PrintFormat("TII #75: MA copy expected=%d copied=%d error=%d",
                  rates_total, copied, GetLastError());
      return 0;
   }

   const bool reset = prev_calculated <= 0 || prev_calculated > rates_total;
   int start = first_valid;
   if(reset)
   {
      ArrayInitialize(g_current_baseline, EMPTY_VALUE);
      ArrayInitialize(g_rolling_baseline, EMPTY_VALUE);
      ArrayInitialize(g_cross_low_up, EMPTY_VALUE);
      ArrayInitialize(g_cross_high_down, EMPTY_VALUE);
      ArrayInitialize(g_signal_code, 0.0);
   }
   else
      start = MathMax(first_valid, prev_calculated - 1);

   for(int index = start; index < rates_total; ++index)
   {
      double current_value = EMPTY_VALUE;
      double rolling_value = EMPTY_VALUE;
      if(!CalculateTiiPoint(index, close, false, current_value) ||
         !CalculateTiiPoint(index, close, true, rolling_value))
      {
         g_current_baseline[index] = EMPTY_VALUE;
         g_rolling_baseline[index] = EMPTY_VALUE;
         g_cross_low_up[index] = EMPTY_VALUE;
         g_cross_high_down[index] = EMPTY_VALUE;
         g_signal_code[index] = 0.0;
         continue;
      }

      g_current_baseline[index] = current_value;
      g_rolling_baseline[index] = rolling_value;
      g_cross_low_up[index] = EMPTY_VALUE;
      g_cross_high_down[index] = EMPTY_VALUE;
      g_signal_code[index] = 0.0;

      if(index == first_valid || index == rates_total - 1)
         continue;
      const double selected = InpSignalFormula == TII_CURRENT_BASELINE
                              ? current_value
                              : rolling_value;
      const double previous = InpSignalFormula == TII_CURRENT_BASELINE
                              ? g_current_baseline[index - 1]
                              : g_rolling_baseline[index - 1];
      if(!FiniteValue(previous))
         continue;
      if(previous <= InpLowLevel && selected > InpLowLevel)
      {
         g_cross_low_up[index] = MathMax(0.0, InpLowLevel - 5.0);
         g_signal_code[index] = 1.0;
      }
      else if(previous >= InpHighLevel && selected < InpHighLevel)
      {
         g_cross_high_down[index] = MathMin(100.0, InpHighLevel + 5.0);
         g_signal_code[index] = -1.0;
      }
   }

   const int closed_index = rates_total - 2;
   const datetime closed_time = time[closed_index];
   if(g_last_checked_bar == 0)
      g_last_checked_bar = closed_time;
   else if(closed_time != g_last_checked_bar)
   {
      g_last_checked_bar = closed_time;
      const int signal = (int)MathRound(g_signal_code[closed_index]);
      if(signal != 0)
      {
         const double value = InpSignalFormula == TII_CURRENT_BASELINE
                              ? g_current_baseline[closed_index]
                              : g_rolling_baseline[closed_index];
         NotifySignal(signal, closed_time, value);
      }
   }

   const double latest = InpSignalFormula == TII_CURRENT_BASELINE
                         ? g_current_baseline[closed_index]
                         : g_rolling_baseline[closed_index];
   const string formula = InpSignalFormula == TII_CURRENT_BASELINE
                          ? "current MA"
                          : "rolling MA";
   IndicatorSetString(
      INDICATOR_SHORTNAME,
      StringFormat("TII #75 | %s %.2f | confirmed 20-up / 80-down", formula, latest)
   );
   return rates_total;
}
//+------------------------------------------------------------------+
