//+------------------------------------------------------------------+
//| 26_Wick_Anomaly_Candidates_v1_00.mq5                           |
//| Marks unusually long confirmed-bar wicks for manual review.     |
//+------------------------------------------------------------------+
#property copyright "FXおもしろラボ"
#property version   "1.00"
#property strict
#property description "Marks unusually long wicks as data-quality review candidates."

#property indicator_chart_window
#property indicator_buffers 4
#property indicator_plots   4

#property indicator_label1 "Upper Wick Candidate"
#property indicator_type1  DRAW_ARROW
#property indicator_color1 clrTomato
#property indicator_width1 2

#property indicator_label2 "Lower Wick Candidate"
#property indicator_type2  DRAW_ARROW
#property indicator_color2 clrLimeGreen
#property indicator_width2 2

#property indicator_label3 "Upper Wick Ratio"
#property indicator_type3  DRAW_NONE

#property indicator_label4 "Lower Wick Ratio"
#property indicator_type4  DRAW_NONE

input int    InpBaselineBars     = 50;   // 基準TRの本数
input double InpWickRatio        = 2.0;  // 候補とするヒゲ/基準値の倍率
input int    InpBarsToScan       = 3000; // 検査する直近バー数
input double InpArrowOffsetPips  = 4.0;  // 矢印を足から離す距離（pips）

double g_upper_marker[];
double g_lower_marker[];
double g_upper_ratio[];
double g_lower_ratio[];

double TrueRangeAt(
   const int index,
   const double &high[],
   const double &low[],
   const double &close[]
);
double Median(double &values[], const int count);
double PipSize();

int OnInit()
{
   if(InpBaselineBars < 10 || InpWickRatio < 1.0 ||
      InpBarsToScan < 100 || InpArrowOffsetPips <= 0.0)
   {
      Print("Wick Anomaly Candidates: invalid input parameters.");
      return INIT_PARAMETERS_INCORRECT;
   }
   if(!SetIndexBuffer(0, g_upper_marker, INDICATOR_DATA) ||
      !SetIndexBuffer(1, g_lower_marker, INDICATOR_DATA) ||
      !SetIndexBuffer(2, g_upper_ratio, INDICATOR_DATA) ||
      !SetIndexBuffer(3, g_lower_ratio, INDICATOR_DATA))
   {
      PrintFormat("Wick Anomaly Candidates: SetIndexBuffer failed, error=%d", GetLastError());
      return INIT_FAILED;
   }
   ArraySetAsSeries(g_upper_marker, false);
   ArraySetAsSeries(g_lower_marker, false);
   ArraySetAsSeries(g_upper_ratio, false);
   ArraySetAsSeries(g_lower_ratio, false);
   PlotIndexSetInteger(0, PLOT_ARROW, 234);
   PlotIndexSetInteger(1, PLOT_ARROW, 233);
   for(int plot = 0; plot < 4; ++plot)
      PlotIndexSetDouble(plot, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   IndicatorSetString(
      INDICATOR_SHORTNAME,
      StringFormat(
         "Wick Anomaly Candidates (%d-bar median, >= %.1fx)",
         InpBaselineBars, InpWickRatio
      )
   );
   return INIT_SUCCEEDED;
}

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 < InpBaselineBars + 3)
      return 0;
   ArraySetAsSeries(open, false);
   ArraySetAsSeries(high, false);
   ArraySetAsSeries(low, false);
   ArraySetAsSeries(close, false);

   if(prev_calculated == 0)
   {
      ArrayInitialize(g_upper_marker, EMPTY_VALUE);
      ArrayInitialize(g_lower_marker, EMPTY_VALUE);
      ArrayInitialize(g_upper_ratio, EMPTY_VALUE);
      ArrayInitialize(g_lower_ratio, EMPTY_VALUE);
   }
   const int first = MathMax(InpBaselineBars + 1, rates_total - InpBarsToScan);
   const int clear_first = MathMax(0, first - 1);
   for(int i = clear_first; i < rates_total; ++i)
   {
      g_upper_marker[i] = EMPTY_VALUE;
      g_lower_marker[i] = EMPTY_VALUE;
      g_upper_ratio[i] = EMPTY_VALUE;
      g_lower_ratio[i] = EMPTY_VALUE;
   }

   double samples[];
   ArrayResize(samples, InpBaselineBars);
   const double marker_offset = InpArrowOffsetPips * PipSize();
   const int last_confirmed = rates_total - 2;
   for(int i = first; i <= last_confirmed; ++i)
   {
      for(int sample = 0; sample < InpBaselineBars; ++sample)
      {
         const int source = i - InpBaselineBars + sample;
         samples[sample] = TrueRangeAt(source, high, low, close);
      }
      const double baseline = Median(samples, InpBaselineBars);
      if(baseline <= _Point)
         continue;

      const double upper_wick = high[i] - MathMax(open[i], close[i]);
      const double lower_wick = MathMin(open[i], close[i]) - low[i];
      const double upper_ratio = upper_wick / baseline;
      const double lower_ratio = lower_wick / baseline;
      g_upper_ratio[i] = upper_ratio;
      g_lower_ratio[i] = lower_ratio;
      if(upper_ratio + 1.0e-12 >= InpWickRatio)
         g_upper_marker[i] = high[i] + marker_offset;
      if(lower_ratio + 1.0e-12 >= InpWickRatio)
         g_lower_marker[i] = low[i] - marker_offset;
   }
   return rates_total;
}

double TrueRangeAt(
   const int index,
   const double &high[],
   const double &low[],
   const double &close[]
)
{
   const double high_low = high[index] - low[index];
   const double high_previous = MathAbs(high[index] - close[index - 1]);
   const double low_previous = MathAbs(low[index] - close[index - 1]);
   return MathMax(high_low, MathMax(high_previous, low_previous));
}

double Median(double &values[], const int count)
{
   if(!ArraySort(values))
      return 0.0;
   const int middle = count / 2;
   if((count % 2) == 1)
      return values[middle];
   return (values[middle - 1] + values[middle]) / 2.0;
}

double PipSize()
{
   return (_Digits == 3 || _Digits == 5) ? _Point * 10.0 : _Point;
}
