//+------------------------------------------------------------------+
//| 34_Gann_Swing_Oscillator_v1_00.mq5                            |
//| CTSL-style Gann swing state reconstructed as an MQL5 indicator.|
//+------------------------------------------------------------------+
#property copyright "FXおもしろラボ"
#property version   "1.00"
#property strict
#property description "Gann swing oscillator reconstructed from higher-high and lower-low sequences."

#property indicator_separate_window
#property indicator_buffers 12
#property indicator_plots   11
#property indicator_minimum -1.35
#property indicator_maximum  1.35
#property indicator_level1   0.0
#property indicator_levelcolor clrDimGray
#property indicator_levelstyle STYLE_DOT
#property indicator_height 190

#property indicator_label1  "Swing state"
#property indicator_type1   DRAW_COLOR_HISTOGRAM
#property indicator_color1  clrTomato,clrLime
#property indicator_width1  3
#property indicator_label2  "Up turn"
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrAqua
#property indicator_width2  2
#property indicator_label3  "Down turn"
#property indicator_type3   DRAW_ARROW
#property indicator_color3  clrGold
#property indicator_width3  2
#property indicator_label4  "Us bars since"
#property indicator_type4   DRAW_NONE
#property indicator_label5  "Ds bars since"
#property indicator_type5   DRAW_NONE
#property indicator_label6  "Highest since up sequence"
#property indicator_type6   DRAW_NONE
#property indicator_label7  "Lowest since down sequence"
#property indicator_type7   DRAW_NONE
#property indicator_label8  "Sd1 candidate"
#property indicator_type8   DRAW_NONE
#property indicator_label9  "Latest direction"
#property indicator_type9   DRAW_NONE
#property indicator_label10 "Latest turn shift"
#property indicator_type10  DRAW_NONE
#property indicator_label11 "Calculation ready"
#property indicator_type11  DRAW_NONE

input group "Display"
input bool InpShowPanel = true; // CTSL分解パネルを表示する

const double PULSE_LEVEL = 1.15;
const string OBJECT_PREFIX = "MQL34_GannSwing_";

double g_state_buffer[];
double g_color_buffer[];
double g_up_turn_buffer[];
double g_down_turn_buffer[];
double g_us_buffer[];
double g_ds_buffer[];
double g_hc_buffer[];
double g_lc_buffer[];
double g_sd1_buffer[];
double g_latest_direction_buffer[];
double g_latest_shift_buffer[];
double g_ready_buffer[];

int g_latest_direction = 0;
int g_latest_shift = -1;
int g_current_us = -1;
int g_current_ds = -1;
datetime g_latest_time = 0;

bool BindBuffers();
void InitializeBuffers();
void DrawPanel();
void SetLabel(const string suffix, const int x, const int y, const string text, const color text_color, const int font_size);
void DeleteObjects();
string StateText(const int direction);

int OnInit()
{
   if(!BindBuffers())
   {
      PrintFormat("Gann swing oscillator: SetIndexBuffer failed, error=%d", GetLastError());
      return INIT_FAILED;
   }

   PlotIndexSetInteger(1, PLOT_ARROW, 233);
   PlotIndexSetInteger(2, PLOT_ARROW, 234);
   for(int plot = 0; plot < 11; ++plot)
      PlotIndexSetDouble(plot, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   for(int plot = 3; plot < 11; ++plot)
      PlotIndexSetInteger(plot, PLOT_SHOW_DATA, false);
   for(int plot = 0; plot < 3; ++plot)
      PlotIndexSetInteger(plot, PLOT_DRAW_BEGIN, 2);

   IndicatorSetInteger(INDICATOR_DIGITS, 0);
   IndicatorSetString(INDICATOR_SHORTNAME, "Gann Swing Oscillator (CTSL reconstruction)");
   EventSetTimer(1);
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   EventKillTimer();
   DeleteObjects();
}

void OnTimer()
{
   if(InpShowPanel)
      DrawPanel();
   ChartRedraw(0);
}

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 < 10)
      return 0;

   ArraySetAsSeries(time, false);
   ArraySetAsSeries(high, false);
   ArraySetAsSeries(low, false);
   InitializeBuffers();

   bool has_up_sequence = false;
   bool has_down_sequence = false;
   double highest_since_up = 0.0;
   double lowest_since_down = 0.0;
   int bars_since_up = -1;
   int bars_since_down = -1;
   int swing_state = 0;
   int latest_turn_index = -1;
   int latest_turn_direction = 0;

   for(int i = 2; i < rates_total; ++i)
   {
      const bool two_higher_highs = high[i] > high[i - 1] && high[i - 1] > high[i - 2];
      const bool two_lower_lows = low[i] < low[i - 1] && low[i - 1] < low[i - 2];

      if(two_higher_highs)
      {
         has_up_sequence = true;
         bars_since_up = 0;
         highest_since_up = high[i];
      }
      else if(has_up_sequence)
      {
         ++bars_since_up;
         highest_since_up = MathMax(highest_since_up, high[i]);
      }

      if(two_lower_lows)
      {
         has_down_sequence = true;
         bars_since_down = 0;
         lowest_since_down = low[i];
      }
      else if(has_down_sequence)
      {
         ++bars_since_down;
         lowest_since_down = MathMin(lowest_since_down, low[i]);
      }

      g_us_buffer[i] = has_up_sequence ? (double)bars_since_up : -1.0;
      g_ds_buffer[i] = has_down_sequence ? (double)bars_since_down : -1.0;
      g_hc_buffer[i] = has_up_sequence ? highest_since_up : EMPTY_VALUE;
      g_lc_buffer[i] = has_down_sequence ? lowest_since_down : EMPTY_VALUE;

      int candidate = 0;
      if(two_higher_highs && has_down_sequence &&
         low[i] != lowest_since_down && low[i - 1] != lowest_since_down)
      {
         candidate = 1;
      }
      else if(two_lower_lows && has_up_sequence &&
              high[i] != highest_since_up && high[i - 1] != highest_since_up)
      {
         candidate = -1;
      }
      g_sd1_buffer[i] = (double)candidate;

      if(candidate != 0 && candidate != swing_state)
      {
         swing_state = candidate;
         latest_turn_index = i;
         latest_turn_direction = candidate;
         if(candidate > 0)
            g_up_turn_buffer[i] = PULSE_LEVEL;
         else
            g_down_turn_buffer[i] = -PULSE_LEVEL;
      }

      g_state_buffer[i] = (double)swing_state;
      g_color_buffer[i] = (swing_state > 0) ? 1.0 : 0.0;
   }

   const int current_index = rates_total - 1;
   g_latest_direction = latest_turn_direction;
   g_latest_shift = (latest_turn_index >= 0) ? current_index - latest_turn_index : -1;
   g_latest_time = (latest_turn_index >= 0) ? time[latest_turn_index] : 0;
   g_current_us = has_up_sequence ? bars_since_up : -1;
   g_current_ds = has_down_sequence ? bars_since_down : -1;
   g_latest_direction_buffer[current_index] = (double)g_latest_direction;
   g_latest_shift_buffer[current_index] = (double)g_latest_shift;
   g_ready_buffer[current_index] = (latest_turn_index >= 0) ? 1.0 : 0.0;

   if(InpShowPanel)
      DrawPanel();
   return rates_total;
}

bool BindBuffers()
{
   bool ok = true;
   if(!SetIndexBuffer(0, g_state_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(1, g_color_buffer, INDICATOR_COLOR_INDEX)) ok = false;
   if(!SetIndexBuffer(2, g_up_turn_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(3, g_down_turn_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(4, g_us_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(5, g_ds_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(6, g_hc_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(7, g_lc_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(8, g_sd1_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(9, g_latest_direction_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(10, g_latest_shift_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(11, g_ready_buffer, INDICATOR_DATA)) ok = false;
   if(!ok)
      return false;

   ArraySetAsSeries(g_state_buffer, false);
   ArraySetAsSeries(g_color_buffer, false);
   ArraySetAsSeries(g_up_turn_buffer, false);
   ArraySetAsSeries(g_down_turn_buffer, false);
   ArraySetAsSeries(g_us_buffer, false);
   ArraySetAsSeries(g_ds_buffer, false);
   ArraySetAsSeries(g_hc_buffer, false);
   ArraySetAsSeries(g_lc_buffer, false);
   ArraySetAsSeries(g_sd1_buffer, false);
   ArraySetAsSeries(g_latest_direction_buffer, false);
   ArraySetAsSeries(g_latest_shift_buffer, false);
   ArraySetAsSeries(g_ready_buffer, false);
   return true;
}

void InitializeBuffers()
{
   ArrayInitialize(g_state_buffer, 0.0);
   ArrayInitialize(g_color_buffer, 0.0);
   ArrayInitialize(g_up_turn_buffer, EMPTY_VALUE);
   ArrayInitialize(g_down_turn_buffer, EMPTY_VALUE);
   ArrayInitialize(g_us_buffer, -1.0);
   ArrayInitialize(g_ds_buffer, -1.0);
   ArrayInitialize(g_hc_buffer, EMPTY_VALUE);
   ArrayInitialize(g_lc_buffer, EMPTY_VALUE);
   ArrayInitialize(g_sd1_buffer, 0.0);
   ArrayInitialize(g_latest_direction_buffer, EMPTY_VALUE);
   ArrayInitialize(g_latest_shift_buffer, EMPTY_VALUE);
   ArrayInitialize(g_ready_buffer, EMPTY_VALUE);
}

void DrawPanel()
{
   const string panel = OBJECT_PREFIX + "Panel";
   if(ObjectFind(0, panel) < 0 && !ObjectCreate(0, panel, OBJ_RECTANGLE_LABEL, 0, 0, 0))
      return;
   ObjectSetInteger(0, panel, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, panel, OBJPROP_XDISTANCE, 18);
   ObjectSetInteger(0, panel, OBJPROP_YDISTANCE, 20);
   ObjectSetInteger(0, panel, OBJPROP_XSIZE, 820);
   ObjectSetInteger(0, panel, OBJPROP_YSIZE, 255);
   ObjectSetInteger(0, panel, OBJPROP_BGCOLOR, C'7,14,18');
   ObjectSetInteger(0, panel, OBJPROP_COLOR, clrAqua);
   ObjectSetInteger(0, panel, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   ObjectSetInteger(0, panel, OBJPROP_BACK, false);
   ObjectSetInteger(0, panel, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, panel, OBJPROP_HIDDEN, true);

   const string latest = (g_latest_direction == 0)
      ? "NONE"
      : StateText(g_latest_direction) + "  " + TimeToString(g_latest_time, TIME_DATE | TIME_MINUTES);
   SetLabel("Title", 36, 34, "GANN SWING OSCILLATOR  |  CTSL -> MQL5", clrAqua, 15);
   SetLabel("Trigger", 36, 70, "TRIGGER  : 2 HIGHER HIGHS / 2 LOWER LOWS", clrWhite, 11);
   SetLabel("Latest", 36, 100, "LATEST   : " + latest, g_latest_direction == 0 ? clrSilver : clrLime, 11);
   SetLabel("State", 36, 130, "STATE    : " + StateText(g_latest_direction) + "  (+1 / -1 HOLD)", clrWhite, 11);
   SetLabel("Counts", 36, 160, "BARS SINCE: Us=" + IntegerToString(g_current_us) + "  Ds=" + IntegerToString(g_current_ds), clrGold, 11);
   SetLabel("Pipeline", 36, 190, "PIPELINE : Ref -> Sum -> BarsSince -> Highest/Lowest -> ValueWhen", clrSilver, 10);
   SetLabel("Footer", 668, 220, "12 BUFFERS / NO ORDERS", clrLime, 10);
}

void SetLabel(const string suffix, const int x, const int y, const string text, const color text_color, const int font_size)
{
   const string name = OBJECT_PREFIX + suffix;
   if(ObjectFind(0, name) < 0 && !ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0))
      return;
   ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0, name, OBJPROP_COLOR, text_color);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, font_size);
   ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
   ObjectSetString(0, name, OBJPROP_FONT, "Consolas");
   ObjectSetString(0, name, OBJPROP_TEXT, text);
}

void DeleteObjects()
{
   ObjectsDeleteAll(0, OBJECT_PREFIX);
}

string StateText(const int direction)
{
   if(direction > 0) return "UP +1";
   if(direction < 0) return "DOWN -1";
   return "WAITING";
}
