//+------------------------------------------------------------------+
//| 31_Strategy_Spec_Checklist_v1_00.mq5                           |
//| Visual checklist for turning a trading idea into requirements.  |
//+------------------------------------------------------------------+
#property copyright "FXおもしろラボ"
#property version   "1.00"
#property strict
#property description "Checks eight requirement groups before an indicator or EA is implemented."

#property indicator_chart_window
#property indicator_buffers 11
#property indicator_plots   11

#property indicator_label1  "Passed checks"
#property indicator_type1   DRAW_NONE
#property indicator_label2  "Total checks"
#property indicator_type2   DRAW_NONE
#property indicator_label3  "Ready flag"
#property indicator_type3   DRAW_NONE
#property indicator_label4  "Identity check"
#property indicator_type4   DRAW_NONE
#property indicator_label5  "Direction check"
#property indicator_type5   DRAW_NONE
#property indicator_label6  "Signal check"
#property indicator_type6   DRAW_NONE
#property indicator_label7  "Invalidation check"
#property indicator_type7   DRAW_NONE
#property indicator_label8  "Exit target check"
#property indicator_type8   DRAW_NONE
#property indicator_label9  "Risk check"
#property indicator_type9   DRAW_NONE
#property indicator_label10 "Execution check"
#property indicator_type10  DRAW_NONE
#property indicator_label11 "Session check"
#property indicator_type11  DRAW_NONE

enum ENUM_SPEC_DIRECTION
{
   SPEC_DIRECTION_UNDEFINED = 0,
   SPEC_DIRECTION_BOTH = 1,
   SPEC_DIRECTION_LONG_ONLY = 2,
   SPEC_DIRECTION_SHORT_ONLY = 3
};

enum ENUM_SPEC_TRIGGER
{
   SPEC_TRIGGER_UNDEFINED = 0,
   SPEC_TRIGGER_LEVEL = 1,
   SPEC_TRIGGER_CROSS = 2,
   SPEC_TRIGGER_PATTERN = 3,
   SPEC_TRIGGER_BREAKOUT = 4,
   SPEC_TRIGGER_CUSTOM = 5
};

enum ENUM_SPEC_TIMING
{
   SPEC_TIMING_UNDEFINED = 0,
   SPEC_TIMING_CONFIRMED_BAR = 1,
   SPEC_TIMING_EVERY_TICK = 2
};

enum ENUM_SPEC_EXIT_MODE
{
   SPEC_EXIT_UNDEFINED = 0,
   SPEC_EXIT_FIXED_PRICE = 1,
   SPEC_EXIT_INDICATOR = 2,
   SPEC_EXIT_TIME = 3,
   SPEC_EXIT_COMBINED = 4
};

input group "1. Strategy identity"
input string InpStrategyName = "MA Cross Example"; // 戦略名

input group "2. Entry definition"
input ENUM_SPEC_DIRECTION InpDirection = SPEC_DIRECTION_BOTH; // 売買方向
input ENUM_TIMEFRAMES InpSignalTimeframe = PERIOD_H1;          // 判定時間足
input ENUM_SPEC_TRIGGER InpEntryTrigger = SPEC_TRIGGER_CROSS; // エントリー条件
input ENUM_SPEC_TIMING InpSignalTiming = SPEC_TIMING_CONFIRMED_BAR; // 判定タイミング

input group "3. Exit definition"
input ENUM_SPEC_EXIT_MODE InpExitMode = SPEC_EXIT_COMBINED; // 決済方式
input double InpStopLossPips = 20.0;                        // 損失無効化幅
input double InpTakeProfitPips = 40.0;                      // 利益確定幅
input int    InpMaxHoldingBars = 24;                        // 最大保有バー数

input group "4. Risk and execution"
input double InpRiskPercent = 1.0;      // 1回の許容リスク率
input int    InpMaxPositions = 1;       // 最大同時ポジション数
input double InpMaxSpreadPips = 2.0;    // 許容スプレッド

input group "5. Trading hours"
input bool InpUseSession = true; // 時間帯を指定する
input int  InpSessionStartHour = 7;  // 開始時刻（チャート時間）
input int  InpSessionEndHour = 20;   // 終了時刻（チャート時間）

input group "6. Display"
input bool InpShowPanel = true; // チェック結果を表示

#define CHECK_COUNT 8
const string OBJECT_PREFIX = "MQL31_SpecChecklist_";

double g_passed_buffer[];
double g_total_buffer[];
double g_ready_buffer[];
double g_identity_buffer[];
double g_direction_buffer[];
double g_signal_buffer[];
double g_invalidation_buffer[];
double g_target_buffer[];
double g_risk_buffer[];
double g_execution_buffer[];
double g_session_buffer[];

bool g_checks[CHECK_COUNT];
int  g_passed = 0;

void EvaluateRequirements();
void InitializeBuffers();
void PublishBuffers();
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 StatusPrefix(const bool passed);
color StatusColor(const bool passed);
string DirectionText();
string TriggerText();
string TimingText();
string ExitModeText();
string TimeframeText();
string SessionText();

int OnInit()
{
   bool buffers_ok = true;
   if(!SetIndexBuffer(0, g_passed_buffer, INDICATOR_DATA)) buffers_ok = false;
   if(!SetIndexBuffer(1, g_total_buffer, INDICATOR_DATA)) buffers_ok = false;
   if(!SetIndexBuffer(2, g_ready_buffer, INDICATOR_DATA)) buffers_ok = false;
   if(!SetIndexBuffer(3, g_identity_buffer, INDICATOR_DATA)) buffers_ok = false;
   if(!SetIndexBuffer(4, g_direction_buffer, INDICATOR_DATA)) buffers_ok = false;
   if(!SetIndexBuffer(5, g_signal_buffer, INDICATOR_DATA)) buffers_ok = false;
   if(!SetIndexBuffer(6, g_invalidation_buffer, INDICATOR_DATA)) buffers_ok = false;
   if(!SetIndexBuffer(7, g_target_buffer, INDICATOR_DATA)) buffers_ok = false;
   if(!SetIndexBuffer(8, g_risk_buffer, INDICATOR_DATA)) buffers_ok = false;
   if(!SetIndexBuffer(9, g_execution_buffer, INDICATOR_DATA)) buffers_ok = false;
   if(!SetIndexBuffer(10, g_session_buffer, INDICATOR_DATA)) buffers_ok = false;
   if(!buffers_ok)
   {
      PrintFormat("Strategy spec checklist: SetIndexBuffer failed, error=%d", GetLastError());
      return INIT_FAILED;
   }

   ArraySetAsSeries(g_passed_buffer, true);
   ArraySetAsSeries(g_total_buffer, true);
   ArraySetAsSeries(g_ready_buffer, true);
   ArraySetAsSeries(g_identity_buffer, true);
   ArraySetAsSeries(g_direction_buffer, true);
   ArraySetAsSeries(g_signal_buffer, true);
   ArraySetAsSeries(g_invalidation_buffer, true);
   ArraySetAsSeries(g_target_buffer, true);
   ArraySetAsSeries(g_risk_buffer, true);
   ArraySetAsSeries(g_execution_buffer, true);
   ArraySetAsSeries(g_session_buffer, true);
   for(int plot = 0; plot < 11; ++plot)
      PlotIndexSetDouble(plot, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   IndicatorSetString(INDICATOR_SHORTNAME, "Strategy Spec Checklist");
   EvaluateRequirements();
   if(InpShowPanel)
      DrawPanel();
   EventSetTimer(1);
   return INIT_SUCCEEDED;
}

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

void OnTimer()
{
   EvaluateRequirements();
   PublishBuffers();
   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 < 1)
      return 0;
   if(prev_calculated == 0)
      InitializeBuffers();
   EvaluateRequirements();
   PublishBuffers();
   return rates_total;
}

void EvaluateRequirements()
{
   string strategy_name = InpStrategyName;
   StringTrimLeft(strategy_name);
   StringTrimRight(strategy_name);

   g_checks[0] = StringLen(strategy_name) >= 3 && StringLen(strategy_name) <= 63;
   g_checks[1] = InpDirection != SPEC_DIRECTION_UNDEFINED;
   g_checks[2] = InpEntryTrigger != SPEC_TRIGGER_UNDEFINED &&
                 InpSignalTiming != SPEC_TIMING_UNDEFINED &&
                 PeriodSeconds(InpSignalTimeframe) > 0;
   g_checks[3] = InpExitMode != SPEC_EXIT_UNDEFINED &&
                 InpStopLossPips > 0.0 && InpStopLossPips <= 1000.0;
   g_checks[4] = (InpTakeProfitPips > 0.0 && InpTakeProfitPips <= 1000.0) ||
                 (InpMaxHoldingBars > 0 && InpMaxHoldingBars <= 10000);
   g_checks[5] = InpRiskPercent > 0.0 && InpRiskPercent <= 5.0;
   g_checks[6] = InpMaxPositions >= 1 && InpMaxPositions <= 10 &&
                 InpMaxSpreadPips > 0.0 && InpMaxSpreadPips <= 20.0;
   g_checks[7] = !InpUseSession ||
                 (InpSessionStartHour >= 0 && InpSessionStartHour <= 23 &&
                  InpSessionEndHour >= 0 && InpSessionEndHour <= 23 &&
                  InpSessionStartHour != InpSessionEndHour);

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

void InitializeBuffers()
{
   ArrayInitialize(g_passed_buffer, EMPTY_VALUE);
   ArrayInitialize(g_total_buffer, EMPTY_VALUE);
   ArrayInitialize(g_ready_buffer, EMPTY_VALUE);
   ArrayInitialize(g_identity_buffer, EMPTY_VALUE);
   ArrayInitialize(g_direction_buffer, EMPTY_VALUE);
   ArrayInitialize(g_signal_buffer, EMPTY_VALUE);
   ArrayInitialize(g_invalidation_buffer, EMPTY_VALUE);
   ArrayInitialize(g_target_buffer, EMPTY_VALUE);
   ArrayInitialize(g_risk_buffer, EMPTY_VALUE);
   ArrayInitialize(g_execution_buffer, EMPTY_VALUE);
   ArrayInitialize(g_session_buffer, EMPTY_VALUE);
}

void PublishBuffers()
{
   if(ArraySize(g_passed_buffer) < 1)
      return;
   g_passed_buffer[0] = (double)g_passed;
   g_total_buffer[0] = (double)CHECK_COUNT;
   g_ready_buffer[0] = (g_passed == CHECK_COUNT) ? 1.0 : 0.0;
   g_identity_buffer[0] = g_checks[0] ? 1.0 : 0.0;
   g_direction_buffer[0] = g_checks[1] ? 1.0 : 0.0;
   g_signal_buffer[0] = g_checks[2] ? 1.0 : 0.0;
   g_invalidation_buffer[0] = g_checks[3] ? 1.0 : 0.0;
   g_target_buffer[0] = g_checks[4] ? 1.0 : 0.0;
   g_risk_buffer[0] = g_checks[5] ? 1.0 : 0.0;
   g_execution_buffer[0] = g_checks[6] ? 1.0 : 0.0;
   g_session_buffer[0] = g_checks[7] ? 1.0 : 0.0;
}

void DrawPanel()
{
   const bool ready = g_passed == CHECK_COUNT;
   const string panel = OBJECT_PREFIX + "Panel";
   if(ObjectFind(0, panel) < 0 && !ObjectCreate(0, panel, OBJ_RECTANGLE_LABEL, 0, 0, 0))
   {
      PrintFormat("Strategy spec checklist: 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, 790);
   ObjectSetInteger(0, panel, OBJPROP_YSIZE, 390);
   ObjectSetInteger(0, panel, OBJPROP_BGCOLOR, C'7,17,14');
   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, "STRATEGY SPEC CHECKLIST", clrAqua, 16);
   SetLabel("Score", 560, 38, IntegerToString(g_passed) + "/" + IntegerToString(CHECK_COUNT), ready ? clrLime : clrGold, 22);
   SetLabel("State", 36, 65, ready ? "READY TO DISCUSS" : "NEEDS DEFINITION", ready ? clrLime : clrGold, 12);
   SetLabel("Notice", 560, 72, "NO ORDERS", clrSilver, 10);

   SetLabel("Identity", 36, 105, StatusPrefix(g_checks[0]) + "1  Strategy name: " + InpStrategyName, StatusColor(g_checks[0]), 11);
   SetLabel("Direction", 36, 135, StatusPrefix(g_checks[1]) + "2  Direction: " + DirectionText(), StatusColor(g_checks[1]), 11);
   SetLabel("Signal", 36, 165, StatusPrefix(g_checks[2]) + "3  Signal: " + TriggerText() + " / " + TimeframeText() + " / " + TimingText(), StatusColor(g_checks[2]), 11);
   SetLabel("Invalidation", 36, 195, StatusPrefix(g_checks[3]) + "4  Invalidation: " + ExitModeText() + " / SL " + DoubleToString(InpStopLossPips, 1) + " pips", StatusColor(g_checks[3]), 11);
   SetLabel("Target", 36, 225, StatusPrefix(g_checks[4]) + "5  Exit target: TP " + DoubleToString(InpTakeProfitPips, 1) + " pips / max " + IntegerToString(InpMaxHoldingBars) + " bars", StatusColor(g_checks[4]), 11);
   SetLabel("Risk", 36, 255, StatusPrefix(g_checks[5]) + "6  Risk: " + DoubleToString(InpRiskPercent, 1) + "% per trade", StatusColor(g_checks[5]), 11);
   SetLabel("Execution", 36, 285, StatusPrefix(g_checks[6]) + "7  Execution: max positions " + IntegerToString(InpMaxPositions) + " / spread <= " + DoubleToString(InpMaxSpreadPips, 1) + " pips", StatusColor(g_checks[6]), 11);
   SetLabel("Session", 36, 315, StatusPrefix(g_checks[7]) + "8  Trading hours: " + SessionText(), StatusColor(g_checks[7]), 11);
   SetLabel("Footer", 36, 355, "Requirements 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("Strategy spec checklist: 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 StatusPrefix(const bool passed)
{
   return passed ? "[OK]  " : "[--]  ";
}

color StatusColor(const bool passed)
{
   return passed ? clrWhite : clrTomato;
}

string DirectionText()
{
   if(InpDirection == SPEC_DIRECTION_BOTH) return "LONG + SHORT";
   if(InpDirection == SPEC_DIRECTION_LONG_ONLY) return "LONG ONLY";
   if(InpDirection == SPEC_DIRECTION_SHORT_ONLY) return "SHORT ONLY";
   return "UNDEFINED";
}

string TriggerText()
{
   if(InpEntryTrigger == SPEC_TRIGGER_LEVEL) return "LEVEL";
   if(InpEntryTrigger == SPEC_TRIGGER_CROSS) return "CROSS";
   if(InpEntryTrigger == SPEC_TRIGGER_PATTERN) return "PATTERN";
   if(InpEntryTrigger == SPEC_TRIGGER_BREAKOUT) return "BREAKOUT";
   if(InpEntryTrigger == SPEC_TRIGGER_CUSTOM) return "CUSTOM";
   return "UNDEFINED";
}

string TimingText()
{
   if(InpSignalTiming == SPEC_TIMING_CONFIRMED_BAR) return "CONFIRMED BAR";
   if(InpSignalTiming == SPEC_TIMING_EVERY_TICK) return "EVERY TICK";
   return "UNDEFINED";
}

string ExitModeText()
{
   if(InpExitMode == SPEC_EXIT_FIXED_PRICE) return "FIXED PRICE";
   if(InpExitMode == SPEC_EXIT_INDICATOR) return "INDICATOR";
   if(InpExitMode == SPEC_EXIT_TIME) return "TIME";
   if(InpExitMode == SPEC_EXIT_COMBINED) return "COMBINED";
   return "UNDEFINED";
}

string TimeframeText()
{
   string value = EnumToString(InpSignalTimeframe);
   if(StringFind(value, "PERIOD_") == 0)
      value = StringSubstr(value, 7);
   return value;
}

string SessionText()
{
   if(!InpUseSession)
      return "NOT RESTRICTED";
   return StringFormat("%02d:00 - %02d:00 (chart time)", InpSessionStartHour, InpSessionEndHour);
}

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