//+------------------------------------------------------------------+
//| 19_Slow_Stochastic_Alert_v1_00.mq5                             |
//| Confirmed-bar alerts for the smoothed Slow Stochastic cross.   |
//+------------------------------------------------------------------+
#property copyright "FXおもしろラボ"
#property version   "1.00"
#property strict
#property description "Slow Stochastic with confirmed-bar cross markers and alerts."

#property indicator_separate_window
#property indicator_minimum 0.0
#property indicator_maximum 100.0
#property indicator_buffers 4
#property indicator_plots   4
#property indicator_level1  20.0
#property indicator_level2  80.0
#property indicator_levelcolor clrDimGray
#property indicator_levelstyle STYLE_DASH

#property indicator_label1 "Slow %K"
#property indicator_type1  DRAW_LINE
#property indicator_color1 clrLime
#property indicator_width1 2

#property indicator_label2 "Slow %D"
#property indicator_type2  DRAW_LINE
#property indicator_color2 clrAqua
#property indicator_width2 1

#property indicator_label3 "Cross Up"
#property indicator_type3  DRAW_ARROW
#property indicator_color3 clrYellow
#property indicator_width3 1

#property indicator_label4 "Cross Down"
#property indicator_type4  DRAW_ARROW
#property indicator_color4 clrTomato
#property indicator_width4 1

input int            InpKPeriod     = 14;              // %K期間
input int            InpDPeriod     = 5;               // %D期間
input int            InpSlowing     = 5;               // Slowing期間
input ENUM_STO_PRICE InpPriceField  = STO_CLOSECLOSE;  // 価格方式
input bool           InpEnablePopup = true;            // 確定足クロスでポップアップ
input bool           InpEnableSound = true;            // 確定足クロスで音を鳴らす
input string         InpSoundFile   = "alert.wav";     // Sounds内のWAVファイル

double g_slow_k[];
double g_slow_d[];
double g_cross_up[];
double g_cross_down[];
double g_raw_main[];
double g_raw_signal[];

int g_stochastic_handle = INVALID_HANDLE;
datetime g_last_checked_bar = 0;

void NotifyCross(const bool is_up, const datetime bar_time);

int OnInit()
{
   if(InpKPeriod < 1 || InpDPeriod < 1 || InpSlowing < 1)
   {
      Print("Slow Stochastic Alert: periods must be positive.");
      return INIT_PARAMETERS_INCORRECT;
   }

   bool buffers_ok = true;
   if(!SetIndexBuffer(0, g_slow_k, INDICATOR_DATA)) buffers_ok = false;
   if(!SetIndexBuffer(1, g_slow_d, INDICATOR_DATA)) buffers_ok = false;
   if(!SetIndexBuffer(2, g_cross_up, INDICATOR_DATA)) buffers_ok = false;
   if(!SetIndexBuffer(3, g_cross_down, INDICATOR_DATA)) buffers_ok = false;
   if(!buffers_ok)
   {
      PrintFormat("Slow Stochastic Alert: SetIndexBuffer failed, error=%d", GetLastError());
      return INIT_FAILED;
   }

   ArraySetAsSeries(g_slow_k, false);
   ArraySetAsSeries(g_slow_d, false);
   ArraySetAsSeries(g_cross_up, false);
   ArraySetAsSeries(g_cross_down, false);
   ArraySetAsSeries(g_raw_main, false);
   ArraySetAsSeries(g_raw_signal, false);

   PlotIndexSetInteger(2, PLOT_ARROW, 233);
   PlotIndexSetInteger(3, PLOT_ARROW, 234);
   for(int plot = 0; plot < 4; ++plot)
      PlotIndexSetDouble(plot, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   ResetLastError();
   g_stochastic_handle = iStochastic(
      _Symbol,
      _Period,
      InpKPeriod,
      InpDPeriod,
      InpSlowing,
      MODE_SMA,
      InpPriceField
   );
   if(g_stochastic_handle == INVALID_HANDLE)
   {
      PrintFormat("Slow Stochastic Alert: iStochastic failed, error=%d", GetLastError());
      return INIT_FAILED;
   }

   const int draw_begin = InpKPeriod + InpDPeriod + InpSlowing;
   for(int plot = 0; plot < 4; ++plot)
      PlotIndexSetInteger(plot, PLOT_DRAW_BEGIN, draw_begin);

   IndicatorSetInteger(INDICATOR_DIGITS, 2);
   IndicatorSetString(
      INDICATOR_SHORTNAME,
      StringFormat("Slow Stochastic Alert (%d,%d,%d)", InpKPeriod, InpDPeriod, InpSlowing)
   );
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   if(g_stochastic_handle != INVALID_HANDLE)
   {
      IndicatorRelease(g_stochastic_handle);
      g_stochastic_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[]
)
{
   if(rates_total < InpKPeriod + InpDPeriod + InpSlowing + 4)
      return 0;
   if(BarsCalculated(g_stochastic_handle) < rates_total)
      return 0;

   ResetLastError();
   const int main_copied = CopyBuffer(g_stochastic_handle, MAIN_LINE, 0, rates_total, g_raw_main);
   const int signal_copied = CopyBuffer(g_stochastic_handle, SIGNAL_LINE, 0, rates_total, g_raw_signal);
   if(main_copied != rates_total || signal_copied != rates_total)
   {
      PrintFormat(
         "Slow Stochastic Alert: CopyBuffer expected=%d main=%d signal=%d error=%d",
         rates_total,
         main_copied,
         signal_copied,
         GetLastError()
      );
      return 0;
   }

   ArrayInitialize(g_slow_k, EMPTY_VALUE);
   ArrayInitialize(g_slow_d, EMPTY_VALUE);
   ArrayInitialize(g_cross_up, EMPTY_VALUE);
   ArrayInitialize(g_cross_down, EMPTY_VALUE);

   for(int i = 2; i < rates_total; ++i)
   {
      g_slow_k[i] = (g_raw_main[i] + g_raw_main[i - 1] + g_raw_main[i - 2]) / 3.0;
      g_slow_d[i] = (g_raw_signal[i] + g_raw_signal[i - 1] + g_raw_signal[i - 2]) / 3.0;
      if(i < 3)
         continue;

      if(g_slow_d[i] < g_slow_k[i] && g_slow_d[i - 1] >= g_slow_k[i - 1])
         g_cross_up[i] = MathMax(0.0, MathMin(g_slow_k[i], g_slow_d[i]) - 4.0);
      else if(g_slow_d[i] > g_slow_k[i] && g_slow_d[i - 1] <= g_slow_k[i - 1])
         g_cross_down[i] = MathMin(100.0, MathMax(g_slow_k[i], g_slow_d[i]) + 4.0);
   }

   ArraySetAsSeries(time, false);
   const int closed_index = rates_total - 2;
   if(closed_index >= 3)
   {
      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;
         if(g_cross_up[closed_index] != EMPTY_VALUE)
            NotifyCross(true, closed_time);
         else if(g_cross_down[closed_index] != EMPTY_VALUE)
            NotifyCross(false, closed_time);
      }
   }
   return rates_total;
}

void NotifyCross(const bool is_up, const datetime bar_time)
{
   const string direction = is_up ? "UP" : "DOWN";
   const string message = StringFormat(
      "%s %s Slow Stochastic cross %s at %s",
      _Symbol,
      EnumToString(_Period),
      direction,
      TimeToString(bar_time, TIME_DATE | TIME_MINUTES)
   );
   if(InpEnablePopup)
      Alert(message);
   if(InpEnableSound && StringLen(InpSoundFile) > 0)
      PlaySound(InpSoundFile);
   Print(message);
}
