//+------------------------------------------------------------------+
//| 27_OCO_Breakout_State_Simulator_v1_00.mq5                      |
//| Visualizes OCO breakout state transitions without trading.      |
//+------------------------------------------------------------------+
#property copyright "FXおもしろラボ"
#property version   "1.00"
#property strict
#property description "Simulates an OCO breakout pair. It never sends trade requests."

#property indicator_chart_window
#property indicator_buffers 5
#property indicator_plots   5

#property indicator_label1 "Buy Stop Plan"
#property indicator_type1  DRAW_LINE
#property indicator_color1 clrDodgerBlue
#property indicator_width1 2

#property indicator_label2 "Sell Stop Plan"
#property indicator_type2  DRAW_LINE
#property indicator_color2 clrTomato
#property indicator_width2 2

#property indicator_label3 "Buy Trigger"
#property indicator_type3  DRAW_ARROW
#property indicator_color3 clrLimeGreen
#property indicator_width3 2

#property indicator_label4 "Sell Trigger"
#property indicator_type4  DRAW_ARROW
#property indicator_color4 clrOrangeRed
#property indicator_width4 2

#property indicator_label5 "OCO State"
#property indicator_type5  DRAW_NONE

input int    InpLookbackBars      = 72;   // シミュレーション開始位置
input double InpEntryDistancePips = 25.0; // 基準価格から上下への距離
input double InpReferencePrice    = 0.0;  // 0なら開始バー終値
input double InpMarkerOffsetPips  = 4.0;  // トリガーマーカーの距離

double g_buy_line[];
double g_sell_line[];
double g_buy_trigger[];
double g_sell_trigger[];
double g_state[];

double PipSize();

int OnInit()
{
   if(InpLookbackBars < 5 || InpEntryDistancePips <= 0.0 ||
      InpReferencePrice < 0.0 || InpMarkerOffsetPips <= 0.0)
   {
      Print("OCO Breakout State Simulator: invalid input parameters.");
      return INIT_PARAMETERS_INCORRECT;
   }
   if(!SetIndexBuffer(0, g_buy_line, INDICATOR_DATA) ||
      !SetIndexBuffer(1, g_sell_line, INDICATOR_DATA) ||
      !SetIndexBuffer(2, g_buy_trigger, INDICATOR_DATA) ||
      !SetIndexBuffer(3, g_sell_trigger, INDICATOR_DATA) ||
      !SetIndexBuffer(4, g_state, INDICATOR_DATA))
   {
      PrintFormat("OCO Breakout State Simulator: SetIndexBuffer failed, error=%d", GetLastError());
      return INIT_FAILED;
   }
   ArraySetAsSeries(g_buy_line, false);
   ArraySetAsSeries(g_sell_line, false);
   ArraySetAsSeries(g_buy_trigger, false);
   ArraySetAsSeries(g_sell_trigger, false);
   ArraySetAsSeries(g_state, false);
   PlotIndexSetInteger(2, PLOT_ARROW, 233);
   PlotIndexSetInteger(3, PLOT_ARROW, 234);
   for(int plot = 0; plot < 5; ++plot)
      PlotIndexSetDouble(plot, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   IndicatorSetString(
      INDICATOR_SHORTNAME,
      StringFormat(
         "OCO Breakout Simulator (%d bars, %.1f pips, NO TRADING)",
         InpLookbackBars, InpEntryDistancePips
      )
   );
   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 < InpLookbackBars + 2)
      return 0;
   ArraySetAsSeries(high, false);
   ArraySetAsSeries(low, false);
   ArraySetAsSeries(close, false);
   if(prev_calculated == 0)
   {
      ArrayInitialize(g_buy_line, EMPTY_VALUE);
      ArrayInitialize(g_sell_line, EMPTY_VALUE);
      ArrayInitialize(g_buy_trigger, EMPTY_VALUE);
      ArrayInitialize(g_sell_trigger, EMPTY_VALUE);
      ArrayInitialize(g_state, EMPTY_VALUE);
   }

   const int start = rates_total - 1 - InpLookbackBars;
   const int clear_first = MathMax(0, start - 1);
   for(int i = clear_first; i < rates_total; ++i)
   {
      g_buy_line[i] = EMPTY_VALUE;
      g_sell_line[i] = EMPTY_VALUE;
      g_buy_trigger[i] = EMPTY_VALUE;
      g_sell_trigger[i] = EMPTY_VALUE;
      g_state[i] = EMPTY_VALUE;
   }

   const double reference = InpReferencePrice > 0.0 ? InpReferencePrice : close[start];
   const double distance = InpEntryDistancePips * PipSize();
   const double buy_entry = reference + distance;
   const double sell_entry = reference - distance;
   const int last_confirmed = rates_total - 2;
   int state = 0; // 0=armed, 1=buy, -1=sell, 2=same-bar ambiguous
   int trigger = -1;
   for(int i = start + 1; i <= last_confirmed; ++i)
   {
      const bool buy_touched = high[i] >= buy_entry;
      const bool sell_touched = low[i] <= sell_entry;
      if(buy_touched && sell_touched)
         state = 2;
      else if(buy_touched)
         state = 1;
      else if(sell_touched)
         state = -1;
      else
         continue;
      trigger = i;
      break;
   }

   for(int i = start; i < rates_total; ++i)
   {
      const bool after_trigger = trigger >= 0 && i > trigger;
      if(!after_trigger || state == 0 || state == 1)
         g_buy_line[i] = buy_entry;
      if(!after_trigger || state == 0 || state == -1)
         g_sell_line[i] = sell_entry;
      g_state[i] = (double)state;
   }

   if(trigger >= 0)
   {
      const double offset = InpMarkerOffsetPips * PipSize();
      if(state == 1 || state == 2)
         g_buy_trigger[trigger] = high[trigger] + offset;
      if(state == -1 || state == 2)
         g_sell_trigger[trigger] = low[trigger] - offset;
   }
   return rates_total;
}

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