//+------------------------------------------------------------------+
//| Ind_TrendRangeRegime_v101.mq5                                    |
//| P0 + P1(セッション・曜日 CONTEXT) + P2(ハースト融合オプション)         |
//| 仕様概要: 本サイトコラム【検証#06】連載を参照                      |
//+------------------------------------------------------------------+
#property copyright "FX Omoshiro Lab"
#property link      "https://fx-omoshiro-lab.com"
#property version   "1.01"
#property strict
#property description "Regime: ADX+R2+DI + 任意ハースト。CONTEXT=スプレッド×セッション×曜日。バッファ0〜5固定。"
#property indicator_separate_window
#property indicator_buffers 6
#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)
   2 = CONFIDENCE  (0..1)
   3 = DIRECTION   (-1,0,+1)
   4 = CONTEXT_SCORE (0..1) スプレッド×セッション×曜日の積
   5 = MURPHY_SCORE  (0..1)
*/

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 "===== 表示・挙動 ====="
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[];

int    g_hADX = 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);

   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";
   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 v101: iADX failed");
      return INIT_FAILED;
   }

   IndicatorSetDouble(INDICATOR_MINIMUM, 0.0);
   IndicatorSetDouble(INDICATOR_MAXIMUM, 4.0);

   Print("[TRRegime v1.01] Hurst=", InpUseHurst ? "ON" : "OFF", " CtxP1=", InpCtxP1Enable ? "ON" : "OFF");
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(g_hADX != INVALID_HANDLE) IndicatorRelease(g_hADX);
   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(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);
   }
   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;
         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;
         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;
         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;
         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);
         }
      }

      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;

      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;
      Comment(StringFormat("TRRegime v1.01 | %s | T=%.3f C=%.3f D=%d ctx=%.2f",
              rs[ri], g_buf_trend[last], g_buf_conf[last], (int)g_buf_dir[last], g_buf_ctx[last]));
   }

   return rates_total;
}

//+------------------------------------------------------------------+
