//+------------------------------------------------------------------+
//| Ind_TrendRangeRegime_v102.mq5                                    |
//| v101 + トレンド平滑(SMA) + 任意MTF(ADX)確認                         |
//| 仕様概要: 本サイトコラム【検証#06】連載を参照                      |
//+------------------------------------------------------------------+
#property copyright "FX Omoshiro Lab"
#property link      "https://fx-omoshiro-lab.com"
#property version   "1.02"
#property strict
#property description "v101 + SMA平滑 + MTF ADX確認。バッファ0〜5はiCustom用固定(内部計算用バッファ6)。"
#property indicator_separate_window
#property indicator_buffers 7
#property indicator_plots   3

#property indicator_label1  "REGIME"
#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  clrSilver
#property indicator_style1  STYLE_SOLID
#property indicator_width1  3

#property indicator_label2  "TREND_SCORE"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrDodgerBlue
#property indicator_style2  STYLE_SOLID
#property indicator_width2  2

#property indicator_label3  "CONFIDENCE"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrOrange
#property indicator_style3  STYLE_SOLID
#property indicator_width3  1

/* iCustom バッファ契約（固定・v100 と同一）
   0 = REGIME      (0..4)
   1 = TREND_SCORE (0..1) ※平滑ON時はSMA後の値
   2 = CONFIDENCE  (0..1)
   3 = DIRECTION   (-1,0,+1)
   4 = CONTEXT_SCORE (0..1) スプレッド×セッション×曜日の積
   5 = MURPHY_SCORE  (0..1)
   6 = (内部) 平滑前トレンドスコア — INDICATOR_CALCULATIONS のため iCustom 非表示
*/

input group "===== 構造パラメータ ====="
input int    InpMicroPeriod   = 20;
input int    InpADXPeriod     = 14;

input group "===== 閾値 ====="
input double InpTrendScoreHigh  = 0.55;
input double InpTrendScoreLow   = 0.38;
input double InpConfMin         = 0.32;
input double InpSlopeEps        = 0.0;

input group "===== 融合ウェイト（短期構造） ====="
input double InpWADX            = 0.40;
input double InpWR2             = 0.35;
input double InpWDI             = 0.25;

input group "===== P2 ハースト（任意） ====="
input bool   InpUseHurst        = false;
input ENUM_TIMEFRAMES InpHurstTF = PERIOD_H1;
input int    InpHurstPeriod     = 100;
input double InpWHurst          = 0.25; // 最終 TREND_SCORE への寄与（残りは短期構造）

input group "===== マーフィー・スプレッド ====="
input int    InpMurphyLookback  = 4;
input int    InpSpreadAvgBars   = 20;
input double InpSpreadSpikeMul  = 1.6;

input group "===== P1 セッション・曜日（サーバー時刻） ====="
input bool   InpCtxP1Enable     = true;
input double InpMulOverlap      = 1.00; // 欧米重複など
input int    InpOverlapStartHour = 13;
input int    InpOverlapEndHour   = 16;
input double InpMulDay          = 0.93; // 主要時間帯（重複以外）
input int    InpDayStartHour    = 8;
input int    InpDayEndHour      = 22;
input double InpMulNight        = 0.82; // 上記以外
input double InpWeekendMul      = 0.85;
input double InpMondayMul       = 0.92;
input double InpFridayMul       = 0.92;
input double InpWeekMidMul      = 1.00;  // 火〜木

input group "===== P3 安定化（平滑）・MTF ====="
input int    InpTrendSmoothBars = 0;   // トレンドSMA本数 0=オフ 2以上で有効
input bool   InpUseMTFConfirm   = false;
input ENUM_TIMEFRAMES InpMTF    = PERIOD_H4;
input double InpMTFMinADX       = 20.0; // これ未満で順張りレジーム時に信頼度を下げる
input double InpMTFConfScale    = 0.88; // 上記時の CONF 乗算

input group "===== 表示・挙動 ====="
input bool   InpUseClosedBarOnly = true;

double g_buf_regime[];
double g_buf_trend[];
double g_buf_conf[];
double g_buf_dir[];
double g_buf_ctx[];
double g_buf_murphy[];
double g_buf_raw_trend[]; // 平滑前（バッファ6 計算用）

int    g_hADX = INVALID_HANDLE;
int    g_hADX_MTF = INVALID_HANDLE;

datetime g_hurst_cache_tf = 0;
double   g_hurst_cache_val = EMPTY_VALUE;

//+------------------------------------------------------------------+
double Clamp01(const double x)
{
   if(x < 0.0) return 0.0;
   if(x > 1.0) return 1.0;
   return x;
}

//+------------------------------------------------------------------+
bool LinRegSlopeR2(const double &y[], const int n, double &slope, double &r2)
{
   if(n < 3) return false;
   double sumx = 0.0, sumy = 0.0, sumxx = 0.0, sumyy = 0.0, sumxy = 0.0;
   for(int k = 0; k < n; k++)
   {
      double x = (double)k;
      double yi = y[k];
      sumx += x;
      sumy += yi;
      sumxx += x * x;
      sumyy += yi * yi;
      sumxy += x * yi;
   }
   double denom = (double)n * sumxx - sumx * sumx;
   if(MathAbs(denom) < 1e-20) return false;
   slope = ((double)n * sumxy - sumx * sumy) / denom;
   double mean_y = sumy / (double)n;
   double ss_tot = 0.0;
   for(int k = 0; k < n; k++)
   {
      double d = y[k] - mean_y;
      ss_tot += d * d;
   }
   if(ss_tot < 1e-20)
   {
      r2 = 0.0;
      return true;
   }
   double intercept = (sumy - slope * sumx) / (double)n;
   double ss_res = 0.0;
   for(int k = 0; k < n; k++)
   {
      double pred = slope * (double)k + intercept;
      double e = y[k] - pred;
      ss_res += e * e;
   }
   r2 = 1.0 - ss_res / ss_tot;
   if(r2 < 0.0) r2 = 0.0;
   if(r2 > 1.0) r2 = 1.0;
   return true;
}

//+------------------------------------------------------------------+
//| R/S ハースト（Ind_HurstTrendRange と同系）                         |
//+------------------------------------------------------------------+
double CalcHurst(const datetime bar_time)
{
   if(!InpUseHurst) return EMPTY_VALUE;
   if(InpHurstPeriod < 10) return EMPTY_VALUE;

   int shift = iBarShift(_Symbol, InpHurstTF, bar_time, false);
   if(shift < 0) return EMPTY_VALUE;

   int need = InpHurstPeriod * 2;
   double prices[];
   int got = CopyClose(_Symbol, InpHurstTF, shift + 1, need, prices);
   if(got < need) return EMPTY_VALUE;

   int n = 0;
   double ld[];
   ArrayResize(ld, got);
   for(int i = 1; i < got; i++)
   {
      if(prices[i - 1] > 0 && prices[i] > 0)
         ld[n++] = MathLog(prices[i] / prices[i - 1]);
   }
   if(n < InpHurstPeriod) return EMPTY_VALUE;

   int s0 = n - InpHurstPeriod;
   double sum = 0;
   for(int k = 0; k < InpHurstPeriod; k++) sum += ld[s0 + k];
   double mean = sum / InpHurstPeriod;

   double ssq = 0, cd = 0, mx = -1e300, mn = 1e300;
   for(int k = 0; k < InpHurstPeriod; k++)
   {
      double d = ld[s0 + k] - mean;
      cd += d;
      if(cd > mx) mx = cd;
      if(cd < mn) mn = cd;
      ssq += d * d;
   }

   double R = mx - mn;
   if(R == 0) R = 1e-10;
   double S = MathSqrt(ssq / InpHurstPeriod);
   if(S == 0) S = 1e-10;
   double H = MathLog(R / S) / MathLog((double)InpHurstPeriod);

   if(H < 0 || H > 1) return EMPTY_VALUE;
   return H;
}

//+------------------------------------------------------------------+
double SessionWeekdayContextMul(const datetime bar_time)
{
   if(!InpCtxP1Enable) return 1.0;

   MqlDateTime dt;
   TimeToStruct(bar_time, dt);
   int h = dt.hour;

   double sm = InpMulNight;
   if(h >= InpOverlapStartHour && h <= InpOverlapEndHour)
      sm = InpMulOverlap;
   else if(h >= InpDayStartHour && h <= InpDayEndHour)
      sm = InpMulDay;

   double wm = InpWeekMidMul;
   int dow = dt.day_of_week;
   if(dow == 0 || dow == 6) wm = InpWeekendMul;
   else if(dow == 1) wm = InpMondayMul;
   else if(dow == 5) wm = InpFridayMul;

   return Clamp01(sm * wm);
}

//+------------------------------------------------------------------+
int OnInit()
{
   SetIndexBuffer(0, g_buf_regime, INDICATOR_DATA);
   SetIndexBuffer(1, g_buf_trend,  INDICATOR_DATA);
   SetIndexBuffer(2, g_buf_conf,  INDICATOR_DATA);
   SetIndexBuffer(3, g_buf_dir,   INDICATOR_DATA);
   SetIndexBuffer(4, g_buf_ctx,   INDICATOR_DATA);
   SetIndexBuffer(5, g_buf_murphy, INDICATOR_DATA);
   SetIndexBuffer(6, g_buf_raw_trend, INDICATOR_CALCULATIONS);

   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   string tag = InpUseHurst ? "P1+P2" : "P1";
   if(InpTrendSmoothBars >= 2) tag += "+SMA";
   if(InpUseMTFConfirm) tag += "+MTF";
   IndicatorSetString(INDICATOR_SHORTNAME,
      StringFormat("TRRegime(%s) Mic=%d", tag, InpMicroPeriod));
   IndicatorSetInteger(INDICATOR_DIGITS, 3);

   g_hADX = iADX(_Symbol, PERIOD_CURRENT, InpADXPeriod);
   if(g_hADX == INVALID_HANDLE)
   {
      Print("Ind_TrendRangeRegime v102: iADX failed");
      return INIT_FAILED;
   }

   if(InpUseMTFConfirm)
   {
      if((int)InpMTF <= 0 || PeriodSeconds(InpMTF) <= PeriodSeconds(Period()))
      {
         Print("Ind_TrendRangeRegime v102: InpMTF はチャート足より大きい時間足を指定してください");
         return INIT_FAILED;
      }
      g_hADX_MTF = iADX(_Symbol, InpMTF, InpADXPeriod);
      if(g_hADX_MTF == INVALID_HANDLE)
      {
         Print("Ind_TrendRangeRegime v102: iADX MTF failed");
         return INIT_FAILED;
      }
   }

   IndicatorSetDouble(INDICATOR_MINIMUM, 0.0);
   IndicatorSetDouble(INDICATOR_MAXIMUM, 4.0);

   Print("[TRRegime v1.02] Hurst=", InpUseHurst ? "ON" : "OFF", " CtxP1=", InpCtxP1Enable ? "ON" : "OFF",
         " Smooth=", InpTrendSmoothBars, " MTF=", InpUseMTFConfirm ? "ON" : "OFF");
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(g_hADX != INVALID_HANDLE) IndicatorRelease(g_hADX);
   if(g_hADX_MTF != INVALID_HANDLE) IndicatorRelease(g_hADX_MTF);
   Comment("");
}

//+------------------------------------------------------------------+
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 need = MathMax(InpMicroPeriod, InpADXPeriod) + InpMurphyLookback + 2;
   if(InpUseHurst)
      need = MathMax(need, InpHurstPeriod + 20);
   if(InpTrendSmoothBars >= 2)
      need = MathMax(need, InpMicroPeriod + InpTrendSmoothBars + 5);

   if(rates_total < need) return 0;

   int start;
   if(prev_calculated <= 0)
   {
      start = 0;
      ArrayInitialize(g_buf_regime, EMPTY_VALUE);
      ArrayInitialize(g_buf_trend,  EMPTY_VALUE);
      ArrayInitialize(g_buf_conf,   EMPTY_VALUE);
      ArrayInitialize(g_buf_dir,    EMPTY_VALUE);
      ArrayInitialize(g_buf_ctx,    EMPTY_VALUE);
      ArrayInitialize(g_buf_murphy, EMPTY_VALUE);
      ArrayInitialize(g_buf_raw_trend, EMPTY_VALUE);
   }
   else
      start = prev_calculated - 1;
   if(start < need) start = need;

   for(int i = start; i < rates_total; i++)
   {
      if(InpUseClosedBarOnly && i == rates_total - 1)
      {
         g_buf_regime[i] = EMPTY_VALUE;
         g_buf_trend[i]  = EMPTY_VALUE;
         g_buf_conf[i]   = EMPTY_VALUE;
         g_buf_dir[i]    = EMPTY_VALUE;
         g_buf_ctx[i]    = EMPTY_VALUE;
         g_buf_murphy[i] = EMPTY_VALUE;
         g_buf_raw_trend[i] = EMPTY_VALUE;
         continue;
      }

      int sh = rates_total - 1 - i;

      double adxArr[1], diP[1], diM[1];
      if(CopyBuffer(g_hADX, 0, sh, 1, adxArr) < 1 ||
         CopyBuffer(g_hADX, 1, sh, 1, diP) < 1 ||
         CopyBuffer(g_hADX, 2, sh, 1, diM) < 1)
      {
         g_buf_regime[i] = EMPTY_VALUE;
         g_buf_trend[i]  = EMPTY_VALUE;
         g_buf_conf[i]   = EMPTY_VALUE;
         g_buf_dir[i]    = EMPTY_VALUE;
         g_buf_ctx[i]    = EMPTY_VALUE;
         g_buf_murphy[i] = EMPTY_VALUE;
         g_buf_raw_trend[i] = EMPTY_VALUE;
         continue;
      }

      double adx = adxArr[0];
      if(adx < 0.0) adx = 0.0;
      if(adx > 100.0) adx = 100.0;
      double adx_n = adx / 100.0;

      double dip = diP[0], dim = diM[0];
      if(dip < 0.0) dip = 0.0;
      if(dim < 0.0) dim = 0.0;
      double di_sum = dip + dim;
      double di_agree = (di_sum > 1e-10) ? MathAbs(dip - dim) / di_sum : 0.0;

      double y[];
      ArrayResize(y, InpMicroPeriod);
      int i0 = i - InpMicroPeriod + 1;
      if(i0 < 0)
      {
         g_buf_regime[i] = EMPTY_VALUE;
         g_buf_trend[i]  = EMPTY_VALUE;
         g_buf_conf[i]   = EMPTY_VALUE;
         g_buf_dir[i]    = EMPTY_VALUE;
         g_buf_ctx[i]    = EMPTY_VALUE;
         g_buf_murphy[i] = EMPTY_VALUE;
         g_buf_raw_trend[i] = EMPTY_VALUE;
         continue;
      }
      for(int k = 0; k < InpMicroPeriod; k++)
         y[k] = close[i0 + k];

      double slope = 0.0, r2 = 0.0;
      if(!LinRegSlopeR2(y, InpMicroPeriod, slope, r2))
      {
         g_buf_regime[i] = EMPTY_VALUE;
         g_buf_trend[i]  = EMPTY_VALUE;
         g_buf_conf[i]   = EMPTY_VALUE;
         g_buf_dir[i]    = EMPTY_VALUE;
         g_buf_ctx[i]    = EMPTY_VALUE;
         g_buf_murphy[i] = EMPTY_VALUE;
         g_buf_raw_trend[i] = EMPTY_VALUE;
         continue;
      }

      double wsum = InpWADX + InpWR2 + InpWDI;
      if(wsum < 1e-10) wsum = 1.0;
      double trend_struct = (InpWADX * adx_n + InpWR2 * r2 + InpWDI * di_agree) / wsum;
      trend_struct = Clamp01(trend_struct);

      double trend_score = trend_struct;
      if(InpUseHurst)
      {
         datetime bt = time[i];
         datetime tf_time = bt;
         if(InpHurstTF != PERIOD_CURRENT)
         {
            int tf_sec = PeriodSeconds(InpHurstTF);
            if(tf_sec > 0)
               tf_time = (datetime)((long)bt / tf_sec * tf_sec);
         }

         double hv = EMPTY_VALUE;
         if(tf_time == g_hurst_cache_tf && g_hurst_cache_val != EMPTY_VALUE)
            hv = g_hurst_cache_val;
         else
         {
            hv = CalcHurst(bt);
            g_hurst_cache_tf = tf_time;
            g_hurst_cache_val = hv;
         }

         if(hv != EMPTY_VALUE && hv >= 0.0 && hv <= 1.0)
         {
            double w = Clamp01(InpWHurst);
            trend_score = (1.0 - w) * trend_struct + w * hv;
            trend_score = Clamp01(trend_score);
         }
      }

      g_buf_raw_trend[i] = trend_score;

      if(InpTrendSmoothBars >= 2)
      {
         double s = 0.0;
         int cnt = 0;
         for(int j = 0; j < InpTrendSmoothBars && i - j >= 0; j++)
         {
            double rv = g_buf_raw_trend[i - j];
            if(rv == EMPTY_VALUE)
               break;
            s += rv;
            cnt++;
         }
         if(cnt > 0)
            trend_score = s / (double)cnt;
      }

      double conf = 1.0 - MathAbs(adx_n - r2) * 2.0;
      if(conf < 0.0) conf = 0.0;
      if(conf > 1.0) conf = 1.0;

      int dir = 0;
      double eps = InpSlopeEps;
      if(eps <= 0.0 && i > 0)
      {
         double atr_proxy = MathAbs(close[i] - close[i - 1]);
         for(int t = 1; t < InpMicroPeriod && i - t >= 0; t++)
            atr_proxy += MathAbs(close[i - t + 1] - close[i - t]);
         atr_proxy /= (double)MathMin(InpMicroPeriod, i + 1);
         eps = atr_proxy * 0.02;
         if(eps < 1e-10) eps = 1e-10;
      }
      else if(eps <= 0.0)
         eps = 1e-10;

      if(MathAbs(slope) > eps)
         dir = (slope > 0.0) ? 1 : -1;
      else
      {
         if(dip > dim + 0.5) dir = 1;
         else if(dim > dip + 0.5) dir = -1;
      }

      double ctx_spread = 1.0;
      if(InpSpreadAvgBars > 0 && i >= InpSpreadAvgBars)
      {
         double ssum = 0.0;
         for(int t = 0; t < InpSpreadAvgBars; t++)
            ssum += (double)spread[i - t];
         double savg = ssum / (double)InpSpreadAvgBars;
         double scur = (double)spread[i];
         if(savg > 1e-10 && scur > savg * InpSpreadSpikeMul)
            ctx_spread = 0.45;
      }

      double ctx_sw = SessionWeekdayContextMul(time[i]);
      double ctx = Clamp01(ctx_spread * ctx_sw);
      if(ctx < 0.05) ctx = 0.05;

      conf *= ctx;

      if(InpUseMTFConfirm && g_hADX_MTF != INVALID_HANDLE)
      {
         int msh = iBarShift(_Symbol, InpMTF, time[i], false);
         if(msh >= 0)
         {
            double adxm[1];
            if(CopyBuffer(g_hADX_MTF, 0, msh, 1, adxm) >= 1)
            {
               bool trend_like = (trend_score >= InpTrendScoreHigh && dir != 0);
               if(trend_like && adxm[0] < InpMTFMinADX)
               {
                  double sc = Clamp01(InpMTFConfScale);
                  if(sc < 0.05) sc = 0.05;
                  conf *= sc;
               }
            }
         }
      }

      int sign_changes = 0;
      for(int t = 0; t < InpMurphyLookback - 1 && i - t - 2 >= 0; t++)
      {
         double r1 = close[i - t]     - close[i - t - 1];
         double r2 = close[i - t - 1] - close[i - t - 2];
         int s1 = (r1 > 0.0) ? 1 : (r1 < 0.0 ? -1 : 0);
         int s2 = (r2 > 0.0) ? 1 : (r2 < 0.0 ? -1 : 0);
         if(s1 != 0 && s2 != 0 && s1 != s2)
            sign_changes++;
      }
      double murphy = 0.15;
      if(trend_score > 0.52 && sign_changes >= 2)
         murphy = 0.82;
      else if(trend_score > 0.45 && sign_changes >= 1)
         murphy = 0.45;

      conf *= (1.0 - murphy * 0.25);
      if(conf < 0.0) conf = 0.0;
      if(conf > 1.0) conf = 1.0;

      int regime = 0;
      if(conf < InpConfMin)
         regime = 0;
      else if(trend_score >= InpTrendScoreHigh && dir > 0)
         regime = 3;
      else if(trend_score >= InpTrendScoreHigh && dir < 0)
         regime = 4;
      else if(trend_score <= InpTrendScoreLow)
         regime = 1;
      else
         regime = 2;

      g_buf_regime[i] = (double)regime;
      g_buf_trend[i]  = trend_score;
      g_buf_conf[i]   = conf;
      g_buf_dir[i]    = (double)dir;
      g_buf_ctx[i]    = ctx;
      g_buf_murphy[i] = murphy;
   }

   int last = rates_total - 1;
   if(last >= 0 && g_buf_regime[last] != EMPTY_VALUE)
   {
      string rs[] = {"UNK", "RNG", "CHP", "UP", "DN"};
      int ri = (int)MathRound(g_buf_regime[last]);
      if(ri < 0) ri = 0;
      if(ri > 4) ri = 4;
      double rawc = g_buf_raw_trend[last];
      if(rawc == EMPTY_VALUE) rawc = 0.0;
      Comment(StringFormat("TRRegime v1.02 | %s | T=%.3f(raw=%.3f) C=%.3f D=%d ctx=%.2f",
              rs[ri], g_buf_trend[last], rawc, g_buf_conf[last], (int)g_buf_dir[last], g_buf_ctx[last]));
   }

   return rates_total;
}

//+------------------------------------------------------------------+
