//+------------------------------------------------------------------+
//| 32_Tester_Result_Chart_Preset_v1_00.mq5                        |
//| Reusable chart preset for reviewing a completed MT5 test.      |
//+------------------------------------------------------------------+
#property copyright "FXおもしろラボ"
#property version   "1.00"
#property strict
#property description "Styles a chart for tester-result review and plots two simple moving averages."

#property indicator_chart_window
#property indicator_buffers 8
#property indicator_plots   8

#property indicator_label1  "Fast SMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrAqua
#property indicator_width1  2
#property indicator_label2  "Slow SMA"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrOrange
#property indicator_width2  2
#property indicator_label3  "Chart checks passed"
#property indicator_type3   DRAW_NONE
#property indicator_label4  "Chart checks total"
#property indicator_type4   DRAW_NONE
#property indicator_label5  "Parameters ready"
#property indicator_type5   DRAW_NONE
#property indicator_label6  "Preset ready"
#property indicator_type6   DRAW_NONE
#property indicator_label7  "No-trading flag"
#property indicator_type7   DRAW_NONE
#property indicator_label8  "Tester mode flag"
#property indicator_type8   DRAW_NONE

input group "1. Review overlays"
input int InpFastPeriod = 20; // 短期SMA期間
input int InpSlowPeriod = 50; // 長期SMA期間

input group "2. Chart preset"
input bool InpShowGrid = false; // グリッドを表示する
input bool InpUseChartShift = true; // 右側に余白を作る

input group "3. Display"
input bool InpShowPanel = true; // プリセット状態を表示する

#define CHART_CHECK_COUNT 7
const string OBJECT_PREFIX = "MQL32_TesterPreset_";

double g_fast_buffer[];
double g_slow_buffer[];
double g_passed_buffer[];
double g_total_buffer[];
double g_parameters_buffer[];
double g_ready_buffer[];
double g_no_trading_buffer[];
double g_tester_buffer[];

bool g_chart_checks[CHART_CHECK_COUNT];
int  g_passed = 0;
bool g_parameters_ready = false;

bool BindBuffers();
bool ApplyChartPreset();
void EvaluateChartPreset();
void InitializeBuffers();
void PublishStatus();
double SimpleAverage(const double &values[], const int shift, const int period, const int available);
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 RuntimeText();

int OnInit()
{
   if(!BindBuffers())
   {
      PrintFormat("Tester result preset: SetIndexBuffer failed, error=%d", GetLastError());
      return INIT_FAILED;
   }

   g_parameters_ready = InpFastPeriod >= 2 && InpFastPeriod <= 500 &&
                        InpSlowPeriod > InpFastPeriod && InpSlowPeriod <= 1000;
   if(!g_parameters_ready)
   {
      Print("Tester result preset: periods must satisfy 2 <= fast < slow <= 1000.");
      return INIT_PARAMETERS_INCORRECT;
   }

   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, InpFastPeriod - 1);
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, InpSlowPeriod - 1);
   for(int plot = 0; plot < 8; ++plot)
      PlotIndexSetDouble(plot, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   IndicatorSetString(
      INDICATOR_SHORTNAME,
      "Tester Result Chart Preset (SMA " + IntegerToString(InpFastPeriod) + "/" + IntegerToString(InpSlowPeriod) + ")"
   );
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);

   if(!ApplyChartPreset())
      PrintFormat("Tester result preset: one or more chart commands failed, error=%d", GetLastError());
   EvaluateChartPreset();
   if(InpShowPanel)
      DrawPanel();
   EventSetTimer(1);
   return INIT_SUCCEEDED;
}

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

void OnTimer()
{
   ApplyChartPreset();
   EvaluateChartPreset();
   PublishStatus();
   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 < InpSlowPeriod)
      return 0;

   ArraySetAsSeries(close, true);
   if(prev_calculated == 0)
      InitializeBuffers();

   int fast_start = rates_total - InpFastPeriod;
   int slow_start = rates_total - InpSlowPeriod;
   if(prev_calculated > 0)
   {
      fast_start = MathMin(fast_start, 1);
      slow_start = MathMin(slow_start, 1);
   }

   for(int shift = fast_start; shift >= 0; --shift)
      g_fast_buffer[shift] = SimpleAverage(close, shift, InpFastPeriod, rates_total);
   for(int shift = slow_start; shift >= 0; --shift)
      g_slow_buffer[shift] = SimpleAverage(close, shift, InpSlowPeriod, rates_total);

   EvaluateChartPreset();
   PublishStatus();
   return rates_total;
}

bool BindBuffers()
{
   bool ok = true;
   if(!SetIndexBuffer(0, g_fast_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(1, g_slow_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(2, g_passed_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(3, g_total_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(4, g_parameters_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(5, g_ready_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(6, g_no_trading_buffer, INDICATOR_DATA)) ok = false;
   if(!SetIndexBuffer(7, g_tester_buffer, INDICATOR_DATA)) ok = false;
   if(!ok)
      return false;

   ArraySetAsSeries(g_fast_buffer, true);
   ArraySetAsSeries(g_slow_buffer, true);
   ArraySetAsSeries(g_passed_buffer, true);
   ArraySetAsSeries(g_total_buffer, true);
   ArraySetAsSeries(g_parameters_buffer, true);
   ArraySetAsSeries(g_ready_buffer, true);
   ArraySetAsSeries(g_no_trading_buffer, true);
   ArraySetAsSeries(g_tester_buffer, true);
   return true;
}

bool ApplyChartPreset()
{
   bool ok = true;
   if(!ChartSetInteger(0, CHART_MODE, CHART_CANDLES)) ok = false;
   if(!ChartSetInteger(0, CHART_AUTOSCROLL, true)) ok = false;
   if(!ChartSetInteger(0, CHART_SHIFT, InpUseChartShift)) ok = false;
   if(InpUseChartShift && !ChartSetDouble(0, CHART_SHIFT_SIZE, 18.0)) ok = false;
   if(!ChartSetInteger(0, CHART_FOREGROUND, false)) ok = false;
   if(!ChartSetInteger(0, CHART_SHOW_GRID, InpShowGrid)) ok = false;
   if(!ChartSetInteger(0, CHART_SHOW_VOLUMES, CHART_VOLUME_HIDE)) ok = false;
   if(!ChartSetInteger(0, CHART_SHOW_TRADE_LEVELS, true)) ok = false;
   if(!ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrBlack)) ok = false;
   if(!ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrWhite)) ok = false;
   if(!ChartSetInteger(0, CHART_COLOR_GRID, clrDimGray)) ok = false;
   if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, clrBlack)) ok = false;
   if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrLime)) ok = false;
   if(!ChartSetInteger(0, CHART_COLOR_CHART_UP, clrLime)) ok = false;
   if(!ChartSetInteger(0, CHART_COLOR_CHART_DOWN, clrLime)) ok = false;
   if(!ChartSetInteger(0, CHART_COLOR_CHART_LINE, clrLime)) ok = false;
   if(!ChartSetInteger(0, CHART_COLOR_BID, clrSilver)) ok = false;
   if(!ChartSetInteger(0, CHART_COLOR_ASK, clrTomato)) ok = false;
   ChartRedraw(0);
   return ok;
}

void EvaluateChartPreset()
{
   g_chart_checks[0] = (color)ChartGetInteger(0, CHART_COLOR_BACKGROUND) == clrBlack;
   g_chart_checks[1] = (bool)ChartGetInteger(0, CHART_SHOW_GRID) == InpShowGrid;
   g_chart_checks[2] = (ENUM_CHART_MODE)ChartGetInteger(0, CHART_MODE) == CHART_CANDLES;
   g_chart_checks[3] = (bool)ChartGetInteger(0, CHART_SHIFT) == InpUseChartShift;
   g_chart_checks[4] = (ENUM_CHART_VOLUME_MODE)ChartGetInteger(0, CHART_SHOW_VOLUMES) == CHART_VOLUME_HIDE;
   g_chart_checks[5] = (color)ChartGetInteger(0, CHART_COLOR_CHART_UP) == clrLime;
   g_chart_checks[6] = (color)ChartGetInteger(0, CHART_COLOR_CHART_DOWN) == clrLime;

   g_passed = 0;
   for(int i = 0; i < CHART_CHECK_COUNT; ++i)
   {
      if(g_chart_checks[i])
         ++g_passed;
   }
}

void InitializeBuffers()
{
   ArrayInitialize(g_fast_buffer, EMPTY_VALUE);
   ArrayInitialize(g_slow_buffer, EMPTY_VALUE);
   ArrayInitialize(g_passed_buffer, EMPTY_VALUE);
   ArrayInitialize(g_total_buffer, EMPTY_VALUE);
   ArrayInitialize(g_parameters_buffer, EMPTY_VALUE);
   ArrayInitialize(g_ready_buffer, EMPTY_VALUE);
   ArrayInitialize(g_no_trading_buffer, EMPTY_VALUE);
   ArrayInitialize(g_tester_buffer, EMPTY_VALUE);
}

void PublishStatus()
{
   if(ArraySize(g_ready_buffer) < 1)
      return;
   g_passed_buffer[0] = (double)g_passed;
   g_total_buffer[0] = (double)CHART_CHECK_COUNT;
   g_parameters_buffer[0] = g_parameters_ready ? 1.0 : 0.0;
   g_ready_buffer[0] = (g_parameters_ready && g_passed == CHART_CHECK_COUNT) ? 1.0 : 0.0;
   g_no_trading_buffer[0] = 1.0;
   g_tester_buffer[0] = (bool)MQLInfoInteger(MQL_TESTER) ? 1.0 : 0.0;
}

double SimpleAverage(const double &values[], const int shift, const int period, const int available)
{
   if(shift < 0 || period < 1 || shift + period > available)
      return EMPTY_VALUE;
   double sum = 0.0;
   for(int i = 0; i < period; ++i)
      sum += values[shift + i];
   return sum / (double)period;
}

void DrawPanel()
{
   const bool ready = g_parameters_ready && g_passed == CHART_CHECK_COUNT;
   const string panel = OBJECT_PREFIX + "Panel";
   if(ObjectFind(0, panel) < 0 && !ObjectCreate(0, panel, OBJ_RECTANGLE_LABEL, 0, 0, 0))
   {
      PrintFormat("Tester result preset: panel creation failed, error=%d", GetLastError());
      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, 590);
   ObjectSetInteger(0, panel, OBJPROP_YSIZE, 250);
   ObjectSetInteger(0, panel, OBJPROP_BGCOLOR, C'7,14,18');
   ObjectSetInteger(0, panel, OBJPROP_COLOR, ready ? clrLime : clrGold);
   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);

   SetLabel("Title", 36, 34, "TESTER RESULT CHART PRESET", clrAqua, 16);
   SetLabel("State", 36, 68, ready ? "PRESET READY" : "CHECK SETTINGS", ready ? clrLime : clrGold, 13);
   SetLabel("Score", 470, 60, IntegerToString(g_passed) + "/" + IntegerToString(CHART_CHECK_COUNT), ready ? clrLime : clrGold, 20);
   SetLabel("Runtime", 36, 102, "RUNTIME  : " + RuntimeText(), clrWhite, 11);
   SetLabel("Overlay", 36, 132, "OVERLAYS : SMA " + IntegerToString(InpFastPeriod) + " / SMA " + IntegerToString(InpSlowPeriod), clrWhite, 11);
   SetLabel("Style", 36, 162, "STYLE    : GREEN ON BLACK / GRID " + (InpShowGrid ? "ON" : "OFF"), clrWhite, 11);
   SetLabel("Template", 36, 192, "NEXT     : SAVE THIS CHART AS tester.tpl", clrGold, 11);
   SetLabel("Footer", 36, 222, "Review display only. This indicator never trades.", clrSilver, 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))
   {
      PrintFormat("Tester result preset: label creation failed name=%s error=%d", name, GetLastError());
      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);
}

string RuntimeText()
{
   if((bool)MQLInfoInteger(MQL_TESTER))
      return (bool)MQLInfoInteger(MQL_VISUAL_MODE) ? "VISUAL TESTER" : "STRATEGY TESTER";
   return "CHART PREVIEW";
}

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