//+------------------------------------------------------------------+
//| 33_MACD_Zero_Cross_Email_v1_00.mq5                            |
//| MACD zero-cross monitor with one notification per signal bar.  |
//+------------------------------------------------------------------+
#property copyright "FXおもしろラボ"
#property version   "1.00"
#property strict
#property description "MACD zero-cross monitor with confirmed or preview alerts and optional email."

#property indicator_separate_window
#property indicator_buffers 12
#property indicator_plots   12
#property indicator_level1  0.0
#property indicator_levelcolor clrDimGray
#property indicator_levelstyle STYLE_DASH
#property indicator_height 190

#property indicator_label1  "MACD"
#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  clrAqua
#property indicator_width1  2
#property indicator_label2  "Signal"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrOrange
#property indicator_width2  1
#property indicator_label3  "Zero cross up"
#property indicator_type3   DRAW_ARROW
#property indicator_color3  clrLime
#property indicator_width3  2
#property indicator_label4  "Zero cross down"
#property indicator_type4   DRAW_ARROW
#property indicator_color4  clrTomato
#property indicator_width4  2
#property indicator_label5  "Latest direction"
#property indicator_type5   DRAW_NONE
#property indicator_label6  "Latest signal shift"
#property indicator_type6   DRAW_NONE
#property indicator_label7  "Evaluation shift"
#property indicator_type7   DRAW_NONE
#property indicator_label8  "Confirmed mode flag"
#property indicator_type8   DRAW_NONE
#property indicator_label9  "Popup enabled"
#property indicator_type9   DRAW_NONE
#property indicator_label10 "Email enabled"
#property indicator_type10  DRAW_NONE
#property indicator_label11 "Tester mode flag"
#property indicator_type11  DRAW_NONE
#property indicator_label12 "Message fields ready"
#property indicator_type12  DRAW_NONE

enum ENUM_ALERT_BAR_MODE
{
   ALERT_CONFIRMED_BAR = 0,
   ALERT_PREVIEW_CURRENT_BAR = 1
};

input group "1. MACD"
input int InpFastPeriod = 12;   // 短期EMA期間
input int InpSlowPeriod = 26;   // 長期EMA期間
input int InpSignalPeriod = 9;  // シグナル期間

input group "2. Notification timing"
input ENUM_ALERT_BAR_MODE InpBarMode = ALERT_CONFIRMED_BAR; // 通知する足
input bool InpUseLocalTime = false; // 本文の生成時刻をPC時刻にする

input group "3. Notification channels"
input bool InpEnablePopup = false; // ポップアップを使う
input bool InpEnableEmail = false; // メールを使う

input group "4. Display"
input bool InpShowPanel = true; // 通知状態を表示する

const string OBJECT_PREFIX = "MQL33_MACDMail_";

double g_macd_buffer[];
double g_signal_buffer[];
double g_cross_up_buffer[];
double g_cross_down_buffer[];
double g_latest_direction_buffer[];
double g_latest_shift_buffer[];
double g_evaluation_shift_buffer[];
double g_confirmed_mode_buffer[];
double g_popup_buffer[];
double g_email_buffer[];
double g_tester_buffer[];
double g_message_ready_buffer[];

int g_macd_handle = INVALID_HANDLE;
bool g_notification_initialized = false;
datetime g_last_alert_bar = 0;
int g_latest_direction = 0;
int g_latest_shift = -1;
datetime g_latest_time = 0;

bool BindBuffers();
bool ParametersAreValid();
int CrossDirection(const double previous_value, const double current_value);
void FindLatestSignal(const int newest_index);
void ProcessNotification(const int evaluation_index, const datetime &time[]);
bool BuildNotification(const int direction, const datetime bar_time, string &subject, string &body);
void DispatchNotification(const int direction, const datetime bar_time);
void InitializeOutputBuffers();
void PublishStatus(const int current_index, const int evaluation_shift, const bool message_ready);
void DrawPanel();
void SetLabel(const string suffix, const int x, const int y, const string text, const color text_color, const int font_size);
void DeleteObjects();
string DirectionText(const int direction);
string TimeframeText();
string ModeText();

int OnInit()
{
   if(!BindBuffers())
   {
      PrintFormat("MACD email alert: SetIndexBuffer failed, error=%d", GetLastError());
      return INIT_FAILED;
   }
   if(!ParametersAreValid())
   {
      Print("MACD email alert: periods must satisfy 1 <= fast < slow and signal >= 1.");
      return INIT_PARAMETERS_INCORRECT;
   }

   PlotIndexSetInteger(2, PLOT_ARROW, 233);
   PlotIndexSetInteger(3, PLOT_ARROW, 234);
   for(int plot = 0; plot < 12; ++plot)
      PlotIndexSetDouble(plot, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   for(int plot = 2; plot < 12; ++plot)
      PlotIndexSetInteger(plot, PLOT_SHOW_DATA, false);
   const int draw_begin = InpSlowPeriod + InpSignalPeriod;
   for(int plot = 0; plot < 4; ++plot)
      PlotIndexSetInteger(plot, PLOT_DRAW_BEGIN, draw_begin);

   ResetLastError();
   g_macd_handle = iMACD(
      _Symbol,
      _Period,
      InpFastPeriod,
      InpSlowPeriod,
      InpSignalPeriod,
      PRICE_CLOSE
   );
   if(g_macd_handle == INVALID_HANDLE)
   {
      PrintFormat("MACD email alert: iMACD failed, error=%d", GetLastError());
      return INIT_FAILED;
   }

   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
   IndicatorSetString(
      INDICATOR_SHORTNAME,
      StringFormat("MACD Zero Cross Email (%d,%d,%d)", InpFastPeriod, InpSlowPeriod, InpSignalPeriod)
   );
   EventSetTimer(1);
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   EventKillTimer();
   DeleteObjects();
   if(g_macd_handle != INVALID_HANDLE)
   {
      IndicatorRelease(g_macd_handle);
      g_macd_handle = INVALID_HANDLE;
   }
}

void OnTimer()
{
   if(InpShowPanel)
      DrawPanel();
   ChartRedraw(0);
}

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 minimum_bars = InpSlowPeriod + InpSignalPeriod + 3;
   if(rates_total < minimum_bars)
      return 0;
   if(BarsCalculated(g_macd_handle) < rates_total)
      return 0;

   ArraySetAsSeries(time, false);
   ResetLastError();
   const int macd_copied = CopyBuffer(g_macd_handle, MAIN_LINE, 0, rates_total, g_macd_buffer);
   const int signal_copied = CopyBuffer(g_macd_handle, SIGNAL_LINE, 0, rates_total, g_signal_buffer);
   if(macd_copied != rates_total || signal_copied != rates_total)
   {
      PrintFormat(
         "MACD email alert: CopyBuffer expected=%d macd=%d signal=%d error=%d",
         rates_total,
         macd_copied,
         signal_copied,
         GetLastError()
      );
      return 0;
   }

   InitializeOutputBuffers();
   const int current_index = rates_total - 1;
   const int evaluation_shift = (InpBarMode == ALERT_CONFIRMED_BAR) ? 1 : 0;
   const int evaluation_index = current_index - evaluation_shift;
   const int newest_marker_index = evaluation_index;

   double recent_scale = 0.0;
   const int scale_start = MathMax(0, current_index - 299);
   for(int i = scale_start; i <= current_index; ++i)
   {
      recent_scale = MathMax(recent_scale, MathAbs(g_macd_buffer[i]));
      recent_scale = MathMax(recent_scale, MathAbs(g_signal_buffer[i]));
   }
   recent_scale = MathMax(recent_scale, _Point * 10.0);
   IndicatorSetDouble(INDICATOR_MINIMUM, -recent_scale * 1.20);
   IndicatorSetDouble(INDICATOR_MAXIMUM, recent_scale * 1.20);
   const double marker_offset = recent_scale * 0.08;

   for(int i = 1; i <= newest_marker_index; ++i)
   {
      const int direction = CrossDirection(g_macd_buffer[i - 1], g_macd_buffer[i]);
      if(direction > 0)
         g_cross_up_buffer[i] = g_macd_buffer[i] - marker_offset;
      else if(direction < 0)
         g_cross_down_buffer[i] = g_macd_buffer[i] + marker_offset;
   }

   FindLatestSignal(newest_marker_index);
   ProcessNotification(evaluation_index, time);

   string subject = "";
   string body = "";
   const datetime message_time = (g_latest_time > 0) ? g_latest_time : time[evaluation_index];
   const int message_direction = (g_latest_direction != 0) ? g_latest_direction : 1;
   const bool message_ready = BuildNotification(message_direction, message_time, subject, body);
   PublishStatus(current_index, evaluation_shift, message_ready);

   if(InpShowPanel)
      DrawPanel();
   return rates_total;
}

bool BindBuffers()
{
   bool ok = true;
   if(!SetIndexBuffer(0, g_macd_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(1, g_signal_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(2, g_cross_up_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(3, g_cross_down_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(4, g_latest_direction_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(5, g_latest_shift_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(6, g_evaluation_shift_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(7, g_confirmed_mode_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(8, g_popup_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(9, g_email_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(10, g_tester_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(11, g_message_ready_buffer, INDICATOR_DATA)) ok = false;
   if(!ok)
      return false;

   ArraySetAsSeries(g_macd_buffer, false);
   ArraySetAsSeries(g_signal_buffer, false);
   ArraySetAsSeries(g_cross_up_buffer, false);
   ArraySetAsSeries(g_cross_down_buffer, false);
   ArraySetAsSeries(g_latest_direction_buffer, false);
   ArraySetAsSeries(g_latest_shift_buffer, false);
   ArraySetAsSeries(g_evaluation_shift_buffer, false);
   ArraySetAsSeries(g_confirmed_mode_buffer, false);
   ArraySetAsSeries(g_popup_buffer, false);
   ArraySetAsSeries(g_email_buffer, false);
   ArraySetAsSeries(g_tester_buffer, false);
   ArraySetAsSeries(g_message_ready_buffer, false);
   return true;
}

bool ParametersAreValid()
{
   return InpFastPeriod >= 1 && InpSlowPeriod > InpFastPeriod &&
          InpSlowPeriod <= 1000 && InpSignalPeriod >= 1 && InpSignalPeriod <= 1000;
}

int CrossDirection(const double previous_value, const double current_value)
{
   if(previous_value == EMPTY_VALUE || current_value == EMPTY_VALUE)
      return 0;
   if(previous_value <= 0.0 && current_value > 0.0)
      return 1;
   if(previous_value >= 0.0 && current_value < 0.0)
      return -1;
   return 0;
}

void FindLatestSignal(const int newest_index)
{
   g_latest_direction = 0;
   g_latest_shift = -1;
   g_latest_time = 0;
   for(int i = newest_index; i >= 1; --i)
   {
      const int direction = CrossDirection(g_macd_buffer[i - 1], g_macd_buffer[i]);
      if(direction == 0)
         continue;
      g_latest_direction = direction;
      g_latest_shift = newest_index + ((InpBarMode == ALERT_CONFIRMED_BAR) ? 1 : 0) - i;
      g_latest_time = iTime(_Symbol, _Period, g_latest_shift);
      break;
   }
}

void ProcessNotification(const int evaluation_index, const datetime &time[])
{
   if(evaluation_index < 1 || evaluation_index >= ArraySize(time))
      return;
   const int direction = CrossDirection(g_macd_buffer[evaluation_index - 1], g_macd_buffer[evaluation_index]);
   const datetime signal_time = time[evaluation_index];

   if(!g_notification_initialized)
   {
      g_notification_initialized = true;
      if(direction != 0)
         g_last_alert_bar = signal_time;
      return;
   }
   if(direction == 0 || signal_time == g_last_alert_bar)
      return;

   g_last_alert_bar = signal_time;
   DispatchNotification(direction, signal_time);
}

bool BuildNotification(const int direction, const datetime bar_time, string &subject, string &body)
{
   MqlTick tick;
   ResetLastError();
   if(!SymbolInfoTick(_Symbol, tick) || tick.bid <= 0.0 || tick.ask <= 0.0 || bar_time <= 0)
      return false;

   string broker = AccountInfoString(ACCOUNT_COMPANY);
   if(StringLen(broker) == 0)
      broker = "N/A";
   const string direction_text = DirectionText(direction);
   const string timeframe = TimeframeText();
   const datetime generated_at = InpUseLocalTime ? TimeLocal() : TimeCurrent();
   const string time_basis = InpUseLocalTime ? "LOCAL" : "SERVER";
   const string indicator_name = MQLInfoString(MQL_PROGRAM_NAME);

   subject = "MACD ZERO CROSS " + direction_text + " | " + _Symbol + " " + timeframe;
   body = StringFormat(
      "Symbol: %s\nTimeframe: %s\nSignal: %s\nBid: %s\nAsk: %s\nSignal bar: %s\nGenerated: %s (%s)\nIndicator: %s\nBroker: %s",
      _Symbol,
      timeframe,
      direction_text,
      DoubleToString(tick.bid, _Digits),
      DoubleToString(tick.ask, _Digits),
      TimeToString(bar_time, TIME_DATE | TIME_MINUTES),
      TimeToString(generated_at, TIME_DATE | TIME_MINUTES | TIME_SECONDS),
      time_basis,
      indicator_name,
      broker
   );
   return StringLen(subject) > 0 && StringLen(body) > 0;
}

void DispatchNotification(const int direction, const datetime bar_time)
{
   string subject = "";
   string body = "";
   if(!BuildNotification(direction, bar_time, subject, body))
   {
      PrintFormat("MACD email alert: message creation failed, error=%d", GetLastError());
      return;
   }

   string log_body = body;
   StringReplace(log_body, "\n", " | ");
   Print(subject + " | " + log_body);
   if((bool)MQLInfoInteger(MQL_TESTER))
   {
      Print("MACD email alert: external notification suppressed in Strategy Tester.");
      return;
   }

   if(InpEnablePopup)
      Alert(subject + "\n" + body);

   if(!InpEnableEmail)
      return;
   if(!(bool)TerminalInfoInteger(TERMINAL_EMAIL_ENABLED))
   {
      Print("MACD email alert: terminal email is disabled or not configured.");
      return;
   }

   ResetLastError();
   if(!SendMail(subject, body))
      PrintFormat("MACD email alert: SendMail failed, error=%d", GetLastError());
}

void InitializeOutputBuffers()
{
   ArrayInitialize(g_cross_up_buffer, EMPTY_VALUE);
   ArrayInitialize(g_cross_down_buffer, EMPTY_VALUE);
   ArrayInitialize(g_latest_direction_buffer, EMPTY_VALUE);
   ArrayInitialize(g_latest_shift_buffer, EMPTY_VALUE);
   ArrayInitialize(g_evaluation_shift_buffer, EMPTY_VALUE);
   ArrayInitialize(g_confirmed_mode_buffer, EMPTY_VALUE);
   ArrayInitialize(g_popup_buffer, EMPTY_VALUE);
   ArrayInitialize(g_email_buffer, EMPTY_VALUE);
   ArrayInitialize(g_tester_buffer, EMPTY_VALUE);
   ArrayInitialize(g_message_ready_buffer, EMPTY_VALUE);
}

void PublishStatus(const int current_index, const int evaluation_shift, const bool message_ready)
{
   if(current_index < 0 || current_index >= ArraySize(g_message_ready_buffer))
      return;
   g_latest_direction_buffer[current_index] = (double)g_latest_direction;
   g_latest_shift_buffer[current_index] = (double)g_latest_shift;
   g_evaluation_shift_buffer[current_index] = (double)evaluation_shift;
   g_confirmed_mode_buffer[current_index] = (InpBarMode == ALERT_CONFIRMED_BAR) ? 1.0 : 0.0;
   g_popup_buffer[current_index] = InpEnablePopup ? 1.0 : 0.0;
   g_email_buffer[current_index] = InpEnableEmail ? 1.0 : 0.0;
   g_tester_buffer[current_index] = (bool)MQLInfoInteger(MQL_TESTER) ? 1.0 : 0.0;
   g_message_ready_buffer[current_index] = message_ready ? 1.0 : 0.0;
}

void DrawPanel()
{
   const string panel = OBJECT_PREFIX + "Panel";
   if(ObjectFind(0, panel) < 0 && !ObjectCreate(0, panel, OBJ_RECTANGLE_LABEL, 0, 0, 0))
      return;
   ObjectSetInteger(0, panel, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, panel, OBJPROP_XDISTANCE, 18);
   ObjectSetInteger(0, panel, OBJPROP_YDISTANCE, 20);
   ObjectSetInteger(0, panel, OBJPROP_XSIZE, 660);
   ObjectSetInteger(0, panel, OBJPROP_YSIZE, 265);
   ObjectSetInteger(0, panel, OBJPROP_BGCOLOR, C'7,14,18');
   ObjectSetInteger(0, panel, OBJPROP_COLOR, clrAqua);
   ObjectSetInteger(0, panel, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   ObjectSetInteger(0, panel, OBJPROP_BACK, false);
   ObjectSetInteger(0, panel, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, panel, OBJPROP_HIDDEN, true);

   const string latest = (g_latest_direction == 0)
      ? "NONE"
      : DirectionText(g_latest_direction) + "  " + TimeToString(g_latest_time, TIME_DATE | TIME_MINUTES);
   const string runtime = (bool)MQLInfoInteger(MQL_TESTER) ? "TESTER / SEND SUPPRESSED" : "LIVE CHART";
   SetLabel("Title", 36, 34, "MACD ZERO CROSS ALERT", clrAqua, 16);
   SetLabel("Mode", 36, 70, "MODE     : " + ModeText(), clrWhite, 11);
   SetLabel("Watch", 36, 100, "WATCH    : MACD " + IntegerToString(InpFastPeriod) + "/" + IntegerToString(InpSlowPeriod) + "/" + IntegerToString(InpSignalPeriod) + " vs ZERO", clrWhite, 11);
   SetLabel("Latest", 36, 130, "LATEST   : " + latest, g_latest_direction == 0 ? clrSilver : clrLime, 11);
   SetLabel("Channels", 36, 160, "CHANNELS : POPUP " + (InpEnablePopup ? "ON" : "OFF") + " / EMAIL " + (InpEnableEmail ? "ON" : "OFF"), clrWhite, 11);
   SetLabel("Runtime", 36, 190, "RUNTIME  : " + runtime, clrGold, 11);
   SetLabel("Fields", 36, 220, "MESSAGE  : SYMBOL / TF / SIDE / BID / ASK / TIME / SOURCE", clrSilver, 10);
   SetLabel("Footer", 510, 232, "NO ORDERS", clrLime, 10);
}

void SetLabel(const string suffix, const int x, const int y, const string text, const color text_color, const int font_size)
{
   const string name = OBJECT_PREFIX + suffix;
   if(ObjectFind(0, name) < 0 && !ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0))
      return;
   ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0, name, OBJPROP_COLOR, text_color);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, font_size);
   ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
   ObjectSetString(0, name, OBJPROP_FONT, "Consolas");
   ObjectSetString(0, name, OBJPROP_TEXT, text);
}

void DeleteObjects()
{
   ObjectsDeleteAll(0, OBJECT_PREFIX);
}

string DirectionText(const int direction)
{
   if(direction > 0) return "BUY";
   if(direction < 0) return "SELL";
   return "NONE";
}

string TimeframeText()
{
   string value = EnumToString(_Period);
   if(StringFind(value, "PERIOD_") == 0)
      value = StringSubstr(value, 7);
   return value;
}

string ModeText()
{
   return (InpBarMode == ALERT_CONFIRMED_BAR) ? "CONFIRMED BAR [1]" : "PREVIEW CURRENT BAR [0]";
}
