//+------------------------------------------------------------------+
//| Ind_TrendRangeRegime_v100.mq5                                    |
//| トレンド／レンジ・レジーム判定（P0 MVP）                            |
//| 仕様概要: 本サイトコラム【検証#06】連載を参照                      |
//+------------------------------------------------------------------+
#property copyright "FX Omoshiro Lab"
#property link      "https://fx-omoshiro-lab.com"
#property version   "1.00"
#property strict
#property description "Regime: ADX + 回帰R2 + DI 融合。バッファ0〜5固定（iCustom用）。"
#property indicator_separate_window
#property indicator_buffers 6
#property indicator_plots   3

//--- Plot0: REGIME (0=UNKNOWN,1=RANGE,2=CHOP,3=TREND_UP,4=TREND_DOWN) ヒストグラム
#property indicator_label1  "REGIME"
#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  clrSilver
#property indicator_style1  STYLE_SOLID
#property indicator_width1  3

//--- Plot1: TREND_SCORE 0〜1
#property indicator_label2  "TREND_SCORE"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrDodgerBlue
#property indicator_style2  STYLE_SOLID
#property indicator_width2  2

//--- Plot2: CONFIDENCE 0〜1
#property indicator_label3  "CONFIDENCE"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrOrange
#property indicator_style3  STYLE_SOLID
#property indicator_width3  1

/* iCustom バッファ契約（固定）
   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;   // ADX 期間

input group "===== 閾値 ====="
input double InpTrendScoreHigh  = 0.55; // これ以上でトレンド系
input double InpTrendScoreLow   = 0.38; // これ以下でレンジ寄り
input double InpConfMin         = 0.32; // これ未満は UNKNOWN
input double InpSlopeEps        = 0.0;  // 方向ゼロ判定（価格/バー、0=ATR比で自動）

input group "===== 融合ウェイト ====="
input double InpWADX            = 0.40;
input double InpWR2             = 0.35;
input double InpWDI             = 0.25; // |+DI−−DI| 正規化

input group "===== マーフィー・コンテキスト ====="
input int    InpMurphyLookback  = 4;    // 短期リターン本数（騙し検出）
input int    InpSpreadAvgBars   = 20;   // スプレッド平均本数
input double InpSpreadSpikeMul  = 1.6;  // 平均×これ以上で CONTEXT 低下

input group "===== 表示・挙動 ====="
input bool   InpUseClosedBarOnly = true; // 未確定足は EMPTY_VALUE

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;

//+------------------------------------------------------------------+
double Clamp01(const double x)
{
   if(x < 0.0) return 0.0;
   if(x > 1.0) return 1.0;
   return x;
}

//+------------------------------------------------------------------+
//| 線形回帰（時系列 x=0..n-1, y=価格）傾き・決定係数 R2                |
//+------------------------------------------------------------------+
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;
}

//+------------------------------------------------------------------+
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);

   IndicatorSetString(INDICATOR_SHORTNAME,
      StringFormat("TRRegime(P0) Mic=%d ADX=%d", InpMicroPeriod, InpADXPeriod));
   IndicatorSetInteger(INDICATOR_DIGITS, 3);

   g_hADX = iADX(_Symbol, PERIOD_CURRENT, InpADXPeriod);
   if(g_hADX == INVALID_HANDLE)
   {
      Print("Ind_TrendRangeRegime: iADX failed");
      return INIT_FAILED;
   }

   IndicatorSetDouble(INDICATOR_MINIMUM, 0.0);
   IndicatorSetDouble(INDICATOR_MAXIMUM, 4.0);

   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(g_hADX != INVALID_HANDLE) IndicatorRelease(g_hADX);
}

//+------------------------------------------------------------------+
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(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_score = (InpWADX * adx_n + InpWR2 * r2 + InpWDI * di_agree) / wsum;
      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 = 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 = 0.45;
      }
      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.0 | %s | T=%.3f C=%.3f D=%d",
              rs[ri], g_buf_trend[last], g_buf_conf[last], (int)g_buf_dir[last]));
   }

   return rates_total;
}

//+------------------------------------------------------------------+
